[ty] Add test coverage for PythonEnvironment::System variants (#17996)

Adds test coverage for https://github.com/astral-sh/ruff/pull/17991,
which includes some minor refactoring of the virtual environment test
infrastructure.

I tried to minimize stylistic changes, but there are still a few because
I was a little confused by the setup. I could see this evolving more in
the future, as I don't think the existing model can capture all the test
coverage I'm looking for.
This commit is contained in:
Zanie Blue 2025-05-10 15:28:15 -05:00 committed by GitHub
parent 316e406ca4
commit 2923c55698
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -617,23 +617,26 @@ mod tests {
use super::*; use super::*;
struct VirtualEnvironmentTester { struct VirtualEnvironmentTestCase {
system: TestSystem,
minor_version: u8,
free_threaded: bool,
system_site_packages: bool, system_site_packages: bool,
pyvenv_cfg_version_field: Option<&'static str>, pyvenv_cfg_version_field: Option<&'static str>,
} }
impl VirtualEnvironmentTester { struct PythonEnvironmentTestCase {
/// Builds a mock virtual environment, and returns the path to the venv system: TestSystem,
fn build_mock_venv(&self) -> SystemPathBuf { minor_version: u8,
let VirtualEnvironmentTester { free_threaded: bool,
virtual_env: Option<VirtualEnvironmentTestCase>,
}
impl PythonEnvironmentTestCase {
/// Builds a mock environment, and returns the path to the environment root.
fn build(&self) -> SystemPathBuf {
let PythonEnvironmentTestCase {
system, system,
minor_version, minor_version,
system_site_packages,
free_threaded, free_threaded,
pyvenv_cfg_version_field, virtual_env,
} = self; } = self;
let memory_fs = system.memory_file_system(); let memory_fs = system.memory_file_system();
let unix_site_packages = if *free_threaded { let unix_site_packages = if *free_threaded {
@ -663,6 +666,14 @@ mod tests {
.create_directory_all(&system_site_packages_path) .create_directory_all(&system_site_packages_path)
.unwrap(); .unwrap();
let Some(VirtualEnvironmentTestCase {
pyvenv_cfg_version_field,
system_site_packages,
}) = virtual_env
else {
return system_install_sys_prefix;
};
let venv_sys_prefix = SystemPathBuf::from("/.venv"); let venv_sys_prefix = SystemPathBuf::from("/.venv");
let (venv_exe, site_packages_path) = if cfg!(target_os = "windows") { let (venv_exe, site_packages_path) = if cfg!(target_os = "windows") {
( (
@ -695,29 +706,56 @@ mod tests {
venv_sys_prefix venv_sys_prefix
} }
fn test(self) { fn run(self) {
let venv_path = self.build_mock_venv(); let env_path = self.build();
let env = PythonEnvironment::new( let env = PythonEnvironment::new(
venv_path.clone(), env_path.clone(),
SysPrefixPathOrigin::VirtualEnvVar, SysPrefixPathOrigin::VirtualEnvVar,
&self.system, &self.system,
) )
.unwrap(); .unwrap();
let PythonEnvironment::Virtual(venv) = &env else { let expect_virtual_env = self.virtual_env.is_some();
panic!("Expected a virtual environment; got {env:?}"); match env {
}; PythonEnvironment::Virtual(venv) if expect_virtual_env => {
self.assert_virtual_environment(&venv, &env_path);
}
PythonEnvironment::Virtual(venv) => {
panic!(
"Expected a system environment, but got a virtual environment: {venv:?}"
);
}
PythonEnvironment::System(env) if !expect_virtual_env => {
self.assert_system_environment(&env, &env_path);
}
PythonEnvironment::System(env) => {
panic!("Expected a virtual environment, but got a system environment: {env:?}");
}
}
}
fn assert_virtual_environment(
&self,
venv: &VirtualEnvironment,
expected_env_path: &SystemPathBuf,
) {
let self_venv = self.virtual_env.as_ref().expect(
"`assert_virtual_environment` should only be used when `virtual_env` is populated",
);
assert_eq!( assert_eq!(
venv.root_path, venv.root_path,
SysPrefixPath { SysPrefixPath {
inner: self.system.canonicalize_path(&venv_path).unwrap(), inner: self.system.canonicalize_path(expected_env_path).unwrap(),
origin: SysPrefixPathOrigin::VirtualEnvVar, origin: SysPrefixPathOrigin::VirtualEnvVar,
} }
); );
assert_eq!(venv.include_system_site_packages, self.system_site_packages); assert_eq!(
venv.include_system_site_packages,
self_venv.system_site_packages
);
if self.pyvenv_cfg_version_field.is_some() { if self_venv.pyvenv_cfg_version_field.is_some() {
assert_eq!( assert_eq!(
venv.version, venv.version,
Some(PythonVersion { Some(PythonVersion {
@ -736,7 +774,7 @@ mod tests {
}; };
assert_eq!(venv.base_executable_home_path, expected_home); assert_eq!(venv.base_executable_home_path, expected_home);
let site_packages_directories = env.site_packages_directories(&self.system).unwrap(); let site_packages_directories = venv.site_packages_directories(&self.system).unwrap();
let expected_venv_site_packages = if cfg!(target_os = "windows") { let expected_venv_site_packages = if cfg!(target_os = "windows") {
SystemPathBuf::from(r"\.venv\Lib\site-packages") SystemPathBuf::from(r"\.venv\Lib\site-packages")
} else if self.free_threaded { } else if self.free_threaded {
@ -768,7 +806,7 @@ mod tests {
)) ))
}; };
if self.system_site_packages { if self_venv.system_site_packages {
assert_eq!( assert_eq!(
&site_packages_directories, &site_packages_directories,
&[expected_venv_site_packages, expected_system_site_packages] &[expected_venv_site_packages, expected_system_site_packages]
@ -777,120 +815,233 @@ mod tests {
assert_eq!(&site_packages_directories, &[expected_venv_site_packages]); assert_eq!(&site_packages_directories, &[expected_venv_site_packages]);
} }
} }
fn assert_system_environment(
&self,
env: &SystemEnvironment,
expected_env_path: &SystemPathBuf,
) {
assert!(
self.virtual_env.is_none(),
"`assert_system_environment` should only be used when `virtual_env` is not populated"
);
assert_eq!(
env.root_path,
SysPrefixPath {
inner: self.system.canonicalize_path(expected_env_path).unwrap(),
origin: SysPrefixPathOrigin::VirtualEnvVar,
}
);
let site_packages_directories = env.site_packages_directories(&self.system).unwrap();
let expected_site_packages = if cfg!(target_os = "windows") {
SystemPathBuf::from(&*format!(
r"\Python3.{}\Lib\site-packages",
self.minor_version
))
} else if self.free_threaded {
SystemPathBuf::from(&*format!(
"/Python3.{minor_version}/lib/python3.{minor_version}t/site-packages",
minor_version = self.minor_version
))
} else {
SystemPathBuf::from(&*format!(
"/Python3.{minor_version}/lib/python3.{minor_version}/site-packages",
minor_version = self.minor_version
))
};
assert_eq!(&site_packages_directories, &[expected_site_packages]);
}
}
#[test]
fn can_find_site_packages_directory_no_virtual_env() {
let test = PythonEnvironmentTestCase {
system: TestSystem::default(),
minor_version: 12,
free_threaded: false,
virtual_env: None,
};
test.run();
}
#[test]
fn can_find_site_packages_directory_no_virtual_env_freethreaded() {
let test = PythonEnvironmentTestCase {
system: TestSystem::default(),
minor_version: 13,
free_threaded: true,
virtual_env: None,
};
test.run();
} }
#[test] #[test]
fn can_find_site_packages_directory_no_version_field_in_pyvenv_cfg() { fn can_find_site_packages_directory_no_version_field_in_pyvenv_cfg() {
let tester = VirtualEnvironmentTester { let test = PythonEnvironmentTestCase {
system: TestSystem::default(), system: TestSystem::default(),
minor_version: 12, minor_version: 12,
free_threaded: false, free_threaded: false,
virtual_env: Some(VirtualEnvironmentTestCase {
system_site_packages: false, system_site_packages: false,
pyvenv_cfg_version_field: None, pyvenv_cfg_version_field: None,
}),
}; };
tester.test(); test.run();
} }
#[test] #[test]
fn can_find_site_packages_directory_venv_style_version_field_in_pyvenv_cfg() { fn can_find_site_packages_directory_venv_style_version_field_in_pyvenv_cfg() {
let tester = VirtualEnvironmentTester { let test = PythonEnvironmentTestCase {
system: TestSystem::default(), system: TestSystem::default(),
minor_version: 12, minor_version: 12,
free_threaded: false, free_threaded: false,
virtual_env: Some(VirtualEnvironmentTestCase {
system_site_packages: false, system_site_packages: false,
pyvenv_cfg_version_field: Some("version = 3.12"), pyvenv_cfg_version_field: Some("version = 3.12"),
}),
}; };
tester.test(); test.run();
} }
#[test] #[test]
fn can_find_site_packages_directory_uv_style_version_field_in_pyvenv_cfg() { fn can_find_site_packages_directory_uv_style_version_field_in_pyvenv_cfg() {
let tester = VirtualEnvironmentTester { let test = PythonEnvironmentTestCase {
system: TestSystem::default(), system: TestSystem::default(),
minor_version: 12, minor_version: 12,
free_threaded: false, free_threaded: false,
virtual_env: Some(VirtualEnvironmentTestCase {
system_site_packages: false, system_site_packages: false,
pyvenv_cfg_version_field: Some("version_info = 3.12"), pyvenv_cfg_version_field: Some("version_info = 3.12"),
}),
}; };
tester.test(); test.run();
} }
#[test] #[test]
fn can_find_site_packages_directory_virtualenv_style_version_field_in_pyvenv_cfg() { fn can_find_site_packages_directory_virtualenv_style_version_field_in_pyvenv_cfg() {
let tester = VirtualEnvironmentTester { let test = PythonEnvironmentTestCase {
system: TestSystem::default(), system: TestSystem::default(),
minor_version: 12, minor_version: 12,
free_threaded: false, free_threaded: false,
virtual_env: Some(VirtualEnvironmentTestCase {
system_site_packages: false, system_site_packages: false,
pyvenv_cfg_version_field: Some("version_info = 3.12.0rc2"), pyvenv_cfg_version_field: Some("version_info = 3.12.0rc2"),
}),
}; };
tester.test(); test.run();
} }
#[test] #[test]
fn can_find_site_packages_directory_freethreaded_build() { fn can_find_site_packages_directory_freethreaded_build() {
let tester = VirtualEnvironmentTester { let test = PythonEnvironmentTestCase {
system: TestSystem::default(), system: TestSystem::default(),
minor_version: 13, minor_version: 13,
free_threaded: true, free_threaded: true,
virtual_env: Some(VirtualEnvironmentTestCase {
system_site_packages: false, system_site_packages: false,
pyvenv_cfg_version_field: Some("version_info = 3.13"), pyvenv_cfg_version_field: Some("version_info = 3.13"),
}),
}; };
tester.test(); test.run();
} }
#[test] #[test]
fn finds_system_site_packages() { fn finds_system_site_packages() {
let tester = VirtualEnvironmentTester { let test = PythonEnvironmentTestCase {
system: TestSystem::default(), system: TestSystem::default(),
minor_version: 13, minor_version: 13,
free_threaded: true, free_threaded: true,
virtual_env: Some(VirtualEnvironmentTestCase {
system_site_packages: true, system_site_packages: true,
pyvenv_cfg_version_field: Some("version_info = 3.13"), pyvenv_cfg_version_field: Some("version_info = 3.13"),
}),
}; };
tester.test(); test.run();
} }
#[test] #[test]
fn reject_venv_that_does_not_exist() { fn reject_env_that_does_not_exist() {
let system = TestSystem::default(); let system = TestSystem::default();
assert!(matches!( assert!(matches!(
PythonEnvironment::new("/.venv", SysPrefixPathOrigin::VirtualEnvVar, &system), PythonEnvironment::new("/env", SysPrefixPathOrigin::PythonCliFlag, &system),
Err(SitePackagesDiscoveryError::EnvDirCanonicalizationError(..)) Err(SitePackagesDiscoveryError::EnvDirCanonicalizationError(..))
)); ));
} }
#[test] #[test]
fn reject_venv_that_is_not_a_directory() { fn reject_env_that_is_not_a_directory() {
let system = TestSystem::default(); let system = TestSystem::default();
system system
.memory_file_system() .memory_file_system()
.write_file_all("/.venv", "") .write_file_all("/env", "")
.unwrap(); .unwrap();
assert!(matches!( assert!(matches!(
PythonEnvironment::new("/.venv", SysPrefixPathOrigin::VirtualEnvVar, &system), PythonEnvironment::new("/env", SysPrefixPathOrigin::PythonCliFlag, &system),
Err(SitePackagesDiscoveryError::EnvDirNotDirectory(..)) Err(SitePackagesDiscoveryError::EnvDirNotDirectory(..))
)); ));
} }
#[test] #[test]
fn env_with_no_pyvenv_cfg_file() { fn cannot_read_lib_directory() {
let system = TestSystem::default(); let system = TestSystem::default();
system system
.memory_file_system() .memory_file_system()
.create_directory_all("/.venv") .create_directory_all("/env")
.unwrap(); .unwrap();
// Environment creation succeeds, but site-packages retrieval fails reading the `lib`
// directory
let env = let env =
PythonEnvironment::new("/.venv", SysPrefixPathOrigin::PythonCliFlag, &system).unwrap(); PythonEnvironment::new("/env", SysPrefixPathOrigin::PythonCliFlag, &system).unwrap();
let PythonEnvironment::System(env) = env else { let site_packages = env.site_packages_directories(&system);
panic!("Expected a system environment; got {env:?}"); if cfg!(unix) {
};
assert!( assert!(
env.root_path matches!(
== SysPrefixPath { site_packages,
inner: system.canonicalize_path(SystemPath::new("/.venv")).unwrap(), Err(SitePackagesDiscoveryError::CouldNotReadLibDirectory(..)),
origin: SysPrefixPathOrigin::PythonCliFlag, ),
"Got {site_packages:?}",
);
} else {
// On Windows, we look for `Lib/site-packages` directly instead of listing the entries
// of `lib/...` — so we don't see the intermediate failure
assert!(
matches!(
site_packages,
Err(SitePackagesDiscoveryError::NoSitePackagesDirFound(..)),
),
"Got {site_packages:?}",
);
} }
}
#[test]
fn cannot_find_site_packages_directory() {
let system = TestSystem::default();
if cfg!(unix) {
system
.memory_file_system()
.create_directory_all("/env/lib")
.unwrap();
} else {
system
.memory_file_system()
.create_directory_all("/env/Lib")
.unwrap();
}
// Environment creation succeeds, but site-packages retrieval fails
let env =
PythonEnvironment::new("/env", SysPrefixPathOrigin::PythonCliFlag, &system).unwrap();
let site_packages = env.site_packages_directories(&system);
assert!(
matches!(
site_packages,
Err(SitePackagesDiscoveryError::NoSitePackagesDirFound(..)),
),
"Got {site_packages:?}",
); );
} }