mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 13:25:17 +00:00
Add OsSystem
support to mdtests (#16518)
## Summary This PR introduces a new mdtest option `system` that can either be `in-memory` or `os` where `in-memory` is the default. The motivation for supporting `os` is so that we can write OS/system specific tests with mdtests. Specifically, I want to write mdtests for the module resolver, testing that module resolution is case sensitive. ## Test Plan I tested that the case-sensitive module resolver test start failing when setting `system = "os"`
This commit is contained in:
parent
48f906e06c
commit
ce0018c3cb
31 changed files with 541 additions and 229 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -2541,6 +2541,7 @@ dependencies = [
|
||||||
"regex",
|
"regex",
|
||||||
"ruff_db",
|
"ruff_db",
|
||||||
"ruff_index",
|
"ruff_index",
|
||||||
|
"ruff_notebook",
|
||||||
"ruff_python_ast",
|
"ruff_python_ast",
|
||||||
"ruff_python_trivia",
|
"ruff_python_trivia",
|
||||||
"ruff_source_file",
|
"ruff_source_file",
|
||||||
|
@ -2549,6 +2550,7 @@ dependencies = [
|
||||||
"salsa",
|
"salsa",
|
||||||
"serde",
|
"serde",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
|
"tempfile",
|
||||||
"thiserror 2.0.11",
|
"thiserror 2.0.11",
|
||||||
"toml",
|
"toml",
|
||||||
]
|
]
|
||||||
|
|
|
@ -255,7 +255,7 @@ mod tests {
|
||||||
use crate::files::Index;
|
use crate::files::Index;
|
||||||
use crate::ProjectMetadata;
|
use crate::ProjectMetadata;
|
||||||
use ruff_db::files::system_path_to_file;
|
use ruff_db::files::system_path_to_file;
|
||||||
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
use ruff_db::system::{DbWithWritableSystem as _, SystemPathBuf};
|
||||||
use ruff_python_ast::name::Name;
|
use ruff_python_ast::name::Name;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -528,7 +528,7 @@ mod tests {
|
||||||
use ruff_db::diagnostic::OldDiagnosticTrait;
|
use ruff_db::diagnostic::OldDiagnosticTrait;
|
||||||
use ruff_db::files::system_path_to_file;
|
use ruff_db::files::system_path_to_file;
|
||||||
use ruff_db::source::source_text;
|
use ruff_db::source::source_text;
|
||||||
use ruff_db::system::{DbWithTestSystem, SystemPath, SystemPathBuf};
|
use ruff_db::system::{DbWithTestSystem, DbWithWritableSystem as _, SystemPath, SystemPathBuf};
|
||||||
use ruff_db::testing::assert_function_query_was_not_run;
|
use ruff_db::testing::assert_function_query_was_not_run;
|
||||||
use ruff_python_ast::name::Name;
|
use ruff_python_ast::name::Name;
|
||||||
|
|
||||||
|
|
|
@ -321,7 +321,7 @@ mod tests {
|
||||||
|
|
||||||
system
|
system
|
||||||
.memory_file_system()
|
.memory_file_system()
|
||||||
.write_files([(root.join("foo.py"), ""), (root.join("bar.py"), "")])
|
.write_files_all([(root.join("foo.py"), ""), (root.join("bar.py"), "")])
|
||||||
.context("Failed to write files")?;
|
.context("Failed to write files")?;
|
||||||
|
|
||||||
let project =
|
let project =
|
||||||
|
@ -349,7 +349,7 @@ mod tests {
|
||||||
|
|
||||||
system
|
system
|
||||||
.memory_file_system()
|
.memory_file_system()
|
||||||
.write_files([
|
.write_files_all([
|
||||||
(
|
(
|
||||||
root.join("pyproject.toml"),
|
root.join("pyproject.toml"),
|
||||||
r#"
|
r#"
|
||||||
|
@ -393,7 +393,7 @@ mod tests {
|
||||||
|
|
||||||
system
|
system
|
||||||
.memory_file_system()
|
.memory_file_system()
|
||||||
.write_files([
|
.write_files_all([
|
||||||
(
|
(
|
||||||
root.join("pyproject.toml"),
|
root.join("pyproject.toml"),
|
||||||
r#"
|
r#"
|
||||||
|
@ -432,7 +432,7 @@ expected `.`, `]`
|
||||||
|
|
||||||
system
|
system
|
||||||
.memory_file_system()
|
.memory_file_system()
|
||||||
.write_files([
|
.write_files_all([
|
||||||
(
|
(
|
||||||
root.join("pyproject.toml"),
|
root.join("pyproject.toml"),
|
||||||
r#"
|
r#"
|
||||||
|
@ -482,7 +482,7 @@ expected `.`, `]`
|
||||||
|
|
||||||
system
|
system
|
||||||
.memory_file_system()
|
.memory_file_system()
|
||||||
.write_files([
|
.write_files_all([
|
||||||
(
|
(
|
||||||
root.join("pyproject.toml"),
|
root.join("pyproject.toml"),
|
||||||
r#"
|
r#"
|
||||||
|
@ -532,7 +532,7 @@ expected `.`, `]`
|
||||||
|
|
||||||
system
|
system
|
||||||
.memory_file_system()
|
.memory_file_system()
|
||||||
.write_files([
|
.write_files_all([
|
||||||
(
|
(
|
||||||
root.join("pyproject.toml"),
|
root.join("pyproject.toml"),
|
||||||
r#"
|
r#"
|
||||||
|
@ -572,7 +572,7 @@ expected `.`, `]`
|
||||||
|
|
||||||
system
|
system
|
||||||
.memory_file_system()
|
.memory_file_system()
|
||||||
.write_files([
|
.write_files_all([
|
||||||
(
|
(
|
||||||
root.join("pyproject.toml"),
|
root.join("pyproject.toml"),
|
||||||
r#"
|
r#"
|
||||||
|
@ -623,7 +623,7 @@ expected `.`, `]`
|
||||||
|
|
||||||
system
|
system
|
||||||
.memory_file_system()
|
.memory_file_system()
|
||||||
.write_files([
|
.write_files_all([
|
||||||
(
|
(
|
||||||
root.join("pyproject.toml"),
|
root.join("pyproject.toml"),
|
||||||
r#"
|
r#"
|
||||||
|
@ -673,7 +673,7 @@ expected `.`, `]`
|
||||||
|
|
||||||
system
|
system
|
||||||
.memory_file_system()
|
.memory_file_system()
|
||||||
.write_file(
|
.write_file_all(
|
||||||
root.join("pyproject.toml"),
|
root.join("pyproject.toml"),
|
||||||
r#"
|
r#"
|
||||||
[project]
|
[project]
|
||||||
|
@ -703,7 +703,7 @@ expected `.`, `]`
|
||||||
|
|
||||||
system
|
system
|
||||||
.memory_file_system()
|
.memory_file_system()
|
||||||
.write_file(
|
.write_file_all(
|
||||||
root.join("pyproject.toml"),
|
root.join("pyproject.toml"),
|
||||||
r#"
|
r#"
|
||||||
[project]
|
[project]
|
||||||
|
@ -735,7 +735,7 @@ expected `.`, `]`
|
||||||
|
|
||||||
system
|
system
|
||||||
.memory_file_system()
|
.memory_file_system()
|
||||||
.write_file(
|
.write_file_all(
|
||||||
root.join("pyproject.toml"),
|
root.join("pyproject.toml"),
|
||||||
r#"
|
r#"
|
||||||
[project]
|
[project]
|
||||||
|
@ -765,7 +765,7 @@ expected `.`, `]`
|
||||||
|
|
||||||
system
|
system
|
||||||
.memory_file_system()
|
.memory_file_system()
|
||||||
.write_file(
|
.write_file_all(
|
||||||
root.join("pyproject.toml"),
|
root.join("pyproject.toml"),
|
||||||
r#"
|
r#"
|
||||||
[project]
|
[project]
|
||||||
|
@ -795,7 +795,7 @@ expected `.`, `]`
|
||||||
|
|
||||||
system
|
system
|
||||||
.memory_file_system()
|
.memory_file_system()
|
||||||
.write_file(
|
.write_file_all(
|
||||||
root.join("pyproject.toml"),
|
root.join("pyproject.toml"),
|
||||||
r#"
|
r#"
|
||||||
[project]
|
[project]
|
||||||
|
@ -828,7 +828,7 @@ expected `.`, `]`
|
||||||
|
|
||||||
system
|
system
|
||||||
.memory_file_system()
|
.memory_file_system()
|
||||||
.write_file(
|
.write_file_all(
|
||||||
root.join("pyproject.toml"),
|
root.join("pyproject.toml"),
|
||||||
r#"
|
r#"
|
||||||
[project]
|
[project]
|
||||||
|
@ -861,7 +861,7 @@ expected `.`, `]`
|
||||||
|
|
||||||
system
|
system
|
||||||
.memory_file_system()
|
.memory_file_system()
|
||||||
.write_file(
|
.write_file_all(
|
||||||
root.join("pyproject.toml"),
|
root.join("pyproject.toml"),
|
||||||
r#"
|
r#"
|
||||||
[project]
|
[project]
|
||||||
|
@ -886,7 +886,7 @@ expected `.`, `]`
|
||||||
|
|
||||||
system
|
system
|
||||||
.memory_file_system()
|
.memory_file_system()
|
||||||
.write_file(
|
.write_file_all(
|
||||||
root.join("pyproject.toml"),
|
root.join("pyproject.toml"),
|
||||||
r#"
|
r#"
|
||||||
[project]
|
[project]
|
||||||
|
@ -911,7 +911,7 @@ expected `.`, `]`
|
||||||
|
|
||||||
system
|
system
|
||||||
.memory_file_system()
|
.memory_file_system()
|
||||||
.write_file(
|
.write_file_all(
|
||||||
root.join("pyproject.toml"),
|
root.join("pyproject.toml"),
|
||||||
r#"
|
r#"
|
||||||
[project]
|
[project]
|
||||||
|
|
|
@ -117,7 +117,7 @@ fn run_corpus_tests(pattern: &str) -> anyhow::Result<()> {
|
||||||
let code = std::fs::read_to_string(source)?;
|
let code = std::fs::read_to_string(source)?;
|
||||||
|
|
||||||
let mut check_with_file_name = |path: &SystemPath| {
|
let mut check_with_file_name = |path: &SystemPath| {
|
||||||
memory_fs.write_file(path, &code).unwrap();
|
memory_fs.write_file_all(path, &code).unwrap();
|
||||||
File::sync_path(&mut db, path);
|
File::sync_path(&mut db, path);
|
||||||
|
|
||||||
// this test is only asserting that we can pull every expression type without a panic
|
// this test is only asserting that we can pull every expression type without a panic
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
# Case Sensitive Imports
|
# Case Sensitive Imports
|
||||||
|
|
||||||
TODO: This test should use the real file system instead of the memory file system.
|
```toml
|
||||||
|
# TODO: This test should use the real file system instead of the memory file system.
|
||||||
|
# but we can't change the file system yet because the tests would then start failing for
|
||||||
|
# case-insensitive file systems.
|
||||||
|
#system = "os"
|
||||||
|
```
|
||||||
|
|
||||||
Python's import system is case-sensitive even on case-insensitive file system. This means, importing
|
Python's import system is case-sensitive even on case-insensitive file system. This means, importing
|
||||||
a module `a` should fail if the file in the search paths is named `A.py`. See
|
a module `a` should fail if the file in the search paths is named `A.py`. See
|
||||||
|
|
|
@ -25,7 +25,9 @@ pub(crate) mod tests {
|
||||||
use crate::lint::{LintRegistry, RuleSelection};
|
use crate::lint::{LintRegistry, RuleSelection};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use ruff_db::files::{File, Files};
|
use ruff_db::files::{File, Files};
|
||||||
use ruff_db::system::{DbWithTestSystem, System, SystemPathBuf, TestSystem};
|
use ruff_db::system::{
|
||||||
|
DbWithTestSystem, DbWithWritableSystem as _, System, SystemPathBuf, TestSystem,
|
||||||
|
};
|
||||||
use ruff_db::vendored::VendoredFileSystem;
|
use ruff_db::vendored::VendoredFileSystem;
|
||||||
use ruff_db::{Db as SourceDb, Upcast};
|
use ruff_db::{Db as SourceDb, Upcast};
|
||||||
use ruff_python_ast::PythonVersion;
|
use ruff_python_ast::PythonVersion;
|
||||||
|
|
|
@ -720,7 +720,7 @@ impl<'db> ResolverContext<'db> {
|
||||||
#[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 as _, DbWithWritableSystem as _};
|
||||||
use ruff_db::testing::{
|
use ruff_db::testing::{
|
||||||
assert_const_function_query_was_not_run, assert_function_query_was_not_run,
|
assert_const_function_query_was_not_run, assert_function_query_was_not_run,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use ruff_db::system::{DbWithTestSystem, SystemPath, SystemPathBuf};
|
use ruff_db::system::{
|
||||||
|
DbWithTestSystem as _, DbWithWritableSystem as _, SystemPath, SystemPathBuf,
|
||||||
|
};
|
||||||
use ruff_db::vendored::VendoredPathBuf;
|
use ruff_db::vendored::VendoredPathBuf;
|
||||||
use ruff_python_ast::PythonVersion;
|
use ruff_python_ast::PythonVersion;
|
||||||
|
|
||||||
|
|
|
@ -409,7 +409,7 @@ impl FusedIterator for ChildrenIter<'_> {}
|
||||||
mod tests {
|
mod tests {
|
||||||
use ruff_db::files::{system_path_to_file, File};
|
use ruff_db::files::{system_path_to_file, File};
|
||||||
use ruff_db::parsed::parsed_module;
|
use ruff_db::parsed::parsed_module;
|
||||||
use ruff_db::system::DbWithTestSystem;
|
use ruff_db::system::DbWithWritableSystem as _;
|
||||||
use ruff_python_ast as ast;
|
use ruff_python_ast as ast;
|
||||||
use ruff_text_size::{Ranged, TextRange};
|
use ruff_text_size::{Ranged, TextRange};
|
||||||
|
|
||||||
|
@ -440,7 +440,7 @@ mod tests {
|
||||||
file: File,
|
file: File,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_case(content: impl ToString) -> TestCase {
|
fn test_case(content: impl AsRef<str>) -> TestCase {
|
||||||
let mut db = TestDb::new();
|
let mut db = TestDb::new();
|
||||||
db.write_file("test.py", content).unwrap();
|
db.write_file("test.py", content).unwrap();
|
||||||
|
|
||||||
|
|
|
@ -545,7 +545,7 @@ mod tests {
|
||||||
system_install_sys_prefix.join(&unix_site_packages);
|
system_install_sys_prefix.join(&unix_site_packages);
|
||||||
(system_home_path, system_exe_path, system_site_packages_path)
|
(system_home_path, system_exe_path, system_site_packages_path)
|
||||||
};
|
};
|
||||||
memory_fs.write_file(system_exe_path, "").unwrap();
|
memory_fs.write_file_all(system_exe_path, "").unwrap();
|
||||||
memory_fs
|
memory_fs
|
||||||
.create_directory_all(&system_site_packages_path)
|
.create_directory_all(&system_site_packages_path)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -562,7 +562,7 @@ mod tests {
|
||||||
venv_sys_prefix.join(&unix_site_packages),
|
venv_sys_prefix.join(&unix_site_packages),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
memory_fs.write_file(&venv_exe, "").unwrap();
|
memory_fs.write_file_all(&venv_exe, "").unwrap();
|
||||||
memory_fs.create_directory_all(&site_packages_path).unwrap();
|
memory_fs.create_directory_all(&site_packages_path).unwrap();
|
||||||
|
|
||||||
let pyvenv_cfg_path = venv_sys_prefix.join("pyvenv.cfg");
|
let pyvenv_cfg_path = venv_sys_prefix.join("pyvenv.cfg");
|
||||||
|
@ -576,7 +576,7 @@ mod tests {
|
||||||
pyvenv_cfg_contents.push_str("include-system-site-packages = TRuE\n");
|
pyvenv_cfg_contents.push_str("include-system-site-packages = TRuE\n");
|
||||||
}
|
}
|
||||||
memory_fs
|
memory_fs
|
||||||
.write_file(pyvenv_cfg_path, &pyvenv_cfg_contents)
|
.write_file_all(pyvenv_cfg_path, &pyvenv_cfg_contents)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
venv_sys_prefix
|
venv_sys_prefix
|
||||||
|
@ -740,7 +740,7 @@ mod tests {
|
||||||
let system = TestSystem::default();
|
let system = TestSystem::default();
|
||||||
system
|
system
|
||||||
.memory_file_system()
|
.memory_file_system()
|
||||||
.write_file("/.venv", "")
|
.write_file_all("/.venv", "")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
VirtualEnvironment::new("/.venv", &system),
|
VirtualEnvironment::new("/.venv", &system),
|
||||||
|
@ -767,7 +767,7 @@ mod tests {
|
||||||
let memory_fs = system.memory_file_system();
|
let memory_fs = system.memory_file_system();
|
||||||
let pyvenv_cfg_path = SystemPathBuf::from("/.venv/pyvenv.cfg");
|
let pyvenv_cfg_path = SystemPathBuf::from("/.venv/pyvenv.cfg");
|
||||||
memory_fs
|
memory_fs
|
||||||
.write_file(&pyvenv_cfg_path, "home = bar = /.venv/bin")
|
.write_file_all(&pyvenv_cfg_path, "home = bar = /.venv/bin")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let venv_result = VirtualEnvironment::new("/.venv", &system);
|
let venv_result = VirtualEnvironment::new("/.venv", &system);
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
|
@ -785,7 +785,9 @@ mod tests {
|
||||||
let system = TestSystem::default();
|
let system = TestSystem::default();
|
||||||
let memory_fs = system.memory_file_system();
|
let memory_fs = system.memory_file_system();
|
||||||
let pyvenv_cfg_path = SystemPathBuf::from("/.venv/pyvenv.cfg");
|
let pyvenv_cfg_path = SystemPathBuf::from("/.venv/pyvenv.cfg");
|
||||||
memory_fs.write_file(&pyvenv_cfg_path, "home =").unwrap();
|
memory_fs
|
||||||
|
.write_file_all(&pyvenv_cfg_path, "home =")
|
||||||
|
.unwrap();
|
||||||
let venv_result = VirtualEnvironment::new("/.venv", &system);
|
let venv_result = VirtualEnvironment::new("/.venv", &system);
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
venv_result,
|
venv_result,
|
||||||
|
@ -803,7 +805,7 @@ mod tests {
|
||||||
let memory_fs = system.memory_file_system();
|
let memory_fs = system.memory_file_system();
|
||||||
let pyvenv_cfg_path = SystemPathBuf::from("/.venv/pyvenv.cfg");
|
let pyvenv_cfg_path = SystemPathBuf::from("/.venv/pyvenv.cfg");
|
||||||
memory_fs
|
memory_fs
|
||||||
.write_file(&pyvenv_cfg_path, "= whatever")
|
.write_file_all(&pyvenv_cfg_path, "= whatever")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let venv_result = VirtualEnvironment::new("/.venv", &system);
|
let venv_result = VirtualEnvironment::new("/.venv", &system);
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
|
@ -821,7 +823,7 @@ mod tests {
|
||||||
let system = TestSystem::default();
|
let system = TestSystem::default();
|
||||||
let memory_fs = system.memory_file_system();
|
let memory_fs = system.memory_file_system();
|
||||||
let pyvenv_cfg_path = SystemPathBuf::from("/.venv/pyvenv.cfg");
|
let pyvenv_cfg_path = SystemPathBuf::from("/.venv/pyvenv.cfg");
|
||||||
memory_fs.write_file(&pyvenv_cfg_path, "").unwrap();
|
memory_fs.write_file_all(&pyvenv_cfg_path, "").unwrap();
|
||||||
let venv_result = VirtualEnvironment::new("/.venv", &system);
|
let venv_result = VirtualEnvironment::new("/.venv", &system);
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
venv_result,
|
venv_result,
|
||||||
|
@ -839,7 +841,7 @@ mod tests {
|
||||||
let memory_fs = system.memory_file_system();
|
let memory_fs = system.memory_file_system();
|
||||||
let pyvenv_cfg_path = SystemPathBuf::from("/.venv/pyvenv.cfg");
|
let pyvenv_cfg_path = SystemPathBuf::from("/.venv/pyvenv.cfg");
|
||||||
memory_fs
|
memory_fs
|
||||||
.write_file(&pyvenv_cfg_path, "home = foo")
|
.write_file_all(&pyvenv_cfg_path, "home = foo")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let venv_result = VirtualEnvironment::new("/.venv", &system);
|
let venv_result = VirtualEnvironment::new("/.venv", &system);
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
|
|
|
@ -4350,7 +4350,7 @@ pub(crate) mod tests {
|
||||||
};
|
};
|
||||||
use ruff_db::files::system_path_to_file;
|
use ruff_db::files::system_path_to_file;
|
||||||
use ruff_db::parsed::parsed_module;
|
use ruff_db::parsed::parsed_module;
|
||||||
use ruff_db::system::DbWithTestSystem;
|
use ruff_db::system::DbWithWritableSystem as _;
|
||||||
use ruff_db::testing::assert_function_query_was_not_run;
|
use ruff_db::testing::assert_function_query_was_not_run;
|
||||||
use ruff_python_ast::PythonVersion;
|
use ruff_python_ast::PythonVersion;
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
|
|
|
@ -6551,7 +6551,7 @@ mod tests {
|
||||||
use crate::symbol::global_symbol;
|
use crate::symbol::global_symbol;
|
||||||
use crate::types::check_types;
|
use crate::types::check_types;
|
||||||
use ruff_db::files::{system_path_to_file, File};
|
use ruff_db::files::{system_path_to_file, File};
|
||||||
use ruff_db::system::DbWithTestSystem;
|
use ruff_db::system::DbWithWritableSystem as _;
|
||||||
use ruff_db::testing::{assert_function_query_was_not_run, assert_function_query_was_run};
|
use ruff_db::testing::{assert_function_query_was_not_run, assert_function_query_was_run};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -348,7 +348,7 @@ mod tests {
|
||||||
use crate::db::tests::{setup_db, TestDb};
|
use crate::db::tests::{setup_db, TestDb};
|
||||||
use crate::symbol::global_symbol;
|
use crate::symbol::global_symbol;
|
||||||
use crate::types::{FunctionType, KnownClass};
|
use crate::types::{FunctionType, KnownClass};
|
||||||
use ruff_db::system::DbWithTestSystem;
|
use ruff_db::system::DbWithWritableSystem as _;
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn get_function_f<'db>(db: &'db TestDb, file: &'static str) -> FunctionType<'db> {
|
fn get_function_f<'db>(db: &'db TestDb, file: &'static str) -> FunctionType<'db> {
|
||||||
|
|
|
@ -15,6 +15,7 @@ red_knot_python_semantic = { workspace = true, features = ["serde"] }
|
||||||
red_knot_vendored = { workspace = true }
|
red_knot_vendored = { workspace = true }
|
||||||
ruff_db = { workspace = true, features = ["testing"] }
|
ruff_db = { workspace = true, features = ["testing"] }
|
||||||
ruff_index = { workspace = true }
|
ruff_index = { workspace = true }
|
||||||
|
ruff_notebook = { workspace = true }
|
||||||
ruff_python_trivia = { workspace = true }
|
ruff_python_trivia = { workspace = true }
|
||||||
ruff_source_file = { workspace = true }
|
ruff_source_file = { workspace = true }
|
||||||
ruff_text_size = { workspace = true }
|
ruff_text_size = { workspace = true }
|
||||||
|
@ -30,6 +31,7 @@ rustc-hash = { workspace = true }
|
||||||
salsa = { workspace = true }
|
salsa = { workspace = true }
|
||||||
smallvec = { workspace = true }
|
smallvec = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
|
tempfile = { workspace = true }
|
||||||
toml = { workspace = true }
|
toml = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
|
|
||||||
|
|
|
@ -490,12 +490,12 @@ pub(crate) enum ErrorAssertionParseError<'a> {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use ruff_db::files::system_path_to_file;
|
use ruff_db::files::system_path_to_file;
|
||||||
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
use ruff_db::system::DbWithWritableSystem as _;
|
||||||
use ruff_python_trivia::textwrap::dedent;
|
use ruff_python_trivia::textwrap::dedent;
|
||||||
use ruff_source_file::OneIndexed;
|
use ruff_source_file::OneIndexed;
|
||||||
|
|
||||||
fn get_assertions(source: &str) -> InlineFileAssertions {
|
fn get_assertions(source: &str) -> InlineFileAssertions {
|
||||||
let mut db = crate::db::Db::setup(SystemPathBuf::from("/src"));
|
let mut db = Db::setup();
|
||||||
db.write_file("/src/test.py", source).unwrap();
|
db.write_file("/src/test.py", source).unwrap();
|
||||||
let file = system_path_to_file(&db, "/src/test.py").unwrap();
|
let file = system_path_to_file(&db, "/src/test.py").unwrap();
|
||||||
InlineFileAssertions::from_file(&db, file)
|
InlineFileAssertions::from_file(&db, file)
|
||||||
|
|
|
@ -12,7 +12,7 @@ use anyhow::Context;
|
||||||
use red_knot_python_semantic::PythonPlatform;
|
use red_knot_python_semantic::PythonPlatform;
|
||||||
use ruff_db::system::{SystemPath, SystemPathBuf};
|
use ruff_db::system::{SystemPath, SystemPathBuf};
|
||||||
use ruff_python_ast::PythonVersion;
|
use ruff_python_ast::PythonVersion;
|
||||||
use serde::Deserialize;
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, Default, Clone)]
|
#[derive(Deserialize, Debug, Default, Clone)]
|
||||||
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
||||||
|
@ -20,6 +20,11 @@ pub(crate) struct MarkdownTestConfig {
|
||||||
pub(crate) environment: Option<Environment>,
|
pub(crate) environment: Option<Environment>,
|
||||||
|
|
||||||
pub(crate) log: Option<Log>,
|
pub(crate) log: Option<Log>,
|
||||||
|
|
||||||
|
/// The [`ruff_db::system::System`] to use for tests.
|
||||||
|
///
|
||||||
|
/// Defaults to the case-sensitive [`ruff_db::system::InMemorySystem`].
|
||||||
|
pub(crate) system: Option<SystemKind>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MarkdownTestConfig {
|
impl MarkdownTestConfig {
|
||||||
|
@ -74,3 +79,19 @@ pub(crate) enum Log {
|
||||||
/// Enable logging and only show filters that match the given [env-filter](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html)
|
/// Enable logging and only show filters that match the given [env-filter](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html)
|
||||||
Filter(String),
|
Filter(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The system to use for tests.
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub(crate) enum SystemKind {
|
||||||
|
/// Use an in-memory system with a case sensitive file system..
|
||||||
|
///
|
||||||
|
/// This is recommended for all tests because it's fast.
|
||||||
|
#[default]
|
||||||
|
InMemory,
|
||||||
|
|
||||||
|
/// Use the os system.
|
||||||
|
///
|
||||||
|
/// This system should only be used when testing system or OS specific behavior.
|
||||||
|
Os,
|
||||||
|
}
|
||||||
|
|
|
@ -1,69 +1,53 @@
|
||||||
use std::sync::Arc;
|
use camino::{Utf8Component, Utf8PathBuf};
|
||||||
|
|
||||||
use red_knot_python_semantic::lint::{LintRegistry, RuleSelection};
|
use red_knot_python_semantic::lint::{LintRegistry, RuleSelection};
|
||||||
use red_knot_python_semantic::{
|
use red_knot_python_semantic::{default_lint_registry, Db as SemanticDb};
|
||||||
default_lint_registry, Db as SemanticDb, Program, ProgramSettings, PythonPlatform,
|
|
||||||
SearchPathSettings,
|
|
||||||
};
|
|
||||||
use ruff_db::files::{File, Files};
|
use ruff_db::files::{File, Files};
|
||||||
use ruff_db::system::{DbWithTestSystem, System, SystemPath, SystemPathBuf, TestSystem};
|
use ruff_db::system::{
|
||||||
|
DbWithWritableSystem, InMemorySystem, OsSystem, System, SystemPath, SystemPathBuf,
|
||||||
|
WritableSystem,
|
||||||
|
};
|
||||||
use ruff_db::vendored::VendoredFileSystem;
|
use ruff_db::vendored::VendoredFileSystem;
|
||||||
use ruff_db::{Db as SourceDb, Upcast};
|
use ruff_db::{Db as SourceDb, Upcast};
|
||||||
use ruff_python_ast::PythonVersion;
|
use ruff_notebook::{Notebook, NotebookError};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tempfile::TempDir;
|
||||||
|
|
||||||
#[salsa::db]
|
#[salsa::db]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct Db {
|
pub(crate) struct Db {
|
||||||
project_root: SystemPathBuf,
|
|
||||||
storage: salsa::Storage<Self>,
|
storage: salsa::Storage<Self>,
|
||||||
files: Files,
|
files: Files,
|
||||||
system: TestSystem,
|
system: MdtestSystem,
|
||||||
vendored: VendoredFileSystem,
|
vendored: VendoredFileSystem,
|
||||||
rule_selection: Arc<RuleSelection>,
|
rule_selection: Arc<RuleSelection>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Db {
|
impl Db {
|
||||||
pub(crate) fn setup(project_root: SystemPathBuf) -> Self {
|
pub(crate) fn setup() -> Self {
|
||||||
let rule_selection = RuleSelection::from_registry(default_lint_registry());
|
let rule_selection = RuleSelection::from_registry(default_lint_registry());
|
||||||
|
|
||||||
let db = Self {
|
Self {
|
||||||
project_root,
|
system: MdtestSystem::in_memory(),
|
||||||
storage: salsa::Storage::default(),
|
storage: salsa::Storage::default(),
|
||||||
system: TestSystem::default(),
|
|
||||||
vendored: red_knot_vendored::file_system().clone(),
|
vendored: red_knot_vendored::file_system().clone(),
|
||||||
files: Files::default(),
|
files: Files::default(),
|
||||||
rule_selection: Arc::new(rule_selection),
|
rule_selection: Arc::new(rule_selection),
|
||||||
};
|
}
|
||||||
|
|
||||||
db.memory_file_system()
|
|
||||||
.create_directory_all(&db.project_root)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
Program::from_settings(
|
|
||||||
&db,
|
|
||||||
ProgramSettings {
|
|
||||||
python_version: PythonVersion::default(),
|
|
||||||
python_platform: PythonPlatform::default(),
|
|
||||||
search_paths: SearchPathSettings::new(vec![db.project_root.clone()]),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.expect("Invalid search path settings");
|
|
||||||
|
|
||||||
db
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn project_root(&self) -> &SystemPath {
|
pub(crate) fn use_os_system_with_temp_dir(&mut self, cwd: SystemPathBuf, temp_dir: TempDir) {
|
||||||
&self.project_root
|
self.system.with_os(cwd, temp_dir);
|
||||||
}
|
Files::sync_all(self);
|
||||||
}
|
|
||||||
|
|
||||||
impl DbWithTestSystem for Db {
|
|
||||||
fn test_system(&self) -> &TestSystem {
|
|
||||||
&self.system
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_system_mut(&mut self) -> &mut TestSystem {
|
pub(crate) fn use_in_memory_system(&mut self) {
|
||||||
&mut self.system
|
self.system.with_in_memory();
|
||||||
|
Files::sync_all(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn create_directory_all(&self, path: &SystemPath) -> ruff_db::system::Result<()> {
|
||||||
|
self.system.create_directory_all(path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,3 +94,175 @@ impl SemanticDb for Db {
|
||||||
impl salsa::Database for Db {
|
impl salsa::Database for Db {
|
||||||
fn salsa_event(&self, _event: &dyn Fn() -> salsa::Event) {}
|
fn salsa_event(&self, _event: &dyn Fn() -> salsa::Event) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DbWithWritableSystem for Db {
|
||||||
|
type System = MdtestSystem;
|
||||||
|
fn writable_system(&self) -> &Self::System {
|
||||||
|
&self.system
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) struct MdtestSystem(Arc<MdtestSystemInner>);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum MdtestSystemInner {
|
||||||
|
InMemory(InMemorySystem),
|
||||||
|
Os {
|
||||||
|
os_system: OsSystem,
|
||||||
|
_temp_dir: TempDir,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MdtestSystem {
|
||||||
|
fn in_memory() -> Self {
|
||||||
|
Self(Arc::new(MdtestSystemInner::InMemory(
|
||||||
|
InMemorySystem::default(),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_system(&self) -> &dyn WritableSystem {
|
||||||
|
match &*self.0 {
|
||||||
|
MdtestSystemInner::InMemory(system) => system,
|
||||||
|
MdtestSystemInner::Os { os_system, .. } => os_system,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_os(&mut self, cwd: SystemPathBuf, temp_dir: TempDir) {
|
||||||
|
self.0 = Arc::new(MdtestSystemInner::Os {
|
||||||
|
os_system: OsSystem::new(cwd),
|
||||||
|
_temp_dir: temp_dir,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_in_memory(&mut self) {
|
||||||
|
if let MdtestSystemInner::InMemory(in_memory) = &*self.0 {
|
||||||
|
in_memory.fs().remove_all();
|
||||||
|
} else {
|
||||||
|
self.0 = Arc::new(MdtestSystemInner::InMemory(InMemorySystem::default()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize_path<'a>(&self, path: &'a SystemPath) -> Cow<'a, SystemPath> {
|
||||||
|
match &*self.0 {
|
||||||
|
MdtestSystemInner::InMemory(_) => Cow::Borrowed(path),
|
||||||
|
MdtestSystemInner::Os { os_system, .. } => {
|
||||||
|
// Make all paths relative to the current directory
|
||||||
|
// to avoid writing or reading from outside the temp directory.
|
||||||
|
let without_root: Utf8PathBuf = path
|
||||||
|
.components()
|
||||||
|
.skip_while(|component| {
|
||||||
|
matches!(
|
||||||
|
component,
|
||||||
|
Utf8Component::RootDir | Utf8Component::Prefix(..)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Cow::Owned(os_system.current_directory().join(&without_root))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl System for MdtestSystem {
|
||||||
|
fn path_metadata(
|
||||||
|
&self,
|
||||||
|
path: &SystemPath,
|
||||||
|
) -> ruff_db::system::Result<ruff_db::system::Metadata> {
|
||||||
|
self.as_system().path_metadata(&self.normalize_path(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn canonicalize_path(&self, path: &SystemPath) -> ruff_db::system::Result<SystemPathBuf> {
|
||||||
|
let canonicalized = self
|
||||||
|
.as_system()
|
||||||
|
.canonicalize_path(&self.normalize_path(path))?;
|
||||||
|
|
||||||
|
if let MdtestSystemInner::Os { os_system, .. } = &*self.0 {
|
||||||
|
// Make the path relative to the current directory
|
||||||
|
Ok(canonicalized
|
||||||
|
.strip_prefix(os_system.current_directory())
|
||||||
|
.unwrap()
|
||||||
|
.to_owned())
|
||||||
|
} else {
|
||||||
|
Ok(canonicalized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_to_string(&self, path: &SystemPath) -> ruff_db::system::Result<String> {
|
||||||
|
self.as_system().read_to_string(&self.normalize_path(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_to_notebook(&self, path: &SystemPath) -> Result<Notebook, NotebookError> {
|
||||||
|
self.as_system()
|
||||||
|
.read_to_notebook(&self.normalize_path(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_virtual_path_to_string(
|
||||||
|
&self,
|
||||||
|
path: &ruff_db::system::SystemVirtualPath,
|
||||||
|
) -> ruff_db::system::Result<String> {
|
||||||
|
self.as_system().read_virtual_path_to_string(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_virtual_path_to_notebook(
|
||||||
|
&self,
|
||||||
|
path: &ruff_db::system::SystemVirtualPath,
|
||||||
|
) -> Result<Notebook, NotebookError> {
|
||||||
|
self.as_system().read_virtual_path_to_notebook(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_directory(&self) -> &SystemPath {
|
||||||
|
self.as_system().current_directory()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn user_config_directory(&self) -> Option<SystemPathBuf> {
|
||||||
|
self.as_system().user_config_directory()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_directory<'a>(
|
||||||
|
&'a self,
|
||||||
|
path: &SystemPath,
|
||||||
|
) -> ruff_db::system::Result<
|
||||||
|
Box<dyn Iterator<Item = ruff_db::system::Result<ruff_db::system::DirectoryEntry>> + 'a>,
|
||||||
|
> {
|
||||||
|
self.as_system().read_directory(&self.normalize_path(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn walk_directory(
|
||||||
|
&self,
|
||||||
|
path: &SystemPath,
|
||||||
|
) -> ruff_db::system::walk_directory::WalkDirectoryBuilder {
|
||||||
|
self.as_system().walk_directory(&self.normalize_path(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn glob(
|
||||||
|
&self,
|
||||||
|
pattern: &str,
|
||||||
|
) -> Result<
|
||||||
|
Box<dyn Iterator<Item = Result<SystemPathBuf, ruff_db::system::GlobError>>>,
|
||||||
|
ruff_db::system::PatternError,
|
||||||
|
> {
|
||||||
|
self.as_system()
|
||||||
|
.glob(self.normalize_path(SystemPath::new(pattern)).as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any(&self) -> &dyn std::any::Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WritableSystem for MdtestSystem {
|
||||||
|
fn write_file(&self, path: &SystemPath, content: &str) -> ruff_db::system::Result<()> {
|
||||||
|
self.as_system()
|
||||||
|
.write_file(&self.normalize_path(path), content)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_directory_all(&self, path: &SystemPath) -> ruff_db::system::Result<()> {
|
||||||
|
self.as_system()
|
||||||
|
.create_directory_all(&self.normalize_path(path))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -148,14 +148,14 @@ mod tests {
|
||||||
use ruff_db::diagnostic::{DiagnosticId, LintName, Severity, Span};
|
use ruff_db::diagnostic::{DiagnosticId, LintName, Severity, Span};
|
||||||
use ruff_db::files::{system_path_to_file, File};
|
use ruff_db::files::{system_path_to_file, File};
|
||||||
use ruff_db::source::line_index;
|
use ruff_db::source::line_index;
|
||||||
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
use ruff_db::system::DbWithWritableSystem as _;
|
||||||
use ruff_source_file::OneIndexed;
|
use ruff_source_file::OneIndexed;
|
||||||
use ruff_text_size::{TextRange, TextSize};
|
use ruff_text_size::{TextRange, TextSize};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sort_and_group() {
|
fn sort_and_group() {
|
||||||
let mut db = Db::setup(SystemPathBuf::from("/src"));
|
let mut db = Db::setup();
|
||||||
db.write_file("/src/test.py", "one\ntwo\n").unwrap();
|
db.write_file("/src/test.py", "one\ntwo\n").unwrap();
|
||||||
let file = system_path_to_file(&db, "/src/test.py").unwrap();
|
let file = system_path_to_file(&db, "/src/test.py").unwrap();
|
||||||
let lines = line_index(&db, file);
|
let lines = line_index(&db, file);
|
||||||
|
|
|
@ -2,14 +2,15 @@ use crate::config::Log;
|
||||||
use crate::parser::{BacktickOffsets, EmbeddedFileSourceMap};
|
use crate::parser::{BacktickOffsets, EmbeddedFileSourceMap};
|
||||||
use camino::Utf8Path;
|
use camino::Utf8Path;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
|
use config::SystemKind;
|
||||||
use parser as test_parser;
|
use parser as test_parser;
|
||||||
use red_knot_python_semantic::types::check_types;
|
use red_knot_python_semantic::types::check_types;
|
||||||
use red_knot_python_semantic::{Program, ProgramSettings, PythonPath, SearchPathSettings};
|
use red_knot_python_semantic::{Program, ProgramSettings, PythonPath, SearchPathSettings};
|
||||||
use ruff_db::diagnostic::{DisplayDiagnosticConfig, OldDiagnosticTrait, OldParseDiagnostic};
|
use ruff_db::diagnostic::{DisplayDiagnosticConfig, OldDiagnosticTrait, OldParseDiagnostic};
|
||||||
use ruff_db::files::{system_path_to_file, File, Files};
|
use ruff_db::files::{system_path_to_file, File};
|
||||||
use ruff_db::panic::catch_unwind;
|
use ruff_db::panic::catch_unwind;
|
||||||
use ruff_db::parsed::parsed_module;
|
use ruff_db::parsed::parsed_module;
|
||||||
use ruff_db::system::{DbWithTestSystem, SystemPath, SystemPathBuf};
|
use ruff_db::system::{DbWithWritableSystem as _, SystemPath, SystemPathBuf};
|
||||||
use ruff_db::testing::{setup_logging, setup_logging_with_filter};
|
use ruff_db::testing::{setup_logging, setup_logging_with_filter};
|
||||||
use ruff_source_file::{LineIndex, OneIndexed};
|
use ruff_source_file::{LineIndex, OneIndexed};
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
@ -42,7 +43,7 @@ pub fn run(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut db = db::Db::setup(SystemPathBuf::from("/src"));
|
let mut db = db::Db::setup();
|
||||||
|
|
||||||
let filter = std::env::var(MDTEST_TEST_FILTER).ok();
|
let filter = std::env::var(MDTEST_TEST_FILTER).ok();
|
||||||
let mut any_failures = false;
|
let mut any_failures = false;
|
||||||
|
@ -56,10 +57,6 @@ pub fn run(
|
||||||
Log::Filter(filter) => setup_logging_with_filter(filter),
|
Log::Filter(filter) => setup_logging_with_filter(filter),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Remove all files so that the db is in a "fresh" state.
|
|
||||||
db.memory_file_system().remove_all();
|
|
||||||
Files::sync_all(&mut db);
|
|
||||||
|
|
||||||
if let Err(failures) = run_test(&mut db, relative_fixture_path, snapshot_path, &test) {
|
if let Err(failures) = run_test(&mut db, relative_fixture_path, snapshot_path, &test) {
|
||||||
any_failures = true;
|
any_failures = true;
|
||||||
println!("\n{}\n", test.name().bold().underline());
|
println!("\n{}\n", test.name().bold().underline());
|
||||||
|
@ -104,9 +101,30 @@ fn run_test(
|
||||||
snapshot_path: &Utf8Path,
|
snapshot_path: &Utf8Path,
|
||||||
test: &parser::MarkdownTest,
|
test: &parser::MarkdownTest,
|
||||||
) -> Result<(), Failures> {
|
) -> Result<(), Failures> {
|
||||||
let project_root = db.project_root().to_path_buf();
|
// Initialize the system and remove all files and directories to reset the system to a clean state.
|
||||||
let src_path = SystemPathBuf::from("/src");
|
match test.configuration().system.unwrap_or_default() {
|
||||||
let custom_typeshed_path = test.configuration().typeshed().map(SystemPath::to_path_buf);
|
SystemKind::InMemory => {
|
||||||
|
db.use_in_memory_system();
|
||||||
|
}
|
||||||
|
SystemKind::Os => {
|
||||||
|
let dir = tempfile::TempDir::new().expect("Creating a temporary directory to succeed");
|
||||||
|
let root_path = dir
|
||||||
|
.path()
|
||||||
|
.canonicalize()
|
||||||
|
.expect("Canonicalizing to succeed");
|
||||||
|
let root_path = SystemPathBuf::from_path_buf(root_path)
|
||||||
|
.expect("Temp directory to be a valid UTF8 path");
|
||||||
|
|
||||||
|
db.use_os_system_with_temp_dir(root_path, dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let project_root = SystemPathBuf::from("/src");
|
||||||
|
db.create_directory_all(&project_root)
|
||||||
|
.expect("Creating the project root to succeed");
|
||||||
|
|
||||||
|
let src_path = project_root.clone();
|
||||||
|
let custom_typeshed_path = test.configuration().typeshed();
|
||||||
let mut typeshed_files = vec![];
|
let mut typeshed_files = vec![];
|
||||||
let mut has_custom_versions_file = false;
|
let mut has_custom_versions_file = false;
|
||||||
|
|
||||||
|
@ -124,7 +142,7 @@ fn run_test(
|
||||||
|
|
||||||
let full_path = embedded.full_path(&project_root);
|
let full_path = embedded.full_path(&project_root);
|
||||||
|
|
||||||
if let Some(ref typeshed_path) = custom_typeshed_path {
|
if let Some(typeshed_path) = custom_typeshed_path {
|
||||||
if let Ok(relative_path) = full_path.strip_prefix(typeshed_path.join("stdlib")) {
|
if let Ok(relative_path) = full_path.strip_prefix(typeshed_path.join("stdlib")) {
|
||||||
if relative_path.as_str() == "VERSIONS" {
|
if relative_path.as_str() == "VERSIONS" {
|
||||||
has_custom_versions_file = true;
|
has_custom_versions_file = true;
|
||||||
|
@ -151,7 +169,7 @@ fn run_test(
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Create a custom typeshed `VERSIONS` file if none was provided.
|
// Create a custom typeshed `VERSIONS` file if none was provided.
|
||||||
if let Some(ref typeshed_path) = custom_typeshed_path {
|
if let Some(typeshed_path) = custom_typeshed_path {
|
||||||
if !has_custom_versions_file {
|
if !has_custom_versions_file {
|
||||||
let versions_file = typeshed_path.join("stdlib/VERSIONS");
|
let versions_file = typeshed_path.join("stdlib/VERSIONS");
|
||||||
let contents = typeshed_files
|
let contents = typeshed_files
|
||||||
|
@ -170,25 +188,26 @@ fn run_test(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Program::get(db)
|
let settings = ProgramSettings {
|
||||||
.update_from_settings(
|
python_version: test.configuration().python_version().unwrap_or_default(),
|
||||||
db,
|
python_platform: test.configuration().python_platform().unwrap_or_default(),
|
||||||
ProgramSettings {
|
search_paths: SearchPathSettings {
|
||||||
python_version: test.configuration().python_version().unwrap_or_default(),
|
src_roots: vec![src_path],
|
||||||
python_platform: test.configuration().python_platform().unwrap_or_default(),
|
extra_paths: test
|
||||||
search_paths: SearchPathSettings {
|
.configuration()
|
||||||
src_roots: vec![src_path],
|
.extra_paths()
|
||||||
extra_paths: test
|
.unwrap_or_default()
|
||||||
.configuration()
|
.to_vec(),
|
||||||
.extra_paths()
|
custom_typeshed: custom_typeshed_path.map(SystemPath::to_path_buf),
|
||||||
.unwrap_or_default()
|
python_path: PythonPath::KnownSitePackages(vec![]),
|
||||||
.to_vec(),
|
},
|
||||||
custom_typeshed: custom_typeshed_path,
|
};
|
||||||
python_path: PythonPath::KnownSitePackages(vec![]),
|
|
||||||
},
|
match Program::try_get(db) {
|
||||||
},
|
Some(program) => program.update_from_settings(db, settings),
|
||||||
)
|
None => Program::from_settings(db, settings).map(|_| ()),
|
||||||
.expect("Failed to update Program settings in TestDb");
|
}
|
||||||
|
.expect("Failed to update Program settings in TestDb");
|
||||||
|
|
||||||
// When snapshot testing is enabled, this is populated with
|
// When snapshot testing is enabled, this is populated with
|
||||||
// all diagnostics. Otherwise it remains empty.
|
// all diagnostics. Otherwise it remains empty.
|
||||||
|
|
|
@ -349,7 +349,7 @@ mod tests {
|
||||||
use super::FailuresByLine;
|
use super::FailuresByLine;
|
||||||
use ruff_db::diagnostic::{DiagnosticId, OldDiagnosticTrait, Severity, Span};
|
use ruff_db::diagnostic::{DiagnosticId, OldDiagnosticTrait, Severity, Span};
|
||||||
use ruff_db::files::{system_path_to_file, File};
|
use ruff_db::files::{system_path_to_file, File};
|
||||||
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
use ruff_db::system::DbWithWritableSystem as _;
|
||||||
use ruff_python_trivia::textwrap::dedent;
|
use ruff_python_trivia::textwrap::dedent;
|
||||||
use ruff_source_file::OneIndexed;
|
use ruff_source_file::OneIndexed;
|
||||||
use ruff_text_size::TextRange;
|
use ruff_text_size::TextRange;
|
||||||
|
@ -413,7 +413,7 @@ mod tests {
|
||||||
) -> Result<(), FailuresByLine> {
|
) -> Result<(), FailuresByLine> {
|
||||||
colored::control::set_override(false);
|
colored::control::set_override(false);
|
||||||
|
|
||||||
let mut db = crate::db::Db::setup(SystemPathBuf::from("/src"));
|
let mut db = crate::db::Db::setup();
|
||||||
db.write_file("/src/test.py", source).unwrap();
|
db.write_file("/src/test.py", source).unwrap();
|
||||||
let file = system_path_to_file(&db, "/src/test.py").unwrap();
|
let file = system_path_to_file(&db, "/src/test.py").unwrap();
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,7 @@ impl Workspace {
|
||||||
pub fn open_file(&mut self, path: &str, contents: &str) -> Result<FileHandle, Error> {
|
pub fn open_file(&mut self, path: &str, contents: &str) -> Result<FileHandle, Error> {
|
||||||
self.system
|
self.system
|
||||||
.fs
|
.fs
|
||||||
.write_file(path, contents)
|
.write_file_all(path, contents)
|
||||||
.map_err(into_error)?;
|
.map_err(into_error)?;
|
||||||
|
|
||||||
let file = system_path_to_file(&self.db, path).expect("File to exist");
|
let file = system_path_to_file(&self.db, path).expect("File to exist");
|
||||||
|
|
|
@ -106,7 +106,7 @@ fn setup_case() -> Case {
|
||||||
let system = TestSystem::default();
|
let system = TestSystem::default();
|
||||||
let fs = system.memory_file_system().clone();
|
let fs = system.memory_file_system().clone();
|
||||||
|
|
||||||
fs.write_files(
|
fs.write_files_all(
|
||||||
TOMLLIB_FILES
|
TOMLLIB_FILES
|
||||||
.iter()
|
.iter()
|
||||||
.map(|file| (tomllib_path(file), file.code().to_string())),
|
.map(|file| (tomllib_path(file), file.code().to_string())),
|
||||||
|
@ -173,7 +173,7 @@ fn benchmark_incremental(criterion: &mut Criterion) {
|
||||||
assert_diagnostics(&case.db, &result);
|
assert_diagnostics(&case.db, &result);
|
||||||
|
|
||||||
case.fs
|
case.fs
|
||||||
.write_file(
|
.write_file_all(
|
||||||
&case.re_path,
|
&case.re_path,
|
||||||
format!("{}\n# A comment\n", source_text(&case.db, case.re).as_str()),
|
format!("{}\n# A comment\n", source_text(&case.db, case.re).as_str()),
|
||||||
)
|
)
|
||||||
|
|
|
@ -496,7 +496,7 @@ impl std::error::Error for FileError {}
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::file_revision::FileRevision;
|
use crate::file_revision::FileRevision;
|
||||||
use crate::files::{system_path_to_file, vendored_path_to_file, FileError};
|
use crate::files::{system_path_to_file, vendored_path_to_file, FileError};
|
||||||
use crate::system::DbWithTestSystem;
|
use crate::system::DbWithWritableSystem as _;
|
||||||
use crate::tests::TestDb;
|
use crate::tests::TestDb;
|
||||||
use crate::vendored::VendoredFileSystemBuilder;
|
use crate::vendored::VendoredFileSystemBuilder;
|
||||||
use zip::CompressionMethod;
|
use zip::CompressionMethod;
|
||||||
|
|
|
@ -85,7 +85,9 @@ impl Eq for ParsedModule {}
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::files::{system_path_to_file, vendored_path_to_file};
|
use crate::files::{system_path_to_file, vendored_path_to_file};
|
||||||
use crate::parsed::parsed_module;
|
use crate::parsed::parsed_module;
|
||||||
use crate::system::{DbWithTestSystem, SystemPath, SystemVirtualPath};
|
use crate::system::{
|
||||||
|
DbWithTestSystem, DbWithWritableSystem as _, SystemPath, SystemVirtualPath,
|
||||||
|
};
|
||||||
use crate::tests::TestDb;
|
use crate::tests::TestDb;
|
||||||
use crate::vendored::{VendoredFileSystemBuilder, VendoredPath};
|
use crate::vendored::{VendoredFileSystemBuilder, VendoredPath};
|
||||||
use crate::Db;
|
use crate::Db;
|
||||||
|
@ -96,7 +98,7 @@ mod tests {
|
||||||
let mut db = TestDb::new();
|
let mut db = TestDb::new();
|
||||||
let path = "test.py";
|
let path = "test.py";
|
||||||
|
|
||||||
db.write_file(path, "x = 10".to_string())?;
|
db.write_file(path, "x = 10")?;
|
||||||
|
|
||||||
let file = system_path_to_file(&db, path).unwrap();
|
let file = system_path_to_file(&db, path).unwrap();
|
||||||
|
|
||||||
|
@ -112,7 +114,7 @@ mod tests {
|
||||||
let mut db = TestDb::new();
|
let mut db = TestDb::new();
|
||||||
let path = SystemPath::new("test.ipynb");
|
let path = SystemPath::new("test.ipynb");
|
||||||
|
|
||||||
db.write_file(path, "%timeit a = b".to_string())?;
|
db.write_file(path, "%timeit a = b")?;
|
||||||
|
|
||||||
let file = system_path_to_file(&db, path).unwrap();
|
let file = system_path_to_file(&db, path).unwrap();
|
||||||
|
|
||||||
|
|
|
@ -176,7 +176,7 @@ mod tests {
|
||||||
|
|
||||||
use crate::files::system_path_to_file;
|
use crate::files::system_path_to_file;
|
||||||
use crate::source::{line_index, source_text};
|
use crate::source::{line_index, source_text};
|
||||||
use crate::system::{DbWithTestSystem, SystemPath};
|
use crate::system::{DbWithWritableSystem as _, SystemPath};
|
||||||
use crate::tests::TestDb;
|
use crate::tests::TestDb;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -184,13 +184,13 @@ mod tests {
|
||||||
let mut db = TestDb::new();
|
let mut db = TestDb::new();
|
||||||
let path = SystemPath::new("test.py");
|
let path = SystemPath::new("test.py");
|
||||||
|
|
||||||
db.write_file(path, "x = 10".to_string())?;
|
db.write_file(path, "x = 10")?;
|
||||||
|
|
||||||
let file = system_path_to_file(&db, path).unwrap();
|
let file = system_path_to_file(&db, path).unwrap();
|
||||||
|
|
||||||
assert_eq!(source_text(&db, file).as_str(), "x = 10");
|
assert_eq!(source_text(&db, file).as_str(), "x = 10");
|
||||||
|
|
||||||
db.write_file(path, "x = 20".to_string()).unwrap();
|
db.write_file(path, "x = 20").unwrap();
|
||||||
|
|
||||||
assert_eq!(source_text(&db, file).as_str(), "x = 20");
|
assert_eq!(source_text(&db, file).as_str(), "x = 20");
|
||||||
|
|
||||||
|
@ -202,7 +202,7 @@ mod tests {
|
||||||
let mut db = TestDb::new();
|
let mut db = TestDb::new();
|
||||||
let path = SystemPath::new("test.py");
|
let path = SystemPath::new("test.py");
|
||||||
|
|
||||||
db.write_file(path, "x = 10".to_string())?;
|
db.write_file(path, "x = 10")?;
|
||||||
|
|
||||||
let file = system_path_to_file(&db, path).unwrap();
|
let file = system_path_to_file(&db, path).unwrap();
|
||||||
|
|
||||||
|
@ -228,7 +228,7 @@ mod tests {
|
||||||
let mut db = TestDb::new();
|
let mut db = TestDb::new();
|
||||||
let path = SystemPath::new("test.py");
|
let path = SystemPath::new("test.py");
|
||||||
|
|
||||||
db.write_file(path, "x = 10\ny = 20".to_string())?;
|
db.write_file(path, "x = 10\ny = 20")?;
|
||||||
|
|
||||||
let file = system_path_to_file(&db, path).unwrap();
|
let file = system_path_to_file(&db, path).unwrap();
|
||||||
let index = line_index(&db, file);
|
let index = line_index(&db, file);
|
||||||
|
|
|
@ -12,7 +12,7 @@ use std::error::Error;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::{fmt, io};
|
use std::{fmt, io};
|
||||||
pub use test::{DbWithTestSystem, InMemorySystem, TestSystem};
|
pub use test::{DbWithTestSystem, DbWithWritableSystem, InMemorySystem, TestSystem};
|
||||||
use walk_directory::WalkDirectoryBuilder;
|
use walk_directory::WalkDirectoryBuilder;
|
||||||
|
|
||||||
use crate::file_revision::FileRevision;
|
use crate::file_revision::FileRevision;
|
||||||
|
@ -161,6 +161,15 @@ pub trait System: Debug {
|
||||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
|
fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// System trait for non-readonly systems.
|
||||||
|
pub trait WritableSystem: System {
|
||||||
|
/// Writes the given content to the file at the given path.
|
||||||
|
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()>;
|
||||||
|
|
||||||
|
/// Creates a directory at `path` as well as any intermediate directories.
|
||||||
|
fn create_directory_all(&self, path: &SystemPath) -> Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub struct Metadata {
|
pub struct Metadata {
|
||||||
revision: FileRevision,
|
revision: FileRevision,
|
||||||
|
|
|
@ -153,28 +153,9 @@ impl MemoryFileSystem {
|
||||||
virtual_files.contains_key(&path.to_path_buf())
|
virtual_files.contains_key(&path.to_path_buf())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes the files to the file system.
|
|
||||||
///
|
|
||||||
/// The operation overrides existing files with the same normalized path.
|
|
||||||
///
|
|
||||||
/// Enclosing directories are automatically created if they don't exist.
|
|
||||||
pub fn write_files<P, C>(&self, files: impl IntoIterator<Item = (P, C)>) -> Result<()>
|
|
||||||
where
|
|
||||||
P: AsRef<SystemPath>,
|
|
||||||
C: ToString,
|
|
||||||
{
|
|
||||||
for (path, content) in files {
|
|
||||||
self.write_file(path.as_ref(), content.to_string())?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stores a new file in the file system.
|
/// Stores a new file in the file system.
|
||||||
///
|
///
|
||||||
/// The operation overrides the content for an existing file with the same normalized `path`.
|
/// The operation overrides the content for an existing file with the same normalized `path`.
|
||||||
///
|
|
||||||
/// Enclosing directories are automatically created if they don't exist.
|
|
||||||
pub fn write_file(&self, path: impl AsRef<SystemPath>, content: impl ToString) -> Result<()> {
|
pub fn write_file(&self, path: impl AsRef<SystemPath>, content: impl ToString) -> Result<()> {
|
||||||
let mut by_path = self.inner.by_path.write().unwrap();
|
let mut by_path = self.inner.by_path.write().unwrap();
|
||||||
|
|
||||||
|
@ -187,6 +168,42 @@ impl MemoryFileSystem {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Writes the files to the file system.
|
||||||
|
///
|
||||||
|
/// The operation overrides existing files with the same normalized path.
|
||||||
|
///
|
||||||
|
/// Enclosing directories are automatically created if they don't exist.
|
||||||
|
pub fn write_files_all<P, C>(&self, files: impl IntoIterator<Item = (P, C)>) -> Result<()>
|
||||||
|
where
|
||||||
|
P: AsRef<SystemPath>,
|
||||||
|
C: ToString,
|
||||||
|
{
|
||||||
|
for (path, content) in files {
|
||||||
|
self.write_file_all(path.as_ref(), content.to_string())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stores a new file in the file system.
|
||||||
|
///
|
||||||
|
/// The operation overrides the content for an existing file with the same normalized `path`.
|
||||||
|
///
|
||||||
|
/// Enclosing directories are automatically created if they don't exist.
|
||||||
|
pub fn write_file_all(
|
||||||
|
&self,
|
||||||
|
path: impl AsRef<SystemPath>,
|
||||||
|
content: impl ToString,
|
||||||
|
) -> Result<()> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
self.create_directory_all(parent)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.write_file(path, content)
|
||||||
|
}
|
||||||
|
|
||||||
/// Stores a new virtual file in the file system.
|
/// Stores a new virtual file in the file system.
|
||||||
///
|
///
|
||||||
/// The operation overrides the content for an existing virtual file with the same `path`.
|
/// The operation overrides the content for an existing virtual file with the same `path`.
|
||||||
|
@ -486,7 +503,11 @@ fn get_or_create_file<'a>(
|
||||||
normalized: &Utf8Path,
|
normalized: &Utf8Path,
|
||||||
) -> Result<&'a mut File> {
|
) -> Result<&'a mut File> {
|
||||||
if let Some(parent) = normalized.parent() {
|
if let Some(parent) = normalized.parent() {
|
||||||
create_dir_all(paths, parent)?;
|
let parent_entry = paths.get(parent).ok_or_else(not_found)?;
|
||||||
|
|
||||||
|
if parent_entry.is_file() {
|
||||||
|
return Err(not_a_directory());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let entry = paths.entry(normalized.to_path_buf()).or_insert_with(|| {
|
let entry = paths.entry(normalized.to_path_buf()).or_insert_with(|| {
|
||||||
|
@ -719,7 +740,7 @@ mod tests {
|
||||||
P: AsRef<SystemPath>,
|
P: AsRef<SystemPath>,
|
||||||
{
|
{
|
||||||
let fs = MemoryFileSystem::new();
|
let fs = MemoryFileSystem::new();
|
||||||
fs.write_files(files.into_iter().map(|path| (path, "")))
|
fs.write_files_all(files.into_iter().map(|path| (path, "")))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
fs
|
fs
|
||||||
|
@ -822,29 +843,25 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn write_file_fails_if_a_component_is_a_file() {
|
fn write_file_fails_if_a_parent_directory_is_missing() {
|
||||||
let fs = with_files(["a/b.py"]);
|
let fs = with_files(["c.py"]);
|
||||||
|
|
||||||
let error = fs
|
let error = fs
|
||||||
.write_file(SystemPath::new("a/b.py/c"), "content".to_string())
|
.write_file(SystemPath::new("a/b.py"), "content".to_string())
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
|
|
||||||
assert_eq!(error.kind(), ErrorKind::Other);
|
assert_eq!(error.kind(), ErrorKind::NotFound);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn write_file_fails_if_path_points_to_a_directory() -> Result<()> {
|
fn write_file_all_fails_if_a_component_is_a_file() {
|
||||||
let fs = MemoryFileSystem::new();
|
let fs = with_files(["a/b.py"]);
|
||||||
|
|
||||||
fs.create_directory_all("a")?;
|
|
||||||
|
|
||||||
let error = fs
|
let error = fs
|
||||||
.write_file(SystemPath::new("a"), "content".to_string())
|
.write_file_all(SystemPath::new("a/b.py/c"), "content".to_string())
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
|
|
||||||
assert_eq!(error.kind(), ErrorKind::Other);
|
assert_eq!(error.kind(), ErrorKind::Other);
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -864,7 +881,7 @@ mod tests {
|
||||||
let fs = MemoryFileSystem::new();
|
let fs = MemoryFileSystem::new();
|
||||||
let path = SystemPath::new("a.py");
|
let path = SystemPath::new("a.py");
|
||||||
|
|
||||||
fs.write_file(path, "Test content".to_string())?;
|
fs.write_file_all(path, "Test content".to_string())?;
|
||||||
|
|
||||||
assert_eq!(fs.read_to_string(path)?, "Test content");
|
assert_eq!(fs.read_to_string(path)?, "Test content");
|
||||||
|
|
||||||
|
@ -895,6 +912,21 @@ mod tests {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn write_file_fails_if_path_points_to_a_directory() -> Result<()> {
|
||||||
|
let fs = MemoryFileSystem::new();
|
||||||
|
|
||||||
|
fs.create_directory_all("a")?;
|
||||||
|
|
||||||
|
let error = fs
|
||||||
|
.write_file(SystemPath::new("a"), "content".to_string())
|
||||||
|
.unwrap_err();
|
||||||
|
|
||||||
|
assert_eq!(error.kind(), ErrorKind::Other);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn read_fails_if_virtual_path_doesnt_exit() {
|
fn read_fails_if_virtual_path_doesnt_exit() {
|
||||||
let fs = MemoryFileSystem::new();
|
let fs = MemoryFileSystem::new();
|
||||||
|
@ -1046,7 +1078,7 @@ mod tests {
|
||||||
let root = SystemPath::new("/src");
|
let root = SystemPath::new("/src");
|
||||||
let system = MemoryFileSystem::with_current_directory(root);
|
let system = MemoryFileSystem::with_current_directory(root);
|
||||||
|
|
||||||
system.write_files([
|
system.write_files_all([
|
||||||
(root.join("foo.py"), "print('foo')"),
|
(root.join("foo.py"), "print('foo')"),
|
||||||
(root.join("a/bar.py"), "print('bar')"),
|
(root.join("a/bar.py"), "print('bar')"),
|
||||||
(root.join("a/baz.py"), "print('baz')"),
|
(root.join("a/baz.py"), "print('baz')"),
|
||||||
|
@ -1105,7 +1137,7 @@ mod tests {
|
||||||
let root = SystemPath::new("/src");
|
let root = SystemPath::new("/src");
|
||||||
let system = MemoryFileSystem::with_current_directory(root);
|
let system = MemoryFileSystem::with_current_directory(root);
|
||||||
|
|
||||||
system.write_files([
|
system.write_files_all([
|
||||||
(root.join("foo.py"), "print('foo')"),
|
(root.join("foo.py"), "print('foo')"),
|
||||||
(root.join("a/bar.py"), "print('bar')"),
|
(root.join("a/bar.py"), "print('bar')"),
|
||||||
(root.join("a/.baz.py"), "print('baz')"),
|
(root.join("a/.baz.py"), "print('baz')"),
|
||||||
|
@ -1151,7 +1183,7 @@ mod tests {
|
||||||
let root = SystemPath::new("/src");
|
let root = SystemPath::new("/src");
|
||||||
let system = MemoryFileSystem::with_current_directory(root);
|
let system = MemoryFileSystem::with_current_directory(root);
|
||||||
|
|
||||||
system.write_file(root.join("foo.py"), "print('foo')")?;
|
system.write_file_all(root.join("foo.py"), "print('foo')")?;
|
||||||
|
|
||||||
let writer = DirectoryEntryToString::new(root.to_path_buf());
|
let writer = DirectoryEntryToString::new(root.to_path_buf());
|
||||||
|
|
||||||
|
@ -1181,7 +1213,7 @@ mod tests {
|
||||||
let root = SystemPath::new("/src");
|
let root = SystemPath::new("/src");
|
||||||
let fs = MemoryFileSystem::with_current_directory(root);
|
let fs = MemoryFileSystem::with_current_directory(root);
|
||||||
|
|
||||||
fs.write_files([
|
fs.write_files_all([
|
||||||
(root.join("foo.py"), "print('foo')"),
|
(root.join("foo.py"), "print('foo')"),
|
||||||
(root.join("a/bar.py"), "print('bar')"),
|
(root.join("a/bar.py"), "print('bar')"),
|
||||||
(root.join("a/.baz.py"), "print('baz')"),
|
(root.join("a/.baz.py"), "print('baz')"),
|
||||||
|
|
|
@ -7,7 +7,7 @@ use ruff_notebook::{Notebook, NotebookError};
|
||||||
|
|
||||||
use crate::system::{
|
use crate::system::{
|
||||||
DirectoryEntry, FileType, GlobError, GlobErrorKind, Metadata, Result, System, SystemPath,
|
DirectoryEntry, FileType, GlobError, GlobErrorKind, Metadata, Result, System, SystemPath,
|
||||||
SystemPathBuf, SystemVirtualPath,
|
SystemPathBuf, SystemVirtualPath, WritableSystem,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::walk_directory::{
|
use super::walk_directory::{
|
||||||
|
@ -191,6 +191,16 @@ impl System for OsSystem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl WritableSystem for OsSystem {
|
||||||
|
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()> {
|
||||||
|
std::fs::write(path.as_std_path(), content)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_directory_all(&self, path: &SystemPath) -> Result<()> {
|
||||||
|
std::fs::create_dir_all(path.as_std_path())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct OsDirectoryWalker;
|
struct OsDirectoryWalker;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use glob::PatternError;
|
use glob::PatternError;
|
||||||
use ruff_notebook::{Notebook, NotebookError};
|
use ruff_notebook::{Notebook, NotebookError};
|
||||||
use ruff_python_trivia::textwrap;
|
|
||||||
use std::panic::RefUnwindSafe;
|
use std::panic::RefUnwindSafe;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
@ -12,6 +11,7 @@ use crate::system::{
|
||||||
use crate::Db;
|
use crate::Db;
|
||||||
|
|
||||||
use super::walk_directory::WalkDirectoryBuilder;
|
use super::walk_directory::WalkDirectoryBuilder;
|
||||||
|
use super::WritableSystem;
|
||||||
|
|
||||||
/// System implementation intended for testing.
|
/// System implementation intended for testing.
|
||||||
///
|
///
|
||||||
|
@ -22,10 +22,16 @@ use super::walk_directory::WalkDirectoryBuilder;
|
||||||
/// Don't use this system for production code. It's intended for testing only.
|
/// Don't use this system for production code. It's intended for testing only.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct TestSystem {
|
pub struct TestSystem {
|
||||||
inner: Arc<dyn System + RefUnwindSafe + Send + Sync>,
|
inner: Arc<dyn WritableSystem + RefUnwindSafe + Send + Sync>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestSystem {
|
impl TestSystem {
|
||||||
|
pub fn new(inner: impl WritableSystem + RefUnwindSafe + Send + Sync + 'static) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(inner),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the [`InMemorySystem`].
|
/// Returns the [`InMemorySystem`].
|
||||||
///
|
///
|
||||||
/// ## Panics
|
/// ## Panics
|
||||||
|
@ -50,12 +56,12 @@ impl TestSystem {
|
||||||
|
|
||||||
fn use_system<S>(&mut self, system: S)
|
fn use_system<S>(&mut self, system: S)
|
||||||
where
|
where
|
||||||
S: System + Send + Sync + RefUnwindSafe + 'static,
|
S: WritableSystem + Send + Sync + RefUnwindSafe + 'static,
|
||||||
{
|
{
|
||||||
self.inner = Arc::new(system);
|
self.inner = Arc::new(system);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn system(&self) -> &dyn System {
|
pub fn system(&self) -> &dyn WritableSystem {
|
||||||
&*self.inner
|
&*self.inner
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -134,6 +140,73 @@ impl Default for TestSystem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl WritableSystem for TestSystem {
|
||||||
|
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()> {
|
||||||
|
self.system().write_file(path, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_directory_all(&self, path: &SystemPath) -> Result<()> {
|
||||||
|
self.system().create_directory_all(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extension trait for databases that use a [`WritableSystem`].
|
||||||
|
///
|
||||||
|
/// Provides various helper function that ease testing.
|
||||||
|
pub trait DbWithWritableSystem: Db + Sized {
|
||||||
|
type System: WritableSystem;
|
||||||
|
|
||||||
|
fn writable_system(&self) -> &Self::System;
|
||||||
|
|
||||||
|
/// Writes the content of the given file and notifies the Db about the change.
|
||||||
|
fn write_file(&mut self, path: impl AsRef<SystemPath>, content: impl AsRef<str>) -> Result<()> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
match self.writable_system().write_file(path, content.as_ref()) {
|
||||||
|
Ok(()) => {
|
||||||
|
File::sync_path(self, path);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
self.writable_system().create_directory_all(parent)?;
|
||||||
|
|
||||||
|
for ancestor in parent.ancestors() {
|
||||||
|
File::sync_path(self, ancestor);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.writable_system().write_file(path, content.as_ref())?;
|
||||||
|
File::sync_path(self, path);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err => err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes auto-dedented text to a file.
|
||||||
|
fn write_dedented(&mut self, path: &str, content: &str) -> Result<()> {
|
||||||
|
self.write_file(path, ruff_python_trivia::textwrap::dedent(content))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes the content of the given files and notifies the Db about the change.
|
||||||
|
fn write_files<P, C, I>(&mut self, files: I) -> Result<()>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = (P, C)>,
|
||||||
|
P: AsRef<SystemPath>,
|
||||||
|
C: AsRef<str>,
|
||||||
|
{
|
||||||
|
for (path, content) in files {
|
||||||
|
self.write_file(path, content)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Extension trait for databases that use [`TestSystem`].
|
/// Extension trait for databases that use [`TestSystem`].
|
||||||
///
|
///
|
||||||
/// Provides various helper function that ease testing.
|
/// Provides various helper function that ease testing.
|
||||||
|
@ -142,35 +215,6 @@ pub trait DbWithTestSystem: Db + Sized {
|
||||||
|
|
||||||
fn test_system_mut(&mut self) -> &mut TestSystem;
|
fn test_system_mut(&mut self) -> &mut TestSystem;
|
||||||
|
|
||||||
/// Writes the content of the given file and notifies the Db about the change.
|
|
||||||
///
|
|
||||||
/// ## Panics
|
|
||||||
/// If the db isn't using the [`InMemorySystem`].
|
|
||||||
fn write_file(&mut self, path: impl AsRef<SystemPath>, content: impl ToString) -> Result<()> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
|
|
||||||
let memory_fs = self.test_system().memory_file_system();
|
|
||||||
|
|
||||||
let sync_ancestors = path
|
|
||||||
.parent()
|
|
||||||
.is_some_and(|parent| !memory_fs.exists(parent));
|
|
||||||
let result = memory_fs.write_file(path, content);
|
|
||||||
|
|
||||||
if result.is_ok() {
|
|
||||||
File::sync_path(self, path);
|
|
||||||
|
|
||||||
// Sync the ancestor paths if the path's parent
|
|
||||||
// directory didn't exist before.
|
|
||||||
if sync_ancestors {
|
|
||||||
for ancestor in path.ancestors() {
|
|
||||||
File::sync_path(self, ancestor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Writes the content of the given virtual file.
|
/// Writes the content of the given virtual file.
|
||||||
///
|
///
|
||||||
/// ## Panics
|
/// ## Panics
|
||||||
|
@ -182,32 +226,6 @@ pub trait DbWithTestSystem: Db + Sized {
|
||||||
.write_virtual_file(path, content);
|
.write_virtual_file(path, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes auto-dedented text to a file.
|
|
||||||
///
|
|
||||||
/// ## Panics
|
|
||||||
/// If the db isn't using the [`InMemorySystem`].
|
|
||||||
fn write_dedented(&mut self, path: &str, content: &str) -> crate::system::Result<()> {
|
|
||||||
self.write_file(path, textwrap::dedent(content))?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Writes the content of the given files and notifies the Db about the change.
|
|
||||||
///
|
|
||||||
/// ## Panics
|
|
||||||
/// If the db isn't using the [`InMemorySystem`].
|
|
||||||
fn write_files<P, C, I>(&mut self, files: I) -> crate::system::Result<()>
|
|
||||||
where
|
|
||||||
I: IntoIterator<Item = (P, C)>,
|
|
||||||
P: AsRef<SystemPath>,
|
|
||||||
C: ToString,
|
|
||||||
{
|
|
||||||
for (path, content) in files {
|
|
||||||
self.write_file(path, content)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Uses the given system instead of the testing system.
|
/// Uses the given system instead of the testing system.
|
||||||
///
|
///
|
||||||
/// This useful for testing advanced file system features like permissions, symlinks, etc.
|
/// This useful for testing advanced file system features like permissions, symlinks, etc.
|
||||||
|
@ -215,7 +233,7 @@ pub trait DbWithTestSystem: Db + Sized {
|
||||||
/// Note that any files written to the memory file system won't be copied over.
|
/// Note that any files written to the memory file system won't be copied over.
|
||||||
fn use_system<S>(&mut self, os: S)
|
fn use_system<S>(&mut self, os: S)
|
||||||
where
|
where
|
||||||
S: System + Send + Sync + RefUnwindSafe + 'static,
|
S: WritableSystem + Send + Sync + RefUnwindSafe + 'static,
|
||||||
{
|
{
|
||||||
self.test_system_mut().use_system(os);
|
self.test_system_mut().use_system(os);
|
||||||
}
|
}
|
||||||
|
@ -229,6 +247,17 @@ pub trait DbWithTestSystem: Db + Sized {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> DbWithWritableSystem for T
|
||||||
|
where
|
||||||
|
T: DbWithTestSystem,
|
||||||
|
{
|
||||||
|
type System = TestSystem;
|
||||||
|
|
||||||
|
fn writable_system(&self) -> &Self::System {
|
||||||
|
self.test_system()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct InMemorySystem {
|
pub struct InMemorySystem {
|
||||||
user_config_directory: Mutex<Option<SystemPathBuf>>,
|
user_config_directory: Mutex<Option<SystemPathBuf>>,
|
||||||
|
@ -236,6 +265,13 @@ pub struct InMemorySystem {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InMemorySystem {
|
impl InMemorySystem {
|
||||||
|
pub fn new(cwd: SystemPathBuf) -> Self {
|
||||||
|
Self {
|
||||||
|
user_config_directory: Mutex::new(None),
|
||||||
|
memory_fs: MemoryFileSystem::with_current_directory(cwd),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn fs(&self) -> &MemoryFileSystem {
|
pub fn fs(&self) -> &MemoryFileSystem {
|
||||||
&self.memory_fs
|
&self.memory_fs
|
||||||
}
|
}
|
||||||
|
@ -314,3 +350,13 @@ impl System for InMemorySystem {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl WritableSystem for InMemorySystem {
|
||||||
|
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()> {
|
||||||
|
self.memory_fs.write_file(path, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_directory_all(&self, path: &SystemPath) -> Result<()> {
|
||||||
|
self.memory_fs.create_directory_all(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -14,7 +14,9 @@ use red_knot_python_semantic::{
|
||||||
PythonPlatform, SearchPathSettings,
|
PythonPlatform, SearchPathSettings,
|
||||||
};
|
};
|
||||||
use ruff_db::files::{system_path_to_file, File, Files};
|
use ruff_db::files::{system_path_to_file, File, Files};
|
||||||
use ruff_db::system::{DbWithTestSystem, System, SystemPathBuf, TestSystem};
|
use ruff_db::system::{
|
||||||
|
DbWithTestSystem, DbWithWritableSystem as _, System, SystemPathBuf, TestSystem,
|
||||||
|
};
|
||||||
use ruff_db::vendored::VendoredFileSystem;
|
use ruff_db::vendored::VendoredFileSystem;
|
||||||
use ruff_db::{Db as SourceDb, Upcast};
|
use ruff_db::{Db as SourceDb, Upcast};
|
||||||
use ruff_python_ast::PythonVersion;
|
use ruff_python_ast::PythonVersion;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue