diff --git a/Cargo.lock b/Cargo.lock index 9d2fd2166..a377d0551 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6717,6 +6717,7 @@ dependencies = [ "serde", "serde_json", "smallvec", + "temp-env", "thiserror 2.0.18", "tracing", "tracing-test", diff --git a/crates/uv-pep508/Cargo.toml b/crates/uv-pep508/Cargo.toml index 5270b2528..788fc6f89 100644 --- a/crates/uv-pep508/Cargo.toml +++ b/crates/uv-pep508/Cargo.toml @@ -34,6 +34,7 @@ rustc-hash = { workspace = true } schemars = { workspace = true, optional = true } serde = { workspace = true, features = ["derive", "rc"] } smallvec = { workspace = true } +temp-env = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true, optional = true } unicode-width = { workspace = true } diff --git a/crates/uv-pep508/src/lib.rs b/crates/uv-pep508/src/lib.rs index 368e6bc99..56416afb6 100644 --- a/crates/uv-pep508/src/lib.rs +++ b/crates/uv-pep508/src/lib.rs @@ -1084,7 +1084,6 @@ impl mod tests { //! Half of these tests are copied from - use std::env; use std::str::FromStr; use insta::assert_snapshot; @@ -1822,7 +1821,7 @@ mod tests { "foo @ file:foo-3.0.0-py3-none-any.whl", "foo @ ./foo-3.0.0-py3-none-any.whl", ]; - let cwd = env::current_dir().unwrap(); + let cwd = std::env::current_dir().unwrap(); for requirement in requirements { assert_eq!( diff --git a/crates/uv-pep508/src/verbatim_url.rs b/crates/uv-pep508/src/verbatim_url.rs index ff424658b..9669d0669 100644 --- a/crates/uv-pep508/src/verbatim_url.rs +++ b/crates/uv-pep508/src/verbatim_url.rs @@ -791,4 +791,77 @@ mod tests { @"ambiguous user/pass authority in URL (not percent-encoded?): https:***@domain/a/b/c" ); } + + #[test] + fn env_vars() { + temp_env::with_vars( + [ + ("FOO", None), + ("BAR", Some("bar")), + ("BAZ", Some("baz")), + ("Not-Valid", Some("Not-Valid")), + ("TEST_1", Some("Test 1")), + ("PROJECT_ROOT", None), + ], + || { + // Basic + assert_eq!(expand_env_vars(""), Cow::Borrowed("")); + assert_eq!(expand_env_vars("test"), Cow::Borrowed("test")); + assert_eq!(expand_env_vars("$"), Cow::Borrowed("$")); + + // Invalid + assert_eq!(expand_env_vars("$FOO"), Cow::Borrowed("$FOO")); + assert_eq!(expand_env_vars("$BAR"), Cow::Borrowed("$BAR")); + assert_eq!(expand_env_vars("${BAR"), Cow::Borrowed("${BAR")); + assert_eq!(expand_env_vars("$BAR}"), Cow::Borrowed("$BAR}")); + assert_eq!(expand_env_vars("${ BAR }"), Cow::Borrowed("${ BAR }")); + assert_eq!( + expand_env_vars("${Not-Valid}"), + Cow::Borrowed("${Not-Valid}") + ); + assert_eq!(expand_env_vars("${}"), Cow::Borrowed("${}")); + + // Missing + assert_eq!(expand_env_vars("${FOO}"), "${FOO}"); + + // Case sensitive + assert_eq!(expand_env_vars("${bar}"), "${bar}"); + + // One variable referenced + assert_eq!(expand_env_vars("${BAR}"), "bar"); + assert_eq!(expand_env_vars("foo ${BAR}"), "foo bar"); + assert_eq!(expand_env_vars("foo ${BAR} baz"), "foo bar baz"); + assert_eq!(expand_env_vars("foo ${BAR} baz ${BAR}"), "foo bar baz bar"); + + // Two variables referenced + assert_eq!(expand_env_vars("${FOO} ${BAR} ${BAZ}"), "${FOO} bar baz"); + assert_eq!(expand_env_vars("<${BAR}-${BAZ}>"), ""); + assert_eq!( + expand_env_vars("${FOO}${BAR}${BAZ}${FOO}${BAR}${BAZ}"), + "${FOO}barbaz${FOO}barbaz" + ); + + // Weird + assert_eq!(expand_env_vars("${${TEST_1}}"), "${Test 1}"); + + // PROJECT_ROOT + let cwd = std::env::current_dir().unwrap(); + let cwd = cwd.to_string_lossy(); + + assert_eq!( + expand_env_vars("${PROJECT_ROOT}/file"), + format!("{cwd}/file") + ); + assert_eq!( + expand_env_vars("$PROJECT_ROOT/file.txt"), + Cow::Borrowed("$PROJECT_ROOT/file.txt") + ); + + assert_eq!( + expand_env_vars("${FOO} ${BAR} ${PROJECT_ROOT} ${BAZ}"), + format!("${{FOO}} bar {cwd} baz") + ); + }, + ); + } }