mirror of https://github.com/astral-sh/ruff
[red-knot] Rework module resolver tests (#12260)
This commit is contained in:
parent
880c31d164
commit
e8b5341c97
|
|
@ -25,13 +25,9 @@ pub(crate) mod tests {
|
|||
use salsa::DebugWithDb;
|
||||
|
||||
use ruff_db::files::Files;
|
||||
use ruff_db::system::{
|
||||
DbWithTestSystem, MemoryFileSystem, SystemPath, SystemPathBuf, TestSystem,
|
||||
};
|
||||
use ruff_db::system::{DbWithTestSystem, TestSystem};
|
||||
use ruff_db::vendored::VendoredFileSystem;
|
||||
|
||||
use crate::resolver::{set_module_resolution_settings, RawModuleResolutionSettings};
|
||||
use crate::supported_py_version::TargetVersion;
|
||||
use crate::vendored_typeshed_stubs;
|
||||
|
||||
use super::*;
|
||||
|
|
@ -127,131 +123,4 @@ pub(crate) mod tests {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct TestCaseBuilder {
|
||||
db: TestDb,
|
||||
src: SystemPathBuf,
|
||||
site_packages: SystemPathBuf,
|
||||
target_version: Option<TargetVersion>,
|
||||
with_vendored_stubs: bool,
|
||||
}
|
||||
|
||||
impl TestCaseBuilder {
|
||||
#[must_use]
|
||||
pub(crate) fn with_target_version(mut self, target_version: TargetVersion) -> Self {
|
||||
self.target_version = Some(target_version);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub(crate) fn with_vendored_stubs_used(mut self) -> Self {
|
||||
self.with_vendored_stubs = true;
|
||||
self
|
||||
}
|
||||
|
||||
fn create_mocked_typeshed(
|
||||
typeshed_dir: &SystemPath,
|
||||
fs: &MemoryFileSystem,
|
||||
) -> std::io::Result<()> {
|
||||
static VERSIONS_DATA: &str = "\
|
||||
asyncio: 3.8- # 'Regular' package on py38+
|
||||
asyncio.tasks: 3.9-3.11
|
||||
collections: 3.9- # 'Regular' package on py39+
|
||||
functools: 3.8-
|
||||
importlib: 3.9- # Namespace package on py39+
|
||||
xml: 3.8-3.8 # Namespace package on py38 only
|
||||
";
|
||||
|
||||
fs.create_directory_all(typeshed_dir)?;
|
||||
fs.write_file(typeshed_dir.join("stdlib/VERSIONS"), VERSIONS_DATA)?;
|
||||
|
||||
// Regular package on py38+
|
||||
fs.create_directory_all(typeshed_dir.join("stdlib/asyncio"))?;
|
||||
fs.touch(typeshed_dir.join("stdlib/asyncio/__init__.pyi"))?;
|
||||
fs.write_file(
|
||||
typeshed_dir.join("stdlib/asyncio/tasks.pyi"),
|
||||
"class Task: ...",
|
||||
)?;
|
||||
|
||||
// Regular package on py39+
|
||||
fs.create_directory_all(typeshed_dir.join("stdlib/collections"))?;
|
||||
fs.touch(typeshed_dir.join("stdlib/collections/__init__.pyi"))?;
|
||||
|
||||
// Namespace package on py38 only
|
||||
fs.create_directory_all(typeshed_dir.join("stdlib/xml"))?;
|
||||
fs.touch(typeshed_dir.join("stdlib/xml/etree.pyi"))?;
|
||||
|
||||
// Namespace package on py39+
|
||||
fs.create_directory_all(typeshed_dir.join("stdlib/importlib"))?;
|
||||
fs.touch(typeshed_dir.join("stdlib/importlib/abc.pyi"))?;
|
||||
|
||||
fs.write_file(
|
||||
typeshed_dir.join("stdlib/functools.pyi"),
|
||||
"def update_wrapper(): ...",
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn build(self) -> std::io::Result<TestCase> {
|
||||
let TestCaseBuilder {
|
||||
mut db,
|
||||
src,
|
||||
with_vendored_stubs,
|
||||
site_packages,
|
||||
target_version,
|
||||
} = self;
|
||||
|
||||
let typeshed_dir = SystemPathBuf::from("/typeshed");
|
||||
|
||||
let custom_typeshed = if with_vendored_stubs {
|
||||
None
|
||||
} else {
|
||||
Self::create_mocked_typeshed(&typeshed_dir, db.memory_file_system())?;
|
||||
Some(typeshed_dir.clone())
|
||||
};
|
||||
|
||||
let settings = RawModuleResolutionSettings {
|
||||
target_version: target_version.unwrap_or_default(),
|
||||
extra_paths: vec![],
|
||||
workspace_root: src.clone(),
|
||||
custom_typeshed: custom_typeshed.clone(),
|
||||
site_packages: Some(site_packages.clone()),
|
||||
};
|
||||
|
||||
set_module_resolution_settings(&mut db, settings);
|
||||
|
||||
Ok(TestCase {
|
||||
db,
|
||||
src: src.clone(),
|
||||
custom_typeshed: typeshed_dir,
|
||||
site_packages: site_packages.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct TestCase {
|
||||
pub(crate) db: TestDb,
|
||||
pub(crate) src: SystemPathBuf,
|
||||
pub(crate) custom_typeshed: SystemPathBuf,
|
||||
pub(crate) site_packages: SystemPathBuf,
|
||||
}
|
||||
|
||||
pub(crate) fn create_resolver_builder() -> std::io::Result<TestCaseBuilder> {
|
||||
let db = TestDb::new();
|
||||
|
||||
let src = SystemPathBuf::from("/src");
|
||||
let site_packages = SystemPathBuf::from("/site_packages");
|
||||
|
||||
let fs = db.memory_file_system();
|
||||
|
||||
fs.create_directory_all(&src)?;
|
||||
fs.create_directory_all(&site_packages)?;
|
||||
|
||||
Ok(TestCaseBuilder {
|
||||
db,
|
||||
src,
|
||||
with_vendored_stubs: false,
|
||||
site_packages,
|
||||
target_version: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ mod state;
|
|||
mod supported_py_version;
|
||||
mod typeshed;
|
||||
|
||||
#[cfg(test)]
|
||||
mod testing;
|
||||
|
||||
pub use db::{Db, Jar};
|
||||
pub use module::{Module, ModuleKind};
|
||||
pub use module_name::ModuleName;
|
||||
|
|
|
|||
|
|
@ -254,6 +254,18 @@ impl fmt::Debug for ModuleResolutionPathBuf {
|
|||
}
|
||||
}
|
||||
|
||||
impl PartialEq<SystemPathBuf> for ModuleResolutionPathBuf {
|
||||
fn eq(&self, other: &SystemPathBuf) -> bool {
|
||||
ModuleResolutionPathRef::from(self) == **other
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<ModuleResolutionPathBuf> for SystemPathBuf {
|
||||
fn eq(&self, other: &ModuleResolutionPathBuf) -> bool {
|
||||
other.eq(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
||||
enum ModuleResolutionPathRefInner<'a> {
|
||||
Extra(&'a SystemPath),
|
||||
|
|
@ -643,9 +655,9 @@ impl PartialEq<ModuleResolutionPathRef<'_>> for VendoredPathBuf {
|
|||
mod tests {
|
||||
use insta::assert_debug_snapshot;
|
||||
|
||||
use crate::db::tests::{create_resolver_builder, TestCase, TestDb};
|
||||
use crate::db::tests::TestDb;
|
||||
use crate::supported_py_version::TargetVersion;
|
||||
use crate::typeshed::LazyTypeshedVersions;
|
||||
use crate::testing::{FileSpec, MockedTypeshed, TestCase, TestCaseBuilder};
|
||||
|
||||
use super::*;
|
||||
|
||||
|
|
@ -943,26 +955,41 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
fn py38_stdlib_test_case() -> (TestDb, ModuleResolutionPathBuf) {
|
||||
let TestCase {
|
||||
db,
|
||||
custom_typeshed,
|
||||
..
|
||||
} = create_resolver_builder().unwrap().build().unwrap();
|
||||
let stdlib_module_path =
|
||||
ModuleResolutionPathBuf::stdlib_from_custom_typeshed_root(&custom_typeshed).unwrap();
|
||||
(db, stdlib_module_path)
|
||||
fn typeshed_test_case(
|
||||
typeshed: MockedTypeshed,
|
||||
target_version: TargetVersion,
|
||||
) -> (TestDb, ModuleResolutionPathBuf) {
|
||||
let TestCase { db, stdlib, .. } = TestCaseBuilder::new()
|
||||
.with_custom_typeshed(typeshed)
|
||||
.with_target_version(target_version)
|
||||
.build();
|
||||
let stdlib = ModuleResolutionPathBuf::standard_library(FilePath::System(stdlib)).unwrap();
|
||||
(db, stdlib)
|
||||
}
|
||||
|
||||
fn py38_typeshed_test_case(typeshed: MockedTypeshed) -> (TestDb, ModuleResolutionPathBuf) {
|
||||
typeshed_test_case(typeshed, TargetVersion::Py38)
|
||||
}
|
||||
|
||||
fn py39_typeshed_test_case(typeshed: MockedTypeshed) -> (TestDb, ModuleResolutionPathBuf) {
|
||||
typeshed_test_case(typeshed, TargetVersion::Py39)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mocked_typeshed_existing_regular_stdlib_pkg_py38() {
|
||||
let (db, stdlib_path) = py38_stdlib_test_case();
|
||||
let resolver = ResolverState {
|
||||
db: &db,
|
||||
typeshed_versions: LazyTypeshedVersions::new(),
|
||||
target_version: TargetVersion::Py38,
|
||||
const VERSIONS: &str = "\
|
||||
asyncio: 3.8-
|
||||
asyncio.tasks: 3.9-3.11
|
||||
";
|
||||
|
||||
const TYPESHED: MockedTypeshed = MockedTypeshed {
|
||||
versions: VERSIONS,
|
||||
stdlib_files: &[("asyncio/__init__.pyi", ""), ("asyncio/tasks.pyi", "")],
|
||||
};
|
||||
|
||||
let (db, stdlib_path) = py38_typeshed_test_case(TYPESHED);
|
||||
let resolver = ResolverState::new(&db, TargetVersion::Py38);
|
||||
|
||||
let asyncio_regular_package = stdlib_path.join("asyncio");
|
||||
assert!(asyncio_regular_package.is_directory(&stdlib_path, &resolver));
|
||||
assert!(asyncio_regular_package.is_regular_package(&stdlib_path, &resolver));
|
||||
|
|
@ -986,13 +1013,14 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn mocked_typeshed_existing_namespace_stdlib_pkg_py38() {
|
||||
let (db, stdlib_path) = py38_stdlib_test_case();
|
||||
let resolver = ResolverState {
|
||||
db: &db,
|
||||
typeshed_versions: LazyTypeshedVersions::new(),
|
||||
target_version: TargetVersion::Py38,
|
||||
const TYPESHED: MockedTypeshed = MockedTypeshed {
|
||||
versions: "xml: 3.8-3.8",
|
||||
stdlib_files: &[("xml/etree.pyi", "")],
|
||||
};
|
||||
|
||||
let (db, stdlib_path) = py38_typeshed_test_case(TYPESHED);
|
||||
let resolver = ResolverState::new(&db, TargetVersion::Py38);
|
||||
|
||||
let xml_namespace_package = stdlib_path.join("xml");
|
||||
assert!(xml_namespace_package.is_directory(&stdlib_path, &resolver));
|
||||
// Paths to directories don't resolve to VfsFiles
|
||||
|
|
@ -1007,13 +1035,14 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn mocked_typeshed_single_file_stdlib_module_py38() {
|
||||
let (db, stdlib_path) = py38_stdlib_test_case();
|
||||
let resolver = ResolverState {
|
||||
db: &db,
|
||||
typeshed_versions: LazyTypeshedVersions::new(),
|
||||
target_version: TargetVersion::Py38,
|
||||
const TYPESHED: MockedTypeshed = MockedTypeshed {
|
||||
versions: "functools: 3.8-",
|
||||
stdlib_files: &[("functools.pyi", "")],
|
||||
};
|
||||
|
||||
let (db, stdlib_path) = py38_typeshed_test_case(TYPESHED);
|
||||
let resolver = ResolverState::new(&db, TargetVersion::Py38);
|
||||
|
||||
let functools_module = stdlib_path.join("functools.pyi");
|
||||
assert!(functools_module.to_file(&stdlib_path, &resolver).is_some());
|
||||
assert!(!functools_module.is_directory(&stdlib_path, &resolver));
|
||||
|
|
@ -1022,13 +1051,14 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn mocked_typeshed_nonexistent_regular_stdlib_pkg_py38() {
|
||||
let (db, stdlib_path) = py38_stdlib_test_case();
|
||||
let resolver = ResolverState {
|
||||
db: &db,
|
||||
typeshed_versions: LazyTypeshedVersions::new(),
|
||||
target_version: TargetVersion::Py38,
|
||||
const TYPESHED: MockedTypeshed = MockedTypeshed {
|
||||
versions: "collections: 3.9-",
|
||||
stdlib_files: &[("collections/__init__.pyi", "")],
|
||||
};
|
||||
|
||||
let (db, stdlib_path) = py38_typeshed_test_case(TYPESHED);
|
||||
let resolver = ResolverState::new(&db, TargetVersion::Py38);
|
||||
|
||||
let collections_regular_package = stdlib_path.join("collections");
|
||||
assert_eq!(
|
||||
collections_regular_package.to_file(&stdlib_path, &resolver),
|
||||
|
|
@ -1040,13 +1070,14 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn mocked_typeshed_nonexistent_namespace_stdlib_pkg_py38() {
|
||||
let (db, stdlib_path) = py38_stdlib_test_case();
|
||||
let resolver = ResolverState {
|
||||
db: &db,
|
||||
typeshed_versions: LazyTypeshedVersions::new(),
|
||||
target_version: TargetVersion::Py38,
|
||||
const TYPESHED: MockedTypeshed = MockedTypeshed {
|
||||
versions: "importlib: 3.9-",
|
||||
stdlib_files: &[("importlib/abc.pyi", "")],
|
||||
};
|
||||
|
||||
let (db, stdlib_path) = py38_typeshed_test_case(TYPESHED);
|
||||
let resolver = ResolverState::new(&db, TargetVersion::Py38);
|
||||
|
||||
let importlib_namespace_package = stdlib_path.join("importlib");
|
||||
assert_eq!(
|
||||
importlib_namespace_package.to_file(&stdlib_path, &resolver),
|
||||
|
|
@ -1063,43 +1094,42 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn mocked_typeshed_nonexistent_single_file_module_py38() {
|
||||
let (db, stdlib_path) = py38_stdlib_test_case();
|
||||
let resolver = ResolverState {
|
||||
db: &db,
|
||||
typeshed_versions: LazyTypeshedVersions::new(),
|
||||
target_version: TargetVersion::Py38,
|
||||
const TYPESHED: MockedTypeshed = MockedTypeshed {
|
||||
versions: "foo: 2.6-",
|
||||
stdlib_files: &[("foo.pyi", "")],
|
||||
};
|
||||
|
||||
let (db, stdlib_path) = py38_typeshed_test_case(TYPESHED);
|
||||
let resolver = ResolverState::new(&db, TargetVersion::Py38);
|
||||
|
||||
let non_existent = stdlib_path.join("doesnt_even_exist");
|
||||
assert_eq!(non_existent.to_file(&stdlib_path, &resolver), None);
|
||||
assert!(!non_existent.is_directory(&stdlib_path, &resolver));
|
||||
assert!(!non_existent.is_regular_package(&stdlib_path, &resolver));
|
||||
}
|
||||
|
||||
fn py39_stdlib_test_case() -> (TestDb, ModuleResolutionPathBuf) {
|
||||
let TestCase {
|
||||
db,
|
||||
custom_typeshed,
|
||||
..
|
||||
} = create_resolver_builder()
|
||||
.unwrap()
|
||||
.with_target_version(TargetVersion::Py39)
|
||||
.build()
|
||||
.unwrap();
|
||||
let stdlib_module_path =
|
||||
ModuleResolutionPathBuf::stdlib_from_custom_typeshed_root(&custom_typeshed).unwrap();
|
||||
(db, stdlib_module_path)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mocked_typeshed_existing_regular_stdlib_pkgs_py39() {
|
||||
let (db, stdlib_path) = py39_stdlib_test_case();
|
||||
let resolver = ResolverState {
|
||||
db: &db,
|
||||
typeshed_versions: LazyTypeshedVersions::new(),
|
||||
target_version: TargetVersion::Py39,
|
||||
const VERSIONS: &str = "\
|
||||
asyncio: 3.8-
|
||||
asyncio.tasks: 3.9-3.11
|
||||
collections: 3.9-
|
||||
";
|
||||
|
||||
const STDLIB: &[FileSpec] = &[
|
||||
("asyncio/__init__.pyi", ""),
|
||||
("asyncio/tasks.pyi", ""),
|
||||
("collections/__init__.pyi", ""),
|
||||
];
|
||||
|
||||
const TYPESHED: MockedTypeshed = MockedTypeshed {
|
||||
versions: VERSIONS,
|
||||
stdlib_files: STDLIB,
|
||||
};
|
||||
|
||||
let (db, stdlib_path) = py39_typeshed_test_case(TYPESHED);
|
||||
let resolver = ResolverState::new(&db, TargetVersion::Py39);
|
||||
|
||||
// Since we've set the target version to Py39,
|
||||
// `collections` should now exist as a directory, according to VERSIONS...
|
||||
let collections_regular_package = stdlib_path.join("collections");
|
||||
|
|
@ -1126,14 +1156,15 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn mocked_typeshed_existing_namespace_stdlib_pkg_py39() {
|
||||
let (db, stdlib_path) = py39_stdlib_test_case();
|
||||
let resolver = ResolverState {
|
||||
db: &db,
|
||||
typeshed_versions: LazyTypeshedVersions::new(),
|
||||
target_version: TargetVersion::Py39,
|
||||
const TYPESHED: MockedTypeshed = MockedTypeshed {
|
||||
versions: "importlib: 3.9-",
|
||||
stdlib_files: &[("importlib/abc.pyi", "")],
|
||||
};
|
||||
|
||||
// The `importlib` directory now also exists...
|
||||
let (db, stdlib_path) = py39_typeshed_test_case(TYPESHED);
|
||||
let resolver = ResolverState::new(&db, TargetVersion::Py39);
|
||||
|
||||
// The `importlib` directory now also exists
|
||||
let importlib_namespace_package = stdlib_path.join("importlib");
|
||||
assert!(importlib_namespace_package.is_directory(&stdlib_path, &resolver));
|
||||
assert!(!importlib_namespace_package.is_regular_package(&stdlib_path, &resolver));
|
||||
|
|
@ -1143,7 +1174,7 @@ mod tests {
|
|||
None
|
||||
);
|
||||
|
||||
// ...As do submodules in the `importlib` namespace package:
|
||||
// Submodules in the `importlib` namespace package also now exist:
|
||||
let importlib_abc = importlib_namespace_package.join("abc.pyi");
|
||||
assert!(!importlib_abc.is_directory(&stdlib_path, &resolver));
|
||||
assert!(!importlib_abc.is_regular_package(&stdlib_path, &resolver));
|
||||
|
|
@ -1152,13 +1183,14 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn mocked_typeshed_nonexistent_namespace_stdlib_pkg_py39() {
|
||||
let (db, stdlib_path) = py39_stdlib_test_case();
|
||||
let resolver = ResolverState {
|
||||
db: &db,
|
||||
typeshed_versions: LazyTypeshedVersions::new(),
|
||||
target_version: TargetVersion::Py39,
|
||||
const TYPESHED: MockedTypeshed = MockedTypeshed {
|
||||
versions: "xml: 3.8-3.8",
|
||||
stdlib_files: &[("xml/etree.pyi", "")],
|
||||
};
|
||||
|
||||
let (db, stdlib_path) = py39_typeshed_test_case(TYPESHED);
|
||||
let resolver = ResolverState::new(&db, TargetVersion::Py39);
|
||||
|
||||
// The `xml` package no longer exists on py39:
|
||||
let xml_namespace_package = stdlib_path.join("xml");
|
||||
assert_eq!(xml_namespace_package.to_file(&stdlib_path, &resolver), None);
|
||||
|
|
|
|||
|
|
@ -385,28 +385,22 @@ impl PackageKind {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ruff_db::files::{system_path_to_file, File, FilePath};
|
||||
use ruff_db::system::DbWithTestSystem;
|
||||
use ruff_db::vendored::{VendoredPath, VendoredPathBuf};
|
||||
use ruff_db::Upcast;
|
||||
use ruff_db::system::{DbWithTestSystem, OsSystem, SystemPath};
|
||||
|
||||
use crate::db::tests::{create_resolver_builder, TestCase};
|
||||
use crate::db::tests::TestDb;
|
||||
use crate::module::ModuleKind;
|
||||
use crate::module_name::ModuleName;
|
||||
use crate::testing::{FileSpec, MockedTypeshed, TestCase, TestCaseBuilder};
|
||||
|
||||
use super::*;
|
||||
|
||||
fn setup_resolver_test() -> TestCase {
|
||||
create_resolver_builder().unwrap().build().unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn first_party_module() -> anyhow::Result<()> {
|
||||
let TestCase { mut db, src, .. } = setup_resolver_test();
|
||||
fn first_party_module() {
|
||||
let TestCase { db, src, .. } = TestCaseBuilder::new()
|
||||
.with_src_files(&[("foo.py", "print('Hello, world!')")])
|
||||
.build();
|
||||
|
||||
let foo_module_name = ModuleName::new_static("foo").unwrap();
|
||||
let foo_path = src.join("foo.py");
|
||||
db.write_file(&foo_path, "print('Hello, world!')")?;
|
||||
|
||||
let foo_module = resolve_module(&db, foo_module_name.clone()).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
|
|
@ -418,25 +412,26 @@ mod tests {
|
|||
assert_eq!(&src, &foo_module.search_path());
|
||||
assert_eq!(ModuleKind::Module, foo_module.kind());
|
||||
|
||||
assert_eq!(&foo_path, foo_module.file().path(&db));
|
||||
let expected_foo_path = src.join("foo.py");
|
||||
assert_eq!(&expected_foo_path, foo_module.file().path(&db));
|
||||
assert_eq!(
|
||||
Some(foo_module),
|
||||
path_to_module(&db, &FilePath::System(foo_path))
|
||||
path_to_module(&db, &FilePath::System(expected_foo_path))
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stdlib() {
|
||||
let TestCase {
|
||||
db,
|
||||
custom_typeshed,
|
||||
..
|
||||
} = setup_resolver_test();
|
||||
const TYPESHED: MockedTypeshed = MockedTypeshed {
|
||||
stdlib_files: &[("functools.pyi", "def update_wrapper(): ...")],
|
||||
versions: "functools: 3.8-",
|
||||
};
|
||||
|
||||
let TestCase { db, stdlib, .. } = TestCaseBuilder::new()
|
||||
.with_custom_typeshed(TYPESHED)
|
||||
.with_target_version(TargetVersion::Py38)
|
||||
.build();
|
||||
|
||||
let stdlib_dir =
|
||||
ModuleResolutionPathBuf::stdlib_from_custom_typeshed_root(&custom_typeshed).unwrap();
|
||||
let functools_module_name = ModuleName::new_static("functools").unwrap();
|
||||
let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap();
|
||||
|
||||
|
|
@ -445,16 +440,15 @@ mod tests {
|
|||
resolve_module(&db, functools_module_name).as_ref()
|
||||
);
|
||||
|
||||
assert_eq!(stdlib_dir, functools_module.search_path().to_path_buf());
|
||||
assert_eq!(&stdlib, &functools_module.search_path().to_path_buf());
|
||||
assert_eq!(ModuleKind::Module, functools_module.kind());
|
||||
|
||||
let expected_functools_path =
|
||||
FilePath::System(custom_typeshed.join("stdlib/functools.pyi"));
|
||||
let expected_functools_path = stdlib.join("functools.pyi");
|
||||
assert_eq!(&expected_functools_path, functools_module.file().path(&db));
|
||||
|
||||
assert_eq!(
|
||||
Some(functools_module),
|
||||
path_to_module(&db, &expected_functools_path)
|
||||
path_to_module(&db, &FilePath::System(expected_functools_path))
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -467,11 +461,29 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn stdlib_resolution_respects_versions_file_py38_existing_modules() {
|
||||
let TestCase {
|
||||
db,
|
||||
custom_typeshed,
|
||||
..
|
||||
} = setup_resolver_test();
|
||||
const VERSIONS: &str = "\
|
||||
asyncio: 3.8- # 'Regular' package on py38+
|
||||
asyncio.tasks: 3.9-3.11 # Submodule on py39+ only
|
||||
functools: 3.8- # Top-level single-file module
|
||||
xml: 3.8-3.8 # Namespace package on py38 only
|
||||
";
|
||||
|
||||
const STDLIB: &[FileSpec] = &[
|
||||
("asyncio/__init__.pyi", ""),
|
||||
("asyncio/tasks.pyi", ""),
|
||||
("functools.pyi", ""),
|
||||
("xml/etree.pyi", ""),
|
||||
];
|
||||
|
||||
const TYPESHED: MockedTypeshed = MockedTypeshed {
|
||||
stdlib_files: STDLIB,
|
||||
versions: VERSIONS,
|
||||
};
|
||||
|
||||
let TestCase { db, stdlib, .. } = TestCaseBuilder::new()
|
||||
.with_custom_typeshed(TYPESHED)
|
||||
.with_target_version(TargetVersion::Py38)
|
||||
.build();
|
||||
|
||||
let existing_modules = create_module_names(&["asyncio", "functools", "xml.etree"]);
|
||||
for module_name in existing_modules {
|
||||
|
|
@ -480,8 +492,7 @@ mod tests {
|
|||
});
|
||||
let search_path = resolved_module.search_path();
|
||||
assert_eq!(
|
||||
&custom_typeshed.join("stdlib"),
|
||||
&search_path,
|
||||
&stdlib, &search_path,
|
||||
"Search path for {module_name} was unexpectedly {search_path:?}"
|
||||
);
|
||||
assert!(
|
||||
|
|
@ -493,7 +504,32 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn stdlib_resolution_respects_versions_file_py38_nonexisting_modules() {
|
||||
let TestCase { db, .. } = setup_resolver_test();
|
||||
const VERSIONS: &str = "\
|
||||
asyncio: 3.8- # 'Regular' package on py38+
|
||||
asyncio.tasks: 3.9-3.11 # Submodule on py39+ only
|
||||
collections: 3.9- # 'Regular' package on py39+
|
||||
importlib: 3.9- # Namespace package on py39+
|
||||
xml: 3.8-3.8 # Namespace package on 3.8 only
|
||||
";
|
||||
|
||||
const STDLIB: &[FileSpec] = &[
|
||||
("collections/__init__.pyi", ""),
|
||||
("asyncio/__init__.pyi", ""),
|
||||
("asyncio/tasks.pyi", ""),
|
||||
("importlib/abc.pyi", ""),
|
||||
("xml/etree.pyi", ""),
|
||||
];
|
||||
|
||||
const TYPESHED: MockedTypeshed = MockedTypeshed {
|
||||
stdlib_files: STDLIB,
|
||||
versions: VERSIONS,
|
||||
};
|
||||
|
||||
let TestCase { db, .. } = TestCaseBuilder::new()
|
||||
.with_custom_typeshed(TYPESHED)
|
||||
.with_target_version(TargetVersion::Py38)
|
||||
.build();
|
||||
|
||||
let nonexisting_modules = create_module_names(&[
|
||||
"collections",
|
||||
"importlib",
|
||||
|
|
@ -501,6 +537,7 @@ mod tests {
|
|||
"xml",
|
||||
"asyncio.tasks",
|
||||
]);
|
||||
|
||||
for module_name in nonexisting_modules {
|
||||
assert!(
|
||||
resolve_module(&db, module_name.clone()).is_none(),
|
||||
|
|
@ -511,15 +548,31 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn stdlib_resolution_respects_versions_file_py39_existing_modules() {
|
||||
let TestCase {
|
||||
db,
|
||||
custom_typeshed,
|
||||
..
|
||||
} = create_resolver_builder()
|
||||
.unwrap()
|
||||
const VERSIONS: &str = "\
|
||||
asyncio: 3.8- # 'Regular' package on py38+
|
||||
asyncio.tasks: 3.9-3.11 # Submodule on py39+ only
|
||||
collections: 3.9- # 'Regular' package on py39+
|
||||
functools: 3.8- # Top-level single-file module
|
||||
importlib: 3.9- # Namespace package on py39+
|
||||
";
|
||||
|
||||
const STDLIB: &[FileSpec] = &[
|
||||
("asyncio/__init__.pyi", ""),
|
||||
("asyncio/tasks.pyi", ""),
|
||||
("collections/__init__.pyi", ""),
|
||||
("functools.pyi", ""),
|
||||
("importlib/abc.pyi", ""),
|
||||
];
|
||||
|
||||
const TYPESHED: MockedTypeshed = MockedTypeshed {
|
||||
stdlib_files: STDLIB,
|
||||
versions: VERSIONS,
|
||||
};
|
||||
|
||||
let TestCase { db, stdlib, .. } = TestCaseBuilder::new()
|
||||
.with_custom_typeshed(TYPESHED)
|
||||
.with_target_version(TargetVersion::Py39)
|
||||
.build()
|
||||
.unwrap();
|
||||
.build();
|
||||
|
||||
let existing_modules = create_module_names(&[
|
||||
"asyncio",
|
||||
|
|
@ -528,14 +581,14 @@ mod tests {
|
|||
"collections",
|
||||
"asyncio.tasks",
|
||||
]);
|
||||
|
||||
for module_name in existing_modules {
|
||||
let resolved_module = resolve_module(&db, module_name.clone()).unwrap_or_else(|| {
|
||||
panic!("Expected module {module_name} to exist in the mock stdlib")
|
||||
});
|
||||
let search_path = resolved_module.search_path();
|
||||
assert_eq!(
|
||||
&custom_typeshed.join("stdlib"),
|
||||
&search_path,
|
||||
&stdlib, &search_path,
|
||||
"Search path for {module_name} was unexpectedly {search_path:?}"
|
||||
);
|
||||
assert!(
|
||||
|
|
@ -546,11 +599,22 @@ mod tests {
|
|||
}
|
||||
#[test]
|
||||
fn stdlib_resolution_respects_versions_file_py39_nonexisting_modules() {
|
||||
let TestCase { db, .. } = create_resolver_builder()
|
||||
.unwrap()
|
||||
const VERSIONS: &str = "\
|
||||
importlib: 3.9- # Namespace package on py39+
|
||||
xml: 3.8-3.8 # Namespace package on 3.8 only
|
||||
";
|
||||
|
||||
const STDLIB: &[FileSpec] = &[("importlib/abc.pyi", ""), ("xml/etree.pyi", "")];
|
||||
|
||||
const TYPESHED: MockedTypeshed = MockedTypeshed {
|
||||
stdlib_files: STDLIB,
|
||||
versions: VERSIONS,
|
||||
};
|
||||
|
||||
let TestCase { db, .. } = TestCaseBuilder::new()
|
||||
.with_custom_typeshed(TYPESHED)
|
||||
.with_target_version(TargetVersion::Py39)
|
||||
.build()
|
||||
.unwrap();
|
||||
.build();
|
||||
|
||||
let nonexisting_modules = create_module_names(&["importlib", "xml", "xml.etree"]);
|
||||
for module_name in nonexisting_modules {
|
||||
|
|
@ -562,11 +626,19 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn first_party_precedence_over_stdlib() -> anyhow::Result<()> {
|
||||
let TestCase { mut db, src, .. } = setup_resolver_test();
|
||||
fn first_party_precedence_over_stdlib() {
|
||||
const SRC: &[FileSpec] = &[("functools.py", "def update_wrapper(): ...")];
|
||||
|
||||
let first_party_functools_path = src.join("functools.py");
|
||||
db.write_file(&first_party_functools_path, "def update_wrapper(): ...")?;
|
||||
const TYPESHED: MockedTypeshed = MockedTypeshed {
|
||||
stdlib_files: &[("functools.pyi", "def update_wrapper(): ...")],
|
||||
versions: "functools: 3.8-",
|
||||
};
|
||||
|
||||
let TestCase { db, src, .. } = TestCaseBuilder::new()
|
||||
.with_src_files(SRC)
|
||||
.with_custom_typeshed(TYPESHED)
|
||||
.with_target_version(TargetVersion::Py38)
|
||||
.build();
|
||||
|
||||
let functools_module_name = ModuleName::new_static("functools").unwrap();
|
||||
let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap();
|
||||
|
|
@ -577,49 +649,39 @@ mod tests {
|
|||
);
|
||||
assert_eq!(&src, &functools_module.search_path());
|
||||
assert_eq!(ModuleKind::Module, functools_module.kind());
|
||||
assert_eq!(
|
||||
&first_party_functools_path,
|
||||
functools_module.file().path(&db)
|
||||
);
|
||||
assert_eq!(&src.join("functools.py"), functools_module.file().path(&db));
|
||||
|
||||
assert_eq!(
|
||||
Some(functools_module),
|
||||
path_to_module(&db, &FilePath::System(first_party_functools_path))
|
||||
path_to_module(&db, &FilePath::System(src.join("functools.py")))
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stdlib_uses_vendored_typeshed_when_no_custom_typeshed_supplied() {
|
||||
let TestCase { db, .. } = create_resolver_builder()
|
||||
.unwrap()
|
||||
.with_vendored_stubs_used()
|
||||
.build()
|
||||
.unwrap();
|
||||
let TestCase { db, stdlib, .. } = TestCaseBuilder::new()
|
||||
.with_vendored_typeshed()
|
||||
.with_target_version(TargetVersion::default())
|
||||
.build();
|
||||
|
||||
let pydoc_data_topics_name = ModuleName::new_static("pydoc_data.topics").unwrap();
|
||||
let pydoc_data_topics = resolve_module(&db, pydoc_data_topics_name).unwrap();
|
||||
|
||||
assert_eq!("pydoc_data.topics", pydoc_data_topics.name());
|
||||
assert_eq!(pydoc_data_topics.search_path(), stdlib);
|
||||
assert_eq!(
|
||||
pydoc_data_topics.search_path(),
|
||||
VendoredPathBuf::from("stdlib")
|
||||
);
|
||||
assert_eq!(
|
||||
&pydoc_data_topics.file().path(db.upcast()),
|
||||
&VendoredPath::new("stdlib/pydoc_data/topics.pyi")
|
||||
pydoc_data_topics.file().path(&db),
|
||||
&stdlib.join("pydoc_data/topics.pyi")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_package() -> anyhow::Result<()> {
|
||||
let TestCase { src, mut db, .. } = setup_resolver_test();
|
||||
|
||||
let foo_dir = src.join("foo");
|
||||
let foo_path = foo_dir.join("__init__.py");
|
||||
|
||||
db.write_file(&foo_path, "print('Hello, world!')")?;
|
||||
fn resolve_package() {
|
||||
let TestCase { src, db, .. } = TestCaseBuilder::new()
|
||||
.with_src_files(&[("foo/__init__.py", "print('Hello, world!'")])
|
||||
.build();
|
||||
|
||||
let foo_path = src.join("foo/__init__.py");
|
||||
let foo_module = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap();
|
||||
|
||||
assert_eq!("foo", foo_module.name());
|
||||
|
|
@ -632,96 +694,84 @@ mod tests {
|
|||
);
|
||||
|
||||
// Resolving by directory doesn't resolve to the init file.
|
||||
assert_eq!(None, path_to_module(&db, &FilePath::System(foo_dir)));
|
||||
|
||||
Ok(())
|
||||
assert_eq!(
|
||||
None,
|
||||
path_to_module(&db, &FilePath::System(src.join("foo")))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn package_priority_over_module() -> anyhow::Result<()> {
|
||||
let TestCase { mut db, src, .. } = setup_resolver_test();
|
||||
fn package_priority_over_module() {
|
||||
const SRC: &[FileSpec] = &[
|
||||
("foo/__init__.py", "print('Hello, world!')"),
|
||||
("foo.py", "print('Hello, world!')"),
|
||||
];
|
||||
|
||||
let foo_dir = src.join("foo");
|
||||
let foo_init = foo_dir.join("__init__.py");
|
||||
|
||||
db.write_file(&foo_init, "print('Hello, world!')")?;
|
||||
|
||||
let foo_py = src.join("foo.py");
|
||||
db.write_file(&foo_py, "print('Hello, world!')")?;
|
||||
let TestCase { db, src, .. } = TestCaseBuilder::new().with_src_files(SRC).build();
|
||||
|
||||
let foo_module = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap();
|
||||
let foo_init_path = src.join("foo/__init__.py");
|
||||
|
||||
assert_eq!(&src, &foo_module.search_path());
|
||||
assert_eq!(&foo_init, foo_module.file().path(&db));
|
||||
assert_eq!(&foo_init_path, foo_module.file().path(&db));
|
||||
assert_eq!(ModuleKind::Package, foo_module.kind());
|
||||
|
||||
assert_eq!(
|
||||
Some(foo_module),
|
||||
path_to_module(&db, &FilePath::System(foo_init))
|
||||
path_to_module(&db, &FilePath::System(foo_init_path))
|
||||
);
|
||||
assert_eq!(
|
||||
None,
|
||||
path_to_module(&db, &FilePath::System(src.join("foo.py")))
|
||||
);
|
||||
assert_eq!(None, path_to_module(&db, &FilePath::System(foo_py)));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typing_stub_over_module() -> anyhow::Result<()> {
|
||||
let TestCase { mut db, src, .. } = setup_resolver_test();
|
||||
fn typing_stub_over_module() {
|
||||
const SRC: &[FileSpec] = &[("foo.py", "print('Hello, world!')"), ("foo.pyi", "x: int")];
|
||||
|
||||
let foo_stub = src.join("foo.pyi");
|
||||
let foo_py = src.join("foo.py");
|
||||
db.write_files([(&foo_stub, "x: int"), (&foo_py, "print('Hello, world!')")])?;
|
||||
let TestCase { db, src, .. } = TestCaseBuilder::new().with_src_files(SRC).build();
|
||||
|
||||
let foo = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap();
|
||||
let foo_stub = src.join("foo.pyi");
|
||||
|
||||
assert_eq!(&src, &foo.search_path());
|
||||
assert_eq!(&foo_stub, foo.file().path(&db));
|
||||
|
||||
assert_eq!(Some(foo), path_to_module(&db, &FilePath::System(foo_stub)));
|
||||
assert_eq!(None, path_to_module(&db, &FilePath::System(foo_py)));
|
||||
|
||||
Ok(())
|
||||
assert_eq!(
|
||||
None,
|
||||
path_to_module(&db, &FilePath::System(src.join("foo.py")))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sub_packages() -> anyhow::Result<()> {
|
||||
let TestCase { mut db, src, .. } = setup_resolver_test();
|
||||
fn sub_packages() {
|
||||
const SRC: &[FileSpec] = &[
|
||||
("foo/__init__.py", ""),
|
||||
("foo/bar/__init__.py", ""),
|
||||
("foo/bar/baz.py", "print('Hello, world!)'"),
|
||||
];
|
||||
|
||||
let foo = src.join("foo");
|
||||
let bar = foo.join("bar");
|
||||
let baz = bar.join("baz.py");
|
||||
|
||||
db.write_files([
|
||||
(&foo.join("__init__.py"), ""),
|
||||
(&bar.join("__init__.py"), ""),
|
||||
(&baz, "print('Hello, world!')"),
|
||||
])?;
|
||||
let TestCase { db, src, .. } = TestCaseBuilder::new().with_src_files(SRC).build();
|
||||
|
||||
let baz_module =
|
||||
resolve_module(&db, ModuleName::new_static("foo.bar.baz").unwrap()).unwrap();
|
||||
let baz_path = src.join("foo/bar/baz.py");
|
||||
|
||||
assert_eq!(&src, &baz_module.search_path());
|
||||
assert_eq!(&baz, baz_module.file().path(&db));
|
||||
assert_eq!(&baz_path, baz_module.file().path(&db));
|
||||
|
||||
assert_eq!(
|
||||
Some(baz_module),
|
||||
path_to_module(&db, &FilePath::System(baz))
|
||||
path_to_module(&db, &FilePath::System(baz_path))
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn namespace_package() -> anyhow::Result<()> {
|
||||
let TestCase {
|
||||
mut db,
|
||||
src,
|
||||
site_packages,
|
||||
..
|
||||
} = setup_resolver_test();
|
||||
|
||||
fn namespace_package() {
|
||||
// From [PEP420](https://peps.python.org/pep-0420/#nested-namespace-packages).
|
||||
// But uses `src` for `project1` and `site_packages2` for `project2`.
|
||||
// But uses `src` for `project1` and `site-packages` for `project2`.
|
||||
// ```
|
||||
// src
|
||||
// parent
|
||||
|
|
@ -732,47 +782,33 @@ mod tests {
|
|||
// child
|
||||
// two.py
|
||||
// ```
|
||||
|
||||
let parent1 = src.join("parent");
|
||||
let child1 = parent1.join("child");
|
||||
let one = child1.join("one.py");
|
||||
|
||||
let parent2 = site_packages.join("parent");
|
||||
let child2 = parent2.join("child");
|
||||
let two = child2.join("two.py");
|
||||
|
||||
db.write_files([
|
||||
(&one, "print('Hello, world!')"),
|
||||
(&two, "print('Hello, world!')"),
|
||||
])?;
|
||||
|
||||
let one_module =
|
||||
resolve_module(&db, ModuleName::new_static("parent.child.one").unwrap()).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
Some(one_module),
|
||||
path_to_module(&db, &FilePath::System(one))
|
||||
);
|
||||
|
||||
let two_module =
|
||||
resolve_module(&db, ModuleName::new_static("parent.child.two").unwrap()).unwrap();
|
||||
assert_eq!(
|
||||
Some(two_module),
|
||||
path_to_module(&db, &FilePath::System(two))
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regular_package_in_namespace_package() -> anyhow::Result<()> {
|
||||
let TestCase {
|
||||
mut db,
|
||||
db,
|
||||
src,
|
||||
site_packages,
|
||||
..
|
||||
} = setup_resolver_test();
|
||||
} = TestCaseBuilder::new()
|
||||
.with_src_files(&[("parent/child/one.py", "print('Hello, world!')")])
|
||||
.with_site_packages_files(&[("parent/child/two.py", "print('Hello, world!')")])
|
||||
.build();
|
||||
|
||||
let one_module_name = ModuleName::new_static("parent.child.one").unwrap();
|
||||
let one_module_path = FilePath::System(src.join("parent/child/one.py"));
|
||||
assert_eq!(
|
||||
resolve_module(&db, one_module_name),
|
||||
path_to_module(&db, &one_module_path)
|
||||
);
|
||||
|
||||
let two_module_name = ModuleName::new_static("parent.child.two").unwrap();
|
||||
let two_module_path = FilePath::System(site_packages.join("parent/child/two.py"));
|
||||
assert_eq!(
|
||||
resolve_module(&db, two_module_name),
|
||||
path_to_module(&db, &two_module_path)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regular_package_in_namespace_package() {
|
||||
// Adopted test case from the [PEP420 examples](https://peps.python.org/pep-0420/#nested-namespace-packages).
|
||||
// The `src/parent/child` package is a regular package. Therefore, `site_packages/parent/child/two.py` should not be resolved.
|
||||
// ```
|
||||
|
|
@ -785,90 +821,69 @@ mod tests {
|
|||
// child
|
||||
// two.py
|
||||
// ```
|
||||
const SRC: &[FileSpec] = &[
|
||||
("parent/child/__init__.py", "print('Hello, world!')"),
|
||||
("parent/child/one.py", "print('Hello, world!')"),
|
||||
];
|
||||
|
||||
let parent1 = src.join("parent");
|
||||
let child1 = parent1.join("child");
|
||||
let one = child1.join("one.py");
|
||||
const SITE_PACKAGES: &[FileSpec] = &[("parent/child/two.py", "print('Hello, world!')")];
|
||||
|
||||
let parent2 = site_packages.join("parent");
|
||||
let child2 = parent2.join("child");
|
||||
let two = child2.join("two.py");
|
||||
let TestCase { db, src, .. } = TestCaseBuilder::new()
|
||||
.with_src_files(SRC)
|
||||
.with_site_packages_files(SITE_PACKAGES)
|
||||
.build();
|
||||
|
||||
db.write_files([
|
||||
(&child1.join("__init__.py"), "print('Hello, world!')"),
|
||||
(&one, "print('Hello, world!')"),
|
||||
(&two, "print('Hello, world!')"),
|
||||
])?;
|
||||
|
||||
let one_module =
|
||||
resolve_module(&db, ModuleName::new_static("parent.child.one").unwrap()).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
Some(one_module),
|
||||
path_to_module(&db, &FilePath::System(one))
|
||||
);
|
||||
let one_module_path = FilePath::System(src.join("parent/child/one.py"));
|
||||
let one_module_name =
|
||||
resolve_module(&db, ModuleName::new_static("parent.child.one").unwrap());
|
||||
assert_eq!(one_module_name, path_to_module(&db, &one_module_path));
|
||||
|
||||
assert_eq!(
|
||||
None,
|
||||
resolve_module(&db, ModuleName::new_static("parent.child.two").unwrap())
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_search_path_priority() -> anyhow::Result<()> {
|
||||
fn module_search_path_priority() {
|
||||
let TestCase {
|
||||
mut db,
|
||||
db,
|
||||
src,
|
||||
site_packages,
|
||||
..
|
||||
} = setup_resolver_test();
|
||||
|
||||
let foo_src = src.join("foo.py");
|
||||
let foo_site_packages = site_packages.join("foo.py");
|
||||
|
||||
db.write_files([(&foo_src, ""), (&foo_site_packages, "")])?;
|
||||
} = TestCaseBuilder::new()
|
||||
.with_src_files(&[("foo.py", "")])
|
||||
.with_site_packages_files(&[("foo.py", "")])
|
||||
.build();
|
||||
|
||||
let foo_module = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap();
|
||||
let foo_src_path = src.join("foo.py");
|
||||
|
||||
assert_eq!(&src, &foo_module.search_path());
|
||||
assert_eq!(&foo_src, foo_module.file().path(&db));
|
||||
|
||||
assert_eq!(&foo_src_path, foo_module.file().path(&db));
|
||||
assert_eq!(
|
||||
Some(foo_module),
|
||||
path_to_module(&db, &FilePath::System(foo_src))
|
||||
);
|
||||
assert_eq!(
|
||||
None,
|
||||
path_to_module(&db, &FilePath::System(foo_site_packages))
|
||||
path_to_module(&db, &FilePath::System(foo_src_path))
|
||||
);
|
||||
|
||||
Ok(())
|
||||
assert_eq!(
|
||||
None,
|
||||
path_to_module(&db, &FilePath::System(site_packages.join("foo.py")))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_family = "unix")]
|
||||
fn symlink() -> anyhow::Result<()> {
|
||||
use ruff_db::system::{OsSystem, SystemPath};
|
||||
|
||||
fn make_relative(path: &SystemPath) -> &SystemPath {
|
||||
path.strip_prefix("/").unwrap_or(path)
|
||||
}
|
||||
|
||||
let TestCase {
|
||||
mut db,
|
||||
src,
|
||||
site_packages,
|
||||
custom_typeshed,
|
||||
} = setup_resolver_test();
|
||||
let mut db = TestDb::new();
|
||||
|
||||
let temp_dir = tempfile::tempdir()?;
|
||||
let root = SystemPath::from_std_path(temp_dir.path()).unwrap();
|
||||
db.use_os_system(OsSystem::new(root));
|
||||
|
||||
let src = root.join(make_relative(&src));
|
||||
let site_packages = root.join(make_relative(&site_packages));
|
||||
let custom_typeshed = root.join(make_relative(&custom_typeshed));
|
||||
let src = root.join("src");
|
||||
let site_packages = root.join("site-packages");
|
||||
let custom_typeshed = root.join("typeshed");
|
||||
|
||||
let foo = src.join("foo.py");
|
||||
let bar = src.join("bar.py");
|
||||
|
|
@ -919,23 +934,22 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn deleting_an_unrelated_file_doesnt_change_module_resolution() -> anyhow::Result<()> {
|
||||
let TestCase { mut db, src, .. } = setup_resolver_test();
|
||||
|
||||
let foo_path = src.join("foo.py");
|
||||
let bar_path = src.join("bar.py");
|
||||
|
||||
db.write_files([(&foo_path, "x = 1"), (&bar_path, "y = 2")])?;
|
||||
fn deleting_an_unrelated_file_doesnt_change_module_resolution() {
|
||||
let TestCase { mut db, src, .. } = TestCaseBuilder::new()
|
||||
.with_src_files(&[("foo.py", "x = 1"), ("bar.py", "x = 2")])
|
||||
.with_target_version(TargetVersion::Py38)
|
||||
.build();
|
||||
|
||||
let foo_module_name = ModuleName::new_static("foo").unwrap();
|
||||
let foo_module = resolve_module(&db, foo_module_name.clone()).unwrap();
|
||||
|
||||
let bar_path = src.join("bar.py");
|
||||
let bar = system_path_to_file(&db, &bar_path).expect("bar.py to exist");
|
||||
|
||||
db.clear_salsa_events();
|
||||
|
||||
// Delete `bar.py`
|
||||
db.memory_file_system().remove_file(&bar_path)?;
|
||||
db.memory_file_system().remove_file(&bar_path).unwrap();
|
||||
bar.touch(&mut db);
|
||||
|
||||
// Re-query the foo module. The foo module should still be cached because `bar.py` isn't relevant
|
||||
|
|
@ -949,14 +963,12 @@ mod tests {
|
|||
.any(|event| { matches!(event.kind, salsa::EventKind::WillExecute { .. }) }));
|
||||
|
||||
assert_eq!(Some(foo_module), foo_module2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adding_a_file_on_which_the_module_resolution_depends_on_invalidates_the_query(
|
||||
) -> anyhow::Result<()> {
|
||||
let TestCase { mut db, src, .. } = setup_resolver_test();
|
||||
let TestCase { mut db, src, .. } = TestCaseBuilder::new().build();
|
||||
let foo_path = src.join("foo.py");
|
||||
|
||||
let foo_module_name = ModuleName::new_static("foo").unwrap();
|
||||
|
|
@ -976,14 +988,13 @@ mod tests {
|
|||
#[test]
|
||||
fn removing_a_file_that_the_module_resolution_depends_on_invalidates_the_query(
|
||||
) -> anyhow::Result<()> {
|
||||
let TestCase { mut db, src, .. } = setup_resolver_test();
|
||||
let foo_path = src.join("foo.py");
|
||||
let foo_init_path = src.join("foo/__init__.py");
|
||||
const SRC: &[FileSpec] = &[("foo.py", "x = 1"), ("foo/__init__.py", "x = 2")];
|
||||
|
||||
db.write_files([(&foo_path, "x = 1"), (&foo_init_path, "x = 2")])?;
|
||||
let TestCase { mut db, src, .. } = TestCaseBuilder::new().with_src_files(SRC).build();
|
||||
|
||||
let foo_module_name = ModuleName::new_static("foo").unwrap();
|
||||
let foo_module = resolve_module(&db, foo_module_name.clone()).expect("foo module to exist");
|
||||
let foo_init_path = src.join("foo/__init__.py");
|
||||
|
||||
assert_eq!(&foo_init_path, foo_module.file().path(&db));
|
||||
|
||||
|
|
@ -994,7 +1005,7 @@ mod tests {
|
|||
File::touch_path(&mut db, &FilePath::System(foo_init_path));
|
||||
|
||||
let foo_module = resolve_module(&db, foo_module_name).expect("Foo module to resolve");
|
||||
assert_eq!(&foo_path, foo_module.file().path(&db));
|
||||
assert_eq!(&src.join("foo.py"), foo_module.file().path(&db));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,290 @@
|
|||
use ruff_db::system::{DbWithTestSystem, SystemPath, SystemPathBuf};
|
||||
use ruff_db::vendored::VendoredPathBuf;
|
||||
|
||||
use crate::db::tests::TestDb;
|
||||
use crate::resolver::{set_module_resolution_settings, RawModuleResolutionSettings};
|
||||
use crate::supported_py_version::TargetVersion;
|
||||
|
||||
/// A test case for the module resolver.
|
||||
///
|
||||
/// You generally shouldn't construct instances of this struct directly;
|
||||
/// instead, use the [`TestCaseBuilder`].
|
||||
pub(crate) struct TestCase<T> {
|
||||
pub(crate) db: TestDb,
|
||||
pub(crate) src: SystemPathBuf,
|
||||
pub(crate) stdlib: T,
|
||||
pub(crate) site_packages: SystemPathBuf,
|
||||
pub(crate) target_version: TargetVersion,
|
||||
}
|
||||
|
||||
/// A `(file_name, file_contents)` tuple
|
||||
pub(crate) type FileSpec = (&'static str, &'static str);
|
||||
|
||||
/// Specification for a typeshed mock to be created as part of a test
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub(crate) struct MockedTypeshed {
|
||||
/// The stdlib files to be created in the typeshed mock
|
||||
pub(crate) stdlib_files: &'static [FileSpec],
|
||||
|
||||
/// The contents of the `stdlib/VERSIONS` file
|
||||
/// to be created in the typeshed mock
|
||||
pub(crate) versions: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct VendoredTypeshed;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct UnspecifiedTypeshed;
|
||||
|
||||
/// A builder for a module-resolver test case.
|
||||
///
|
||||
/// The builder takes care of creating a [`TestDb`]
|
||||
/// instance, applying the module resolver settings,
|
||||
/// and creating mock directories for the stdlib, `site-packages`,
|
||||
/// first-party code, etc.
|
||||
///
|
||||
/// For simple tests that do not involve typeshed,
|
||||
/// test cases can be created as follows:
|
||||
///
|
||||
/// ```rs
|
||||
/// let test_case = TestCaseBuilder::new()
|
||||
/// .with_src_files(...)
|
||||
/// .build();
|
||||
///
|
||||
/// let test_case2 = TestCaseBuilder::new()
|
||||
/// .with_site_packages_files(...)
|
||||
/// .build();
|
||||
/// ```
|
||||
///
|
||||
/// Any tests can specify the target Python version that should be used
|
||||
/// in the module resolver settings:
|
||||
///
|
||||
/// ```rs
|
||||
/// let test_case = TestCaseBuilder::new()
|
||||
/// .with_src_files(...)
|
||||
/// .with_target_version(...)
|
||||
/// .build();
|
||||
/// ```
|
||||
///
|
||||
/// For tests checking that standard-library module resolution is working
|
||||
/// correctly, you should usually create a [`MockedTypeshed`] instance
|
||||
/// and pass it to the [`TestCaseBuilder::with_custom_typeshed`] method.
|
||||
/// If you need to check something that involves the vendored typeshed stubs
|
||||
/// we include as part of the binary, you can instead use the
|
||||
/// [`TestCaseBuilder::with_vendored_typeshed`] method.
|
||||
/// For either of these, you should almost always try to be explicit
|
||||
/// about the Python version you want to be specified in the module-resolver
|
||||
/// settings for the test:
|
||||
///
|
||||
/// ```rs
|
||||
/// const TYPESHED = MockedTypeshed { ... };
|
||||
///
|
||||
/// let test_case = resolver_test_case()
|
||||
/// .with_custom_typeshed(TYPESHED)
|
||||
/// .with_target_version(...)
|
||||
/// .build();
|
||||
///
|
||||
/// let test_case2 = resolver_test_case()
|
||||
/// .with_vendored_typeshed()
|
||||
/// .with_target_version(...)
|
||||
/// .build();
|
||||
/// ```
|
||||
///
|
||||
/// If you have not called one of those options, the `stdlib` field
|
||||
/// on the [`TestCase`] instance created from `.build()` will be set
|
||||
/// to `()`.
|
||||
pub(crate) struct TestCaseBuilder<T> {
|
||||
typeshed_option: T,
|
||||
target_version: TargetVersion,
|
||||
first_party_files: Vec<FileSpec>,
|
||||
site_packages_files: Vec<FileSpec>,
|
||||
}
|
||||
|
||||
impl<T> TestCaseBuilder<T> {
|
||||
/// Specify files to be created in the `src` mock directory
|
||||
pub(crate) fn with_src_files(mut self, files: &[FileSpec]) -> Self {
|
||||
self.first_party_files.extend(files.iter().copied());
|
||||
self
|
||||
}
|
||||
|
||||
/// Specify files to be created in the `site-packages` mock directory
|
||||
pub(crate) fn with_site_packages_files(mut self, files: &[FileSpec]) -> Self {
|
||||
self.site_packages_files.extend(files.iter().copied());
|
||||
self
|
||||
}
|
||||
|
||||
/// Specify the target Python version the module resolver should assume
|
||||
pub(crate) fn with_target_version(mut self, target_version: TargetVersion) -> Self {
|
||||
self.target_version = target_version;
|
||||
self
|
||||
}
|
||||
|
||||
fn write_mock_directory(
|
||||
db: &mut TestDb,
|
||||
location: impl AsRef<SystemPath>,
|
||||
files: impl IntoIterator<Item = FileSpec>,
|
||||
) -> SystemPathBuf {
|
||||
let root = location.as_ref().to_path_buf();
|
||||
db.write_files(
|
||||
files
|
||||
.into_iter()
|
||||
.map(|(relative_path, contents)| (root.join(relative_path), contents)),
|
||||
)
|
||||
.unwrap();
|
||||
root
|
||||
}
|
||||
}
|
||||
|
||||
impl TestCaseBuilder<UnspecifiedTypeshed> {
|
||||
pub(crate) fn new() -> TestCaseBuilder<UnspecifiedTypeshed> {
|
||||
Self {
|
||||
typeshed_option: UnspecifiedTypeshed,
|
||||
target_version: TargetVersion::default(),
|
||||
first_party_files: vec![],
|
||||
site_packages_files: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Use the vendored stdlib stubs included in the Ruff binary for this test case
|
||||
pub(crate) fn with_vendored_typeshed(self) -> TestCaseBuilder<VendoredTypeshed> {
|
||||
let TestCaseBuilder {
|
||||
typeshed_option: _,
|
||||
target_version,
|
||||
first_party_files,
|
||||
site_packages_files,
|
||||
} = self;
|
||||
TestCaseBuilder {
|
||||
typeshed_option: VendoredTypeshed,
|
||||
target_version,
|
||||
first_party_files,
|
||||
site_packages_files,
|
||||
}
|
||||
}
|
||||
|
||||
/// Use a mock typeshed directory for this test case
|
||||
pub(crate) fn with_custom_typeshed(
|
||||
self,
|
||||
typeshed: MockedTypeshed,
|
||||
) -> TestCaseBuilder<MockedTypeshed> {
|
||||
let TestCaseBuilder {
|
||||
typeshed_option: _,
|
||||
target_version,
|
||||
first_party_files,
|
||||
site_packages_files,
|
||||
} = self;
|
||||
TestCaseBuilder {
|
||||
typeshed_option: typeshed,
|
||||
target_version,
|
||||
first_party_files,
|
||||
site_packages_files,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn build(self) -> TestCase<()> {
|
||||
let TestCase {
|
||||
db,
|
||||
src,
|
||||
stdlib: _,
|
||||
site_packages,
|
||||
target_version,
|
||||
} = self.with_custom_typeshed(MockedTypeshed::default()).build();
|
||||
TestCase {
|
||||
db,
|
||||
src,
|
||||
stdlib: (),
|
||||
site_packages,
|
||||
target_version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TestCaseBuilder<MockedTypeshed> {
|
||||
pub(crate) fn build(self) -> TestCase<SystemPathBuf> {
|
||||
let TestCaseBuilder {
|
||||
typeshed_option,
|
||||
target_version,
|
||||
first_party_files,
|
||||
site_packages_files,
|
||||
} = self;
|
||||
|
||||
let mut db = TestDb::new();
|
||||
|
||||
let site_packages =
|
||||
Self::write_mock_directory(&mut db, "/site-packages", site_packages_files);
|
||||
let src = Self::write_mock_directory(&mut db, "/src", first_party_files);
|
||||
let typeshed = Self::build_typeshed_mock(&mut db, &typeshed_option);
|
||||
|
||||
set_module_resolution_settings(
|
||||
&mut db,
|
||||
RawModuleResolutionSettings {
|
||||
target_version,
|
||||
extra_paths: vec![],
|
||||
workspace_root: src.clone(),
|
||||
custom_typeshed: Some(typeshed.clone()),
|
||||
site_packages: Some(site_packages.clone()),
|
||||
},
|
||||
);
|
||||
|
||||
TestCase {
|
||||
db,
|
||||
src,
|
||||
stdlib: typeshed.join("stdlib"),
|
||||
site_packages,
|
||||
target_version,
|
||||
}
|
||||
}
|
||||
|
||||
fn build_typeshed_mock(db: &mut TestDb, typeshed_to_build: &MockedTypeshed) -> SystemPathBuf {
|
||||
let typeshed = SystemPathBuf::from("/typeshed");
|
||||
let MockedTypeshed {
|
||||
stdlib_files,
|
||||
versions,
|
||||
} = typeshed_to_build;
|
||||
Self::write_mock_directory(
|
||||
db,
|
||||
typeshed.join("stdlib"),
|
||||
stdlib_files
|
||||
.iter()
|
||||
.copied()
|
||||
.chain(std::iter::once(("VERSIONS", *versions))),
|
||||
);
|
||||
typeshed
|
||||
}
|
||||
}
|
||||
|
||||
impl TestCaseBuilder<VendoredTypeshed> {
|
||||
pub(crate) fn build(self) -> TestCase<VendoredPathBuf> {
|
||||
let TestCaseBuilder {
|
||||
typeshed_option: VendoredTypeshed,
|
||||
target_version,
|
||||
first_party_files,
|
||||
site_packages_files,
|
||||
} = self;
|
||||
|
||||
let mut db = TestDb::new();
|
||||
|
||||
let site_packages =
|
||||
Self::write_mock_directory(&mut db, "/site-packages", site_packages_files);
|
||||
let src = Self::write_mock_directory(&mut db, "/src", first_party_files);
|
||||
|
||||
set_module_resolution_settings(
|
||||
&mut db,
|
||||
RawModuleResolutionSettings {
|
||||
target_version,
|
||||
extra_paths: vec![],
|
||||
workspace_root: src.clone(),
|
||||
custom_typeshed: None,
|
||||
site_packages: Some(site_packages.clone()),
|
||||
},
|
||||
);
|
||||
|
||||
TestCase {
|
||||
db,
|
||||
src,
|
||||
stdlib: VendoredPathBuf::from("stdlib"),
|
||||
site_packages,
|
||||
target_version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -131,7 +131,6 @@ pub enum TypeshedVersionsParseErrorKind {
|
|||
version: String,
|
||||
err: std::num::ParseIntError,
|
||||
},
|
||||
EmptyVersionsFile,
|
||||
}
|
||||
|
||||
impl fmt::Display for TypeshedVersionsParseErrorKind {
|
||||
|
|
@ -160,7 +159,6 @@ impl fmt::Display for TypeshedVersionsParseErrorKind {
|
|||
f,
|
||||
"Failed to convert '{version}' to a pair of integers due to {err}",
|
||||
),
|
||||
Self::EmptyVersionsFile => f.write_str("Versions file was empty!"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -307,14 +305,7 @@ impl FromStr for TypeshedVersions {
|
|||
};
|
||||
}
|
||||
|
||||
if map.is_empty() {
|
||||
Err(TypeshedVersionsParseError {
|
||||
line_number: None,
|
||||
reason: TypeshedVersionsParseErrorKind::EmptyVersionsFile,
|
||||
})
|
||||
} else {
|
||||
Ok(Self(map))
|
||||
}
|
||||
Ok(Self(map))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -685,31 +676,6 @@ foo: 3.8- # trailing comment
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_empty_versions_file() {
|
||||
assert_eq!(
|
||||
TypeshedVersions::from_str(""),
|
||||
Err(TypeshedVersionsParseError {
|
||||
line_number: None,
|
||||
reason: TypeshedVersionsParseErrorKind::EmptyVersionsFile
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
TypeshedVersions::from_str(" "),
|
||||
Err(TypeshedVersionsParseError {
|
||||
line_number: None,
|
||||
reason: TypeshedVersionsParseErrorKind::EmptyVersionsFile
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
TypeshedVersions::from_str(" \n \n \n "),
|
||||
Err(TypeshedVersionsParseError {
|
||||
line_number: None,
|
||||
reason: TypeshedVersionsParseErrorKind::EmptyVersionsFile
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_huge_versions_file() {
|
||||
let offset = 100;
|
||||
|
|
|
|||
Loading…
Reference in New Issue