|
1 | | -use std::fs; |
2 | | -use std::path::PathBuf; |
3 | | - |
4 | 1 | use red_knot_python_semantic::{HasTy, SemanticModel}; |
5 | 2 | use red_knot_workspace::db::RootDatabase; |
6 | 3 | use red_knot_workspace::workspace::WorkspaceMetadata; |
7 | 4 | use ruff_db::files::{system_path_to_file, File}; |
8 | 5 | use ruff_db::parsed::parsed_module; |
9 | | -use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf}; |
| 6 | +use ruff_db::system::{SystemPath, SystemPathBuf, TestSystem}; |
10 | 7 | use ruff_python_ast::visitor::source_order; |
11 | 8 | use ruff_python_ast::visitor::source_order::SourceOrderVisitor; |
12 | 9 | use ruff_python_ast::{self as ast, Alias, Expr, Parameter, ParameterWithDefault, Stmt}; |
13 | 10 |
|
14 | | -fn setup_db(workspace_root: &SystemPath) -> anyhow::Result<RootDatabase> { |
15 | | - let system = OsSystem::new(workspace_root); |
| 11 | +fn setup_db(workspace_root: &SystemPath, system: TestSystem) -> anyhow::Result<RootDatabase> { |
16 | 12 | let workspace = WorkspaceMetadata::from_path(workspace_root, &system, None)?; |
17 | 13 | RootDatabase::new(workspace, system) |
18 | 14 | } |
19 | 15 |
|
20 | | -/// Test that all snippets in testcorpus can be checked without panic |
| 16 | +fn get_workspace_root() -> anyhow::Result<SystemPathBuf> { |
| 17 | + Ok(SystemPathBuf::from(String::from_utf8( |
| 18 | + std::process::Command::new("cargo") |
| 19 | + .args(["locate-project", "--workspace", "--message-format", "plain"]) |
| 20 | + .output()? |
| 21 | + .stdout, |
| 22 | + )?) |
| 23 | + .parent() |
| 24 | + .unwrap() |
| 25 | + .to_owned()) |
| 26 | +} |
| 27 | + |
| 28 | +/// Test that all snippets in testcorpus can be checked without panic (except for [`KNOWN_FAILURES`]) |
21 | 29 | #[test] |
22 | 30 | #[allow(clippy::print_stdout)] |
23 | 31 | fn corpus_no_panic() -> anyhow::Result<()> { |
24 | | - let root = SystemPathBuf::from_path_buf(tempfile::TempDir::new()?.into_path()).unwrap(); |
25 | | - let db = setup_db(&root)?; |
26 | | - |
27 | | - let corpus = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("resources/test/corpus"); |
28 | | - |
29 | | - for path in fs::read_dir(&corpus)? { |
30 | | - let source = path?.path(); |
31 | | - println!("checking {source:?}"); |
32 | | - let source_fn = source.file_name().unwrap().to_str().unwrap(); |
33 | | - let py_dest = root.join(source_fn); |
34 | | - fs::copy(&source, py_dest.as_std_path())?; |
35 | | - // this test is only asserting that we can pull every expression type without a panic |
36 | | - // (and some non-expressions that clearly define a single type) |
37 | | - let file = system_path_to_file(&db, py_dest).unwrap(); |
38 | | - pull_types(&db, file); |
39 | | - |
40 | | - // try the file as a stub also |
41 | | - println!("re-checking as .pyi"); |
42 | | - let pyi_dest = root.join(format!("{source_fn}i")); |
43 | | - std::fs::copy(source, pyi_dest.as_std_path())?; |
44 | | - let file = system_path_to_file(&db, pyi_dest).unwrap(); |
45 | | - pull_types(&db, file); |
| 32 | + let root = SystemPathBuf::from("/src"); |
| 33 | + |
| 34 | + let system = TestSystem::default(); |
| 35 | + let memory_fs = system.memory_file_system(); |
| 36 | + memory_fs.create_directory_all(root.as_ref())?; |
| 37 | + |
| 38 | + let mut db = setup_db(&root, system.clone())?; |
| 39 | + |
| 40 | + let crate_root = String::from(env!("CARGO_MANIFEST_DIR")); |
| 41 | + let workspace_root = get_workspace_root()?; |
| 42 | + let workspace_root = workspace_root.to_string(); |
| 43 | + |
| 44 | + let corpus = vec![ |
| 45 | + format!("{crate_root}/resources/test/corpus/**/*.py"), |
| 46 | + format!("{workspace_root}/crates/ruff_python_parser/resources/**/*.py"), |
| 47 | + format!("{workspace_root}/crates/ruff_linter/resources/**/*.py"), |
| 48 | + // TODO: Enable running over typeshed stubs once there are fewer failures: |
| 49 | + // format!("{workspace_root}/crates/red_knot_vendored/vendor/typeshed/**/*.pyi"), |
| 50 | + ] |
| 51 | + .into_iter() |
| 52 | + .flat_map(|pattern| glob::glob(&pattern).unwrap()); |
| 53 | + |
| 54 | + for path in corpus { |
| 55 | + let path = path?; |
| 56 | + let relative_path = path.strip_prefix(&workspace_root)?; |
| 57 | + |
| 58 | + let (py_expected_to_fail, pyi_expected_to_fail) = KNOWN_FAILURES |
| 59 | + .iter() |
| 60 | + .find_map(|(path, py_fail, pyi_fail)| { |
| 61 | + if Some(*path) |
| 62 | + == relative_path |
| 63 | + .to_str() |
| 64 | + .map(|p| p.replace('\\', "/")) |
| 65 | + .as_deref() |
| 66 | + { |
| 67 | + Some((*py_fail, *pyi_fail)) |
| 68 | + } else { |
| 69 | + None |
| 70 | + } |
| 71 | + }) |
| 72 | + .unwrap_or((false, false)); |
| 73 | + |
| 74 | + let source = path.as_path(); |
| 75 | + let source_filename = source.file_name().unwrap().to_str().unwrap(); |
| 76 | + |
| 77 | + let code = std::fs::read_to_string(source)?; |
| 78 | + |
| 79 | + let mut check_with_file_name = |path: &SystemPath| { |
| 80 | + memory_fs.write_file(path, &code).unwrap(); |
| 81 | + File::sync_path(&mut db, path); |
| 82 | + |
| 83 | + // this test is only asserting that we can pull every expression type without a panic |
| 84 | + // (and some non-expressions that clearly define a single type) |
| 85 | + let file = system_path_to_file(&db, path).unwrap(); |
| 86 | + |
| 87 | + let result = std::panic::catch_unwind(|| pull_types(&db, file)); |
| 88 | + |
| 89 | + let expected_to_fail = if path.extension().map(|e| e == "pyi").unwrap_or(false) { |
| 90 | + pyi_expected_to_fail |
| 91 | + } else { |
| 92 | + py_expected_to_fail |
| 93 | + }; |
| 94 | + if let Err(err) = result { |
| 95 | + if !expected_to_fail { |
| 96 | + println!("Check failed for {relative_path:?}. Consider fixing it or adding it to KNOWN_FAILURES"); |
| 97 | + std::panic::resume_unwind(err); |
| 98 | + } |
| 99 | + } else { |
| 100 | + assert!(!expected_to_fail, "Expected to panic, but did not. Consider removing this path from KNOWN_FAILURES"); |
| 101 | + } |
| 102 | + |
| 103 | + memory_fs.remove_all(); |
| 104 | + file.sync(&mut db); |
| 105 | + }; |
| 106 | + |
| 107 | + if source.extension().map(|e| e == "pyi").unwrap_or(false) { |
| 108 | + println!("checking {relative_path:?}"); |
| 109 | + let pyi_dest = root.join(source_filename); |
| 110 | + check_with_file_name(&pyi_dest); |
| 111 | + } else { |
| 112 | + println!("checking {relative_path:?}"); |
| 113 | + let py_dest = root.join(source_filename); |
| 114 | + check_with_file_name(&py_dest); |
| 115 | + |
| 116 | + let pyi_dest = root.join(format!("{source_filename}i")); |
| 117 | + println!("re-checking as stub file: {pyi_dest:?}"); |
| 118 | + check_with_file_name(&pyi_dest); |
| 119 | + } |
46 | 120 | } |
47 | 121 | Ok(()) |
48 | 122 | } |
@@ -144,3 +218,94 @@ impl SourceOrderVisitor<'_> for PullTypesVisitor<'_> { |
144 | 218 | source_order::walk_alias(self, alias); |
145 | 219 | } |
146 | 220 | } |
| 221 | + |
| 222 | +/// Whether or not the .py/.pyi version of this file is expected to fail |
| 223 | +const KNOWN_FAILURES: &[(&str, bool, bool)] = &[ |
| 224 | + // Probably related to missing support for type aliases / type params: |
| 225 | + ("crates/ruff_python_parser/resources/inline/err/type_param_invalid_bound_expr.py", true, true), |
| 226 | + ("crates/ruff_python_parser/resources/inline/err/type_param_type_var_invalid_default_expr.py", true, true), |
| 227 | + ("crates/ruff_python_parser/resources/inline/err/type_param_param_spec_invalid_default_expr.py", true, true), |
| 228 | + ("crates/ruff_python_parser/resources/inline/err/type_param_type_var_missing_default.py", true, true), |
| 229 | + ("crates/ruff_python_parser/resources/inline/err/type_param_type_var_tuple_invalid_default_expr.py", true, true), |
| 230 | + ("crates/ruff_python_parser/resources/inline/ok/type_param_param_spec.py", true, true), |
| 231 | + ("crates/ruff_python_parser/resources/inline/ok/type_param_type_var.py", true, true), |
| 232 | + ("crates/ruff_python_parser/resources/inline/ok/type_param_type_var_tuple.py", true, true), |
| 233 | + ("crates/ruff_python_parser/resources/valid/statement/type.py", true, true), |
| 234 | + // Fails for unknown reasons: |
| 235 | + ("crates/ruff_python_parser/resources/valid/expressions/f_string.py", true, true), |
| 236 | + ("crates/ruff_linter/resources/test/fixtures/flake8_future_annotations/no_future_import_uses_union_inner.py", true, true), |
| 237 | + ("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI011.py", true, true), |
| 238 | + ("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI015.py", true, true), |
| 239 | + ("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI016.py", true, true), |
| 240 | + ("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019.py", true, true), |
| 241 | + ("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI020.py", true, true), |
| 242 | + ("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI020.py", true, true), |
| 243 | + ("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI030.py", true, true), |
| 244 | + ("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI030.py", true, true), |
| 245 | + ("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI034.py", true, true), |
| 246 | + ("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI034.py", true, true), |
| 247 | + ("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI035.py", true, true), |
| 248 | + ("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI035.py", true, true), |
| 249 | + ("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI036.py", true, true), |
| 250 | + ("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI036.py", true, true), |
| 251 | + ("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI041.py", true, true), |
| 252 | + ("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI041.py", true, true), |
| 253 | + ("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI051.py", true, true), |
| 254 | + ("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI051.py", true, true), |
| 255 | + ("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI052.py", true, true), |
| 256 | + ("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI052.py", true, true), |
| 257 | + ("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI055.py", true, true), |
| 258 | + ("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI055.py", true, true), |
| 259 | + ("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI062.py", true, true), |
| 260 | + ("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI062.py", true, true), |
| 261 | + ("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI063.py", true, true), |
| 262 | + ("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI063.py", true, true), |
| 263 | + ("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI064.py", true, true), |
| 264 | + ("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI064.py", true, true), |
| 265 | + ("crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TCH004_13.py", true, true), |
| 266 | + ("crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TCH004_13.py", true, true), |
| 267 | + ("crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TCH004_15.py", true, true), |
| 268 | + ("crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TCH004_15.py", true, true), |
| 269 | + ("crates/ruff_linter/resources/test/fixtures/flake8_type_checking/quote.py", true, true), |
| 270 | + ("crates/ruff_linter/resources/test/fixtures/flake8_type_checking/quote.py", true, true), |
| 271 | + ("crates/ruff_linter/resources/test/fixtures/flake8_type_checking/quote2.py", true, true), |
| 272 | + ("crates/ruff_linter/resources/test/fixtures/flake8_type_checking/quote2.py", true, true), |
| 273 | + ("crates/ruff_linter/resources/test/fixtures/flake8_type_checking/quote3.py", true, true), |
| 274 | + ("crates/ruff_linter/resources/test/fixtures/flake8_type_checking/quote3.py", true, true), |
| 275 | + ("crates/ruff_linter/resources/test/fixtures/pyflakes/F401_19.py", true, true), |
| 276 | + ("crates/ruff_linter/resources/test/fixtures/pyflakes/F401_19.py", true, true), |
| 277 | + ("crates/ruff_linter/resources/test/fixtures/pyflakes/F541.py", true, true), |
| 278 | + ("crates/ruff_linter/resources/test/fixtures/pyflakes/F541.py", true, true), |
| 279 | + ("crates/ruff_linter/resources/test/fixtures/pyflakes/F632.py", true, true), |
| 280 | + ("crates/ruff_linter/resources/test/fixtures/pyflakes/F632.py", true, true), |
| 281 | + ("crates/ruff_linter/resources/test/fixtures/pyflakes/F811_19.py", true, false), |
| 282 | + ("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_0.py", true, true), |
| 283 | + ("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_0.py", true, true), |
| 284 | + ("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_14.py", false, true), |
| 285 | + ("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_15.py", true, true), |
| 286 | + ("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_15.py", true, true), |
| 287 | + ("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_17.py", true, true), |
| 288 | + ("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_17.py", true, true), |
| 289 | + ("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_2.py", true, true), |
| 290 | + ("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_2.py", true, true), |
| 291 | + ("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_20.py", true, true), |
| 292 | + ("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_20.py", true, true), |
| 293 | + ("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_26.py", true, false), |
| 294 | + ("crates/ruff_linter/resources/test/fixtures/pyflakes/project/foo/bar.py", true, true), |
| 295 | + ("crates/ruff_linter/resources/test/fixtures/pyflakes/project/foo/bar.py", true, true), |
| 296 | + ("crates/ruff_linter/resources/test/fixtures/pyflakes/project/foo/bop/baz.py", true, true), |
| 297 | + ("crates/ruff_linter/resources/test/fixtures/pyflakes/project/foo/bop/baz.py", true, true), |
| 298 | + ("crates/ruff_linter/resources/test/fixtures/pylint/single_string_slots.py", true, true), |
| 299 | + ("crates/ruff_linter/resources/test/fixtures/pylint/single_string_slots.py", true, true), |
| 300 | + ("crates/ruff_linter/resources/test/fixtures/pyupgrade/UP037_0.py", true, true), |
| 301 | + ("crates/ruff_linter/resources/test/fixtures/pyupgrade/UP037_0.py", true, true), |
| 302 | + ("crates/ruff_linter/resources/test/fixtures/pyupgrade/UP039.py", true, false), |
| 303 | + ("crates/ruff_linter/resources/test/fixtures/pyupgrade/UP044.py", true, true), |
| 304 | + ("crates/ruff_linter/resources/test/fixtures/pyupgrade/UP044.py", true, true), |
| 305 | + ("crates/ruff_linter/resources/test/fixtures/ruff/RUF013_0.py", true, true), |
| 306 | + ("crates/ruff_linter/resources/test/fixtures/ruff/RUF013_0.py", true, true), |
| 307 | + ("crates/ruff_linter/resources/test/fixtures/ruff/RUF013_3.py", true, true), |
| 308 | + ("crates/ruff_linter/resources/test/fixtures/ruff/RUF013_3.py", true, true), |
| 309 | + ("crates/ruff_linter/resources/test/fixtures/ruff/RUF022.py", true, true), |
| 310 | + ("crates/ruff_linter/resources/test/fixtures/ruff/RUF022.py", true, true), |
| 311 | +]; |
0 commit comments