use anyhow::Context; use insta::internals::SettingsBindDropGuard; use insta_cmd::{assert_cmd_snapshot, get_cargo_bin}; use std::fmt::Write; use std::path::{Path, PathBuf}; use std::process::Command; use tempfile::TempDir; #[test] fn test_run_in_sub_directory() -> anyhow::Result<()> { let case = TestCase::with_files([("test.py", "~"), ("subdir/nothing", "")])?; assert_cmd_snapshot!(case.command().current_dir(case.root().join("subdir")).arg(".."), @r" success: false exit_code: 1 ----- stdout ----- error: invalid-syntax --> /test.py:1:2 | 1 | ~ | ^ Expected an expression | Found 1 diagnostic ----- stderr ----- "); Ok(()) } #[test] fn test_include_hidden_files_by_default() -> anyhow::Result<()> { let case = TestCase::with_files([(".test.py", "~")])?; assert_cmd_snapshot!(case.command(), @r" success: false exit_code: 1 ----- stdout ----- error: invalid-syntax --> .test.py:1:2 | 1 | ~ | ^ Expected an expression | Found 1 diagnostic ----- stderr ----- "); Ok(()) } #[test] fn test_respect_ignore_files() -> anyhow::Result<()> { // First test that the default option works correctly (the file is skipped) let case = TestCase::with_files([(".ignore", "test.py"), ("test.py", "~")])?; assert_cmd_snapshot!(case.command(), @r" success: true exit_code: 0 ----- stdout ----- All checks passed! ----- stderr ----- WARN No python files found under the given path(s) "); // Test that we can set to false via CLI assert_cmd_snapshot!(case.command().arg("--no-respect-ignore-files"), @r" success: false exit_code: 1 ----- stdout ----- error: invalid-syntax --> test.py:1:2 | 1 | ~ | ^ Expected an expression | Found 1 diagnostic ----- stderr ----- "); // Test that we can set to false via config file case.write_file("ty.toml", "respect-ignore-files = false")?; assert_cmd_snapshot!(case.command(), @r" success: false exit_code: 1 ----- stdout ----- error: invalid-syntax --> test.py:1:2 | 1 | ~ | ^ Expected an expression | Found 1 diagnostic ----- stderr ----- "); // Ensure CLI takes precedence case.write_file("ty.toml", "respect-ignore-files = true")?; assert_cmd_snapshot!(case.command().arg("--no-respect-ignore-files"), @r" success: false exit_code: 1 ----- stdout ----- error: invalid-syntax --> test.py:1:2 | 1 | ~ | ^ Expected an expression | Found 1 diagnostic ----- stderr ----- "); Ok(()) } /// Specifying an option on the CLI should take precedence over the same setting in the /// project's configuration. Here, this is tested for the Python version. #[test] fn config_override_python_version() -> anyhow::Result<()> { let case = TestCase::with_files([ ( "pyproject.toml", r#" [tool.ty.environment] python-version = "3.11" "#, ), ( "test.py", r#" import sys # Access `sys.last_exc` that was only added in Python 3.12 print(sys.last_exc) "#, ), ])?; assert_cmd_snapshot!(case.command(), @r" success: false exit_code: 1 ----- stdout ----- error: lint:unresolved-attribute: Type `` has no attribute `last_exc` --> test.py:5:7 | 4 | # Access `sys.last_exc` that was only added in Python 3.12 5 | print(sys.last_exc) | ^^^^^^^^^^^^ | Found 1 diagnostic ----- stderr ----- "); assert_cmd_snapshot!(case.command().arg("--python-version").arg("3.12"), @r" success: true exit_code: 0 ----- stdout ----- All checks passed! ----- stderr ----- "); Ok(()) } /// Same as above, but for the Python platform. #[test] fn config_override_python_platform() -> anyhow::Result<()> { let case = TestCase::with_files([ ( "pyproject.toml", r#" [tool.ty.environment] python-platform = "linux" "#, ), ( "test.py", r#" import sys from typing_extensions import reveal_type reveal_type(sys.platform) "#, ), ])?; assert_cmd_snapshot!(case.command(), @r#" success: true exit_code: 0 ----- stdout ----- info: revealed-type: Revealed type --> test.py:5:1 | 3 | from typing_extensions import reveal_type 4 | 5 | reveal_type(sys.platform) | ^^^^^^^^^^^^^^^^^^^^^^^^^ `Literal["linux"]` | Found 1 diagnostic ----- stderr ----- "#); assert_cmd_snapshot!(case.command().arg("--python-platform").arg("all"), @r" success: true exit_code: 0 ----- stdout ----- info: revealed-type: Revealed type --> test.py:5:1 | 3 | from typing_extensions import reveal_type 4 | 5 | reveal_type(sys.platform) | ^^^^^^^^^^^^^^^^^^^^^^^^^ `LiteralString` | Found 1 diagnostic ----- stderr ----- "); Ok(()) } /// Paths specified on the CLI are relative to the current working directory and not the project root. /// /// We test this by adding an extra search path from the CLI to the libs directory when /// running the CLI from the child directory (using relative paths). /// /// Project layout: /// ``` /// - libs /// |- utils.py /// - child /// | - test.py /// - pyproject.toml /// ``` /// /// And the command is run in the `child` directory. #[test] fn cli_arguments_are_relative_to_the_current_directory() -> anyhow::Result<()> { let case = TestCase::with_files([ ( "pyproject.toml", r#" [tool.ty.environment] python-version = "3.11" "#, ), ( "libs/utils.py", r#" def add(a: int, b: int) -> int: return a + b "#, ), ( "child/test.py", r#" from utils import add stat = add(10, 15) "#, ), ])?; // Make sure that the CLI fails when the `libs` directory is not in the search path. assert_cmd_snapshot!(case.command().current_dir(case.root().join("child")), @r" success: false exit_code: 1 ----- stdout ----- error: lint:unresolved-import: Cannot resolve imported module `utils` --> test.py:2:6 | 2 | from utils import add | ^^^^^ 3 | 4 | stat = add(10, 15) | Found 1 diagnostic ----- stderr ----- "); assert_cmd_snapshot!(case.command().current_dir(case.root().join("child")).arg("--extra-search-path").arg("../libs"), @r" success: true exit_code: 0 ----- stdout ----- All checks passed! ----- stderr ----- "); Ok(()) } /// Paths specified in a configuration file are relative to the project root. /// /// We test this by adding `libs` (as a relative path) to the extra search path in the configuration and run /// the CLI from a subdirectory. /// /// Project layout: /// ``` /// - libs /// |- utils.py /// - child /// | - test.py /// - pyproject.toml /// ``` #[test] fn paths_in_configuration_files_are_relative_to_the_project_root() -> anyhow::Result<()> { let case = TestCase::with_files([ ( "pyproject.toml", r#" [tool.ty.environment] python-version = "3.11" extra-paths = ["libs"] "#, ), ( "libs/utils.py", r#" def add(a: int, b: int) -> int: return a + b "#, ), ( "child/test.py", r#" from utils import add stat = add(10, 15) "#, ), ])?; assert_cmd_snapshot!(case.command().current_dir(case.root().join("child")), @r" success: true exit_code: 0 ----- stdout ----- All checks passed! ----- stderr ----- "); Ok(()) } /// The rule severity can be changed in the configuration file #[test] fn configuration_rule_severity() -> anyhow::Result<()> { let case = TestCase::with_file( "test.py", r#" y = 4 / 0 for a in range(0, int(y)): x = a print(x) # possibly-unresolved-reference "#, )?; // Assert that there's a possibly unresolved reference diagnostic // and that division-by-zero has a severity of error by default. assert_cmd_snapshot!(case.command(), @r" success: false exit_code: 1 ----- stdout ----- error: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero --> test.py:2:5 | 2 | y = 4 / 0 | ^^^^^ 3 | 4 | for a in range(0, int(y)): | warning: lint:possibly-unresolved-reference: Name `x` used when possibly not defined --> test.py:7:7 | 5 | x = a 6 | 7 | print(x) # possibly-unresolved-reference | ^ | Found 2 diagnostics ----- stderr ----- "); case.write_file( "pyproject.toml", r#" [tool.ty.rules] division-by-zero = "warn" # demote to warn possibly-unresolved-reference = "ignore" "#, )?; assert_cmd_snapshot!(case.command(), @r" success: true exit_code: 0 ----- stdout ----- warning: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero --> test.py:2:5 | 2 | y = 4 / 0 | ^^^^^ 3 | 4 | for a in range(0, int(y)): | Found 1 diagnostic ----- stderr ----- "); Ok(()) } /// The rule severity can be changed using `--ignore`, `--warn`, and `--error` #[test] fn cli_rule_severity() -> anyhow::Result<()> { let case = TestCase::with_file( "test.py", r#" import does_not_exit y = 4 / 0 for a in range(0, int(y)): x = a print(x) # possibly-unresolved-reference "#, )?; // Assert that there's a possibly unresolved reference diagnostic // and that division-by-zero has a severity of error by default. assert_cmd_snapshot!(case.command(), @r" success: false exit_code: 1 ----- stdout ----- error: lint:unresolved-import: Cannot resolve imported module `does_not_exit` --> test.py:2:8 | 2 | import does_not_exit | ^^^^^^^^^^^^^ 3 | 4 | y = 4 / 0 | error: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero --> test.py:4:5 | 2 | import does_not_exit 3 | 4 | y = 4 / 0 | ^^^^^ 5 | 6 | for a in range(0, int(y)): | warning: lint:possibly-unresolved-reference: Name `x` used when possibly not defined --> test.py:9:7 | 7 | x = a 8 | 9 | print(x) # possibly-unresolved-reference | ^ | Found 3 diagnostics ----- stderr ----- "); assert_cmd_snapshot!( case .command() .arg("--ignore") .arg("possibly-unresolved-reference") .arg("--warn") .arg("division-by-zero") .arg("--warn") .arg("unresolved-import"), @r" success: true exit_code: 0 ----- stdout ----- warning: lint:unresolved-import: Cannot resolve imported module `does_not_exit` --> test.py:2:8 | 2 | import does_not_exit | ^^^^^^^^^^^^^ 3 | 4 | y = 4 / 0 | warning: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero --> test.py:4:5 | 2 | import does_not_exit 3 | 4 | y = 4 / 0 | ^^^^^ 5 | 6 | for a in range(0, int(y)): | Found 2 diagnostics ----- stderr ----- " ); Ok(()) } /// The rule severity can be changed using `--ignore`, `--warn`, and `--error` and /// values specified last override previous severities. #[test] fn cli_rule_severity_precedence() -> anyhow::Result<()> { let case = TestCase::with_file( "test.py", r#" y = 4 / 0 for a in range(0, int(y)): x = a print(x) # possibly-unresolved-reference "#, )?; // Assert that there's a possibly unresolved reference diagnostic // and that division-by-zero has a severity of error by default. assert_cmd_snapshot!(case.command(), @r" success: false exit_code: 1 ----- stdout ----- error: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero --> test.py:2:5 | 2 | y = 4 / 0 | ^^^^^ 3 | 4 | for a in range(0, int(y)): | warning: lint:possibly-unresolved-reference: Name `x` used when possibly not defined --> test.py:7:7 | 5 | x = a 6 | 7 | print(x) # possibly-unresolved-reference | ^ | Found 2 diagnostics ----- stderr ----- "); assert_cmd_snapshot!( case .command() .arg("--error") .arg("possibly-unresolved-reference") .arg("--warn") .arg("division-by-zero") // Override the error severity with warning .arg("--ignore") .arg("possibly-unresolved-reference"), @r" success: true exit_code: 0 ----- stdout ----- warning: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero --> test.py:2:5 | 2 | y = 4 / 0 | ^^^^^ 3 | 4 | for a in range(0, int(y)): | Found 1 diagnostic ----- stderr ----- " ); Ok(()) } /// ty warns about unknown rules specified in a configuration file #[test] fn configuration_unknown_rules() -> anyhow::Result<()> { let case = TestCase::with_files([ ( "pyproject.toml", r#" [tool.ty.rules] division-by-zer = "warn" # incorrect rule name "#, ), ("test.py", "print(10)"), ])?; assert_cmd_snapshot!(case.command(), @r#" success: true exit_code: 0 ----- stdout ----- warning: unknown-rule --> pyproject.toml:3:1 | 2 | [tool.ty.rules] 3 | division-by-zer = "warn" # incorrect rule name | ^^^^^^^^^^^^^^^ Unknown lint rule `division-by-zer` | Found 1 diagnostic ----- stderr ----- "#); Ok(()) } /// ty warns about unknown rules specified in a CLI argument #[test] fn cli_unknown_rules() -> anyhow::Result<()> { let case = TestCase::with_file("test.py", "print(10)")?; assert_cmd_snapshot!(case.command().arg("--ignore").arg("division-by-zer"), @r" success: true exit_code: 0 ----- stdout ----- warning: unknown-rule: Unknown lint rule `division-by-zer` Found 1 diagnostic ----- stderr ----- "); Ok(()) } #[test] fn exit_code_only_warnings() -> anyhow::Result<()> { let case = TestCase::with_file("test.py", r"print(x) # [unresolved-reference]")?; assert_cmd_snapshot!(case.command(), @r" success: true exit_code: 0 ----- stdout ----- warning: lint:unresolved-reference: Name `x` used when not defined --> test.py:1:7 | 1 | print(x) # [unresolved-reference] | ^ | Found 1 diagnostic ----- stderr ----- "); Ok(()) } #[test] fn exit_code_only_info() -> anyhow::Result<()> { let case = TestCase::with_file( "test.py", r#" from typing_extensions import reveal_type reveal_type(1) "#, )?; assert_cmd_snapshot!(case.command(), @r" success: true exit_code: 0 ----- stdout ----- info: revealed-type: Revealed type --> test.py:3:1 | 2 | from typing_extensions import reveal_type 3 | reveal_type(1) | ^^^^^^^^^^^^^^ `Literal[1]` | Found 1 diagnostic ----- stderr ----- "); Ok(()) } #[test] fn exit_code_only_info_and_error_on_warning_is_true() -> anyhow::Result<()> { let case = TestCase::with_file( "test.py", r#" from typing_extensions import reveal_type reveal_type(1) "#, )?; assert_cmd_snapshot!(case.command().arg("--error-on-warning"), @r" success: true exit_code: 0 ----- stdout ----- info: revealed-type: Revealed type --> test.py:3:1 | 2 | from typing_extensions import reveal_type 3 | reveal_type(1) | ^^^^^^^^^^^^^^ `Literal[1]` | Found 1 diagnostic ----- stderr ----- "); Ok(()) } #[test] fn exit_code_no_errors_but_error_on_warning_is_true() -> anyhow::Result<()> { let case = TestCase::with_file("test.py", r"print(x) # [unresolved-reference]")?; assert_cmd_snapshot!(case.command().arg("--error-on-warning"), @r" success: false exit_code: 1 ----- stdout ----- warning: lint:unresolved-reference: Name `x` used when not defined --> test.py:1:7 | 1 | print(x) # [unresolved-reference] | ^ | Found 1 diagnostic ----- stderr ----- "); Ok(()) } #[test] fn exit_code_no_errors_but_error_on_warning_is_enabled_in_configuration() -> anyhow::Result<()> { let case = TestCase::with_files([ ("test.py", r"print(x) # [unresolved-reference]"), ( "ty.toml", r#" [terminal] error-on-warning = true "#, ), ])?; assert_cmd_snapshot!(case.command(), @r" success: false exit_code: 1 ----- stdout ----- warning: lint:unresolved-reference: Name `x` used when not defined --> test.py:1:7 | 1 | print(x) # [unresolved-reference] | ^ | Found 1 diagnostic ----- stderr ----- "); Ok(()) } #[test] fn exit_code_both_warnings_and_errors() -> anyhow::Result<()> { let case = TestCase::with_file( "test.py", r#" print(x) # [unresolved-reference] print(4[1]) # [non-subscriptable] "#, )?; assert_cmd_snapshot!(case.command(), @r" success: false exit_code: 1 ----- stdout ----- warning: lint:unresolved-reference: Name `x` used when not defined --> test.py:2:7 | 2 | print(x) # [unresolved-reference] | ^ 3 | print(4[1]) # [non-subscriptable] | error: lint:non-subscriptable: Cannot subscript object of type `Literal[4]` with no `__getitem__` method --> test.py:3:7 | 2 | print(x) # [unresolved-reference] 3 | print(4[1]) # [non-subscriptable] | ^ | Found 2 diagnostics ----- stderr ----- "); Ok(()) } #[test] fn exit_code_both_warnings_and_errors_and_error_on_warning_is_true() -> anyhow::Result<()> { let case = TestCase::with_file( "test.py", r###" print(x) # [unresolved-reference] print(4[1]) # [non-subscriptable] "###, )?; assert_cmd_snapshot!(case.command().arg("--error-on-warning"), @r" success: false exit_code: 1 ----- stdout ----- warning: lint:unresolved-reference: Name `x` used when not defined --> test.py:2:7 | 2 | print(x) # [unresolved-reference] | ^ 3 | print(4[1]) # [non-subscriptable] | error: lint:non-subscriptable: Cannot subscript object of type `Literal[4]` with no `__getitem__` method --> test.py:3:7 | 2 | print(x) # [unresolved-reference] 3 | print(4[1]) # [non-subscriptable] | ^ | Found 2 diagnostics ----- stderr ----- "); Ok(()) } #[test] fn exit_code_exit_zero_is_true() -> anyhow::Result<()> { let case = TestCase::with_file( "test.py", r#" print(x) # [unresolved-reference] print(4[1]) # [non-subscriptable] "#, )?; assert_cmd_snapshot!(case.command().arg("--exit-zero"), @r" success: true exit_code: 0 ----- stdout ----- warning: lint:unresolved-reference: Name `x` used when not defined --> test.py:2:7 | 2 | print(x) # [unresolved-reference] | ^ 3 | print(4[1]) # [non-subscriptable] | error: lint:non-subscriptable: Cannot subscript object of type `Literal[4]` with no `__getitem__` method --> test.py:3:7 | 2 | print(x) # [unresolved-reference] 3 | print(4[1]) # [non-subscriptable] | ^ | Found 2 diagnostics ----- stderr ----- "); Ok(()) } #[test] fn user_configuration() -> anyhow::Result<()> { let case = TestCase::with_files([ ( "project/ty.toml", r#" [rules] division-by-zero = "warn" "#, ), ( "project/main.py", r#" y = 4 / 0 for a in range(0, int(y)): x = a print(x) "#, ), ])?; let config_directory = case.root().join("home/.config"); let config_env_var = if cfg!(windows) { "APPDATA" } else { "XDG_CONFIG_HOME" }; assert_cmd_snapshot!( case.command().current_dir(case.root().join("project")).env(config_env_var, config_directory.as_os_str()), @r" success: true exit_code: 0 ----- stdout ----- warning: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero --> main.py:2:5 | 2 | y = 4 / 0 | ^^^^^ 3 | 4 | for a in range(0, int(y)): | warning: lint:possibly-unresolved-reference: Name `x` used when possibly not defined --> main.py:7:7 | 5 | x = a 6 | 7 | print(x) | ^ | Found 2 diagnostics ----- stderr ----- " ); // The user-level configuration promotes `possibly-unresolved-reference` to an error. // Changing the level for `division-by-zero` has no effect, because the project-level configuration // has higher precedence. case.write_file( config_directory.join("ty/ty.toml"), r#" [rules] division-by-zero = "error" possibly-unresolved-reference = "error" "#, )?; assert_cmd_snapshot!( case.command().current_dir(case.root().join("project")).env(config_env_var, config_directory.as_os_str()), @r" success: false exit_code: 1 ----- stdout ----- warning: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero --> main.py:2:5 | 2 | y = 4 / 0 | ^^^^^ 3 | 4 | for a in range(0, int(y)): | error: lint:possibly-unresolved-reference: Name `x` used when possibly not defined --> main.py:7:7 | 5 | x = a 6 | 7 | print(x) | ^ | Found 2 diagnostics ----- stderr ----- " ); Ok(()) } #[test] fn check_specific_paths() -> anyhow::Result<()> { let case = TestCase::with_files([ ( "project/main.py", r#" y = 4 / 0 # error: division-by-zero "#, ), ( "project/tests/test_main.py", r#" import does_not_exist # error: unresolved-import "#, ), ( "project/other.py", r#" from main2 import z # error: unresolved-import print(z) "#, ), ])?; assert_cmd_snapshot!( case.command(), @r" success: false exit_code: 1 ----- stdout ----- error: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero --> project/main.py:2:5 | 2 | y = 4 / 0 # error: division-by-zero | ^^^^^ | error: lint:unresolved-import: Cannot resolve imported module `main2` --> project/other.py:2:6 | 2 | from main2 import z # error: unresolved-import | ^^^^^ 3 | 4 | print(z) | error: lint:unresolved-import: Cannot resolve imported module `does_not_exist` --> project/tests/test_main.py:2:8 | 2 | import does_not_exist # error: unresolved-import | ^^^^^^^^^^^^^^ | Found 3 diagnostics ----- stderr ----- " ); // Now check only the `tests` and `other.py` files. // We should no longer see any diagnostics related to `main.py`. assert_cmd_snapshot!( case.command().arg("project/tests").arg("project/other.py"), @r" success: false exit_code: 1 ----- stdout ----- error: lint:unresolved-import: Cannot resolve imported module `main2` --> project/other.py:2:6 | 2 | from main2 import z # error: unresolved-import | ^^^^^ 3 | 4 | print(z) | error: lint:unresolved-import: Cannot resolve imported module `does_not_exist` --> project/tests/test_main.py:2:8 | 2 | import does_not_exist # error: unresolved-import | ^^^^^^^^^^^^^^ | Found 2 diagnostics ----- stderr ----- " ); Ok(()) } #[test] fn check_non_existing_path() -> anyhow::Result<()> { let case = TestCase::with_files([])?; let mut settings = insta::Settings::clone_current(); settings.add_filter( ®ex::escape("The system cannot find the path specified. (os error 3)"), "No such file or directory (os error 2)", ); let _s = settings.bind_to_scope(); assert_cmd_snapshot!( case.command().arg("project/main.py").arg("project/tests"), @r" success: false exit_code: 1 ----- stdout ----- error: io: `/project/main.py`: No such file or directory (os error 2) error: io: `/project/tests`: No such file or directory (os error 2) Found 2 diagnostics ----- stderr ----- WARN No python files found under the given path(s) " ); Ok(()) } #[test] fn concise_diagnostics() -> anyhow::Result<()> { let case = TestCase::with_file( "test.py", r#" print(x) # [unresolved-reference] print(4[1]) # [non-subscriptable] "#, )?; assert_cmd_snapshot!(case.command().arg("--output-format=concise"), @r" success: false exit_code: 1 ----- stdout ----- warning[lint:unresolved-reference] test.py:2:7: Name `x` used when not defined error[lint:non-subscriptable] test.py:3:7: Cannot subscript object of type `Literal[4]` with no `__getitem__` method Found 2 diagnostics ----- stderr ----- "); Ok(()) } /// This tests the diagnostic format for revealed type. /// /// This test was introduced because changes were made to /// how the revealed type diagnostic was constructed and /// formatted in "verbose" mode. But it required extra /// logic to ensure the concise version didn't regress on /// information content. So this test was introduced to /// capture that. #[test] fn concise_revealed_type() -> anyhow::Result<()> { let case = TestCase::with_file( "test.py", r#" from typing_extensions import reveal_type x = "hello" reveal_type(x) "#, )?; assert_cmd_snapshot!(case.command().arg("--output-format=concise"), @r#" success: true exit_code: 0 ----- stdout ----- info[revealed-type] test.py:5:1: Revealed type: `Literal["hello"]` Found 1 diagnostic ----- stderr ----- "#); Ok(()) } #[test] fn can_handle_large_binop_expressions() -> anyhow::Result<()> { let mut content = String::new(); writeln!( &mut content, " from typing_extensions import reveal_type total = 1{plus_one_repeated} reveal_type(total) ", plus_one_repeated = " + 1".repeat(2000 - 1) )?; let case = TestCase::with_file("test.py", &ruff_python_trivia::textwrap::dedent(&content))?; assert_cmd_snapshot!(case.command(), @r" success: true exit_code: 0 ----- stdout ----- info: revealed-type: Revealed type --> test.py:4:1 | 2 | from typing_extensions import reveal_type 3 | total = 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1... 4 | reveal_type(total) | ^^^^^^^^^^^^^^^^^^ `Literal[2000]` | Found 1 diagnostic ----- stderr ----- "); Ok(()) } struct TestCase { _temp_dir: TempDir, _settings_scope: SettingsBindDropGuard, project_dir: PathBuf, } impl TestCase { fn new() -> anyhow::Result { let temp_dir = TempDir::new()?; // Canonicalize the tempdir path because macos uses symlinks for tempdirs // and that doesn't play well with our snapshot filtering. let project_dir = temp_dir .path() .canonicalize() .context("Failed to canonicalize project path")?; let mut settings = insta::Settings::clone_current(); settings.add_filter(&tempdir_filter(&project_dir), "/"); settings.add_filter(r#"\\(\w\w|\s|\.|")"#, "/$1"); let settings_scope = settings.bind_to_scope(); Ok(Self { project_dir, _temp_dir: temp_dir, _settings_scope: settings_scope, }) } fn with_files<'a>(files: impl IntoIterator) -> anyhow::Result { let case = Self::new()?; case.write_files(files)?; Ok(case) } fn with_file(path: impl AsRef, content: &str) -> anyhow::Result { let case = Self::new()?; case.write_file(path, content)?; Ok(case) } fn write_files<'a>( &self, files: impl IntoIterator, ) -> anyhow::Result<()> { for (path, content) in files { self.write_file(path, content)?; } Ok(()) } fn write_file(&self, path: impl AsRef, content: &str) -> anyhow::Result<()> { let path = path.as_ref(); let path = self.project_dir.join(path); if let Some(parent) = path.parent() { std::fs::create_dir_all(parent) .with_context(|| format!("Failed to create directory `{}`", parent.display()))?; } std::fs::write(&path, &*ruff_python_trivia::textwrap::dedent(content)) .with_context(|| format!("Failed to write file `{path}`", path = path.display()))?; Ok(()) } fn root(&self) -> &Path { &self.project_dir } fn command(&self) -> Command { let mut command = Command::new(get_cargo_bin("ty")); command.current_dir(&self.project_dir).arg("check"); command } } fn tempdir_filter(path: &Path) -> String { format!(r"{}\\?/?", regex::escape(path.to_str().unwrap())) }