8000 Move eval files into a separate directory by jakkdl · Pull Request #102 · python-trio/flake8-async · GitHub
[go: up one dir, main page]

Skip to content

Move eval files into a separate directory #102

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ repos:
rev: 6.0.0
hooks:
- id: flake8
args: ["--exclude", ".*,tests/trio*.py"]
args: ["--exclude", ".*,tests/eval_files/*"]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing the linter exclude since new location

language_version: python3
additional_dependencies:
- flake8-builtins
Expand All @@ -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"]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately still duplicated hook so need to change here as well

language_version: python3
additional_dependencies:
- flake8-eradicate
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Comment on lines -29 to +32
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

small doc update


### `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[<error_code>].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.
Expand Down
3 changes: 2 additions & 1 deletion flake8_trio.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Comment on lines -1742 to +1743
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed test_flake8_trio.py to use Path, but since you can just pass that on to tokenize.open it needed no code change. Nothing actually passes a str parameter anymore, but I see no reason not to continue accepting that

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think to be excruciatingly correct we could specify os.PathLike here, but I think the union is fine 😃

with tokenize.open(filename) as f:
source = f.read()
return cls(ast.parse(source))
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -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__", "**/.*"]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to change exclude rules here as well for pyright

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
8 changes: 4 additions & 4 deletions tests/test_changelog_and_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Comment on lines -71 to +72
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed the dict key to "eval_files", and now look for them in the eval_files subdirectory. The remaining changes in this function are just updating the dict key.

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:
Expand All @@ -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]] = {}
Expand Down
8 changes: 5 additions & 3 deletions tests/test_decorator.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import ast
from pathlib import Path

from flake8.main.application import Application

Expand Down Expand Up @@ -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]
Comment on lines -83 to +85
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File path is now no longer hardcoded, but expects trio_options.py in the same directory. Not strictly necessary since this directory isn't moved, but makes it more robust.



def test_command_line_1(capfd):
Expand All @@ -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)
Comment on lines -92 to +96
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update to using the new file_path variable instead of hardcoding the expected file location in the error message. See the comment for trio_options.py for the row change.

)
+ "\n"
)
Expand Down
24 changes: 7 additions & 17 deletions tests/test_flake8_trio.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
)
Comment on lines -28 to 30
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for a regex, and using Path functions to do os.path.splitext (.stem) and os.listdir (.iterdir()).
It now also runs regardless of your current working directory, while previously it required being in the project root.



Expand Down Expand Up @@ -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]
Expand All @@ -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:
Comment on lines -84 to +79
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with path being a Path this can be simplified

lines = file.readlines()

for lineno, line in enumerate(lines, start=1):
Expand Down Expand Up @@ -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)
Comment on lines -149 to +144
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With Plugin.from_filename taking a Path, and path containing the full path, we don't need the helper function at all here.

assert_expected_errors(plugin, *expected, args=parsed_args)


Expand Down Expand Up @@ -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:
Comment on lines -192 to +190
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More simplifying thanks to Path.

source = f.read()
tree = SyncTransformer().visit(ast.parse(source))

Expand All @@ -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))


Comment on lines -210 to -214
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With moving everything to using Path this function can now be completely removed 🎉

def initialize_options(plugin: Plugin, args: list[str] | None = None):
om = _default_option_manager()
plugin.add_options(om)
Expand Down
3 changes: 3 additions & 0 deletions tests/trio_options.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
app = None


@app.route # type: ignore
async def f():
print("hello world")
Comment on lines +1 to 6
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With these changes this file passes linters, so I don't have to exclude it in the rules. It was previously caught by the tests/trio_*.py pattern, but since it's not an eval'd file I didn't move it into the eval directory.
Although given the support for #ARG it's actually pretty simple to remove the dedicated test for this and make it an eval'd test file - but I'll leave that for a later TODO. Otherwise this file would probably benefit from a comment and/or another directory for other_test_files (or something)

0