[ty] Isolate uv integration tests from inherited environment settings (#28342)

Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
Alex WaygoodandMicha Reiser authored and GitHub committed 2026-09-08 13:09:55 +02:00
1 parent 321eebc2d6
commit 12132db288
16 files changed
+273 -105

No files matched your search

-9
View File
@@ -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
-3
View File
@@ -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() }}
+11 -15
View File
@@ -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();
+98
View File
@@ -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.
+15 -3
View File
@@ -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> {
+60 -22
View File
@@ -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())
-14
View File
@@ -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()
+7 -1
View File
@@ -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"))
+6 -6
View File
@@ -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);
}
+5 -15
View File
@@ -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 {
+3 -1
View File
@@ -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;
+26
View File
@@ -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;
+7 -10
View File
@@ -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)?;
+7 -6
View File
@@ -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()?;
+24
View File
@@ -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"