8000 Meta-tests by jakkdl · Pull Request #14 · python-trio/flake8-async · GitHub
[go: up one dir, main page]

Skip to content

Meta-tests #14

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
Jul 29, 2022
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
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ pip install flake8-trio
- **TRIO100**: a `with trio.fail_after(...):` or `with trio.move_on_after(...):`
context does not contain any `await` statements. This makes it pointless, as
the timeout can only be triggered by a checkpoint.
- **TRIO101** `yield` inside a nursery or cancel scope is only safe when implementing a context manager - otherwise, it breaks exception handling.
- **TRIO102** it's unsafe to await inside `finally:` unless you use a shielded
- **TRIO101**: `yield` inside a nursery or cancel scope is only safe when implementing a context manager - otherwise, it breaks exception handling.
- **TRIO102**: it's unsafe to await inside `finally:` unless you use a shielded
cancel scope with a timeout"
- **TRIO105** Calling a trio async function without immediately `await`ing it.
- **TRIO105**: Calling a trio async function without immediately `await`ing it.
- **TRIO106**: trio must be imported with `import trio` for the linter to work
3 changes: 2 additions & 1 deletion flake8_trio.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@ def check_for_trio100(self, node: Union[ast.With, ast.AsyncWith]) -> None:
for item in (i.context_expr for i in node.items):
call = get_trio_scope(item, *cancel_scope_names)
if call and not any(
isinstance(x, checkpoint_node_types) for x in ast.walk(node)
isinstance(x, checkpoint_node_types) and x != node
for x in ast.walk(node)
):
self.problems.append(
make_error(TRIO100, item.lineno, item.col_offset, call)
Expand Down
25 changes: 24 additions & 1 deletion tests/test_changelog_and_version.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"""Tests for flake8-trio package metadata."""
import re
import unittest
from pathlib import Path
from typing import Iterable, NamedTuple
from typing import Dict, Iterable, NamedTuple, Set

import flake8_trio

Expand Down Expand Up @@ -43,3 +44,25 @@ def test_version_increments_are_correct():
assert prev[:2] < current[:2], msg
else:
assert current == prev._replace(patch=prev.patch + 1), msg


class test_messages_documented(unittest.TestCase):
def runTest(self):
documented_errors: Dict[str, Set[str]] = {}
for filename in (
"CHANGELOG.md",
"README.md",
"flake8_trio.py",
"tests/test_flake8_trio.py",
):
with open(Path(__file__).parent.parent / filename) as f:
lines = f.readlines()
documented_errors[filename] = set()
for line in lines:
for error_msg in re.findall(r"TRIO\d\d\d", line):
documented_errors[filename].add(error_msg)

file_items = list(documented_errors.items())
first = file_items.pop()
for file, documented in file_items:
self.assertSetEqual(first[1], documented, msg=(first[0], file))
13 changes: 7 additions & 6 deletions tests/test_flake8_trio.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,19 @@ def assert_expected_errors(self, test_file: str, *expected: Error) -> None:
filename = Path(__file__).absolute().parent / test_file
plugin = Plugin.from_filename(str(filename))
errors = tuple(plugin.run())
self.assertEqual(errors, expected)
self.assertTupleEqual(errors, expected)

def test_tree(self):
plugin = Plugin(ast.parse(""))
errors = list(plugin.run())
self.assertEqual(errors, [])
self.assertSequenceEqual(errors, [])

def test_trio100(self):
self.assert_expected_errors(
"trio100.py",
make_error(TRIO100, 3, 5, "trio.move_on_after"),
make_error(TRIO100, 23, 9, "trio.fail_after"),
make_error(TRIO100, 8, 15, "trio.fail_after"),
make_error(TRIO100, 26, 9, "trio.fail_after"),
)

@unittest.skipIf(sys.version_info < (3, 9), "requires 3.9+")
Expand Down Expand Up @@ -100,11 +101,11 @@ def test_trio105(self):
make_error(TRIO105, 36, 4, "sleep"),
make_error(TRIO105, 37, 4, "sleep_forever"),
make_error(TRIO105, 38, 4, "sleep_until"),
make_error(TRIO105, 45, 15, "open_file"),
make_error(TRIO105, 50, 8, "open_file"),
make_error(TRIO105, 44, 15, "open_file"),
make_error(TRIO105, 49, 8, "open_file"),
)

