use insta_cmd::assert_cmd_snapshot; use ruff_python_ast::PythonVersion; use crate::CliTest; /// 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 = CliTest::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[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) | ^^^^^^^^^^^^ | info: rule `unresolved-attribute` is enabled by default Found 1 diagnostic ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); assert_cmd_snapshot!(case.command().arg("--python-version").arg("3.12"), @r" success: true exit_code: 0 ----- stdout ----- All checks passed! ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) } /// Same as above, but for the Python platform. #[test] fn config_override_python_platform() -> anyhow::Result<()> { let case = CliTest::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:13 | 3 | from typing_extensions import reveal_type 4 | 5 | reveal_type(sys.platform) | ^^^^^^^^^^^^ `Literal["linux"]` | Found 1 diagnostic ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "#); 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:13 | 3 | from typing_extensions import reveal_type 4 | 5 | reveal_type(sys.platform) | ^^^^^^^^^^^^ `LiteralString` | Found 1 diagnostic ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) } #[test] fn config_file_annotation_showing_where_python_version_set_typing_error() -> anyhow::Result<()> { let case = CliTest::with_files([ ( "pyproject.toml", r#" [tool.ty.environment] python-version = "3.8" "#, ), ( "test.py", r#" aiter "#, ), ])?; assert_cmd_snapshot!(case.command(), @r#" success: false exit_code: 1 ----- stdout ----- error[unresolved-reference]: Name `aiter` used when not defined --> test.py:2:1 | 2 | aiter | ^^^^^ | info: `aiter` was added as a builtin in Python 3.10 info: Python 3.8 was assumed when resolving types --> pyproject.toml:3:18 | 2 | [tool.ty.environment] 3 | python-version = "3.8" | ^^^^^ Python 3.8 assumed due to this configuration setting | info: rule `unresolved-reference` is enabled by default Found 1 diagnostic ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "#); assert_cmd_snapshot!(case.command().arg("--python-version=3.9"), @r" success: false exit_code: 1 ----- stdout ----- error[unresolved-reference]: Name `aiter` used when not defined --> test.py:2:1 | 2 | aiter | ^^^^^ | info: `aiter` was added as a builtin in Python 3.10 info: Python 3.9 was assumed when resolving types because it was specified on the command line info: rule `unresolved-reference` is enabled by default Found 1 diagnostic ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) } /// This tests that, even if no Python *version* has been specified on the CLI or in a config file, /// ty is still able to infer the Python version from a `--python` argument on the CLI, /// *even if* the `--python` argument points to a system installation. /// /// We currently cannot infer the Python version from a system installation on Windows: /// on Windows, we can only infer the Python version from a virtual environment. /// This is because we use the layout of the Python installation to infer the Python version: /// on Unix, the `site-packages` directory of an installation will be located at /// `/lib/pythonX.Y/site-packages`. On Windows, however, the `site-packages` /// directory will be located at `/Lib/site-packages`, which doesn't give us the /// same information. #[cfg(not(windows))] #[test] fn python_version_inferred_from_system_installation() -> anyhow::Result<()> { let cpython_case = CliTest::with_files([ ("pythons/Python3.8/bin/python", ""), ("pythons/Python3.8/lib/python3.8/site-packages/foo.py", ""), ("test.py", "aiter"), ])?; assert_cmd_snapshot!(cpython_case.command().arg("--python").arg("pythons/Python3.8/bin/python"), @r" success: false exit_code: 1 ----- stdout ----- error[unresolved-reference]: Name `aiter` used when not defined --> test.py:1:1 | 1 | aiter | ^^^^^ | info: `aiter` was added as a builtin in Python 3.10 info: Python 3.8 was assumed when resolving types because of the layout of your Python installation info: The primary `site-packages` directory of your installation was found at `lib/python3.8/site-packages/` info: No Python version was specified on the command line or in a configuration file info: rule `unresolved-reference` is enabled by default Found 1 diagnostic ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); let pypy_case = CliTest::with_files([ ("pythons/pypy3.8/bin/python", ""), ("pythons/pypy3.8/lib/pypy3.8/site-packages/foo.py", ""), ("test.py", "aiter"), ])?; assert_cmd_snapshot!(pypy_case.command().arg("--python").arg("pythons/pypy3.8/bin/python"), @r" success: false exit_code: 1 ----- stdout ----- error[unresolved-reference]: Name `aiter` used when not defined --> test.py:1:1 | 1 | aiter | ^^^^^ | info: `aiter` was added as a builtin in Python 3.10 info: Python 3.8 was assumed when resolving types because of the layout of your Python installation info: The primary `site-packages` directory of your installation was found at `lib/pypy3.8/site-packages/` info: No Python version was specified on the command line or in a configuration file info: rule `unresolved-reference` is enabled by default Found 1 diagnostic ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); let free_threaded_case = CliTest::with_files([ ("pythons/Python3.13t/bin/python", ""), ( "pythons/Python3.13t/lib/python3.13t/site-packages/foo.py", "", ), ("test.py", "import string.templatelib"), ])?; assert_cmd_snapshot!(free_threaded_case.command().arg("--python").arg("pythons/Python3.13t/bin/python"), @r" success: false exit_code: 1 ----- stdout ----- error[unresolved-import]: Cannot resolve imported module `string.templatelib` --> test.py:1:8 | 1 | import string.templatelib | ^^^^^^^^^^^^^^^^^^ | info: The stdlib module `string.templatelib` is only available on Python 3.14+ info: Python 3.13 was assumed when resolving modules because of the layout of your Python installation info: The primary `site-packages` directory of your installation was found at `lib/python3.13t/site-packages/` info: No Python version was specified on the command line or in a configuration file info: rule `unresolved-import` is enabled by default Found 1 diagnostic ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) } /// On Unix systems, it's common for a Python installation at `.venv/bin/python` to only be a symlink /// to a system Python installation. We must be careful not to resolve the symlink too soon! /// If we do, we will incorrectly add the system installation's `site-packages` as a search path, /// when we should be adding the virtual environment's `site-packages` directory as a search path instead. #[cfg(unix)] #[test] fn python_argument_points_to_symlinked_executable() -> anyhow::Result<()> { let case = CliTest::with_files([ ( "system-installation/lib/python3.13/site-packages/foo.py", "", ), ("system-installation/bin/python", ""), ( "strange-venv-location/lib/python3.13/site-packages/bar.py", "", ), ( "test.py", "\ import foo import bar", ), ])?; case.write_symlink( "system-installation/bin/python", "strange-venv-location/bin/python", )?; assert_cmd_snapshot!(case.command().arg("--python").arg("strange-venv-location/bin/python"), @r" success: false exit_code: 1 ----- stdout ----- error[unresolved-import]: Cannot resolve imported module `foo` --> test.py:1:8 | 1 | import foo | ^^^ 2 | import bar | info: Searched in the following paths during module resolution: info: 1. / (first-party code) info: 2. vendored://stdlib (stdlib typeshed stubs vendored by ty) info: 3. /strange-venv-location/lib/python3.13/site-packages (site-packages) info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment info: rule `unresolved-import` is enabled by default Found 1 diagnostic ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) } /// On Unix systems, a virtual environment can come with multiple `site-packages` directories: /// one at `/lib/pythonX.Y/site-packages` and one at /// `/lib64/pythonX.Y/site-packages`. According to [the stdlib docs], the `lib64` /// is not *meant* to have any Python files in it (only C extensions and similar). Empirically, /// however, it sometimes does indeed have Python files in it: popular tools such as poetry /// appear to sometimes install Python packages into the `lib64` site-packages directory even /// though they probably shouldn't. We therefore check for both a `lib64` and a `lib` directory, /// and add them both as search paths if they both exist. /// /// See: /// - /// - . /// /// [the stdlib docs]: https://docs.python.org/3/library/sys.html#sys.platlibdir #[cfg(unix)] #[test] fn lib64_site_packages_directory_on_unix() -> anyhow::Result<()> { let case = CliTest::with_files([ (".venv/lib/python3.13/site-packages/foo.py", ""), (".venv/lib64/python3.13/site-packages/bar.py", ""), ("test.py", "import foo, bar, baz"), ])?; assert_cmd_snapshot!(case.command().arg("--python").arg(".venv"), @r" success: false exit_code: 1 ----- stdout ----- error[unresolved-import]: Cannot resolve imported module `baz` --> test.py:1:18 | 1 | import foo, bar, baz | ^^^ | info: Searched in the following paths during module resolution: info: 1. / (first-party code) info: 2. vendored://stdlib (stdlib typeshed stubs vendored by ty) info: 3. /.venv/lib/python3.13/site-packages (site-packages) info: 4. /.venv/lib64/python3.13/site-packages (site-packages) info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment info: rule `unresolved-import` is enabled by default Found 1 diagnostic ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) } #[test] fn pyvenv_cfg_file_annotation_showing_where_python_version_set() -> anyhow::Result<()> { let case = CliTest::with_files([ ( "pyproject.toml", r#" [tool.ty.environment] python = "venv" "#, ), ( "venv/pyvenv.cfg", r#" version = 3.8 home = foo/bar/bin "#, ), if cfg!(target_os = "windows") { ("foo/bar/bin/python.exe", "") } else { ("foo/bar/bin/python", "") }, if cfg!(target_os = "windows") { ("venv/Lib/site-packages/foo.py", "") } else { ("venv/lib/python3.8/site-packages/foo.py", "") }, ("test.py", "aiter"), ])?; assert_cmd_snapshot!(case.command(), @r" success: false exit_code: 1 ----- stdout ----- error[unresolved-reference]: Name `aiter` used when not defined --> test.py:1:1 | 1 | aiter | ^^^^^ | info: `aiter` was added as a builtin in Python 3.10 info: Python 3.8 was assumed when resolving types because of your virtual environment --> venv/pyvenv.cfg:2:11 | 2 | version = 3.8 | ^^^ Python version inferred from virtual environment metadata file 3 | home = foo/bar/bin | info: No Python version was specified on the command line or in a configuration file info: rule `unresolved-reference` is enabled by default Found 1 diagnostic ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) } #[test] fn pyvenv_cfg_file_annotation_no_trailing_newline() -> anyhow::Result<()> { let case = CliTest::with_files([ ( "pyproject.toml", r#" [tool.ty.environment] python = "venv" "#, ), ( "venv/pyvenv.cfg", r#"home = foo/bar/bin version = 3.8"#, ), if cfg!(target_os = "windows") { ("foo/bar/bin/python.exe", "") } else { ("foo/bar/bin/python", "") }, if cfg!(target_os = "windows") { ("venv/Lib/site-packages/foo.py", "") } else { ("venv/lib/python3.8/site-packages/foo.py", "") }, ("test.py", "aiter"), ])?; assert_cmd_snapshot!(case.command(), @r" success: false exit_code: 1 ----- stdout ----- error[unresolved-reference]: Name `aiter` used when not defined --> test.py:1:1 | 1 | aiter | ^^^^^ | info: `aiter` was added as a builtin in Python 3.10 info: Python 3.8 was assumed when resolving types because of your virtual environment --> venv/pyvenv.cfg:4:23 | 4 | version = 3.8 | ^^^ Python version inferred from virtual environment metadata file | info: No Python version was specified on the command line or in a configuration file info: rule `unresolved-reference` is enabled by default Found 1 diagnostic ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) } #[test] fn config_file_annotation_showing_where_python_version_set_syntax_error() -> anyhow::Result<()> { let case = CliTest::with_files([ ( "pyproject.toml", r#" [project] requires-python = ">=3.8" "#, ), ( "test.py", r#" match object(): case int(): pass case _: pass "#, ), ])?; assert_cmd_snapshot!(case.command(), @r#" success: false exit_code: 1 ----- stdout ----- error[invalid-syntax] --> test.py:2:1 | 2 | match object(): | ^^^^^ Cannot use `match` statement on Python 3.8 (syntax was added in Python 3.10) 3 | case int(): 4 | pass | info: Python 3.8 was assumed when parsing syntax --> pyproject.toml:3:19 | 2 | [project] 3 | requires-python = ">=3.8" | ^^^^^^^ Python 3.8 assumed due to this configuration setting | Found 1 diagnostic ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "#); assert_cmd_snapshot!(case.command().arg("--python-version=3.9"), @r" success: false exit_code: 1 ----- stdout ----- error[invalid-syntax] --> test.py:2:1 | 2 | match object(): | ^^^^^ Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10) 3 | case int(): 4 | pass | info: Python 3.9 was assumed when parsing syntax because it was specified on the command line Found 1 diagnostic ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) } #[test] fn python_cli_argument_virtual_environment() -> anyhow::Result<()> { let path_to_executable = if cfg!(windows) { "my-venv/Scripts/python.exe" } else { "my-venv/bin/python" }; let other_venv_path = "my-venv/foo/some_other_file.txt"; let case = CliTest::with_files([ ("test.py", ""), ( if cfg!(windows) { "my-venv/Lib/site-packages/foo.py" } else { "my-venv/lib/python3.13/site-packages/foo.py" }, "", ), (path_to_executable, ""), (other_venv_path, ""), ])?; // Passing a path to the installation works assert_cmd_snapshot!(case.command().arg("--python").arg("my-venv"), @r" success: true exit_code: 0 ----- stdout ----- All checks passed! ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); // And so does passing a path to the executable inside the installation assert_cmd_snapshot!(case.command().arg("--python").arg(path_to_executable), @r" success: true exit_code: 0 ----- stdout ----- All checks passed! ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); // But random other paths inside the installation are rejected assert_cmd_snapshot!(case.command().arg("--python").arg(other_venv_path), @r" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. ty failed Cause: Invalid `--python` argument `/my-venv/foo/some_other_file.txt`: does not point to a Python executable or a directory on disk "); // And so are paths that do not exist on disk assert_cmd_snapshot!(case.command().arg("--python").arg("not-a-directory-or-executable"), @r" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. ty failed Cause: Invalid `--python` argument `/not-a-directory-or-executable`: does not point to a Python executable or a directory on disk Cause: No such file or directory (os error 2) "); Ok(()) } #[test] fn python_cli_argument_system_installation() -> anyhow::Result<()> { let path_to_executable = if cfg!(windows) { "Python3.11/python.exe" } else { "Python3.11/bin/python" }; let case = CliTest::with_files([ ("test.py", ""), ( if cfg!(windows) { "Python3.11/Lib/site-packages/foo.py" } else { "Python3.11/lib/python3.11/site-packages/foo.py" }, "", ), (path_to_executable, ""), ])?; // Passing a path to the installation works assert_cmd_snapshot!(case.command().arg("--python").arg("Python3.11"), @r" success: true exit_code: 0 ----- stdout ----- All checks passed! ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); // And so does passing a path to the executable inside the installation assert_cmd_snapshot!(case.command().arg("--python").arg(path_to_executable), @r" success: true exit_code: 0 ----- stdout ----- All checks passed! ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) } #[test] fn config_file_broken_python_setting() -> anyhow::Result<()> { let case = CliTest::with_files([ ( "pyproject.toml", r#" [project] name = "test" version = "0.1.0" description = "Some description" readme = "README.md" requires-python = ">=3.13" dependencies = [] [tool.ty.environment] python = "not-a-directory-or-executable" "#, ), ("test.py", ""), ])?; assert_cmd_snapshot!(case.command(), @r#" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. ty failed Cause: Invalid `environment.python` setting --> Invalid setting in configuration file `/pyproject.toml` | 9 | 10 | [tool.ty.environment] 11 | python = "not-a-directory-or-executable" | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ does not point to a Python executable or a directory on disk | Cause: No such file or directory (os error 2) "#); Ok(()) } #[test] fn config_file_python_setting_directory_with_no_site_packages() -> anyhow::Result<()> { let case = CliTest::with_files([ ( "pyproject.toml", r#" [tool.ty.environment] python = "directory-but-no-site-packages" "#, ), ("directory-but-no-site-packages/lib/foo.py", ""), ("test.py", ""), ])?; assert_cmd_snapshot!(case.command(), @r#" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. ty failed Cause: Failed to discover the site-packages directory Cause: Invalid `environment.python` setting --> Invalid setting in configuration file `/pyproject.toml` | 1 | 2 | [tool.ty.environment] 3 | python = "directory-but-no-site-packages" | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Could not find a `site-packages` directory for this Python installation/executable | "#); Ok(()) } // This error message is never emitted on Windows, because Windows installations have simpler layouts #[cfg(not(windows))] #[test] fn unix_system_installation_with_no_lib_directory() -> anyhow::Result<()> { let case = CliTest::with_files([ ( "pyproject.toml", r#" [tool.ty.environment] python = "directory-but-no-site-packages" "#, ), ("directory-but-no-site-packages/foo.py", ""), ("test.py", ""), ])?; assert_cmd_snapshot!(case.command(), @r#" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. ty failed Cause: Failed to discover the site-packages directory Cause: Failed to iterate over the contents of the `lib`/`lib64` directories of the Python installation --> Invalid setting in configuration file `/pyproject.toml` | 1 | 2 | [tool.ty.environment] 3 | python = "directory-but-no-site-packages" | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | "#); Ok(()) } #[test] fn defaults_to_a_new_python_version() -> anyhow::Result<()> { let case = CliTest::with_files([ ( "ty.toml", &*format!( r#" [environment] python-version = "{}" python-platform = "linux" "#, PythonVersion::default() ), ), ( "main.py", r#" import os os.grantpt(1) # only available on unix, Python 3.13 or newer "#, ), ])?; assert_cmd_snapshot!(case.command(), @r" success: false exit_code: 1 ----- stdout ----- error[unresolved-attribute]: Type `` has no attribute `grantpt` --> main.py:4:1 | 2 | import os 3 | 4 | os.grantpt(1) # only available on unix, Python 3.13 or newer | ^^^^^^^^^^ | info: rule `unresolved-attribute` is enabled by default Found 1 diagnostic ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); // Use default (which should be latest supported) let case = CliTest::with_files([ ( "ty.toml", r#" [environment] python-platform = "linux" "#, ), ( "main.py", r#" import os os.grantpt(1) # only available on unix, Python 3.13 or newer "#, ), ])?; assert_cmd_snapshot!(case.command(), @r" success: true exit_code: 0 ----- stdout ----- All checks passed! ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) } /// The `site-packages` directory is used by ty for external import. /// Ty does the following checks to discover the `site-packages` directory in the order: /// 1) If `VIRTUAL_ENV` environment variable is set /// 2) If `CONDA_PREFIX` environment variable is set (and .filename != `CONDA_DEFAULT_ENV`) /// 3) If a `.venv` directory exists at the project root /// 4) If `CONDA_PREFIX` environment variable is set (and .filename == `CONDA_DEFAULT_ENV`) /// /// This test (and the next one) is aiming at validating the logic around these cases. /// /// To do this we create a program that has these 4 imports: /// /// ```python /// from package1 import ActiveVenv /// from package1 import ChildConda /// from package1 import WorkingVenv /// from package1 import BaseConda /// ``` /// /// We then create 4 different copies of package1. Each copy defines all of these /// classes... except the one that describes it. Therefore we know we got e.g. /// the working venv if we get a diagnostic like this: /// /// ```text /// Unresolved import /// 4 | from package1 import WorkingVenv /// | ^^^^^^^^^^^ /// ``` /// /// This test uses a directory structure as follows: /// /// ├── project /// │ ├── test.py /// │ └── .venv /// │ ├── pyvenv.cfg /// │ └── lib /// │ └── python3.13 /// │ └── site-packages /// │ └── package1 /// │ └── __init__.py /// ├── myvenv /// │ ├── pyvenv.cfg /// │ └── lib /// │ └── python3.13 /// │ └── site-packages /// │ └── package1 /// │ └── __init__.py /// ├── conda-env /// │ └── lib /// │ └── python3.13 /// │ └── site-packages /// │ └── package1 /// │ └── __init__.py /// └── conda /// └── envs /// └── base /// └── lib /// └── python3.13 /// └── site-packages /// └── package1 /// └── __init__.py /// /// test.py imports package1 /// And the command is run in the `child` directory. #[test] fn check_venv_resolution_with_working_venv() -> anyhow::Result<()> { let child_conda_package1_path = if cfg!(windows) { "conda-env/Lib/site-packages/package1/__init__.py" } else { "conda-env/lib/python3.13/site-packages/package1/__init__.py" }; let base_conda_package1_path = if cfg!(windows) { "conda/envs/base/Lib/site-packages/package1/__init__.py" } else { "conda/envs/base/lib/python3.13/site-packages/package1/__init__.py" }; let working_venv_package1_path = if cfg!(windows) { "project/.venv/Lib/site-packages/package1/__init__.py" } else { "project/.venv/lib/python3.13/site-packages/package1/__init__.py" }; let active_venv_package1_path = if cfg!(windows) { "myvenv/Lib/site-packages/package1/__init__.py" } else { "myvenv/lib/python3.13/site-packages/package1/__init__.py" }; let case = CliTest::with_files([ ( "project/test.py", r#" from package1 import ActiveVenv from package1 import ChildConda from package1 import WorkingVenv from package1 import BaseConda "#, ), ( "project/.venv/pyvenv.cfg", r#" home = ./ "#, ), ( "myvenv/pyvenv.cfg", r#" home = ./ "#, ), ( active_venv_package1_path, r#" class ChildConda: ... class WorkingVenv: ... class BaseConda: ... "#, ), ( child_conda_package1_path, r#" class ActiveVenv: ... class WorkingVenv: ... class BaseConda: ... "#, ), ( working_venv_package1_path, r#" class ActiveVenv: ... class ChildConda: ... class BaseConda: ... "#, ), ( base_conda_package1_path, r#" class ActiveVenv: ... class ChildConda: ... class WorkingVenv: ... "#, ), ])?; // Run with nothing set, should find the working venv assert_cmd_snapshot!(case.command() .current_dir(case.root().join("project")), @r" success: false exit_code: 1 ----- stdout ----- error[unresolved-import]: Module `package1` has no member `WorkingVenv` --> test.py:4:22 | 2 | from package1 import ActiveVenv 3 | from package1 import ChildConda 4 | from package1 import WorkingVenv | ^^^^^^^^^^^ 5 | from package1 import BaseConda | info: rule `unresolved-import` is enabled by default Found 1 diagnostic ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); // Run with VIRTUAL_ENV set, should find the active venv assert_cmd_snapshot!(case.command() .current_dir(case.root().join("project")) .env("VIRTUAL_ENV", case.root().join("myvenv")), @r" success: false exit_code: 1 ----- stdout ----- error[unresolved-import]: Module `package1` has no member `ActiveVenv` --> test.py:2:22 | 2 | from package1 import ActiveVenv | ^^^^^^^^^^ 3 | from package1 import ChildConda 4 | from package1 import WorkingVenv | info: rule `unresolved-import` is enabled by default Found 1 diagnostic ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); // run with CONDA_PREFIX set, should find the child conda assert_cmd_snapshot!(case.command() .current_dir(case.root().join("project")) .env("CONDA_PREFIX", case.root().join("conda-env")), @r" success: false exit_code: 1 ----- stdout ----- error[unresolved-import]: Module `package1` has no member `ChildConda` --> test.py:3:22 | 2 | from package1 import ActiveVenv 3 | from package1 import ChildConda | ^^^^^^^^^^ 4 | from package1 import WorkingVenv 5 | from package1 import BaseConda | info: rule `unresolved-import` is enabled by default Found 1 diagnostic ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); // run with CONDA_PREFIX and CONDA_DEFAULT_ENV set (unequal), should find child conda assert_cmd_snapshot!(case.command() .current_dir(case.root().join("project")) .env("CONDA_PREFIX", case.root().join("conda-env")) .env("CONDA_DEFAULT_ENV", "base"), @r" success: false exit_code: 1 ----- stdout ----- error[unresolved-import]: Module `package1` has no member `ChildConda` --> test.py:3:22 | 2 | from package1 import ActiveVenv 3 | from package1 import ChildConda | ^^^^^^^^^^ 4 | from package1 import WorkingVenv 5 | from package1 import BaseConda | info: rule `unresolved-import` is enabled by default Found 1 diagnostic ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); // run with CONDA_PREFIX and CONDA_DEFAULT_ENV (unequal) and VIRTUAL_ENV set, // should find child active venv assert_cmd_snapshot!(case.command() .current_dir(case.root().join("project")) .env("CONDA_PREFIX", case.root().join("conda-env")) .env("CONDA_DEFAULT_ENV", "base") .env("VIRTUAL_ENV", case.root().join("myvenv")), @r" success: false exit_code: 1 ----- stdout ----- error[unresolved-import]: Module `package1` has no member `ActiveVenv` --> test.py:2:22 | 2 | from package1 import ActiveVenv | ^^^^^^^^^^ 3 | from package1 import ChildConda 4 | from package1 import WorkingVenv | info: rule `unresolved-import` is enabled by default Found 1 diagnostic ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); // run with CONDA_PREFIX and CONDA_DEFAULT_ENV (equal!) set, should find working venv assert_cmd_snapshot!(case.command() .current_dir(case.root().join("project")) .env("CONDA_PREFIX", case.root().join("conda/envs/base")) .env("CONDA_DEFAULT_ENV", "base"), @r" success: false exit_code: 1 ----- stdout ----- error[unresolved-import]: Module `package1` has no member `WorkingVenv` --> test.py:4:22 | 2 | from package1 import ActiveVenv 3 | from package1 import ChildConda 4 | from package1 import WorkingVenv | ^^^^^^^^^^^ 5 | from package1 import BaseConda | info: rule `unresolved-import` is enabled by default Found 1 diagnostic ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) } /// The exact same test as above, but without a working venv /// /// In this case the Base Conda should be a possible outcome. #[test] fn check_venv_resolution_without_working_venv() -> anyhow::Result<()> { let child_conda_package1_path = if cfg!(windows) { "conda-env/Lib/site-packages/package1/__init__.py" } else { "conda-env/lib/python3.13/site-packages/package1/__init__.py" }; let base_conda_package1_path = if cfg!(windows) { "conda/envs/base/Lib/site-packages/package1/__init__.py" } else { "conda/envs/base/lib/python3.13/site-packages/package1/__init__.py" }; let active_venv_package1_path = if cfg!(windows) { "myvenv/Lib/site-packages/package1/__init__.py" } else { "myvenv/lib/python3.13/site-packages/package1/__init__.py" }; let case = CliTest::with_files([ ( "project/test.py", r#" from package1 import ActiveVenv from package1 import ChildConda from package1 import WorkingVenv from package1 import BaseConda "#, ), ( "myvenv/pyvenv.cfg", r#" home = ./ "#, ), ( active_venv_package1_path, r#" class ChildConda: ... class WorkingVenv: ... class BaseConda: ... "#, ), ( child_conda_package1_path, r#" class ActiveVenv: ... class WorkingVenv: ... class BaseConda: ... "#, ), ( base_conda_package1_path, r#" class ActiveVenv: ... class ChildConda: ... class WorkingVenv: ... "#, ), ])?; // Run with nothing set, should fail to find anything assert_cmd_snapshot!(case.command() .current_dir(case.root().join("project")), @r" success: false exit_code: 1 ----- stdout ----- error[unresolved-import]: Cannot resolve imported module `package1` --> test.py:2:6 | 2 | from package1 import ActiveVenv | ^^^^^^^^ 3 | from package1 import ChildConda 4 | from package1 import WorkingVenv | info: Searched in the following paths during module resolution: info: 1. /project (first-party code) info: 2. vendored://stdlib (stdlib typeshed stubs vendored by ty) info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment info: rule `unresolved-import` is enabled by default error[unresolved-import]: Cannot resolve imported module `package1` --> test.py:3:6 | 2 | from package1 import ActiveVenv 3 | from package1 import ChildConda | ^^^^^^^^ 4 | from package1 import WorkingVenv 5 | from package1 import BaseConda | info: Searched in the following paths during module resolution: info: 1. /project (first-party code) info: 2. vendored://stdlib (stdlib typeshed stubs vendored by ty) info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment info: rule `unresolved-import` is enabled by default error[unresolved-import]: Cannot resolve imported module `package1` --> test.py:4:6 | 2 | from package1 import ActiveVenv 3 | from package1 import ChildConda 4 | from package1 import WorkingVenv | ^^^^^^^^ 5 | from package1 import BaseConda | info: Searched in the following paths during module resolution: info: 1. /project (first-party code) info: 2. vendored://stdlib (stdlib typeshed stubs vendored by ty) info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment info: rule `unresolved-import` is enabled by default error[unresolved-import]: Cannot resolve imported module `package1` --> test.py:5:6 | 3 | from package1 import ChildConda 4 | from package1 import WorkingVenv 5 | from package1 import BaseConda | ^^^^^^^^ | info: Searched in the following paths during module resolution: info: 1. /project (first-party code) info: 2. vendored://stdlib (stdlib typeshed stubs vendored by ty) info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment info: rule `unresolved-import` is enabled by default Found 4 diagnostics ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); // Run with VIRTUAL_ENV set, should find the active venv assert_cmd_snapshot!(case.command() .current_dir(case.root().join("project")) .env("VIRTUAL_ENV", case.root().join("myvenv")), @r" success: false exit_code: 1 ----- stdout ----- error[unresolved-import]: Module `package1` has no member `ActiveVenv` --> test.py:2:22 | 2 | from package1 import ActiveVenv | ^^^^^^^^^^ 3 | from package1 import ChildConda 4 | from package1 import WorkingVenv | info: rule `unresolved-import` is enabled by default Found 1 diagnostic ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); // run with CONDA_PREFIX set, should find the child conda assert_cmd_snapshot!(case.command() .current_dir(case.root().join("project")) .env("CONDA_PREFIX", case.root().join("conda-env")), @r" success: false exit_code: 1 ----- stdout ----- error[unresolved-import]: Module `package1` has no member `ChildConda` --> test.py:3:22 | 2 | from package1 import ActiveVenv 3 | from package1 import ChildConda | ^^^^^^^^^^ 4 | from package1 import WorkingVenv 5 | from package1 import BaseConda | info: rule `unresolved-import` is enabled by default Found 1 diagnostic ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); // run with CONDA_PREFIX and CONDA_DEFAULT_ENV set (unequal), should find child conda assert_cmd_snapshot!(case.command() .current_dir(case.root().join("project")) .env("CONDA_PREFIX", case.root().join("conda-env")) .env("CONDA_DEFAULT_ENV", "base"), @r" success: false exit_code: 1 ----- stdout ----- error[unresolved-import]: Module `package1` has no member `ChildConda` --> test.py:3:22 | 2 | from package1 import ActiveVenv 3 | from package1 import ChildConda | ^^^^^^^^^^ 4 | from package1 import WorkingVenv 5 | from package1 import BaseConda | info: rule `unresolved-import` is enabled by default Found 1 diagnostic ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); // run with CONDA_PREFIX and CONDA_DEFAULT_ENV (unequal) and VIRTUAL_ENV set, // should find child active venv assert_cmd_snapshot!(case.command() .current_dir(case.root().join("project")) .env("CONDA_PREFIX", case.root().join("conda-env")) .env("CONDA_DEFAULT_ENV", "base") .env("VIRTUAL_ENV", case.root().join("myvenv")), @r" success: false exit_code: 1 ----- stdout ----- error[unresolved-import]: Module `package1` has no member `ActiveVenv` --> test.py:2:22 | 2 | from package1 import ActiveVenv | ^^^^^^^^^^ 3 | from package1 import ChildConda 4 | from package1 import WorkingVenv | info: rule `unresolved-import` is enabled by default Found 1 diagnostic ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); // run with CONDA_PREFIX and CONDA_DEFAULT_ENV (equal!) set, should find base conda assert_cmd_snapshot!(case.command() .current_dir(case.root().join("project")) .env("CONDA_PREFIX", case.root().join("conda/envs/base")) .env("CONDA_DEFAULT_ENV", "base"), @r" success: false exit_code: 1 ----- stdout ----- error[unresolved-import]: Module `package1` has no member `BaseConda` --> test.py:5:22 | 3 | from package1 import ChildConda 4 | from package1 import WorkingVenv 5 | from package1 import BaseConda | ^^^^^^^^^ | info: rule `unresolved-import` is enabled by default Found 1 diagnostic ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) } #[test] fn src_root_deprecation_warning() -> anyhow::Result<()> { let case = CliTest::with_files([ ( "pyproject.toml", r#" [tool.ty.src] root = "./src" "#, ), ("src/test.py", ""), ])?; assert_cmd_snapshot!(case.command(), @r#" success: true exit_code: 0 ----- stdout ----- warning[deprecated-setting]: The `src.root` setting is deprecated. Use `environment.root` instead. --> pyproject.toml:3:8 | 2 | [tool.ty.src] 3 | root = "./src" | ^^^^^^^ | Found 1 diagnostic ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "#); Ok(()) } #[test] fn src_root_deprecation_warning_with_environment_root() -> anyhow::Result<()> { let case = CliTest::with_files([ ( "pyproject.toml", r#" [tool.ty.src] root = "./src" [tool.ty.environment] root = ["./app"] "#, ), ("app/test.py", ""), ])?; assert_cmd_snapshot!(case.command(), @r#" success: true exit_code: 0 ----- stdout ----- warning[deprecated-setting]: The `src.root` setting is deprecated. Use `environment.root` instead. --> pyproject.toml:3:8 | 2 | [tool.ty.src] 3 | root = "./src" | ^^^^^^^ 4 | 5 | [tool.ty.environment] | info: The `src.root` setting was ignored in favor of the `environment.root` setting Found 1 diagnostic ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "#); Ok(()) } #[test] fn environment_root_takes_precedence_over_src_root() -> anyhow::Result<()> { let case = CliTest::with_files([ ( "pyproject.toml", r#" [tool.ty.src] root = "./src" [tool.ty.environment] root = ["./app"] "#, ), ("src/test.py", "import my_module"), ( "app/my_module.py", "# This module exists in app/ but not src/", ), ])?; // The test should pass because environment.root points to ./app where my_module.py exists // If src.root took precedence, it would fail because my_module.py doesn't exist in ./src assert_cmd_snapshot!(case.command(), @r#" success: true exit_code: 0 ----- stdout ----- warning[deprecated-setting]: The `src.root` setting is deprecated. Use `environment.root` instead. --> pyproject.toml:3:8 | 2 | [tool.ty.src] 3 | root = "./src" | ^^^^^^^ 4 | 5 | [tool.ty.environment] | info: The `src.root` setting was ignored in favor of the `environment.root` setting Found 1 diagnostic ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "#); Ok(()) } #[test] fn default_root_src_layout() -> anyhow::Result<()> { let case = CliTest::with_files([ ("src/foo.py", "foo = 10"), ("bar.py", "bar = 20"), ( "src/main.py", r#" from foo import foo from bar import bar print(f"{foo} {bar}") "#, ), ])?; assert_cmd_snapshot!(case.command(), @r" success: true exit_code: 0 ----- stdout ----- All checks passed! ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) } #[test] fn default_root_project_name_folder() -> anyhow::Result<()> { let case = CliTest::with_files([ ( "pyproject.toml", r#" [project] name = "psycopg" "#, ), ("psycopg/psycopg/foo.py", "foo = 10"), ("bar.py", "bar = 20"), ( "psycopg/psycopg/main.py", r#" from psycopg.foo import foo from bar import bar print(f"{foo} {bar}") "#, ), ])?; assert_cmd_snapshot!(case.command(), @r" success: true exit_code: 0 ----- stdout ----- All checks passed! ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) } #[test] fn default_root_flat_layout() -> anyhow::Result<()> { let case = CliTest::with_files([ ("app/foo.py", "foo = 10"), ("bar.py", "bar = 20"), ( "app/main.py", r#" from app.foo import foo from bar import bar print(f"{foo} {bar}") "#, ), ])?; assert_cmd_snapshot!(case.command(), @r" success: true exit_code: 0 ----- stdout ----- All checks passed! ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) } #[test] fn default_root_tests_folder() -> anyhow::Result<()> { let case = CliTest::with_files([ ("src/foo.py", "foo = 10"), ("tests/bar.py", "bar = 20"), ( "tests/test_bar.py", r#" from foo import foo from bar import bar print(f"{foo} {bar}") "#, ), ])?; assert_cmd_snapshot!(case.command(), @r" success: true exit_code: 0 ----- stdout ----- All checks passed! ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) } /// If `tests/__init__.py` is present, it is considered a package and `tests` is not added to `sys.path`. #[test] fn default_root_tests_package() -> anyhow::Result<()> { let case = CliTest::with_files([ ("src/foo.py", "foo = 10"), ("tests/__init__.py", ""), ("tests/bar.py", "bar = 20"), ( "tests/test_bar.py", r#" from foo import foo from bar import bar # expected unresolved import print(f"{foo} {bar}") "#, ), ])?; assert_cmd_snapshot!(case.command(), @r#" success: false exit_code: 1 ----- stdout ----- error[unresolved-import]: Cannot resolve imported module `bar` --> tests/test_bar.py:3:6 | 2 | from foo import foo 3 | from bar import bar # expected unresolved import | ^^^ 4 | 5 | print(f"{foo} {bar}") | info: Searched in the following paths during module resolution: info: 1. / (first-party code) info: 2. /src (first-party code) info: 3. vendored://stdlib (stdlib typeshed stubs vendored by ty) info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment info: rule `unresolved-import` is enabled by default Found 1 diagnostic ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "#); Ok(()) } #[test] fn default_root_python_folder() -> anyhow::Result<()> { let case = CliTest::with_files([ ("src/foo.py", "foo = 10"), ("python/bar.py", "bar = 20"), ( "python/test_bar.py", r#" from foo import foo from bar import bar print(f"{foo} {bar}") "#, ), ])?; assert_cmd_snapshot!(case.command(), @r" success: true exit_code: 0 ----- stdout ----- All checks passed! ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) } /// If `python/__init__.py` is present, it is considered a package and `python` is not added to search paths. #[test] fn default_root_python_package() -> anyhow::Result<()> { let case = CliTest::with_files([ ("src/foo.py", "foo = 10"), ("python/__init__.py", ""), ("python/bar.py", "bar = 20"), ( "python/test_bar.py", r#" from foo import foo from bar import bar # expected unresolved import print(f"{foo} {bar}") "#, ), ])?; assert_cmd_snapshot!(case.command(), @r#" success: false exit_code: 1 ----- stdout ----- error[unresolved-import]: Cannot resolve imported module `bar` --> python/test_bar.py:3:6 | 2 | from foo import foo 3 | from bar import bar # expected unresolved import | ^^^ 4 | 5 | print(f"{foo} {bar}") | info: Searched in the following paths during module resolution: info: 1. / (first-party code) info: 2. /src (first-party code) info: 3. vendored://stdlib (stdlib typeshed stubs vendored by ty) info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment info: rule `unresolved-import` is enabled by default Found 1 diagnostic ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "#); Ok(()) } /// Similarly, if `python/__init__.pyi` is present, it is considered a package and `python` is not added to search paths. #[test] fn default_root_python_package_pyi() -> anyhow::Result<()> { let case = CliTest::with_files([ ("src/foo.py", "foo = 10"), ("python/__init__.pyi", ""), ("python/bar.py", "bar = 20"), ( "python/test_bar.py", r#" from foo import foo from bar import bar # expected unresolved import print(f"{foo} {bar}") "#, ), ])?; assert_cmd_snapshot!(case.command(), @r#" success: false exit_code: 1 ----- stdout ----- error[unresolved-import]: Cannot resolve imported module `bar` --> python/test_bar.py:3:6 | 2 | from foo import foo 3 | from bar import bar # expected unresolved import | ^^^ 4 | 5 | print(f"{foo} {bar}") | info: Searched in the following paths during module resolution: info: 1. / (first-party code) info: 2. /src (first-party code) info: 3. vendored://stdlib (stdlib typeshed stubs vendored by ty) info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment info: rule `unresolved-import` is enabled by default Found 1 diagnostic ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "#); Ok(()) } #[test] fn pythonpath_is_respected() -> anyhow::Result<()> { let case = CliTest::with_files([ ("baz-dir/baz.py", "it = 42"), ( "src/foo.py", r#" import baz print(f"{baz.it}") "#, ), ])?; assert_cmd_snapshot!(case.command(), @r#" success: false exit_code: 1 ----- stdout ----- error[unresolved-import]: Cannot resolve imported module `baz` --> src/foo.py:2:8 | 2 | import baz | ^^^ 3 | print(f"{baz.it}") | info: Searched in the following paths during module resolution: info: 1. / (first-party code) info: 2. /src (first-party code) info: 3. vendored://stdlib (stdlib typeshed stubs vendored by ty) info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment info: rule `unresolved-import` is enabled by default Found 1 diagnostic ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "#); assert_cmd_snapshot!(case.command() .env("PYTHONPATH", case.root().join("baz-dir")), @r#" success: true exit_code: 0 ----- stdout ----- All checks passed! ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "#); Ok(()) } #[test] fn pythonpath_multiple_dirs_is_respected() -> anyhow::Result<()> { let case = CliTest::with_files([ ("baz-dir/baz.py", "it = 42"), ("foo-dir/foo.py", "it = 42"), ( "src/main.py", r#" import baz import foo print(f"{baz.it}") print(f"{foo.it}") "#, ), ])?; assert_cmd_snapshot!(case.command(), @r#" success: false exit_code: 1 ----- stdout ----- error[unresolved-import]: Cannot resolve imported module `baz` --> src/main.py:2:8 | 2 | import baz | ^^^ 3 | import foo | info: Searched in the following paths during module resolution: info: 1. / (first-party code) info: 2. /src (first-party code) info: 3. vendored://stdlib (stdlib typeshed stubs vendored by ty) info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment info: rule `unresolved-import` is enabled by default error[unresolved-import]: Cannot resolve imported module `foo` --> src/main.py:3:8 | 2 | import baz 3 | import foo | ^^^ 4 | 5 | print(f"{baz.it}") | info: Searched in the following paths during module resolution: info: 1. / (first-party code) info: 2. /src (first-party code) info: 3. vendored://stdlib (stdlib typeshed stubs vendored by ty) info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment info: rule `unresolved-import` is enabled by default Found 2 diagnostics ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "#); let pythonpath = std::env::join_paths([case.root().join("baz-dir"), case.root().join("foo-dir")])?; assert_cmd_snapshot!(case.command() .env("PYTHONPATH", pythonpath), @r#" success: true exit_code: 0 ----- stdout ----- All checks passed! ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "#); Ok(()) }