Skip to content

Commit c758a7c

Browse files
committed
Add system test
1 parent f8fb714 commit c758a7c

File tree

2 files changed

+223
-0
lines changed

2 files changed

+223
-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

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

0 commit comments

Comments
 (0)