mirror of
https://github.com/astral-sh/ruff.git
synced 2026-10-28 10:45:37 +08:00
[ty] Isolate uv integration tests from inherited environment settings (#28342)
Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
1 parent
321eebc2d6
commit
12132db288
16 files changed
+273
-105
No files matched your search
@@ -405,9 +405,6 @@ jobs:
|
||||
# This step is just to get nice GitHub annotations on the PR diff in the files-changed tab.
|
||||
run: cargo test -p ty_python_semantic --test mdtest || true
|
||||
- name: "Run tests"
|
||||
env:
|
||||
# Tests create disposable uv projects that must be resolved without a lockfile.
|
||||
UV_LOCKED: 0
|
||||
run: uv run --locked --only-dev cargo insta test --all-features --unreferenced reject --test-runner nextest --disable-nextest-doctest
|
||||
- name: "Run doctests"
|
||||
run: cargo test --doc --all-features
|
||||
@@ -464,9 +461,6 @@ jobs:
|
||||
version: "0.12.9"
|
||||
enable-cache: "true"
|
||||
- name: "Run tests"
|
||||
env:
|
||||
# Tests create disposable uv projects that must be resolved without a lockfile.
|
||||
UV_LOCKED: 0
|
||||
run: uv run --locked --only-dev cargo nextest run --cargo-profile profiling --all-features
|
||||
- name: "Run doctests"
|
||||
run: cargo test --doc --profile profiling --all-features
|
||||
@@ -502,9 +496,6 @@ jobs:
|
||||
version: "0.12.9"
|
||||
enable-cache: "true"
|
||||
- name: "Run tests"
|
||||
env:
|
||||
# Tests create disposable uv projects that must be resolved without a lockfile.
|
||||
UV_LOCKED: 0
|
||||
run: |
|
||||
uv run --locked --only-dev cargo nextest run --all-features --profile ci
|
||||
cargo test --all-features --doc
|
||||
|
||||
@@ -281,9 +281,6 @@ jobs:
|
||||
# Work around https://github.com/mitsuhiko/insta/pull/924 until its fix is released.
|
||||
CI: "0"
|
||||
MDTEST_UPDATE_SNAPSHOTS: "1"
|
||||
# `UV_LOCKED: 1` will cause spurious changes to snapshots
|
||||
# until https://github.com/astral-sh/ty/issues/4460 is fixed.
|
||||
UV_LOCKED: 0
|
||||
run: uv run --only-dev cargo insta test --accept --all-features --test-runner nextest --disable-nextest-doctest
|
||||
- name: Commit and push snapshot changes
|
||||
if: ${{ success() }}
|
||||
|
||||
@@ -3,23 +3,18 @@ use std::time::Duration;
|
||||
|
||||
use divan::{Bencher, bench};
|
||||
use rayon::ThreadPoolBuilder;
|
||||
use ruff_db::system::{OsSystem, SystemPath, TestSystem};
|
||||
use ty_project::{Db, ProjectDatabase, ProjectMetadata, ScriptEnvironmentAvailability};
|
||||
use ruff_db::system::{OsSystem, System, SystemPath, TestSystem};
|
||||
use ty_project::{
|
||||
Db, ProjectDatabase, ProjectMetadata, ScriptEnvironmentAvailability, uv_test_env_vars,
|
||||
};
|
||||
use ty_static::EnvVars;
|
||||
|
||||
fn setup_iteration(root: &SystemPath) -> ProjectDatabase {
|
||||
fn setup_iteration(root: &SystemPath, uv: &SystemPath) -> ProjectDatabase {
|
||||
let system = TestSystem::new(OsSystem::new(root));
|
||||
for name in [
|
||||
EnvVars::VIRTUAL_ENV,
|
||||
EnvVars::CONDA_PREFIX,
|
||||
EnvVars::CONDA_DEFAULT_ENV,
|
||||
EnvVars::CONDA_ROOT,
|
||||
EnvVars::PYTHONPATH,
|
||||
] {
|
||||
system.remove_env_var(name);
|
||||
}
|
||||
system.clear_env_vars();
|
||||
system.set_env_vars(uv_test_env_vars());
|
||||
system.set_env_var(EnvVars::TY_UV, "scripts");
|
||||
system.set_env_var(EnvVars::UV, "uv");
|
||||
system.set_env_var(EnvVars::UV, uv.as_str());
|
||||
|
||||
let metadata = ProjectMetadata::discover(root, &system).unwrap();
|
||||
ProjectDatabase::fallible(metadata, system).unwrap()
|
||||
@@ -29,6 +24,7 @@ fn setup_iteration(root: &SystemPath) -> ProjectDatabase {
|
||||
fn simple_script(bencher: Bencher) {
|
||||
let directory = tempfile::tempdir().unwrap();
|
||||
let root = SystemPath::from_std_path(directory.path()).unwrap();
|
||||
let uv = OsSystem::default().which("uv").unwrap();
|
||||
std::fs::write(
|
||||
root.join("script.py"),
|
||||
r#"# /// script
|
||||
@@ -50,7 +46,7 @@ def greet(user: User) -> str:
|
||||
.unwrap();
|
||||
|
||||
// Synchronize once before measuring so this benchmark covers the warm uv cache case.
|
||||
let output = Command::new("uv")
|
||||
let output = Command::new(uv.as_std_path())
|
||||
.args(["workspace", "metadata", "--sync", "--script"])
|
||||
.arg(root.join("script.py").as_std_path())
|
||||
.current_dir(root.as_std_path())
|
||||
@@ -63,7 +59,7 @@ def greet(user: User) -> str:
|
||||
);
|
||||
|
||||
bencher
|
||||
.with_inputs(|| setup_iteration(root))
|
||||
.with_inputs(|| setup_iteration(root, &uv))
|
||||
.bench_local_refs(|db| {
|
||||
// Include environment initialization in the measurement, as in the CLI.
|
||||
let environments = db.uv_environments().clone();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::process::Output;
|
||||
|
||||
use super::{Result, SystemPath, SystemPathBuf};
|
||||
@@ -8,6 +9,7 @@ pub struct Command {
|
||||
executable: String,
|
||||
arguments: Vec<String>,
|
||||
current_directory: Option<SystemPathBuf>,
|
||||
environment: CommandEnv,
|
||||
}
|
||||
|
||||
impl Command {
|
||||
@@ -17,6 +19,7 @@ impl Command {
|
||||
executable: executable.into(),
|
||||
arguments: Vec::new(),
|
||||
current_directory: None,
|
||||
environment: CommandEnv::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +45,48 @@ impl Command {
|
||||
self
|
||||
}
|
||||
|
||||
/// Clears the inherited environment and any variables previously set on this command.
|
||||
pub fn env_clear(&mut self) -> &mut Self {
|
||||
self.environment.clear();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets an environment variable for the command.
|
||||
pub fn env(&mut self, name: impl Into<String>, value: impl Into<String>) -> &mut Self {
|
||||
self.environment.set(name, value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Removes an environment variable from the command.
|
||||
pub fn env_remove(&mut self, name: impl Into<String>) -> &mut Self {
|
||||
self.environment.remove(name);
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns the environment variables explicitly set or removed for the command.
|
||||
///
|
||||
/// Removed variables have a value of `None`. Variables inherited from the parent process
|
||||
/// are not included.
|
||||
#[cfg_attr(
|
||||
not(feature = "os"),
|
||||
expect(dead_code, reason = "available to non-OS command executors")
|
||||
)]
|
||||
pub(crate) fn get_envs(&self) -> impl Iterator<Item = (&str, Option<&str>)> {
|
||||
self.environment
|
||||
.vars
|
||||
.iter()
|
||||
.map(|(name, value)| (name.as_str(), value.as_deref()))
|
||||
}
|
||||
|
||||
/// Returns whether the command clears environment variables inherited from its parent process.
|
||||
#[cfg_attr(
|
||||
not(feature = "os"),
|
||||
expect(dead_code, reason = "available to non-OS command executors")
|
||||
)]
|
||||
pub(crate) fn get_env_clear(&self) -> bool {
|
||||
self.environment.get_clear()
|
||||
}
|
||||
|
||||
/// Returns the executable to invoke.
|
||||
pub fn get_executable(&self) -> &str {
|
||||
&self.executable
|
||||
@@ -56,6 +101,59 @@ impl Command {
|
||||
pub fn get_current_dir(&self) -> Option<&SystemPath> {
|
||||
self.current_directory.as_deref()
|
||||
}
|
||||
|
||||
pub(super) fn env_merge(&mut self, environment: &CommandEnv) {
|
||||
self.environment.merge(environment);
|
||||
}
|
||||
}
|
||||
|
||||
/// Environment changes relative to an inherited environment.
|
||||
#[derive(Debug, Default)]
|
||||
pub(super) struct CommandEnv {
|
||||
clear: bool,
|
||||
vars: BTreeMap<String, Option<String>>,
|
||||
}
|
||||
|
||||
impl CommandEnv {
|
||||
pub(super) fn get_clear(&self) -> bool {
|
||||
self.clear
|
||||
}
|
||||
|
||||
pub(super) fn get(&self, name: &str) -> Option<&Option<String>> {
|
||||
self.vars.get(name)
|
||||
}
|
||||
|
||||
pub(super) fn clear(&mut self) {
|
||||
self.clear = true;
|
||||
self.vars.clear();
|
||||
}
|
||||
|
||||
pub(super) fn set(&mut self, name: impl Into<String>, value: impl Into<String>) {
|
||||
self.vars.insert(name.into(), Some(value.into()));
|
||||
}
|
||||
|
||||
pub(super) fn remove(&mut self, name: impl Into<String>) {
|
||||
let name = name.into();
|
||||
if self.clear {
|
||||
self.vars.remove(&name);
|
||||
} else {
|
||||
self.vars.insert(name, None);
|
||||
}
|
||||
}
|
||||
|
||||
/// Uses `environment` as a base without replacing explicit changes.
|
||||
fn merge(&mut self, environment: &Self) {
|
||||
if self.clear {
|
||||
return;
|
||||
}
|
||||
|
||||
self.clear = environment.clear;
|
||||
for (name, value) in &environment.vars {
|
||||
self.vars
|
||||
.entry(name.clone())
|
||||
.or_insert_with(|| value.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes [`Command`]s.
|
||||
|
||||
@@ -234,10 +234,22 @@ impl CommandExecutor for OsSystem {
|
||||
.get_current_dir()
|
||||
.unwrap_or_else(|| self.current_directory());
|
||||
|
||||
std::process::Command::new(command.get_executable())
|
||||
let mut process = std::process::Command::new(command.get_executable());
|
||||
process
|
||||
.args(command.get_args())
|
||||
.current_dir(directory.as_std_path())
|
||||
.output()
|
||||
.current_dir(directory.as_std_path());
|
||||
if command.get_env_clear() {
|
||||
process.env_clear();
|
||||
}
|
||||
|
||||
for (name, value) in command.get_envs() {
|
||||
if let Some(value) = value {
|
||||
process.env(name, value);
|
||||
} else {
|
||||
process.env_remove(name);
|
||||
}
|
||||
}
|
||||
process.output()
|
||||
}
|
||||
|
||||
fn dyn_clone(&self) -> Box<dyn CommandExecutor> {
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
use ruff_notebook::{Notebook, NotebookError};
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::panic::RefUnwindSafe;
|
||||
use std::process::Output;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::Db;
|
||||
use crate::files::File;
|
||||
use crate::system::{
|
||||
CommandExecutor, DirectoryEntry, MemoryFileSystem, Metadata, Result, System, SystemPath,
|
||||
SystemPathBuf, SystemVirtualPath, WhichError, WhichResult,
|
||||
Command, CommandExecutor, DirectoryEntry, MemoryFileSystem, Metadata, Result, System,
|
||||
SystemPath, SystemPathBuf, SystemVirtualPath, WhichError, WhichResult,
|
||||
};
|
||||
|
||||
use super::WritableSystem;
|
||||
use super::command::CommandEnv;
|
||||
use super::walk_directory::WalkDirectoryBuilder;
|
||||
|
||||
/// System implementation intended for testing.
|
||||
@@ -23,16 +24,15 @@ use super::walk_directory::WalkDirectoryBuilder;
|
||||
#[derive(Debug)]
|
||||
pub struct TestSystem {
|
||||
inner: Arc<dyn WritableSystem + RefUnwindSafe + Send + Sync>,
|
||||
/// Environment variable overrides. If a key is present here, it takes precedence
|
||||
/// over the inner system's environment variables.
|
||||
env_overrides: Arc<Mutex<FxHashMap<String, Option<String>>>>,
|
||||
/// Environment changes shared by system lookups and cloned command executors.
|
||||
environment: Arc<Mutex<CommandEnv>>,
|
||||
}
|
||||
|
||||
impl Clone for TestSystem {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.clone(),
|
||||
env_overrides: self.env_overrides.clone(),
|
||||
environment: self.environment.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,21 +41,36 @@ impl TestSystem {
|
||||
pub fn new(inner: impl WritableSystem + RefUnwindSafe + Send + Sync + 'static) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(inner),
|
||||
env_overrides: Arc::new(Mutex::new(FxHashMap::default())),
|
||||
environment: Arc::new(Mutex::new(CommandEnv::default())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Clears the inherited environment and any previously set variables.
|
||||
pub fn clear_env_vars(&self) {
|
||||
self.environment.lock().unwrap().clear();
|
||||
}
|
||||
|
||||
/// Sets an environment variable override. This takes precedence over the inner system.
|
||||
pub fn set_env_var(&self, name: impl Into<String>, value: impl Into<String>) {
|
||||
self.env_overrides
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(name.into(), Some(value.into()));
|
||||
self.environment.lock().unwrap().set(name, value);
|
||||
}
|
||||
|
||||
/// Sets multiple environment variable overrides.
|
||||
pub fn set_env_vars<I, K, V>(&self, variables: I)
|
||||
where
|
||||
I: IntoIterator<Item = (K, V)>,
|
||||
K: Into<String>,
|
||||
V: Into<String>,
|
||||
{
|
||||
let mut environment = self.environment.lock().unwrap();
|
||||
for (name, value) in variables {
|
||||
environment.set(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes an environment variable override, making it appear as not set.
|
||||
pub fn remove_env_var(&self, name: impl Into<String>) {
|
||||
self.env_overrides.lock().unwrap().insert(name.into(), None);
|
||||
self.environment.lock().unwrap().remove(name);
|
||||
}
|
||||
|
||||
/// Returns the [`InMemorySystem`].
|
||||
@@ -140,8 +155,20 @@ impl System for TestSystem {
|
||||
Err(WhichError::CannotFindBinaryPath)
|
||||
}
|
||||
|
||||
fn run_command(&self, mut command: Command) -> Result<Output> {
|
||||
let system = self.system();
|
||||
let Some(executor) = system.command_executor() else {
|
||||
return system.run_command(command);
|
||||
};
|
||||
|
||||
command.env_merge(&self.environment.lock().unwrap());
|
||||
|
||||
executor.execute(command)
|
||||
}
|
||||
|
||||
fn command_executor(&self) -> Option<&dyn CommandExecutor> {
|
||||
self.system().command_executor()
|
||||
self.system().command_executor()?;
|
||||
Some(self)
|
||||
}
|
||||
|
||||
fn read_directory<'a>(
|
||||
@@ -168,15 +195,16 @@ impl System for TestSystem {
|
||||
}
|
||||
|
||||
fn env_var(&self, name: &str) -> std::result::Result<String, std::env::VarError> {
|
||||
// Check overrides first
|
||||
if let Some(override_value) = self.env_overrides.lock().unwrap().get(name) {
|
||||
return match override_value {
|
||||
Some(value) => Ok(value.clone()),
|
||||
None => Err(std::env::VarError::NotPresent),
|
||||
};
|
||||
let (value, clear) = {
|
||||
let environment = self.environment.lock().unwrap();
|
||||
(environment.get(name).cloned(), environment.get_clear())
|
||||
};
|
||||
match value {
|
||||
Some(Some(value)) => Ok(value),
|
||||
Some(None) => Err(std::env::VarError::NotPresent),
|
||||
None if clear => Err(std::env::VarError::NotPresent),
|
||||
None => self.system().env_var(name),
|
||||
}
|
||||
// Fall back to inner system
|
||||
self.system().env_var(name)
|
||||
}
|
||||
|
||||
fn dyn_clone(&self) -> Box<dyn System> {
|
||||
@@ -184,6 +212,16 @@ impl System for TestSystem {
|
||||
}
|
||||
}
|
||||
|
||||
impl CommandExecutor for TestSystem {
|
||||
fn execute(&self, command: Command) -> Result<Output> {
|
||||
self.run_command(command)
|
||||
}
|
||||
|
||||
fn dyn_clone(&self) -> Box<dyn CommandExecutor> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TestSystem {
|
||||
fn default() -> Self {
|
||||
Self::new(InMemorySystem::default())
|
||||
|
||||
@@ -1001,20 +1001,6 @@ impl CliTest {
|
||||
command
|
||||
}
|
||||
|
||||
#[cfg(feature = "test-uv")]
|
||||
pub(crate) fn command_inheriting_environment(&self) -> Command {
|
||||
let mut command = Command::new(&self.ty_binary_path);
|
||||
command.current_dir(&self.project_dir).arg("check");
|
||||
|
||||
// Point user config discovery at a test-local directory to avoid picking up host config.
|
||||
command.env(
|
||||
user_config_directory_env_var(),
|
||||
self.user_config_directory(),
|
||||
);
|
||||
|
||||
command
|
||||
}
|
||||
|
||||
fn user_config_directory(&self) -> PathBuf {
|
||||
self.project_dir
|
||||
.parent()
|
||||
|
||||
@@ -1437,14 +1437,16 @@ mod uv_metadata {
|
||||
use std::{fs, process::Command};
|
||||
|
||||
use insta_cmd::assert_cmd_snapshot;
|
||||
use ty_project::uv_test_env_vars;
|
||||
use ty_static::EnvVars;
|
||||
|
||||
use crate::CliTest;
|
||||
use crate::uv_workspace::{uv_sync_command, write_dependency_wheel};
|
||||
|
||||
fn command_with_script_uv(case: &CliTest) -> Command {
|
||||
let mut command = case.command_inheriting_environment();
|
||||
let mut command = case.command();
|
||||
command
|
||||
.envs(uv_test_env_vars())
|
||||
.env(EnvVars::TY_UV, "1")
|
||||
.env(EnvVars::UV, "uv")
|
||||
.env("UV_CACHE_DIR", case.root().join("cache"));
|
||||
@@ -1453,6 +1455,8 @@ mod uv_metadata {
|
||||
|
||||
fn assert_uv_supports_script_metadata() -> anyhow::Result<()> {
|
||||
let output = Command::new("uv")
|
||||
.env_clear()
|
||||
.envs(uv_test_env_vars())
|
||||
.args(["workspace", "metadata", "--help"])
|
||||
.output()?;
|
||||
|
||||
@@ -1809,6 +1813,8 @@ mod uv_metadata {
|
||||
// come from the separate environment that uv creates for the script.
|
||||
let environment = case.root().join(".venv");
|
||||
let output = Command::new("uv")
|
||||
.env_clear()
|
||||
.envs(uv_test_env_vars())
|
||||
.args(["venv", "--no-project", "--python", "3.12"])
|
||||
.arg(&environment)
|
||||
.env("UV_CACHE_DIR", case.root().join("cache"))
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
use std::{fmt::Write as _, fs::File, io::Write, path::Path, process::Command};
|
||||
|
||||
use insta_cmd::assert_cmd_snapshot;
|
||||
#[cfg(feature = "test-uv")]
|
||||
use ty_project::uv_test_env_vars;
|
||||
use ty_static::EnvVars;
|
||||
#[cfg(feature = "test-uv")]
|
||||
use zip::{CompressionMethod, ZipWriter, write::SimpleFileOptions};
|
||||
@@ -55,6 +57,8 @@ requires-python = ">=3.8"
|
||||
fn uv_command(case: &CliTest) -> Command {
|
||||
let mut command = Command::new("uv");
|
||||
command
|
||||
.env_clear()
|
||||
.envs(uv_test_env_vars())
|
||||
.current_dir(case.root())
|
||||
.env("UV_CACHE_DIR", case.root().join("cache"))
|
||||
.env("UV_OFFLINE", "1")
|
||||
@@ -79,17 +83,13 @@ pub(super) fn uv_sync_command(
|
||||
|
||||
let mut command = case.command();
|
||||
command
|
||||
.envs(uv_test_env_vars())
|
||||
.env("TY_UV", "1")
|
||||
.env("UV", "uv")
|
||||
.env("UV_CACHE_DIR", case.root().join("cache"))
|
||||
.env("UV_OFFLINE", "1")
|
||||
.env("UV_PYTHON_DOWNLOADS", "never")
|
||||
.env("TY_OUTPUT_FORMAT", "concise")
|
||||
.env("PATH", std::env::var_os("PATH").unwrap_or_default());
|
||||
#[cfg(windows)]
|
||||
if let Some(path_ext) = std::env::var_os("PATHEXT") {
|
||||
command.env("PATHEXT", path_ext);
|
||||
}
|
||||
.env("TY_OUTPUT_FORMAT", "concise");
|
||||
if let Some(virtual_env) = virtual_env {
|
||||
command.env("VIRTUAL_ENV", virtual_env);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ use ty_project::metadata::value::{RelativeGlobPattern, RelativePathBuf};
|
||||
use ty_project::watch::{ChangeEvent, ProjectWatcher, directory_watcher};
|
||||
use ty_project::{ChangeResult, Db, ProjectDatabase, ProjectMetadata};
|
||||
use ty_python_core::platform::PythonPlatform;
|
||||
use ty_static::EnvVars;
|
||||
|
||||
struct TestCase {
|
||||
db: ProjectDatabase,
|
||||
@@ -431,7 +430,7 @@ where
|
||||
let os_system = OsSystem::new(&project_path);
|
||||
let user_config_directory_override = os_system.with_user_config_directory(None);
|
||||
let system = TestSystem::new(os_system.clone());
|
||||
isolate_environment(&system);
|
||||
system.clear_env_vars();
|
||||
configure_system(&system);
|
||||
|
||||
let mut setup_context = SetupContext {
|
||||
@@ -541,18 +540,6 @@ where
|
||||
Ok(test_case)
|
||||
}
|
||||
|
||||
fn isolate_environment(system: &TestSystem) {
|
||||
for name in [
|
||||
EnvVars::VIRTUAL_ENV,
|
||||
EnvVars::CONDA_PREFIX,
|
||||
EnvVars::CONDA_DEFAULT_ENV,
|
||||
EnvVars::CONDA_ROOT,
|
||||
EnvVars::PYTHONPATH,
|
||||
] {
|
||||
system.remove_env_var(name);
|
||||
}
|
||||
}
|
||||
|
||||
/// Dedents and updates a file's content, ensuring that its last modified time changes.
|
||||
fn update_file(path: impl AsRef<SystemPath>, content: &str) -> anyhow::Result<()> {
|
||||
let path = path.as_ref().as_std_path();
|
||||
@@ -2634,7 +2621,7 @@ mod uv_metadata {
|
||||
use ruff_db::diagnostic::DiagnosticId;
|
||||
use ruff_db::files::File;
|
||||
use ruff_db::system::{OsSystem, System as _};
|
||||
use ty_project::{Db, ScriptEnvironmentAvailability, UseUv, UvSyncChanges};
|
||||
use ty_project::{Db, ScriptEnvironmentAvailability, UseUv, UvSyncChanges, uv_test_env_vars};
|
||||
use ty_static::EnvVars;
|
||||
|
||||
use super::{SetupContext, TestCase, event_for_file, setup_with_system, update_file};
|
||||
@@ -2893,6 +2880,8 @@ mod uv_metadata {
|
||||
}
|
||||
if use_uv == UseUv::On {
|
||||
let output = Command::new(uv.as_std_path())
|
||||
.env_clear()
|
||||
.envs(uv_test_env_vars())
|
||||
.current_dir(context.project_path())
|
||||
.args(["sync", "--offline"])
|
||||
.output()?;
|
||||
@@ -2905,6 +2894,7 @@ mod uv_metadata {
|
||||
Ok(())
|
||||
},
|
||||
|system| {
|
||||
system.set_env_vars(uv_test_env_vars());
|
||||
system.set_env_var(
|
||||
EnvVars::TY_UV,
|
||||
match use_uv {
|
||||
|
||||
@@ -35,7 +35,9 @@ pub use ty_python_semantic::Db as SemanticDb;
|
||||
use ty_python_semantic::dependency::{DependencyMetadata, DependencyProjectKind};
|
||||
use ty_python_semantic::lint::RuleSelection;
|
||||
use uv::DependencyMetadataError;
|
||||
pub use uv::{ScriptEnvironmentAvailability, UseUv, UvEnvironments, UvSyncChanges};
|
||||
pub use uv::{
|
||||
ScriptEnvironmentAvailability, UseUv, UvEnvironments, UvSyncChanges, uv_test_env_vars,
|
||||
};
|
||||
|
||||
mod db;
|
||||
mod files;
|
||||
|
||||
@@ -69,6 +69,32 @@ impl Combine for UseUv {
|
||||
}
|
||||
}
|
||||
|
||||
/// Host variables needed by uv integration tests to find executables, Python installations, and caches.
|
||||
///
|
||||
/// Tests use this allowlist after clearing the inherited environment so host settings such as
|
||||
/// `UV_LOCKED` and `PYTHONPATH` cannot affect subprocesses.
|
||||
#[allow(
|
||||
clippy::disallowed_methods,
|
||||
reason = "Test only code, intentionally inherit variables from the host's environment."
|
||||
)]
|
||||
pub fn uv_test_env_vars() -> impl Iterator<Item = (&'static str, String)> {
|
||||
[
|
||||
"PATH",
|
||||
"PATHEXT",
|
||||
"SYSTEMROOT",
|
||||
"HOME",
|
||||
"USERPROFILE",
|
||||
"XDG_DATA_HOME",
|
||||
"TMPDIR",
|
||||
"TEMP",
|
||||
"TMP",
|
||||
"UV_CACHE_DIR",
|
||||
"UV_PYTHON_INSTALL_DIR",
|
||||
]
|
||||
.into_iter()
|
||||
.filter_map(|name| std::env::var(name).ok().map(|value| (name, value)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ruff_db::system::TestSystem;
|
||||
|
||||
@@ -960,7 +960,7 @@ mod tests {
|
||||
|
||||
use super::super::{ScriptEnvironmentAvailability, UvSyncChanges, script_environment};
|
||||
use crate::db::testing::TestDb;
|
||||
use crate::{Db as _, ProjectMetadata, UseUv};
|
||||
use crate::{Db as _, ProjectMetadata, UseUv, uv_test_env_vars};
|
||||
|
||||
#[test]
|
||||
fn newer_project_refresh_discards_old_metadata() -> anyhow::Result<()> {
|
||||
@@ -1248,6 +1248,8 @@ mod tests {
|
||||
|
||||
fn sync_workspace(&self) -> anyhow::Result<()> {
|
||||
let output = Command::new(self.db.test_system().env_var(EnvVars::UV)?)
|
||||
.env_clear()
|
||||
.envs(uv_test_env_vars())
|
||||
.current_dir(self.db.project().root(&self.db))
|
||||
.args(["sync", "--offline"])
|
||||
.output()?;
|
||||
@@ -1277,6 +1279,8 @@ mod tests {
|
||||
root.join("bin/python")
|
||||
};
|
||||
let output = Command::new(python.as_std_path())
|
||||
.env_clear()
|
||||
.envs(uv_test_env_vars())
|
||||
.args(["-c", &format!("import {module}")])
|
||||
.output()?;
|
||||
|
||||
@@ -1298,18 +1302,11 @@ mod tests {
|
||||
let metadata = ProjectMetadata::new("test", root.clone()).with_use_uv(use_uv);
|
||||
let mut db = TestDb::new(metadata);
|
||||
db.use_system(OsSystem::new(&root));
|
||||
db.test_system().clear_env_vars();
|
||||
db.test_system().set_env_vars(uv_test_env_vars());
|
||||
|
||||
let uv = OsSystem::default().which("uv")?;
|
||||
db.test_system().set_env_var(EnvVars::UV, uv.as_str());
|
||||
for name in [
|
||||
EnvVars::VIRTUAL_ENV,
|
||||
EnvVars::CONDA_PREFIX,
|
||||
EnvVars::CONDA_DEFAULT_ENV,
|
||||
EnvVars::CONDA_ROOT,
|
||||
EnvVars::PYTHONPATH,
|
||||
] {
|
||||
db.test_system().remove_env_var(name);
|
||||
}
|
||||
|
||||
let path = root.join(file_name);
|
||||
db.write_dedented(path.as_str(), source)?;
|
||||
|
||||
@@ -88,6 +88,8 @@ use rustc_hash::FxHashMap;
|
||||
use serde_json::{Value, json};
|
||||
use tempfile::TempDir;
|
||||
use ty_project::UseUv;
|
||||
#[cfg(feature = "test-uv")]
|
||||
use ty_project::uv_test_env_vars;
|
||||
use ty_server::{ClientOptions, LogLevel, Server, init_logging};
|
||||
|
||||
/// Number of times to retry receiving a message before giving up
|
||||
@@ -232,6 +234,7 @@ impl TestServer {
|
||||
|
||||
// Create test system and set environment variable overrides
|
||||
let test_system = Arc::new(TestSystem::new(os_system));
|
||||
test_system.clear_env_vars();
|
||||
for (name, value) in env_vars {
|
||||
match value {
|
||||
Some(value) => {
|
||||
@@ -1279,11 +1282,7 @@ impl TestServerBuilder {
|
||||
test_context: TestContext::new()?,
|
||||
initialization_options: None,
|
||||
client_capabilities,
|
||||
env_vars: vec![
|
||||
("HOME".into(), None),
|
||||
("PATH".into(), None),
|
||||
("VIRTUAL_ENV".into(), None),
|
||||
],
|
||||
env_vars: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1300,8 +1299,10 @@ impl TestServerBuilder {
|
||||
|
||||
/// Enable uv integration using the uv executable on the test process's PATH.
|
||||
#[cfg(feature = "test-uv")]
|
||||
pub(crate) fn with_real_uv(self, use_uv: UseUv) -> Result<Self> {
|
||||
pub(crate) fn with_real_uv(mut self, use_uv: UseUv) -> Result<Self> {
|
||||
let uv = OsSystem::default().which("uv")?;
|
||||
self.env_vars
|
||||
.extend(uv_test_env_vars().map(|(name, value)| (name.to_owned(), Some(value))));
|
||||
Ok(self.with_use_uv(use_uv).with_env_var("UV", uv.as_str()))
|
||||
}
|
||||
|
||||
|
||||
@@ -968,6 +968,8 @@ mod uv_metadata {
|
||||
use ruff_db::system::SystemPath;
|
||||
use serde_json::json;
|
||||
use ty_project::UseUv;
|
||||
#[cfg(feature = "test-uv")]
|
||||
use ty_project::uv_test_env_vars;
|
||||
|
||||
use crate::TestServerBuilder;
|
||||
|
||||
@@ -1008,6 +1010,8 @@ package = "invalid"
|
||||
|
||||
server.write_file("src/pyproject.toml", manifest)?;
|
||||
let output = Command::new("uv")
|
||||
.env_clear()
|
||||
.envs(uv_test_env_vars())
|
||||
.current_dir(server.file_path("src"))
|
||||
.args(["sync", "--offline"])
|
||||
.output()?;
|
||||
|
||||
@@ -878,6 +878,30 @@ kind = "inherent_method"
|
||||
level = "expect"
|
||||
reason = "command descriptions expose a complete argument builder and inspection API"
|
||||
|
||||
[[override]]
|
||||
lint = "hawk::dead_public"
|
||||
crate = "ruff_db"
|
||||
item = "system::command::Command::env_clear"
|
||||
kind = "inherent_method"
|
||||
level = "expect"
|
||||
reason = "command environment methods mirror the standard library Command API"
|
||||
|
||||
[[override]]
|
||||
lint = "hawk::dead_public"
|
||||
crate = "ruff_db"
|
||||
item = "system::command::Command::env"
|
||||
kind = "inherent_method"
|
||||
level = "expect"
|
||||
reason = "command environment methods mirror the standard library Command API"
|
||||
|
||||
[[override]]
|
||||
lint = "hawk::dead_public"
|
||||
crate = "ruff_db"
|
||||
item = "system::command::Command::env_remove"
|
||||
kind = "inherent_method"
|
||||
level = "expect"
|
||||
reason = "command environment methods mirror the standard library Command API"
|
||||
|
||||
[[override]]
|
||||
lint = "hawk::dead_public"
|
||||
crate = "ty_python_core"
|
||||
|
||||
Reference in new issue
Block a user