self.assertEqual(
self.assertSetEqual(
set(trio_async_functions),
{
o[0]
Expand Down
51 changes: 51 additions & 0 deletions tests/test_trio_tests.py
8000
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import inspect
import os
import os.path
import re
import unittest
from types import FunctionType
from typing import Dict, Optional, Set, Tuple

from test_flake8_trio import Flake8TrioTestCase


class TestTrioTests(unittest.TestCase):
def runTest(self):
# get files
trio_tests: Dict[str, Tuple[str, Optional[FunctionType]]] = {
os.path.splitext(f)[0]: (f, None)
for f in os.listdir("tests")
if re.match(r"^trio.*.py", f)
}

# get functions
for o in inspect.getmembers(Flake8TrioTestCase):
if inspect.isfunction(o[1]) and re.match(r"^test_trio\d\d\d", o[0]):
key = o[0][5:]

self.assertIn(key, trio_tests)
self.assertIsNone(trio_tests[key][1], msg=key)

trio_tests[key] = (trio_tests[key][0], o[1])

for test, (filename, func) in sorted(trio_tests.items()):
self.assertIsNotNone(func, msg=test)
assert func is not None # for type checkers

with open(os.path.join("tests", filename), encoding="utf-8") as file:
file_error_lines = {
lineno + 1
for lineno, line in enumerate(file)
if re.search(r"# *error", line, flags=re.I)
}

func_error_lines: Set[int] = set()
for line in inspect.getsourcelines(func)[0]:
m = re.search(r"(?<=make_error\(TRIO\d\d\d, )\d*", line)
if not m:
continue
lineno = int(m.group())
self.assertNotIn(lineno, func_error_lines, msg=test)
func_error_lines.add(lineno)

self.assertSetEqual(file_error_lines, func_error_lines, msg=test)
7 changes: 5 additions & 2 deletions tests/trio100.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import trio

with trio.move_on_after(10):
with trio.move_on_after(10): # error
pass


async def function_name():
async with trio.fail_after(10): # error
pass

with trio.move_on_after(10):
await trio.sleep(1)

Expand All @@ -20,7 +23,7 @@ async def function_name():
with open("filename") as _:
pass

with trio.fail_after(10):
with trio.fail_after(10): # error
pass

send_channel, receive_channel = trio.open_memory_channel(0)
Expand Down
6 changes: 3 additions & 3 deletions tests/trio100_py39.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
async def function_name():
with (
open("veryverylongfilenamesoshedsplitsthisintotwolines") as _,
trio.fail_after(10),
trio.fail_after(10), # error
):
pass

with (
trio.fail_after(5),
trio.fail_after(5), # error
open("veryverylongfilenamesoshedsplitsthisintotwolines") as _,
trio.move_on_after(5),
trio.move_on_after(5), # error
):
pass
8 changes: 4 additions & 4 deletions tests/trio102.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ async def foo():
pass
finally:
with open("bar"):
await foo() # safe
await foo() # error
with open("bar"):
pass
with trio.move_on_after():
Expand All @@ -81,11 +81,11 @@ async def foo():
with trio.CancelScope(deadline=30):
await foo() # error
with trio.CancelScope(deadline=30, shield=(1 == 1)):
await foo() # safe in theory, but deemed error
await foo() # error: though safe in theory
myvar = True
with trio.open_nursery(10) as s:
s.shield = myvar
await foo() # safe in theory, but deemed error
await foo() # error: though safe in theory
with trio.CancelScope(deadline=30, shield=True):
with trio.move_on_after(30):
await foo() # safe
Expand Down Expand Up @@ -120,4 +120,4 @@ async def foo3():
await foo() # safe
with trio.fail_after(5), trio.move_on_after(30) as s:
s.shield = True
await foo() # safe in theory, but we don't bother parsing
await foo() # error: safe in theory, but we don't bother parsing
35 changes: 17 additions & 18 deletions tests/trio105.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,31 +21,30 @@ async def foo():
await trio.sleep_forever()
await trio.sleep_until()

# errors
trio.aclose_forcefully()
trio.open_file()
trio.open_ssl_over_tcp_listeners()
trio.open_ssl_over_tcp_stream()
trio.open_tcp_listeners()
trio.open_tcp_stream()
trio.open_unix_socket()
trio.run_process()
trio.serve_listeners()
trio.serve_ssl_over_tcp()
trio.serve_tcp()
trio.sleep()
trio.sleep_forever()
trio.sleep_until()
# all async functions
trio.aclose_forcefully() # error
trio.open_file() # error
trio.open_ssl_over_tcp_listeners() # error
trio.open_ssl_over_tcp_stream() # error
trio.open_tcp_listeners() # error
trio.open_tcp_stream() # error
trio.open_unix_socket() # error
trio.run_process() # error
trio.serve_listeners() # error
trio.serve_ssl_over_tcp() # error
trio.serve_tcp() # error
trio.sleep() # error
trio.sleep_forever() # error
trio.sleep_until() # error

# safe
async with await trio.open_file() as f:
pass

# error
async with trio.open_file() as f:
async with trio.open_file() as f: # error
pass

# safe in theory, but deemed sufficiently poor style that parsing
# it isn't supported
k = trio.open_file()
k = trio.open_file() # error
await k
6 changes: 3 additions & 3 deletions tests/trio106.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import importlib

import trio
import trio as foo
from trio import * # noqa
from trio import blah, open_file as foo # noqa
import trio as foo # error
from trio import * # noqa # error
from trio import blah, open_file as foo # noqa # error

# Note that our tests exercise the Visitor classes, without going through the noqa filter later in flake8 - so these suppressions are picked up by our project-wide linter stack but not the tests.

Expand Down
0