Skip to content

Commit

Permalink
uv/tests: add new 'ecosystem' integration test
Browse files Browse the repository at this point in the history
This adds a new top-level directory with bare-bones directories for a
sampling of ecosystem projects. The idea is for each directory to have
enough that `uv lock` can run.

The point of these tests is to 1) ensure resolution works in common
cases and 2) track changes to resolutions (and the lock file) in real
world projects.

Unfortunately, it does look like in some cases, re-running `uv lock`
results in changes to the lock file. For those cases, I've disabled the
deterministic checking in exchange for getting the lock files tracked
in tests. I haven't investigated yet whether either of #5733 or #5887
fix the deterministic problem.

There is probably a better way to go about integrating ecosystem
projects. In particular, it would be really nice if there was a good
flow for upgrading ecosystem packages to their latest version. The main
complexity is that some projects require edits to their `pyproject.toml`
(or a complete migration from non-`pyproject.toml` to `pyproject.toml`).
Although, the projects added here in this initial set were limited to
those that didn't require any changes.
  • Loading branch information
BurntSushi committed Aug 12, 2024
1 parent 73fbe27 commit ada2745
Show file tree
Hide file tree
Showing 28 changed files with 29,994 additions and 0 deletions.
1 change: 1 addition & 0 deletions _typos.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[files]
extend-exclude = [
"**/snapshots/",
"ecosystem/**",
"scripts/**/*.in",
]
ignore-hidden = false
Expand Down
164 changes: 164 additions & 0 deletions crates/uv/tests/ecosystem.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
#![cfg(all(feature = "python", feature = "pypi"))]

use std::path::{Path, PathBuf};

use anyhow::Result;
use assert_fs::prelude::*;
use insta::assert_snapshot;

use common::{deterministic_lock, TestContext};

mod common;

// These tests just run `uv lock` on an assorted of ecosystem
// projects.
//
// The idea here is to provide a body of ecosystem projects that
// let us very easily observe any changes to the actual resolution
// produced in the lock file.

/// We use a different exclude newer here because, at the time of
/// creating these benchmarks, the `pyproject.toml` files from the
/// projects wouldn't work with the exclude-newer value we use
/// elsewhere (which is 2024-03-25 at time of writing). So Instead of
/// bumping everything else, we just use our own here.
static EXCLUDE_NEWER: &str = "2024-08-08T00:00:00Z";

// Source: https://github.com/astral-sh/packse/blob/737bc7008fa7825669ee50e90d9d0c26df32a016/pyproject.toml
#[test]
fn packse() -> Result<()> {
lock_ecosystem_package("3.12", "packse")
}

// Source: https://github.com/konstin/github-wikidata-bot/blob/8218d20985eb480cb8633026f9dabc9e5ec4b5e3/pyproject.toml
#[test]
fn github_wikidata_bot() -> Result<()> {
lock_ecosystem_package("3.12", "github-wikidata-bot")
}

// Source: https://github.com/psf/black/blob/9ff047a9575f105f659043f28573e1941e9cdfb3/pyproject.toml
#[test]
fn black() -> Result<()> {
lock_ecosystem_package("3.12", "black")
}

// Source: https://github.com/home-assistant/core/blob/7c5fcec062e1d2cfaa794a169fafa629a70bbc9e/pyproject.toml
#[test]
fn home_assistant_core() -> Result<()> {
lock_ecosystem_package("3.12", "home-assistant-core")
}

// Source: https://github.com/konstin/transformers/blob/da3c00433d93e43bf1e7360b1057e8c160e7978e/pyproject.toml
#[test]
fn transformers() -> Result<()> {
lock_ecosystem_package_non_deterministic("3.12", "transformers")
}

// Source: https://github.com/konstin/warehouse/blob/baae127d90417104c8dee3fdd3855e2ba17aa428/pyproject.toml
#[test]
fn warehouse() -> Result<()> {
lock_ecosystem_package_non_deterministic("3.11", "warehouse")
}

// Currently ignored because the project doesn't build with `uv` yet.
//
// Source: https://github.com/apache/airflow/blob/c55438d9b2eb9b6680641eefdd0cbc67a28d1d29/pyproject.toml
#[ignore]
#[test]
fn airflow() -> Result<()> {
lock_ecosystem_package("3.12", "airflow")
}

// Currently ignored because the project doesn't build with `uv` yet.
//
// Source: https://github.com/pretix/pretix/blob/a682eab18e9421dc0aff18a6ed8495aa3c75c39b/pyproject.toml
#[ignore]
#[test]
fn pretix() -> Result<()> {
lock_ecosystem_package("3.12", "pretix")
}

/// Does a lock on the given ecosystem package for the given name. That
/// is, there should be a directory at `./ecosystem/{name}` from the
/// root of the `uv` repository.
fn lock_ecosystem_package(python_version: &str, name: &str) -> Result<()> {
let dir = PathBuf::from(format!("../../ecosystem/{name}"));
let context = TestContext::new(python_version);
setup_project_dir(&context, &dir)?;

deterministic_lock! { context =>
let mut cmd = context.lock();
cmd.env("UV_EXCLUDE_NEWER", EXCLUDE_NEWER);
let (snapshot, _) = common::run_and_format(
&mut cmd,
context.filters(),
name,
Some(common::WindowsFilters::Platform),
);
insta::assert_snapshot!(format!("{name}-uv-lock-output"), snapshot);

let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(format!("{name}-lock-file"), lock);
});
}
Ok(())
}

/// This is like `lock_ecosystem_package`, but does not assert that a
/// re-run of `uv lock` does not change the lock file.
///
/// Ideally, this routine would never be used. But it was added as
/// a stop-gap to enable at least tracking the lock files of some
/// ecosystem packages even if re-locking is producing different
/// results.
fn lock_ecosystem_package_non_deterministic(python_version: &str, name: &str) -> Result<()> {
let dir = PathBuf::from(format!("../../ecosystem/{name}"));
let context = TestContext::new(python_version);
setup_project_dir(&context, &dir)?;

let mut cmd = context.lock();
cmd.env("UV_EXCLUDE_NEWER", EXCLUDE_NEWER);
let (snapshot, _) = common::run_and_format(
&mut cmd,
context.filters(),
name,
Some(common::WindowsFilters::Platform),
);
insta::assert_snapshot!(format!("{name}-uv-lock-output"), snapshot);

let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(format!("{name}-lock-file"), lock);
});
Ok(())
}

/// Copies the project specific files from `project_dir` into the given
/// test context.
fn setup_project_dir(ctx: &TestContext, project_dir: &Path) -> Result<()> {
// Ideally I think we'd probably just do a recursive copy,
// but for now we just look for the specific files we want.
let required_files = ["pyproject.toml"];
for file_name in required_files {
let file_contents = fs_err::read_to_string(project_dir.join(file_name))?;
let test_file = ctx.temp_dir.child(file_name);
test_file.write_str(&file_contents)?;
}

let optional_files = ["PKG-INFO"];
for file_name in optional_files {
let path = project_dir.join(file_name);
if !path.exists() {
continue;
}
let file_contents = fs_err::read_to_string(path)?;
let test_file = ctx.temp_dir.child(file_name);
test_file.write_str(&file_contents)?;
}
Ok(())
}
Loading

0 comments on commit ada2745

Please sign in to comment.