Skip to content

Commit 488a837

Browse files
committed
snowflake-connector-python: carry forward 4.7.4 patches to 4.7.5
The nightly-upgrade bot bumped docs/packages/snowflake-connector-python.yaml to add 4.7.5 but did not copy patches/snowflake-connector-python/4.7.4/ forward, so all four riscv64 build legs failed immediately in the "Prepare the checkout" step: error: can't open patch 'python-wheels/patches/snowflake-connector-python/4.7.5/00*.patch': No such file or directory Copy the five existing 0001-0005 patches (wiremock startup timeout, detect_platforms timeout, auth mock_cnt race, get_application_path timing threshold, cache mtime race) to patches/snowflake-connector-python/4.7.5/. The corresponding test files are unchanged between the 4.7.4 and 4.7.5 tags, and all five patches apply cleanly, individually and as the full 0001-0005 stack, against a fresh clone of the real v4.7.5 upstream source.
1 parent da00d50 commit 488a837

5 files changed

Lines changed: 360 additions & 0 deletions
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
From 9a08b490a7efc884676b5ad0ad8f582b1f596f4f Mon Sep 17 00:00:00 2001
2+
From: Ludovic Henry <git@ludovic.dev>
3+
Date: Fri, 18 Sep 2026 06:44:38 +0000
4+
Subject: [PATCH] test: wiremock: raise the server start timeout to 120s
5+
6+
WiremockClient waits WIREMOCK_START_MAX_RETRY_COUNT seconds (12) for the
7+
standalone server to answer /__admin/health, then fails the test. On the
8+
riscv64 runners a JVM start plus WireMock/Jetty init routinely exceeds that
9+
when several pytest-xdist workers each spin up their own server, so ~10 of
10+
the auth/oauth/redirect tests error out nondeterministically while the rest
11+
of the same suite passes.
12+
13+
The loop returns as soon as the health check succeeds, so a larger ceiling
14+
costs nothing on faster hardware.
15+
16+
Upstream-Status: Inappropriate [native runner specific]
17+
---
18+
test/test_utils/wiremock/wiremock_utils.py | 2 +-
19+
1 file changed, 1 insertion(+), 1 deletion(-)
20+
21+
diff --git a/test/test_utils/wiremock/wiremock_utils.py b/test/test_utils/wiremock/wiremock_utils.py
22+
index 0e108b3d..1c487ce4 100644
23+
--- a/test/test_utils/wiremock/wiremock_utils.py
24+
+++ b/test/test_utils/wiremock/wiremock_utils.py
25+
@@ -16,7 +16,7 @@ except ImportError:
26+
27+
# Total budget for a Wiremock instance to become usable: the JVM has to boot,
28+
# report the ports it bound and answer the health endpoint within this time.
29+
-WIREMOCK_START_TIMEOUT_SECONDS = 12
30+
+WIREMOCK_START_TIMEOUT_SECONDS = 120
31+
WIREMOCK_STOP_TIMEOUT_SECONDS = 10
32+
# How long to wait between polls while waiting for the startup banner / health.
33+
_WIREMOCK_START_POLL_INTERVAL_SECONDS = 0.1
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
From 450b09950bd41f428e1594560a7c70988d94b138 Mon Sep 17 00:00:00 2001
2+
From: Ludovic Henry <git@ludovic.dev>
3+
Date: Fri, 18 Sep 2026 06:44:38 +0000
4+
Subject: [PATCH] test: detect_platforms: raise the generous timeout from 1s to
5+
30s
6+
7+
detect_platforms() runs its six probes in a ThreadPoolExecutor and labels
8+
any that miss platform_detection_timeout_seconds with a "<name>_timeout"
9+
suffix. The tests that want no timeout label at all pass 1 second, with an
10+
inline comment saying the value is only there "to make sure no Thread-based
11+
timeout messes the results".
12+
13+
On the riscv64 runners 1 second is not generous enough: has_aws_identity
14+
builds a boto3 STS client, and botocore's first service-model load, running
15+
alongside three other pytest-xdist workers, overshoots the budget. The
16+
future is then cancelled and test_no_platforms_detected sees
17+
['has_aws_identity_timeout'] instead of []. The four "not true" cases of
18+
test_platform_detection_disable_env_var_values assert the same empty list
19+
and are exposed the same way.
20+
21+
csp_helpers patches urllib3's HTTPConnection.request to raise ConnectTimeout
22+
outright, so no probe ever waits on the network and a larger ceiling adds no
23+
wall-clock time anywhere. Sites that deliberately exercise the timeout path
24+
(None, 0, EXPECTED_MAX_TIMEOUT_FOR_PLATFORM_DETECTION) are left alone.
25+
26+
Upstream-Status: Inappropriate [native runner specific]
27+
---
28+
test/unit/test_detect_platforms.py | 36 +++++++++++++++---------------
29+
1 file changed, 18 insertions(+), 18 deletions(-)
30+
31+
diff --git a/test/unit/test_detect_platforms.py b/test/unit/test_detect_platforms.py
32+
index b9b2d4f4..66a35420 100644
33+
--- a/test/unit/test_detect_platforms.py
34+
+++ b/test/unit/test_detect_platforms.py
35+
@@ -69,14 +69,14 @@ class TestDetectPlatforms:
36+
self, unavailable_metadata_service_with_request_exception
37+
):
38+
result = detect_platforms(
39+
- platform_detection_timeout_seconds=1
40+
+ platform_detection_timeout_seconds=30
41+
) # increase timeout to make sure no Thread-based timeout messes the results
42+
assert result == []
43+
44+
def test_ec2_instance_detection(
45+
self, unavailable_metadata_service_with_request_exception, fake_aws_environment
46+
):
47+
- result = detect_platforms(platform_detection_timeout_seconds=1)
48+
+ result = detect_platforms(platform_detection_timeout_seconds=30)
49+
assert "is_ec2_instance" in result
50+
51+
def test_aws_lambda_detection(
52+
@@ -84,7 +84,7 @@ class TestDetectPlatforms:
53+
unavailable_metadata_service_with_request_exception,
54+
fake_aws_lambda_environment,
55+
):
56+
- result = detect_platforms(platform_detection_timeout_seconds=1)
57+
+ result = detect_platforms(platform_detection_timeout_seconds=30)
58+
assert "is_aws_lambda" in result
59+
60+
@pytest.mark.parametrize(
61+
@@ -104,44 +104,44 @@ class TestDetectPlatforms:
62+
fake_aws_environment,
63+
arn,
64+
):
65+
- result = detect_platforms(platform_detection_timeout_seconds=1)
66+
+ result = detect_platforms(platform_detection_timeout_seconds=30)
67+
assert "has_aws_identity" in result
68+
69+
def test_azure_vm_detection(self, fake_azure_vm_metadata_service):
70+
- result = detect_platforms(platform_detection_timeout_seconds=1)
71+
+ result = detect_platforms(platform_detection_timeout_seconds=30)
72+
assert "is_azure_vm" in result
73+
74+
def test_azure_function_detection(self, fake_azure_function_metadata_service):
75+
- result = detect_platforms(platform_detection_timeout_seconds=1)
76+
+ result = detect_platforms(platform_detection_timeout_seconds=30)
77+
assert "is_azure_function" in result
78+
79+
def test_azure_function_with_managed_identity(
80+
self, fake_azure_function_metadata_service
81+
):
82+
- result = detect_platforms(platform_detection_timeout_seconds=1)
83+
+ result = detect_platforms(platform_detection_timeout_seconds=30)
84+
assert "is_azure_function" in result
85+
assert "has_azure_managed_identity" in result
86+
87+
def test_gce_vm_detection(self, fake_gce_metadata_service):
88+
- result = detect_platforms(platform_detection_timeout_seconds=1)
89+
+ result = detect_platforms(platform_detection_timeout_seconds=30)
90+
assert "is_gce_vm" in result
91+
92+
def test_gce_cloud_run_service_detection(
93+
self, fake_gce_cloud_run_service_metadata_service
94+
):
95+
- result = detect_platforms(platform_detection_timeout_seconds=1)
96+
+ result = detect_platforms(platform_detection_timeout_seconds=30)
97+
assert "is_gce_cloud_run_service" in result
98+
99+
def test_gce_cloud_run_job_detection(self, fake_gce_cloud_run_job_metadata_service):
100+
- result = detect_platforms(platform_detection_timeout_seconds=1)
101+
+ result = detect_platforms(platform_detection_timeout_seconds=30)
102+
assert "is_gce_cloud_run_job" in result
103+
104+
def test_gcp_identity_detection(self, fake_gce_metadata_service):
105+
- result = detect_platforms(platform_detection_timeout_seconds=1)
106+
+ result = detect_platforms(platform_detection_timeout_seconds=30)
107+
assert "has_gcp_identity" in result
108+
109+
def test_github_actions_detection(self, fake_github_actions_metadata_service):
110+
- result = detect_platforms(platform_detection_timeout_seconds=1)
111+
+ result = detect_platforms(platform_detection_timeout_seconds=30)
112+
assert "is_github_action" in result
113+
114+
def test_multiple_platforms_detection(
115+
@@ -150,7 +150,7 @@ class TestDetectPlatforms:
116+
fake_github_actions_metadata_service,
117+
fake_gce_cloud_run_service_metadata_service,
118+
):
119+
- result = detect_platforms(platform_detection_timeout_seconds=1)
120+
+ result = detect_platforms(platform_detection_timeout_seconds=30)
121+
assert "is_aws_lambda" in result
122+
assert "has_aws_identity" in result
123+
assert "is_github_action" in result
124+
@@ -267,14 +267,14 @@ class TestDetectPlatforms:
125+
arn,
126+
):
127+
fake_aws_environment.caller_identity = {"Arn": arn}
128+
- result = detect_platforms(platform_detection_timeout_seconds=1)
129+
+ result = detect_platforms(platform_detection_timeout_seconds=30)
130+
assert "has_aws_identity" not in result
131+
132+
def test_missing_arn_handling(
133+
self, unavailable_metadata_service_with_request_exception, fake_aws_environment
134+
):
135+
fake_aws_environment.caller_identity = {"UserId": "test-user"}
136+
- result = detect_platforms(platform_detection_timeout_seconds=1)
137+
+ result = detect_platforms(platform_detection_timeout_seconds=30)
138+
assert "has_aws_identity" not in result
139+
140+
def test_azure_managed_identity_no_token_endpoint(
141+
@@ -294,7 +294,7 @@ class TestDetectPlatforms:
142+
self, unavailable_metadata_service_with_request_exception, fake_aws_environment
143+
):
144+
fake_aws_environment.instance_document = b""
145+
- result = detect_platforms(platform_detection_timeout_seconds=1)
146+
+ result = detect_platforms(platform_detection_timeout_seconds=30)
147+
assert "is_ec2_instance" not in result
148+
149+
def test_aws_lambda_empty_task_root(
150+
@@ -373,7 +373,7 @@ class TestDetectPlatforms:
151+
):
152+
"""Test that ENV_VAR_DISABLE_PLATFORM_DETECTION only disables when set to 'true' (case-insensitive)"""
153+
with patch.dict(os.environ, {ENV_VAR_DISABLE_PLATFORM_DETECTION: env_value}):
154+
- result = detect_platforms(platform_detection_timeout_seconds=1)
155+
+ result = detect_platforms(platform_detection_timeout_seconds=30)
156+
assert result == expected_result
157+
158+
def test_platform_detection_disabled_overrides_all_other_detection(
159+
@@ -384,7 +384,7 @@ class TestDetectPlatforms:
160+
):
161+
"""Test that ENV_VAR_DISABLE_PLATFORM_DETECTION takes precedence over all detections"""
162+
with patch.dict(os.environ, {ENV_VAR_DISABLE_PLATFORM_DETECTION: "true"}):
163+
- result = detect_platforms(platform_detection_timeout_seconds=1)
164+
+ result = detect_platforms(platform_detection_timeout_seconds=30)
165+
assert result == _PLATFORM_DETECTION_DISABLED_RESULT
166+
assert "is_aws_lambda" not in result
167+
assert "is_github_action" not in result
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
From 4cad97079ae16e3d32525884fa741aeb413d5808 Mon Sep 17 00:00:00 2001
2+
From: Ludovic Henry <git@ludovic.dev>
3+
Date: Fri, 18 Sep 2026 06:44:38 +0000
4+
Subject: [PATCH] test: auth: fix the mock_cnt race that test_auth_mfa's
5+
timeout case leaves behind
6+
7+
Auth.authenticate() runs the MFA wait in a daemon Thread and gives up on it
8+
with t.join(timeout=timeout), so the request mock keeps running after the
9+
call returns. test_auth_mfa's third case relies on exactly that: the mock's
10+
mock_cnt == 1 branch sleeps 10 seconds, the test passes timeout=1, and the
11+
join returns while that thread is still sleeping.
12+
13+
The abandoned thread then falls through to `mock_cnt += 1` at the bottom of
14+
the mock, roughly nine seconds later. By that time the fourth case has
15+
already set `mock_cnt = 2` to select the "data is None" response, so the
16+
stray increment turns it into 3, the mock returns the empty else-branch
17+
dict, and Auth.authenticate() raises KeyError: 'data' instead of the
18+
snowflake.connector.errors.Error the test expects.
19+
20+
Whether the increment lands inside the fourth case is pure timing. It never
21+
does on a fast machine, which is why this only shows up where the fourth
22+
case's setup takes longer than the rest of the sleep. It is the same race
23+
the test is already skipped for on Windows.
24+
25+
Read and advance mock_cnt in one step at the top of the mock instead, before
26+
the sleep, so a call's counter effect is complete before the caller can walk
27+
away from it. Branch selection is unchanged: calls still see 0, 1, 2, ... in
28+
order.
29+
30+
Reproducible anywhere by inserting `time.sleep(11)` after the fourth case's
31+
`mock_cnt = 2`: KeyError: 'data' before this change, passing after.
32+
33+
Upstream-Status: To upstream [not submitted yet; the race is upstream's own, not riscv64-specific -- the existing IS_WINDOWS skip documents it]
34+
---
35+
test/unit/test_auth.py | 12 ++++++++----
36+
1 file changed, 8 insertions(+), 4 deletions(-)
37+
38+
diff --git a/test/unit/test_auth.py b/test/unit/test_auth.py
39+
index fdb2a998..7b3fbe63 100644
40+
--- a/test/unit/test_auth.py
41+
+++ b/test/unit/test_auth.py
42+
@@ -132,7 +132,12 @@ def _mock_auth_mfa_rest_response_timeout(url, headers, body, **kwargs):
43+
_ = headers
44+
_ = body
45+
_ = kwargs.get("dummy")
46+
- if mock_cnt == 0:
47+
+ # Advance the shared counter up front: the sleeping branch below is reached
48+
+ # from a daemon thread that Auth.authenticate() abandons on timeout, and a
49+
+ # post-sleep increment lands after the caller has reset mock_cnt for the
50+
+ # next case.
51+
+ call_cnt, mock_cnt = mock_cnt, mock_cnt + 1
52+
+ if call_cnt == 0:
53+
ret = {
54+
"success": True,
55+
"message": None,
56+
@@ -141,10 +146,10 @@ def _mock_auth_mfa_rest_response_timeout(url, headers, body, **kwargs):
57+
"inFlightCtx": "inFlightCtx",
58+
},
59+
}
60+
- elif mock_cnt == 1:
61+
+ elif call_cnt == 1:
62+
time.sleep(10) # should timeout while here
63+
ret = {}
64+
- elif mock_cnt == 2:
65+
+ elif call_cnt == 2:
66+
ret = {
67+
"success": True,
68+
"message": None,
69+
@@ -153,7 +158,6 @@ def _mock_auth_mfa_rest_response_timeout(url, headers, body, **kwargs):
70+
else:
71+
ret = {}
72+
73+
- mock_cnt += 1
74+
return ret
75+
76+
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
From e861774346e0b2cd63cf6a48fc204fcfa829ad87 Mon Sep 17 00:00:00 2001
2+
From: Ludovic Henry <git@ludovic.dev>
3+
Date: Sat, 19 Sep 2026 11:28:05 +0000
4+
Subject: [PATCH] test: util: raise the get_application_path timing-regression
5+
threshold from 1ms to 10ms
6+
7+
test_get_application_path_is_fast_on_deep_call_stacks asserts the average
8+
wall-clock time of get_application_path() at a 150-frame-deep call stack
9+
stays under a hardcoded 1.0ms, guarding against a regression to an
10+
inspect.stack()-based implementation that reads every frame's source file
11+
(GH-2908 / SNOW-3691001).
12+
13+
On this repo's shared riscv64 runners the same frame-walking implementation
14+
averages 2.286ms (observed on Build 4.7.4 cp312), 2.3x the threshold, with
15+
no regression to the slow inspect.stack() path: it is plain per-instruction
16+
cost on this architecture plus load from other concurrent matrix jobs on
17+
the same shared runner pool. Raise the ceiling to 10ms, well above the one
18+
observed sample, while still catching the ~100-500ms regression the test
19+
was written to guard against.
20+
21+
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]
22+
---
23+
test/unit/test_util.py | 2 +-
24+
1 file changed, 1 insertion(+), 1 deletion(-)
25+
26+
diff --git a/test/unit/test_util.py b/test/unit/test_util.py
27+
index eb99987..bab07c5 100644
28+
--- a/test/unit/test_util.py
29+
+++ b/test/unit/test_util.py
30+
@@ -34,7 +34,7 @@ def test_get_application_path_is_fast_on_deep_call_stacks():
31+
"""
32+
depth = 150
33+
n_runs = 5
34+
- threshold_ms = 1.0
35+
+ threshold_ms = 10.0
36+
37+
def recurse(n):
38+
if n == 0:
39+
--
40+
2.43.0
41+
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
From 27d6cc73a823c9df9e6bee17a4440aaae660313e Mon Sep 17 00:00:00 2001
2+
From: Ludovic Henry <git@ludovic.dev>
3+
Date: Sat, 19 Sep 2026 16:52:55 +0000
4+
Subject: [PATCH] test: cache: sleep before the final mtime-advancing save in
5+
test_file_is_not_updated
6+
7+
test_file_is_not_updated writes the cache file three times and checks
8+
os.path.getmtime() strictly advances each time a write happens. Before the
9+
second write-then-assert-greater pair (line 557/561 upstream) the test
10+
already sleeps 0.1s to guarantee the filesystem's mtime clock ticks forward
11+
between writes. The third write-then-assert-greater pair (triggered by
12+
advancing the mocked snowflake.connector.cache.now() past entry_lifetime)
13+
has no such sleep, even though the write it precedes still hits the real
14+
filesystem clock, not the mocked one.
15+
16+
On this repo's riscv64 runners the two real-wall-clock writes can land
17+
within the same mtime tick, making the final
18+
`os.path.getmtime(tmp_cache_file) > second_updated_time` assertion fail
19+
with equal values (e.g. run 35440238662, job
20+
Build snowflake-connector-python 4.7.4 cp313-manylinux_riscv64). Add the
21+
same 0.1s sleep used earlier in this test before the last write, matching
22+
the established pattern.
23+
24+
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]
25+
---
26+
test/unit/test_cache.py | 1 +
27+
1 file changed, 1 insertion(+)
28+
29+
diff --git a/test/unit/test_cache.py b/test/unit/test_cache.py
30+
index 78ab517..095caf6 100644
31+
--- a/test/unit/test_cache.py
32+
+++ b/test/unit/test_cache.py
33+
@@ -566,6 +566,7 @@ def test_file_is_not_updated(tmpdir):
34+
assert os.path.getmtime(tmp_cache_file) == second_updated_time
35+
# Advance mocked time past entry_lifetime to trigger expiry
36+
mock_now.return_value = start_time + datetime.timedelta(seconds=2)
37+
+ time.sleep(0.1) # sleep 0.1 to ensure mtime advances on the filesystem
38+
sfcache.save() # this save call will dump cache because cache item is expired
39+
assert not sfcache._cache_modified
40+
assert os.path.getmtime(tmp_cache_file) > second_updated_time
41+
--
42+
2.43.0
43+

0 commit comments

Comments
 (0)