use std::env::consts::EXE_SUFFIX;
use anyhow::Result;
use assert_cmd::prelude::*;
use assert_fs::fixture::ChildPath;
use assert_fs::prelude::*;
use fs_err as fs;
use indoc::indoc;
use predicates::Predicate;
use url::Url;
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};
use uv_fs::{Simplified, copy_dir_all};
use uv_static::EnvVars;
use uv_test::{download_to_disk, site_packages_path, uv_snapshot};
#[test]
fn missing_requirements_txt() {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: File not found: `requirements.txt`
");
requirements_txt.assert(predicates::path::missing());
}
#[test]
fn missing_venv() -> Result<()> {
let context = uv_test::test_context!("3.12")
.with_filtered_virtualenv_bin()
.with_filtered_python_names();
let requirements = context.temp_dir.child("requirements.txt");
requirements.write_str("anyio")?;
fs::remove_dir_all(&context.venv)?;
uv_snapshot!(context.filters(), context.pip_sync().arg("requirements.txt"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Failed to inspect Python interpreter from active virtual environment at `.venv/[BIN]/[PYTHON]`
Caused by: Python interpreter not found at `[VENV]/[BIN]/[PYTHON]`
");
assert!(predicates::path::missing().eval(&context.venv));
uv_snapshot!(context.filters(), context.pip_sync().arg("requirements.txt").env_remove(EnvVars::VIRTUAL_ENV), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: No virtual environment found; run `uv venv` to create an environment, or pass `--system` to install into a non-virtual environment
");
assert!(predicates::path::missing().eval(&context.venv));
Ok(())
}
#[test]
fn missing_system() -> Result<()> {
let context = uv_test::test_context_with_versions!(&[]);
let requirements = context.temp_dir.child("requirements.txt");
requirements.write_str("anyio")?;
uv_snapshot!(context.filters(), context.pip_sync().arg("requirements.txt").arg("--system"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: No system Python installation found
");
Ok(())
}
#[test]
fn install() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("MarkupSafe==2.1.3")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ markupsafe==2.1.3
"
);
assert!(
!context
.site_packages()
.join("markupsafe")
.join("__pycache__")
.join("__init__.cpython-312.pyc")
.exists()
);
context
.assert_command("from markupsafe import Markup")
.success();
fs::remove_dir_all(context.cache_dir.path())?;
context
.assert_command("from markupsafe import Markup")
.success();
Ok(())
}
#[test]
fn install_copy() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("MarkupSafe==2.1.3")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--link-mode")
.arg("copy")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ markupsafe==2.1.3
"
);
context
.assert_command("from markupsafe import Markup")
.success();
fs::remove_dir_all(context.cache_dir.path())?;
context
.assert_command("from markupsafe import Markup")
.success();
Ok(())
}
#[test]
fn install_hardlink() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("MarkupSafe==2.1.3")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--link-mode")
.arg("hardlink")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ markupsafe==2.1.3
"
);
context
.assert_command("from markupsafe import Markup")
.success();
fs::remove_dir_all(context.cache_dir.path())?;
context
.assert_command("from markupsafe import Markup")
.success();
Ok(())
}
#[test]
fn install_hardlink_after_emlink() -> anyhow::Result<()> {
use walkdir::WalkDir;
let Some(context) = uv_test::test_context!("3.12").with_cache_on_lowlinks_fs()? else {
return Ok(());
};
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("iniconfig==2.0.0")?;
context
.pip_sync()
.arg("requirements.txt")
.arg("--link-mode")
.arg("hardlink")
.assert()
.success();
let cached_file = WalkDir::new(context.cache_dir.path())
.into_iter()
.filter_map(Result::ok)
.find(|e| e.file_type().is_file() && e.path().extension().is_some_and(|ext| ext == "py"))
.expect("should find a cached file")
.into_path();
let hardlink_dir = tempfile::tempdir_in(
context
.cache_dir
.parent()
.expect("cache dir should have a parent"),
)?;
let mut hit_emlink = false;
for i in 0..66000 {
let link_path = hardlink_dir.path().join(format!("link_{i}"));
match fs::hard_link(&cached_file, &link_path) {
Ok(()) => {}
Err(e) if e.kind() == std::io::ErrorKind::TooManyLinks => {
hit_emlink = true;
break;
}
Err(err) => {
return Err(err.into());
}
}
}
assert!(
hit_emlink,
"Expected to hit TooManyLinks while creating hardlinks"
);
let venv2_dir = tempfile::tempdir_in(
context
.cache_dir
.parent()
.expect("cache dir should have a parent"),
)?;
let venv2 = venv2_dir.path().join("venv2");
context.venv().arg(&venv2).assert().success();
context
.pip_sync()
.arg("requirements.txt")
.arg("--link-mode")
.arg("hardlink")
.env(EnvVars::VIRTUAL_ENV, &venv2)
.assert()
.success();
let extra_link = hardlink_dir.path().join("post_recovery_link");
fs::hard_link(&cached_file, &extra_link)?;
Ok(())
}
#[test]
#[cfg(unix)] fn install_symlink() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("MarkupSafe==2.1.3")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--link-mode")
.arg("symlink")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ markupsafe==2.1.3
"
);
context
.assert_command("from markupsafe import Markup")
.success();
fs::remove_dir_all(context.cache_dir.path())?;
context
.assert_command("from markupsafe import Markup")
.failure();
Ok(())
}
#[test]
fn install_symlink_no_cache() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("MarkupSafe==2.1.3")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--link-mode")
.arg("symlink")
.arg("--no-cache")
.arg("--strict"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
error: Symlink-based installation is not supported with `--no-cache`. The created environment will be rendered unusable by the removal of the cache.
"
);
Ok(())
}
#[test]
fn install_many() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("MarkupSafe==2.1.3\ntomli==2.0.1")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Prepared 2 packages in [TIME]
Installed 2 packages in [TIME]
+ markupsafe==2.1.3
+ tomli==2.0.1
"
);
context
.assert_command("from markupsafe import Markup; import tomli")
.success();
Ok(())
}
#[test]
fn noop() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("MarkupSafe==2.1.3")?;
context
.pip_sync()
.arg("requirements.txt")
.arg("--strict")
.assert()
.success();
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Checked 1 package in [TIME]
"
);
context
.assert_command("from markupsafe import Markup")
.success();
Ok(())
}
#[test]
fn pip_sync_empty() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.touch()?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
warning: Requirements file `requirements.txt` does not contain any dependencies
No requirements found (hint: use `--allow-empty-requirements` to clear the environment)
"
);
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--allow-empty-requirements"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
warning: Requirements file `requirements.txt` does not contain any dependencies
Resolved in [TIME]
Checked in [TIME]
"
);
requirements_txt.write_str("iniconfig==2.0.0")?;
context
.pip_sync()
.arg("requirements.txt")
.assert()
.success();
requirements_txt.write_str("")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--allow-empty-requirements"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
warning: Requirements file `requirements.txt` does not contain any dependencies
Resolved in [TIME]
Uninstalled 1 package in [TIME]
- iniconfig==2.0.0
"
);
Ok(())
}
#[test]
fn link() -> Result<()> {
let context1 = uv_test::test_context!("3.12");
let requirements_txt = context1.temp_dir.child("requirements.txt");
requirements_txt.write_str("iniconfig==2.0.0")?;
context1
.pip_sync()
.arg(requirements_txt.path())
.arg("--strict")
.assert()
.success();
let context2 = uv_test::test_context!("3.12");
let mut cmd = context1.pip_sync();
cmd.env(EnvVars::VIRTUAL_ENV, context2.venv.as_os_str())
.current_dir(&context2.temp_dir);
uv_snapshot!(cmd
.arg(requirements_txt.path())
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Installed 1 package in [TIME]
+ iniconfig==2.0.0
"
);
context2
.python_command()
.arg("-c")
.arg("import iniconfig")
.current_dir(&context2.temp_dir)
.assert()
.success();
Ok(())
}
#[test]
fn add_remove() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("iniconfig==2.0.0")?;
context
.pip_sync()
.arg("requirements.txt")
.arg("--strict")
.assert()
.success();
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("tomli==2.0.1")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
- iniconfig==2.0.0
+ tomli==2.0.1
"
);
context.assert_command("import tomli").success();
context.assert_command("import markupsafe").failure();
Ok(())
}
#[test]
fn install_sequential() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("iniconfig==2.0.0")?;
context
.pip_sync()
.arg("requirements.txt")
.arg("--strict")
.assert()
.success();
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("iniconfig==2.0.0\ntomli==2.0.1")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ tomli==2.0.1
"
);
context
.assert_command("import iniconfig; import tomli")
.success();
Ok(())
}
#[test]
fn upgrade() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("tomli==2.0.0")?;
context
.pip_sync()
.arg("requirements.txt")
.arg("--strict")
.assert()
.success();
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("tomli==2.0.1")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
- tomli==2.0.0
+ tomli==2.0.1
"
);
context.assert_command("import tomli").success();
Ok(())
}
#[test]
fn install_url() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("werkzeug @ https://files.pythonhosted.org/packages/ff/1d/960bb4017c68674a1cb099534840f18d3def3ce44aed12b5ed8b78e0153e/Werkzeug-2.0.0-py3-none-any.whl")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ werkzeug==2.0.0 (from https://files.pythonhosted.org/packages/ff/1d/960bb4017c68674a1cb099534840f18d3def3ce44aed12b5ed8b78e0153e/Werkzeug-2.0.0-py3-none-any.whl)
"
);
context.assert_command("import werkzeug").success();
Ok(())
}
#[test]
#[cfg(feature = "test-git")]
fn install_git_commit() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage@b270df1a2fb5d012294e9aaf05e7e0bab1e6a389")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ uv-public-pypackage==0.1.0 (from git+https://github.com/astral-test/uv-public-pypackage@b270df1a2fb5d012294e9aaf05e7e0bab1e6a389)
"
);
context
.assert_command("import uv_public_pypackage")
.success();
Ok(())
}
#[test]
#[cfg(feature = "test-git")]
fn install_git_tag() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(
"uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage@test-tag",
)?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ uv-public-pypackage==0.1.0 (from git+https://github.com/astral-test/uv-public-pypackage@0dacfd662c64cb4ceb16e6cf65a157a8b715b979)
"
);
context
.assert_command("import uv_public_pypackage")
.success();
Ok(())
}
#[test]
#[cfg(feature = "test-git")]
fn install_git_subdirectories() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("example-pkg-a @ git+https://github.com/pypa/sample-namespace-packages.git@df7530eeb8fa0cb7dbb8ecb28363e8e36bfa2f45#subdirectory=pkg_resources/pkg_a\nexample-pkg-b @ git+https://github.com/pypa/sample-namespace-packages.git@df7530eeb8fa0cb7dbb8ecb28363e8e36bfa2f45#subdirectory=pkg_resources/pkg_b")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Prepared 2 packages in [TIME]
Installed 2 packages in [TIME]
+ example-pkg-a==1 (from git+https://github.com/pypa/sample-namespace-packages.git@df7530eeb8fa0cb7dbb8ecb28363e8e36bfa2f45#subdirectory=pkg_resources/pkg_a)
+ example-pkg-b==1 (from git+https://github.com/pypa/sample-namespace-packages.git@df7530eeb8fa0cb7dbb8ecb28363e8e36bfa2f45#subdirectory=pkg_resources/pkg_b)
"
);
context.assert_command("import example_pkg").success();
context.assert_command("import example_pkg.a").success();
context.assert_command("import example_pkg.b").success();
Ok(())
}
#[test]
fn install_sdist() -> Result<()> {
let context = uv_test::test_context!("3.12").with_exclude_newer("2025-01-29T00:00:00Z");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("source-distribution==0.0.1")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ source-distribution==0.0.1
"
);
context
.assert_command("import source_distribution")
.success();
Ok(())
}
#[test]
fn install_sdist_url() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("source-distribution @ https://files.pythonhosted.org/packages/10/1f/57aa4cce1b1abf6b433106676e15f9fa2c92ed2bd4cf77c3b50a9e9ac773/source_distribution-0.0.1.tar.gz")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ source-distribution==0.0.1 (from https://files.pythonhosted.org/packages/10/1f/57aa4cce1b1abf6b433106676e15f9fa2c92ed2bd4cf77c3b50a9e9ac773/source_distribution-0.0.1.tar.gz)
"
);
context
.assert_command("import source_distribution")
.success();
Ok(())
}
#[test]
fn install_sdist_archive_type_bz2() -> Result<()> {
let context = uv_test::test_context!("3.9");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(&format!(
"bz2 @ {}",
context
.workspace_root
.join("test/links/bz2-1.0.0.tar.bz2")
.display()
))?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
warning: bz2 @ file://[WORKSPACE]/test/links/bz2-1.0.0.tar.bz2 is not a standards-compliant source distribution: expected '.tar.gz' but found '.tar.bz2'. A future version of uv will reject source distributions that do not meet the requirements specified in PEP 625
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ bz2==1.0.0 (from file://[WORKSPACE]/test/links/bz2-1.0.0.tar.bz2)
"
);
Ok(())
}
#[test]
fn install_url_then_install_url() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("werkzeug @ https://files.pythonhosted.org/packages/ff/1d/960bb4017c68674a1cb099534840f18d3def3ce44aed12b5ed8b78e0153e/Werkzeug-2.0.0-py3-none-any.whl")?;
context
.pip_sync()
.arg("requirements.txt")
.arg("--strict")
.assert()
.success();
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Checked 1 package in [TIME]
"
);
context.assert_command("import werkzeug").success();
Ok(())
}
#[test]
fn install_url_then_install_version() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("werkzeug @ https://files.pythonhosted.org/packages/ff/1d/960bb4017c68674a1cb099534840f18d3def3ce44aed12b5ed8b78e0153e/Werkzeug-2.0.0-py3-none-any.whl")?;
context
.pip_sync()
.arg("requirements.txt")
.arg("--strict")
.assert()
.success();
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("werkzeug==2.0.0")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Checked 1 package in [TIME]
"
);
context.assert_command("import werkzeug").success();
Ok(())
}
#[test]
fn install_version_then_install_url() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("werkzeug==2.0.0")?;
context
.pip_sync()
.arg("requirements.txt")
.arg("--strict")
.assert()
.success();
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("werkzeug @ https://files.pythonhosted.org/packages/ff/1d/960bb4017c68674a1cb099534840f18d3def3ce44aed12b5ed8b78e0153e/Werkzeug-2.0.0-py3-none-any.whl")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
- werkzeug==2.0.0
+ werkzeug==2.0.0 (from https://files.pythonhosted.org/packages/ff/1d/960bb4017c68674a1cb099534840f18d3def3ce44aed12b5ed8b78e0153e/Werkzeug-2.0.0-py3-none-any.whl)
"
);
context.assert_command("import werkzeug").success();
Ok(())
}
#[cfg(feature = "test-python-eol")]
#[test]
fn install_numpy_py38() -> Result<()> {
let context = uv_test::test_context!("3.8");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("numpy")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ numpy==1.24.4
"
);
context.assert_command("import numpy").success();
Ok(())
}
#[test]
fn install_no_index() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("iniconfig==2.0.0")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--no-index")
.arg("--strict"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because iniconfig was not found in the provided package locations and you require iniconfig==2.0.0, we can conclude that your requirements are unsatisfiable.
hint: Packages were unavailable because index lookups were disabled and no additional package locations were provided (try: `--find-links <uri>`)
"
);
context.assert_command("import iniconfig").failure();
Ok(())
}
#[test]
fn install_no_index_cached() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("iniconfig==2.0.0")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ iniconfig==2.0.0
"
);
context.assert_command("import iniconfig").success();
context.pip_uninstall().arg("iniconfig").assert().success();
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--no-index")
.arg("--strict"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because iniconfig was not found in the provided package locations and you require iniconfig==2.0.0, we can conclude that your requirements are unsatisfiable.
hint: Packages were unavailable because index lookups were disabled and no additional package locations were provided (try: `--find-links <uri>`)
"
);
context.assert_command("import iniconfig").failure();
Ok(())
}
#[test]
fn warn_on_yanked() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_in = context.temp_dir.child("requirements.txt");
requirements_in.write_str("colorama==0.4.2")?;
uv_snapshot!(context.filters(), windows_filters=false, context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @r#"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ colorama==0.4.2
warning: `colorama==0.4.2` is yanked (reason: "Bad build, missing files, will not install")
"#
);
Ok(())
}
#[test]
fn warn_on_yanked_dry_run() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_in = context.temp_dir.child("requirements.txt");
requirements_in.write_str("colorama==0.4.2")?;
uv_snapshot!(context.filters(), windows_filters=false, context.pip_sync()
.arg("requirements.txt")
.arg("--dry-run")
.arg("--strict"), @r#"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Would download 1 package
Would install 1 package
+ colorama==0.4.2
warning: `colorama==0.4.2` is yanked (reason: "Bad build, missing files, will not install")
"#
);
Ok(())
}
#[test]
fn install_local_wheel() -> Result<()> {
let context = uv_test::test_context!("3.12");
let archive = context.temp_dir.child("tomli-2.0.1-py3-none-any.whl");
download_to_disk(
"https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl",
&archive,
);
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(&format!(
"tomli @ {}",
Url::from_file_path(archive.path()).unwrap()
))?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ tomli==2.0.1 (from file://[TEMP_DIR]/tomli-2.0.1-py3-none-any.whl)
"
);
context.assert_command("import tomli").success();
context.reset_venv();
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt")
.arg("--strict")
, @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Installed 1 package in [TIME]
+ tomli==2.0.1 (from file://[TEMP_DIR]/tomli-2.0.1-py3-none-any.whl)
"
);
context.assert_command("import tomli").success();
context.reset_venv();
filetime::set_file_mtime(&archive, filetime::FileTime::now()).unwrap();
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt")
.arg("--strict")
, @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ tomli==2.0.1 (from file://[TEMP_DIR]/tomli-2.0.1-py3-none-any.whl)
"
);
context.assert_command("import tomli").success();
filetime::set_file_mtime(&archive, filetime::FileTime::now()).unwrap();
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
~ tomli==2.0.1 (from file://[TEMP_DIR]/tomli-2.0.1-py3-none-any.whl)
"
);
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Checked 1 package in [TIME]
"
);
context.assert_command("import tomli").success();
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(&format!("{}", Url::from_file_path(archive.path()).unwrap()))?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Checked 1 package in [TIME]
"
);
context.assert_command("import tomli").success();
Ok(())
}
#[test]
fn mismatched_version() -> Result<()> {
let context = uv_test::test_context!("3.12");
let archive = context.temp_dir.child("tomli-3.7.2-py3-none-any.whl");
download_to_disk(
"https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl",
&archive,
);
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(&format!(
"tomli @ {}",
Url::from_file_path(archive.path()).unwrap()
))?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
error: Failed to install: tomli-3.7.2-py3-none-any.whl (tomli==3.7.2 (from file://[TEMP_DIR]/tomli-3.7.2-py3-none-any.whl))
Caused by: Wheel version does not match filename (2.0.1 != 3.7.2), which indicates a malformed wheel. If this is intentional, set `UV_SKIP_WHEEL_FILENAME_CHECK=1`.
"
);
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt")
.arg("--strict")
.env(EnvVars::UV_SKIP_WHEEL_FILENAME_CHECK, "1"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Installed 1 package in [TIME]
+ tomli==3.7.2 (from file://[TEMP_DIR]/tomli-3.7.2-py3-none-any.whl)
"
);
Ok(())
}
#[test]
fn mismatched_name() -> Result<()> {
let context = uv_test::test_context!("3.12");
let archive = context.temp_dir.child("foo-2.0.1-py3-none-any.whl");
download_to_disk(
"https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl",
&archive,
);
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(&format!(
"foo @ {}",
Url::from_file_path(archive.path()).unwrap()
))?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because foo has an invalid package format and you require foo, we can conclude that your requirements are unsatisfiable.
hint: The structure of `foo` was invalid
Caused by: The .dist-info directory tomli-2.0.1 does not start with the normalized package name: foo
"
);
Ok(())
}
#[test]
fn install_local_source_distribution() -> Result<()> {
let context = uv_test::test_context!("3.12");
let archive = context.temp_dir.child("wheel-0.42.0.tar.gz");
download_to_disk(
"https://files.pythonhosted.org/packages/b0/b4/bc2baae3970c282fae6c2cb8e0f179923dceb7eaffb0e76170628f9af97b/wheel-0.42.0.tar.gz",
&archive,
);
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(&format!(
"wheel @ {}",
Url::from_file_path(archive.path()).unwrap()
))?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ wheel==0.42.0 (from file://[TEMP_DIR]/wheel-0.42.0.tar.gz)
"
);
context.assert_command("import wheel").success();
Ok(())
}
#[test]
fn install_build_system_no_backend() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("build-system-no-backend @ https://files.pythonhosted.org/packages/ec/25/1e531108ca027dc3a3b37d351f4b86d811df4884c6a81cd99e73b8b589f5/build-system-no-backend-0.1.0.tar.gz")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ build-system-no-backend==0.1.0 (from https://files.pythonhosted.org/packages/ec/25/1e531108ca027dc3a3b37d351f4b86d811df4884c6a81cd99e73b8b589f5/build-system-no-backend-0.1.0.tar.gz)
"
);
context
.assert_command("import build_system_no_backend")
.success();
Ok(())
}
#[test]
fn install_url_source_dist_cached() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("source_distribution @ https://files.pythonhosted.org/packages/10/1f/57aa4cce1b1abf6b433106676e15f9fa2c92ed2bd4cf77c3b50a9e9ac773/source_distribution-0.0.1.tar.gz")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ source-distribution==0.0.1 (from https://files.pythonhosted.org/packages/10/1f/57aa4cce1b1abf6b433106676e15f9fa2c92ed2bd4cf77c3b50a9e9ac773/source_distribution-0.0.1.tar.gz)
"
);
context
.assert_command("import source_distribution")
.success();
context.reset_venv();
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict")
, @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Installed 1 package in [TIME]
+ source-distribution==0.0.1 (from https://files.pythonhosted.org/packages/10/1f/57aa4cce1b1abf6b433106676e15f9fa2c92ed2bd4cf77c3b50a9e9ac773/source_distribution-0.0.1.tar.gz)
"
);
context
.assert_command("import source_distribution")
.success();
context.reset_venv();
let filters = std::iter::once(("Removed \\d+ files?", "Removed [N] files"))
.chain(context.filters())
.collect::<Vec<_>>();
uv_snapshot!(
filters,
context.clean().arg("source_distribution"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Removed [N] files ([SIZE])
"
);
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict")
, @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ source-distribution==0.0.1 (from https://files.pythonhosted.org/packages/10/1f/57aa4cce1b1abf6b433106676e15f9fa2c92ed2bd4cf77c3b50a9e9ac773/source_distribution-0.0.1.tar.gz)
"
);
context
.assert_command("import source_distribution")
.success();
Ok(())
}
#[test]
#[cfg(feature = "test-git")]
fn install_git_source_dist_cached() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage@b270df1a2fb5d012294e9aaf05e7e0bab1e6a389")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ uv-public-pypackage==0.1.0 (from git+https://github.com/astral-test/uv-public-pypackage@b270df1a2fb5d012294e9aaf05e7e0bab1e6a389)
"
);
context
.assert_command("import uv_public_pypackage")
.success();
context.reset_venv();
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict")
, @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Installed 1 package in [TIME]
+ uv-public-pypackage==0.1.0 (from git+https://github.com/astral-test/uv-public-pypackage@b270df1a2fb5d012294e9aaf05e7e0bab1e6a389)
"
);
context
.assert_command("import uv_public_pypackage")
.success();
context.reset_venv();
let filters = if cfg!(windows) {
[("Removed 2 files", "Removed 3 files")]
.into_iter()
.chain(context.filters())
.collect()
} else {
context.filters()
};
uv_snapshot!(filters, context.clean()
.arg("werkzeug"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
No cache entries found
"
);
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict")
, @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Installed 1 package in [TIME]
+ uv-public-pypackage==0.1.0 (from git+https://github.com/astral-test/uv-public-pypackage@b270df1a2fb5d012294e9aaf05e7e0bab1e6a389)
"
);
context
.assert_command("import uv_public_pypackage")
.success();
Ok(())
}
#[test]
fn install_registry_source_dist_cached() -> Result<()> {
let context = uv_test::test_context!("3.12").with_exclude_newer("2025-01-29T00:00:00Z");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("source_distribution==0.0.1")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ source-distribution==0.0.1
"
);
context
.assert_command("import source_distribution")
.success();
context.reset_venv();
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict")
, @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Installed 1 package in [TIME]
+ source-distribution==0.0.1
"
);
context
.assert_command("import source_distribution")
.success();
context.reset_venv();
let filters = std::iter::once(("Removed \\d+ files?", "Removed [N] files"))
.chain(context.filters())
.collect::<Vec<_>>();
uv_snapshot!(filters, context.clean()
.arg("source_distribution"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Removed [N] files ([SIZE])
"
);
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict")
, @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ source-distribution==0.0.1
"
);
context
.assert_command("import source_distribution")
.success();
Ok(())
}
#[test]
fn install_path_source_dist_cached() -> Result<()> {
let context = uv_test::test_context!("3.12");
let archive = context.temp_dir.child("source_distribution-0.0.1.tar.gz");
download_to_disk(
"https://files.pythonhosted.org/packages/10/1f/57aa4cce1b1abf6b433106676e15f9fa2c92ed2bd4cf77c3b50a9e9ac773/source_distribution-0.0.1.tar.gz",
&archive,
);
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(&format!(
"source-distribution @ {}",
Url::from_file_path(archive.path()).unwrap()
))?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ source-distribution==0.0.1 (from file://[TEMP_DIR]/source_distribution-0.0.1.tar.gz)
"
);
context
.assert_command("import source_distribution")
.success();
context.reset_venv();
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt")
.arg("--strict")
, @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Installed 1 package in [TIME]
+ source-distribution==0.0.1 (from file://[TEMP_DIR]/source_distribution-0.0.1.tar.gz)
"
);
context
.assert_command("import source_distribution")
.success();
context.reset_venv();
let filters = std::iter::once(("Removed \\d+ files?", "Removed [N] files"))
.chain(context.filters())
.collect::<Vec<_>>();
uv_snapshot!(
filters,
context.clean().arg("source-distribution"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Removed [N] files ([SIZE])
"
);
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt")
.arg("--strict")
, @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ source-distribution==0.0.1 (from file://[TEMP_DIR]/source_distribution-0.0.1.tar.gz)
"
);
context
.assert_command("import source_distribution")
.success();
Ok(())
}
#[test]
fn install_path_built_dist_cached() -> Result<()> {
let context = uv_test::test_context!("3.12");
let archive = context.temp_dir.child("tomli-2.0.1-py3-none-any.whl");
download_to_disk(
"https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl",
&archive,
);
let requirements_txt = context.temp_dir.child("requirements.txt");
let url = Url::from_file_path(archive.path()).unwrap();
requirements_txt.write_str(&format!("tomli @ {url}"))?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ tomli==2.0.1 (from file://[TEMP_DIR]/tomli-2.0.1-py3-none-any.whl)
"
);
context.assert_command("import tomli").success();
context.reset_venv();
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt")
.arg("--strict")
, @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Installed 1 package in [TIME]
+ tomli==2.0.1 (from file://[TEMP_DIR]/tomli-2.0.1-py3-none-any.whl)
"
);
context.assert_command("import tomli").success();
context.reset_venv();
let filters = std::iter::once(("Removed \\d+ files?", "Removed [N] files"))
.chain(context.filters())
.collect::<Vec<_>>();
uv_snapshot!(
filters,
context.clean().arg("tomli"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Removed [N] files ([SIZE])
"
);
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt")
.arg("--strict")
, @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ tomli==2.0.1 (from file://[TEMP_DIR]/tomli-2.0.1-py3-none-any.whl)
"
);
context.assert_command("import tomli").success();
Ok(())
}
#[test]
fn install_url_built_dist_cached() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("tqdm @ https://files.pythonhosted.org/packages/00/e5/f12a80907d0884e6dff9c16d0c0114d81b8cd07dc3ae54c5e962cc83037e/tqdm-4.66.1-py3-none-any.whl")?;
let context_filters = if cfg!(windows) {
[("warning: The package `tqdm` requires `colorama ; sys_platform == 'win32'`, but it's not installed\n", "")]
.into_iter()
.chain(context.filters())
.collect()
} else {
context.filters()
};
uv_snapshot!(context_filters, context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ tqdm==4.66.1 (from https://files.pythonhosted.org/packages/00/e5/f12a80907d0884e6dff9c16d0c0114d81b8cd07dc3ae54c5e962cc83037e/tqdm-4.66.1-py3-none-any.whl)
"
);
context.assert_command("import tqdm").success();
context.reset_venv();
uv_snapshot!(context_filters, context.pip_sync()
.arg("requirements.txt")
.arg("--strict")
, @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Installed 1 package in [TIME]
+ tqdm==4.66.1 (from https://files.pythonhosted.org/packages/00/e5/f12a80907d0884e6dff9c16d0c0114d81b8cd07dc3ae54c5e962cc83037e/tqdm-4.66.1-py3-none-any.whl)
"
);
context.assert_command("import tqdm").success();
context.reset_venv();
let filters = std::iter::once(("Removed \\d+ files?", "Removed [N] files"))
.chain(context_filters.clone())
.collect::<Vec<_>>();
uv_snapshot!(
filters,
context.clean().arg("tqdm"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Removed [N] files ([SIZE])
"
);
uv_snapshot!(context_filters, context.pip_sync()
.arg("requirements.txt")
.arg("--strict")
, @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ tqdm==4.66.1 (from https://files.pythonhosted.org/packages/00/e5/f12a80907d0884e6dff9c16d0c0114d81b8cd07dc3ae54c5e962cc83037e/tqdm-4.66.1-py3-none-any.whl)
"
);
context.assert_command("import tqdm").success();
Ok(())
}
#[test]
fn duplicate_package_overlap() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("MarkupSafe==2.1.3\nMarkupSafe==2.1.2")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because you require markupsafe==2.1.3 and markupsafe==2.1.2, we can conclude that your requirements are unsatisfiable.
"
);
Ok(())
}
#[test]
fn duplicate_package_disjoint() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("MarkupSafe==2.1.3\nMarkupSafe==2.1.2 ; python_version < '3.6'")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ markupsafe==2.1.3
"
);
Ok(())
}
#[test]
fn reinstall() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("MarkupSafe==2.1.3\ntomli==2.0.1")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Prepared 2 packages in [TIME]
Installed 2 packages in [TIME]
+ markupsafe==2.1.3
+ tomli==2.0.1
"
);
context.assert_command("import markupsafe").success();
context.assert_command("import tomli").success();
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--reinstall")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Prepared 2 packages in [TIME]
Uninstalled 2 packages in [TIME]
Installed 2 packages in [TIME]
~ markupsafe==2.1.3
~ tomli==2.0.1
"
);
context.assert_command("import markupsafe").success();
context.assert_command("import tomli").success();
Ok(())
}
#[test]
fn reinstall_package() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("MarkupSafe==2.1.3\ntomli==2.0.1")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Prepared 2 packages in [TIME]
Installed 2 packages in [TIME]
+ markupsafe==2.1.3
+ tomli==2.0.1
"
);
context.assert_command("import markupsafe").success();
context.assert_command("import tomli").success();
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--reinstall-package")
.arg("tomli")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
~ tomli==2.0.1
"
);
context.assert_command("import markupsafe").success();
context.assert_command("import tomli").success();
Ok(())
}
#[test]
#[cfg(feature = "test-git")]
fn reinstall_git() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage@b270df1a2fb5d012294e9aaf05e7e0bab1e6a389")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ uv-public-pypackage==0.1.0 (from git+https://github.com/astral-test/uv-public-pypackage@b270df1a2fb5d012294e9aaf05e7e0bab1e6a389)
"
);
context
.assert_command("import uv_public_pypackage")
.success();
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--reinstall-package")
.arg("uv-public-pypackage")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
~ uv-public-pypackage==0.1.0 (from git+https://github.com/astral-test/uv-public-pypackage@b270df1a2fb5d012294e9aaf05e7e0bab1e6a389)
"
);
context
.assert_command("import uv_public_pypackage")
.success();
Ok(())
}
#[test]
fn refresh() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("MarkupSafe==2.1.3\ntomli==2.0.1")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Prepared 2 packages in [TIME]
Installed 2 packages in [TIME]
+ markupsafe==2.1.3
+ tomli==2.0.1
"
);
context.assert_command("import markupsafe").success();
context.assert_command("import tomli").success();
context.reset_venv();
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--refresh")
.arg("--strict")
, @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Prepared 2 packages in [TIME]
Installed 2 packages in [TIME]
+ markupsafe==2.1.3
+ tomli==2.0.1
"
);
context.assert_command("import markupsafe").success();
context.assert_command("import tomli").success();
Ok(())
}
#[test]
fn refresh_package() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("MarkupSafe==2.1.3\ntomli==2.0.1")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Prepared 2 packages in [TIME]
Installed 2 packages in [TIME]
+ markupsafe==2.1.3
+ tomli==2.0.1
"
);
context.assert_command("import markupsafe").success();
context.assert_command("import tomli").success();
context.reset_venv();
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--refresh-package")
.arg("tomli")
.arg("--strict")
, @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Prepared 1 package in [TIME]
Installed 2 packages in [TIME]
+ markupsafe==2.1.3
+ tomli==2.0.1
"
);
context.assert_command("import markupsafe").success();
context.assert_command("import tomli").success();
Ok(())
}
#[test]
fn sync_editable() -> Result<()> {
let context = uv_test::test_context!("3.12");
let poetry_editable = context.temp_dir.child("poetry_editable");
copy_dir_all(
context.workspace_root.join("test/packages/poetry_editable"),
&poetry_editable,
)?;
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(&indoc::formatdoc! {r"
anyio==3.7.0
-e file://{poetry_editable}
",
poetry_editable = poetry_editable.display()
})?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg(requirements_txt.path()), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Prepared 2 packages in [TIME]
Installed 2 packages in [TIME]
+ anyio==3.7.0
+ poetry-editable==0.1.0 (from file://[TEMP_DIR]/poetry_editable)
"
);
uv_snapshot!(context.filters(), context.pip_sync()
.arg(requirements_txt.path()), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Checked 2 packages in [TIME]
"
);
uv_snapshot!(context.filters(), context.pip_sync()
.arg(requirements_txt.path())
.arg("--reinstall-package")
.arg("poetry-editable"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
~ poetry-editable==0.1.0 (from file://[TEMP_DIR]/poetry_editable)
"
);
let python_source_file = poetry_editable.path().join("poetry_editable/__init__.py");
let check_installed = indoc::indoc! {r#"
from poetry_editable import a
assert a() == "a", a()
"#};
context.assert_command(check_installed).success();
let python_version_1 = indoc::indoc! {r"
version = 1
"};
fs_err::write(&python_source_file, python_version_1)?;
let check_installed = indoc::indoc! {r"
from poetry_editable import version
assert version == 1, version
"};
context.assert_command(check_installed).success();
let python_version_2 = indoc::indoc! {r"
version = 2
"};
fs_err::write(&python_source_file, python_version_2)?;
let check_installed = indoc::indoc! {r"
from poetry_editable import version
assert version == 2, version
"};
context.assert_command(check_installed).success();
uv_snapshot!(context.filters(), context.pip_sync()
.arg(requirements_txt.path()), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Checked 2 packages in [TIME]
"
);
let pyproject_toml = poetry_editable.path().join("pyproject.toml");
let pyproject_toml_contents = fs_err::read_to_string(&pyproject_toml)?;
fs_err::write(
&pyproject_toml,
pyproject_toml_contents.replace("0.1.0", "0.1.1"),
)?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg(requirements_txt.path()), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
- poetry-editable==0.1.0 (from file://[TEMP_DIR]/poetry_editable)
+ poetry-editable==0.1.1 (from file://[TEMP_DIR]/poetry_editable)
"
);
let pyproject_toml = poetry_editable.path().join("pyproject.toml");
let pyproject_toml_contents = fs_err::read_to_string(&pyproject_toml)?;
fs_err::write(
&pyproject_toml,
pyproject_toml_contents.replace("0.1.0", "0.1.1"),
)?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg(requirements_txt.path()), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
~ poetry-editable==0.1.1 (from file://[TEMP_DIR]/poetry_editable)
"
);
Ok(())
}
#[test]
fn sync_editable_and_registry() -> Result<()> {
let context = uv_test::test_context!("3.12");
copy_dir_all(
context.workspace_root.join("test/packages/black_editable"),
context.temp_dir.join("black_editable"),
)?;
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(indoc::indoc! {r"
black==24.1.0
"
})?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg(requirements_txt.path())
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ black==24.1.0
warning: The package `black` requires `click>=8.0.0`, but it's not installed
warning: The package `black` requires `mypy-extensions>=0.4.3`, but it's not installed
warning: The package `black` requires `packaging>=22.0`, but it's not installed
warning: The package `black` requires `pathspec>=0.9.0`, but it's not installed
warning: The package `black` requires `platformdirs>=2`, but it's not installed
"
);
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(indoc::indoc! {r"
-e file:./black_editable
"
})?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg(requirements_txt.path()), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
- black==24.1.0
+ black==0.1.0 (from file://[TEMP_DIR]/black_editable)
"
);
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(indoc::indoc! {r"
black
"
})?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg(requirements_txt.path()), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Checked 1 package in [TIME]
"
);
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(indoc::indoc! {r"
black==23.10.0
"
})?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg(requirements_txt.path())
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
- black==0.1.0 (from file://[TEMP_DIR]/black_editable)
+ black==23.10.0
warning: The package `black` requires `click>=8.0.0`, but it's not installed
warning: The package `black` requires `mypy-extensions>=0.4.3`, but it's not installed
warning: The package `black` requires `packaging>=22.0`, but it's not installed
warning: The package `black` requires `pathspec>=0.9.0`, but it's not installed
warning: The package `black` requires `platformdirs>=2`, but it's not installed
"
);
Ok(())
}
#[test]
fn sync_editable_and_local() -> Result<()> {
let context = uv_test::test_context!("3.12");
copy_dir_all(
context.workspace_root.join("test/packages/black_editable"),
context.temp_dir.join("black_editable"),
)?;
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(indoc::indoc! {r"
-e file:./black_editable
"
})?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg(requirements_txt.path()), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ black==0.1.0 (from file://[TEMP_DIR]/black_editable)
"
);
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(indoc::indoc! {r"
black @ file:./black_editable
"
})?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg(requirements_txt.path()), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
~ black==0.1.0 (from file://[TEMP_DIR]/black_editable)
"
);
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(indoc::indoc! {r"
-e file:./black_editable
"
})?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg(requirements_txt.path()), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
~ black==0.1.0 (from file://[TEMP_DIR]/black_editable)
"
);
Ok(())
}
#[test]
fn incompatible_wheel() -> Result<()> {
let context = uv_test::test_context!("3.12");
let wheel = context.temp_dir.child("foo-1.2.3-py3-none-any.whl");
wheel.touch()?;
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(&format!("foo @ {}", wheel.path().simplified_display()))?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because foo has an invalid package format and you require foo, we can conclude that your requirements are unsatisfiable.
hint: The structure of `foo` was invalid
Caused by: Failed to read from zip file
Caused by: unable to locate the end of central directory record
"
);
Ok(())
}
#[test]
fn sync_legacy_sdist_pep_517() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str("flake8 @ https://files.pythonhosted.org/packages/66/53/3ad4a3b74d609b3b9008a10075c40e7c8909eae60af53623c3888f7a529a/flake8-6.0.0.tar.gz")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.in"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ flake8==6.0.0 (from https://files.pythonhosted.org/packages/66/53/3ad4a3b74d609b3b9008a10075c40e7c8909eae60af53623c3888f7a529a/flake8-6.0.0.tar.gz)
"
);
Ok(())
}
#[test]
fn find_links() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(indoc! {r"
markupsafe==2.1.3
numpy==1.26.3
tqdm==1000.0.0
werkzeug @ https://files.pythonhosted.org/packages/c3/fc/254c3e9b5feb89ff5b9076a23218dafbc99c96ac5941e900b71206e6313b/werkzeug-3.0.1-py3-none-any.whl
"})?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt")
.arg("--find-links")
.arg(context.workspace_root.join("test/links/")), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 4 packages in [TIME]
Prepared 4 packages in [TIME]
Installed 4 packages in [TIME]
+ markupsafe==2.1.3
+ numpy==1.26.3
+ tqdm==1000.0.0
+ werkzeug==3.0.1 (from https://files.pythonhosted.org/packages/c3/fc/254c3e9b5feb89ff5b9076a23218dafbc99c96ac5941e900b71206e6313b/werkzeug-3.0.1-py3-none-any.whl)
"
);
Ok(())
}
#[test]
fn find_links_no_index_match() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(indoc! {r"
tqdm==1000.0.0
"})?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt")
.arg("--no-index")
.arg("--find-links")
.arg(context.workspace_root.join("test/links/")), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ tqdm==1000.0.0
"
);
Ok(())
}
#[test]
fn find_links_offline_match() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(indoc! {r"
tqdm==1000.0.0
"})?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt")
.arg("--offline")
.arg("--find-links")
.arg(context.workspace_root.join("test/links/")), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ tqdm==1000.0.0
"
);
Ok(())
}
#[test]
fn find_links_offline_no_match() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(indoc! {r"
numpy
tqdm==1000.0.0
"})?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt")
.arg("--offline")
.arg("--find-links")
.arg(context.workspace_root.join("test/links/")), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because numpy was not found in the cache and you require numpy, we can conclude that your requirements are unsatisfiable.
hint: Packages were unavailable because the network was disabled. When the network is disabled, registry packages may only be read from the cache.
"
);
Ok(())
}
#[test]
fn find_links_wheel_cache() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(indoc! {r"
tqdm==1000.0.0
"})?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt")
.arg("--find-links")
.arg(context.workspace_root.join("test/links/")), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ tqdm==1000.0.0
"
);
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt")
.arg("--reinstall")
.arg("--find-links")
.arg(context.workspace_root.join("test/links/")), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
~ tqdm==1000.0.0
"
);
Ok(())
}
#[test]
fn find_links_source_cache() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(indoc! {r"
tqdm==999.0.0
"})?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt")
.arg("--find-links")
.arg(context.workspace_root.join("test/links/")), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ tqdm==999.0.0
"
);
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt")
.arg("--reinstall")
.arg("--find-links")
.arg(context.workspace_root.join("test/links/")), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
~ tqdm==999.0.0
"
);
Ok(())
}
#[test]
fn offline() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str("black==23.10.1")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.in")
.arg("--offline"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because black was not found in the cache and you require black==23.10.1, we can conclude that your requirements are unsatisfiable.
hint: Packages were unavailable because the network was disabled. When the network is disabled, registry packages may only be read from the cache.
"
);
uv_snapshot!(context.pip_sync()
.arg("requirements.in"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ black==23.10.1
"
);
context.reset_venv();
uv_snapshot!(context.pip_sync()
.arg("requirements.in")
.arg("--offline")
, @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Installed 1 package in [TIME]
+ black==23.10.1
"
);
Ok(())
}
#[test]
fn compatible_constraint() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("anyio==3.7.0")?;
let constraints_txt = context.temp_dir.child("constraints.txt");
constraints_txt.write_str("anyio==3.7.0")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--constraint")
.arg("constraints.txt"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ anyio==3.7.0
"
);
Ok(())
}
#[test]
fn incompatible_constraint() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("anyio==3.7.0")?;
let constraints_txt = context.temp_dir.child("constraints.txt");
constraints_txt.write_str("anyio==3.6.0")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--constraint")
.arg("constraints.txt"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because you require anyio==3.7.0 and anyio==3.6.0, we can conclude that your requirements are unsatisfiable.
"
);
Ok(())
}
#[test]
fn irrelevant_constraint() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("anyio==3.7.0")?;
let constraints_txt = context.temp_dir.child("constraints.txt");
constraints_txt.write_str("black==23.10.1")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--constraint")
.arg("constraints.txt"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ anyio==3.7.0
"
);
Ok(())
}
#[test]
fn repeat_requirement_identical() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str("anyio\nanyio")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.in"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ anyio==4.3.0
");
Ok(())
}
#[test]
fn repeat_requirement_compatible() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str("anyio\nanyio==4.0.0")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.in"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ anyio==4.0.0
");
Ok(())
}
#[test]
fn repeat_requirement_incompatible() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str("anyio<4.0.0\nanyio==4.0.0")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.in"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because you require anyio<4.0.0 and anyio==4.0.0, we can conclude that your requirements are unsatisfiable.
");
Ok(())
}
#[test]
fn tar_dont_preserve_mtime() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("tomli @ https://files.pythonhosted.org/packages/c0/3f/d7af728f075fb08564c5949a9c95e44352e23dee646869fa104a3b2060a3/tomli-2.0.1.tar.gz")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ tomli==2.0.1 (from https://files.pythonhosted.org/packages/c0/3f/d7af728f075fb08564c5949a9c95e44352e23dee646869fa104a3b2060a3/tomli-2.0.1.tar.gz)
");
Ok(())
}
#[test]
fn set_read_permissions() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str("databricks==0.2")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.in"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ databricks==0.2
");
Ok(())
}
#[test]
fn pip_entrypoints() -> Result<()> {
let context = uv_test::test_context!("3.12");
for pip_requirement in [
"pip==24.0",
"pip==24.1b1",
] {
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(pip_requirement)?;
context
.pip_sync()
.arg("requirements.txt")
.arg("--strict")
.output()
.expect("Failed to install pip");
let bin_dir = context.venv.join(if cfg!(unix) {
"bin"
} else if cfg!(windows) {
"Scripts"
} else {
unimplemented!("Only Windows and Unix are supported")
});
ChildPath::new(bin_dir.join(format!("pip3.10{EXE_SUFFIX}")))
.assert(predicates::path::missing());
ChildPath::new(bin_dir.join(format!("pip3.12{EXE_SUFFIX}")))
.assert(predicates::path::exists());
}
Ok(())
}
#[test]
fn invalidate_on_change() -> Result<()> {
let context = uv_test::test_context!("3.12");
let editable_dir = context.temp_dir.child("editable");
editable_dir.create_dir_all()?;
let pyproject_toml = editable_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"[project]
name = "example"
version = "0.0.0"
dependencies = [
"anyio==4.0.0"
]
requires-python = ">=3.8"
"#,
)?;
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str(&format!("-e {}", editable_dir.path().display()))?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.in"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ example==0.0.0 (from file://[TEMP_DIR]/editable)
"
);
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.in"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Checked 1 package in [TIME]
"
);
pyproject_toml.write_str(
r#"[project]
name = "example"
version = "0.0.0"
dependencies = [
"anyio==3.7.1"
]
requires-python = ">=3.8"
"#,
)?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.in"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
~ example==0.0.0 (from file://[TEMP_DIR]/editable)
"
);
Ok(())
}
#[test]
fn compile() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("MarkupSafe==2.1.3")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--compile")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
Bytecode compiled 3 files in [TIME]
+ markupsafe==2.1.3
"
);
assert!(
context
.site_packages()
.join("markupsafe")
.join("__pycache__")
.join("__init__.cpython-312.pyc")
.exists()
);
context.assert_command("import markupsafe").success();
Ok(())
}
#[test]
fn recompile() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("MarkupSafe==2.1.3")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ markupsafe==2.1.3
"
);
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--compile")
.arg("--strict"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Bytecode compiled 3 files in [TIME]
"
);
assert!(
context
.site_packages()
.join("markupsafe")
.join("__pycache__")
.join("__init__.cpython-312.pyc")
.exists()
);
context.assert_command("import markupsafe").success();
Ok(())
}
#[test]
fn requires_python_editable() -> Result<()> {
let context = uv_test::test_context!("3.12");
let editable_dir = context.temp_dir.child("editable");
editable_dir.create_dir_all()?;
let pyproject_toml = editable_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"[project]
name = "example"
version = "0.0.0"
dependencies = [
"anyio==4.0.0"
]
requires-python = ">=3.13"
"#,
)?;
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str(&format!("-e {}", editable_dir.path().display()))?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.in"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because the current Python version (3.12.[X]) does not satisfy Python>=3.13 and example==0.0.0 depends on Python>=3.13, we can conclude that example==0.0.0 cannot be used.
And because only example==0.0.0 is available and you require example, we can conclude that your requirements are unsatisfiable.
"
);
Ok(())
}
#[test]
fn requires_python_direct_url() -> Result<()> {
let context = uv_test::test_context!("3.12");
let editable_dir = context.temp_dir.child("editable");
editable_dir.create_dir_all()?;
let pyproject_toml = editable_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"[project]
name = "example"
version = "0.0.0"
dependencies = [
"anyio==4.0.0"
]
requires-python = ">=3.13"
"#,
)?;
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str(&format!("example @ {}", editable_dir.path().display()))?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.in"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because the current Python version (3.12.[X]) does not satisfy Python>=3.13 and example==0.0.0 depends on Python>=3.13, we can conclude that example==0.0.0 cannot be used.
And because only example==0.0.0 is available and you require example, we can conclude that your requirements are unsatisfiable.
"
);
Ok(())
}
#[test]
fn require_hashes_unknown_algorithm() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(
"anyio==4.0.0 --hash=foo:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f",
)?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--require-hashes"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Unsupported hash algorithm (expected one of: `md5`, `sha256`, `sha384`, `sha512`, or `blake2b`) on: `foo`
"
);
Ok(())
}
#[test]
fn require_hashes_missing_hash() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("anyio==4.0.0")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ anyio==4.0.0
"
);
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--require-hashes"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: In `--require-hashes` mode, all requirements must have a hash, but none were provided for: anyio==4.0.0
"
);
Ok(())
}
#[test]
fn require_hashes_missing_version() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(
"anyio --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f",
)?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ anyio==4.3.0
"
);
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--require-hashes"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: In `--require-hashes` mode, all requirements must have their versions pinned with `==`, but found: anyio
"
);
Ok(())
}
#[test]
fn require_hashes_invalid_operator() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(
"anyio>4.0.0 --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f",
)?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ anyio==4.3.0
"
);
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--require-hashes"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: In `--require-hashes` mode, all requirements must have their versions pinned with `==`, but found: anyio>4.0.0
"
);
Ok(())
}
#[test]
fn require_hashes_wheel_no_binary() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("anyio==4.0.0 --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--no-binary")
.arg(":all:")
.arg("--require-hashes"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
× Failed to download and build `anyio==4.0.0`
╰─▶ Hash mismatch for `anyio==4.0.0`
Expected:
sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f
Computed:
sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a
"
);
Ok(())
}
#[test]
fn require_hashes_wheel_only_binary() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("anyio==4.0.0 --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--only-binary")
.arg(":all:")
.arg("--require-hashes"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ anyio==4.0.0
"
);
Ok(())
}
#[test]
fn require_hashes_source_no_binary() -> Result<()> {
let context = uv_test::test_context!("3.12").with_exclude_newer("2025-01-29T00:00:00Z");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("source-distribution==0.0.1 --hash=sha256:1f83ed7498336c7f2ab9b002cf22583d91115ebc624053dc4eb3a45694490106")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--no-binary")
.arg(":all:")
.arg("--require-hashes"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ source-distribution==0.0.1
"
);
Ok(())
}
#[test]
fn require_hashes_source_only_binary() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("anyio==4.0.0 --hash=sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--only-binary")
.arg(":all:")
.arg("--require-hashes"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
× Failed to download `anyio==4.0.0`
╰─▶ Hash mismatch for `anyio==4.0.0`
Expected:
sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a
Computed:
sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f
"
);
Ok(())
}
#[test]
fn require_hashes_wrong_digest() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("anyio==4.0.0 --hash=sha256:afdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--require-hashes"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
× Failed to download `anyio==4.0.0`
╰─▶ Hash mismatch for `anyio==4.0.0`
Expected:
sha256:afdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f
Computed:
sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f
"
);
Ok(())
}
#[test]
fn require_hashes_wrong_algorithm() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("anyio==4.0.0 --hash=sha512:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--require-hashes"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
× Failed to download `anyio==4.0.0`
╰─▶ Hash mismatch for `anyio==4.0.0`
Expected:
sha512:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f
Computed:
sha512:f30761c1e8725b49c498273b90dba4b05c0fd157811994c806183062cb6647e773364ce45f0e1ff0b10e32fe6d0232ea5ad39476ccf37109d6b49603a09c11c2
"
);
Ok(())
}
#[test]
fn require_hashes_source_url() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("source-distribution @ https://files.pythonhosted.org/packages/10/1f/57aa4cce1b1abf6b433106676e15f9fa2c92ed2bd4cf77c3b50a9e9ac773/source_distribution-0.0.1.tar.gz --hash=sha256:1f83ed7498336c7f2ab9b002cf22583d91115ebc624053dc4eb3a45694490106")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--require-hashes"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ source-distribution==0.0.1 (from https://files.pythonhosted.org/packages/10/1f/57aa4cce1b1abf6b433106676e15f9fa2c92ed2bd4cf77c3b50a9e9ac773/source_distribution-0.0.1.tar.gz)
"
);
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--reinstall")
.arg("--require-hashes"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
~ source-distribution==0.0.1 (from https://files.pythonhosted.org/packages/10/1f/57aa4cce1b1abf6b433106676e15f9fa2c92ed2bd4cf77c3b50a9e9ac773/source_distribution-0.0.1.tar.gz)
"
);
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("source-distribution @ https://files.pythonhosted.org/packages/10/1f/57aa4cce1b1abf6b433106676e15f9fa2c92ed2bd4cf77c3b50a9e9ac773/source_distribution-0.0.1.tar.gz --hash=sha256:a7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--reinstall")
.arg("--require-hashes"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× Failed to download and build `source-distribution @ https://files.pythonhosted.org/packages/10/1f/57aa4cce1b1abf6b433106676e15f9fa2c92ed2bd4cf77c3b50a9e9ac773/source_distribution-0.0.1.tar.gz`
╰─▶ Hash mismatch for `source-distribution @ https://files.pythonhosted.org/packages/10/1f/57aa4cce1b1abf6b433106676e15f9fa2c92ed2bd4cf77c3b50a9e9ac773/source_distribution-0.0.1.tar.gz`
Expected:
sha256:a7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a
Computed:
sha256:1f83ed7498336c7f2ab9b002cf22583d91115ebc624053dc4eb3a45694490106
"
);
Ok(())
}
#[test]
fn require_hashes_source_url_mismatch() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("source-distribution @ https://files.pythonhosted.org/packages/10/1f/57aa4cce1b1abf6b433106676e15f9fa2c92ed2bd4cf77c3b50a9e9ac773/source_distribution-0.0.1.tar.gz --hash=sha256:a7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--require-hashes"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× Failed to download and build `source-distribution @ https://files.pythonhosted.org/packages/10/1f/57aa4cce1b1abf6b433106676e15f9fa2c92ed2bd4cf77c3b50a9e9ac773/source_distribution-0.0.1.tar.gz`
╰─▶ Hash mismatch for `source-distribution @ https://files.pythonhosted.org/packages/10/1f/57aa4cce1b1abf6b433106676e15f9fa2c92ed2bd4cf77c3b50a9e9ac773/source_distribution-0.0.1.tar.gz`
Expected:
sha256:a7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a
Computed:
sha256:1f83ed7498336c7f2ab9b002cf22583d91115ebc624053dc4eb3a45694490106
"
);
Ok(())
}
#[test]
fn require_hashes_wheel_url() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--require-hashes"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ anyio==4.0.0 (from https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl)
"
);
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--reinstall")
.arg("--require-hashes"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
~ anyio==4.0.0 (from https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl)
"
);
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl --hash=sha256:afdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--reinstall")
.arg("--require-hashes"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
× Failed to download `anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl`
╰─▶ Hash mismatch for `anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl`
Expected:
sha256:afdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f
Computed:
sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f
"
);
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("anyio==4.0.0 --hash=sha256:afdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f\niniconfig==2.0.0 --hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--require-hashes"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ iniconfig==2.0.0
"
);
Ok(())
}
#[test]
fn require_hashes_wheel_url_mismatch() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl --hash=sha256:afdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--require-hashes"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
× Failed to download `anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl`
╰─▶ Hash mismatch for `anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl`
Expected:
sha256:afdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f
Computed:
sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f
"
);
Ok(())
}
#[test]
#[cfg(feature = "test-git")]
fn require_hashes_git() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("anyio @ git+https://github.com/agronholm/anyio@4a23745badf5bf5ef7928f1e346e9986bd696d82 --hash=sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--require-hashes"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× Failed to download and build `anyio @ git+https://github.com/agronholm/anyio@4a23745badf5bf5ef7928f1e346e9986bd696d82`
╰─▶ Hash-checking is not supported for Git repositories: `anyio @ git+https://github.com/agronholm/anyio@4a23745badf5bf5ef7928f1e346e9986bd696d82`
"
);
Ok(())
}
#[test]
fn require_hashes_source_tree() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(&format!(
"black @ {} --hash=sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a",
context
.workspace_root
.join("test/packages/black_editable")
.display()
))?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt")
.arg("--require-hashes"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× Failed to build `black @ file://[WORKSPACE]/test/packages/black_editable`
╰─▶ Hash-checking is not supported for local directories: `black @ file://[WORKSPACE]/test/packages/black_editable`
"
);
Ok(())
}
#[test]
fn require_hashes_re_download() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("anyio==4.0.0")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ anyio==4.0.0
"
);
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("anyio==4.0.0 --hash=sha256:afdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--reinstall")
.arg("--require-hashes"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
× Failed to download `anyio==4.0.0`
╰─▶ Hash mismatch for `anyio==4.0.0`
Expected:
sha256:afdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f
Computed:
sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f
"
);
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("anyio==4.0.0 --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--reinstall")
.arg("--require-hashes"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
~ anyio==4.0.0
"
);
Ok(())
}
#[test]
fn require_hashes_wheel_path() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(&format!(
"tqdm @ {} --hash=sha256:a34996d4bd5abb2336e14ff0a2d22b92cfd0f0ed344e6883041ce01953276a13",
context
.workspace_root
.join("test/links/tqdm-1000.0.0-py3-none-any.whl")
.display()
))?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt")
.arg("--require-hashes"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ tqdm==1000.0.0 (from file://[WORKSPACE]/test/links/tqdm-1000.0.0-py3-none-any.whl)
"
);
Ok(())
}
#[test]
fn require_hashes_wheel_path_mismatch() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(&format!(
"tqdm @ {} --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f",
context
.workspace_root
.join("test/links/tqdm-1000.0.0-py3-none-any.whl")
.display()
))?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt")
.arg("--require-hashes"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
× Failed to read `tqdm @ file://[WORKSPACE]/test/links/tqdm-1000.0.0-py3-none-any.whl`
╰─▶ Hash mismatch for `tqdm @ file://[WORKSPACE]/test/links/tqdm-1000.0.0-py3-none-any.whl`
Expected:
sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f
Computed:
sha256:a34996d4bd5abb2336e14ff0a2d22b92cfd0f0ed344e6883041ce01953276a13
"
);
Ok(())
}
#[test]
fn require_hashes_source_path() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(&format!(
"tqdm @ {} --hash=sha256:89fa05cffa7f457658373b85de302d24d0c205ceda2819a8739e324b75e9430b",
context
.workspace_root
.join("test/links/tqdm-999.0.0.tar.gz")
.display()
))?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt")
.arg("--require-hashes"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ tqdm==999.0.0 (from file://[WORKSPACE]/test/links/tqdm-999.0.0.tar.gz)
"
);
Ok(())
}
#[test]
fn require_hashes_source_path_mismatch() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(&format!(
"tqdm @ {} --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f",
context
.workspace_root
.join("test/links/tqdm-999.0.0.tar.gz")
.display()
))?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt")
.arg("--require-hashes"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× Failed to build `tqdm @ file://[WORKSPACE]/test/links/tqdm-999.0.0.tar.gz`
╰─▶ Hash mismatch for `tqdm @ file://[WORKSPACE]/test/links/tqdm-999.0.0.tar.gz`
Expected:
sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f
Computed:
sha256:89fa05cffa7f457658373b85de302d24d0c205ceda2819a8739e324b75e9430b
"
);
Ok(())
}
#[test]
fn require_hashes_unnamed() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str(indoc::indoc! {r"
https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f
"} )?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--require-hashes"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ anyio==4.0.0 (from https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl)
"
);
Ok(())
}
#[test]
fn require_hashes_editable() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(&indoc::formatdoc! {r"
-e file://{workspace_root}/test/packages/black_editable[d]
",
workspace_root = context.workspace_root.simplified_display(),
})?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg(requirements_txt.path())
.arg("--require-hashes"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: In `--require-hashes` mode, all requirements must have a hash, but none were provided for: file://[WORKSPACE]/test/packages/black_editable[d]
"
);
Ok(())
}
#[test]
fn require_hashes_repeated_dependency() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("anyio==4.0.0 --hash=sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a\nanyio")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--require-hashes"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: In `--require-hashes` mode, all requirements must have their versions pinned with `==`, but found: anyio
"
);
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("anyio\nanyio==4.0.0 --hash=sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--require-hashes"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: In `--require-hashes` mode, all requirements must have their versions pinned with `==`, but found: anyio
"
);
Ok(())
}
#[test]
fn require_hashes_repeated_hash() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str(indoc::indoc! { r"
anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f
anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f
" })?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--require-hashes"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ anyio==4.0.0 (from https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl)
"
);
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str(indoc::indoc! { r"
anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f
anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl --hash=sha512:f30761c1e8725b49c498273b90dba4b05c0fd157811994c806183062cb6647e773364ce45f0e1ff0b10e32fe6d0232ea5ad39476ccf37109d6b49603a09c11c2
" })?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--require-hashes")
.arg("--reinstall"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
~ anyio==4.0.0 (from https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl)
"
);
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str(indoc::indoc! { r"
anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f
anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl --hash=sha512:e30761c1e8725b49c498273b90dba4b05c0fd157811994c806183062cb6647e773364ce45f0e1ff0b10e32fe6d0232ea5ad39476ccf37109d6b49603a09c11c2
" })?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--require-hashes")
.arg("--reinstall"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
× Failed to download `anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl`
╰─▶ Hash mismatch for `anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl`
Expected:
sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f
sha512:e30761c1e8725b49c498273b90dba4b05c0fd157811994c806183062cb6647e773364ce45f0e1ff0b10e32fe6d0232ea5ad39476ccf37109d6b49603a09c11c2
Computed:
sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f
sha512:f30761c1e8725b49c498273b90dba4b05c0fd157811994c806183062cb6647e773364ce45f0e1ff0b10e32fe6d0232ea5ad39476ccf37109d6b49603a09c11c2
"
);
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str(indoc::indoc! { r"
anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl --hash=sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a
anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl --hash=sha512:e30761c1e8725b49c498273b90dba4b05c0fd157811994c806183062cb6647e773364ce45f0e1ff0b10e32fe6d0232ea5ad39476ccf37109d6b49603a09c11c2
" })?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--require-hashes")
.arg("--reinstall"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
× Failed to download `anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl`
╰─▶ Hash mismatch for `anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl`
Expected:
sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a
sha512:e30761c1e8725b49c498273b90dba4b05c0fd157811994c806183062cb6647e773364ce45f0e1ff0b10e32fe6d0232ea5ad39476ccf37109d6b49603a09c11c2
Computed:
sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f
sha512:f30761c1e8725b49c498273b90dba4b05c0fd157811994c806183062cb6647e773364ce45f0e1ff0b10e32fe6d0232ea5ad39476ccf37109d6b49603a09c11c2
"
);
Ok(())
}
#[test]
fn require_hashes_repeated_hash_multiple_files() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_a = context.temp_dir.child("requirements-a.txt");
requirements_a.write_str(indoc::indoc! { r"
anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f
" })?;
let requirements_b = context.temp_dir.child("requirements-b.txt");
requirements_b.write_str(indoc::indoc! { r"
anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl --hash=sha512:f30761c1e8725b49c498273b90dba4b05c0fd157811994c806183062cb6647e773364ce45f0e1ff0b10e32fe6d0232ea5ad39476ccf37109d6b49603a09c11c2
" })?;
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(indoc::indoc! { r"
-r requirements-a.txt
-r requirements-b.txt
" })?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--require-hashes"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ anyio==4.0.0 (from https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl)
"
);
Ok(())
}
#[test]
fn require_hashes_at_least_one() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("anyio==4.0.0 --hash=sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--require-hashes"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ anyio==4.0.0
"
);
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("anyio==4.0.0 --hash=sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a --hash=md5:420d85e19168705cdf0223621b18831a")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--reinstall")
.arg("--require-hashes"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
~ anyio==4.0.0
"
);
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("anyio==4.0.0 --hash=sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a --hash=md5:1234")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--reinstall")
.arg("--require-hashes"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
~ anyio==4.0.0
"
);
Ok(())
}
#[test]
fn require_hashes_find_links_no_hash() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("example-a-961b4c22==1.0.0 --hash=sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--reinstall")
.arg("--require-hashes")
.arg("--find-links")
.arg("https://raw.githubusercontent.com/astral-test/astral-test-hash/main/no-hash/simple-html/example-a-961b4c22/index.html"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ example-a-961b4c22==1.0.0
"
);
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("example-a-961b4c22==1.0.0 --hash=sha256:123")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--reinstall")
.arg("--require-hashes")
.arg("--find-links")
.arg("https://raw.githubusercontent.com/astral-test/astral-test-hash/main/no-hash/simple-html/example-a-961b4c22/index.html"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
× Failed to download `example-a-961b4c22==1.0.0`
╰─▶ Hash mismatch for `example-a-961b4c22==1.0.0`
Expected:
sha256:123
Computed:
sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e
"
);
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("example-a-961b4c22==1.0.0 --hash=sha256:294e788dbe500fdc39e8b88e82652ab67409a1dc9dd06543d0fe0ae31b713eb3")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--reinstall")
.arg("--require-hashes")
.arg("--find-links")
.arg("https://raw.githubusercontent.com/astral-test/astral-test-hash/main/no-hash/simple-html/example-a-961b4c22/index.html"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
× Failed to download `example-a-961b4c22==1.0.0`
╰─▶ Hash mismatch for `example-a-961b4c22==1.0.0`
Expected:
sha256:294e788dbe500fdc39e8b88e82652ab67409a1dc9dd06543d0fe0ae31b713eb3
Computed:
sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e
"
);
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("example-a-961b4c22==1.0.0 --hash=sha256:294e788dbe500fdc39e8b88e82652ab67409a1dc9dd06543d0fe0ae31b713eb3")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--no-binary")
.arg(":all:")
.arg("--reinstall")
.arg("--require-hashes")
.arg("--find-links")
.arg("https://raw.githubusercontent.com/astral-test/astral-test-hash/main/no-hash/simple-html/example-a-961b4c22/index.html"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
~ example-a-961b4c22==1.0.0
"
);
Ok(())
}
#[test]
fn require_hashes_find_links_valid_hash() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("example-a-961b4c22==1.0.0 --hash=sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--require-hashes")
.arg("--find-links")
.arg("https://raw.githubusercontent.com/astral-test/astral-test-hash/main/valid-hash/simple-html/example-a-961b4c22/index.html"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ example-a-961b4c22==1.0.0
"
);
Ok(())
}
#[test]
fn require_hashes_find_links_invalid_hash() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("example-a-961b4c22==1.0.0 --hash=sha256:123")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--reinstall")
.arg("--require-hashes")
.arg("--find-links")
.arg("https://raw.githubusercontent.com/astral-test/astral-test-hash/main/invalid-hash/simple-html/example-a-961b4c22/index.html"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
× Failed to download `example-a-961b4c22==1.0.0`
╰─▶ Hash mismatch for `example-a-961b4c22==1.0.0`
Expected:
sha256:123
Computed:
sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e
"
);
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("example-a-961b4c22==1.0.0 --hash=sha256:8838f9d005ff0432b258ba648d9cabb1cbdf06ac29d14f788b02edae544032ea")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--reinstall")
.arg("--require-hashes")
.arg("--find-links")
.arg("https://raw.githubusercontent.com/astral-test/astral-test-hash/main/invalid-hash/simple-html/example-a-961b4c22/index.html"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
× Failed to download `example-a-961b4c22==1.0.0`
╰─▶ Hash mismatch for `example-a-961b4c22==1.0.0`
Expected:
sha256:8838f9d005ff0432b258ba648d9cabb1cbdf06ac29d14f788b02edae544032ea
Computed:
sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e
"
);
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("example-a-961b4c22==1.0.0 --hash=sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--reinstall")
.arg("--require-hashes")
.arg("--find-links")
.arg("https://raw.githubusercontent.com/astral-test/astral-test-hash/main/invalid-hash/simple-html/example-a-961b4c22/index.html"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ example-a-961b4c22==1.0.0
"
);
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("example-a-961b4c22==1.0.0 --hash=sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--refresh")
.arg("--reinstall")
.arg("--require-hashes")
.arg("--find-links")
.arg("https://raw.githubusercontent.com/astral-test/astral-test-hash/main/invalid-hash/simple-html/example-a-961b4c22/index.html"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
~ example-a-961b4c22==1.0.0
"
);
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("example-a-961b4c22==1.0.0 --hash=sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e --hash=sha256:a3cf07a05aac526131a2e8b6e4375ee6c6eaac8add05b88035e960ac6cd999ee")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--refresh")
.arg("--reinstall")
.arg("--require-hashes")
.arg("--find-links")
.arg("https://raw.githubusercontent.com/astral-test/astral-test-hash/main/invalid-hash/simple-html/example-a-961b4c22/index.html"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
× Failed to download and build `example-a-961b4c22==1.0.0`
╰─▶ Hash mismatch for `example-a-961b4c22==1.0.0`
Expected:
sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e
sha256:a3cf07a05aac526131a2e8b6e4375ee6c6eaac8add05b88035e960ac6cd999ee
Computed:
sha256:294e788dbe500fdc39e8b88e82652ab67409a1dc9dd06543d0fe0ae31b713eb3
"
);
Ok(())
}
#[test]
fn require_hashes_registry_no_hash() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("example-a-961b4c22==1.0.0 --hash=sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e")?;
uv_snapshot!(context.pip_sync()
.env_remove(EnvVars::UV_EXCLUDE_NEWER)
.arg("requirements.txt")
.arg("--require-hashes")
.arg("--index-url")
.arg("https://astral-test.github.io/astral-test-hash/no-hash/simple-html/"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ example-a-961b4c22==1.0.0
"
);
Ok(())
}
#[test]
fn require_hashes_registry_valid_hash() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("example-a-961b4c22==1.0.0 --hash=sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e")?;
uv_snapshot!(context.pip_sync()
.env_remove(EnvVars::UV_EXCLUDE_NEWER)
.arg("requirements.txt")
.arg("--require-hashes")
.arg("--find-links")
.arg("https://astral-test.github.io/astral-test-hash/valid-hash/simple-html/"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because example-a-961b4c22 was not found in the package registry and you require example-a-961b4c22==1.0.0, we can conclude that your requirements are unsatisfiable.
"
);
Ok(())
}
#[test]
fn require_hashes_registry_invalid_hash() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("example-a-961b4c22==1.0.0 --hash=sha256:123")?;
uv_snapshot!(context.pip_sync()
.env_remove(EnvVars::UV_EXCLUDE_NEWER)
.arg("requirements.txt")
.arg("--reinstall")
.arg("--require-hashes")
.arg("--index-url")
.arg("https://astral-test.github.io/astral-test-hash/invalid-hash/simple-html/"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
× Failed to download `example-a-961b4c22==1.0.0`
╰─▶ Hash mismatch for `example-a-961b4c22==1.0.0`
Expected:
sha256:123
Computed:
sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e
"
);
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("example-a-961b4c22==1.0.0 --hash=sha256:8838f9d005ff0432b258ba648d9cabb1cbdf06ac29d14f788b02edae544032ea")?;
uv_snapshot!(context.pip_sync()
.env_remove(EnvVars::UV_EXCLUDE_NEWER)
.arg("requirements.txt")
.arg("--reinstall")
.arg("--require-hashes")
.arg("--index-url")
.arg("https://astral-test.github.io/astral-test-hash/invalid-hash/simple-html/"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
× Failed to download `example-a-961b4c22==1.0.0`
╰─▶ Hash mismatch for `example-a-961b4c22==1.0.0`
Expected:
sha256:8838f9d005ff0432b258ba648d9cabb1cbdf06ac29d14f788b02edae544032ea
Computed:
sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e
"
);
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("example-a-961b4c22==1.0.0 --hash=sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e")?;
uv_snapshot!(context.pip_sync()
.env_remove(EnvVars::UV_EXCLUDE_NEWER)
.arg("requirements.txt")
.arg("--reinstall")
.arg("--require-hashes")
.arg("--index-url")
.arg("https://astral-test.github.io/astral-test-hash/invalid-hash/simple-html/"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ example-a-961b4c22==1.0.0
"
);
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("example-a-961b4c22==1.0.0 --hash=sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e")?;
uv_snapshot!(context.pip_sync()
.env_remove(EnvVars::UV_EXCLUDE_NEWER)
.arg("requirements.txt")
.arg("--refresh")
.arg("--reinstall")
.arg("--require-hashes")
.arg("--index-url")
.arg("https://astral-test.github.io/astral-test-hash/invalid-hash/simple-html/"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
~ example-a-961b4c22==1.0.0
"
);
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("example-a-961b4c22==1.0.0 --hash=sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e --hash=sha256:a3cf07a05aac526131a2e8b6e4375ee6c6eaac8add05b88035e960ac6cd999ee")?;
uv_snapshot!(context.pip_sync()
.env_remove(EnvVars::UV_EXCLUDE_NEWER)
.arg("requirements.txt")
.arg("--refresh")
.arg("--reinstall")
.arg("--require-hashes")
.arg("--index-url")
.arg("https://astral-test.github.io/astral-test-hash/invalid-hash/simple-html/"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
× Failed to download and build `example-a-961b4c22==1.0.0`
╰─▶ Hash mismatch for `example-a-961b4c22==1.0.0`
Expected:
sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e
sha256:a3cf07a05aac526131a2e8b6e4375ee6c6eaac8add05b88035e960ac6cd999ee
Computed:
sha256:294e788dbe500fdc39e8b88e82652ab67409a1dc9dd06543d0fe0ae31b713eb3
"
);
Ok(())
}
#[test]
fn require_hashes_url() -> Result<()> {
let context = uv_test::test_context!("3.12").with_exclude_newer("2025-01-29T00:00:00Z");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("iniconfig @ https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl#sha256=b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--require-hashes"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ iniconfig==2.0.0 (from https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl#sha256=b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374)
"
);
Ok(())
}
#[test]
fn require_hashes_url_other_fragment() -> Result<()> {
let context = uv_test::test_context!("3.12").with_exclude_newer("2025-01-29T00:00:00Z");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("iniconfig @ https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl#foo=bar")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--require-hashes"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: In `--require-hashes` mode, all requirements must have a hash, but none were provided for: iniconfig @ https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl#foo=bar
"
);
Ok(())
}
#[test]
fn require_hashes_url_invalid() -> Result<()> {
let context = uv_test::test_context!("3.12").with_exclude_newer("2025-01-29T00:00:00Z");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("iniconfig @ https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl#sha256=c6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--require-hashes"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
× Failed to download `iniconfig @ https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl#sha256=c6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374`
╰─▶ Hash mismatch for `iniconfig @ https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl#sha256=c6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374`
Expected:
sha256:c6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374
Computed:
sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374
"
);
Ok(())
}
#[test]
fn require_hashes_url_merge() -> Result<()> {
let context = uv_test::test_context!("3.12").with_exclude_newer("2025-01-29T00:00:00Z");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl#sha256=cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f --hash sha512:f30761c1e8725b49c498273b90dba4b05c0fd157811994c806183062cb6647e773364ce45f0e1ff0b10e32fe6d0232ea5ad39476ccf37109d6b49603a09c11c2")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--require-hashes"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ anyio==4.0.0 (from https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl#sha256=cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f)
"
);
Ok(())
}
#[test]
fn require_hashes_url_unnamed() -> Result<()> {
let context = uv_test::test_context!("3.12").with_exclude_newer("2025-01-29T00:00:00Z");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt
.write_str("https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl#sha256=b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--require-hashes"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ iniconfig==2.0.0 (from https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl#sha256=b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374)
"
);
Ok(())
}
#[test]
fn target_built_distribution() -> Result<()> {
let context = uv_test::test_context!("3.12")
.with_filtered_python_names()
.with_filtered_virtualenv_bin()
.with_filtered_exe_suffix();
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str("iniconfig==2.0.0")?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.in")
.arg("--target")
.arg("target"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Using CPython 3.12.[X] interpreter at: .venv/[BIN]/[PYTHON]
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ iniconfig==2.0.0
");
assert!(context.temp_dir.child("target").child("iniconfig").is_dir());
context.assert_command("import iniconfig").failure();
context
.python_command()
.arg("-c")
.arg("import iniconfig")
.env(EnvVars::PYTHONPATH, context.temp_dir.child("target").path())
.current_dir(&context.temp_dir)
.assert()
.success();
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str("iniconfig==1.1.1")?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.in")
.arg("--target")
.arg("target"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Using CPython 3.12.[X] interpreter at: .venv/[BIN]/[PYTHON]
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
- iniconfig==2.0.0
+ iniconfig==1.1.1
");
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str("flask")?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.in")
.arg("--target")
.arg("target"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Using CPython 3.12.[X] interpreter at: .venv/[BIN]/[PYTHON]
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
+ flask==3.0.2
- iniconfig==1.1.1
");
assert!(
context
.temp_dir
.child("target")
.child("bin")
.child(format!("flask{EXE_SUFFIX}"))
.is_file()
);
Ok(())
}
#[test]
fn target_source_distribution() -> Result<()> {
let context = uv_test::test_context!("3.12")
.with_filtered_python_names()
.with_filtered_virtualenv_bin()
.with_filtered_exe_suffix();
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str("iniconfig==2.0.0")?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.in")
.arg("--no-binary")
.arg("iniconfig")
.arg("--target")
.arg("target"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Using CPython 3.12.[X] interpreter at: .venv/[BIN]/[PYTHON]
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ iniconfig==2.0.0
");
assert!(!context.temp_dir.child("target").child("hatchling").is_dir());
assert!(context.temp_dir.child("target").child("iniconfig").is_dir());
context.assert_command("import iniconfig").failure();
context
.python_command()
.arg("-c")
.arg("import iniconfig")
.env(EnvVars::PYTHONPATH, context.temp_dir.child("target").path())
.current_dir(&context.temp_dir)
.assert()
.success();
Ok(())
}
#[test]
fn target_no_build_isolation() -> Result<()> {
let context = uv_test::test_context!("3.12")
.with_filtered_python_names()
.with_filtered_virtualenv_bin()
.with_filtered_exe_suffix();
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str("flit_core")?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.in"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ flit-core==3.9.0
");
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str("wheel")?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.in")
.arg("--no-build-isolation")
.arg("--no-binary")
.arg("wheel")
.arg("--target")
.arg("target"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Using CPython 3.12.[X] interpreter at: .venv/[BIN]/[PYTHON]
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ wheel==0.43.0
");
assert!(!context.temp_dir.child("target").child("flit_core").is_dir());
assert!(context.temp_dir.child("target").child("wheel").is_dir());
context.assert_command("import wheel").failure();
context
.python_command()
.arg("-c")
.arg("import wheel")
.env(EnvVars::PYTHONPATH, context.temp_dir.child("target").path())
.current_dir(&context.temp_dir)
.assert()
.success();
Ok(())
}
#[test]
fn target_system() -> Result<()> {
let context = uv_test::test_context_with_versions!(&["3.12"]);
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str("iniconfig==2.0.0")?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.in")
.arg("--target")
.arg("target"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ iniconfig==2.0.0
");
assert!(context.temp_dir.child("target").child("iniconfig").is_dir());
Ok(())
}
#[test]
fn prefix() -> Result<()> {
let context = uv_test::test_context!("3.12")
.with_filtered_python_names()
.with_filtered_virtualenv_bin()
.with_filtered_exe_suffix();
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str("iniconfig==2.0.0")?;
let prefix = context.temp_dir.child("prefix");
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.in")
.arg("--prefix")
.arg(prefix.path()), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Using CPython 3.12.[X] interpreter at: .venv/[BIN]/[PYTHON]
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ iniconfig==2.0.0
");
context.assert_command("import iniconfig").failure();
context
.python_command()
.arg("-c")
.arg("import iniconfig")
.env(
EnvVars::PYTHONPATH,
site_packages_path(&context.temp_dir.join("prefix"), "python3.12"),
)
.current_dir(&context.temp_dir)
.assert()
.success();
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str("iniconfig==1.1.1")?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.in")
.arg("--prefix")
.arg(prefix.path()), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Using CPython 3.12.[X] interpreter at: .venv/[BIN]/[PYTHON]
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
- iniconfig==2.0.0
+ iniconfig==1.1.1
");
Ok(())
}
#[test]
fn preserve_markers() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("anyio ; python_version > '3.7'")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ anyio==4.3.0
"
);
Ok(())
}
#[test]
fn incompatible_build_constraint() -> Result<()> {
let context = uv_test::test_context!("3.9");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("requests==1.2")?;
let constraints_txt = context.temp_dir.child("build_constraints.txt");
constraints_txt.write_str("setuptools==1")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--build-constraint")
.arg("build_constraints.txt"), @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
× Failed to download and build `requests==1.2.0`
├─▶ Failed to resolve requirements from `setup.py` build
├─▶ No solution found when resolving: `setuptools>=40.8.0`
╰─▶ Because you require setuptools>=40.8.0 and setuptools==1, we can conclude that your requirements are unsatisfiable.
"
);
Ok(())
}
#[test]
fn compatible_build_constraint() -> Result<()> {
let context = uv_test::test_context!("3.9");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("requests==1.2")?;
let constraints_txt = context.temp_dir.child("build_constraints.txt");
constraints_txt.write_str("setuptools>=40")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--build-constraint")
.arg("build_constraints.txt"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ requests==1.2.0
"
);
Ok(())
}
#[test]
fn sync_seed() -> Result<()> {
let context = uv_test::test_context!("3.9");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("requests==1.2")?;
uv_snapshot!(context.filters(), context.pip_install()
.arg("pip"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ pip==24.0
"
);
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
- pip==24.0
+ requests==1.2.0
"
);
uv_snapshot!(context.filters(), context.venv().arg("--clear")
.arg("--seed"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Using CPython 3.9.[X] interpreter at: [PYTHON-3.9]
Creating virtual environment with seed packages at: .venv
+ pip==24.0
+ setuptools==69.2.0
+ wheel==0.43.0
Activate with: source .venv/[BIN]/activate
"
);
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Installed 1 package in [TIME]
+ requests==1.2.0
"
);
Ok(())
}
#[test]
fn sanitize() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("payload-package @ https://github.com/astral-sh/sanitize-wheel-test/raw/bc59283d5b4b136a191792e32baa51b477fdf65e/payload_package-0.1.0-py3-none-any.whl")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ payload-package==0.1.0 (from https://github.com/astral-sh/sanitize-wheel-test/raw/bc59283d5b4b136a191792e32baa51b477fdf65e/payload_package-0.1.0-py3-none-any.whl)
"
);
if let Some(parent) = context.temp_dir.parent() {
assert!(!parent.join("payload").exists());
}
Ok(())
}
#[test]
fn semicolon_trailing_space() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements = context.temp_dir.child("requirements.txt");
requirements.write_str("iniconfig @ https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl; python_version > '3.10'")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ iniconfig==2.0.0 (from https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl)
"
);
Ok(())
}
#[test]
fn semicolon_no_space() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements = context.temp_dir.child("requirements.txt");
requirements.write_str("iniconfig @ https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl;python_version > '3.10'")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Couldn't parse requirement in `requirements.txt` at position 0
Caused by: Expected direct URL (`https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl;python_version%20%3E%20'3.10'`) to end in a supported file extension: `.whl`, `.tar.gz`, `.zip`, `.tar.bz2`, `.tar.lz`, `.tar.lzma`, `.tar.xz`, `.tar.zst`, `.tar`, `.tbz`, `.tgz`, `.tlz`, or `.txz`
iniconfig @ https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl;python_version > '3.10'
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
"
);
Ok(())
}
#[test]
fn pep_751() -> Result<()> {
let context = uv_test::test_context!("3.12");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["anyio"]
"#,
)?;
context
.export()
.arg("-o")
.arg("pylock.toml")
.assert()
.success();
uv_snapshot!(context.filters(), context.pip_sync()
.arg("--preview")
.arg("pylock.toml"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Prepared 3 packages in [TIME]
Installed 3 packages in [TIME]
+ anyio==4.3.0
+ idna==3.6
+ sniffio==1.3.1
"
);
uv_snapshot!(context.filters(), context.pip_sync()
.arg("--preview")
.arg("pylock.toml"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Checked 3 packages in [TIME]
"
);
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["iniconfig"]
"#,
)?;
context
.export()
.arg("-o")
.arg("pylock.toml")
.assert()
.success();
uv_snapshot!(context.filters(), context.pip_sync()
.arg("--preview")
.arg("pylock.toml"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Prepared 1 package in [TIME]
Uninstalled 3 packages in [TIME]
Installed 1 package in [TIME]
- anyio==4.3.0
- idna==3.6
+ iniconfig==2.0.0
- sniffio==1.3.1
"
);
Ok(())
}
#[tokio::test]
async fn pep_751_remote() -> Result<()> {
let context = uv_test::test_context!("3.12");
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/pylock.toml"))
.respond_with(ResponseTemplate::new(200).set_body_string(indoc! {r#"
lock-version = "1.0"
created-by = "uv"
requires-python = ">=3.12"
[[packages]]
name = "anyio"
version = "4.3.0"
sdist = { url = "https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz", upload-time = 2024-02-19T08:36:28Z, size = 159642, hashes = { sha256 = "f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6" } }
wheels = [{ url = "https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl", upload-time = 2024-02-19T08:36:26Z, size = 85584, hashes = { sha256 = "048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8" } }]
dependencies = [
{ name = "idna" },
{ name = "sniffio" },
]
[[packages]]
name = "idna"
version = "3.6"
sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", upload-time = 2023-11-25T15:40:54Z, size = 175426, hashes = { sha256 = "9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca" } }
wheels = [{ url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", upload-time = 2023-11-25T15:40:52Z, size = 61567, hashes = { sha256 = "c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f" } }]
[[packages]]
name = "sniffio"
version = "1.3.1"
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", upload-time = 2024-02-25T23:20:04Z, size = 20372, hashes = { sha256 = "f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc" } }
wheels = [{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", upload-time = 2024-02-25T23:20:01Z, size = 10235, hashes = { sha256 = "2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2" } }]
"#}))
.mount(&server)
.await;
let pylock_url = format!("{}/pylock.toml", server.uri());
uv_snapshot!(context.filters(), context.pip_sync()
.arg("--preview")
.arg(&pylock_url), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Prepared 3 packages in [TIME]
Installed 3 packages in [TIME]
+ anyio==4.3.0
+ idna==3.6
+ sniffio==1.3.1
"
);
Ok(())
}
#[test]
fn pep_751_wheel_only() -> Result<()> {
let context = uv_test::test_context!("3.12");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12.0"
dependencies = ["torch"]
"#,
)?;
context
.export()
.arg("-o")
.arg("pylock.toml")
.assert()
.success();
uv_snapshot!(context.filters(), context.pip_sync()
.arg("--preview")
.arg("pylock.toml")
.arg("--dry-run")
.arg("--python-platform")
.arg("macos"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Would download 9 packages
Would install 9 packages
+ filelock==3.13.1
+ fsspec==2024.3.1
+ jinja2==3.1.3
+ markupsafe==2.1.5
+ mpmath==1.3.0
+ networkx==3.2.1
+ sympy==1.12
+ torch==2.2.1
+ typing-extensions==4.10.0
"
);
uv_snapshot!(context.filters(), context.pip_sync()
.arg("--preview")
.arg("pylock.toml")
.arg("--dry-run")
.arg("--python-platform")
.arg("macos")
.arg("--python-version")
.arg("3.8"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Package `torch` can't be installed because it doesn't have a source distribution or wheel for the current platform
hint: You're using CPython 3.8 (`cp38`), but `torch` (v2.2.1) only has wheels with the following Python implementation tag: `cp312`
"
);
Ok(())
}
#[test]
fn pep_751_build_options() -> Result<()> {
let context = uv_test::test_context!("3.12").with_exclude_newer("2025-01-29T00:00:00Z");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["anyio"]
"#,
)?;
context
.export()
.arg("-o")
.arg("pylock.toml")
.assert()
.success();
uv_snapshot!(context.filters(), context.pip_sync()
.arg("--preview")
.arg("pylock.toml")
.arg("--no-binary")
.arg("anyio"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Prepared 4 packages in [TIME]
Installed 4 packages in [TIME]
+ anyio==4.8.0
+ idna==3.10
+ sniffio==1.3.1
+ typing-extensions==4.12.2
"
);
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["odrive"]
"#,
)?;
context
.export()
.arg("-o")
.arg("pylock.toml")
.assert()
.success();
uv_snapshot!(context.filters(), context.pip_sync()
.arg("--preview")
.arg("pylock.toml")
.arg("--no-binary")
.arg("odrive"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Package `odrive` can't be installed because it is marked as `--no-binary` but has no source distribution
"
);
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["source-distribution"]
"#,
)?;
context
.export()
.arg("-o")
.arg("pylock.toml")
.assert()
.success();
uv_snapshot!(context.filters(), context.pip_sync()
.arg("--preview")
.arg("pylock.toml")
.arg("--only-binary")
.arg("source-distribution"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Package `source-distribution` can't be installed because it is marked as `--no-build` but has no binary distribution
"
);
uv_snapshot!(context.filters(), context.pip_sync()
.arg("--preview")
.arg("pylock.toml")
.arg("--no-binary")
.arg("source-distribution"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Prepared 1 package in [TIME]
Uninstalled 4 packages in [TIME]
Installed 1 package in [TIME]
- anyio==4.8.0
- idna==3.10
- sniffio==1.3.1
+ source-distribution==0.0.3
- typing-extensions==4.12.2
"
);
Ok(())
}
#[test]
fn pep_751_direct_url_tags() -> Result<()> {
let context = uv_test::test_context!("3.12");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["MarkupSafe @ https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl"]
"#,
)?;
context
.export()
.arg("-o")
.arg("pylock.toml")
.assert()
.success();
uv_snapshot!(context.filters(), context.pip_sync()
.arg("--preview")
.arg("pylock.toml")
.arg("--python-platform")
.arg("linux"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Failed to determine installation plan
Caused by: A URL dependency is incompatible with the current platform: https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl
hint: The wheel is compatible with macOS (`macosx_11_0_arm64`), but you're on Linux (`manylinux_2_28_x86_64`)
"
);
uv_snapshot!(context.filters(), context.pip_sync()
.arg("--preview")
.arg("pylock.toml")
.arg("--python-platform")
.arg("macos"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Installed 1 package in [TIME]
+ markupsafe==3.0.2 (from https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl)
"
);
Ok(())
}
#[test]
fn incompatible_python_version_direct_url() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("numpy @ https://files.pythonhosted.org/packages/ae/11/7c546fcf42145f29b71e4d6f429e96d8d68e5a7ba1830b2e68d7418f0bbd/numpy-2.3.2-cp313-cp313-win32.whl")?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt")
.arg("--python-platform")
.arg("windows"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
error: Failed to determine installation plan
Caused by: A URL dependency is incompatible with the current platform: https://files.pythonhosted.org/packages/ae/11/7c546fcf42145f29b71e4d6f429e96d8d68e5a7ba1830b2e68d7418f0bbd/numpy-2.3.2-cp313-cp313-win32.whl
hint: The wheel is compatible with CPython 3.13 (`cp313`), but you're using CPython 3.12 (`cp312`)
"
);
Ok(())
}
#[test]
fn incompatible_platform_direct_url() -> Result<()> {
let context = uv_test::test_context!("3.13");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("numpy @ https://files.pythonhosted.org/packages/ae/11/7c546fcf42145f29b71e4d6f429e96d8d68e5a7ba1830b2e68d7418f0bbd/numpy-2.3.2-cp313-cp313-win32.whl")?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt")
.arg("--python-platform")
.arg("linux"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
error: Failed to determine installation plan
Caused by: A URL dependency is incompatible with the current platform: https://files.pythonhosted.org/packages/ae/11/7c546fcf42145f29b71e4d6f429e96d8d68e5a7ba1830b2e68d7418f0bbd/numpy-2.3.2-cp313-cp313-win32.whl
hint: The wheel is compatible with Windows (`win32`), but you're on Linux (`manylinux_2_28_x86_64`)
"
);
Ok(())
}
#[cfg(feature = "test-python-managed")]
#[test]
fn sync_missing_python_no_target() -> Result<()> {
let context = uv_test::test_context!("3.11")
.with_python_download_cache()
.with_managed_python_dirs();
let requirements = context.temp_dir.child("requirements.txt");
requirements.write_str("anyio")?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("--python").arg("3.12")
.arg("requirements.txt"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: No virtual environment found for Python 3.12; run `uv venv` to create an environment, or pass `--system` to install into a non-virtual environment
"
);
Ok(())
}
#[cfg(feature = "test-python-managed")]
#[test]
fn sync_with_target_installs_missing_python() -> Result<()> {
let context = uv_test::test_context!("3.11")
.with_python_download_cache()
.with_managed_python_dirs()
.with_filtered_latest_python_versions();
let target_dir = context.temp_dir.child("target-dir");
let requirements = context.temp_dir.child("requirements.txt");
requirements.write_str("anyio")?;
uv_snapshot!(context.filters(), context.pip_sync()
.arg("requirements.txt")
.arg("--python").arg("3.12")
.arg("--target").arg(target_dir.path()), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Using CPython 3.12.[LATEST]
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ anyio==4.3.0
"
);
Ok(())
}