From ed50e40d05db1b34d9cf7f1148599a67d6f3df43 Mon Sep 17 00:00:00 2001 From: konsti Date: Thu, 15 Jan 2026 11:40:31 +0100 Subject: [PATCH] Clear known env vars for tests (#14080) Currently, it's possible to break our test suite by having an env var set that influences uv, either a `UV_*` var, or something more generic such as the XDG env vars. We previously fixed them env-var-by-env-var as we discovered. By clearing uv-specific env var for subcommands, we can invert this. We intentionally avoid clearing all env vars as some of them, especially on Windows, are system env vars required for basic operations. Notable limitations are that this only affects tests that use `TestContext::add_shared_env` (default, can be opted-out). Fixed #9873 --- crates/uv-macros/src/lib.rs | 20 +++++++++++++++++ crates/uv/tests/it/common/mod.rs | 38 ++++++++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/crates/uv-macros/src/lib.rs b/crates/uv-macros/src/lib.rs index d32e908fe..db2cc9853 100644 --- a/crates/uv-macros/src/lib.rs +++ b/crates/uv-macros/src/lib.rs @@ -143,6 +143,21 @@ pub fn attribute_env_vars_metadata(_attr: TokenStream, input: TokenStream) -> To return quote! { #ast #(#added_in_errors)* }.into(); } + let env_var_names = ast.items.iter().filter_map(|item| { + if let ImplItem::Const(item) = item { + let syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(lit), + .. + }) = &item.expr + else { + return None; + }; + Some(lit.value()) + } else { + None + } + }); + let struct_name = &ast.self_ty; let pairs = constants.iter().map(|(name, doc, added_in, _span)| { if let Some(added_in) = added_in { @@ -160,6 +175,11 @@ pub fn attribute_env_vars_metadata(_attr: TokenStream, input: TokenStream) -> To pub fn metadata<'a>() -> &'a [(&'static str, &'static str, Option<&'static str>)] { &[#(#pairs),*] } + + /// Returns all environment variable names defined as constants (including hidden ones). + pub fn all_names() -> &'static [&'static str] { + &[#(#env_var_names),*] + } } }; diff --git a/crates/uv/tests/it/common/mod.rs b/crates/uv/tests/it/common/mod.rs index 85e7e290f..a742793a6 100644 --- a/crates/uv/tests/it/common/mod.rs +++ b/crates/uv/tests/it/common/mod.rs @@ -1444,6 +1444,9 @@ impl TestContext { command } + /// The path to the Python interpreter in the venv. + /// + /// Don't use this for `Command::new`, use `Self::python_command` instead. pub fn interpreter(&self) -> PathBuf { let venv = &self.venv; if cfg!(unix) { @@ -1726,8 +1729,39 @@ impl TestContext { /// Creates a new `Command` that is intended to be suitable for use in /// all tests, but with the given binary. + /// + /// Clears environment variables defined in [`EnvVars`] to avoid reading + /// test host settings. fn new_command_with(bin: &Path) -> Command { - Command::new(bin) + let mut command = Command::new(bin); + + let passthrough = [ + // For debugging tests. + EnvVars::RUST_LOG, + EnvVars::RUST_BACKTRACE, + // Windows System configuration. + EnvVars::SYSTEMDRIVE, + // Work around small default stack sizes and large futures in debug builds. + EnvVars::RUST_MIN_STACK, + EnvVars::UV_STACK_SIZE, + // Allow running tests with custom network settings. + EnvVars::ALL_PROXY, + EnvVars::HTTPS_PROXY, + EnvVars::HTTP_PROXY, + EnvVars::NO_PROXY, + EnvVars::SSL_CERT_DIR, + EnvVars::SSL_CERT_FILE, + EnvVars::UV_NATIVE_TLS, + ]; + + for env_var in EnvVars::all_names() + .iter() + .filter(|name| !passthrough.contains(name)) + { + command.env_remove(env_var); + } + + command } } @@ -1790,7 +1824,7 @@ pub fn get_python(version: &PythonVersion) -> PathBuf { /// Create a virtual environment at the given path. pub fn create_venv_from_executable>(path: P, cache_dir: &ChildPath, python: &Path) { - assert_cmd::Command::new(get_bin()) + TestContext::new_command_with(&get_bin()) .arg("venv") .arg(path.as_ref().as_os_str()) .arg("--clear")