Add a `--show-settings` option for configuration testing (#4304)

## Summary

The fixtures here are pretty large, but it lets us test what we actually
care about (the resolved settings) rather than inferring the resolved
settings from behavior, which I think is a big improvement.

I also broke the tests down into more granular cases.
This commit is contained in:
Charlie Marsh 2024-06-13 20:14:23 -07:00 committed by GitHub
parent 1d6d98f3a3
commit f01ab57518
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 2152 additions and 526 deletions

View File

@ -99,9 +99,13 @@ pub(crate) struct GlobalArgs {
/// parent directories. /// parent directories.
#[arg(global = true, long, hide = true)] #[arg(global = true, long, hide = true)]
pub(crate) isolated: bool, pub(crate) isolated: bool,
/// Show the resolved settings for the current command.
#[arg(global = true, long, hide = true)]
pub(crate) show_settings: bool,
} }
#[derive(Debug, Clone, clap::ValueEnum)] #[derive(Debug, Copy, Clone, clap::ValueEnum)]
pub(crate) enum ColorChoice { pub(crate) enum ColorChoice {
/// Enables colored output only when the output is going to a terminal or TTY with support. /// Enables colored output only when the output is going to a terminal or TTY with support.
Auto, Auto,
@ -188,7 +192,7 @@ pub(crate) enum CacheCommand {
Dir, Dir,
} }
#[derive(Args)] #[derive(Args, Debug)]
#[allow(clippy::struct_excessive_bools)] #[allow(clippy::struct_excessive_bools)]
pub(crate) struct CleanArgs { pub(crate) struct CleanArgs {
/// The packages to remove from the cache. /// The packages to remove from the cache.

View File

@ -1,4 +1,5 @@
use std::env; use std::env;
use std::fmt::Write;
use std::io::stdout; use std::io::stdout;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::ExitCode; use std::process::ExitCode;
@ -139,7 +140,10 @@ async fn run() -> Result<ExitStatus> {
}; };
// Resolve the global settings. // Resolve the global settings.
let globals = GlobalSettings::resolve(cli.global_args, filesystem.as_ref()); let globals = GlobalSettings::resolve(&cli.global_args, filesystem.as_ref());
// Resolve the cache settings.
let cache_settings = CacheSettings::resolve(cli.cache_args, filesystem.as_ref());
// Configure the `tracing` crate, which controls internal logging. // Configure the `tracing` crate, which controls internal logging.
#[cfg(feature = "tracing-durations-export")] #[cfg(feature = "tracing-durations-export")]
@ -186,9 +190,25 @@ async fn run() -> Result<ExitStatus> {
debug!("uv {}", version::version()); debug!("uv {}", version::version());
// Resolve the cache settings. // Write out any resolved settings.
let cache = CacheSettings::resolve(cli.cache_args, filesystem.as_ref()); macro_rules! show_settings {
let cache = Cache::from_settings(cache.no_cache, cache.cache_dir)?; ($arg:expr) => {
if globals.show_settings {
writeln!(printer.stdout(), "{:#?}", $arg)?;
return Ok(ExitStatus::Success);
}
};
($arg:expr, false) => {
if globals.show_settings {
writeln!(printer.stdout(), "{:#?}", $arg)?;
}
};
}
show_settings!(globals, false);
show_settings!(cache_settings, false);
// Configure the cache.
let cache = Cache::from_settings(cache_settings.no_cache, cache_settings.cache_dir)?;
match cli.command { match cli.command {
Commands::Pip(PipNamespace { Commands::Pip(PipNamespace {
@ -198,6 +218,8 @@ async fn run() -> Result<ExitStatus> {
// Resolve the settings from the command-line arguments and workspace configuration. // Resolve the settings from the command-line arguments and workspace configuration.
let args = PipCompileSettings::resolve(args, filesystem); let args = PipCompileSettings::resolve(args, filesystem);
show_settings!(args);
rayon::ThreadPoolBuilder::new() rayon::ThreadPoolBuilder::new()
.num_threads(args.settings.concurrency.installs) .num_threads(args.settings.concurrency.installs)
.build_global() .build_global()
@ -275,6 +297,8 @@ async fn run() -> Result<ExitStatus> {
// Resolve the settings from the command-line arguments and workspace configuration. // Resolve the settings from the command-line arguments and workspace configuration.
let args = PipSyncSettings::resolve(args, filesystem); let args = PipSyncSettings::resolve(args, filesystem);
show_settings!(args);
rayon::ThreadPoolBuilder::new() rayon::ThreadPoolBuilder::new()
.num_threads(args.settings.concurrency.installs) .num_threads(args.settings.concurrency.installs)
.build_global() .build_global()
@ -335,6 +359,8 @@ async fn run() -> Result<ExitStatus> {
// Resolve the settings from the command-line arguments and workspace configuration. // Resolve the settings from the command-line arguments and workspace configuration.
let args = PipInstallSettings::resolve(args, filesystem); let args = PipInstallSettings::resolve(args, filesystem);
show_settings!(args);
rayon::ThreadPoolBuilder::new() rayon::ThreadPoolBuilder::new()
.num_threads(args.settings.concurrency.installs) .num_threads(args.settings.concurrency.installs)
.build_global() .build_global()
@ -410,6 +436,7 @@ async fn run() -> Result<ExitStatus> {
}) => { }) => {
// Resolve the settings from the command-line arguments and workspace configuration. // Resolve the settings from the command-line arguments and workspace configuration.
let args = PipUninstallSettings::resolve(args, filesystem); let args = PipUninstallSettings::resolve(args, filesystem);
show_settings!(args);
// Initialize the cache. // Initialize the cache.
let cache = cache.init()?; let cache = cache.init()?;
@ -445,6 +472,7 @@ async fn run() -> Result<ExitStatus> {
}) => { }) => {
// Resolve the settings from the command-line arguments and workspace configuration. // Resolve the settings from the command-line arguments and workspace configuration.
let args = PipFreezeSettings::resolve(args, filesystem); let args = PipFreezeSettings::resolve(args, filesystem);
show_settings!(args);
// Initialize the cache. // Initialize the cache.
let cache = cache.init()?; let cache = cache.init()?;
@ -466,6 +494,7 @@ async fn run() -> Result<ExitStatus> {
// Resolve the settings from the command-line arguments and workspace configuration. // Resolve the settings from the command-line arguments and workspace configuration.
let args = PipListSettings::resolve(args, filesystem); let args = PipListSettings::resolve(args, filesystem);
show_settings!(args);
// Initialize the cache. // Initialize the cache.
let cache = cache.init()?; let cache = cache.init()?;
@ -488,6 +517,7 @@ async fn run() -> Result<ExitStatus> {
}) => { }) => {
// Resolve the settings from the command-line arguments and workspace configuration. // Resolve the settings from the command-line arguments and workspace configuration.
let args = PipShowSettings::resolve(args, filesystem); let args = PipShowSettings::resolve(args, filesystem);
show_settings!(args);
// Initialize the cache. // Initialize the cache.
let cache = cache.init()?; let cache = cache.init()?;
@ -507,6 +537,7 @@ async fn run() -> Result<ExitStatus> {
}) => { }) => {
// Resolve the settings from the command-line arguments and workspace configuration. // Resolve the settings from the command-line arguments and workspace configuration.
let args = PipCheckSettings::resolve(args, filesystem); let args = PipCheckSettings::resolve(args, filesystem);
show_settings!(args);
// Initialize the cache. // Initialize the cache.
let cache = cache.init()?; let cache = cache.init()?;
@ -522,7 +553,10 @@ async fn run() -> Result<ExitStatus> {
Commands::Cache(CacheNamespace { Commands::Cache(CacheNamespace {
command: CacheCommand::Clean(args), command: CacheCommand::Clean(args),
}) })
| Commands::Clean(args) => commands::cache_clean(&args.package, &cache, printer), | Commands::Clean(args) => {
show_settings!(args);
commands::cache_clean(&args.package, &cache, printer)
}
Commands::Cache(CacheNamespace { Commands::Cache(CacheNamespace {
command: CacheCommand::Prune, command: CacheCommand::Prune,
}) => commands::cache_prune(&cache, printer), }) => commands::cache_prune(&cache, printer),
@ -537,6 +571,7 @@ async fn run() -> Result<ExitStatus> {
// Resolve the settings from the command-line arguments and workspace configuration. // Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::VenvSettings::resolve(args, filesystem); let args = settings::VenvSettings::resolve(args, filesystem);
show_settings!(args);
// Initialize the cache. // Initialize the cache.
let cache = cache.init()?; let cache = cache.init()?;
@ -573,6 +608,7 @@ async fn run() -> Result<ExitStatus> {
Commands::Project(ProjectCommand::Run(args)) => { Commands::Project(ProjectCommand::Run(args)) => {
// Resolve the settings from the command-line arguments and workspace configuration. // Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::RunSettings::resolve(args, filesystem); let args = settings::RunSettings::resolve(args, filesystem);
show_settings!(args);
// Initialize the cache. // Initialize the cache.
let cache = cache.init()?.with_refresh(args.refresh); let cache = cache.init()?.with_refresh(args.refresh);
@ -605,6 +641,7 @@ async fn run() -> Result<ExitStatus> {
Commands::Project(ProjectCommand::Sync(args)) => { Commands::Project(ProjectCommand::Sync(args)) => {
// Resolve the settings from the command-line arguments and workspace configuration. // Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::SyncSettings::resolve(args, filesystem); let args = settings::SyncSettings::resolve(args, filesystem);
show_settings!(args);
// Initialize the cache. // Initialize the cache.
let cache = cache.init()?.with_refresh(args.refresh); let cache = cache.init()?.with_refresh(args.refresh);
@ -626,6 +663,7 @@ async fn run() -> Result<ExitStatus> {
Commands::Project(ProjectCommand::Lock(args)) => { Commands::Project(ProjectCommand::Lock(args)) => {
// Resolve the settings from the command-line arguments and workspace configuration. // Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::LockSettings::resolve(args, filesystem); let args = settings::LockSettings::resolve(args, filesystem);
show_settings!(args);
// Initialize the cache. // Initialize the cache.
let cache = cache.init()?.with_refresh(args.refresh); let cache = cache.init()?.with_refresh(args.refresh);
@ -645,6 +683,7 @@ async fn run() -> Result<ExitStatus> {
Commands::Project(ProjectCommand::Add(args)) => { Commands::Project(ProjectCommand::Add(args)) => {
// Resolve the settings from the command-line arguments and workspace configuration. // Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::AddSettings::resolve(args, filesystem); let args = settings::AddSettings::resolve(args, filesystem);
show_settings!(args);
// Initialize the cache. // Initialize the cache.
let cache = cache.init()?; let cache = cache.init()?;
@ -664,6 +703,7 @@ async fn run() -> Result<ExitStatus> {
Commands::Project(ProjectCommand::Remove(args)) => { Commands::Project(ProjectCommand::Remove(args)) => {
// Resolve the settings from the command-line arguments and workspace configuration. // Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::RemoveSettings::resolve(args, filesystem); let args = settings::RemoveSettings::resolve(args, filesystem);
show_settings!(args);
// Initialize the cache. // Initialize the cache.
let cache = cache.init()?; let cache = cache.init()?;
@ -697,6 +737,7 @@ async fn run() -> Result<ExitStatus> {
}) => { }) => {
// Resolve the settings from the command-line arguments and workspace configuration. // Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::ToolRunSettings::resolve(args, filesystem); let args = settings::ToolRunSettings::resolve(args, filesystem);
show_settings!(args);
// Initialize the cache. // Initialize the cache.
let cache = cache.init()?.with_refresh(args.refresh); let cache = cache.init()?.with_refresh(args.refresh);
@ -723,6 +764,7 @@ async fn run() -> Result<ExitStatus> {
}) => { }) => {
// Resolve the settings from the command-line arguments and workspace configuration. // Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::ToolchainListSettings::resolve(args, filesystem); let args = settings::ToolchainListSettings::resolve(args, filesystem);
show_settings!(args);
// Initialize the cache. // Initialize the cache.
let cache = cache.init()?; let cache = cache.init()?;
@ -742,6 +784,7 @@ async fn run() -> Result<ExitStatus> {
}) => { }) => {
// Resolve the settings from the command-line arguments and workspace configuration. // Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::ToolchainInstallSettings::resolve(args, filesystem); let args = settings::ToolchainInstallSettings::resolve(args, filesystem);
show_settings!(args);
// Initialize the cache. // Initialize the cache.
let cache = cache.init()?; let cache = cache.init()?;

View File

@ -41,12 +41,13 @@ pub(crate) struct GlobalSettings {
pub(crate) native_tls: bool, pub(crate) native_tls: bool,
pub(crate) connectivity: Connectivity, pub(crate) connectivity: Connectivity,
pub(crate) isolated: bool, pub(crate) isolated: bool,
pub(crate) show_settings: bool,
pub(crate) preview: PreviewMode, pub(crate) preview: PreviewMode,
} }
impl GlobalSettings { impl GlobalSettings {
/// Resolve the [`GlobalSettings`] from the CLI and filesystem configuration. /// Resolve the [`GlobalSettings`] from the CLI and filesystem configuration.
pub(crate) fn resolve(args: GlobalArgs, workspace: Option<&FilesystemOptions>) -> Self { pub(crate) fn resolve(args: &GlobalArgs, workspace: Option<&FilesystemOptions>) -> Self {
Self { Self {
quiet: args.quiet, quiet: args.quiet,
verbose: args.verbose, verbose: args.verbose,
@ -79,6 +80,7 @@ impl GlobalSettings {
Connectivity::Online Connectivity::Online
}, },
isolated: args.isolated, isolated: args.isolated,
show_settings: args.show_settings,
preview: PreviewMode::from( preview: PreviewMode::from(
flag(args.preview, args.no_preview) flag(args.preview, args.no_preview)
.combine(workspace.and_then(|workspace| workspace.globals.preview)) .combine(workspace.and_then(|workspace| workspace.globals.preview))

View File

@ -35,6 +35,9 @@ pub const INSTA_FILTERS: &[(&str, &str)] = &[
(r"(\s|\()(\d+m )?(\d+\.)?\d+(ms|s)", "$1[TIME]"), (r"(\s|\()(\d+m )?(\d+\.)?\d+(ms|s)", "$1[TIME]"),
// File sizes // File sizes
(r"(\s|\()(\d+\.)?\d+([KM]i)?B", "$1[SIZE]"), (r"(\s|\()(\d+\.)?\d+([KM]i)?B", "$1[SIZE]"),
// Timestamps
(r"tv_sec: \d+", "tv_sec: [TIME]"),
(r"tv_nsec: \d+", "tv_nsec: [TIME]"),
// Rewrite Windows output to Unix output // Rewrite Windows output to Unix output
(r"\\([\w\d])", "/$1"), (r"\\([\w\d])", "/$1"),
(r"uv.exe", "uv"), (r"uv.exe", "uv"),

View File

@ -8962,524 +8962,6 @@ fn python_platform() -> Result<()> {
Ok(()) Ok(())
} }
/// Verify that command-line arguments take precedence over on-disk configuration.
#[test]
fn resolve_configuration() -> Result<()> {
let context = TestContext::new("3.12");
// Write a `uv.toml` file to the directory.
let config = context.temp_dir.child("uv.toml");
config.write_str(indoc::indoc! {r#"
[pip]
resolution = "lowest-direct"
generate-hashes = true
index-url = "https://pypi.org/simple"
"#})?;
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str("anyio>3.0.0")?;
// Resolution should use the lowest direct version, and generate hashes.
uv_snapshot!(context.compile()
.arg("requirements.in"), @r###"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in
anyio==3.0.1 \
--hash=sha256:1ef7622396ab55829d4236a6f75e2199df6d26a4ba79bea0cb942a5fd2f79a23 \
--hash=sha256:ed71f7542ef39875b65def219794d9dcb0a48c571317b13612c12b1f292701b5
# via -r requirements.in
idna==3.6 \
--hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \
--hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f
# via anyio
sniffio==1.3.1 \
--hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \
--hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc
# via anyio
----- stderr -----
Resolved 3 packages in [TIME]
"###
);
// Resolution should use the highest version, and generate hashes.
uv_snapshot!(context.compile()
.arg("requirements.in")
.arg("--resolution=highest"), @r###"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in --resolution=highest
anyio==4.3.0 \
--hash=sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8 \
--hash=sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6
# via -r requirements.in
idna==3.6 \
--hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \
--hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f
# via anyio
sniffio==1.3.1 \
--hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \
--hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc
# via anyio
----- stderr -----
Resolved 3 packages in [TIME]
"###
);
// Resolution should use the highest version, and omit hashes.
uv_snapshot!(context.compile()
.arg("requirements.in")
.arg("--resolution=highest")
.arg("--no-generate-hashes"), @r###"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in --resolution=highest --no-generate-hashes
anyio==4.3.0
# via -r requirements.in
idna==3.6
# via anyio
sniffio==1.3.1
# via anyio
----- stderr -----
Resolved 3 packages in [TIME]
"###
);
// Write a `pyproject.toml` file to the directory.
let pyproject = context.temp_dir.child("pyproject.toml");
pyproject.write_str(indoc::indoc! {r#"
[project]
name = "example"
version = "0.0.0"
"#})?;
// Resolution should use the lowest direct version, and generate hashes.
uv_snapshot!(context.compile()
.arg("requirements.in"), @r###"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in
anyio==3.0.1 \
--hash=sha256:1ef7622396ab55829d4236a6f75e2199df6d26a4ba79bea0cb942a5fd2f79a23 \
--hash=sha256:ed71f7542ef39875b65def219794d9dcb0a48c571317b13612c12b1f292701b5
# via -r requirements.in
idna==3.6 \
--hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \
--hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f
# via anyio
sniffio==1.3.1 \
--hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \
--hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc
# via anyio
----- stderr -----
Resolved 3 packages in [TIME]
"###
);
// Remove the `uv.toml` file.
fs_err::remove_file(config.path())?;
// Resolution should use the highest version, and omit hashes.
uv_snapshot!(context.compile()
.arg("requirements.in"), @r###"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in
anyio==4.3.0
# via -r requirements.in
idna==3.6
# via anyio
sniffio==1.3.1
# via anyio
----- stderr -----
Resolved 3 packages in [TIME]
"###
);
// Add configuration to the `pyproject.toml` file.
pyproject.write_str(indoc::indoc! {r#"
[project]
name = "example"
version = "0.0.0"
[tool.uv.pip]
resolution = "lowest-direct"
generate-hashes = true
index-url = "https://pypi.org/simple"
"#})?;
// Resolution should use the lowest direct version, and generate hashes.
uv_snapshot!(context.compile()
.arg("requirements.in"), @r###"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in
anyio==3.0.1 \
--hash=sha256:1ef7622396ab55829d4236a6f75e2199df6d26a4ba79bea0cb942a5fd2f79a23 \
--hash=sha256:ed71f7542ef39875b65def219794d9dcb0a48c571317b13612c12b1f292701b5
# via -r requirements.in
idna==3.6 \
--hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \
--hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f
# via anyio
sniffio==1.3.1 \
--hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \
--hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc
# via anyio
----- stderr -----
Resolved 3 packages in [TIME]
"###
);
// Add an extra index URL entry to the `pyproject.toml` file.
pyproject.write_str(indoc::indoc! {r#"
[project]
name = "example"
version = "0.0.0"
[tool.uv.pip]
index-url = "https://test.pypi.org/simple"
extra-index-url = ["https://pypi.org/simple"]
"#})?;
// Resolution should succeed, since the PyPI index is preferred.
uv_snapshot!(context.compile()
.arg("requirements.in"), @r###"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in
anyio==4.3.0
# via -r requirements.in
idna==3.6
# via anyio
sniffio==1.3.1
# via anyio
----- stderr -----
Resolved 3 packages in [TIME]
"###
);
// Providing an additional index URL on the command-line should fail, since it will be
// preferred (but the test index alone can't satisfy the requirements).
uv_snapshot!(context.compile()
.arg("requirements.in")
.arg("--extra-index-url")
.arg("https://test.pypi.org/simple"), @r###"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
Because only idna<2.8 is available and anyio==3.5.0 depends on idna>=2.8, we can conclude that anyio==3.5.0 cannot be used.
And because only the following versions of anyio are available:
anyio<=3.0.0
anyio==3.5.0
and you require anyio, we can conclude that the requirements are unsatisfiable.
"###
);
// If we allow the resolver to use _any_ index, it should succeed, since it now has _both_
// the test and PyPI indexes in its `--extra-index-url`.
uv_snapshot!(context.compile()
.arg("requirements.in")
.arg("--extra-index-url")
.arg("https://test.pypi.org/simple")
.arg("--index-strategy")
.arg("unsafe-best-match"), @r###"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in --index-strategy unsafe-best-match
anyio==4.3.0
# via -r requirements.in
idna==3.6
# via anyio
sniffio==1.3.1
# via anyio
----- stderr -----
Resolved 3 packages in [TIME]
"###
);
// Write out a `--find-links` entry.
pyproject.write_str(indoc::indoc! {r#"
[project]
name = "example"
version = "0.0.0"
[tool.uv.pip]
no-index = true
find-links = ["https://download.pytorch.org/whl/torch_stable.html"]
"#})?;
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str("tqdm")?;
uv_snapshot!(context.compile()
.arg("requirements.in"), @r###"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in
tqdm==4.66.2
# via -r requirements.in
----- stderr -----
Resolved 1 package in [TIME]
"###
);
// Write out to the top-level (`tool.uv`, rather than `tool.uv.pip`).
pyproject.write_str(indoc::indoc! {r#"
[project]
name = "example"
version = "0.0.0"
[tool.uv]
resolution = "lowest-direct"
"#})?;
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str("anyio>3.0.0")?;
uv_snapshot!(context.compile()
.arg("requirements.in"), @r###"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in
anyio==3.0.1
# via -r requirements.in
idna==3.6
# via anyio
sniffio==1.3.1
# via anyio
----- stderr -----
Resolved 3 packages in [TIME]
"###
);
// Write out to both the top-level (`tool.uv`) and the pip section (`tool.uv.pip`). The
// `tool.uv.pip` section should take precedence.
pyproject.write_str(indoc::indoc! {r#"
[project]
name = "example"
version = "0.0.0"
[tool.uv]
resolution = "lowest-direct"
[tool.uv.pip]
resolution = "highest"
"#})?;
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str("anyio>3.0.0")?;
uv_snapshot!(context.compile()
.arg("requirements.in"), @r###"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in
anyio==4.3.0
# via -r requirements.in
idna==3.6
# via anyio
sniffio==1.3.1
# via anyio
----- stderr -----
Resolved 3 packages in [TIME]
"###
);
// But the command-line should take precedence over both.
uv_snapshot!(context.compile()
.arg("requirements.in")
.arg("--resolution=lowest-direct"), @r###"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in --resolution=lowest-direct
anyio==3.0.1
# via -r requirements.in
idna==3.6
# via anyio
sniffio==1.3.1
# via anyio
----- stderr -----
Resolved 3 packages in [TIME]
"###
);
Ok(())
}
/// Verify that user configuration is respected.
#[test]
#[cfg(not(windows))]
fn resolve_user_configuration() -> Result<()> {
// Create a temporary directory to store the user configuration.
let xdg = assert_fs::TempDir::new().expect("Failed to create temp dir");
let uv = xdg.child("uv");
let config = uv.child("uv.toml");
config.write_str(indoc::indoc! {r#"
[pip]
resolution = "lowest-direct"
"#})?;
let context = TestContext::new("3.12");
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str("anyio>3.0.0")?;
// Resolution should use the lowest direct version.
uv_snapshot!(context.compile()
.arg("requirements.in")
.env("XDG_CONFIG_HOME", xdg.path()), @r###"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in
anyio==3.0.1
# via -r requirements.in
idna==3.6
# via anyio
sniffio==1.3.1
# via anyio
----- stderr -----
Resolved 3 packages in [TIME]
"###
);
// Add a local configuration to generate hashes.
let config = context.temp_dir.child("uv.toml");
config.write_str(indoc::indoc! {r"
[pip]
generate-hashes = true
"})?;
// Resolution should use the lowest direct version and generate hashes.
uv_snapshot!(context.compile()
.arg("requirements.in")
.env("XDG_CONFIG_HOME", xdg.path()), @r###"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in
anyio==3.0.1 \
--hash=sha256:1ef7622396ab55829d4236a6f75e2199df6d26a4ba79bea0cb942a5fd2f79a23 \
--hash=sha256:ed71f7542ef39875b65def219794d9dcb0a48c571317b13612c12b1f292701b5
# via -r requirements.in
idna==3.6 \
--hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \
--hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f
# via anyio
sniffio==1.3.1 \
--hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \
--hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc
# via anyio
----- stderr -----
Resolved 3 packages in [TIME]
"###
);
// Add a local configuration to override the user configuration.
let config = context.temp_dir.child("uv.toml");
config.write_str(indoc::indoc! {r#"
[pip]
resolution = "highest"
"#})?;
// Resolution should use the highest version.
uv_snapshot!(context.compile()
.arg("requirements.in")
.env("XDG_CONFIG_HOME", xdg.path()), @r###"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in
anyio==4.3.0
# via -r requirements.in
idna==3.6
# via anyio
sniffio==1.3.1
# via anyio
----- stderr -----
Resolved 3 packages in [TIME]
"###
);
// However, the user-level `tool.uv.pip` settings override the project-level `tool.uv` settings.
// This is awkward, but we merge the user configuration into the workspace configuration, so
// the resulting configuration has both `tool.uv.pip.resolution` (from the user configuration)
// and `tool.uv.resolution` (from the workspace settings), so we choose the former.
let config = context.temp_dir.child("uv.toml");
config.write_str(indoc::indoc! {r#"
resolution = "highest"
"#})?;
// Resolution should use the highest version.
uv_snapshot!(context.compile()
.arg("requirements.in")
.env("XDG_CONFIG_HOME", xdg.path()), @r###"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in
anyio==3.0.1
# via -r requirements.in
idna==3.6
# via anyio
sniffio==1.3.1
# via anyio
----- stderr -----
Resolved 3 packages in [TIME]
"###
);
Ok(())
}
/// Resolve a specific source distribution via a Git HTTPS dependency. /// Resolve a specific source distribution via a Git HTTPS dependency.
#[test] #[test]
#[cfg(feature = "git")] #[cfg(feature = "git")]

File diff suppressed because it is too large Load Diff