use ruff_db::Db as _;
use ruff_db::files::FileRootKind;
use ruff_db::system::{
DbWithTestSystem as _, DbWithWritableSystem as _, SystemPath, SystemPathBuf,
};
use ruff_db::vendored::VendoredPathBuf;
use ruff_python_ast::PythonVersion;
use crate::db::tests::TestDb;
use crate::settings::SearchPathSettings;
use crate::strategy::FallibleStrategy;
pub(crate) struct TestCase<T> {
pub(crate) db: TestDb,
pub(crate) src: SystemPathBuf,
pub(crate) stdlib: T,
pub(crate) site_packages: SystemPathBuf,
pub(crate) python_version: PythonVersion,
}
pub(crate) type FileSpec = (&'static str, &'static str);
#[derive(Debug, Clone, Copy, Default)]
pub(crate) struct MockedTypeshed {
pub(crate) stdlib_files: &'static [FileSpec],
pub(crate) versions: &'static str,
}
#[derive(Debug)]
pub(crate) struct VendoredTypeshed;
#[derive(Debug)]
pub(crate) struct UnspecifiedTypeshed;
pub(crate) struct TestCaseBuilder<T> {
typeshed_option: T,
python_version: PythonVersion,
first_party_files: Vec<FileSpec>,
site_packages_files: Vec<FileSpec>,
roots: Vec<SystemPathBuf>,
}
impl<T> TestCaseBuilder<T> {
fn with_typeshed<U>(self, typeshed_option: U) -> TestCaseBuilder<U> {
TestCaseBuilder {
typeshed_option,
python_version: self.python_version,
first_party_files: self.first_party_files,
site_packages_files: self.site_packages_files,
roots: self.roots,
}
}
pub(crate) fn with_src_files(mut self, files: &[FileSpec]) -> Self {
self.first_party_files.extend(files.iter().copied());
self
}
pub(crate) fn with_site_packages_files(mut self, files: &[FileSpec]) -> Self {
self.site_packages_files.extend(files.iter().copied());
self
}
pub(crate) fn with_python_version(mut self, python_version: PythonVersion) -> Self {
self.python_version = python_version;
self
}
pub(crate) fn with_library_root(mut self, root: impl AsRef<SystemPath>) -> Self {
self.roots.push(root.as_ref().to_path_buf());
self
}
fn write_mock_directory(
db: &mut TestDb,
location: impl AsRef<SystemPath>,
files: impl IntoIterator<Item = FileSpec>,
) -> SystemPathBuf {
let root = location.as_ref().to_path_buf();
db.memory_file_system().create_directory_all(&root).unwrap();
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,
python_version: PythonVersion::default(),
first_party_files: vec![],
site_packages_files: vec![],
roots: vec![],
}
}
pub(crate) fn with_vendored_typeshed(self) -> TestCaseBuilder<VendoredTypeshed> {
self.with_typeshed(VendoredTypeshed)
}
pub(crate) fn with_mocked_typeshed(
self,
typeshed: MockedTypeshed,
) -> TestCaseBuilder<MockedTypeshed> {
self.with_typeshed(typeshed)
}
pub(crate) fn build(self) -> TestCase<()> {
let TestCase {
db,
src,
stdlib: _,
site_packages,
python_version,
} = self.with_mocked_typeshed(MockedTypeshed::default()).build();
TestCase {
db,
src,
stdlib: (),
site_packages,
python_version,
}
}
}
impl TestCaseBuilder<MockedTypeshed> {
pub(crate) fn build(self) -> TestCase<SystemPathBuf> {
let TestCaseBuilder {
typeshed_option,
python_version,
first_party_files,
site_packages_files,
roots,
} = self;
let mut db = TestDb::new().with_python_version(python_version);
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);
let stdlib = typeshed.join("stdlib");
let search_paths = SearchPathSettings {
src_roots: vec![src.clone()],
custom_typeshed: Some(typeshed),
site_packages_paths: vec![site_packages.clone()],
..SearchPathSettings::empty()
}
.to_search_paths(db.system(), db.vendored(), &FallibleStrategy)
.expect("Valid search path settings");
db = db.with_search_paths(search_paths);
db.files()
.try_add_root(&db, SystemPath::new("/src"), FileRootKind::Project);
db.files()
.try_add_root(&db, &stdlib, FileRootKind::SearchPath);
for root in &roots {
db.files().try_add_root(&db, root, FileRootKind::SearchPath);
}
TestCase {
db,
src,
stdlib,
site_packages,
python_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,
python_version,
first_party_files,
site_packages_files,
roots,
} = self;
let mut db = TestDb::new().with_python_version(python_version);
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 search_paths = SearchPathSettings {
src_roots: vec![src.clone()],
site_packages_paths: vec![site_packages.clone()],
..SearchPathSettings::empty()
}
.to_search_paths(db.system(), db.vendored(), &FallibleStrategy)
.expect("Valid search path settings");
db = db.with_search_paths(search_paths);
db.files()
.try_add_root(&db, SystemPath::new("/src"), FileRootKind::Project);
for root in &roots {
db.files().try_add_root(&db, root, FileRootKind::SearchPath);
}
TestCase {
db,
src,
stdlib: VendoredPathBuf::from("stdlib"),
site_packages,
python_version,
}
}
}