Open-Source Wikis

/

uv

/

How to contribute

/

Testing

astral-sh/uv

Testing

uv leans hard on integration tests with snapshot assertions. This page describes how the test infrastructure is laid out and how to add tests effectively.

Test layout

Tests are split between unit tests (in each crate's source as #[cfg(test)] mod tests) and integration tests (in crates/uv/tests/it/, plus a few crate-local integration suites under crates/<crate>/tests/).

Integration tests dominate. The crates/uv/tests/it/ directory contains:

File What it covers
lock.rs (35,569 lines) uv lock behavior and lockfile output
pip_compile.rs (18,574 lines) uv pip compile
lock_conflict.rs (16,570 lines) Conflict marker handling in lockfiles
sync.rs (16,268 lines) uv sync
edit.rs (15,231 lines) uv add / uv remove / version edits
pip_install.rs (15,136 lines) uv pip install
show_settings.rs (11,702 lines) Settings resolution across config sources
export.rs, tool_run.rs, version.rs, run.rs, … Other commands

Each file is a single Rust source with hundreds of #[test] functions. Most use the uv_snapshot! macro to assert on the full stdout/stderr/exit code of a uv invocation.

Running tests

The recommended runner is cargo nextest:

# Run a single test by name
$ cargo nextest run -E 'test(test_basic_add)'

# Run all tests
$ cargo nextest run --workspace

# Run tests in a single crate
$ cargo nextest run -p uv-resolver

Snapshot management uses cargo-insta:

# Run and accept new snapshots
$ cargo insta test --accept --test-runner nextest

# Update snapshots for one test
$ cargo insta test --accept --test-runner nextest -- test_basic_add

# Review pending snapshot diffs
$ cargo insta review

If you've staged snapshot changes through CI rather than locally, use ./scripts/apply-ci-snapshots.sh to pull them into your working tree.

The uv_snapshot! macro

uv defines a custom uv_snapshot! macro for asserting CLI output. The pattern looks like:

#[test]
fn test_add() {
    let context = TestContext::new("3.12");
    uv_snapshot!(context.filters(), context.add().arg("requests"), @"");
}

The macro:

  • Runs the command using assert_cmd::Command.
  • Captures stdout, stderr, and exit code.
  • Applies the test context's filters (e.g., normalizing temp paths, durations, hashes).
  • Compares against the inline snapshot string.

context.filters() is critical for stable output. Common filters strip:

  • Temporary directory paths.
  • Resolved durations (Resolved 5 packages in 12ms).
  • Hash digests.
  • OS-specific path separators.

Always copy the style of nearby tests in the same file when adding new cases.

Test contexts

Most tests construct a TestContext (defined in crates/uv/tests/it/common/). The context:

  • Creates a fresh temp directory.
  • Optionally provisions a Python interpreter at the requested version.
  • Sets up a clean cache and config root.
  • Provides builder methods (context.add(), context.lock(), context.run(), …) that return pre-configured assert_cmd::Commands with the right environment.

This keeps test functions short — most are 5 to 30 lines including the snapshot.

Python interpreters in tests

Tests that need Python use the test context's --python setup. To make sure the requested versions are available locally:

$ cargo run python install

uv's CI uses cargo run python install plus UV_PYTHON_INSTALL_DIR to provision a stable set of interpreters before running the test suite.

The test fixtures cover CPython (multiple minor versions), PyPy, GraalPy, and the freethreaded CPython variant.

Git and Git LFS

Some tests exercise Git source distributions and need a working git binary. A smaller subset also requires Git LFS. You can disable both via --no-default-features --features ... if your environment can't provide them:

$ cargo nextest run --no-default-features --features other,relevant,features

The git and git-lfs features in crates/uv gate the relevant tests.

System Python tests

.github/workflows/test-system.yml runs uv against system Pythons on macOS, Linux, and Windows. The fixtures are in scripts/check_system_python.py and scripts/check_registry.py. These tests are not normally run locally — they verify behaviour against real installs you might not have on hand.

Smoke and ecosystem tests

  • test-smoke.yml runs a small, fast check on each platform that the binary works end to end.
  • test-ecosystem.yml runs uv against a curated list of real PyPI projects to catch regressions in resolution. The corpus lives in scripts/popular_packages/ and test/requirements/.

Trampoline tests

test-windows-trampolines.yml builds and runs the Windows trampoline binaries via QEMU/Wine when full Windows runners aren't appropriate. The associated Rust integration tests live in crates/uv-trampoline-builder/.

Updating dependencies

Tests can break when dependencies change. The AGENTS.md is explicit:

NEVER update all dependencies in the lockfile and ALWAYS use cargo update --precise to make lockfile changes.

Renovate handles routine bumps via PRs to main.

Profiling tests

For perf-sensitive changes, build with the profiling profile and use the tracing-durations-export feature:

$ RUST_LOG=uv=info \
  TRACING_DURATIONS_FILE=target/traces/jupyter.ndjson \
  cargo run --features tracing-durations-export --profile profiling -- pip compile test/requirements/jupyter.in

The scripts/benchmark/ package provides higher-level harnesses. See Tooling for details.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Testing – uv wiki | Factory