[red-knot] Fix python setting in mdtests, and rewrite a site-packages test as an mdtest (#17222)

## Summary

This PR does the following things:
- Fixes the `python` configuration setting for mdtest (added in
https://github.com/astral-sh/ruff/pull/17221) so that it expects a path
pointing to a venv's `sys.prefix` variable rather than the a path
pointing to the venv's `site-packages` subdirectory. This brings the
`python` setting in mdtest in sync with our CLI `--python` flag.
- Tweaks mdtest so that it automatically creates a valid `pyvenv.cfg`
file for you if you don't specify one. This makes it much more ergonomic
to write an mdtest with a custom `python` setting: red-knot will reject
a `python` setting that points to a directory that doesn't have a
`pyvenv.cfg` file in it
- Tweaks mdtest so that it doesn't check a custom `pyvenv.cfg` as Python
source code if you _do_ add a custom `pyvenv.cfg` file for your mock
virtual environment in an mdtest. (You get a lot of diagnostics about
Python syntax errors in the `pyvenv.cfg` file, otherwise!)
- Rewrites the test added in
https://github.com/astral-sh/ruff/pull/17178 as an mdtest, and deletes
the original test that was added in that PR

## Test Plan

I verified that the new mdtest fails if I revert the changes to
`resolver.rs` that were added in
https://github.com/astral-sh/ruff/pull/17178
This commit is contained in:
Alex Waygood
2025-04-06 18:24:32 +01:00
committed by GitHub
parent 73a9974d8a
commit ac5d220d75
6 changed files with 132 additions and 51 deletions

View File

@@ -236,3 +236,36 @@ X: int = 42
```py
from .parser import X # error: [unresolved-import]
```
## Relative imports in `site-packages`
Relative imports in `site-packages` are correctly resolved even when the `site-packages` search path
is a subdirectory of the first-party search path. Note that mdtest sets the first-party search path
to `/src/`, which is why the virtual environment in this test is a subdirectory of `/src/`, even
though this is not how a typical Python project would be structured:
```toml
[environment]
python = "/src/.venv"
python-version = "3.13"
```
`/src/bar.py`:
```py
from foo import A
reveal_type(A) # revealed: Literal[A]
```
`/src/.venv/<path-to-site-packages>/foo/__init__.py`:
```py
from .a import A as A
```
`/src/.venv/<path-to-site-packages>/foo/a.py`:
```py
class A: ...
```

View File

@@ -19,7 +19,7 @@ pub(crate) mod tests {
use std::sync::Arc;
use crate::program::{Program, SearchPathSettings};
use crate::{default_lint_registry, ProgramSettings, PythonPath, PythonPlatform};
use crate::{default_lint_registry, ProgramSettings, PythonPlatform};
use super::Db;
use crate::lint::{LintRegistry, RuleSelection};
@@ -139,8 +139,6 @@ pub(crate) mod tests {
python_version: PythonVersion,
/// Target Python platform
python_platform: PythonPlatform,
/// Paths to the directory to use for `site-packages`
site_packages: Vec<SystemPathBuf>,
/// Path and content pairs for files that should be present
files: Vec<(&'a str, &'a str)>,
}
@@ -150,7 +148,6 @@ pub(crate) mod tests {
Self {
python_version: PythonVersion::default(),
python_platform: PythonPlatform::default(),
site_packages: vec![],
files: vec![],
}
}
@@ -169,14 +166,6 @@ pub(crate) mod tests {
self
}
pub(crate) fn with_site_packages_search_path(
mut self,
path: &(impl AsRef<SystemPath> + ?Sized),
) -> Self {
self.site_packages.push(path.as_ref().to_path_buf());
self
}
pub(crate) fn build(self) -> anyhow::Result<TestDb> {
let mut db = TestDb::new();
@@ -186,15 +175,12 @@ pub(crate) mod tests {
db.write_files(self.files)
.context("Failed to write test files")?;
let mut search_paths = SearchPathSettings::new(vec![src_root]);
search_paths.python_path = PythonPath::KnownSitePackages(self.site_packages);
Program::from_settings(
&db,
ProgramSettings {
python_version: self.python_version,
python_platform: self.python_platform,
search_paths,
search_paths: SearchPathSettings::new(vec![src_root]),
},
)
.context("Failed to configure Program settings")?;

View File

@@ -10,6 +10,7 @@ pub use module_resolver::{resolve_module, system_module_search_paths, KnownModul
pub use program::{Program, ProgramSettings, PythonPath, SearchPathSettings};
pub use python_platform::PythonPlatform;
pub use semantic_model::{HasType, SemanticModel};
pub use site_packages::SysPrefixPathOrigin;
pub mod ast_node_ref;
mod db;

View File

@@ -7392,7 +7392,7 @@ impl StringPartsCollector {
#[cfg(test)]
mod tests {
use crate::db::tests::{setup_db, TestDb, TestDbBuilder};
use crate::db::tests::{setup_db, TestDb};
use crate::semantic_index::definition::Definition;
use crate::semantic_index::symbol::FileScopeId;
use crate::semantic_index::{global_scope, semantic_index, symbol_table, use_def_map};
@@ -7400,7 +7400,7 @@ mod tests {
use crate::types::check_types;
use ruff_db::diagnostic::Diagnostic;
use ruff_db::files::{system_path_to_file, File};
use ruff_db::system::{DbWithWritableSystem as _, SystemPath};
use ruff_db::system::DbWithWritableSystem as _;
use ruff_db::testing::{assert_function_query_was_not_run, assert_function_query_was_run};
use super::*;
@@ -7556,26 +7556,6 @@ mod tests {
Ok(())
}
#[test]
fn relative_import_resolution_in_site_packages_when_site_packages_is_subdirectory_of_first_party_search_path(
) {
let project_root = SystemPath::new("/src");
let foo_dot_py = project_root.join("foo.py");
let site_packages = project_root.join(".venv/lib/python3.13/site-packages");
let db = TestDbBuilder::new()
.with_site_packages_search_path(&site_packages)
.with_file(&foo_dot_py, "from bar import A")
.with_file(&site_packages.join("bar/__init__.py"), "from .a import *")
.with_file(&site_packages.join("bar/a.py"), "class A: ...")
.build()
.unwrap();
assert_file_diagnostics(&db, foo_dot_py.as_str(), &[]);
let a_symbol = get_symbol(&db, foo_dot_py.as_str(), &[], "A");
assert!(a_symbol.expect_type().is_class_literal());
}
#[test]
fn pep695_type_params() {
let mut db = setup_db();