Skip to content

Commit

Permalink
[red-knot] consolidate diagnostic and inference tests (#13248)
Browse files Browse the repository at this point in the history
Pull the tests from `types.rs` into `infer.rs`.

All of these are integration tests with the same basic form: create a
code sample, run type inference or check on it, and make some assertions
about types and/or diagnostics. These are the sort of tests we will want
to move into a test framework with a low-boilerplate custom textual
format. In the meantime, having them together (and more importantly,
their helper utilities together) means that it's easy to keep tests for
related language features together (iterable tests with other iterable
tests, callable tests with other callable tests), without an artificial
split based on tests which test diagnostics vs tests which test
inference. And it allows a single test to more easily test both
diagnostics and inference. (Ultimately in the test framework, they will
likely all test diagnostics, just in some cases the diagnostics will
come from `reveal_type()`.)
  • Loading branch information
carljm committed Sep 5, 2024
1 parent 2a3775e commit a4ebe7d
Show file tree
Hide file tree
Showing 2 changed files with 285 additions and 326 deletions.
229 changes: 0 additions & 229 deletions crates/red_knot_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -659,232 +659,3 @@ pub struct BytesLiteralType<'db> {
#[return_ref]
value: Box<[u8]>,
}

#[cfg(test)]
mod tests {
use anyhow::Context;

use ruff_db::files::system_path_to_file;
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};

use crate::db::tests::TestDb;
use crate::{Program, ProgramSettings, PythonVersion, SearchPathSettings};

use super::TypeCheckDiagnostics;

fn setup_db() -> TestDb {
let db = TestDb::new();
db.memory_file_system()
.create_directory_all("/src")
.unwrap();

Program::from_settings(
&db,
&ProgramSettings {
target_version: PythonVersion::default(),
search_paths: SearchPathSettings::new(SystemPathBuf::from("/src")),
},
)
.expect("Valid search path settings");

db
}

fn assert_diagnostic_messages(diagnostics: &TypeCheckDiagnostics, expected: &[&str]) {
let messages: Vec<&str> = diagnostics
.iter()
.map(|diagnostic| diagnostic.message())
.collect();
assert_eq!(&messages, expected);
}

#[test]
fn unresolved_import_statement() -> anyhow::Result<()> {
let mut db = setup_db();

db.write_file("src/foo.py", "import bar\n")
.context("Failed to write foo.py")?;

let foo = system_path_to_file(&db, "src/foo.py").context("Failed to resolve foo.py")?;

let diagnostics = super::check_types(&db, foo);
assert_diagnostic_messages(&diagnostics, &["Cannot resolve import 'bar'."]);

Ok(())
}

#[test]
fn unresolved_import_from_statement() {
let mut db = setup_db();

db.write_file("src/foo.py", "from bar import baz\n")
.unwrap();
let foo = system_path_to_file(&db, "src/foo.py").unwrap();
let diagnostics = super::check_types(&db, foo);
assert_diagnostic_messages(&diagnostics, &["Cannot resolve import 'bar'."]);
}

#[test]
fn unresolved_import_from_resolved_module() {
let mut db = setup_db();

db.write_files([("/src/a.py", ""), ("/src/b.py", "from a import thing")])
.unwrap();

let b_file = system_path_to_file(&db, "/src/b.py").unwrap();
let b_file_diagnostics = super::check_types(&db, b_file);
assert_diagnostic_messages(&b_file_diagnostics, &["Module 'a' has no member 'thing'"]);
}

#[test]
fn resolved_import_of_symbol_from_unresolved_import() {
let mut db = setup_db();

db.write_files([
("/src/a.py", "import foo as foo"),
("/src/b.py", "from a import foo"),
])
.unwrap();

let a_file = system_path_to_file(&db, "/src/a.py").unwrap();
let a_file_diagnostics = super::check_types(&db, a_file);
assert_diagnostic_messages(&a_file_diagnostics, &["Cannot resolve import 'foo'."]);

// Importing the unresolved import into a second first-party file should not trigger
// an additional "unresolved import" violation
let b_file = system_path_to_file(&db, "/src/b.py").unwrap();
let b_file_diagnostics = super::check_types(&db, b_file);
assert_eq!(&*b_file_diagnostics, &[]);
}

#[test]
fn invalid_callable() {
let mut db = setup_db();

db.write_dedented(
"src/a.py",
"
nonsense = 123
x = nonsense()
",
)
.unwrap();

let a_file = system_path_to_file(&db, "/src/a.py").unwrap();
let a_file_diagnostics = super::check_types(&db, a_file);
assert_diagnostic_messages(
&a_file_diagnostics,
&["Object of type 'Literal[123]' is not callable"],
);
}

#[test]
fn invalid_iterable() {
let mut db = setup_db();

db.write_dedented(
"src/a.py",
"
nonsense = 123
for x in nonsense:
pass
",
)
.unwrap();

let a_file = system_path_to_file(&db, "/src/a.py").unwrap();
let a_file_diagnostics = super::check_types(&db, a_file);
assert_diagnostic_messages(
&a_file_diagnostics,
&["Object of type 'Literal[123]' is not iterable"],
);
}

#[test]
fn new_iteration_protocol_takes_precedence_over_old_style() {
let mut db = setup_db();

db.write_dedented(
"src/a.py",
"
class NotIterable:
def __getitem__(self, key: int) -> int:
return 42
__iter__ = None
for x in NotIterable():
pass
",
)
.unwrap();

let a_file = system_path_to_file(&db, "/src/a.py").unwrap();
let a_file_diagnostics = super::check_types(&db, a_file);
assert_diagnostic_messages(
&a_file_diagnostics,
&["Object of type 'NotIterable' is not iterable"],
);
}

#[test]
fn starred_expressions_must_be_iterable() {
let mut db = setup_db();

db.write_dedented(
"src/a.py",
"
class NotIterable: pass
class Iterator:
def __next__(self) -> int:
return 42
class Iterable:
def __iter__(self) -> Iterator:
x = [*NotIterable()]
y = [*Iterable()]
",
)
.unwrap();

let a_file = system_path_to_file(&db, "/src/a.py").unwrap();
let a_file_diagnostics = super::check_types(&db, a_file);
assert_diagnostic_messages(
&a_file_diagnostics,
&["Object of type 'NotIterable' is not iterable"],
);
}

#[test]
fn yield_from_expression_must_be_iterable() {
let mut db = setup_db();

db.write_dedented(
"src/a.py",
"
class NotIterable: pass
class Iterator:
def __next__(self) -> int:
return 42
class Iterable:
def __iter__(self) -> Iterator:
def generator_function():
yield from Iterable()
yield from NotIterable()
",
)
.unwrap();

let a_file = system_path_to_file(&db, "/src/a.py").unwrap();
let a_file_diagnostics = super::check_types(&db, a_file);
assert_diagnostic_messages(
&a_file_diagnostics,
&["Object of type 'NotIterable' is not iterable"],
);
}
}
Loading

0 comments on commit a4ebe7d

Please sign in to comment.