mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-29 03:02:27 +00:00
[ty] Re-arrange "list modules" implementation for Salsa caching
This basically splits `list_modules` into a higher level "aggregation" routine and a lower level "get modules for one search path" routine. This permits Salsa to cache the lower level components, e.g., many search paths refer to directories that rarely change. This saves us interaction with the system. This did require a fair bit of surgery in terms of being careful about adding file roots. Namely, now that we rely even more on file roots existing for correct handling of cache invalidation, there were several spots in our code that needed to be updated to add roots (that we weren't previously doing). This feels Not Great, and it would be better if we had some kind of abstraction that handled this for us. But it isn't clear to me at this time what that looks like.
This commit is contained in:
parent
468eb37d75
commit
ddd4bab67c
4 changed files with 177 additions and 47 deletions
|
|
@ -12,25 +12,69 @@ use super::resolver::{
|
||||||
ModuleResolveMode, ResolverContext, is_non_shadowable, resolve_file_module, search_paths,
|
ModuleResolveMode, ResolverContext, is_non_shadowable, resolve_file_module, search_paths,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// List all available modules.
|
/// List all available top-level modules.
|
||||||
#[salsa::tracked]
|
#[salsa::tracked]
|
||||||
pub fn list_modules(db: &dyn Db) -> Vec<Module<'_>> {
|
pub fn list_modules(db: &dyn Db) -> Vec<Module<'_>> {
|
||||||
let mut lister = Lister::new(db);
|
let mut modules = BTreeMap::new();
|
||||||
for search_path in search_paths(db, ModuleResolveMode::StubsAllowed) {
|
for search_path in search_paths(db, ModuleResolveMode::StubsAllowed) {
|
||||||
match search_path.as_path() {
|
for module in list_modules_in(db, SearchPathIngredient::new(db, search_path.clone())) {
|
||||||
SystemOrVendoredPathRef::System(system_search_path) => {
|
match modules.entry(module.name(db)) {
|
||||||
let Ok(it) = db.system().read_directory(system_search_path) else {
|
Entry::Vacant(entry) => {
|
||||||
continue;
|
entry.insert(module);
|
||||||
};
|
}
|
||||||
for result in it {
|
Entry::Occupied(mut entry) => {
|
||||||
let Ok(entry) = result else { continue };
|
// The only case where a module can override
|
||||||
lister.add_path(search_path, &entry.path().into(), entry.file_type().into());
|
// a module with the same name in a higher
|
||||||
|
// precedent search path is if the higher
|
||||||
|
// precedent search path contained a namespace
|
||||||
|
// package and the lower precedent search path
|
||||||
|
// contained a "regular" module.
|
||||||
|
if let (None, Some(_)) = (entry.get().search_path(db), module.search_path(db)) {
|
||||||
|
entry.insert(module);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SystemOrVendoredPathRef::Vendored(vendored_search_path) => {
|
}
|
||||||
for entry in db.vendored().read_directory(vendored_search_path) {
|
}
|
||||||
lister.add_path(search_path, &entry.path().into(), entry.file_type().into());
|
modules.into_values().collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[salsa::tracked(debug, heap_size=ruff_memory_usage::heap_size)]
|
||||||
|
struct SearchPathIngredient<'db> {
|
||||||
|
#[returns(ref)]
|
||||||
|
path: SearchPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List all available top-level modules in the given `SearchPath`.
|
||||||
|
#[salsa::tracked]
|
||||||
|
fn list_modules_in<'db>(
|
||||||
|
db: &'db dyn Db,
|
||||||
|
search_path: SearchPathIngredient<'db>,
|
||||||
|
) -> Vec<Module<'db>> {
|
||||||
|
let mut lister = Lister::new(db, search_path.path(db));
|
||||||
|
match search_path.path(db).as_path() {
|
||||||
|
SystemOrVendoredPathRef::System(system_search_path) => {
|
||||||
|
// Read the revision on the corresponding file root to
|
||||||
|
// register an explicit dependency on this directory. When
|
||||||
|
// the revision gets bumped, the cache that Salsa creates
|
||||||
|
// for this routine will be invalidated.
|
||||||
|
let root = db
|
||||||
|
.files()
|
||||||
|
.root(db, system_search_path)
|
||||||
|
.expect("System search path should have a registered root");
|
||||||
|
let _ = root.revision(db);
|
||||||
|
|
||||||
|
let Ok(it) = db.system().read_directory(system_search_path) else {
|
||||||
|
return vec![];
|
||||||
|
};
|
||||||
|
for result in it {
|
||||||
|
let Ok(entry) = result else { continue };
|
||||||
|
lister.add_path(&entry.path().into(), entry.file_type().into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SystemOrVendoredPathRef::Vendored(vendored_search_path) => {
|
||||||
|
for entry in db.vendored().read_directory(vendored_search_path) {
|
||||||
|
lister.add_path(&entry.path().into(), entry.file_type().into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -47,17 +91,19 @@ pub fn list_modules(db: &dyn Db) -> Vec<Module<'_>> {
|
||||||
struct Lister<'db> {
|
struct Lister<'db> {
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
program: Program,
|
program: Program,
|
||||||
|
search_path: &'db SearchPath,
|
||||||
modules: BTreeMap<&'db ModuleName, Module<'db>>,
|
modules: BTreeMap<&'db ModuleName, Module<'db>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> Lister<'db> {
|
impl<'db> Lister<'db> {
|
||||||
/// Create new state that can accumulate modules from a list
|
/// Create new state that can accumulate modules from a list
|
||||||
/// of file paths.
|
/// of file paths.
|
||||||
fn new(db: &'db dyn Db) -> Lister<'db> {
|
fn new(db: &'db dyn Db, search_path: &'db SearchPath) -> Lister<'db> {
|
||||||
let program = Program::get(db);
|
let program = Program::get(db);
|
||||||
Lister {
|
Lister {
|
||||||
db,
|
db,
|
||||||
program,
|
program,
|
||||||
|
search_path,
|
||||||
modules: BTreeMap::new(),
|
modules: BTreeMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -67,19 +113,17 @@ impl<'db> Lister<'db> {
|
||||||
self.modules.into_values().collect()
|
self.modules.into_values().collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add the given `path` (from `search_path`) as a possible
|
/// Add the given `path` as a possible module to this lister. The
|
||||||
/// module to this lister. The `file_type` should be the type
|
/// `file_type` should be the type of `path` (file, directory or
|
||||||
/// of `path` (file, directory or symlink).
|
/// symlink).
|
||||||
///
|
///
|
||||||
/// This may decide that the given path does not correspond to
|
/// This may decide that the given path does not correspond to
|
||||||
/// a valid Python module. In which case, it is dropped and this
|
/// a valid Python module. In which case, it is dropped and this
|
||||||
/// is a no-op.
|
/// is a no-op.
|
||||||
fn add_path(
|
///
|
||||||
&mut self,
|
/// Callers must ensure that the path given came from the same
|
||||||
search_path: &SearchPath,
|
/// `SearchPath` used to create this `Lister`.
|
||||||
path: &SystemOrVendoredPathRef<'_>,
|
fn add_path(&mut self, path: &SystemOrVendoredPathRef<'_>, file_type: FileType) {
|
||||||
file_type: FileType,
|
|
||||||
) {
|
|
||||||
let mut has_py_extension = false;
|
let mut has_py_extension = false;
|
||||||
// We must have no extension, a Python source file extension (`.py`)
|
// We must have no extension, a Python source file extension (`.py`)
|
||||||
// or a Python stub file extension (`.pyi`).
|
// or a Python stub file extension (`.pyi`).
|
||||||
|
|
@ -91,7 +135,7 @@ impl<'db> Lister<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(name) = path.file_name() else { return };
|
let Some(name) = path.file_name() else { return };
|
||||||
let mut module_path = search_path.to_module_path();
|
let mut module_path = self.search_path.to_module_path();
|
||||||
module_path.push(name);
|
module_path.push(name);
|
||||||
let Some(module_name) = module_path.to_module_name() else {
|
let Some(module_name) = module_path.to_module_name() else {
|
||||||
return;
|
return;
|
||||||
|
|
@ -99,7 +143,7 @@ impl<'db> Lister<'db> {
|
||||||
|
|
||||||
// Some modules cannot shadow a subset of special
|
// Some modules cannot shadow a subset of special
|
||||||
// modules from the standard library.
|
// modules from the standard library.
|
||||||
if !search_path.is_standard_library() && self.is_non_shadowable(&module_name) {
|
if !self.search_path.is_standard_library() && self.is_non_shadowable(&module_name) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,7 +157,7 @@ impl<'db> Lister<'db> {
|
||||||
self.db,
|
self.db,
|
||||||
module_name,
|
module_name,
|
||||||
ModuleKind::Package,
|
ModuleKind::Package,
|
||||||
search_path.clone(),
|
self.search_path.clone(),
|
||||||
file,
|
file,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -152,7 +196,7 @@ impl<'db> Lister<'db> {
|
||||||
let is_dir =
|
let is_dir =
|
||||||
file_type.is_definitely_directory() || module_path.is_directory(&self.context());
|
file_type.is_definitely_directory() || module_path.is_directory(&self.context());
|
||||||
if is_dir {
|
if is_dir {
|
||||||
if !search_path.is_standard_library() {
|
if !self.search_path.is_standard_library() {
|
||||||
self.add_module(
|
self.add_module(
|
||||||
&module_path,
|
&module_path,
|
||||||
Module::namespace_package(self.db, module_name),
|
Module::namespace_package(self.db, module_name),
|
||||||
|
|
@ -185,7 +229,7 @@ impl<'db> Lister<'db> {
|
||||||
self.db,
|
self.db,
|
||||||
module_name,
|
module_name,
|
||||||
ModuleKind::Module,
|
ModuleKind::Module,
|
||||||
search_path.clone(),
|
self.search_path.clone(),
|
||||||
file,
|
file,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -214,15 +258,14 @@ impl<'db> Lister<'db> {
|
||||||
(None, Some(_)) => {
|
(None, Some(_)) => {
|
||||||
entry.insert(module);
|
entry.insert(module);
|
||||||
}
|
}
|
||||||
(Some(search_path_existing), Some(search_path_new)) => {
|
(Some(_), Some(_)) => {
|
||||||
// Merging across search paths is only necessary for
|
// Merging across search paths is only necessary for
|
||||||
// namespace packages. For all other modules, entries
|
// namespace packages. For all other modules, entries
|
||||||
// from earlier search paths take precedence. Thus, all
|
// from earlier search paths take precedence. Thus, all
|
||||||
// of the cases below require that we're in the same
|
// of the cases below require that we're in the same
|
||||||
// directory.
|
// directory. ... Which is true here, because a `Lister`
|
||||||
if search_path_existing != search_path_new {
|
// only works for one specific search path.
|
||||||
return;
|
|
||||||
}
|
|
||||||
// When we have a `foo/__init__.py` and a `foo.py` in
|
// When we have a `foo/__init__.py` and a `foo.py` in
|
||||||
// the same directory, the former takes precedent.
|
// the same directory, the former takes precedent.
|
||||||
// (This case can only occur when both have a search
|
// (This case can only occur when both have a search
|
||||||
|
|
@ -322,7 +365,7 @@ fn is_python_extension(ext: &str) -> bool {
|
||||||
mod tests {
|
mod tests {
|
||||||
use camino::{Utf8Component, Utf8Path};
|
use camino::{Utf8Component, Utf8Path};
|
||||||
use ruff_db::Db as _;
|
use ruff_db::Db as _;
|
||||||
use ruff_db::files::{File, FilePath};
|
use ruff_db::files::{File, FilePath, FileRootKind};
|
||||||
use ruff_db::system::{
|
use ruff_db::system::{
|
||||||
DbWithTestSystem, DbWithWritableSystem, OsSystem, SystemPath, SystemPathBuf,
|
DbWithTestSystem, DbWithWritableSystem, OsSystem, SystemPath, SystemPathBuf,
|
||||||
};
|
};
|
||||||
|
|
@ -906,6 +949,12 @@ mod tests {
|
||||||
std::fs::write(foo.as_std_path(), "")?;
|
std::fs::write(foo.as_std_path(), "")?;
|
||||||
std::os::unix::fs::symlink(foo.as_std_path(), bar.as_std_path())?;
|
std::os::unix::fs::symlink(foo.as_std_path(), bar.as_std_path())?;
|
||||||
|
|
||||||
|
db.files().try_add_root(&db, &src, FileRootKind::Project);
|
||||||
|
db.files()
|
||||||
|
.try_add_root(&db, &site_packages, FileRootKind::LibrarySearchPath);
|
||||||
|
db.files()
|
||||||
|
.try_add_root(&db, &custom_typeshed, FileRootKind::LibrarySearchPath);
|
||||||
|
|
||||||
Program::from_settings(
|
Program::from_settings(
|
||||||
&db,
|
&db,
|
||||||
ProgramSettings {
|
ProgramSettings {
|
||||||
|
|
@ -966,11 +1015,13 @@ mod tests {
|
||||||
// Now write the foo file
|
// Now write the foo file
|
||||||
db.write_file(&foo_path, "x = 1")?;
|
db.write_file(&foo_path, "x = 1")?;
|
||||||
|
|
||||||
// FIXME: This is obviously wrong. The Salsa cache
|
|
||||||
// isn't being invalidated.
|
|
||||||
insta::assert_debug_snapshot!(
|
insta::assert_debug_snapshot!(
|
||||||
list_snapshot(&db),
|
list_snapshot(&db),
|
||||||
@"[]",
|
@r#"
|
||||||
|
[
|
||||||
|
Module::File("foo", "first-party", "/src/foo.py", Module, None),
|
||||||
|
]
|
||||||
|
"#,
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -1090,14 +1141,11 @@ mod tests {
|
||||||
let src_functools_path = src.join("functools.py");
|
let src_functools_path = src.join("functools.py");
|
||||||
db.write_file(&src_functools_path, "FOO: int").unwrap();
|
db.write_file(&src_functools_path, "FOO: int").unwrap();
|
||||||
|
|
||||||
// FIXME: This looks wrong! This is a cache invalidation
|
|
||||||
// problem, not a logic problem in the "list modules"
|
|
||||||
// implementation.
|
|
||||||
insta::assert_debug_snapshot!(
|
insta::assert_debug_snapshot!(
|
||||||
list_snapshot(&db),
|
list_snapshot(&db),
|
||||||
@r#"
|
@r#"
|
||||||
[
|
[
|
||||||
Module::File("functools", "std-custom", "/typeshed/stdlib/functools.pyi", Module, None),
|
Module::File("functools", "first-party", "/src/functools.py", Module, None),
|
||||||
]
|
]
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
|
|
@ -1157,6 +1205,7 @@ mod tests {
|
||||||
|
|
||||||
let TestCase { mut db, .. } = TestCaseBuilder::new()
|
let TestCase { mut db, .. } = TestCaseBuilder::new()
|
||||||
.with_site_packages_files(SITE_PACKAGES)
|
.with_site_packages_files(SITE_PACKAGES)
|
||||||
|
.with_library_root("/x")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
db.write_files(x_directory).unwrap();
|
db.write_files(x_directory).unwrap();
|
||||||
|
|
@ -1181,6 +1230,7 @@ mod tests {
|
||||||
|
|
||||||
let TestCase { mut db, .. } = TestCaseBuilder::new()
|
let TestCase { mut db, .. } = TestCaseBuilder::new()
|
||||||
.with_site_packages_files(SITE_PACKAGES)
|
.with_site_packages_files(SITE_PACKAGES)
|
||||||
|
.with_library_root("/y/src")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
db.write_files(external_files).unwrap();
|
db.write_files(external_files).unwrap();
|
||||||
|
|
@ -1207,6 +1257,7 @@ mod tests {
|
||||||
|
|
||||||
let TestCase { db, .. } = TestCaseBuilder::new()
|
let TestCase { db, .. } = TestCaseBuilder::new()
|
||||||
.with_site_packages_files(SITE_PACKAGES)
|
.with_site_packages_files(SITE_PACKAGES)
|
||||||
|
.with_library_root("/x")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(
|
insta::assert_debug_snapshot!(
|
||||||
|
|
@ -1246,6 +1297,9 @@ not_a_directory
|
||||||
|
|
||||||
let TestCase { mut db, .. } = TestCaseBuilder::new()
|
let TestCase { mut db, .. } = TestCaseBuilder::new()
|
||||||
.with_site_packages_files(SITE_PACKAGES)
|
.with_site_packages_files(SITE_PACKAGES)
|
||||||
|
.with_library_root("/x/y/src")
|
||||||
|
.with_library_root("/")
|
||||||
|
.with_library_root("/baz")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
db.write_files(root_files).unwrap();
|
db.write_files(root_files).unwrap();
|
||||||
|
|
@ -1279,6 +1333,8 @@ not_a_directory
|
||||||
|
|
||||||
let TestCase { mut db, .. } = TestCaseBuilder::new()
|
let TestCase { mut db, .. } = TestCaseBuilder::new()
|
||||||
.with_site_packages_files(SITE_PACKAGES)
|
.with_site_packages_files(SITE_PACKAGES)
|
||||||
|
.with_library_root("/x")
|
||||||
|
.with_library_root("/y")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
db.write_files(external_directories).unwrap();
|
db.write_files(external_directories).unwrap();
|
||||||
|
|
@ -1325,6 +1381,7 @@ not_a_directory
|
||||||
..
|
..
|
||||||
} = TestCaseBuilder::new()
|
} = TestCaseBuilder::new()
|
||||||
.with_site_packages_files(SITE_PACKAGES)
|
.with_site_packages_files(SITE_PACKAGES)
|
||||||
|
.with_library_root("/x")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
db.write_files(x_directory).unwrap();
|
db.write_files(x_directory).unwrap();
|
||||||
|
|
@ -1360,6 +1417,7 @@ not_a_directory
|
||||||
|
|
||||||
let TestCase { mut db, .. } = TestCaseBuilder::new()
|
let TestCase { mut db, .. } = TestCaseBuilder::new()
|
||||||
.with_site_packages_files(SITE_PACKAGES)
|
.with_site_packages_files(SITE_PACKAGES)
|
||||||
|
.with_library_root("/x")
|
||||||
.build();
|
.build();
|
||||||
let src_path = SystemPathBuf::from("/x/src");
|
let src_path = SystemPathBuf::from("/x/src");
|
||||||
|
|
||||||
|
|
@ -1411,6 +1469,15 @@ not_a_directory
|
||||||
])
|
])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
db.files()
|
||||||
|
.try_add_root(&db, SystemPath::new("/src"), FileRootKind::Project);
|
||||||
|
db.files()
|
||||||
|
.try_add_root(&db, &venv_site_packages, FileRootKind::LibrarySearchPath);
|
||||||
|
db.files()
|
||||||
|
.try_add_root(&db, &system_site_packages, FileRootKind::LibrarySearchPath);
|
||||||
|
db.files()
|
||||||
|
.try_add_root(&db, SystemPath::new("/x"), FileRootKind::LibrarySearchPath);
|
||||||
|
|
||||||
Program::from_settings(
|
Program::from_settings(
|
||||||
&db,
|
&db,
|
||||||
ProgramSettings {
|
ProgramSettings {
|
||||||
|
|
@ -1497,6 +1564,8 @@ not_a_directory
|
||||||
std::os::unix::fs::symlink(a_package_target.as_std_path(), a_src.as_std_path())
|
std::os::unix::fs::symlink(a_package_target.as_std_path(), a_src.as_std_path())
|
||||||
.context("Failed to symlink `src/a` to `a-package`")?;
|
.context("Failed to symlink `src/a` to `a-package`")?;
|
||||||
|
|
||||||
|
db.files().try_add_root(&db, &root, FileRootKind::Project);
|
||||||
|
|
||||||
Program::from_settings(
|
Program::from_settings(
|
||||||
&db,
|
&db,
|
||||||
ProgramSettings {
|
ProgramSettings {
|
||||||
|
|
@ -1535,6 +1604,11 @@ not_a_directory
|
||||||
let mut db = TestDb::new();
|
let mut db = TestDb::new();
|
||||||
db.write_file(&installed_foo_module, "").unwrap();
|
db.write_file(&installed_foo_module, "").unwrap();
|
||||||
|
|
||||||
|
db.files()
|
||||||
|
.try_add_root(&db, &project_directory, FileRootKind::Project);
|
||||||
|
db.files()
|
||||||
|
.try_add_root(&db, &site_packages, FileRootKind::LibrarySearchPath);
|
||||||
|
|
||||||
Program::from_settings(
|
Program::from_settings(
|
||||||
&db,
|
&db,
|
||||||
ProgramSettings {
|
ProgramSettings {
|
||||||
|
|
|
||||||
|
|
@ -433,13 +433,16 @@ pub(crate) fn dynamic_resolution_paths<'db>(
|
||||||
let site_packages_dir = site_packages_search_path
|
let site_packages_dir = site_packages_search_path
|
||||||
.as_system_path()
|
.as_system_path()
|
||||||
.expect("Expected site package path to be a system path");
|
.expect("Expected site package path to be a system path");
|
||||||
|
let site_packages_dir = system
|
||||||
|
.canonicalize_path(site_packages_dir)
|
||||||
|
.unwrap_or_else(|_| site_packages_dir.to_path_buf());
|
||||||
|
|
||||||
if !existing_paths.insert(Cow::Borrowed(site_packages_dir)) {
|
if !existing_paths.insert(Cow::Owned(site_packages_dir.clone())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let site_packages_root = files
|
let site_packages_root = files
|
||||||
.root(db, site_packages_dir)
|
.root(db, &site_packages_dir)
|
||||||
.expect("Site-package root to have been created");
|
.expect("Site-package root to have been created");
|
||||||
|
|
||||||
// This query needs to be re-executed each time a `.pth` file
|
// This query needs to be re-executed each time a `.pth` file
|
||||||
|
|
@ -457,7 +460,7 @@ pub(crate) fn dynamic_resolution_paths<'db>(
|
||||||
// containing a (relative or absolute) path.
|
// containing a (relative or absolute) path.
|
||||||
// Each of these paths may point to an editable install of a package,
|
// Each of these paths may point to an editable install of a package,
|
||||||
// so should be considered an additional search path.
|
// so should be considered an additional search path.
|
||||||
let pth_file_iterator = match PthFileIterator::new(db, site_packages_dir) {
|
let pth_file_iterator = match PthFileIterator::new(db, &site_packages_dir) {
|
||||||
Ok(iterator) => iterator,
|
Ok(iterator) => iterator,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use ruff_db::Db;
|
use ruff_db::Db;
|
||||||
|
use ruff_db::files::FileRootKind;
|
||||||
use ruff_db::system::{
|
use ruff_db::system::{
|
||||||
DbWithTestSystem as _, DbWithWritableSystem as _, SystemPath, SystemPathBuf,
|
DbWithTestSystem as _, DbWithWritableSystem as _, SystemPath, SystemPathBuf,
|
||||||
};
|
};
|
||||||
|
|
@ -107,6 +108,14 @@ pub(crate) struct TestCaseBuilder<T> {
|
||||||
python_platform: PythonPlatform,
|
python_platform: PythonPlatform,
|
||||||
first_party_files: Vec<FileSpec>,
|
first_party_files: Vec<FileSpec>,
|
||||||
site_packages_files: Vec<FileSpec>,
|
site_packages_files: Vec<FileSpec>,
|
||||||
|
// Additional file roots (beyond site_packages, src and stdlib)
|
||||||
|
// that should be registered with the `Db` abstraction.
|
||||||
|
//
|
||||||
|
// This is necessary to make testing "list modules" work. Namely,
|
||||||
|
// "list modules" relies on caching via a file root's revision,
|
||||||
|
// and if file roots aren't registered, then the implementation
|
||||||
|
// can't access the root's revision.
|
||||||
|
roots: Vec<SystemPathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> TestCaseBuilder<T> {
|
impl<T> TestCaseBuilder<T> {
|
||||||
|
|
@ -128,6 +137,12 @@ impl<T> TestCaseBuilder<T> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a "library" root to this test case.
|
||||||
|
pub(crate) fn with_library_root(mut self, root: impl AsRef<SystemPath>) -> Self {
|
||||||
|
self.roots.push(root.as_ref().to_path_buf());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
fn write_mock_directory(
|
fn write_mock_directory(
|
||||||
db: &mut TestDb,
|
db: &mut TestDb,
|
||||||
location: impl AsRef<SystemPath>,
|
location: impl AsRef<SystemPath>,
|
||||||
|
|
@ -154,6 +169,7 @@ impl TestCaseBuilder<UnspecifiedTypeshed> {
|
||||||
python_platform: PythonPlatform::default(),
|
python_platform: PythonPlatform::default(),
|
||||||
first_party_files: vec![],
|
first_party_files: vec![],
|
||||||
site_packages_files: vec![],
|
site_packages_files: vec![],
|
||||||
|
roots: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,6 +181,7 @@ impl TestCaseBuilder<UnspecifiedTypeshed> {
|
||||||
python_platform,
|
python_platform,
|
||||||
first_party_files,
|
first_party_files,
|
||||||
site_packages_files,
|
site_packages_files,
|
||||||
|
roots,
|
||||||
} = self;
|
} = self;
|
||||||
TestCaseBuilder {
|
TestCaseBuilder {
|
||||||
typeshed_option: VendoredTypeshed,
|
typeshed_option: VendoredTypeshed,
|
||||||
|
|
@ -172,6 +189,7 @@ impl TestCaseBuilder<UnspecifiedTypeshed> {
|
||||||
python_platform,
|
python_platform,
|
||||||
first_party_files,
|
first_party_files,
|
||||||
site_packages_files,
|
site_packages_files,
|
||||||
|
roots,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -186,6 +204,7 @@ impl TestCaseBuilder<UnspecifiedTypeshed> {
|
||||||
python_platform,
|
python_platform,
|
||||||
first_party_files,
|
first_party_files,
|
||||||
site_packages_files,
|
site_packages_files,
|
||||||
|
roots,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
TestCaseBuilder {
|
TestCaseBuilder {
|
||||||
|
|
@ -194,6 +213,7 @@ impl TestCaseBuilder<UnspecifiedTypeshed> {
|
||||||
python_platform,
|
python_platform,
|
||||||
first_party_files,
|
first_party_files,
|
||||||
site_packages_files,
|
site_packages_files,
|
||||||
|
roots,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -224,6 +244,7 @@ impl TestCaseBuilder<MockedTypeshed> {
|
||||||
python_platform,
|
python_platform,
|
||||||
first_party_files,
|
first_party_files,
|
||||||
site_packages_files,
|
site_packages_files,
|
||||||
|
roots,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let mut db = TestDb::new();
|
let mut db = TestDb::new();
|
||||||
|
|
@ -233,6 +254,19 @@ impl TestCaseBuilder<MockedTypeshed> {
|
||||||
let src = Self::write_mock_directory(&mut db, "/src", first_party_files);
|
let src = Self::write_mock_directory(&mut db, "/src", first_party_files);
|
||||||
let typeshed = Self::build_typeshed_mock(&mut db, &typeshed_option);
|
let typeshed = Self::build_typeshed_mock(&mut db, &typeshed_option);
|
||||||
|
|
||||||
|
// This root is needed for correct Salsa tracking.
|
||||||
|
// Namely, a `SearchPath` is treated as an input, and
|
||||||
|
// thus the revision number must be bumped accordingly
|
||||||
|
// when the directory tree changes. We rely on detecting
|
||||||
|
// this revision from the file root. If we don't add them
|
||||||
|
// here, they won't get added.
|
||||||
|
//
|
||||||
|
// Roots for other search paths are added as part of
|
||||||
|
// search path initialization in `Program::from_settings`,
|
||||||
|
// and any remaining are added below.
|
||||||
|
db.files()
|
||||||
|
.try_add_root(&db, SystemPath::new("/src"), FileRootKind::Project);
|
||||||
|
|
||||||
Program::from_settings(
|
Program::from_settings(
|
||||||
&db,
|
&db,
|
||||||
ProgramSettings {
|
ProgramSettings {
|
||||||
|
|
@ -251,10 +285,17 @@ impl TestCaseBuilder<MockedTypeshed> {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let stdlib = typeshed.join("stdlib");
|
||||||
|
db.files()
|
||||||
|
.try_add_root(&db, &stdlib, FileRootKind::LibrarySearchPath);
|
||||||
|
for root in &roots {
|
||||||
|
db.files()
|
||||||
|
.try_add_root(&db, root, FileRootKind::LibrarySearchPath);
|
||||||
|
}
|
||||||
TestCase {
|
TestCase {
|
||||||
db,
|
db,
|
||||||
src,
|
src,
|
||||||
stdlib: typeshed.join("stdlib"),
|
stdlib,
|
||||||
site_packages,
|
site_packages,
|
||||||
python_version,
|
python_version,
|
||||||
}
|
}
|
||||||
|
|
@ -286,6 +327,7 @@ impl TestCaseBuilder<VendoredTypeshed> {
|
||||||
python_platform,
|
python_platform,
|
||||||
first_party_files,
|
first_party_files,
|
||||||
site_packages_files,
|
site_packages_files,
|
||||||
|
roots,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let mut db = TestDb::new();
|
let mut db = TestDb::new();
|
||||||
|
|
@ -294,6 +336,9 @@ impl TestCaseBuilder<VendoredTypeshed> {
|
||||||
Self::write_mock_directory(&mut db, "/site-packages", site_packages_files);
|
Self::write_mock_directory(&mut db, "/site-packages", site_packages_files);
|
||||||
let src = Self::write_mock_directory(&mut db, "/src", first_party_files);
|
let src = Self::write_mock_directory(&mut db, "/src", first_party_files);
|
||||||
|
|
||||||
|
db.files()
|
||||||
|
.try_add_root(&db, SystemPath::new("/src"), FileRootKind::Project);
|
||||||
|
|
||||||
Program::from_settings(
|
Program::from_settings(
|
||||||
&db,
|
&db,
|
||||||
ProgramSettings {
|
ProgramSettings {
|
||||||
|
|
@ -311,6 +356,10 @@ impl TestCaseBuilder<VendoredTypeshed> {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
for root in &roots {
|
||||||
|
db.files()
|
||||||
|
.try_add_root(&db, root, FileRootKind::LibrarySearchPath);
|
||||||
|
}
|
||||||
TestCase {
|
TestCase {
|
||||||
db,
|
db,
|
||||||
src,
|
src,
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use config::SystemKind;
|
||||||
use parser as test_parser;
|
use parser as test_parser;
|
||||||
use ruff_db::Db as _;
|
use ruff_db::Db as _;
|
||||||
use ruff_db::diagnostic::{Diagnostic, DisplayDiagnosticConfig};
|
use ruff_db::diagnostic::{Diagnostic, DisplayDiagnosticConfig};
|
||||||
use ruff_db::files::{File, system_path_to_file};
|
use ruff_db::files::{File, FileRootKind, system_path_to_file};
|
||||||
use ruff_db::panic::catch_unwind;
|
use ruff_db::panic::catch_unwind;
|
||||||
use ruff_db::parsed::parsed_module;
|
use ruff_db::parsed::parsed_module;
|
||||||
use ruff_db::system::{DbWithWritableSystem as _, SystemPath, SystemPathBuf};
|
use ruff_db::system::{DbWithWritableSystem as _, SystemPath, SystemPathBuf};
|
||||||
|
|
@ -184,6 +184,8 @@ fn run_test(
|
||||||
let project_root = SystemPathBuf::from("/src");
|
let project_root = SystemPathBuf::from("/src");
|
||||||
db.create_directory_all(&project_root)
|
db.create_directory_all(&project_root)
|
||||||
.expect("Creating the project root to succeed");
|
.expect("Creating the project root to succeed");
|
||||||
|
db.files()
|
||||||
|
.try_add_root(db, &project_root, FileRootKind::Project);
|
||||||
|
|
||||||
let src_path = project_root.clone();
|
let src_path = project_root.clone();
|
||||||
let custom_typeshed_path = test.configuration().typeshed();
|
let custom_typeshed_path = test.configuration().typeshed();
|
||||||
|
|
@ -255,6 +257,8 @@ fn run_test(
|
||||||
|
|
||||||
// Create a custom typeshed `VERSIONS` file if none was provided.
|
// Create a custom typeshed `VERSIONS` file if none was provided.
|
||||||
if let Some(typeshed_path) = custom_typeshed_path {
|
if let Some(typeshed_path) = custom_typeshed_path {
|
||||||
|
db.files()
|
||||||
|
.try_add_root(db, typeshed_path, FileRootKind::LibrarySearchPath);
|
||||||
if !has_custom_versions_file {
|
if !has_custom_versions_file {
|
||||||
let versions_file = typeshed_path.join("stdlib/VERSIONS");
|
let versions_file = typeshed_path.join("stdlib/VERSIONS");
|
||||||
let contents = typeshed_files
|
let contents = typeshed_files
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue