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 salsa::DebugWithDb;
|
||||||
|
|
||||||
use ruff_db::files::Files;
|
use ruff_db::files::Files;
|
||||||
use ruff_db::system::{
|
use ruff_db::system::{DbWithTestSystem, TestSystem};
|
||||||
DbWithTestSystem, MemoryFileSystem, SystemPath, SystemPathBuf, TestSystem,
|
|
||||||
};
|
|
||||||
use ruff_db::vendored::VendoredFileSystem;
|
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 crate::vendored_typeshed_stubs;
|
||||||
|
|
||||||
use super::*;
|
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 supported_py_version;
|
||||||
mod typeshed;
|
mod typeshed;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod testing;
|
||||||
|
|
||||||
pub use db::{Db, Jar};
|
pub use db::{Db, Jar};
|
||||||
pub use module::{Module, ModuleKind};
|
pub use module::{Module, ModuleKind};
|
||||||
pub use module_name::ModuleName;
|
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)]
|
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
||||||
enum ModuleResolutionPathRefInner<'a> {
|
enum ModuleResolutionPathRefInner<'a> {
|
||||||
Extra(&'a SystemPath),
|
Extra(&'a SystemPath),
|
||||||
|
|
@ -643,9 +655,9 @@ impl PartialEq<ModuleResolutionPathRef<'_>> for VendoredPathBuf {
|
||||||
mod tests {
|
mod tests {
|
||||||
use insta::assert_debug_snapshot;
|
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::supported_py_version::TargetVersion;
|
||||||
use crate::typeshed::LazyTypeshedVersions;
|
use crate::testing::{FileSpec, MockedTypeshed, TestCase, TestCaseBuilder};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
|
@ -943,26 +955,41 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn py38_stdlib_test_case() -> (TestDb, ModuleResolutionPathBuf) {
|
fn typeshed_test_case(
|
||||||
let TestCase {
|
typeshed: MockedTypeshed,
|
||||||
db,
|
target_version: TargetVersion,
|
||||||
custom_typeshed,
|
) -> (TestDb, ModuleResolutionPathBuf) {
|
||||||
..
|
let TestCase { db, stdlib, .. } = TestCaseBuilder::new()
|
||||||
} = create_resolver_builder().unwrap().build().unwrap();
|
.with_custom_typeshed(typeshed)
|
||||||
let stdlib_module_path =
|
.with_target_version(target_version)
|
||||||
ModuleResolutionPathBuf::stdlib_from_custom_typeshed_root(&custom_typeshed).unwrap();
|
.build();
|
||||||
(db, stdlib_module_path)
|
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]
|
#[test]
|
||||||
fn mocked_typeshed_existing_regular_stdlib_pkg_py38() {
|
fn mocked_typeshed_existing_regular_stdlib_pkg_py38() {
|
||||||
let (db, stdlib_path) = py38_stdlib_test_case();
|
const VERSIONS: &str = "\
|
||||||
let resolver = ResolverState {
|
asyncio: 3.8-
|
||||||
db: &db,
|
asyncio.tasks: 3.9-3.11
|
||||||
typeshed_versions: LazyTypeshedVersions::new(),
|
";
|
||||||
target_version: TargetVersion::Py38,
|
|
||||||
|
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");
|
let asyncio_regular_package = stdlib_path.join("asyncio");
|
||||||
assert!(asyncio_regular_package.is_directory(&stdlib_path, &resolver));
|
assert!(asyncio_regular_package.is_directory(&stdlib_path, &resolver));
|
||||||
assert!(asyncio_regular_package.is_regular_package(&stdlib_path, &resolver));
|
assert!(asyncio_regular_package.is_regular_package(&stdlib_path, &resolver));
|
||||||
|
|
@ -986,13 +1013,14 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn mocked_typeshed_existing_namespace_stdlib_pkg_py38() {
|
fn mocked_typeshed_existing_namespace_stdlib_pkg_py38() {
|
||||||
let (db, stdlib_path) = py38_stdlib_test_case();
|
const TYPESHED: MockedTypeshed = MockedTypeshed {
|
||||||
let resolver = ResolverState {
|
versions: "xml: 3.8-3.8",
|
||||||
db: &db,
|
stdlib_files: &[("xml/etree.pyi", "")],
|
||||||
typeshed_versions: LazyTypeshedVersions::new(),
|
|
||||||
target_version: TargetVersion::Py38,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let (db, stdlib_path) = py38_typeshed_test_case(TYPESHED);
|
||||||
|
let resolver = ResolverState::new(&db, TargetVersion::Py38);
|
||||||
|
|
||||||
let xml_namespace_package = stdlib_path.join("xml");
|
let xml_namespace_package = stdlib_path.join("xml");
|
||||||
assert!(xml_namespace_package.is_directory(&stdlib_path, &resolver));
|
assert!(xml_namespace_package.is_directory(&stdlib_path, &resolver));
|
||||||
// Paths to directories don't resolve to VfsFiles
|
// Paths to directories don't resolve to VfsFiles
|
||||||
|
|
@ -1007,13 +1035,14 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn mocked_typeshed_single_file_stdlib_module_py38() {
|
fn mocked_typeshed_single_file_stdlib_module_py38() {
|
||||||
let (db, stdlib_path) = py38_stdlib_test_case();
|
const TYPESHED: MockedTypeshed = MockedTypeshed {
|
||||||
let resolver = ResolverState {
|
versions: "functools: 3.8-",
|
||||||
db: &db,
|
stdlib_files: &[("functools.pyi", "")],
|
||||||
typeshed_versions: LazyTypeshedVersions::new(),
|
|
||||||
target_version: TargetVersion::Py38,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let (db, stdlib_path) = py38_typeshed_test_case(TYPESHED);
|
||||||
|
let resolver = ResolverState::new(&db, TargetVersion::Py38);
|
||||||
|
|
||||||
let functools_module = stdlib_path.join("functools.pyi");
|
let functools_module = stdlib_path.join("functools.pyi");
|
||||||
assert!(functools_module.to_file(&stdlib_path, &resolver).is_some());
|
assert!(functools_module.to_file(&stdlib_path, &resolver).is_some());
|
||||||
assert!(!functools_module.is_directory(&stdlib_path, &resolver));
|
assert!(!functools_module.is_directory(&stdlib_path, &resolver));
|
||||||
|
|
@ -1022,13 +1051,14 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn mocked_typeshed_nonexistent_regular_stdlib_pkg_py38() {
|
fn mocked_typeshed_nonexistent_regular_stdlib_pkg_py38() {
|
||||||
let (db, stdlib_path) = py38_stdlib_test_case();
|
const TYPESHED: MockedTypeshed = MockedTypeshed {
|
||||||
let resolver = ResolverState {
|
versions: "collections: 3.9-",
|
||||||
db: &db,
|
stdlib_files: &[("collections/__init__.pyi", "")],
|
||||||
typeshed_versions: LazyTypeshedVersions::new(),
|
|
||||||
target_version: TargetVersion::Py38,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let (db, stdlib_path) = py38_typeshed_test_case(TYPESHED);
|
||||||
|
let resolver = ResolverState::new(&db, TargetVersion::Py38);
|
||||||
|
|
||||||
let collections_regular_package = stdlib_path.join("collections");
|
let collections_regular_package = stdlib_path.join("collections");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
collections_regular_package.to_file(&stdlib_path, &resolver),
|
collections_regular_package.to_file(&stdlib_path, &resolver),
|
||||||
|
|
@ -1040,13 +1070,14 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn mocked_typeshed_nonexistent_namespace_stdlib_pkg_py38() {
|
fn mocked_typeshed_nonexistent_namespace_stdlib_pkg_py38() {
|
||||||
let (db, stdlib_path) = py38_stdlib_test_case();
|
const TYPESHED: MockedTypeshed = MockedTypeshed {
|
||||||
let resolver = ResolverState {
|
versions: "importlib: 3.9-",
|
||||||
db: &db,
|
stdlib_files: &[("importlib/abc.pyi", "")],
|
||||||
typeshed_versions: LazyTypeshedVersions::new(),
|
|
||||||
target_version: TargetVersion::Py38,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let (db, stdlib_path) = py38_typeshed_test_case(TYPESHED);
|
||||||
|
let resolver = ResolverState::new(&db, TargetVersion::Py38);
|
||||||
|
|
||||||
let importlib_namespace_package = stdlib_path.join("importlib");
|
let importlib_namespace_package = stdlib_path.join("importlib");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
importlib_namespace_package.to_file(&stdlib_path, &resolver),
|
importlib_namespace_package.to_file(&stdlib_path, &resolver),
|
||||||
|
|
@ -1063,43 +1094,42 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn mocked_typeshed_nonexistent_single_file_module_py38() {
|
fn mocked_typeshed_nonexistent_single_file_module_py38() {
|
||||||
let (db, stdlib_path) = py38_stdlib_test_case();
|
const TYPESHED: MockedTypeshed = MockedTypeshed {
|
||||||
let resolver = ResolverState {
|
versions: "foo: 2.6-",
|
||||||
db: &db,
|
stdlib_files: &[("foo.pyi", "")],
|
||||||
typeshed_versions: LazyTypeshedVersions::new(),
|
|
||||||
target_version: TargetVersion::Py38,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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");
|
let non_existent = stdlib_path.join("doesnt_even_exist");
|
||||||
assert_eq!(non_existent.to_file(&stdlib_path, &resolver), None);
|
assert_eq!(non_existent.to_file(&stdlib_path, &resolver), None);
|
||||||
assert!(!non_existent.is_directory(&stdlib_path, &resolver));
|
assert!(!non_existent.is_directory(&stdlib_path, &resolver));
|
||||||
assert!(!non_existent.is_regular_package(&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]
|
#[test]
|
||||||
fn mocked_typeshed_existing_regular_stdlib_pkgs_py39() {
|
fn mocked_typeshed_existing_regular_stdlib_pkgs_py39() {
|
||||||
let (db, stdlib_path) = py39_stdlib_test_case();
|
const VERSIONS: &str = "\
|
||||||
let resolver = ResolverState {
|
asyncio: 3.8-
|
||||||
db: &db,
|
asyncio.tasks: 3.9-3.11
|
||||||
typeshed_versions: LazyTypeshedVersions::new(),
|
collections: 3.9-
|
||||||
target_version: TargetVersion::Py39,
|
";
|
||||||
|
|
||||||
|
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,
|
// Since we've set the target version to Py39,
|
||||||
// `collections` should now exist as a directory, according to VERSIONS...
|
// `collections` should now exist as a directory, according to VERSIONS...
|
||||||
let collections_regular_package = stdlib_path.join("collections");
|
let collections_regular_package = stdlib_path.join("collections");
|
||||||
|
|
@ -1126,14 +1156,15 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn mocked_typeshed_existing_namespace_stdlib_pkg_py39() {
|
fn mocked_typeshed_existing_namespace_stdlib_pkg_py39() {
|
||||||
let (db, stdlib_path) = py39_stdlib_test_case();
|
const TYPESHED: MockedTypeshed = MockedTypeshed {
|
||||||
let resolver = ResolverState {
|
versions: "importlib: 3.9-",
|
||||||
db: &db,
|
stdlib_files: &[("importlib/abc.pyi", "")],
|
||||||
typeshed_versions: LazyTypeshedVersions::new(),
|
|
||||||
target_version: TargetVersion::Py39,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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");
|
let importlib_namespace_package = stdlib_path.join("importlib");
|
||||||
assert!(importlib_namespace_package.is_directory(&stdlib_path, &resolver));
|
assert!(importlib_namespace_package.is_directory(&stdlib_path, &resolver));
|
||||||
assert!(!importlib_namespace_package.is_regular_package(&stdlib_path, &resolver));
|
assert!(!importlib_namespace_package.is_regular_package(&stdlib_path, &resolver));
|
||||||
|
|
@ -1143,7 +1174,7 @@ mod tests {
|
||||||
None
|
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");
|
let importlib_abc = importlib_namespace_package.join("abc.pyi");
|
||||||
assert!(!importlib_abc.is_directory(&stdlib_path, &resolver));
|
assert!(!importlib_abc.is_directory(&stdlib_path, &resolver));
|
||||||
assert!(!importlib_abc.is_regular_package(&stdlib_path, &resolver));
|
assert!(!importlib_abc.is_regular_package(&stdlib_path, &resolver));
|
||||||
|
|
@ -1152,13 +1183,14 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn mocked_typeshed_nonexistent_namespace_stdlib_pkg_py39() {
|
fn mocked_typeshed_nonexistent_namespace_stdlib_pkg_py39() {
|
||||||
let (db, stdlib_path) = py39_stdlib_test_case();
|
const TYPESHED: MockedTypeshed = MockedTypeshed {
|
||||||
let resolver = ResolverState {
|
versions: "xml: 3.8-3.8",
|
||||||
db: &db,
|
stdlib_files: &[("xml/etree.pyi", "")],
|
||||||
typeshed_versions: LazyTypeshedVersions::new(),
|
|
||||||
target_version: TargetVersion::Py39,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let (db, stdlib_path) = py39_typeshed_test_case(TYPESHED);
|
||||||
|
let resolver = ResolverState::new(&db, TargetVersion::Py39);
|
||||||
|
|
||||||
// The `xml` package no longer exists on py39:
|
// The `xml` package no longer exists on py39:
|
||||||
let xml_namespace_package = stdlib_path.join("xml");
|
let xml_namespace_package = stdlib_path.join("xml");
|
||||||
assert_eq!(xml_namespace_package.to_file(&stdlib_path, &resolver), None);
|
assert_eq!(xml_namespace_package.to_file(&stdlib_path, &resolver), None);
|
||||||
|
|
|
||||||
|
|
@ -385,28 +385,22 @@ impl PackageKind {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use ruff_db::files::{system_path_to_file, File, FilePath};
|
use ruff_db::files::{system_path_to_file, File, FilePath};
|
||||||
use ruff_db::system::DbWithTestSystem;
|
use ruff_db::system::{DbWithTestSystem, OsSystem, SystemPath};
|
||||||
use ruff_db::vendored::{VendoredPath, VendoredPathBuf};
|
|
||||||
use ruff_db::Upcast;
|
|
||||||
|
|
||||||
use crate::db::tests::{create_resolver_builder, TestCase};
|
use crate::db::tests::TestDb;
|
||||||
use crate::module::ModuleKind;
|
use crate::module::ModuleKind;
|
||||||
use crate::module_name::ModuleName;
|
use crate::module_name::ModuleName;
|
||||||
|
use crate::testing::{FileSpec, MockedTypeshed, TestCase, TestCaseBuilder};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
fn setup_resolver_test() -> TestCase {
|
|
||||||
create_resolver_builder().unwrap().build().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn first_party_module() -> anyhow::Result<()> {
|
fn first_party_module() {
|
||||||
let TestCase { mut db, src, .. } = setup_resolver_test();
|
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_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();
|
let foo_module = resolve_module(&db, foo_module_name.clone()).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
@ -418,25 +412,26 @@ mod tests {
|
||||||
assert_eq!(&src, &foo_module.search_path());
|
assert_eq!(&src, &foo_module.search_path());
|
||||||
assert_eq!(ModuleKind::Module, foo_module.kind());
|
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!(
|
assert_eq!(
|
||||||
Some(foo_module),
|
Some(foo_module),
|
||||||
path_to_module(&db, &FilePath::System(foo_path))
|
path_to_module(&db, &FilePath::System(expected_foo_path))
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn stdlib() {
|
fn stdlib() {
|
||||||
let TestCase {
|
const TYPESHED: MockedTypeshed = MockedTypeshed {
|
||||||
db,
|
stdlib_files: &[("functools.pyi", "def update_wrapper(): ...")],
|
||||||
custom_typeshed,
|
versions: "functools: 3.8-",
|
||||||
..
|
};
|
||||||
} = setup_resolver_test();
|
|
||||||
|
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_name = ModuleName::new_static("functools").unwrap();
|
||||||
let functools_module = resolve_module(&db, functools_module_name.clone()).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()
|
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());
|
assert_eq!(ModuleKind::Module, functools_module.kind());
|
||||||
|
|
||||||
let expected_functools_path =
|
let expected_functools_path = stdlib.join("functools.pyi");
|
||||||
FilePath::System(custom_typeshed.join("stdlib/functools.pyi"));
|
|
||||||
assert_eq!(&expected_functools_path, functools_module.file().path(&db));
|
assert_eq!(&expected_functools_path, functools_module.file().path(&db));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Some(functools_module),
|
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]
|
#[test]
|
||||||
fn stdlib_resolution_respects_versions_file_py38_existing_modules() {
|
fn stdlib_resolution_respects_versions_file_py38_existing_modules() {
|
||||||
let TestCase {
|
const VERSIONS: &str = "\
|
||||||
db,
|
asyncio: 3.8- # 'Regular' package on py38+
|
||||||
custom_typeshed,
|
asyncio.tasks: 3.9-3.11 # Submodule on py39+ only
|
||||||
..
|
functools: 3.8- # Top-level single-file module
|
||||||
} = setup_resolver_test();
|
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"]);
|
let existing_modules = create_module_names(&["asyncio", "functools", "xml.etree"]);
|
||||||
for module_name in existing_modules {
|
for module_name in existing_modules {
|
||||||
|
|
@ -480,8 +492,7 @@ mod tests {
|
||||||
});
|
});
|
||||||
let search_path = resolved_module.search_path();
|
let search_path = resolved_module.search_path();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&custom_typeshed.join("stdlib"),
|
&stdlib, &search_path,
|
||||||
&search_path,
|
|
||||||
"Search path for {module_name} was unexpectedly {search_path:?}"
|
"Search path for {module_name} was unexpectedly {search_path:?}"
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
|
|
@ -493,7 +504,32 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn stdlib_resolution_respects_versions_file_py38_nonexisting_modules() {
|
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(&[
|
let nonexisting_modules = create_module_names(&[
|
||||||
"collections",
|
"collections",
|
||||||
"importlib",
|
"importlib",
|
||||||
|
|
@ -501,6 +537,7 @@ mod tests {
|
||||||
"xml",
|
"xml",
|
||||||
"asyncio.tasks",
|
"asyncio.tasks",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
for module_name in nonexisting_modules {
|
for module_name in nonexisting_modules {
|
||||||
assert!(
|
assert!(
|
||||||
resolve_module(&db, module_name.clone()).is_none(),
|
resolve_module(&db, module_name.clone()).is_none(),
|
||||||
|
|
@ -511,15 +548,31 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn stdlib_resolution_respects_versions_file_py39_existing_modules() {
|
fn stdlib_resolution_respects_versions_file_py39_existing_modules() {
|
||||||
let TestCase {
|
const VERSIONS: &str = "\
|
||||||
db,
|
asyncio: 3.8- # 'Regular' package on py38+
|
||||||
custom_typeshed,
|
asyncio.tasks: 3.9-3.11 # Submodule on py39+ only
|
||||||
..
|
collections: 3.9- # 'Regular' package on py39+
|
||||||
} = create_resolver_builder()
|
functools: 3.8- # Top-level single-file module
|
||||||
.unwrap()
|
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)
|
.with_target_version(TargetVersion::Py39)
|
||||||
.build()
|
.build();
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let existing_modules = create_module_names(&[
|
let existing_modules = create_module_names(&[
|
||||||
"asyncio",
|
"asyncio",
|
||||||
|
|
@ -528,14 +581,14 @@ mod tests {
|
||||||
"collections",
|
"collections",
|
||||||
"asyncio.tasks",
|
"asyncio.tasks",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
for module_name in existing_modules {
|
for module_name in existing_modules {
|
||||||
let resolved_module = resolve_module(&db, module_name.clone()).unwrap_or_else(|| {
|
let resolved_module = resolve_module(&db, module_name.clone()).unwrap_or_else(|| {
|
||||||
panic!("Expected module {module_name} to exist in the mock stdlib")
|
panic!("Expected module {module_name} to exist in the mock stdlib")
|
||||||
});
|
});
|
||||||
let search_path = resolved_module.search_path();
|
let search_path = resolved_module.search_path();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&custom_typeshed.join("stdlib"),
|
&stdlib, &search_path,
|
||||||
&search_path,
|
|
||||||
"Search path for {module_name} was unexpectedly {search_path:?}"
|
"Search path for {module_name} was unexpectedly {search_path:?}"
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
|
|
@ -546,11 +599,22 @@ mod tests {
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn stdlib_resolution_respects_versions_file_py39_nonexisting_modules() {
|
fn stdlib_resolution_respects_versions_file_py39_nonexisting_modules() {
|
||||||
let TestCase { db, .. } = create_resolver_builder()
|
const VERSIONS: &str = "\
|
||||||
.unwrap()
|
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)
|
.with_target_version(TargetVersion::Py39)
|
||||||
.build()
|
.build();
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let nonexisting_modules = create_module_names(&["importlib", "xml", "xml.etree"]);
|
let nonexisting_modules = create_module_names(&["importlib", "xml", "xml.etree"]);
|
||||||
for module_name in nonexisting_modules {
|
for module_name in nonexisting_modules {
|
||||||
|
|
@ -562,11 +626,19 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn first_party_precedence_over_stdlib() -> anyhow::Result<()> {
|
fn first_party_precedence_over_stdlib() {
|
||||||
let TestCase { mut db, src, .. } = setup_resolver_test();
|
const SRC: &[FileSpec] = &[("functools.py", "def update_wrapper(): ...")];
|
||||||
|
|
||||||
let first_party_functools_path = src.join("functools.py");
|
const TYPESHED: MockedTypeshed = MockedTypeshed {
|
||||||
db.write_file(&first_party_functools_path, "def update_wrapper(): ...")?;
|
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_name = ModuleName::new_static("functools").unwrap();
|
||||||
let functools_module = resolve_module(&db, functools_module_name.clone()).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!(&src, &functools_module.search_path());
|
||||||
assert_eq!(ModuleKind::Module, functools_module.kind());
|
assert_eq!(ModuleKind::Module, functools_module.kind());
|
||||||
assert_eq!(
|
assert_eq!(&src.join("functools.py"), functools_module.file().path(&db));
|
||||||
&first_party_functools_path,
|
|
||||||
functools_module.file().path(&db)
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Some(functools_module),
|
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]
|
#[test]
|
||||||
fn stdlib_uses_vendored_typeshed_when_no_custom_typeshed_supplied() {
|
fn stdlib_uses_vendored_typeshed_when_no_custom_typeshed_supplied() {
|
||||||
let TestCase { db, .. } = create_resolver_builder()
|
let TestCase { db, stdlib, .. } = TestCaseBuilder::new()
|
||||||
.unwrap()
|
.with_vendored_typeshed()
|
||||||
.with_vendored_stubs_used()
|
.with_target_version(TargetVersion::default())
|
||||||
.build()
|
.build();
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let pydoc_data_topics_name = ModuleName::new_static("pydoc_data.topics").unwrap();
|
let pydoc_data_topics_name = ModuleName::new_static("pydoc_data.topics").unwrap();
|
||||||
let pydoc_data_topics = resolve_module(&db, pydoc_data_topics_name).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", pydoc_data_topics.name());
|
||||||
|
assert_eq!(pydoc_data_topics.search_path(), stdlib);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
pydoc_data_topics.search_path(),
|
pydoc_data_topics.file().path(&db),
|
||||||
VendoredPathBuf::from("stdlib")
|
&stdlib.join("pydoc_data/topics.pyi")
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
&pydoc_data_topics.file().path(db.upcast()),
|
|
||||||
&VendoredPath::new("stdlib/pydoc_data/topics.pyi")
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn resolve_package() -> anyhow::Result<()> {
|
fn resolve_package() {
|
||||||
let TestCase { src, mut db, .. } = setup_resolver_test();
|
let TestCase { src, db, .. } = TestCaseBuilder::new()
|
||||||
|
.with_src_files(&[("foo/__init__.py", "print('Hello, world!'")])
|
||||||
let foo_dir = src.join("foo");
|
.build();
|
||||||
let foo_path = foo_dir.join("__init__.py");
|
|
||||||
|
|
||||||
db.write_file(&foo_path, "print('Hello, world!')")?;
|
|
||||||
|
|
||||||
|
let foo_path = src.join("foo/__init__.py");
|
||||||
let foo_module = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap();
|
let foo_module = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap();
|
||||||
|
|
||||||
assert_eq!("foo", foo_module.name());
|
assert_eq!("foo", foo_module.name());
|
||||||
|
|
@ -632,96 +694,84 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Resolving by directory doesn't resolve to the init file.
|
// Resolving by directory doesn't resolve to the init file.
|
||||||
assert_eq!(None, path_to_module(&db, &FilePath::System(foo_dir)));
|
assert_eq!(
|
||||||
|
None,
|
||||||
Ok(())
|
path_to_module(&db, &FilePath::System(src.join("foo")))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn package_priority_over_module() -> anyhow::Result<()> {
|
fn package_priority_over_module() {
|
||||||
let TestCase { mut db, src, .. } = setup_resolver_test();
|
const SRC: &[FileSpec] = &[
|
||||||
|
("foo/__init__.py", "print('Hello, world!')"),
|
||||||
|
("foo.py", "print('Hello, world!')"),
|
||||||
|
];
|
||||||
|
|
||||||
let foo_dir = src.join("foo");
|
let TestCase { db, src, .. } = TestCaseBuilder::new().with_src_files(SRC).build();
|
||||||
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 foo_module = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap();
|
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!(&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!(ModuleKind::Package, foo_module.kind());
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Some(foo_module),
|
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]
|
#[test]
|
||||||
fn typing_stub_over_module() -> anyhow::Result<()> {
|
fn typing_stub_over_module() {
|
||||||
let TestCase { mut db, src, .. } = setup_resolver_test();
|
const SRC: &[FileSpec] = &[("foo.py", "print('Hello, world!')"), ("foo.pyi", "x: int")];
|
||||||
|
|
||||||
let foo_stub = src.join("foo.pyi");
|
let TestCase { db, src, .. } = TestCaseBuilder::new().with_src_files(SRC).build();
|
||||||
let foo_py = src.join("foo.py");
|
|
||||||
db.write_files([(&foo_stub, "x: int"), (&foo_py, "print('Hello, world!')")])?;
|
|
||||||
|
|
||||||
let foo = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap();
|
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!(&src, &foo.search_path());
|
||||||
assert_eq!(&foo_stub, foo.file().path(&db));
|
assert_eq!(&foo_stub, foo.file().path(&db));
|
||||||
|
|
||||||
assert_eq!(Some(foo), path_to_module(&db, &FilePath::System(foo_stub)));
|
assert_eq!(Some(foo), path_to_module(&db, &FilePath::System(foo_stub)));
|
||||||
assert_eq!(None, path_to_module(&db, &FilePath::System(foo_py)));
|
assert_eq!(
|
||||||
|
None,
|
||||||
Ok(())
|
path_to_module(&db, &FilePath::System(src.join("foo.py")))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sub_packages() -> anyhow::Result<()> {
|
fn sub_packages() {
|
||||||
let TestCase { mut db, src, .. } = setup_resolver_test();
|
const SRC: &[FileSpec] = &[
|
||||||
|
("foo/__init__.py", ""),
|
||||||
|
("foo/bar/__init__.py", ""),
|
||||||
|
("foo/bar/baz.py", "print('Hello, world!)'"),
|
||||||
|
];
|
||||||
|
|
||||||
let foo = src.join("foo");
|
let TestCase { db, src, .. } = TestCaseBuilder::new().with_src_files(SRC).build();
|
||||||
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 baz_module =
|
let baz_module =
|
||||||
resolve_module(&db, ModuleName::new_static("foo.bar.baz").unwrap()).unwrap();
|
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!(&src, &baz_module.search_path());
|
||||||
assert_eq!(&baz, baz_module.file().path(&db));
|
assert_eq!(&baz_path, baz_module.file().path(&db));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Some(baz_module),
|
Some(baz_module),
|
||||||
path_to_module(&db, &FilePath::System(baz))
|
path_to_module(&db, &FilePath::System(baz_path))
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn namespace_package() -> anyhow::Result<()> {
|
fn namespace_package() {
|
||||||
let TestCase {
|
|
||||||
mut db,
|
|
||||||
src,
|
|
||||||
site_packages,
|
|
||||||
..
|
|
||||||
} = setup_resolver_test();
|
|
||||||
|
|
||||||
// From [PEP420](https://peps.python.org/pep-0420/#nested-namespace-packages).
|
// 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
|
// src
|
||||||
// parent
|
// parent
|
||||||
|
|
@ -732,47 +782,33 @@ mod tests {
|
||||||
// child
|
// child
|
||||||
// two.py
|
// 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 {
|
let TestCase {
|
||||||
mut db,
|
db,
|
||||||
src,
|
src,
|
||||||
site_packages,
|
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).
|
// 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.
|
// 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
|
// child
|
||||||
// two.py
|
// two.py
|
||||||
// ```
|
// ```
|
||||||
|
const SRC: &[FileSpec] = &[
|
||||||
|
("parent/child/__init__.py", "print('Hello, world!')"),
|
||||||
|
("parent/child/one.py", "print('Hello, world!')"),
|
||||||
|
];
|
||||||
|
|
||||||
let parent1 = src.join("parent");
|
const SITE_PACKAGES: &[FileSpec] = &[("parent/child/two.py", "print('Hello, world!')")];
|
||||||
let child1 = parent1.join("child");
|
|
||||||
let one = child1.join("one.py");
|
|
||||||
|
|
||||||
let parent2 = site_packages.join("parent");
|
let TestCase { db, src, .. } = TestCaseBuilder::new()
|
||||||
let child2 = parent2.join("child");
|
.with_src_files(SRC)
|
||||||
let two = child2.join("two.py");
|
.with_site_packages_files(SITE_PACKAGES)
|
||||||
|
.build();
|
||||||
|
|
||||||
db.write_files([
|
let one_module_path = FilePath::System(src.join("parent/child/one.py"));
|
||||||
(&child1.join("__init__.py"), "print('Hello, world!')"),
|
let one_module_name =
|
||||||
(&one, "print('Hello, world!')"),
|
resolve_module(&db, ModuleName::new_static("parent.child.one").unwrap());
|
||||||
(&two, "print('Hello, world!')"),
|
assert_eq!(one_module_name, path_to_module(&db, &one_module_path));
|
||||||
])?;
|
|
||||||
|
|
||||||
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))
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
None,
|
None,
|
||||||
resolve_module(&db, ModuleName::new_static("parent.child.two").unwrap())
|
resolve_module(&db, ModuleName::new_static("parent.child.two").unwrap())
|
||||||
);
|
);
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn module_search_path_priority() -> anyhow::Result<()> {
|
fn module_search_path_priority() {
|
||||||
let TestCase {
|
let TestCase {
|
||||||
mut db,
|
db,
|
||||||
src,
|
src,
|
||||||
site_packages,
|
site_packages,
|
||||||
..
|
..
|
||||||
} = setup_resolver_test();
|
} = TestCaseBuilder::new()
|
||||||
|
.with_src_files(&[("foo.py", "")])
|
||||||
let foo_src = src.join("foo.py");
|
.with_site_packages_files(&[("foo.py", "")])
|
||||||
let foo_site_packages = site_packages.join("foo.py");
|
.build();
|
||||||
|
|
||||||
db.write_files([(&foo_src, ""), (&foo_site_packages, "")])?;
|
|
||||||
|
|
||||||
let foo_module = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap();
|
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!(&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!(
|
assert_eq!(
|
||||||
Some(foo_module),
|
Some(foo_module),
|
||||||
path_to_module(&db, &FilePath::System(foo_src))
|
path_to_module(&db, &FilePath::System(foo_src_path))
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
None,
|
|
||||||
path_to_module(&db, &FilePath::System(foo_site_packages))
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
assert_eq!(
|
||||||
|
None,
|
||||||
|
path_to_module(&db, &FilePath::System(site_packages.join("foo.py")))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
fn symlink() -> anyhow::Result<()> {
|
fn symlink() -> anyhow::Result<()> {
|
||||||
use ruff_db::system::{OsSystem, SystemPath};
|
let mut db = TestDb::new();
|
||||||
|
|
||||||
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 temp_dir = tempfile::tempdir()?;
|
let temp_dir = tempfile::tempdir()?;
|
||||||
let root = SystemPath::from_std_path(temp_dir.path()).unwrap();
|
let root = SystemPath::from_std_path(temp_dir.path()).unwrap();
|
||||||
db.use_os_system(OsSystem::new(root));
|
db.use_os_system(OsSystem::new(root));
|
||||||
|
|
||||||
let src = root.join(make_relative(&src));
|
let src = root.join("src");
|
||||||
let site_packages = root.join(make_relative(&site_packages));
|
let site_packages = root.join("site-packages");
|
||||||
let custom_typeshed = root.join(make_relative(&custom_typeshed));
|
let custom_typeshed = root.join("typeshed");
|
||||||
|
|
||||||
let foo = src.join("foo.py");
|
let foo = src.join("foo.py");
|
||||||
let bar = src.join("bar.py");
|
let bar = src.join("bar.py");
|
||||||
|
|
@ -919,23 +934,22 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn deleting_an_unrelated_file_doesnt_change_module_resolution() -> anyhow::Result<()> {
|
fn deleting_an_unrelated_file_doesnt_change_module_resolution() {
|
||||||
let TestCase { mut db, src, .. } = setup_resolver_test();
|
let TestCase { mut db, src, .. } = TestCaseBuilder::new()
|
||||||
|
.with_src_files(&[("foo.py", "x = 1"), ("bar.py", "x = 2")])
|
||||||
let foo_path = src.join("foo.py");
|
.with_target_version(TargetVersion::Py38)
|
||||||
let bar_path = src.join("bar.py");
|
.build();
|
||||||
|
|
||||||
db.write_files([(&foo_path, "x = 1"), (&bar_path, "y = 2")])?;
|
|
||||||
|
|
||||||
let foo_module_name = ModuleName::new_static("foo").unwrap();
|
let foo_module_name = ModuleName::new_static("foo").unwrap();
|
||||||
let foo_module = resolve_module(&db, foo_module_name.clone()).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");
|
let bar = system_path_to_file(&db, &bar_path).expect("bar.py to exist");
|
||||||
|
|
||||||
db.clear_salsa_events();
|
db.clear_salsa_events();
|
||||||
|
|
||||||
// Delete `bar.py`
|
// Delete `bar.py`
|
||||||
db.memory_file_system().remove_file(&bar_path)?;
|
db.memory_file_system().remove_file(&bar_path).unwrap();
|
||||||
bar.touch(&mut db);
|
bar.touch(&mut db);
|
||||||
|
|
||||||
// Re-query the foo module. The foo module should still be cached because `bar.py` isn't relevant
|
// 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 { .. }) }));
|
.any(|event| { matches!(event.kind, salsa::EventKind::WillExecute { .. }) }));
|
||||||
|
|
||||||
assert_eq!(Some(foo_module), foo_module2);
|
assert_eq!(Some(foo_module), foo_module2);
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn adding_a_file_on_which_the_module_resolution_depends_on_invalidates_the_query(
|
fn adding_a_file_on_which_the_module_resolution_depends_on_invalidates_the_query(
|
||||||
) -> anyhow::Result<()> {
|
) -> 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_path = src.join("foo.py");
|
||||||
|
|
||||||
let foo_module_name = ModuleName::new_static("foo").unwrap();
|
let foo_module_name = ModuleName::new_static("foo").unwrap();
|
||||||
|
|
@ -976,14 +988,13 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn removing_a_file_that_the_module_resolution_depends_on_invalidates_the_query(
|
fn removing_a_file_that_the_module_resolution_depends_on_invalidates_the_query(
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let TestCase { mut db, src, .. } = setup_resolver_test();
|
const SRC: &[FileSpec] = &[("foo.py", "x = 1"), ("foo/__init__.py", "x = 2")];
|
||||||
let foo_path = src.join("foo.py");
|
|
||||||
let foo_init_path = src.join("foo/__init__.py");
|
|
||||||
|
|
||||||
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_name = ModuleName::new_static("foo").unwrap();
|
||||||
let foo_module = resolve_module(&db, foo_module_name.clone()).expect("foo module to exist");
|
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));
|
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));
|
File::touch_path(&mut db, &FilePath::System(foo_init_path));
|
||||||
|
|
||||||
let foo_module = resolve_module(&db, foo_module_name).expect("Foo module to resolve");
|
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(())
|
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,
|
version: String,
|
||||||
err: std::num::ParseIntError,
|
err: std::num::ParseIntError,
|
||||||
},
|
},
|
||||||
EmptyVersionsFile,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for TypeshedVersionsParseErrorKind {
|
impl fmt::Display for TypeshedVersionsParseErrorKind {
|
||||||
|
|
@ -160,7 +159,6 @@ impl fmt::Display for TypeshedVersionsParseErrorKind {
|
||||||
f,
|
f,
|
||||||
"Failed to convert '{version}' to a pair of integers due to {err}",
|
"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() {
|
Ok(Self(map))
|
||||||
Err(TypeshedVersionsParseError {
|
|
||||||
line_number: None,
|
|
||||||
reason: TypeshedVersionsParseErrorKind::EmptyVersionsFile,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
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]
|
#[test]
|
||||||
fn invalid_huge_versions_file() {
|
fn invalid_huge_versions_file() {
|
||||||
let offset = 100;
|
let offset = 100;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue