Skip to content

Commit 1962efe

Browse files
committed
Add system test
1 parent f8fb714 commit 1962efe

File tree

2 files changed

+213
-0
lines changed

2 files changed

+213
-0
lines changed

.github/workflows/ci.yml

+4
Original file line numberDiff line numberDiff line change
@@ -1604,6 +1604,10 @@ jobs:
16041604
- name: "Validate global Python install"
16051605
run: python ./scripts/check_system_python.py --uv ./uv.exe
16061606

1607+
# NB: Run this last, we are modifying the registry
1608+
- name: "Test PEP 514 registration"
1609+
run: python ./scripts/check_registry.py --uv ./uv.exe
1610+
16071611
system-test-windows-python-313:
16081612
timeout-minutes: 10
16091613
needs: build-binary-windows

scripts/check_registry.py

+209
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
"""Check that adding uv's python-build-standalone distributions are successfully added
2+
and removed from the Windows registry following PEP 514."""
3+
4+
import re
5+
import subprocess
6+
import sys
7+
from argparse import ArgumentParser
8+
9+
# This is the snapshot as of python build standalone 20250115, we redact URL and hash
10+
# below. We don't redact the path inside the runner, if the runner configuration changes
11+
# (or uv's installation paths), please update the snapshot.
12+
expected_registry = [
13+
r"""
14+
Name Property
15+
---- --------
16+
Astral DisplayName : Astral Software Inc.
17+
SupportUrl : https://github.com/astral-sh/uv
18+
""",
19+
r"""
20+
Hive: HKEY_CURRENT_USER\Software\Python
21+
22+
23+
Name Property
24+
---- --------
25+
Astral DisplayName : Astral Software Inc.
26+
SupportUrl : https://github.com/astral-sh/uv
27+
28+
29+
Hive: HKEY_CURRENT_USER\Software\Python\Astral
30+
31+
32+
Name Property
33+
---- --------
34+
CPython3.11.11 DisplayName : CPython 3.11.11 (64-bit)
35+
SupportUrl : https://github.com/astral-sh/uv
36+
Version : 3.11.11
37+
SysVersion : 3.11.11
38+
SysArchitecture : 64bit
39+
DownloadUrl : <downloadUrl>
40+
DownloadSha256 : <downloadSha256>
41+
42+
43+
Hive: HKEY_CURRENT_USER\Software\Python\Astral\CPython3.11.11
44+
45+
46+
Name Property
47+
---- --------
48+
InstallPath (default) : C:\Users\runneradmin\AppData\Roaming\uv\python\cpython-3.11.11-windows-x86_64-none
49+
ExecutablePath : C:\Users\runneradmin\AppData\Roaming\uv\python\cpython-3.11.11-windows-x86_64-none\python.exe
50+
WindowedExecutablePath : C:\Users\runneradmin\AppData\Roaming\uv\python\cpython-3.11.11-windows-x86_64-none\pythonw.exe
51+
""",
52+
r"""
53+
Hive: HKEY_CURRENT_USER\Software\Python\Astral
54+
55+
56+
Name Property
57+
---- --------
58+
CPython3.12.8 DisplayName : CPython 3.12.8 (64-bit)
59+
SupportUrl : https://github.com/astral-sh/uv
60+
Version : 3.12.8
61+
SysVersion : 3.12.8
62+
SysArchitecture : 64bit
63+
DownloadUrl : <downloadUrl>
64+
DownloadSha256 : <downloadSha256>
65+
66+
67+
Hive: HKEY_CURRENT_USER\Software\Python\Astral\CPython3.12.8
68+
69+
70+
Name Property
71+
---- --------
72+
InstallPath (default) : C:\Users\runneradmin\AppData\Roaming\uv\python\cpython-3.12.8-windows-x86_64-none
73+
ExecutablePath : C:\Users\runneradmin\AppData\Roaming\uv\python\cpython-3.12.8-windows-x86_64-none\python.exe
74+
WindowedExecutablePath : C:\Users\runneradmin\AppData\Roaming\uv\python\cpython-3.12.8-windows-x86_64-none\pythonw.exe
75+
""",
76+
r"""
77+
Hive: HKEY_CURRENT_USER\Software\Python\Astral
78+
79+
80+
Name Property
81+
---- --------
82+
CPython3.13.1 DisplayName : CPython 3.13.1 (64-bit)
83+
SupportUrl : https://github.com/astral-sh/uv
84+
Version : 3.13.1
85+
SysVersion : 3.13.1
86+
SysArchitecture : 64bit
87+
DownloadUrl : <downloadUrl>
88+
DownloadSha256 : <downloadSha256>
89+
90+
91+
Hive: HKEY_CURRENT_USER\Software\Python\Astral\CPython3.13.1
92+
93+
94+
Name Property
95+
---- --------
96+
InstallPath (default) : C:\Users\runneradmin\AppData\Roaming\uv\python\cpython-3.13.1-windows-x86_64-none
97+
ExecutablePath : C:\Users\runneradmin\AppData\Roaming\uv\python\cpython-3.13.1-windows-x86_64-none\python.exe
98+
WindowedExecutablePath : C:\Users\runneradmin\AppData\Roaming\uv\python\cpython-3.13.1-windows-x86_64-none\pythonw.exe
99+
""",
100+
]
101+
102+
103+
def filter_snapshot(snapshot: str) -> str:
104+
snapshot = snapshot.strip("\n\r")
105+
# Trim trailing whitespace
106+
snapshot = "\n".join(line.rstrip() for line in snapshot.splitlines())
107+
# Long URLs are wrapped into multiple lines
108+
snapshot = re.sub(
109+
"DownloadUrl ( *): .*(\n.*)+?(\n +)DownloadSha256",
110+
r"DownloadUrl \1: <downloadUrl>\3DownloadSha256",
111+
snapshot,
112+
)
113+
snapshot = re.sub(
114+
"DownloadSha256 ( *): .*", r"DownloadSha256 \1: <downloadSha256>", snapshot
115+
)
116+
return snapshot
117+
118+
119+
def main(uv: str):
120+
# Check 1: Install interpreters and check that all their keys are set in the
121+
# registry and that the Python launcher for Windows finds it.
122+
# Check 1a: Install of new interpreters.
123+
# Check 2b: Request installation of already installed interpreters.
124+
for _ in range(2):
125+
print("Installing Python 3.11.11, 3.12.8, and 3.13.1")
126+
subprocess.check_call([uv, "python", "install", "-v", "--preview", "3.11.11"])
127+
subprocess.check_call([uv, "python", "install", "-v", "--preview", "3.12.8"])
128+
subprocess.check_call([uv, "python", "install", "-v", "--preview", "3.13.1"])
129+
# Use the powershell command to get an outside view on the registry values we wrote
130+
list_registry_command = [
131+
"powershell",
132+
"-Command",
133+
# By default, powershell wraps the output at terminal size
134+
r"Get-ChildItem -Path HKCU:\Software\Python -Recurse | Format-Table | Out-String -width 1000",
135+
]
136+
actual_registry = subprocess.check_output(list_registry_command, text=True)
137+
for expected in expected_registry:
138+
if filter_snapshot(expected) not in filter_snapshot(actual_registry):
139+
print("Registry mismatch:")
140+
print("Expected Snippet:")
141+
print("=" * 80)
142+
print(filter_snapshot(expected))
143+
print("=" * 80)
144+
print("Actual:")
145+
print("=" * 80)
146+
print(filter_snapshot(actual_registry))
147+
print("=" * 80)
148+
sys.exit(1)
149+
py_311_line = r" -V:Astral/CPython3.11.11 C:\Users\runneradmin\AppData\Roaming\uv\python\cpython-3.11.11-windows-x86_64-none\python.exe"
150+
py_312_line = r" -V:Astral/CPython3.12.8 C:\Users\runneradmin\AppData\Roaming\uv\python\cpython-3.12.8-windows-x86_64-none\python.exe"
151+
py_313_line = r" -V:Astral/CPython3.13.1 C:\Users\runneradmin\AppData\Roaming\uv\python\cpython-3.13.1-windows-x86_64-none\python.exe"
152+
listed_interpreters = subprocess.check_output(["py", "--list-paths"], text=True)
153+
py_listed = set(listed_interpreters.splitlines())
154+
if (
155+
py_311_line not in py_listed
156+
or py_312_line not in py_listed
157+
or py_313_line not in py_listed
158+
):
159+
print(
160+
f"Python launcher interpreter mismatch: {py_listed} vs. {py_311_line}, {py_312_line}, {py_313_line}"
161+
)
162+
sys.exit(1)
163+
164+
# Check 2: Remove a single interpreter and check that its gone.
165+
# Check 2a: Removing an existing interpreter.
166+
# Check 2b: Remove a missing interpreter.
167+
for _ in range(2):
168+
print("Removing Python 3.11.11")
169+
subprocess.check_call([uv, "python", "uninstall", "-v", "--preview", "3.11.11"])
170+
listed_interpreters = subprocess.check_output(["py", "--list-paths"], text=True)
171+
py_listed = set(listed_interpreters.splitlines())
172+
if (
173+
py_311_line in py_listed
174+
or py_312_line not in py_listed
175+
or py_313_line not in py_listed
176+
):
177+
print(
178+
f"Python launcher interpreter not removed: {py_listed} vs. {py_312_line}, {py_313_line}"
179+
)
180+
sys.exit(1)
181+
182+
# Check 3: Remove all interpreters and check that they are all gone.
183+
# Check 3a: Clear a used registry.
184+
# Check 3b: Clear an empty registry.
185+
subprocess.check_call([uv, "python", "uninstall", "-v", "--preview", "--all"])
186+
for _ in range(2):
187+
empty_registry = subprocess.check_output(list_registry_command, text=True)
188+
if empty_registry.strip():
189+
print("Registry not cleared:")
190+
print("=" * 80)
191+
print(empty_registry)
192+
print("=" * 80)
193+
sys.exit(1)
194+
listed_interpreters = subprocess.check_output(["py", "--list-paths"], text=True)
195+
py_listed = set(listed_interpreters.splitlines())
196+
if (
197+
py_311_line in py_listed
198+
or py_312_line in py_listed
199+
or py_313_line in py_listed
200+
):
201+
print(f"Python launcher interpreter not cleared: {py_listed}")
202+
sys.exit(1)
203+
204+
205+
if __name__ == "__main__":
206+
parser = ArgumentParser()
207+
parser.add_argument("--uv", default="./uv.exe")
208+
args = parser.parse_args()
209+
main(args.uv)

0 commit comments

Comments
 (0)