mirror of https://github.com/astral-sh/uv
184 lines
6.4 KiB
Python
184 lines
6.4 KiB
Python
#!/usr/bin/env -S uv run --script
|
|
# /// script
|
|
# requires-python = ">=3.13"
|
|
# ///
|
|
|
|
import argparse
|
|
import difflib
|
|
import json
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("base", type=Path)
|
|
parser.add_argument("branch", type=Path)
|
|
parser.add_argument("--mode", choices=["compile", "lock", "pyproject-toml"])
|
|
parser.add_argument(
|
|
"--markdown",
|
|
action="store_true",
|
|
)
|
|
parser.add_argument(
|
|
"--limit",
|
|
type=int,
|
|
default=None,
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
# Supress noise from fluctuations in execution time
|
|
redact_time = re.compile(r"(0\.)?(\d+)ms|(\d+).(\d+)s")
|
|
|
|
parameters = json.loads(args.base.joinpath("parameters.json").read_text())
|
|
|
|
total = 0
|
|
successful = 0
|
|
differences = []
|
|
files = sorted(dir for dir in args.base.iterdir() if dir.is_dir())
|
|
for package_dir in files[: args.limit]:
|
|
package = package_dir.name
|
|
package_branch = args.branch.joinpath(package)
|
|
if not package_branch.is_dir():
|
|
print(f"Package {package} not found in branch")
|
|
continue
|
|
|
|
total += 1
|
|
|
|
summary = package_dir.joinpath("summary.json").read_text()
|
|
if json.loads(summary)["exit_code"] == 0:
|
|
successful += 1
|
|
else:
|
|
# Don't show differences in the error messages,
|
|
# also `uv.lock` doesn't exist for failed resolutions
|
|
continue
|
|
|
|
if args.mode == "compile":
|
|
resolution = package_dir.joinpath("stdout.txt").read_text()
|
|
else:
|
|
resolution = package_dir.joinpath("uv.lock").read_text()
|
|
if package_dir.joinpath("stdout.txt").read_text().strip():
|
|
raise RuntimeError(f"Stdout not empty (base): {package}")
|
|
stderr = package_dir.joinpath("stderr.txt").read_text()
|
|
stderr = redact_time.sub(r"[TIME]", stderr)
|
|
|
|
if args.mode == "compile":
|
|
resolution_branch = package_branch.joinpath("stdout.txt").read_text()
|
|
else:
|
|
resolution_branch = package_branch.joinpath("uv.lock").read_text()
|
|
if package_branch.joinpath("stdout.txt").read_text().strip():
|
|
raise RuntimeError(f"Stdout not empty (branch): {package}")
|
|
stderr_branch = package_branch.joinpath("stderr.txt").read_text()
|
|
stderr_branch = redact_time.sub(r"[TIME]", stderr_branch)
|
|
|
|
if resolution != resolution_branch or stderr != stderr_branch:
|
|
differences.append(
|
|
(package, resolution, resolution_branch, stderr, stderr_branch)
|
|
)
|
|
|
|
if args.markdown:
|
|
print(
|
|
"## Ecosystem testing report "
|
|
f"({args.mode.replace('pyproject-toml', 'pyproject.toml')})"
|
|
)
|
|
if args.mode == "pyproject-toml":
|
|
print(
|
|
" * Dataset: A set of top level `pyproject.toml` from GitHub projects popular in 2025. "
|
|
+ "Only `pyproject.toml` files with a `[project]` section and static dependencies are included."
|
|
)
|
|
else:
|
|
print(
|
|
" * Dataset: The top 15k PyPI packages. A handful of pathological cases were filtered out."
|
|
)
|
|
print(
|
|
" * Command: "
|
|
+ f"`{'uv pip compile' if args.mode == 'compile' else 'uv lock'}` with `--no-build` "
|
|
+ f"on Python {parameters['python']} "
|
|
+ (
|
|
"pinned to the latest package version. "
|
|
if parameters["latest"]
|
|
else ". "
|
|
)
|
|
)
|
|
print(
|
|
f" * Successfully resolved packages: {successful}/{total} ({successful / total:.0%}). "
|
|
+ "Only success resolutions can be compared."
|
|
)
|
|
print(f" * Different packages: {len(differences)}/{successful}")
|
|
|
|
for (
|
|
package,
|
|
resolution,
|
|
resolution_branch,
|
|
stderr,
|
|
stderr_branch,
|
|
) in differences:
|
|
if args.mode == "compile":
|
|
context_window = 999999
|
|
else:
|
|
context_window = 3
|
|
print(f"\n<details>\n<summary>{package}</summary>\n")
|
|
if resolution != resolution_branch:
|
|
print("```diff")
|
|
sys.stdout.writelines(
|
|
difflib.unified_diff(
|
|
resolution.splitlines(keepends=True),
|
|
resolution_branch.splitlines(keepends=True),
|
|
fromfile="base",
|
|
tofile="branch",
|
|
# Show the dependencies in full
|
|
n=context_window,
|
|
)
|
|
)
|
|
print("```")
|
|
if stderr != stderr_branch:
|
|
print("```diff")
|
|
sys.stdout.writelines(
|
|
difflib.unified_diff(
|
|
stderr.splitlines(keepends=True),
|
|
stderr_branch.splitlines(keepends=True),
|
|
fromfile="base",
|
|
tofile="branch",
|
|
# Show the log in full
|
|
n=context_window,
|
|
)
|
|
)
|
|
print("```")
|
|
print("</details>\n")
|
|
else:
|
|
for (
|
|
package,
|
|
resolution,
|
|
resolution_branch,
|
|
stderr,
|
|
stderr_branch,
|
|
) in differences:
|
|
print("--------------------------------")
|
|
print(f"Package {package}")
|
|
if resolution != resolution_branch:
|
|
sys.stdout.writelines(
|
|
difflib.unified_diff(
|
|
resolution.splitlines(keepends=True),
|
|
resolution_branch.splitlines(keepends=True),
|
|
fromfile="base",
|
|
tofile="branch",
|
|
)
|
|
)
|
|
if stderr != stderr_branch:
|
|
sys.stdout.writelines(
|
|
difflib.unified_diff(
|
|
stderr.splitlines(keepends=True),
|
|
stderr_branch.splitlines(keepends=True),
|
|
fromfile="base",
|
|
tofile="branch",
|
|
)
|
|
)
|
|
print(
|
|
f"Successfully resolved packages: {successful}/{total} ({successful / total:.0%})"
|
|
)
|
|
print(f"Different packages: {len(differences)}/{successful}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|