Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/packages/snowflake-connector-python.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,4 @@ versions:
- filename: snowflake_connector_python-4.7.4-cp314-cp314t-manylinux_2_39_riscv64.whl
sha256: 31d0678ab9709683ebf076b39a7475fe7cce0927158c3b4e189521faf91466ef
requires-python: '>=3.10'
- version: 4.7.5
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
From 9a08b490a7efc884676b5ad0ad8f582b1f596f4f Mon Sep 17 00:00:00 2001
From: Ludovic Henry <git@ludovic.dev>
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
From 450b09950bd41f428e1594560a7c70988d94b138 Mon Sep 17 00:00:00 2001
From: Ludovic Henry <git@ludovic.dev>
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 "<name>_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
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
From 4cad97079ae16e3d32525884fa741aeb413d5808 Mon Sep 17 00:00:00 2001
From: Ludovic Henry <git@ludovic.dev>
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


Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
From e861774346e0b2cd63cf6a48fc204fcfa829ad87 Mon Sep 17 00:00:00 2001
From: Ludovic Henry <git@ludovic.dev>
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

Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
From 27d6cc73a823c9df9e6bee17a4440aaae660313e Mon Sep 17 00:00:00 2001
From: Ludovic Henry <git@ludovic.dev>
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

Loading