From 8d6d5d624d3c05e544fe72dfa70928d96f120bb5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 17 Sep 2026 21:35:09 +0000 Subject: [PATCH 1/3] snowflake-connector-python: Add versions 4.7.3, 4.7.4 Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- docs/packages/snowflake-connector-python.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/packages/snowflake-connector-python.yaml b/docs/packages/snowflake-connector-python.yaml index 9777767cda..51924d11e3 100644 --- a/docs/packages/snowflake-connector-python.yaml +++ b/docs/packages/snowflake-connector-python.yaml @@ -18,3 +18,5 @@ versions: - filename: snowflake_connector_python-4.7.2-cp314-cp314t-manylinux_2_39_riscv64.whl sha256: 7528a56efc7172b8c4d4d92e727f9619f5a012707af67a7d622acd270e591e63 requires-python: '>=3.10' +- version: 4.7.3 +- version: 4.7.4 From 1eb44924f7ba72f61b3e072104d60a73b22a3417 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Fri, 18 Sep 2026 06:44:50 +0000 Subject: [PATCH 2/3] snowflake-connector-python: Add 4.7.3 and 4.7.4 patches The wiremock patch is rebased: upstream replaced the start retry count with a timeout in seconds, so the 120s budget is set on that instead. --- ...ise-the-server-start-timeout-to-120s.patch | 33 ++++ ...forms-raise-the-generous-timeout-fro.patch | 167 ++++++++++++++++++ ...e-mock_cnt-race-that-test_auth_mfa-s.patch | 76 ++++++++ ...ise-the-server-start-timeout-to-120s.patch | 33 ++++ ...forms-raise-the-generous-timeout-fro.patch | 167 ++++++++++++++++++ ...e-mock_cnt-race-that-test_auth_mfa-s.patch | 76 ++++++++ 6 files changed, 552 insertions(+) create mode 100644 patches/snowflake-connector-python/4.7.3/0001-test-wiremock-raise-the-server-start-timeout-to-120s.patch create mode 100644 patches/snowflake-connector-python/4.7.3/0002-test-detect_platforms-raise-the-generous-timeout-fro.patch create mode 100644 patches/snowflake-connector-python/4.7.3/0003-test-auth-fix-the-mock_cnt-race-that-test_auth_mfa-s.patch create mode 100644 patches/snowflake-connector-python/4.7.4/0001-test-wiremock-raise-the-server-start-timeout-to-120s.patch create mode 100644 patches/snowflake-connector-python/4.7.4/0002-test-detect_platforms-raise-the-generous-timeout-fro.patch create mode 100644 patches/snowflake-connector-python/4.7.4/0003-test-auth-fix-the-mock_cnt-race-that-test_auth_mfa-s.patch diff --git a/patches/snowflake-connector-python/4.7.3/0001-test-wiremock-raise-the-server-start-timeout-to-120s.patch b/patches/snowflake-connector-python/4.7.3/0001-test-wiremock-raise-the-server-start-timeout-to-120s.patch new file mode 100644 index 0000000000..55276dea10 --- /dev/null +++ b/patches/snowflake-connector-python/4.7.3/0001-test-wiremock-raise-the-server-start-timeout-to-120s.patch @@ -0,0 +1,33 @@ +From 9a08b490a7efc884676b5ad0ad8f582b1f596f4f Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Fri, 18 Sep 2026 06:44:38 +0000 +Subject: [PATCH] test: wiremock: raise the server start timeout to 120s + +WiremockClient waits WIREMOCK_START_MAX_RETRY_COUNT seconds (12) for the +standalone server to answer /__admin/health, then fails the test. On the +riscv64 runners a JVM start plus WireMock/Jetty init routinely exceeds that +when several pytest-xdist workers each spin up their own server, so ~10 of +the auth/oauth/redirect tests error out nondeterministically while the rest +of the same suite passes. + +The loop returns as soon as the health check succeeds, so a larger ceiling +costs nothing on faster hardware. + +Upstream-Status: Inappropriate [native runner specific] +--- + test/test_utils/wiremock/wiremock_utils.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/test/test_utils/wiremock/wiremock_utils.py b/test/test_utils/wiremock/wiremock_utils.py +index 0e108b3d..1c487ce4 100644 +--- a/test/test_utils/wiremock/wiremock_utils.py ++++ b/test/test_utils/wiremock/wiremock_utils.py +@@ -16,7 +16,7 @@ except ImportError: + + # Total budget for a Wiremock instance to become usable: the JVM has to boot, + # report the ports it bound and answer the health endpoint within this time. +-WIREMOCK_START_TIMEOUT_SECONDS = 12 ++WIREMOCK_START_TIMEOUT_SECONDS = 120 + WIREMOCK_STOP_TIMEOUT_SECONDS = 10 + # How long to wait between polls while waiting for the startup banner / health. + _WIREMOCK_START_POLL_INTERVAL_SECONDS = 0.1 diff --git a/patches/snowflake-connector-python/4.7.3/0002-test-detect_platforms-raise-the-generous-timeout-fro.patch b/patches/snowflake-connector-python/4.7.3/0002-test-detect_platforms-raise-the-generous-timeout-fro.patch new file mode 100644 index 0000000000..9fa2353cc0 --- /dev/null +++ b/patches/snowflake-connector-python/4.7.3/0002-test-detect_platforms-raise-the-generous-timeout-fro.patch @@ -0,0 +1,167 @@ +From 450b09950bd41f428e1594560a7c70988d94b138 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Fri, 18 Sep 2026 06:44:38 +0000 +Subject: [PATCH] test: detect_platforms: raise the generous timeout from 1s to + 30s + +detect_platforms() runs its six probes in a ThreadPoolExecutor and labels +any that miss platform_detection_timeout_seconds with a "_timeout" +suffix. The tests that want no timeout label at all pass 1 second, with an +inline comment saying the value is only there "to make sure no Thread-based +timeout messes the results". + +On the riscv64 runners 1 second is not generous enough: has_aws_identity +builds a boto3 STS client, and botocore's first service-model load, running +alongside three other pytest-xdist workers, overshoots the budget. The +future is then cancelled and test_no_platforms_detected sees +['has_aws_identity_timeout'] instead of []. The four "not true" cases of +test_platform_detection_disable_env_var_values assert the same empty list +and are exposed the same way. + +csp_helpers patches urllib3's HTTPConnection.request to raise ConnectTimeout +outright, so no probe ever waits on the network and a larger ceiling adds no +wall-clock time anywhere. Sites that deliberately exercise the timeout path +(None, 0, EXPECTED_MAX_TIMEOUT_FOR_PLATFORM_DETECTION) are left alone. + +Upstream-Status: Inappropriate [native runner specific] +--- + test/unit/test_detect_platforms.py | 36 +++++++++++++++--------------- + 1 file changed, 18 insertions(+), 18 deletions(-) + +diff --git a/test/unit/test_detect_platforms.py b/test/unit/test_detect_platforms.py +index b9b2d4f4..66a35420 100644 +--- a/test/unit/test_detect_platforms.py ++++ b/test/unit/test_detect_platforms.py +@@ -69,14 +69,14 @@ class TestDetectPlatforms: + self, unavailable_metadata_service_with_request_exception + ): + result = detect_platforms( +- platform_detection_timeout_seconds=1 ++ platform_detection_timeout_seconds=30 + ) # increase timeout to make sure no Thread-based timeout messes the results + assert result == [] + + def test_ec2_instance_detection( + self, unavailable_metadata_service_with_request_exception, fake_aws_environment + ): +- result = detect_platforms(platform_detection_timeout_seconds=1) ++ result = detect_platforms(platform_detection_timeout_seconds=30) + assert "is_ec2_instance" in result + + def test_aws_lambda_detection( +@@ -84,7 +84,7 @@ class TestDetectPlatforms: + unavailable_metadata_service_with_request_exception, + fake_aws_lambda_environment, + ): +- result = detect_platforms(platform_detection_timeout_seconds=1) ++ result = detect_platforms(platform_detection_timeout_seconds=30) + assert "is_aws_lambda" in result + + @pytest.mark.parametrize( +@@ -104,44 +104,44 @@ class TestDetectPlatforms: + fake_aws_environment, + arn, + ): +- result = detect_platforms(platform_detection_timeout_seconds=1) ++ result = detect_platforms(platform_detection_timeout_seconds=30) + assert "has_aws_identity" in result + + def test_azure_vm_detection(self, fake_azure_vm_metadata_service): +- result = detect_platforms(platform_detection_timeout_seconds=1) ++ result = detect_platforms(platform_detection_timeout_seconds=30) + assert "is_azure_vm" in result + + def test_azure_function_detection(self, fake_azure_function_metadata_service): +- result = detect_platforms(platform_detection_timeout_seconds=1) ++ result = detect_platforms(platform_detection_timeout_seconds=30) + assert "is_azure_function" in result + + def test_azure_function_with_managed_identity( + self, fake_azure_function_metadata_service + ): +- result = detect_platforms(platform_detection_timeout_seconds=1) ++ result = detect_platforms(platform_detection_timeout_seconds=30) + assert "is_azure_function" in result + assert "has_azure_managed_identity" in result + + def test_gce_vm_detection(self, fake_gce_metadata_service): +- result = detect_platforms(platform_detection_timeout_seconds=1) ++ result = detect_platforms(platform_detection_timeout_seconds=30) + assert "is_gce_vm" in result + + def test_gce_cloud_run_service_detection( + self, fake_gce_cloud_run_service_metadata_service + ): +- result = detect_platforms(platform_detection_timeout_seconds=1) ++ result = detect_platforms(platform_detection_timeout_seconds=30) + assert "is_gce_cloud_run_service" in result + + def test_gce_cloud_run_job_detection(self, fake_gce_cloud_run_job_metadata_service): +- result = detect_platforms(platform_detection_timeout_seconds=1) ++ result = detect_platforms(platform_detection_timeout_seconds=30) + assert "is_gce_cloud_run_job" in result + + def test_gcp_identity_detection(self, fake_gce_metadata_service): +- result = detect_platforms(platform_detection_timeout_seconds=1) ++ result = detect_platforms(platform_detection_timeout_seconds=30) + assert "has_gcp_identity" in result + + def test_github_actions_detection(self, fake_github_actions_metadata_service): +- result = detect_platforms(platform_detection_timeout_seconds=1) ++ result = detect_platforms(platform_detection_timeout_seconds=30) + assert "is_github_action" in result + + def test_multiple_platforms_detection( +@@ -150,7 +150,7 @@ class TestDetectPlatforms: + fake_github_actions_metadata_service, + fake_gce_cloud_run_service_metadata_service, + ): +- result = detect_platforms(platform_detection_timeout_seconds=1) ++ result = detect_platforms(platform_detection_timeout_seconds=30) + assert "is_aws_lambda" in result + assert "has_aws_identity" in result + assert "is_github_action" in result +@@ -267,14 +267,14 @@ class TestDetectPlatforms: + arn, + ): + fake_aws_environment.caller_identity = {"Arn": arn} +- result = detect_platforms(platform_detection_timeout_seconds=1) ++ result = detect_platforms(platform_detection_timeout_seconds=30) + assert "has_aws_identity" not in result + + def test_missing_arn_handling( + self, unavailable_metadata_service_with_request_exception, fake_aws_environment + ): + fake_aws_environment.caller_identity = {"UserId": "test-user"} +- result = detect_platforms(platform_detection_timeout_seconds=1) ++ result = detect_platforms(platform_detection_timeout_seconds=30) + assert "has_aws_identity" not in result + + def test_azure_managed_identity_no_token_endpoint( +@@ -294,7 +294,7 @@ class TestDetectPlatforms: + self, unavailable_metadata_service_with_request_exception, fake_aws_environment + ): + fake_aws_environment.instance_document = b"" +- result = detect_platforms(platform_detection_timeout_seconds=1) ++ result = detect_platforms(platform_detection_timeout_seconds=30) + assert "is_ec2_instance" not in result + + def test_aws_lambda_empty_task_root( +@@ -373,7 +373,7 @@ class TestDetectPlatforms: + ): + """Test that ENV_VAR_DISABLE_PLATFORM_DETECTION only disables when set to 'true' (case-insensitive)""" + with patch.dict(os.environ, {ENV_VAR_DISABLE_PLATFORM_DETECTION: env_value}): +- result = detect_platforms(platform_detection_timeout_seconds=1) ++ result = detect_platforms(platform_detection_timeout_seconds=30) + assert result == expected_result + + def test_platform_detection_disabled_overrides_all_other_detection( +@@ -384,7 +384,7 @@ class TestDetectPlatforms: + ): + """Test that ENV_VAR_DISABLE_PLATFORM_DETECTION takes precedence over all detections""" + with patch.dict(os.environ, {ENV_VAR_DISABLE_PLATFORM_DETECTION: "true"}): +- result = detect_platforms(platform_detection_timeout_seconds=1) ++ result = detect_platforms(platform_detection_timeout_seconds=30) + assert result == _PLATFORM_DETECTION_DISABLED_RESULT + assert "is_aws_lambda" not in result + assert "is_github_action" not in result diff --git a/patches/snowflake-connector-python/4.7.3/0003-test-auth-fix-the-mock_cnt-race-that-test_auth_mfa-s.patch b/patches/snowflake-connector-python/4.7.3/0003-test-auth-fix-the-mock_cnt-race-that-test_auth_mfa-s.patch new file mode 100644 index 0000000000..aaa2f56930 --- /dev/null +++ b/patches/snowflake-connector-python/4.7.3/0003-test-auth-fix-the-mock_cnt-race-that-test_auth_mfa-s.patch @@ -0,0 +1,76 @@ +From 4cad97079ae16e3d32525884fa741aeb413d5808 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Fri, 18 Sep 2026 06:44:38 +0000 +Subject: [PATCH] test: auth: fix the mock_cnt race that test_auth_mfa's + timeout case leaves behind + +Auth.authenticate() runs the MFA wait in a daemon Thread and gives up on it +with t.join(timeout=timeout), so the request mock keeps running after the +call returns. test_auth_mfa's third case relies on exactly that: the mock's +mock_cnt == 1 branch sleeps 10 seconds, the test passes timeout=1, and the +join returns while that thread is still sleeping. + +The abandoned thread then falls through to `mock_cnt += 1` at the bottom of +the mock, roughly nine seconds later. By that time the fourth case has +already set `mock_cnt = 2` to select the "data is None" response, so the +stray increment turns it into 3, the mock returns the empty else-branch +dict, and Auth.authenticate() raises KeyError: 'data' instead of the +snowflake.connector.errors.Error the test expects. + +Whether the increment lands inside the fourth case is pure timing. It never +does on a fast machine, which is why this only shows up where the fourth +case's setup takes longer than the rest of the sleep. It is the same race +the test is already skipped for on Windows. + +Read and advance mock_cnt in one step at the top of the mock instead, before +the sleep, so a call's counter effect is complete before the caller can walk +away from it. Branch selection is unchanged: calls still see 0, 1, 2, ... in +order. + +Reproducible anywhere by inserting `time.sleep(11)` after the fourth case's +`mock_cnt = 2`: KeyError: 'data' before this change, passing after. + +Upstream-Status: To upstream [not submitted yet; the race is upstream's own, not riscv64-specific -- the existing IS_WINDOWS skip documents it] +--- + test/unit/test_auth.py | 12 ++++++++---- + 1 file changed, 8 insertions(+), 4 deletions(-) + +diff --git a/test/unit/test_auth.py b/test/unit/test_auth.py +index fdb2a998..7b3fbe63 100644 +--- a/test/unit/test_auth.py ++++ b/test/unit/test_auth.py +@@ -132,7 +132,12 @@ def _mock_auth_mfa_rest_response_timeout(url, headers, body, **kwargs): + _ = headers + _ = body + _ = kwargs.get("dummy") +- if mock_cnt == 0: ++ # Advance the shared counter up front: the sleeping branch below is reached ++ # from a daemon thread that Auth.authenticate() abandons on timeout, and a ++ # post-sleep increment lands after the caller has reset mock_cnt for the ++ # next case. ++ call_cnt, mock_cnt = mock_cnt, mock_cnt + 1 ++ if call_cnt == 0: + ret = { + "success": True, + "message": None, +@@ -141,10 +146,10 @@ def _mock_auth_mfa_rest_response_timeout(url, headers, body, **kwargs): + "inFlightCtx": "inFlightCtx", + }, + } +- elif mock_cnt == 1: ++ elif call_cnt == 1: + time.sleep(10) # should timeout while here + ret = {} +- elif mock_cnt == 2: ++ elif call_cnt == 2: + ret = { + "success": True, + "message": None, +@@ -153,7 +158,6 @@ def _mock_auth_mfa_rest_response_timeout(url, headers, body, **kwargs): + else: + ret = {} + +- mock_cnt += 1 + return ret + + diff --git a/patches/snowflake-connector-python/4.7.4/0001-test-wiremock-raise-the-server-start-timeout-to-120s.patch b/patches/snowflake-connector-python/4.7.4/0001-test-wiremock-raise-the-server-start-timeout-to-120s.patch new file mode 100644 index 0000000000..55276dea10 --- /dev/null +++ b/patches/snowflake-connector-python/4.7.4/0001-test-wiremock-raise-the-server-start-timeout-to-120s.patch @@ -0,0 +1,33 @@ +From 9a08b490a7efc884676b5ad0ad8f582b1f596f4f Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Fri, 18 Sep 2026 06:44:38 +0000 +Subject: [PATCH] test: wiremock: raise the server start timeout to 120s + +WiremockClient waits WIREMOCK_START_MAX_RETRY_COUNT seconds (12) for the +standalone server to answer /__admin/health, then fails the test. On the +riscv64 runners a JVM start plus WireMock/Jetty init routinely exceeds that +when several pytest-xdist workers each spin up their own server, so ~10 of +the auth/oauth/redirect tests error out nondeterministically while the rest +of the same suite passes. + +The loop returns as soon as the health check succeeds, so a larger ceiling +costs nothing on faster hardware. + +Upstream-Status: Inappropriate [native runner specific] +--- + test/test_utils/wiremock/wiremock_utils.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/test/test_utils/wiremock/wiremock_utils.py b/test/test_utils/wiremock/wiremock_utils.py +index 0e108b3d..1c487ce4 100644 +--- a/test/test_utils/wiremock/wiremock_utils.py ++++ b/test/test_utils/wiremock/wiremock_utils.py +@@ -16,7 +16,7 @@ except ImportError: + + # Total budget for a Wiremock instance to become usable: the JVM has to boot, + # report the ports it bound and answer the health endpoint within this time. +-WIREMOCK_START_TIMEOUT_SECONDS = 12 ++WIREMOCK_START_TIMEOUT_SECONDS = 120 + WIREMOCK_STOP_TIMEOUT_SECONDS = 10 + # How long to wait between polls while waiting for the startup banner / health. + _WIREMOCK_START_POLL_INTERVAL_SECONDS = 0.1 diff --git a/patches/snowflake-connector-python/4.7.4/0002-test-detect_platforms-raise-the-generous-timeout-fro.patch b/patches/snowflake-connector-python/4.7.4/0002-test-detect_platforms-raise-the-generous-timeout-fro.patch new file mode 100644 index 0000000000..9fa2353cc0 --- /dev/null +++ b/patches/snowflake-connector-python/4.7.4/0002-test-detect_platforms-raise-the-generous-timeout-fro.patch @@ -0,0 +1,167 @@ +From 450b09950bd41f428e1594560a7c70988d94b138 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Fri, 18 Sep 2026 06:44:38 +0000 +Subject: [PATCH] test: detect_platforms: raise the generous timeout from 1s to + 30s + +detect_platforms() runs its six probes in a ThreadPoolExecutor and labels +any that miss platform_detection_timeout_seconds with a "_timeout" +suffix. The tests that want no timeout label at all pass 1 second, with an +inline comment saying the value is only there "to make sure no Thread-based +timeout messes the results". + +On the riscv64 runners 1 second is not generous enough: has_aws_identity +builds a boto3 STS client, and botocore's first service-model load, running +alongside three other pytest-xdist workers, overshoots the budget. The +future is then cancelled and test_no_platforms_detected sees +['has_aws_identity_timeout'] instead of []. The four "not true" cases of +test_platform_detection_disable_env_var_values assert the same empty list +and are exposed the same way. + +csp_helpers patches urllib3's HTTPConnection.request to raise ConnectTimeout +outright, so no probe ever waits on the network and a larger ceiling adds no +wall-clock time anywhere. Sites that deliberately exercise the timeout path +(None, 0, EXPECTED_MAX_TIMEOUT_FOR_PLATFORM_DETECTION) are left alone. + +Upstream-Status: Inappropriate [native runner specific] +--- + test/unit/test_detect_platforms.py | 36 +++++++++++++++--------------- + 1 file changed, 18 insertions(+), 18 deletions(-) + +diff --git a/test/unit/test_detect_platforms.py b/test/unit/test_detect_platforms.py +index b9b2d4f4..66a35420 100644 +--- a/test/unit/test_detect_platforms.py ++++ b/test/unit/test_detect_platforms.py +@@ -69,14 +69,14 @@ class TestDetectPlatforms: + self, unavailable_metadata_service_with_request_exception + ): + result = detect_platforms( +- platform_detection_timeout_seconds=1 ++ platform_detection_timeout_seconds=30 + ) # increase timeout to make sure no Thread-based timeout messes the results + assert result == [] + + def test_ec2_instance_detection( + self, unavailable_metadata_service_with_request_exception, fake_aws_environment + ): +- result = detect_platforms(platform_detection_timeout_seconds=1) ++ result = detect_platforms(platform_detection_timeout_seconds=30) + assert "is_ec2_instance" in result + + def test_aws_lambda_detection( +@@ -84,7 +84,7 @@ class TestDetectPlatforms: + unavailable_metadata_service_with_request_exception, + fake_aws_lambda_environment, + ): +- result = detect_platforms(platform_detection_timeout_seconds=1) ++ result = detect_platforms(platform_detection_timeout_seconds=30) + assert "is_aws_lambda" in result + + @pytest.mark.parametrize( +@@ -104,44 +104,44 @@ class TestDetectPlatforms: + fake_aws_environment, + arn, + ): +- result = detect_platforms(platform_detection_timeout_seconds=1) ++ result = detect_platforms(platform_detection_timeout_seconds=30) + assert "has_aws_identity" in result + + def test_azure_vm_detection(self, fake_azure_vm_metadata_service): +- result = detect_platforms(platform_detection_timeout_seconds=1) ++ result = detect_platforms(platform_detection_timeout_seconds=30) + assert "is_azure_vm" in result + + def test_azure_function_detection(self, fake_azure_function_metadata_service): +- result = detect_platforms(platform_detection_timeout_seconds=1) ++ result = detect_platforms(platform_detection_timeout_seconds=30) + assert "is_azure_function" in result + + def test_azure_function_with_managed_identity( + self, fake_azure_function_metadata_service + ): +- result = detect_platforms(platform_detection_timeout_seconds=1) ++ result = detect_platforms(platform_detection_timeout_seconds=30) + assert "is_azure_function" in result + assert "has_azure_managed_identity" in result + + def test_gce_vm_detection(self, fake_gce_metadata_service): +- result = detect_platforms(platform_detection_timeout_seconds=1) ++ result = detect_platforms(platform_detection_timeout_seconds=30) + assert "is_gce_vm" in result + + def test_gce_cloud_run_service_detection( + self, fake_gce_cloud_run_service_metadata_service + ): +- result = detect_platforms(platform_detection_timeout_seconds=1) ++ result = detect_platforms(platform_detection_timeout_seconds=30) + assert "is_gce_cloud_run_service" in result + + def test_gce_cloud_run_job_detection(self, fake_gce_cloud_run_job_metadata_service): +- result = detect_platforms(platform_detection_timeout_seconds=1) ++ result = detect_platforms(platform_detection_timeout_seconds=30) + assert "is_gce_cloud_run_job" in result + + def test_gcp_identity_detection(self, fake_gce_metadata_service): +- result = detect_platforms(platform_detection_timeout_seconds=1) ++ result = detect_platforms(platform_detection_timeout_seconds=30) + assert "has_gcp_identity" in result + + def test_github_actions_detection(self, fake_github_actions_metadata_service): +- result = detect_platforms(platform_detection_timeout_seconds=1) ++ result = detect_platforms(platform_detection_timeout_seconds=30) + assert "is_github_action" in result + + def test_multiple_platforms_detection( +@@ -150,7 +150,7 @@ class TestDetectPlatforms: + fake_github_actions_metadata_service, + fake_gce_cloud_run_service_metadata_service, + ): +- result = detect_platforms(platform_detection_timeout_seconds=1) ++ result = detect_platforms(platform_detection_timeout_seconds=30) + assert "is_aws_lambda" in result + assert "has_aws_identity" in result + assert "is_github_action" in result +@@ -267,14 +267,14 @@ class TestDetectPlatforms: + arn, + ): + fake_aws_environment.caller_identity = {"Arn": arn} +- result = detect_platforms(platform_detection_timeout_seconds=1) ++ result = detect_platforms(platform_detection_timeout_seconds=30) + assert "has_aws_identity" not in result + + def test_missing_arn_handling( + self, unavailable_metadata_service_with_request_exception, fake_aws_environment + ): + fake_aws_environment.caller_identity = {"UserId": "test-user"} +- result = detect_platforms(platform_detection_timeout_seconds=1) ++ result = detect_platforms(platform_detection_timeout_seconds=30) + assert "has_aws_identity" not in result + + def test_azure_managed_identity_no_token_endpoint( +@@ -294,7 +294,7 @@ class TestDetectPlatforms: + self, unavailable_metadata_service_with_request_exception, fake_aws_environment + ): + fake_aws_environment.instance_document = b"" +- result = detect_platforms(platform_detection_timeout_seconds=1) ++ result = detect_platforms(platform_detection_timeout_seconds=30) + assert "is_ec2_instance" not in result + + def test_aws_lambda_empty_task_root( +@@ -373,7 +373,7 @@ class TestDetectPlatforms: + ): + """Test that ENV_VAR_DISABLE_PLATFORM_DETECTION only disables when set to 'true' (case-insensitive)""" + with patch.dict(os.environ, {ENV_VAR_DISABLE_PLATFORM_DETECTION: env_value}): +- result = detect_platforms(platform_detection_timeout_seconds=1) ++ result = detect_platforms(platform_detection_timeout_seconds=30) + assert result == expected_result + + def test_platform_detection_disabled_overrides_all_other_detection( +@@ -384,7 +384,7 @@ class TestDetectPlatforms: + ): + """Test that ENV_VAR_DISABLE_PLATFORM_DETECTION takes precedence over all detections""" + with patch.dict(os.environ, {ENV_VAR_DISABLE_PLATFORM_DETECTION: "true"}): +- result = detect_platforms(platform_detection_timeout_seconds=1) ++ result = detect_platforms(platform_detection_timeout_seconds=30) + assert result == _PLATFORM_DETECTION_DISABLED_RESULT + assert "is_aws_lambda" not in result + assert "is_github_action" not in result diff --git a/patches/snowflake-connector-python/4.7.4/0003-test-auth-fix-the-mock_cnt-race-that-test_auth_mfa-s.patch b/patches/snowflake-connector-python/4.7.4/0003-test-auth-fix-the-mock_cnt-race-that-test_auth_mfa-s.patch new file mode 100644 index 0000000000..aaa2f56930 --- /dev/null +++ b/patches/snowflake-connector-python/4.7.4/0003-test-auth-fix-the-mock_cnt-race-that-test_auth_mfa-s.patch @@ -0,0 +1,76 @@ +From 4cad97079ae16e3d32525884fa741aeb413d5808 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Fri, 18 Sep 2026 06:44:38 +0000 +Subject: [PATCH] test: auth: fix the mock_cnt race that test_auth_mfa's + timeout case leaves behind + +Auth.authenticate() runs the MFA wait in a daemon Thread and gives up on it +with t.join(timeout=timeout), so the request mock keeps running after the +call returns. test_auth_mfa's third case relies on exactly that: the mock's +mock_cnt == 1 branch sleeps 10 seconds, the test passes timeout=1, and the +join returns while that thread is still sleeping. + +The abandoned thread then falls through to `mock_cnt += 1` at the bottom of +the mock, roughly nine seconds later. By that time the fourth case has +already set `mock_cnt = 2` to select the "data is None" response, so the +stray increment turns it into 3, the mock returns the empty else-branch +dict, and Auth.authenticate() raises KeyError: 'data' instead of the +snowflake.connector.errors.Error the test expects. + +Whether the increment lands inside the fourth case is pure timing. It never +does on a fast machine, which is why this only shows up where the fourth +case's setup takes longer than the rest of the sleep. It is the same race +the test is already skipped for on Windows. + +Read and advance mock_cnt in one step at the top of the mock instead, before +the sleep, so a call's counter effect is complete before the caller can walk +away from it. Branch selection is unchanged: calls still see 0, 1, 2, ... in +order. + +Reproducible anywhere by inserting `time.sleep(11)` after the fourth case's +`mock_cnt = 2`: KeyError: 'data' before this change, passing after. + +Upstream-Status: To upstream [not submitted yet; the race is upstream's own, not riscv64-specific -- the existing IS_WINDOWS skip documents it] +--- + test/unit/test_auth.py | 12 ++++++++---- + 1 file changed, 8 insertions(+), 4 deletions(-) + +diff --git a/test/unit/test_auth.py b/test/unit/test_auth.py +index fdb2a998..7b3fbe63 100644 +--- a/test/unit/test_auth.py ++++ b/test/unit/test_auth.py +@@ -132,7 +132,12 @@ def _mock_auth_mfa_rest_response_timeout(url, headers, body, **kwargs): + _ = headers + _ = body + _ = kwargs.get("dummy") +- if mock_cnt == 0: ++ # Advance the shared counter up front: the sleeping branch below is reached ++ # from a daemon thread that Auth.authenticate() abandons on timeout, and a ++ # post-sleep increment lands after the caller has reset mock_cnt for the ++ # next case. ++ call_cnt, mock_cnt = mock_cnt, mock_cnt + 1 ++ if call_cnt == 0: + ret = { + "success": True, + "message": None, +@@ -141,10 +146,10 @@ def _mock_auth_mfa_rest_response_timeout(url, headers, body, **kwargs): + "inFlightCtx": "inFlightCtx", + }, + } +- elif mock_cnt == 1: ++ elif call_cnt == 1: + time.sleep(10) # should timeout while here + ret = {} +- elif mock_cnt == 2: ++ elif call_cnt == 2: + ret = { + "success": True, + "message": None, +@@ -153,7 +158,6 @@ def _mock_auth_mfa_rest_response_timeout(url, headers, body, **kwargs): + else: + ret = {} + +- mock_cnt += 1 + return ret + + From 3499afbefa74575d4f45a38ddee071119fab5fe8 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Fri, 18 Sep 2026 17:10:48 +0000 Subject: [PATCH 3/3] snowflake-connector-python: fix matrix collapse and cp314t curve test The bare `include:` python list shared no key with the `version` matrix axis, so each entry merged into every combination in order and the last one (cp314t) silently overwrote matrix.python for every leg -- only cp314t ever built, for every version, since the version-axis refactor (#1937). Restore a real `python:` axis alongside it. That surfaced the actual cp314t (and now cp312/cp313/cp314) failure: test_auth_keypair_ecdsa_unsupported_curve generates a SECP192R1 key as test setup, and our riscv64 cryptography wheel's OpenSSL build has no legacy provider, so cryptography itself raises UnsupportedAlgorithm before the connector's own curve check ever runs. Deselect it. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_011RwtHNpuiuiCk4MstTLu8m --- .../workflows/build-snowflake-connector-python.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-snowflake-connector-python.yml b/.github/workflows/build-snowflake-connector-python.yml index ae217844ab..39bb197e22 100644 --- a/.github/workflows/build-snowflake-connector-python.yml +++ b/.github/workflows/build-snowflake-connector-python.yml @@ -53,6 +53,11 @@ jobs: matrix: version: ${{ fromJSON(needs.setup.outputs.versions) }} # Per-interpreter (not abi3): the Cython extension links the version-specific ABI. + python: ["cp312", "cp313", "cp314", "cp314t"] + # `include` entries that share no key with another axis merge into every + # combination in order, so without the `python` axis above the last entry + # here (cp314t) silently overwrote matrix.python for every version and the + # cp312/cp313/cp314 legs never ran. include: - python: "cp312" pytest_dist: "-n auto --dist loadfile" @@ -133,7 +138,10 @@ jobs: # The first three -k names need a native minicore blob, which upstream # ships for eight platforms but not riscv64; the last five chmod a path # and expect the EACCES that root - which cibuildwheel runs the tests - # as - never gets. + # as - never gets. test_auth_keypair_ecdsa_unsupported_curve generates a + # SECP192R1 key as test setup; our riscv64 cryptography wheel's OpenSSL + # build has no legacy provider, so cryptography itself raises + # UnsupportedAlgorithm before the connector's own curve check runs. CIBW_TEST_COMMAND: >- python -c "from snowflake.connector.nanoarrow_arrow_iterator import PyArrowRowIterator" && pytest -m "unit and not sso and not pandas and not lambda and not aio" @@ -144,7 +152,8 @@ jobs: and not test_log_debug_config_file_parent_dir_permissions and not test_read_only and not test_config_file_inaccessible_path - and not test_put_error" + and not test_put_error + and not test_auth_keypair_ecdsa_unsupported_curve" ${{ matrix.pytest_dist }} --ignore {package}/test/unit/aio --ignore {package}/test/unit/test_ocsp.py