diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f49ece9a..fe9dfea2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -39,7 +39,7 @@ repos: rev: 6.0.0 hooks: - id: flake8 - args: ["--exclude", ".*,tests/trio*.py"] + args: ["--exclude", ".*,tests/eval_files/*"] language_version: python3 additional_dependencies: - flake8-builtins @@ -63,7 +63,7 @@ repos: rev: 5.0.4 hooks: - id: flake8 - args: ["--exclude", ".*,tests/trio*.py", "--select=E800"] + args: ["--exclude", ".*,tests/eval_files/*", "--select=E800"] language_version: python3 additional_dependencies: - flake8-eradicate diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fa569b41..fc0188ea 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,10 +26,10 @@ tox -p --develop ``` ## Meta-tests -To check that all codes are tested and documented there's a test that error codes mentioned in `README.md`, `CHANGELOG.md` (matching `TRIO\d\d\d`), the keys in `flake8_trio.Error_codes` and codes parsed from filenames matching `tests/trio*.py`, are all equal. +To check that all codes are tested and documented there's a test that error codes mentioned in `README.md`, `CHANGELOG.md` (matching `TRIO\d\d\d`), the keys in `flake8_trio.Error_codes` and codes parsed from filenames and files in `tests/eval_files/`, are all equal. ## Test generator -Tests are automatically generated for files named `trio*.py` in the `tests/` directory, with the code that it's testing interpreted from the file name. The file extension is split off, if there's a match for for `_py\d*` it strips that off and uses it to determine if there's a minimum python version for which the test should only run. +Tests are automatically generated for files in the `tests/eval_files/` directory, with the code that it's testing interpreted from the file name. The file extension is split off, if there's a match for for `_py\d*` it strips that off and uses it to determine if there's a minimum python version for which the test should only run. ### `error:` Lines containing `error:` are parsed as expecting an error of the code matching the file name, with everything on the line after the colon `eval`'d and passed as arguments to `flake8_trio.Error_codes[].str_format`. The `globals` argument to `eval` contains a `lineno` variable assigned the current line number, and the `flake8_trio.Statement` namedtuple. The first element after `error:` *must* be an integer containing the column where the error on that line originates. diff --git a/flake8_trio.py b/flake8_trio.py index dc3ace8c..e8daf7ad 100644 --- a/flake8_trio.py +++ b/flake8_trio.py @@ -18,6 +18,7 @@ from argparse import ArgumentTypeError, Namespace from collections.abc import Iterable from fnmatch import fnmatch +from pathlib import Path from typing import TYPE_CHECKING, Any, NamedTuple, TypeVar, Union, cast # guard against internal flake8 changes @@ -1739,7 +1740,7 @@ def __init__(self, tree: ast.AST): self._tree = tree @classmethod - def from_filename(cls, filename: str) -> Plugin: + def from_filename(cls, filename: str | Path) -> Plugin: with tokenize.open(filename) as f: source = f.read() return cls(ast.parse(source)) diff --git a/pyproject.toml b/pyproject.toml index b26029b7..f5ed8b47 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,3 @@ [tool.pyright] strict = ["*.py", "tests/test_flake8_trio.py", "tests/conftest.py"] -exclude = ["tests/trio*.py", "**/node_modules", "**/__pycache__", "**/.*"] +exclude = ["tests/eval_files/*", "**/node_modules", "**/__pycache__", "**/.*"] diff --git a/tests/trio100.py b/tests/eval_files/trio100.py similarity index 100% rename from tests/trio100.py rename to tests/eval_files/trio100.py diff --git a/tests/trio101.py b/tests/eval_files/trio101.py similarity index 100% rename from tests/trio101.py rename to tests/eval_files/trio101.py diff --git a/tests/trio102.py b/tests/eval_files/trio102.py similarity index 100% rename from tests/trio102.py rename to tests/eval_files/trio102.py diff --git a/tests/trio103.py b/tests/eval_files/trio103.py similarity index 100% rename from tests/trio103.py rename to tests/eval_files/trio103.py diff --git a/tests/trio103_no_104.py b/tests/eval_files/trio103_no_104.py similarity index 100% rename from tests/trio103_no_104.py rename to tests/eval_files/trio103_no_104.py diff --git a/tests/trio104.py b/tests/eval_files/trio104.py similarity index 100% rename from tests/trio104.py rename to tests/eval_files/trio104.py diff --git a/tests/trio105.py b/tests/eval_files/trio105.py similarity index 100% rename from tests/trio105.py rename to tests/eval_files/trio105.py diff --git a/tests/trio106.py b/tests/eval_files/trio106.py similarity index 100% rename from tests/trio106.py rename to tests/eval_files/trio106.py diff --git a/tests/trio107.py b/tests/eval_files/trio107.py similarity index 100% rename from tests/trio107.py rename to tests/eval_files/trio107.py diff --git a/tests/trio108.py b/tests/eval_files/trio108.py similarity index 100% rename from tests/trio108.py rename to tests/eval_files/trio108.py diff --git a/tests/trio109.py b/tests/eval_files/trio109.py similarity index 100% rename from tests/trio109.py rename to tests/eval_files/trio109.py diff --git a/tests/trio110.py b/tests/eval_files/trio110.py similarity index 100% rename from tests/trio110.py rename to tests/eval_files/trio110.py diff --git a/tests/trio111.py b/tests/eval_files/trio111.py similarity index 100% rename from tests/trio111.py rename to tests/eval_files/trio111.py diff --git a/tests/trio112.py b/tests/eval_files/trio112.py similarity index 100% rename from tests/trio112.py rename to tests/eval_files/trio112.py diff --git a/tests/trio113.py b/tests/eval_files/trio113.py similarity index 100% rename from tests/trio113.py rename to tests/eval_files/trio113.py diff --git a/tests/trio114.py b/tests/eval_files/trio114.py similarity index 100% rename from tests/trio114.py rename to tests/eval_files/trio114.py diff --git a/tests/trio115.py b/tests/eval_files/trio115.py similarity index 100% rename from tests/trio115.py rename to tests/eval_files/trio115.py diff --git a/tests/trio116.py b/tests/eval_files/trio116.py similarity index 100% rename from tests/trio116.py rename to tests/eval_files/trio116.py diff --git a/tests/trio200.py b/tests/eval_files/trio200.py similarity index 100% rename from tests/trio200.py rename to tests/eval_files/trio200.py diff --git a/tests/trio210.py b/tests/eval_files/trio210.py similarity index 100% rename from tests/trio210.py rename to tests/eval_files/trio210.py diff --git a/tests/trio211.py b/tests/eval_files/trio211.py similarity index 100% rename from tests/trio211.py rename to tests/eval_files/trio211.py diff --git a/tests/trio22x.py b/tests/eval_files/trio22x.py similarity index 100% rename from tests/trio22x.py rename to tests/eval_files/trio22x.py diff --git a/tests/trio23x.py b/tests/eval_files/trio23x.py similarity index 100% rename from tests/trio23x.py rename to tests/eval_files/trio23x.py diff --git a/tests/trio240.py b/tests/eval_files/trio240.py similarity index 100% rename from tests/trio240.py rename to tests/eval_files/trio240.py diff --git a/tests/trio900.py b/tests/eval_files/trio900.py similarity index 100% rename from tests/trio900.py rename to tests/eval_files/trio900.py diff --git a/tests/test_changelog_and_version.py b/tests/test_changelog_and_version.py index c3c068d0..7cca96d7 100644 --- a/tests/test_changelog_and_version.py +++ b/tests/test_changelog_and_version.py @@ -68,14 +68,14 @@ def runTest(self): documented_errors["flake8_trio.py"] = set(ERROR_CODES) # get tested error codes from file names and from `INCLUDE` lines - documented_errors["tests/trio*.py"] = set() - p = Path(__file__).parent + documented_errors["eval_files"] = set() + p = Path(__file__).parent / "eval_files" for file_path in p.iterdir(): if not file_path.is_file(): continue if m := re.search(r"trio\d\d\d", str(file_path)): - documented_errors["tests/trio*.py"].add(m.group().upper()) + documented_errors["eval_files"].add(m.group().upper()) with open(file_path) as file: for line in file: @@ -87,7 +87,7 @@ def runTest(self): # depending on whether there's a group in the pattern or not. # (or bytes, if both inputs are bytes) assert isinstance(m, str) - documented_errors["tests/trio*.py"].add(m) + documented_errors["eval_files"].add(m) break unique_errors: dict[str, set[str]] = {} diff --git a/tests/test_decorator.py b/tests/test_decorator.py index ece721a0..c5e3b21d 100644 --- a/tests/test_decorator.py +++ b/tests/test_decorator.py @@ -1,6 +1,7 @@ from __future__ import annotations import ast +from pathlib import Path from flake8.main.application import Application @@ -80,7 +81,8 @@ def test_pep614(): assert not wrap(("(any, expression, we, like)",), "no match here") -common_flags = ["--select=TRIO", "tests/trio_options.py"] +file_path = str(Path(__file__).parent / "trio_options.py") +common_flags = ["--select=TRIO", file_path] def test_command_line_1(capfd): @@ -89,9 +91,9 @@ def test_command_line_1(capfd): expected_out = ( - "tests/trio_options.py:2:1: TRIO107 " + f"{file_path}:5:1: TRIO107 " + Visitor107_108.error_codes["TRIO107"].format( - "exit", Statement("function definition", 2) + "exit", Statement("function definition", 5) ) + "\n" ) diff --git a/tests/test_flake8_trio.py b/tests/test_flake8_trio.py index c99097fa..6bb16c1b 100644 --- a/tests/test_flake8_trio.py +++ b/tests/test_flake8_trio.py @@ -25,13 +25,8 @@ from flake8_trio import ERROR_CLASSES, Error, Plugin, Statement -# TODO: Move test_eval files into a separate directory -trio_test_files_regex = re.compile(r"trio\d.*.py") - -test_files: list[tuple[str, str]] = sorted( - (os.path.splitext(f)[0].upper(), f) - for f in os.listdir("tests") - if re.match(trio_test_files_regex, f) +test_files: list[tuple[str, Path]] = sorted( + (f.stem.upper(), f) for f in (Path(__file__).parent / "eval_files").iterdir() ) @@ -66,7 +61,7 @@ def check_version(test: str): @pytest.mark.parametrize(("test", "path"), test_files) -def test_eval(test: str, path: str): +def test_eval(test: str, path: Path): # version check check_version(test) test = test.split("_")[0] @@ -81,7 +76,7 @@ def test_eval(test: str, path: str): expected: list[Error] = [] - with open(os.path.join("tests", path), encoding="utf-8") as file: + with open(path, encoding="utf-8") as file: lines = file.readlines() for lineno, line in enumerate(lines, start=1): @@ -146,7 +141,7 @@ def test_eval(test: str, path: str): assert parsed_args, "no parsed_args" assert expected, f"failed to parse any errors in file {path}" - plugin = read_file(path) + plugin = Plugin.from_filename(path) assert_expected_errors(plugin, *expected, args=parsed_args) @@ -189,10 +184,10 @@ def visit_AsyncFor(self, node: ast.AST): @pytest.mark.parametrize(("test", "path"), test_files) -def test_noerror_on_sync_code(test: str, path: str): +def test_noerror_on_sync_code(test: str, path: Path): if any(e in test for e in error_codes_ignored_when_checking_transformed_sync_code): return - with tokenize.open(f"tests/{path}") as f: + with tokenize.open(path) as f: source = f.read() tree = SyncTransformer().visit(ast.parse(source)) @@ -207,11 +202,6 @@ def test_noerror_on_sync_code(test: str, path: str): ) -def read_file(test_file: str): - filename = Path(__file__).absolute().parent / test_file - return Plugin.from_filename(str(filename)) - - def initialize_options(plugin: Plugin, args: list[str] | None = None): om = _default_option_manager() plugin.add_options(om) diff --git a/tests/trio_options.py b/tests/trio_options.py index f5a916ad..951b6a96 100644 --- a/tests/trio_options.py +++ b/tests/trio_options.py @@ -1,3 +1,6 @@ +app = None + + @app.route # type: ignore async def f(): print("hello world")