mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 05:15:12 +00:00
[ty] Improve tests for site-packages
discovery (#18374)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
## Summary - Convert tests demonstrating our resilience to malformed/absent `version` fields in `pyvenf.cfg` files to mdtests. Also make them more expansive. - Convert the regression test I added in https://github.com/astral-sh/ruff/pull/18157 to an mdtest - Add comments next to unit tests that cannot be converted to mdtests (but where it's not obvious why they can't) so I don't have to do this exercise again 😄 - In `site_packages.rs`, factor out the logic for figuring out where we expect the system-installation `site-packages` to be. Currently we have the same logic twice. ## Test Plan `cargo test -p ty_python_semantic`
This commit is contained in:
parent
363f061f09
commit
ad2f667ee4
2 changed files with 175 additions and 65 deletions
|
@ -1,5 +1,115 @@
|
||||||
# Tests for `site-packages` discovery
|
# Tests for `site-packages` discovery
|
||||||
|
|
||||||
|
## Malformed or absent `version` fields
|
||||||
|
|
||||||
|
The `version`/`version_info` key in a `pyvenv.cfg` file is provided by most virtual-environment
|
||||||
|
creation tools to indicate the Python version the virtual environment is for. They key is useful for
|
||||||
|
our purposes, so we try to parse it when possible. However, the key is not read by the CPython
|
||||||
|
standard library, and is provided under different keys depending on which virtual-environment
|
||||||
|
creation tool created the `pyvenv.cfg` file (the stdlib `venv` module calls the key `version`,
|
||||||
|
whereas uv and virtualenv both call it `version_info`). We therefore do not return an error when
|
||||||
|
discovering a virtual environment's `site-packages` directory if the virtula environment contains a
|
||||||
|
`pyvenv.cfg` file which doesn't have this key, or if the associated value of the key doesn't parse
|
||||||
|
according to our expectations. The file isn't really *invalid* in this situation.
|
||||||
|
|
||||||
|
### No `version` field
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python = "/.venv"
|
||||||
|
```
|
||||||
|
|
||||||
|
`/.venv/pyvenv.cfg`:
|
||||||
|
|
||||||
|
```cfg
|
||||||
|
home = /doo/doo/wop/cpython-3.13.2-macos-aarch64-none/bin
|
||||||
|
```
|
||||||
|
|
||||||
|
`/doo/doo/wop/cpython-3.13.2-macos-aarch64-none/bin/python`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
```
|
||||||
|
|
||||||
|
`/.venv/<path-to-site-packages>/foo.py`:
|
||||||
|
|
||||||
|
```py
|
||||||
|
X: int = 42
|
||||||
|
```
|
||||||
|
|
||||||
|
`/src/main.py`:
|
||||||
|
|
||||||
|
```py
|
||||||
|
from foo import X
|
||||||
|
|
||||||
|
reveal_type(X) # revealed: int
|
||||||
|
```
|
||||||
|
|
||||||
|
### Malformed stdlib-style version field
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python = "/.venv"
|
||||||
|
```
|
||||||
|
|
||||||
|
`/.venv/pyvenv.cfg`:
|
||||||
|
|
||||||
|
```cfg
|
||||||
|
home = /doo/doo/wop/cpython-3.13.2-macos-aarch64-none/bin
|
||||||
|
version = wut
|
||||||
|
```
|
||||||
|
|
||||||
|
`/doo/doo/wop/cpython-3.13.2-macos-aarch64-none/bin/python`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
```
|
||||||
|
|
||||||
|
`/.venv/<path-to-site-packages>/foo.py`:
|
||||||
|
|
||||||
|
```py
|
||||||
|
X: int = 42
|
||||||
|
```
|
||||||
|
|
||||||
|
`/src/main.py`:
|
||||||
|
|
||||||
|
```py
|
||||||
|
from foo import X
|
||||||
|
|
||||||
|
reveal_type(X) # revealed: int
|
||||||
|
```
|
||||||
|
|
||||||
|
### Malformed uv-style version field
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python = "/.venv"
|
||||||
|
```
|
||||||
|
|
||||||
|
`/.venv/pyvenv.cfg`:
|
||||||
|
|
||||||
|
```cfg
|
||||||
|
home = /doo/doo/wop/cpython-3.13.2-macos-aarch64-none/bin
|
||||||
|
version_info = no-really-wut
|
||||||
|
```
|
||||||
|
|
||||||
|
`/doo/doo/wop/cpython-3.13.2-macos-aarch64-none/bin/python`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
```
|
||||||
|
|
||||||
|
`/.venv/<path-to-site-packages>/foo.py`:
|
||||||
|
|
||||||
|
```py
|
||||||
|
X: int = 42
|
||||||
|
```
|
||||||
|
|
||||||
|
`/src/main.py`:
|
||||||
|
|
||||||
|
```py
|
||||||
|
from foo import X
|
||||||
|
|
||||||
|
reveal_type(X) # revealed: int
|
||||||
|
```
|
||||||
|
|
||||||
## Ephemeral uv environments
|
## Ephemeral uv environments
|
||||||
|
|
||||||
If you use the `--with` flag when invoking `uv run`, uv will create an "ephemeral" virtual
|
If you use the `--with` flag when invoking `uv run`, uv will create an "ephemeral" virtual
|
||||||
|
@ -57,3 +167,41 @@ from bar import Y
|
||||||
reveal_type(X) # revealed: int
|
reveal_type(X) # revealed: int
|
||||||
reveal_type(Y) # revealed: str
|
reveal_type(Y) # revealed: str
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## `pyvenv.cfg` files with unusual values
|
||||||
|
|
||||||
|
`pyvenv.cfg` files can have unusual values in them, which can contain arbitrary characters. This
|
||||||
|
includes `=` characters. The following is a regression test for
|
||||||
|
<https://github.com/astral-sh/ty/issues/430>.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python = "/.venv"
|
||||||
|
```
|
||||||
|
|
||||||
|
`/.venv/pyvenv.cfg`:
|
||||||
|
|
||||||
|
```cfg
|
||||||
|
home = /doo/doo/wop/cpython-3.13.2-macos-aarch64-none/bin
|
||||||
|
version_info = 3.13
|
||||||
|
command = /.pyenv/versions/3.13.3/bin/python3.13 -m venv --without-pip --prompt="python-default/3.13.3" /somewhere-else/python/virtualenvs/python-default/3.13.3
|
||||||
|
```
|
||||||
|
|
||||||
|
`/doo/doo/wop/cpython-3.13.2-macos-aarch64-none/bin/python`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
```
|
||||||
|
|
||||||
|
`/.venv/<path-to-site-packages>/foo.py`:
|
||||||
|
|
||||||
|
```py
|
||||||
|
X: int = 42
|
||||||
|
```
|
||||||
|
|
||||||
|
`/src/main.py`:
|
||||||
|
|
||||||
|
```py
|
||||||
|
from foo import X
|
||||||
|
|
||||||
|
reveal_type(X) # revealed: int
|
||||||
|
```
|
||||||
|
|
|
@ -1001,22 +1001,7 @@ mod tests {
|
||||||
))
|
))
|
||||||
};
|
};
|
||||||
|
|
||||||
let expected_system_site_packages = if cfg!(target_os = "windows") {
|
let expected_system_site_packages = self.expected_system_site_packages();
|
||||||
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
|
|
||||||
))
|
|
||||||
};
|
|
||||||
|
|
||||||
if self_venv.system_site_packages {
|
if self_venv.system_site_packages {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -1051,33 +1036,33 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
let site_packages_directories = env.site_packages_directories(&self.system).unwrap();
|
let site_packages_directories = env.site_packages_directories(&self.system).unwrap();
|
||||||
|
let expected_site_packages = self.expected_system_site_packages();
|
||||||
|
assert_eq!(
|
||||||
|
site_packages_directories,
|
||||||
|
std::slice::from_ref(&expected_site_packages)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let expected_site_packages = if cfg!(target_os = "windows") {
|
fn expected_system_site_packages(&self) -> SystemPathBuf {
|
||||||
SystemPathBuf::from(&*format!(
|
let minor_version = self.minor_version;
|
||||||
r"\Python3.{}\Lib\site-packages",
|
if cfg!(target_os = "windows") {
|
||||||
self.minor_version
|
SystemPathBuf::from(&*format!(r"\Python3.{minor_version}\Lib\site-packages"))
|
||||||
))
|
|
||||||
} else if self.free_threaded {
|
} else if self.free_threaded {
|
||||||
SystemPathBuf::from(&*format!(
|
SystemPathBuf::from(&*format!(
|
||||||
"/Python3.{minor_version}/lib/python3.{minor_version}t/site-packages",
|
"/Python3.{minor_version}/lib/python3.{minor_version}t/site-packages"
|
||||||
minor_version = self.minor_version
|
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
SystemPathBuf::from(&*format!(
|
SystemPathBuf::from(&*format!(
|
||||||
"/Python3.{minor_version}/lib/python3.{minor_version}/site-packages",
|
"/Python3.{minor_version}/lib/python3.{minor_version}/site-packages"
|
||||||
minor_version = self.minor_version
|
|
||||||
))
|
))
|
||||||
};
|
}
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
site_packages_directories,
|
|
||||||
[expected_site_packages].as_slice()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_find_site_packages_directory_no_virtual_env() {
|
fn can_find_site_packages_directory_no_virtual_env() {
|
||||||
|
// Shouldn't be converted to an mdtest because mdtest automatically creates a
|
||||||
|
// pyvenv.cfg file for you if it sees you creating a `site-packages` directory.
|
||||||
let test = PythonEnvironmentTestCase {
|
let test = PythonEnvironmentTestCase {
|
||||||
system: TestSystem::default(),
|
system: TestSystem::default(),
|
||||||
minor_version: 12,
|
minor_version: 12,
|
||||||
|
@ -1090,6 +1075,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_find_site_packages_directory_no_virtual_env_freethreaded() {
|
fn can_find_site_packages_directory_no_virtual_env_freethreaded() {
|
||||||
|
// Shouldn't be converted to an mdtest because mdtest automatically creates a
|
||||||
|
// pyvenv.cfg file for you if it sees you creating a `site-packages` directory.
|
||||||
let test = PythonEnvironmentTestCase {
|
let test = PythonEnvironmentTestCase {
|
||||||
system: TestSystem::default(),
|
system: TestSystem::default(),
|
||||||
minor_version: 13,
|
minor_version: 13,
|
||||||
|
@ -1132,23 +1119,10 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn can_find_site_packages_directory_no_version_field_in_pyvenv_cfg() {
|
|
||||||
let test = PythonEnvironmentTestCase {
|
|
||||||
system: TestSystem::default(),
|
|
||||||
minor_version: 12,
|
|
||||||
free_threaded: false,
|
|
||||||
origin: SysPrefixPathOrigin::VirtualEnvVar,
|
|
||||||
virtual_env: Some(VirtualEnvironmentTestCase {
|
|
||||||
pyvenv_cfg_version_field: None,
|
|
||||||
..VirtualEnvironmentTestCase::default()
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
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() {
|
||||||
|
// Shouldn't be converted to an mdtest because we want to assert
|
||||||
|
// that we parsed the `version` field correctly in `test.run()`.
|
||||||
let test = PythonEnvironmentTestCase {
|
let test = PythonEnvironmentTestCase {
|
||||||
system: TestSystem::default(),
|
system: TestSystem::default(),
|
||||||
minor_version: 12,
|
minor_version: 12,
|
||||||
|
@ -1164,6 +1138,8 @@ mod tests {
|
||||||
|
|
||||||
#[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() {
|
||||||
|
// Shouldn't be converted to an mdtest because we want to assert
|
||||||
|
// that we parsed the `version` field correctly in `test.run()`.
|
||||||
let test = PythonEnvironmentTestCase {
|
let test = PythonEnvironmentTestCase {
|
||||||
system: TestSystem::default(),
|
system: TestSystem::default(),
|
||||||
minor_version: 12,
|
minor_version: 12,
|
||||||
|
@ -1179,6 +1155,8 @@ mod tests {
|
||||||
|
|
||||||
#[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() {
|
||||||
|
// Shouldn't be converted to an mdtest because we want to assert
|
||||||
|
// that we parsed the `version` field correctly in `test.run()`.
|
||||||
let test = PythonEnvironmentTestCase {
|
let test = PythonEnvironmentTestCase {
|
||||||
system: TestSystem::default(),
|
system: TestSystem::default(),
|
||||||
minor_version: 12,
|
minor_version: 12,
|
||||||
|
@ -1209,6 +1187,9 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn finds_system_site_packages() {
|
fn finds_system_site_packages() {
|
||||||
|
// Can't be converted to an mdtest because the system installation's `sys.prefix`
|
||||||
|
// path is at a different location relative to the `pyvenv.cfg` file's `home` value
|
||||||
|
// on Windows.
|
||||||
let test = PythonEnvironmentTestCase {
|
let test = PythonEnvironmentTestCase {
|
||||||
system: TestSystem::default(),
|
system: TestSystem::default(),
|
||||||
minor_version: 13,
|
minor_version: 13,
|
||||||
|
@ -1366,25 +1347,6 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See <https://github.com/astral-sh/ty/issues/430>
|
|
||||||
#[test]
|
|
||||||
fn parsing_pyvenv_cfg_with_equals_in_value() {
|
|
||||||
let test = PythonEnvironmentTestCase {
|
|
||||||
system: TestSystem::default(),
|
|
||||||
minor_version: 13,
|
|
||||||
free_threaded: true,
|
|
||||||
origin: SysPrefixPathOrigin::VirtualEnvVar,
|
|
||||||
virtual_env: Some(VirtualEnvironmentTestCase {
|
|
||||||
pyvenv_cfg_version_field: Some("version_info = 3.13"),
|
|
||||||
command_field: Some(
|
|
||||||
r#"command = /.pyenv/versions/3.13.3/bin/python3.13 -m venv --without-pip --prompt="python-default/3.13.3" /somewhere-else/python/virtualenvs/python-default/3.13.3"#,
|
|
||||||
),
|
|
||||||
..VirtualEnvironmentTestCase::default()
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
test.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parsing_pyvenv_cfg_with_key_but_no_value_fails() {
|
fn parsing_pyvenv_cfg_with_key_but_no_value_fails() {
|
||||||
let system = TestSystem::default();
|
let system = TestSystem::default();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue