mirror of https://github.com/astral-sh/uv
Use released packse for scenario updates (#2256)
- Now that `packse` is being published to PyPI we can install it from there. - Tweaks the tooling around scenario updates to manage a temporary virtual environment for you. - Makes use of a new index URL - Includes local version segment scenarios (supersedes https://github.com/astral-sh/uv/pull/2022)
This commit is contained in:
parent
b3ac0e30ec
commit
fd03362520
|
|
@ -1,7 +1,7 @@
|
|||
//! DO NOT EDIT
|
||||
//!
|
||||
//! Generated with scripts/scenarios/update.py
|
||||
//! Scenarios from <https://github.com/zanieb/packse/tree/4f39539c1b858e28268554604e75c69e25272e5a/scenarios>
|
||||
//! Generated with ./scripts/scenarios/sync.sh
|
||||
//! Scenarios from <https://github.com/zanieb/packse/tree/0.3.7/scenarios>
|
||||
//!
|
||||
#![cfg(all(feature = "python", feature = "pypi"))]
|
||||
|
||||
|
|
@ -27,9 +27,9 @@ fn command(context: &TestContext, python_versions: &[&str]) -> Command {
|
|||
.arg("compile")
|
||||
.arg("requirements.in")
|
||||
.arg("--index-url")
|
||||
.arg("https://test.pypi.org/simple")
|
||||
.arg("https://astral-sh.github.io/packse/0.3.7/simple-html/")
|
||||
.arg("--find-links")
|
||||
.arg("https://raw.githubusercontent.com/zanieb/packse/4f39539c1b858e28268554604e75c69e25272e5a/vendor/links.html")
|
||||
.arg("https://raw.githubusercontent.com/zanieb/packse/0.3.7/vendor/links.html")
|
||||
.arg("--cache-dir")
|
||||
.arg(context.cache_dir.path())
|
||||
.env("VIRTUAL_ENV", context.venv.as_os_str())
|
||||
|
|
@ -46,14 +46,12 @@ fn command(context: &TestContext, python_versions: &[&str]) -> Command {
|
|||
command
|
||||
}
|
||||
|
||||
/// requires-incompatible-python-version-compatible-override
|
||||
///
|
||||
/// The user requires a package which requires a Python version greater than the
|
||||
/// current version, but they use an alternative Python version for package
|
||||
/// resolution.
|
||||
///
|
||||
/// ```text
|
||||
/// 3f4ac9b2
|
||||
/// incompatible-python-compatible-override
|
||||
/// ├── environment
|
||||
/// │ └── python3.9
|
||||
/// ├── root
|
||||
|
|
@ -64,17 +62,17 @@ fn command(context: &TestContext, python_versions: &[&str]) -> Command {
|
|||
/// └── requires python>=3.10 (incompatible with environment)
|
||||
/// ```
|
||||
#[test]
|
||||
fn requires_incompatible_python_version_compatible_override() -> Result<()> {
|
||||
fn incompatible_python_compatible_override() -> Result<()> {
|
||||
let context = TestContext::new("3.9");
|
||||
let python_versions = &[];
|
||||
|
||||
// In addition to the standard filters, swap out package names for more realistic messages
|
||||
let mut filters = INSTA_FILTERS.to_vec();
|
||||
filters.push((r"a-3f4ac9b2", "albatross"));
|
||||
filters.push((r"-3f4ac9b2", ""));
|
||||
filters.push((r"incompatible-python-compatible-override-a", "albatross"));
|
||||
filters.push((r"incompatible-python-compatible-override-", "pkg-"));
|
||||
|
||||
let requirements_in = context.temp_dir.child("requirements.in");
|
||||
requirements_in.write_str("a-3f4ac9b2==1.0.0")?;
|
||||
requirements_in.write_str("incompatible-python-compatible-override-a==1.0.0")?;
|
||||
|
||||
let output = uv_snapshot!(filters, command(&context, python_versions)
|
||||
.arg("--python-version=3.11")
|
||||
|
|
@ -92,21 +90,18 @@ fn requires_incompatible_python_version_compatible_override() -> Result<()> {
|
|||
"###
|
||||
);
|
||||
|
||||
output
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("a-3f4ac9b2==1.0.0"));
|
||||
output.assert().success().stdout(predicate::str::contains(
|
||||
"incompatible-python-compatible-override-a==1.0.0",
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// requires-compatible-python-version-incompatible-override
|
||||
///
|
||||
/// The user requires a package which requires a compatible Python version, but they
|
||||
/// request an incompatible Python version for package resolution.
|
||||
///
|
||||
/// ```text
|
||||
/// fd6db412
|
||||
/// compatible-python-incompatible-override
|
||||
/// ├── environment
|
||||
/// │ └── python3.11
|
||||
/// ├── root
|
||||
|
|
@ -117,17 +112,17 @@ fn requires_incompatible_python_version_compatible_override() -> Result<()> {
|
|||
/// └── requires python>=3.10
|
||||
/// ```
|
||||
#[test]
|
||||
fn requires_compatible_python_version_incompatible_override() -> Result<()> {
|
||||
fn compatible_python_incompatible_override() -> Result<()> {
|
||||
let context = TestContext::new("3.11");
|
||||
let python_versions = &[];
|
||||
|
||||
// In addition to the standard filters, swap out package names for more realistic messages
|
||||
let mut filters = INSTA_FILTERS.to_vec();
|
||||
filters.push((r"a-fd6db412", "albatross"));
|
||||
filters.push((r"-fd6db412", ""));
|
||||
filters.push((r"compatible-python-incompatible-override-a", "albatross"));
|
||||
filters.push((r"compatible-python-incompatible-override-", "pkg-"));
|
||||
|
||||
let requirements_in = context.temp_dir.child("requirements.in");
|
||||
requirements_in.write_str("a-fd6db412==1.0.0")?;
|
||||
requirements_in.write_str("compatible-python-incompatible-override-a==1.0.0")?;
|
||||
|
||||
let output = uv_snapshot!(filters, command(&context, python_versions)
|
||||
.arg("--python-version=3.9")
|
||||
|
|
@ -149,14 +144,12 @@ fn requires_compatible_python_version_incompatible_override() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// requires-incompatible-python-version-compatible-override-no-wheels
|
||||
///
|
||||
/// The user requires a package which requires a incompatible Python version, but
|
||||
/// they request a compatible Python version for package resolution. There are only
|
||||
/// source distributions available for the package.
|
||||
///
|
||||
/// ```text
|
||||
/// 3521037f
|
||||
/// incompatible-python-compatible-override-unavailable-no-wheels
|
||||
/// ├── environment
|
||||
/// │ └── python3.9
|
||||
/// ├── root
|
||||
|
|
@ -167,17 +160,24 @@ fn requires_compatible_python_version_incompatible_override() -> Result<()> {
|
|||
/// └── requires python>=3.10 (incompatible with environment)
|
||||
/// ```
|
||||
#[test]
|
||||
fn requires_incompatible_python_version_compatible_override_no_wheels() -> Result<()> {
|
||||
fn incompatible_python_compatible_override_unavailable_no_wheels() -> Result<()> {
|
||||
let context = TestContext::new("3.9");
|
||||
let python_versions = &[];
|
||||
|
||||
// In addition to the standard filters, swap out package names for more realistic messages
|
||||
let mut filters = INSTA_FILTERS.to_vec();
|
||||
filters.push((r"a-3521037f", "albatross"));
|
||||
filters.push((r"-3521037f", ""));
|
||||
filters.push((
|
||||
r"incompatible-python-compatible-override-unavailable-no-wheels-a",
|
||||
"albatross",
|
||||
));
|
||||
filters.push((
|
||||
r"incompatible-python-compatible-override-unavailable-no-wheels-",
|
||||
"pkg-",
|
||||
));
|
||||
|
||||
let requirements_in = context.temp_dir.child("requirements.in");
|
||||
requirements_in.write_str("a-3521037f==1.0.0")?;
|
||||
requirements_in
|
||||
.write_str("incompatible-python-compatible-override-unavailable-no-wheels-a==1.0.0")?;
|
||||
|
||||
// Since there are no wheels for the package and it is not compatible with the
|
||||
// local installation, we cannot build the source distribution to determine its
|
||||
|
|
@ -202,15 +202,13 @@ fn requires_incompatible_python_version_compatible_override_no_wheels() -> Resul
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// requires-incompatible-python-version-compatible-override-no-wheels-available-system
|
||||
///
|
||||
/// The user requires a package which requires a incompatible Python version, but
|
||||
/// they request a compatible Python version for package resolution. There are only
|
||||
/// source distributions available for the package. The user has a compatible Python
|
||||
/// version installed elsewhere on their system.
|
||||
///
|
||||
/// ```text
|
||||
/// c68bcf5c
|
||||
/// incompatible-python-compatible-override-available-no-wheels
|
||||
/// ├── environment
|
||||
/// │ ├── python3.11
|
||||
/// │ └── python3.9 (active)
|
||||
|
|
@ -222,18 +220,24 @@ fn requires_incompatible_python_version_compatible_override_no_wheels() -> Resul
|
|||
/// └── requires python>=3.10 (incompatible with environment)
|
||||
/// ```
|
||||
#[test]
|
||||
fn requires_incompatible_python_version_compatible_override_no_wheels_available_system(
|
||||
) -> Result<()> {
|
||||
fn incompatible_python_compatible_override_available_no_wheels() -> Result<()> {
|
||||
let context = TestContext::new("3.9");
|
||||
let python_versions = &["3.11"];
|
||||
|
||||
// In addition to the standard filters, swap out package names for more realistic messages
|
||||
let mut filters = INSTA_FILTERS.to_vec();
|
||||
filters.push((r"a-c68bcf5c", "albatross"));
|
||||
filters.push((r"-c68bcf5c", ""));
|
||||
filters.push((
|
||||
r"incompatible-python-compatible-override-available-no-wheels-a",
|
||||
"albatross",
|
||||
));
|
||||
filters.push((
|
||||
r"incompatible-python-compatible-override-available-no-wheels-",
|
||||
"pkg-",
|
||||
));
|
||||
|
||||
let requirements_in = context.temp_dir.child("requirements.in");
|
||||
requirements_in.write_str("a-c68bcf5c==1.0.0")?;
|
||||
requirements_in
|
||||
.write_str("incompatible-python-compatible-override-available-no-wheels-a==1.0.0")?;
|
||||
|
||||
// Since there is a compatible Python version available on the system, it should be
|
||||
// used to build the source distributions.
|
||||
|
|
@ -252,22 +256,19 @@ fn requires_incompatible_python_version_compatible_override_no_wheels_available_
|
|||
"###
|
||||
);
|
||||
|
||||
output
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("a-c68bcf5c==1.0.0"));
|
||||
output.assert().success().stdout(predicate::str::contains(
|
||||
"incompatible-python-compatible-override-available-no-wheels-a==1.0.0",
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// requires-incompatible-python-version-compatible-override-no-compatible-wheels
|
||||
///
|
||||
/// The user requires a package which requires a incompatible Python version, but
|
||||
/// they request a compatible Python version for package resolution. There is a
|
||||
/// wheel available for the package, but it does not have a compatible tag.
|
||||
///
|
||||
/// ```text
|
||||
/// d7b25a2d
|
||||
/// incompatible-python-compatible-override-no-compatible-wheels
|
||||
/// ├── environment
|
||||
/// │ └── python3.9
|
||||
/// ├── root
|
||||
|
|
@ -278,17 +279,24 @@ fn requires_incompatible_python_version_compatible_override_no_wheels_available_
|
|||
/// └── requires python>=3.10 (incompatible with environment)
|
||||
/// ```
|
||||
#[test]
|
||||
fn requires_incompatible_python_version_compatible_override_no_compatible_wheels() -> Result<()> {
|
||||
fn incompatible_python_compatible_override_no_compatible_wheels() -> Result<()> {
|
||||
let context = TestContext::new("3.9");
|
||||
let python_versions = &[];
|
||||
|
||||
// In addition to the standard filters, swap out package names for more realistic messages
|
||||
let mut filters = INSTA_FILTERS.to_vec();
|
||||
filters.push((r"a-d7b25a2d", "albatross"));
|
||||
filters.push((r"-d7b25a2d", ""));
|
||||
filters.push((
|
||||
r"incompatible-python-compatible-override-no-compatible-wheels-a",
|
||||
"albatross",
|
||||
));
|
||||
filters.push((
|
||||
r"incompatible-python-compatible-override-no-compatible-wheels-",
|
||||
"pkg-",
|
||||
));
|
||||
|
||||
let requirements_in = context.temp_dir.child("requirements.in");
|
||||
requirements_in.write_str("a-d7b25a2d==1.0.0")?;
|
||||
requirements_in
|
||||
.write_str("incompatible-python-compatible-override-no-compatible-wheels-a==1.0.0")?;
|
||||
|
||||
// Since there are no compatible wheels for the package and it is not compatible
|
||||
// with the local installation, we cannot build the source distribution to
|
||||
|
|
@ -313,15 +321,13 @@ fn requires_incompatible_python_version_compatible_override_no_compatible_wheels
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// requires-incompatible-python-version-compatible-override-other-wheel
|
||||
///
|
||||
/// The user requires a package which requires a incompatible Python version, but
|
||||
/// they request a compatible Python version for package resolution. There are only
|
||||
/// source distributions available for the compatible version of the package, but
|
||||
/// there is an incompatible version with a wheel available.
|
||||
///
|
||||
/// ```text
|
||||
/// a9179f0c
|
||||
/// incompatible-python-compatible-override-other-wheel
|
||||
/// ├── environment
|
||||
/// │ └── python3.9
|
||||
/// ├── root
|
||||
|
|
@ -335,17 +341,23 @@ fn requires_incompatible_python_version_compatible_override_no_compatible_wheels
|
|||
/// └── requires python>=3.12 (incompatible with environment)
|
||||
/// ```
|
||||
#[test]
|
||||
fn requires_incompatible_python_version_compatible_override_other_wheel() -> Result<()> {
|
||||
fn incompatible_python_compatible_override_other_wheel() -> Result<()> {
|
||||
let context = TestContext::new("3.9");
|
||||
let python_versions = &[];
|
||||
|
||||
// In addition to the standard filters, swap out package names for more realistic messages
|
||||
let mut filters = INSTA_FILTERS.to_vec();
|
||||
filters.push((r"a-a9179f0c", "albatross"));
|
||||
filters.push((r"-a9179f0c", ""));
|
||||
filters.push((
|
||||
r"incompatible-python-compatible-override-other-wheel-a",
|
||||
"albatross",
|
||||
));
|
||||
filters.push((
|
||||
r"incompatible-python-compatible-override-other-wheel-",
|
||||
"pkg-",
|
||||
));
|
||||
|
||||
let requirements_in = context.temp_dir.child("requirements.in");
|
||||
requirements_in.write_str("a-a9179f0c")?;
|
||||
requirements_in.write_str("incompatible-python-compatible-override-other-wheel-a")?;
|
||||
|
||||
// Since there are no wheels for the version of the package compatible with the
|
||||
// target and it is not compatible with the local installation, we cannot build the
|
||||
|
|
@ -378,13 +390,11 @@ fn requires_incompatible_python_version_compatible_override_other_wheel() -> Res
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// requires-python-patch-version-override-no-patch
|
||||
///
|
||||
/// The user requires a package which requires a Python version with a patch version
|
||||
/// and the user provides a target version without a patch version.
|
||||
///
|
||||
/// ```text
|
||||
/// e1884826
|
||||
/// python-patch-override-no-patch
|
||||
/// ├── environment
|
||||
/// │ └── python3.8.18
|
||||
/// ├── root
|
||||
|
|
@ -395,17 +405,17 @@ fn requires_incompatible_python_version_compatible_override_other_wheel() -> Res
|
|||
/// └── requires python>=3.8.4
|
||||
/// ```
|
||||
#[test]
|
||||
fn requires_python_patch_version_override_no_patch() -> Result<()> {
|
||||
fn python_patch_override_no_patch() -> Result<()> {
|
||||
let context = TestContext::new("3.8.18");
|
||||
let python_versions = &[];
|
||||
|
||||
// In addition to the standard filters, swap out package names for more realistic messages
|
||||
let mut filters = INSTA_FILTERS.to_vec();
|
||||
filters.push((r"a-e1884826", "albatross"));
|
||||
filters.push((r"-e1884826", ""));
|
||||
filters.push((r"python-patch-override-no-patch-a", "albatross"));
|
||||
filters.push((r"python-patch-override-no-patch-", "pkg-"));
|
||||
|
||||
let requirements_in = context.temp_dir.child("requirements.in");
|
||||
requirements_in.write_str("a-e1884826==1.0.0")?;
|
||||
requirements_in.write_str("python-patch-override-no-patch-a==1.0.0")?;
|
||||
|
||||
// Since the resolver is asked to solve with 3.8, the minimum compatible Python
|
||||
// requirement is treated as 3.8.0.
|
||||
|
|
@ -428,13 +438,11 @@ fn requires_python_patch_version_override_no_patch() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// requires-python-patch-version-override-patch-compatible
|
||||
///
|
||||
/// The user requires a package which requires a Python version with a patch version
|
||||
/// and the user provides a target version with a compatible patch version.
|
||||
///
|
||||
/// ```text
|
||||
/// 91b4bcfc
|
||||
/// python-patch-override-patch-compatible
|
||||
/// ├── environment
|
||||
/// │ └── python3.8.18
|
||||
/// ├── root
|
||||
|
|
@ -445,17 +453,17 @@ fn requires_python_patch_version_override_no_patch() -> Result<()> {
|
|||
/// └── requires python>=3.8.0
|
||||
/// ```
|
||||
#[test]
|
||||
fn requires_python_patch_version_override_patch_compatible() -> Result<()> {
|
||||
fn python_patch_override_patch_compatible() -> Result<()> {
|
||||
let context = TestContext::new("3.8.18");
|
||||
let python_versions = &[];
|
||||
|
||||
// In addition to the standard filters, swap out package names for more realistic messages
|
||||
let mut filters = INSTA_FILTERS.to_vec();
|
||||
filters.push((r"a-91b4bcfc", "albatross"));
|
||||
filters.push((r"-91b4bcfc", ""));
|
||||
filters.push((r"python-patch-override-patch-compatible-a", "albatross"));
|
||||
filters.push((r"python-patch-override-patch-compatible-", "pkg-"));
|
||||
|
||||
let requirements_in = context.temp_dir.child("requirements.in");
|
||||
requirements_in.write_str("a-91b4bcfc==1.0.0")?;
|
||||
requirements_in.write_str("python-patch-override-patch-compatible-a==1.0.0")?;
|
||||
|
||||
let output = uv_snapshot!(filters, command(&context, python_versions)
|
||||
.arg("--python-version=3.8.0")
|
||||
|
|
@ -473,10 +481,9 @@ fn requires_python_patch_version_override_patch_compatible() -> Result<()> {
|
|||
"###
|
||||
);
|
||||
|
||||
output
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("a-91b4bcfc==1.0.0"));
|
||||
output.assert().success().stdout(predicate::str::contains(
|
||||
"python-patch-override-patch-compatible-a==1.0.0",
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1 +0,0 @@
|
|||
packse-scenarios
|
||||
|
|
@ -0,0 +1,285 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generates and updates snapshot test cases from packse scenarios.
|
||||
|
||||
Important:
|
||||
|
||||
This script is the backend called by `./scripts/scenarios/sync.sh`, consider using that
|
||||
if not developing scenarios.
|
||||
|
||||
Requirements:
|
||||
|
||||
$ uv pip install -r scripts/scenarios/requirements.txt
|
||||
|
||||
Uses `git`, `rustfmt`, and `cargo insta test` requirements from the project.
|
||||
|
||||
Usage:
|
||||
|
||||
Regenerate the scenario test files using the given scenarios:
|
||||
|
||||
$ ./scripts/scenarios/generate.py <path>
|
||||
|
||||
Scenarios can be developed locally with the following workflow:
|
||||
|
||||
Serve scenarios on a local index using packse
|
||||
|
||||
$ packse serve <path to scenarios>
|
||||
|
||||
Override the uv package index and update the tests
|
||||
|
||||
$ UV_INDEX_URL="http://localhost:3141" ./scripts/scenarios/generate.py <path to scenarios>
|
||||
|
||||
If an editable version of packse is installed, this script will use its bundled scenarios by default.
|
||||
|
||||
Use
|
||||
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import importlib.metadata
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import textwrap
|
||||
from pathlib import Path
|
||||
|
||||
TOOL_ROOT = Path(__file__).parent
|
||||
TEMPLATES = TOOL_ROOT / "templates"
|
||||
INSTALL_TEMPLATE = TEMPLATES / "install.mustache"
|
||||
COMPILE_TEMPLATE = TEMPLATES / "compile.mustache"
|
||||
PACKSE = TOOL_ROOT / "packse-scenarios"
|
||||
REQUIREMENTS = TOOL_ROOT / "requirements.txt"
|
||||
PROJECT_ROOT = TOOL_ROOT.parent.parent
|
||||
TESTS = PROJECT_ROOT / "crates" / "uv" / "tests"
|
||||
INSTALL_TESTS = TESTS / "pip_install_scenarios.rs"
|
||||
COMPILE_TESTS = TESTS / "pip_compile_scenarios.rs"
|
||||
|
||||
CUTE_NAMES = {
|
||||
"a": "albatross",
|
||||
"b": "bluebird",
|
||||
"c": "crow",
|
||||
"d": "duck",
|
||||
"e": "eagle",
|
||||
"f": "flamingo",
|
||||
"g": "goose",
|
||||
"h": "heron",
|
||||
}
|
||||
|
||||
try:
|
||||
import packse
|
||||
import packse.inspect
|
||||
except ImportError:
|
||||
print(
|
||||
f"missing requirement `packse`: install the requirements at {REQUIREMENTS.relative_to(PROJECT_ROOT)}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
exit(1)
|
||||
|
||||
|
||||
try:
|
||||
import chevron_blue
|
||||
except ImportError:
|
||||
print(
|
||||
f"missing requirement `chevron-blue`: install the requirements at {REQUIREMENTS.relative_to(PROJECT_ROOT)}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
exit(1)
|
||||
|
||||
|
||||
def main(scenarios: list[Path], snapshot_update: bool = True):
|
||||
# Fetch packse version
|
||||
packse_version = importlib.metadata.version("packse")
|
||||
|
||||
debug = logging.getLogger().getEffectiveLevel() <= logging.DEBUG
|
||||
|
||||
if not scenarios:
|
||||
if packse_version == "0.0.0":
|
||||
path = packse.__development_base_path__ / "scenarios"
|
||||
if path.exists():
|
||||
logging.info(
|
||||
"Detected development version of packse, using scenarios from %s",
|
||||
path,
|
||||
)
|
||||
scenarios = path.glob("*.json")
|
||||
else:
|
||||
logging.error(
|
||||
"No scenarios provided. Found development version of packse but is missing scenarios. Is it installed as an editable?"
|
||||
)
|
||||
sys.exit(1)
|
||||
else:
|
||||
logging.error("No scenarios provided, nothing to do.")
|
||||
return
|
||||
|
||||
targets = []
|
||||
for target in scenarios:
|
||||
if target.is_dir():
|
||||
targets.extend(target.glob("*.json"))
|
||||
else:
|
||||
targets.append(target)
|
||||
|
||||
logging.info("Loading scenario metadata...")
|
||||
data = packse.inspect.inspect(
|
||||
targets=targets,
|
||||
no_hash=True,
|
||||
)
|
||||
|
||||
data["scenarios"] = [
|
||||
scenario
|
||||
for scenario in data["scenarios"]
|
||||
# Drop the example scenario
|
||||
if scenario["name"] != "example"
|
||||
]
|
||||
|
||||
# Wrap the description onto multiple lines
|
||||
for scenario in data["scenarios"]:
|
||||
scenario["description_lines"] = textwrap.wrap(scenario["description"], width=80)
|
||||
|
||||
# Wrap the expected explanation onto multiple lines
|
||||
for scenario in data["scenarios"]:
|
||||
expected = scenario["expected"]
|
||||
expected["explanation_lines"] = (
|
||||
textwrap.wrap(expected["explanation"], width=80)
|
||||
if expected["explanation"]
|
||||
else []
|
||||
)
|
||||
|
||||
# TEMPORARY
|
||||
# We do not yet support local version identifiers
|
||||
for scenario in data["scenarios"]:
|
||||
expected = scenario["expected"]
|
||||
if (
|
||||
scenario["name"].startswith("local-")
|
||||
and scenario["name"] != "local-not-latest"
|
||||
):
|
||||
expected["satisfiable"] = False
|
||||
expected[
|
||||
"explanation"
|
||||
] = "We do not have correct behavior for local version identifiers yet"
|
||||
|
||||
# Generate cute names for each scenario
|
||||
for scenario in data["scenarios"]:
|
||||
for package in scenario["packages"]:
|
||||
package["cute_name"] = CUTE_NAMES[package["name"].rsplit("-")[-1]]
|
||||
|
||||
# Split scenarios into `install` and `compile` cases
|
||||
install_scenarios = []
|
||||
compile_scenarios = []
|
||||
|
||||
for scenario in data["scenarios"]:
|
||||
if (scenario["resolver_options"] or {}).get("python") is not None:
|
||||
compile_scenarios.append(scenario)
|
||||
else:
|
||||
install_scenarios.append(scenario)
|
||||
|
||||
for template, tests, scenarios in [
|
||||
(INSTALL_TEMPLATE, INSTALL_TESTS, install_scenarios),
|
||||
(COMPILE_TEMPLATE, COMPILE_TESTS, compile_scenarios),
|
||||
]:
|
||||
data = {"scenarios": scenarios}
|
||||
|
||||
ref = "HEAD" if packse_version == "0.0.0" else packse_version
|
||||
|
||||
# Add generated metadata
|
||||
data[
|
||||
"generated_from"
|
||||
] = f"https://github.com/zanieb/packse/tree/{ref}/scenarios"
|
||||
data["generated_with"] = "./scripts/scenarios/sync.sh"
|
||||
data[
|
||||
"vendor_links"
|
||||
] = f"https://raw.githubusercontent.com/zanieb/packse/{ref}/vendor/links.html"
|
||||
|
||||
data["index_url"] = f"https://astral-sh.github.io/packse/{ref}/simple-html/"
|
||||
|
||||
# Render the template
|
||||
logging.info(f"Rendering template {template.name}")
|
||||
output = chevron_blue.render(
|
||||
template=template.read_text(), data=data, no_escape=True, warn=True
|
||||
)
|
||||
|
||||
# Update the test files
|
||||
logging.info(
|
||||
f"Updating test file at `{tests.relative_to(PROJECT_ROOT)}`...",
|
||||
)
|
||||
with open(tests, "wt") as test_file:
|
||||
test_file.write(output)
|
||||
|
||||
# Format
|
||||
logging.info(
|
||||
"Formatting test file...",
|
||||
)
|
||||
subprocess.check_call(
|
||||
["rustfmt", str(tests)],
|
||||
stderr=subprocess.STDOUT,
|
||||
stdout=sys.stderr if debug else subprocess.DEVNULL,
|
||||
)
|
||||
|
||||
# Update snapshots
|
||||
if snapshot_update:
|
||||
logging.info("Updating snapshots...")
|
||||
env = os.environ.copy()
|
||||
env["UV_TEST_PYTHON_PATH"] = str(PROJECT_ROOT / "bin")
|
||||
subprocess.call(
|
||||
[
|
||||
"cargo",
|
||||
"insta",
|
||||
"test",
|
||||
"--features",
|
||||
"pypi,python",
|
||||
"--accept",
|
||||
"--test-runner",
|
||||
"nextest",
|
||||
"--test",
|
||||
tests.with_suffix("").name,
|
||||
],
|
||||
cwd=PROJECT_ROOT,
|
||||
stderr=subprocess.STDOUT,
|
||||
stdout=sys.stderr if debug else subprocess.DEVNULL,
|
||||
env=env,
|
||||
)
|
||||
else:
|
||||
logging.info("Skipping snapshot update")
|
||||
|
||||
logging.info("Done!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generates and updates snapshot test cases from packse scenarios.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"scenarios",
|
||||
type=Path,
|
||||
nargs="*",
|
||||
help="The scenario files to use",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v",
|
||||
"--verbose",
|
||||
action="store_true",
|
||||
help="Enable debug logging",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-q",
|
||||
"--quiet",
|
||||
action="store_true",
|
||||
help="Disable logging",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--no-snapshot-update",
|
||||
action="store_true",
|
||||
help="Disable automatic snapshot updates",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
if args.quiet:
|
||||
log_level = logging.CRITICAL
|
||||
elif args.verbose:
|
||||
log_level = logging.DEBUG
|
||||
else:
|
||||
log_level = logging.INFO
|
||||
|
||||
logging.basicConfig(level=log_level, format="%(message)s")
|
||||
|
||||
main(args.scenarios, snapshot_update=not args.no_snapshot_update)
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
chevron-blue
|
||||
packse>=0.3.6
|
||||
|
|
@ -1,4 +1,69 @@
|
|||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile scripts/scenarios/requirements.in -o scripts/scenarios/requirements.txt --refresh-package packse --upgrade
|
||||
certifi==2024.2.2
|
||||
# via requests
|
||||
charset-normalizer==3.3.2
|
||||
# via requests
|
||||
chevron-blue==0.2.1
|
||||
packse @ git+https://github.com/zanieb/packse
|
||||
waitress @ git+https://github.com/zanieb/waitress@d6d764bcc970e1e50486153588eda8a92cf5b5e4
|
||||
devpi-server @ git+https://github.com/zanieb/devpi@22f71acb8f08a59a098e7ad434cf388a1193fc24#subdirectory=server
|
||||
# via packse
|
||||
docutils==0.20.1
|
||||
# via readme-renderer
|
||||
editables==0.5
|
||||
# via hatchling
|
||||
hatchling==1.21.1
|
||||
# via packse
|
||||
idna==3.6
|
||||
# via requests
|
||||
importlib-metadata==7.0.1
|
||||
# via twine
|
||||
jaraco-classes==3.3.1
|
||||
# via keyring
|
||||
keyring==24.3.1
|
||||
# via twine
|
||||
markdown-it-py==3.0.0
|
||||
# via rich
|
||||
mdurl==0.1.2
|
||||
# via markdown-it-py
|
||||
more-itertools==10.2.0
|
||||
# via jaraco-classes
|
||||
msgspec==0.18.6
|
||||
# via packse
|
||||
nh3==0.2.15
|
||||
# via readme-renderer
|
||||
packaging==23.2
|
||||
# via hatchling
|
||||
packse==0.3.7
|
||||
pathspec==0.12.1
|
||||
# via hatchling
|
||||
pkginfo==1.10.0
|
||||
# via twine
|
||||
pluggy==1.4.0
|
||||
# via hatchling
|
||||
pygments==2.17.2
|
||||
# via
|
||||
# readme-renderer
|
||||
# rich
|
||||
readme-renderer==43.0
|
||||
# via twine
|
||||
requests==2.31.0
|
||||
# via
|
||||
# requests-toolbelt
|
||||
# twine
|
||||
requests-toolbelt==1.0.0
|
||||
# via twine
|
||||
rfc3986==2.0.0
|
||||
# via twine
|
||||
rich==13.7.1
|
||||
# via twine
|
||||
setuptools==69.1.1
|
||||
# via packse
|
||||
trove-classifiers==2024.3.3
|
||||
# via hatchling
|
||||
twine==4.0.2
|
||||
# via packse
|
||||
urllib3==2.2.1
|
||||
# via
|
||||
# requests
|
||||
# twine
|
||||
zipp==3.17.0
|
||||
# via importlib-metadata
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Sync test scenarios with the pinned version of packse.
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# Install the pinned packse version in a temporary virtual environment, fetch scenarios, and regenerate test cases and snapshots:
|
||||
#
|
||||
# $ ./scripts/scenarios/sync.sh
|
||||
#
|
||||
# Additional arguments are passed to `./scripts/scenarios/generate.py`, for example:
|
||||
#
|
||||
# $ ./scripts/scenarios/sync.sh --verbose --no-snapshot-update
|
||||
#
|
||||
# For development purposes, the `./scripts/scenarios/generate.py` script can be used directly to generate
|
||||
# test cases from a local set of scenarios.
|
||||
|
||||
set -eu
|
||||
|
||||
script_root="$(realpath "$(dirname "$0")")"
|
||||
|
||||
cd "$script_root"
|
||||
echo "Setting up a temporary environment..."
|
||||
uv venv
|
||||
|
||||
source ".venv/bin/activate"
|
||||
uv pip install -r requirements.txt --refresh-package packse
|
||||
|
||||
echo "Fetching packse scenarios..."
|
||||
packse fetch --dest "$script_root/scenarios" --force
|
||||
|
||||
python "$script_root/generate.py" "$script_root/scenarios" "$@"
|
||||
|
||||
# Cleanup
|
||||
rm -r "$script_root/scenarios"
|
||||
|
|
@ -27,7 +27,7 @@ fn command(context: &TestContext, python_versions: &[&str]) -> Command {
|
|||
.arg("compile")
|
||||
.arg("requirements.in")
|
||||
.arg("--index-url")
|
||||
.arg("https://test.pypi.org/simple")
|
||||
.arg("{{index_url}}")
|
||||
.arg("--find-links")
|
||||
.arg("{{vendor_links}}")
|
||||
.arg("--cache-dir")
|
||||
|
|
@ -48,14 +48,12 @@ fn command(context: &TestContext, python_versions: &[&str]) -> Command {
|
|||
|
||||
{{#scenarios}}
|
||||
|
||||
/// {{name}}
|
||||
///
|
||||
{{#description_lines}}
|
||||
/// {{.}}
|
||||
{{/description_lines}}
|
||||
///
|
||||
/// ```text
|
||||
/// {{version}}
|
||||
/// {{name}}
|
||||
{{#tree}}
|
||||
/// {{.}}
|
||||
{{/tree}}
|
||||
|
|
@ -70,7 +68,7 @@ fn {{module_name}}() -> Result<()> {
|
|||
{{#packages}}
|
||||
filters.push((r"{{name}}", "{{cute_name}}"));
|
||||
{{/packages}}
|
||||
filters.push((r"-{{version}}", ""));
|
||||
filters.push((r"{{name}}-", "pkg-"));
|
||||
|
||||
let requirements_in = context.temp_dir.child("requirements.in");
|
||||
{{#root.requires}}
|
||||
|
|
@ -86,11 +84,11 @@ fn {{module_name}}() -> Result<()> {
|
|||
{{/resolver_options.prereleases}}
|
||||
{{#resolver_options.no_build}}
|
||||
.arg("--only-binary")
|
||||
.arg("{{.}}-{{version}}")
|
||||
.arg("{{.}}")
|
||||
{{/resolver_options.no_build}}
|
||||
{{#resolver_options.no_binary}}
|
||||
.arg("--no-binary")
|
||||
.arg("{{.}}-{{version}}")
|
||||
.arg("{{.}}")
|
||||
{{/resolver_options.no_binary}}
|
||||
{{#resolver_options.python}}
|
||||
.arg("--python-version={{.}}")
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ fn command(context: &TestContext) -> Command {
|
|||
.arg("pip")
|
||||
.arg("install")
|
||||
.arg("--index-url")
|
||||
.arg("https://test.pypi.org/simple")
|
||||
.arg("{{index_url}}")
|
||||
.arg("--find-links")
|
||||
.arg("{{vendor_links}}")
|
||||
.arg("--cache-dir")
|
||||
|
|
@ -67,14 +67,12 @@ fn command(context: &TestContext) -> Command {
|
|||
|
||||
{{#scenarios}}
|
||||
|
||||
/// {{name}}
|
||||
///
|
||||
{{#description_lines}}
|
||||
/// {{.}}
|
||||
{{/description_lines}}
|
||||
///
|
||||
/// ```text
|
||||
/// {{version}}
|
||||
/// {{name}}
|
||||
{{#tree}}
|
||||
/// {{.}}
|
||||
{{/tree}}
|
||||
|
|
@ -88,7 +86,7 @@ fn {{module_name}}() {
|
|||
{{#packages}}
|
||||
filters.push((r"{{name}}", "{{cute_name}}"));
|
||||
{{/packages}}
|
||||
filters.push((r"-{{version}}", ""));
|
||||
filters.push((r"{{name}}-", "pkg-"));
|
||||
|
||||
uv_snapshot!(filters, command(&context)
|
||||
{{#resolver_options.prereleases}}
|
||||
|
|
@ -96,11 +94,11 @@ fn {{module_name}}() {
|
|||
{{/resolver_options.prereleases}}
|
||||
{{#resolver_options.no_build}}
|
||||
.arg("--only-binary")
|
||||
.arg("{{.}}-{{version}}")
|
||||
.arg("{{.}}")
|
||||
{{/resolver_options.no_build}}
|
||||
{{#resolver_options.no_binary}}
|
||||
.arg("--no-binary")
|
||||
.arg("{{.}}-{{version}}")
|
||||
.arg("{{.}}")
|
||||
{{/resolver_options.no_binary}}
|
||||
{{#root.requires}}
|
||||
.arg("{{requirement}}")
|
||||
|
|
|
|||
|
|
@ -1,251 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generates and updates snapshot test cases from packse scenarios.
|
||||
|
||||
Usage:
|
||||
|
||||
Regenerate the scenario test file:
|
||||
|
||||
$ ./scripts/scenarios/update.py
|
||||
|
||||
Scenarios are pinned to a specific commit. Change the `PACKSE_COMMIT` constant to update them.
|
||||
|
||||
Scenarios can be developed locally with the following workflow:
|
||||
|
||||
Install the local version of packse
|
||||
|
||||
$ pip install -e <path to packse>
|
||||
|
||||
From the packse repository, build and publish the scenarios to a local index
|
||||
|
||||
$ packse index up --bg
|
||||
$ packse build scenarios/*
|
||||
$ packse publish dist/* --index-url http://localhost:3141/packages/local --anonymous
|
||||
|
||||
Override the default PyPI index for uv and update the scenarios
|
||||
|
||||
$ UV_INDEX_URL="http://localhost:3141/packages/all/+simple" ./scripts/scenarios/update.py
|
||||
|
||||
Requirements:
|
||||
|
||||
Requires `packse` and `chevron-blue`.
|
||||
|
||||
$ pip install -r scripts/scenarios/requirements.txt
|
||||
|
||||
Also supports a local, editable requirement on `packse`.
|
||||
|
||||
Uses `git`, `rustfmt`, and `cargo insta test` requirements from the project.
|
||||
"""
|
||||
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import textwrap
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
PACKSE_COMMIT = "4f39539c1b858e28268554604e75c69e25272e5a"
|
||||
TOOL_ROOT = Path(__file__).parent
|
||||
TEMPLATES = TOOL_ROOT / "templates"
|
||||
INSTALL_TEMPLATE = TEMPLATES / "install.mustache"
|
||||
COMPILE_TEMPLATE = TEMPLATES / "compile.mustache"
|
||||
PACKSE = TOOL_ROOT / "packse-scenarios"
|
||||
REQUIREMENTS = TOOL_ROOT / "requirements.txt"
|
||||
PROJECT_ROOT = TOOL_ROOT.parent.parent
|
||||
TESTS = PROJECT_ROOT / "crates" / "uv" / "tests"
|
||||
INSTALL_TESTS = TESTS / "pip_install_scenarios.rs"
|
||||
COMPILE_TESTS = TESTS / "pip_compile_scenarios.rs"
|
||||
|
||||
CUTE_NAMES = {
|
||||
"a": "albatross",
|
||||
"b": "bluebird",
|
||||
"c": "crow",
|
||||
"d": "duck",
|
||||
"e": "eagle",
|
||||
"f": "flamingo",
|
||||
"g": "goose",
|
||||
"h": "heron",
|
||||
}
|
||||
|
||||
try:
|
||||
import packse
|
||||
except ImportError:
|
||||
print(
|
||||
f"missing requirement `packse`: install the requirements at {REQUIREMENTS.relative_to(PROJECT_ROOT)}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
exit(1)
|
||||
|
||||
|
||||
try:
|
||||
import chevron_blue
|
||||
except ImportError:
|
||||
print(
|
||||
f"missing requirement `chevron-blue`: install the requirements at {REQUIREMENTS.relative_to(PROJECT_ROOT)}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
exit(1)
|
||||
|
||||
|
||||
if packse.__development_base_path__.name != "packse":
|
||||
# Not a local editable installation, download latest scenarios
|
||||
if PACKSE.exists():
|
||||
shutil.rmtree(PACKSE)
|
||||
|
||||
print("Downloading scenarios from packse repository...", file=sys.stderr)
|
||||
# Perform a sparse checkout where we only grab the `scenarios` folder
|
||||
subprocess.check_call(
|
||||
[
|
||||
"git",
|
||||
"clone",
|
||||
"-n",
|
||||
"--depth=1",
|
||||
"--filter=tree:0",
|
||||
"https://github.com/zanieb/packse",
|
||||
str(PACKSE),
|
||||
],
|
||||
cwd=TOOL_ROOT,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.STDOUT,
|
||||
)
|
||||
subprocess.check_call(
|
||||
["git", "sparse-checkout", "set", "--no-cone", "scenarios"],
|
||||
cwd=PACKSE,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.STDOUT,
|
||||
)
|
||||
subprocess.check_call(
|
||||
["git", "checkout", PACKSE_COMMIT],
|
||||
cwd=PACKSE,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.STDOUT,
|
||||
)
|
||||
scenarios_path = str(PACKSE / "scenarios")
|
||||
commit = PACKSE_COMMIT
|
||||
|
||||
else:
|
||||
print(
|
||||
f"Using scenarios in packse repository at {packse.__development_base_path__}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
scenarios_path = str(packse.__development_base_path__ / "scenarios")
|
||||
|
||||
# Get the commit from the repository
|
||||
commit = (
|
||||
subprocess.check_output(
|
||||
["git", "show", "-s", "--format=%H", "HEAD"],
|
||||
cwd=packse.__development_base_path__,
|
||||
)
|
||||
.decode()
|
||||
.strip()
|
||||
)
|
||||
|
||||
if commit != PACKSE_COMMIT:
|
||||
print(f"WARNING: Expected commit {PACKSE_COMMIT!r} but found {commit!r}.")
|
||||
|
||||
print("Loading scenario metadata...", file=sys.stderr)
|
||||
data = json.loads(
|
||||
subprocess.check_output(
|
||||
[
|
||||
sys.executable,
|
||||
"-m",
|
||||
"packse",
|
||||
"inspect",
|
||||
"--short-names",
|
||||
scenarios_path,
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
data["scenarios"] = [
|
||||
scenario
|
||||
for scenario in data["scenarios"]
|
||||
# Drop the example scenario
|
||||
if scenario["name"] != "example"
|
||||
]
|
||||
|
||||
# Wrap the description onto multiple lines
|
||||
for scenario in data["scenarios"]:
|
||||
scenario["description_lines"] = textwrap.wrap(scenario["description"], width=80)
|
||||
|
||||
|
||||
# Wrap the expected explanation onto multiple lines
|
||||
for scenario in data["scenarios"]:
|
||||
expected = scenario["expected"]
|
||||
expected["explanation_lines"] = (
|
||||
textwrap.wrap(expected["explanation"], width=80)
|
||||
if expected["explanation"]
|
||||
else []
|
||||
)
|
||||
|
||||
# Generate cute names for each scenario
|
||||
for scenario in data["scenarios"]:
|
||||
for package in scenario["packages"]:
|
||||
package["cute_name"] = CUTE_NAMES[package["name"][0]]
|
||||
|
||||
|
||||
# Split scenarios into `install` and `compile` cases
|
||||
install_scenarios = []
|
||||
compile_scenarios = []
|
||||
|
||||
for scenario in data["scenarios"]:
|
||||
if (scenario["resolver_options"] or {}).get("python") is not None:
|
||||
compile_scenarios.append(scenario)
|
||||
else:
|
||||
install_scenarios.append(scenario)
|
||||
|
||||
for template, tests, scenarios in [
|
||||
(INSTALL_TEMPLATE, INSTALL_TESTS, install_scenarios),
|
||||
(COMPILE_TEMPLATE, COMPILE_TESTS, compile_scenarios),
|
||||
]:
|
||||
data = {"scenarios": scenarios}
|
||||
|
||||
# Add generated metadata
|
||||
data["generated_from"] = f"https://github.com/zanieb/packse/tree/{commit}/scenarios"
|
||||
data["generated_with"] = " ".join(sys.argv)
|
||||
data[
|
||||
"vendor_links"
|
||||
] = f"https://raw.githubusercontent.com/zanieb/packse/{commit}/vendor/links.html"
|
||||
|
||||
# Render the template
|
||||
print(f"Rendering template {template.name}", file=sys.stderr)
|
||||
output = chevron_blue.render(
|
||||
template=template.read_text(), data=data, no_escape=True, warn=True
|
||||
)
|
||||
|
||||
# Update the test files
|
||||
print(
|
||||
f"Updating test file at `{tests.relative_to(PROJECT_ROOT)}`...",
|
||||
file=sys.stderr,
|
||||
)
|
||||
with open(tests, "wt") as test_file:
|
||||
test_file.write(output)
|
||||
|
||||
# Format
|
||||
print(
|
||||
"Formatting test file...",
|
||||
file=sys.stderr,
|
||||
)
|
||||
subprocess.check_call(["rustfmt", str(tests)])
|
||||
|
||||
# Update snapshots
|
||||
print("Updating snapshots...\n", file=sys.stderr)
|
||||
subprocess.call(
|
||||
[
|
||||
"cargo",
|
||||
"insta",
|
||||
"test",
|
||||
"--features",
|
||||
"pypi,python",
|
||||
"--accept",
|
||||
"--test-runner",
|
||||
"nextest",
|
||||
"--test",
|
||||
tests.with_suffix("").name,
|
||||
],
|
||||
cwd=PROJECT_ROOT,
|
||||
)
|
||||
|
||||
print("\nDone!", file=sys.stderr)
|
||||
Loading…
Reference in New Issue