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/5] 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 b1ffce080d00981f257c074605d2de5f6757666d Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Fri, 18 Sep 2026 06:44:50 +0000 Subject: [PATCH 2/5] 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 1d2ed74022f7fde37b6b42a72c9652a07b0b2ed1 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Fri, 18 Sep 2026 17:10:48 +0000 Subject: [PATCH 3/5] 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. --- .../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 From 4ca3f4f7caadd3dfd9e867e90086fee631081f69 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Sat, 19 Sep 2026 11:28:55 +0000 Subject: [PATCH 4/5] snowflake-connector-python: raise the get_application_path timing threshold Build 4.7.4 cp312 failed: test_get_application_path_is_fast_on_deep_call_stacks asserts the average wall-clock time of get_application_path() at a 150-frame-deep call stack stays under a hardcoded 1.0ms, guarding against a regression to an inspect.stack()-based implementation that reads every frame's source file (GH-2908 / SNOW-3691001). On this repo's shared riscv64 runners the same, non-regressed frame-walking implementation averaged 2.286ms in the failing job's log, 2.3x the threshold -- plain per-instruction cost on this architecture plus load from other concurrent matrix jobs on the same shared runner pool, not a code regression. The upstream test file is byte-identical between the 4.7.3 and 4.7.4 tags, so the same patch (0004, raising the ceiling to 10ms, well above the one observed sample) applies to both pending versions. Build 4.7.3 cp313 and Build 4.7.4 cp314 also failed on this run, but their job logs are unavailable everywhere I checked (GitHub API, raw blob storage, and the full run-log zip all 404/omit them), and get_workflow_job shows their "Build wheels" step stuck at status=in_progress with every later step left pending -- unlike Build 4.7.4 cp312's clean pytest failure and full log. That signature, plus both runs finishing in ~10-11.5 minutes against 17-30 minutes for every successful leg, points to a riscv64 runner crash/disconnect rather than a code-level failure. I triggered a safe rerun_failed_jobs (no force-push) to get fresh logs for those two, but the runner pool stayed queued for over an hour with no job starting, so this could not be confirmed. No fix is applied for those two failures; they likely just need CI to retry once the runner pool clears. Upstream-Status: Inappropriate [riscv64 runner is slower per-instruction than upstream's CI hosts, and shared with concurrent matrix jobs; the 1ms threshold assumes faster/dedicated hardware] --- ...the-get_application_path-timing-regr.patch | 41 +++++++++++++++++++ ...the-get_application_path-timing-regr.patch | 41 +++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 patches/snowflake-connector-python/4.7.3/0004-test-util-raise-the-get_application_path-timing-regr.patch create mode 100644 patches/snowflake-connector-python/4.7.4/0004-test-util-raise-the-get_application_path-timing-regr.patch diff --git a/patches/snowflake-connector-python/4.7.3/0004-test-util-raise-the-get_application_path-timing-regr.patch b/patches/snowflake-connector-python/4.7.3/0004-test-util-raise-the-get_application_path-timing-regr.patch new file mode 100644 index 0000000000..e26d2a96b6 --- /dev/null +++ b/patches/snowflake-connector-python/4.7.3/0004-test-util-raise-the-get_application_path-timing-regr.patch @@ -0,0 +1,41 @@ +From 8bcd734d265ee8fee0813631bc36509c8fbfc80c Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Sat, 19 Sep 2026 11:27:54 +0000 +Subject: [PATCH] test: util: raise the get_application_path timing-regression + threshold from 1ms to 10ms + +test_get_application_path_is_fast_on_deep_call_stacks asserts the average +wall-clock time of get_application_path() at a 150-frame-deep call stack +stays under a hardcoded 1.0ms, guarding against a regression to an +inspect.stack()-based implementation that reads every frame's source file +(GH-2908 / SNOW-3691001). + +On this repo's shared riscv64 runners the same frame-walking implementation +averages 2.286ms (observed on Build 4.7.4 cp312), 2.3x the threshold, with +no regression to the slow inspect.stack() path: it is plain per-instruction +cost on this architecture plus load from other concurrent matrix jobs on +the same shared runner pool. Raise the ceiling to 10ms, well above the one +observed sample, while still catching the ~100-500ms regression the test +was written to guard against. + +Upstream-Status: Inappropriate [riscv64 runner is slower per-instruction than upstream's CI hosts, and shared with concurrent matrix jobs; the 1ms threshold assumes faster/dedicated hardware] +--- + test/unit/test_util.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/test/unit/test_util.py b/test/unit/test_util.py +index eb99987..bab07c5 100644 +--- a/test/unit/test_util.py ++++ b/test/unit/test_util.py +@@ -34,7 +34,7 @@ def test_get_application_path_is_fast_on_deep_call_stacks(): + """ + depth = 150 + n_runs = 5 +- threshold_ms = 1.0 ++ threshold_ms = 10.0 + + def recurse(n): + if n == 0: +-- +2.43.0 + diff --git a/patches/snowflake-connector-python/4.7.4/0004-test-util-raise-the-get_application_path-timing-regr.patch b/patches/snowflake-connector-python/4.7.4/0004-test-util-raise-the-get_application_path-timing-regr.patch new file mode 100644 index 0000000000..85bcf405b6 --- /dev/null +++ b/patches/snowflake-connector-python/4.7.4/0004-test-util-raise-the-get_application_path-timing-regr.patch @@ -0,0 +1,41 @@ +From e861774346e0b2cd63cf6a48fc204fcfa829ad87 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Sat, 19 Sep 2026 11:28:05 +0000 +Subject: [PATCH] test: util: raise the get_application_path timing-regression + threshold from 1ms to 10ms + +test_get_application_path_is_fast_on_deep_call_stacks asserts the average +wall-clock time of get_application_path() at a 150-frame-deep call stack +stays under a hardcoded 1.0ms, guarding against a regression to an +inspect.stack()-based implementation that reads every frame's source file +(GH-2908 / SNOW-3691001). + +On this repo's shared riscv64 runners the same frame-walking implementation +averages 2.286ms (observed on Build 4.7.4 cp312), 2.3x the threshold, with +no regression to the slow inspect.stack() path: it is plain per-instruction +cost on this architecture plus load from other concurrent matrix jobs on +the same shared runner pool. Raise the ceiling to 10ms, well above the one +observed sample, while still catching the ~100-500ms regression the test +was written to guard against. + +Upstream-Status: Inappropriate [riscv64 runner is slower per-instruction than upstream's CI hosts, and shared with concurrent matrix jobs; the 1ms threshold assumes faster/dedicated hardware] +--- + test/unit/test_util.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/test/unit/test_util.py b/test/unit/test_util.py +index eb99987..bab07c5 100644 +--- a/test/unit/test_util.py ++++ b/test/unit/test_util.py +@@ -34,7 +34,7 @@ def test_get_application_path_is_fast_on_deep_call_stacks(): + """ + depth = 150 + n_runs = 5 +- threshold_ms = 1.0 ++ threshold_ms = 10.0 + + def recurse(n): + if n == 0: +-- +2.43.0 + From e80d8135cea803452bea22edf4c968965a03134c Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Sat, 19 Sep 2026 16:54:25 +0000 Subject: [PATCH 5/5] snowflake-connector-python: fix mtime race in test_file_is_not_updated MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test_file_is_not_updated (test/unit/test_cache.py) writes the cache file three times and asserts os.path.getmtime() strictly advances after each write. Before the second write-then-assert-greater pair the test already sleeps 0.1s to guarantee the filesystem's mtime clock ticks forward between writes, but the third pair (triggered by advancing the mocked snowflake.connector.cache.now() past entry_lifetime) has no such sleep, even though that write still hits the real filesystem clock, not the mocked one. On this repo's riscv64 runners the two real-wall-clock writes landed within the same mtime tick, failing the final `os.path.getmtime(tmp_cache_file) > second_updated_time` assertion with equal values (run 35440238662, job "Build snowflake-connector-python 4.7.4 cp313-manylinux_riscv64"). Confirmed against real upstream sources: the test file is byte-identical between the v4.7.3 and v4.7.4 tags, and the missing sleep is an asymmetry within the test itself (the same 0.1s sleep already exists two lines earlier for the same purpose), not a functional defect in cache.py. Only the 4.7.4 cp313 leg hit the race this run — all four 4.7.3 legs happened to land on different mtime ticks — but since both versions carry the identical unpatched test, both get the fix. Add a 0005 patch to both patches/snowflake-connector-python/4.7.3/ and patches/snowflake-connector-python/4.7.4/, matching the sleep already used earlier in the same test. Validated with `git apply --check` against fresh clones of the v4.7.3 and v4.7.4 tags, individually and as the full 0001-0005 stack. --- ...-before-the-final-mtime-advancing-sa.patch | 43 +++++++++++++++++++ ...-before-the-final-mtime-advancing-sa.patch | 43 +++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 patches/snowflake-connector-python/4.7.3/0005-test-cache-sleep-before-the-final-mtime-advancing-sa.patch create mode 100644 patches/snowflake-connector-python/4.7.4/0005-test-cache-sleep-before-the-final-mtime-advancing-sa.patch diff --git a/patches/snowflake-connector-python/4.7.3/0005-test-cache-sleep-before-the-final-mtime-advancing-sa.patch b/patches/snowflake-connector-python/4.7.3/0005-test-cache-sleep-before-the-final-mtime-advancing-sa.patch new file mode 100644 index 0000000000..5ff7d14f31 --- /dev/null +++ b/patches/snowflake-connector-python/4.7.3/0005-test-cache-sleep-before-the-final-mtime-advancing-sa.patch @@ -0,0 +1,43 @@ +From 27d6cc73a823c9df9e6bee17a4440aaae660313e Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Sat, 19 Sep 2026 16:52:55 +0000 +Subject: [PATCH] test: cache: sleep before the final mtime-advancing save in + test_file_is_not_updated + +test_file_is_not_updated writes the cache file three times and checks +os.path.getmtime() strictly advances each time a write happens. Before the +second write-then-assert-greater pair (line 557/561 upstream) the test +already sleeps 0.1s to guarantee the filesystem's mtime clock ticks forward +between writes. The third write-then-assert-greater pair (triggered by +advancing the mocked snowflake.connector.cache.now() past entry_lifetime) +has no such sleep, even though the write it precedes still hits the real +filesystem clock, not the mocked one. + +On this repo's riscv64 runners the two real-wall-clock writes can land +within the same mtime tick, making the final +`os.path.getmtime(tmp_cache_file) > second_updated_time` assertion fail +with equal values (e.g. run 35440238662, job +Build snowflake-connector-python 4.7.4 cp313-manylinux_riscv64). Add the +same 0.1s sleep used earlier in this test before the last write, matching +the established pattern. + +Upstream-Status: Inappropriate [riscv64 runner filesystem mtime resolution/timing is coarser than upstream's CI hosts; the test already sleeps before an analogous write two lines earlier but is missing the same sleep before this one] +--- + test/unit/test_cache.py | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/test/unit/test_cache.py b/test/unit/test_cache.py +index 78ab517..095caf6 100644 +--- a/test/unit/test_cache.py ++++ b/test/unit/test_cache.py +@@ -566,6 +566,7 @@ def test_file_is_not_updated(tmpdir): + assert os.path.getmtime(tmp_cache_file) == second_updated_time + # Advance mocked time past entry_lifetime to trigger expiry + mock_now.return_value = start_time + datetime.timedelta(seconds=2) ++ time.sleep(0.1) # sleep 0.1 to ensure mtime advances on the filesystem + sfcache.save() # this save call will dump cache because cache item is expired + assert not sfcache._cache_modified + assert os.path.getmtime(tmp_cache_file) > second_updated_time +-- +2.43.0 + diff --git a/patches/snowflake-connector-python/4.7.4/0005-test-cache-sleep-before-the-final-mtime-advancing-sa.patch b/patches/snowflake-connector-python/4.7.4/0005-test-cache-sleep-before-the-final-mtime-advancing-sa.patch new file mode 100644 index 0000000000..5ff7d14f31 --- /dev/null +++ b/patches/snowflake-connector-python/4.7.4/0005-test-cache-sleep-before-the-final-mtime-advancing-sa.patch @@ -0,0 +1,43 @@ +From 27d6cc73a823c9df9e6bee17a4440aaae660313e Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Sat, 19 Sep 2026 16:52:55 +0000 +Subject: [PATCH] test: cache: sleep before the final mtime-advancing save in + test_file_is_not_updated + +test_file_is_not_updated writes the cache file three times and checks +os.path.getmtime() strictly advances each time a write happens. Before the +second write-then-assert-greater pair (line 557/561 upstream) the test +already sleeps 0.1s to guarantee the filesystem's mtime clock ticks forward +between writes. The third write-then-assert-greater pair (triggered by +advancing the mocked snowflake.connector.cache.now() past entry_lifetime) +has no such sleep, even though the write it precedes still hits the real +filesystem clock, not the mocked one. + +On this repo's riscv64 runners the two real-wall-clock writes can land +within the same mtime tick, making the final +`os.path.getmtime(tmp_cache_file) > second_updated_time` assertion fail +with equal values (e.g. run 35440238662, job +Build snowflake-connector-python 4.7.4 cp313-manylinux_riscv64). Add the +same 0.1s sleep used earlier in this test before the last write, matching +the established pattern. + +Upstream-Status: Inappropriate [riscv64 runner filesystem mtime resolution/timing is coarser than upstream's CI hosts; the test already sleeps before an analogous write two lines earlier but is missing the same sleep before this one] +--- + test/unit/test_cache.py | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/test/unit/test_cache.py b/test/unit/test_cache.py +index 78ab517..095caf6 100644 +--- a/test/unit/test_cache.py ++++ b/test/unit/test_cache.py +@@ -566,6 +566,7 @@ def test_file_is_not_updated(tmpdir): + assert os.path.getmtime(tmp_cache_file) == second_updated_time + # Advance mocked time past entry_lifetime to trigger expiry + mock_now.return_value = start_time + datetime.timedelta(seconds=2) ++ time.sleep(0.1) # sleep 0.1 to ensure mtime advances on the filesystem + sfcache.save() # this save call will dump cache because cache item is expired + assert not sfcache._cache_modified + assert os.path.getmtime(tmp_cache_file) > second_updated_time +-- +2.43.0 +