From 83219d436a7bf964384801a36a25ca601f1ea0d9 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Thu, 30 Nov 2023 15:50:19 +0100 Subject: [PATCH 1/5] pass lint again --- pyproject.toml | 5 +++-- src/pytest_patterns/plugin.py | 4 ++-- tests/test_basics.py | 8 ++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e612c9a..f58d935 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ path = "src/pytest_patterns/__about__.py" [tool.hatch.envs.default] dependencies = [ "coverage[toml]>=6.5", - "pytest", + "pytest>=7", "pytest-cov", ] @@ -59,12 +59,13 @@ python = ["3.7", "3.8", "3.9", "3.10", "3.11"] [tool.hatch.envs.lint] detached = true dependencies = [ + "pytest>=7", "black>=23.1.0", "mypy>=1.0.0", "ruff>=0.0.243", ] [tool.hatch.envs.lint.scripts] -typing = "mypy --install-types --non-interactive {args:src/pytest_patterns tests}" +typing = "mypy {args:src/pytest_patterns tests}" style = [ "ruff {args:.}", "black --check --diff {args:.}", diff --git a/src/pytest_patterns/plugin.py b/src/pytest_patterns/plugin.py index 15d8baf..a733181 100644 --- a/src/pytest_patterns/plugin.py +++ b/src/pytest_patterns/plugin.py @@ -1,6 +1,6 @@ import enum import re -from typing import Iterable, List, Set, Tuple +from typing import List, Set, Tuple import pytest @@ -207,7 +207,7 @@ def __init__(self, library, name): # Modifiers (Verbs) def merge(self, *base_patterns): - """Merge the rules from those patterns (recursively) into this pattern.""" + """Merge rules from base_patterns (recursively) into this pattern.""" self.inherited.update(base_patterns) def normalize(self, mode: str): diff --git a/tests/test_basics.py b/tests/test_basics.py index 9a46bfa..7ed07a1 100644 --- a/tests/test_basics.py +++ b/tests/test_basics.py @@ -370,7 +370,7 @@ def test_complex_example(patterns, fcqemu_patterns): simplevm outmigrate-finished exitcode=0 simplevm release-lock count=0 target='/run/qemu.simplevm.lock' simplevm release-lock result='unlocked' target='/run/qemu.simplevm.lock' -""" +""" # noqa: E501 ) # The migration process may take a couple of rounds to complete, # so this might appear more often: @@ -378,7 +378,7 @@ def test_complex_example(patterns, fcqemu_patterns): """ simplevm query-migrate arguments={} id=None subsystem='qemu/qmp' simplevm migration-status mbps=... remaining='...' status='active' -""" +""" # noqa: E501 ) assert ( @@ -670,7 +670,7 @@ def test_complex_example(patterns, fcqemu_patterns): simplevm outmigrate-finished exitcode=0 simplevm release-lock count=0 target='/run/qemu.simplevm.lock' simplevm release-lock result='unlocked' target='/run/qemu.simplevm.lock' -""" +""" # noqa: E501 ) @@ -849,7 +849,7 @@ def test_html(patterns): -""" +""" # noqa: E501 ) From 409d16ea78065146abc157455612657edf9755f7 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Thu, 30 Nov 2023 15:54:44 +0100 Subject: [PATCH 2/5] sync pre-commit with lint --- .pre-commit-config.yaml | 11 +++-------- pyproject.toml | 3 +-- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 88acfb3..ce58e1d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -30,12 +30,7 @@ repos: - id: black repo: https://github.com/psf/black rev: 23.3.0 -- hooks: - - args: - - --ignore - - E501 - - --ignore - - F401 - id: ruff - repo: https://github.com/astral-sh/ruff-pre-commit +- repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.0.289 + hooks: + - id: ruff diff --git a/pyproject.toml b/pyproject.toml index f58d935..2956187 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ classifiers = [ "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", ] -dependencies = [] +dependencies = ["pytest>=7",] [project.urls] Documentation = "https://github.com/unknown/pytest-patterns#readme" @@ -41,7 +41,6 @@ path = "src/pytest_patterns/__about__.py" [tool.hatch.envs.default] dependencies = [ "coverage[toml]>=6.5", - "pytest>=7", "pytest-cov", ] From 2373c6c0f31423923ee9904a7e67ef075ecf42c5 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Thu, 30 Nov 2023 16:26:03 +0100 Subject: [PATCH 3/5] mypy strict pass --- pyproject.toml | 4 ++ src/pytest_patterns/plugin.py | 84 +++++++++++++++++++---------------- tests/test_basics.py | 31 ++++++------- 3 files changed, 66 insertions(+), 53 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2956187..52a9e76 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -163,3 +163,7 @@ exclude_lines = [ [tool.isort] profile = "black" line_length = 80 + +[tool.mypy] +strict=true +python_version = "3.8" \ No newline at end of file diff --git a/src/pytest_patterns/plugin.py b/src/pytest_patterns/plugin.py index a733181..8e46456 100644 --- a/src/pytest_patterns/plugin.py +++ b/src/pytest_patterns/plugin.py @@ -1,22 +1,25 @@ import enum import re -from typing import List, Set, Tuple +from typing import List, Set, Tuple, Any, Iterator, Optional, Union import pytest @pytest.fixture -def patterns(): - yield PatternsLib() +def patterns() -> "PatternsLib": + return PatternsLib() -def pytest_assertrepr_compare(op, left, right): +def pytest_assertrepr_compare(op: str, left: Any, right: Any) -> Optional[List[str]]: if op != "==": - return - if left.__class__.__name__ == "Pattern": + return None + if isinstance(left, Pattern): return list(left._audit(right).report()) - elif right.__class__.__name__ == "Pattern": + elif isinstance(right, Pattern): return list(right._audit(left).report()) + else: + return None + class Status(enum.Enum): @@ -26,7 +29,7 @@ class Status(enum.Enum): REFUSED = 4 @property - def symbol(self): + def symbol(self) -> str: return STATUS_SYMBOLS[self] @@ -40,7 +43,7 @@ def symbol(self): EMPTY_LINE_PATTERN = "" -def match(pattern, line): +def match(pattern: str, line: str) -> Optional[ Union[bool, re.Match[str]]]: if pattern == EMPTY_LINE_PATTERN: if not line: return True @@ -48,8 +51,8 @@ def match(pattern, line): line = line.replace("\t", " " * 8) pattern = re.escape(pattern) pattern = pattern.replace(r"\.\.\.", ".*?") - pattern = re.compile("^" + pattern + "$") - return pattern.match(line) + re_pattern = re.compile("^" + pattern + "$") + return re_pattern.match(line) class Line: @@ -59,10 +62,10 @@ class Line: def __init__(self, data: str): self.data = data - def matches(self, expectation: str): + def matches(self, expectation: str) -> bool: return bool(match(expectation, self.data)) - def mark(self, status: Status, cause: str): + def mark(self, status: Status, cause: str) -> None: if status.value <= self.status.value: # Stay in the current status return @@ -83,10 +86,10 @@ def __init__(self, content: str): for line in content.splitlines(): self.content.append(Line(line)) - def cursor(self): + def cursor(self) -> Iterator[Line]: return iter(self.content) - def in_order(self, name: str, expected_lines: List[str]): + def in_order(self, name: str, expected_lines: List[str]) -> None: """Expect all lines exist and come in order, but they may be interleaved with other lines.""" cursor = self.cursor() @@ -100,7 +103,7 @@ def in_order(self, name: str, expected_lines: List[str]): # Reset the scan, maybe the other lines will match cursor = self.cursor() - def optional(self, name: str, tolerated_lines: List[str]): + def optional(self, name: str, tolerated_lines: List[str]) -> None: """Those lines may exist and then they may appear anywhere a number of times, or they may not exist. """ @@ -109,14 +112,14 @@ def optional(self, name: str, tolerated_lines: List[str]): if line.matches(tolerated_line): line.mark(Status.OPTIONAL, name) - def refused(self, name: str, refused_lines: List[str]): + def refused(self, name: str, refused_lines: List[str]) -> None: for refused_line in refused_lines: for line in self.cursor(): if line.matches(refused_line): line.mark(Status.REFUSED, name) self.matched_refused.add((name, refused_line)) - def continuous(self, name: str, continuous_lines: List[str]): + def continuous(self, name: str, continuous_lines: List[str]) -> None: continuous_cursor = enumerate(continuous_lines) continuous_index, continuous_line = next(continuous_cursor) for line in self.cursor(): @@ -148,7 +151,7 @@ def continuous(self, name: str, continuous_lines: List[str]): [(name, line) for i, line in continuous_cursor] ) - def report(self): + def report(self) -> Iterator[str]: yield "String did not meet the expectations." yield "" yield " | ".join( @@ -170,16 +173,16 @@ def report(self): yield "" yield "These are the unmatched expected lines: " yield "" - for name, line in self.unmatched_expectations: - yield format_line_report(Status.REFUSED.symbol, name, line) + for name, line_str in self.unmatched_expectations: + yield format_line_report(Status.REFUSED.symbol, name, line_str) if self.matched_refused: yield "" yield "These are the matched refused lines: " yield "" - for name, line in self.matched_refused: - yield format_line_report(Status.REFUSED.symbol, name, line) + for name, line_str in self.matched_refused: + yield format_line_report(Status.REFUSED.symbol, name, line_str) - def is_ok(self): + def is_ok(self) -> bool: if self.unmatched_expectations: return False for line in self.content: @@ -188,7 +191,7 @@ def is_ok(self): return True -def format_line_report(symbol, cause, line): +def format_line_report(symbol: str, cause: str, line: str) -> str: return symbol + " " + cause.ljust(15)[:15] + " | " + line @@ -198,7 +201,12 @@ def pattern_lines(lines: str) -> List[str]: class Pattern: - def __init__(self, library, name): + name: str + library: "PatternsLib" + ops: List[Tuple[str, str, Any]] + inherited: Set[str] + + def __init__(self, library: "PatternsLib", name: str): self.name = name self.library = library self.ops = [] @@ -206,51 +214,51 @@ def __init__(self, library, name): # Modifiers (Verbs) - def merge(self, *base_patterns): + def merge(self, *base_patterns: str) -> None: """Merge rules from base_patterns (recursively) into this pattern.""" self.inherited.update(base_patterns) - def normalize(self, mode: str): + def normalize(self, mode: str) -> None: pass # Matches (Adjectives) - def continuous(self, lines: str): + def continuous(self, lines: str) -> None: """These lines must appear once and they must be continuous.""" self.ops.append(("continuous", self.name, pattern_lines(lines))) - def in_order(self, lines: str): + def in_order(self, lines: str) -> None: """These lines must appear once and they must be in order.""" self.ops.append(("in_order", self.name, pattern_lines(lines))) - def optional(self, lines: str): + def optional(self, lines: str) -> None: """These lines are optional.""" self.ops.append(("optional", self.name, pattern_lines(lines))) - def refused(self, lines: str): + def refused(self, lines: str) -> None: """If those lines appear they are refused.""" self.ops.append(("refused", self.name, pattern_lines(lines))) # Internal API - def flat_ops(self): + def flat_ops(self) -> Iterator[Tuple[str, str, Any]]: for inherited_pattern in self.inherited: yield from getattr(self.library, inherited_pattern).flat_ops() yield from self.ops - def _audit(self, content): + def _audit(self, content: str) -> Audit: audit = Audit(content) for op, *args in self.flat_ops(): getattr(audit, op)(*args) return audit - def __eq__(self, other): + def __eq__(self, other: object) -> bool: assert isinstance(other, str) audit = self._audit(other) return audit.is_ok() class PatternsLib: - def __getattr__(self, name): - self.__dict__[name] = Pattern(self, name) - return self.__dict__[name] + def __getattr__(self, name: str) -> Pattern: + res = self.__dict__[name] = Pattern(self, name) + return res diff --git a/tests/test_basics.py b/tests/test_basics.py index 7ed07a1..788bce3 100644 --- a/tests/test_basics.py +++ b/tests/test_basics.py @@ -1,4 +1,5 @@ import pytest +from pytest_patterns.plugin import PatternsLib GENERIC_HEADER = [ "String did not meet the expectations.", @@ -10,11 +11,11 @@ ] -def test_patternslib_multiple_accesses(patterns): +def test_patternslib_multiple_accesses(patterns: PatternsLib) -> None: assert patterns.foo is patterns.foo -def test_empty_pattern_empty_string_is_ok(patterns): +def test_empty_pattern_empty_string_is_ok(patterns:PatternsLib) -> None: # This is fine IMHO. The whole general assumption is that we only reject # unexpected content and fail if required content is missing. If there is # no content, then there is no unexpected content and if you didn't expect @@ -24,7 +25,7 @@ def test_empty_pattern_empty_string_is_ok(patterns): assert audit.is_ok() -def test_unexpected_lines_fail(patterns): +def test_unexpected_lines_fail(patterns:PatternsLib) -> None: audit = patterns.nothing._audit("This is an unexpected line") assert list(audit.report()) == [ *GENERIC_HEADER, @@ -33,7 +34,7 @@ def test_unexpected_lines_fail(patterns): assert not audit.is_ok() -def test_empty_lines_do_not_match(patterns): +def test_empty_lines_do_not_match(patterns :PatternsLib) -> None: patterns.nothing.optional("") audit = patterns.nothing._audit( """ @@ -46,7 +47,7 @@ def test_empty_lines_do_not_match(patterns): assert not audit.is_ok() -def test_empty_lines_match_special_marker(patterns): +def test_empty_lines_match_special_marker(patterns: PatternsLib) -> None: patterns.empty.optional("") audit = patterns.empty._audit( """ @@ -63,7 +64,7 @@ def test_empty_lines_match_special_marker(patterns): assert audit.is_ok() -def test_comprehensive(patterns): +def test_comprehensive(patterns: PatternsLib) -> None: sample = patterns.sample sample.in_order( @@ -106,7 +107,7 @@ def test_comprehensive(patterns): ) -def test_in_order_lines_clear_with_intermittent_input(patterns): +def test_in_order_lines_clear_with_intermittent_input(patterns: PatternsLib) -> None: pattern = patterns.in_order pattern.in_order( """ @@ -131,7 +132,7 @@ def test_in_order_lines_clear_with_intermittent_input(patterns): assert audit.is_ok() -def test_missing_ordered_lines_fail(patterns): +def test_missing_ordered_lines_fail(patterns: PatternsLib) -> None: pattern = patterns.in_order pattern.in_order( """ @@ -156,7 +157,7 @@ def test_missing_ordered_lines_fail(patterns): assert not audit.is_ok() -def test_refused_lines_fail(patterns): +def test_refused_lines_fail(patterns: PatternsLib) -> None: pattern = patterns.refused pattern.refused("This is a refused line") @@ -172,7 +173,7 @@ def test_refused_lines_fail(patterns): assert not audit.is_ok() -def test_continuous_lines_only_clear_if_not_interrupted(patterns): +def test_continuous_lines_only_clear_if_not_interrupted(patterns: PatternsLib) -> None: pattern = patterns.focus pattern.optional("asdf") pattern.continuous( @@ -239,7 +240,7 @@ def test_continuous_lines_only_clear_if_not_interrupted(patterns): assert not audit.is_ok() -def test_continuous_lines_fail_and_report_if_first_line_isnt_matching(patterns): +def test_continuous_lines_fail_and_report_if_first_line_isnt_matching(patterns: PatternsLib) -> None: pattern = patterns.focus pattern.continuous( """ @@ -267,7 +268,7 @@ def test_continuous_lines_fail_and_report_if_first_line_isnt_matching(patterns): assert not audit.is_ok() -def test_optional(patterns): +def test_optional(patterns: PatternsLib) -> None: pattern = patterns.optional pattern.optional("pong") pattern.optional("ping") @@ -285,7 +286,7 @@ def test_optional(patterns): @pytest.fixture() -def fcqemu_patterns(patterns): +def fcqemu_patterns(patterns: PatternsLib) -> None: patterns.debug.optional("simplevm> ...") # This part of the heartbeats must show up @@ -308,7 +309,7 @@ def fcqemu_patterns(patterns): patterns.failure.refused("...fail...") -def test_complex_example(patterns, fcqemu_patterns): +def test_complex_example(patterns: PatternsLib, fcqemu_patterns: None) -> None: outmigration = patterns.outmigration outmigration.merge("debug", "heartbeat", "failure") @@ -674,7 +675,7 @@ def test_complex_example(patterns, fcqemu_patterns): ) -def test_html(patterns): +def test_html(patterns: PatternsLib) -> None: patterns.owrap.in_order( """ From f5af2d7a44727c5f3b71a4268b1ac2f6dc215521 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Thu, 30 Nov 2023 16:29:48 +0100 Subject: [PATCH 4/5] python3.8 Match typing --- src/pytest_patterns/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pytest_patterns/plugin.py b/src/pytest_patterns/plugin.py index 8e46456..465f21a 100644 --- a/src/pytest_patterns/plugin.py +++ b/src/pytest_patterns/plugin.py @@ -43,7 +43,7 @@ def symbol(self) -> str: EMPTY_LINE_PATTERN = "" -def match(pattern: str, line: str) -> Optional[ Union[bool, re.Match[str]]]: +def match(pattern: str, line: str) -> Optional[ Union[bool, "re.Match[str]"]]: if pattern == EMPTY_LINE_PATTERN: if not line: return True From a258609df4834063f89c5b4c8b5eed20cea16e36 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Thu, 30 Nov 2023 17:16:46 +0100 Subject: [PATCH 5/5] future __annotations__ and pyproject-fmt --- .pre-commit-config.yaml | 11 +++++++--- pyproject.toml | 35 ++++++++++++++++++------------- src/pytest_patterns/plugin.py | 39 +++++++++++++++++++---------------- tests/test_basics.py | 19 +++++++++++------ 4 files changed, 63 insertions(+), 41 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ce58e1d..1380ed2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - exclude: "(?x)^(\n environments/.*/secret.*|\n .*\\.patch\n)$\n" id: check-toml repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 - hooks: - args: - --profile @@ -29,8 +29,13 @@ repos: - hooks: - id: black repo: https://github.com/psf/black - rev: 23.3.0 + rev: 23.11.0 - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.289 + rev: v0.1.6 hooks: - id: ruff + args: [--fix, --exit-non-zero-on-fix] +- repo: https://github.com/tox-dev/pyproject-fmt + rev: "1.5.2" + hooks: + - id: pyproject-fmt diff --git a/pyproject.toml b/pyproject.toml index 52a9e76..2a5291b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,37 +1,44 @@ [build-system] -requires = ["hatchling"] build-backend = "hatchling.build" +requires = [ + "hatchling", +] [project] name = "pytest-patterns" -dynamic = ["version"] -description = 'pytest plugin to make testing complicated long string output easy to write and easy to debug' +description = "pytest plugin to make testing complicated long string output easy to write and easy to debug" readme = "README.md" -requires-python = ">=3.7" +keywords = [ +] license = "MIT" -keywords = [] authors = [ { name = "Christian Theune", email = "ct@flyingcircus.io" }, ] +requires-python = ">=3.7" classifiers = [ - "Framework :: Pytest", "Development Status :: 4 - Beta", + "Framework :: Pytest", "Programming Language :: Python", + "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", ] -dependencies = ["pytest>=7",] - +dynamic = [ + "version", +] +dependencies = [ + "pytest>=7", +] [project.urls] Documentation = "https://github.com/unknown/pytest-patterns#readme" Issues = "https://github.com/unknown/pytest-patterns/issues" Source = "https://github.com/unknown/pytest-patterns" - [project.entry-points.pytest11] myproject = "pytest_patterns.plugin" @@ -141,6 +148,10 @@ ban-relative-imports = "all" # Tests can use magic values, assertions, and relative imports "tests/**/*" = ["PLR2004", "S101", "TID252"] +[tool.isort] +profile = "black" +line_length = 80 + [tool.coverage.run] source_pkgs = ["pytest_patterns", "tests"] branch = true @@ -160,10 +171,6 @@ exclude_lines = [ "if TYPE_CHECKING:", ] -[tool.isort] -profile = "black" -line_length = 80 - [tool.mypy] strict=true -python_version = "3.8" \ No newline at end of file +python_version = "3.8" diff --git a/src/pytest_patterns/plugin.py b/src/pytest_patterns/plugin.py index 465f21a..3ca016b 100644 --- a/src/pytest_patterns/plugin.py +++ b/src/pytest_patterns/plugin.py @@ -1,16 +1,20 @@ +from __future__ import annotations + import enum import re -from typing import List, Set, Tuple, Any, Iterator, Optional, Union +from typing import Any, Iterator import pytest @pytest.fixture -def patterns() -> "PatternsLib": +def patterns() -> PatternsLib: return PatternsLib() -def pytest_assertrepr_compare(op: str, left: Any, right: Any) -> Optional[List[str]]: +def pytest_assertrepr_compare( + op: str, left: Any, right: Any +) -> list[str] | None: if op != "==": return None if isinstance(left, Pattern): @@ -21,7 +25,6 @@ def pytest_assertrepr_compare(op: str, left: Any, right: Any) -> Optional[List[s return None - class Status(enum.Enum): UNEXPECTED = 1 OPTIONAL = 2 @@ -43,7 +46,7 @@ def symbol(self) -> str: EMPTY_LINE_PATTERN = "" -def match(pattern: str, line: str) -> Optional[ Union[bool, "re.Match[str]"]]: +def match(pattern: str, line: str) -> bool | re.Match[str] | None: if pattern == EMPTY_LINE_PATTERN: if not line: return True @@ -74,9 +77,9 @@ def mark(self, status: Status, cause: str) -> None: class Audit: - content: List[Line] - unmatched_expectations: List[Tuple[str, str]] - matched_refused: Set[Tuple[str, str]] + content: list[Line] + unmatched_expectations: list[tuple[str, str]] + matched_refused: set[tuple[str, str]] def __init__(self, content: str): self.unmatched_expectations = [] @@ -89,7 +92,7 @@ def __init__(self, content: str): def cursor(self) -> Iterator[Line]: return iter(self.content) - def in_order(self, name: str, expected_lines: List[str]) -> None: + def in_order(self, name: str, expected_lines: list[str]) -> None: """Expect all lines exist and come in order, but they may be interleaved with other lines.""" cursor = self.cursor() @@ -103,7 +106,7 @@ def in_order(self, name: str, expected_lines: List[str]) -> None: # Reset the scan, maybe the other lines will match cursor = self.cursor() - def optional(self, name: str, tolerated_lines: List[str]) -> None: + def optional(self, name: str, tolerated_lines: list[str]) -> None: """Those lines may exist and then they may appear anywhere a number of times, or they may not exist. """ @@ -112,14 +115,14 @@ def optional(self, name: str, tolerated_lines: List[str]) -> None: if line.matches(tolerated_line): line.mark(Status.OPTIONAL, name) - def refused(self, name: str, refused_lines: List[str]) -> None: + def refused(self, name: str, refused_lines: list[str]) -> None: for refused_line in refused_lines: for line in self.cursor(): if line.matches(refused_line): line.mark(Status.REFUSED, name) self.matched_refused.add((name, refused_line)) - def continuous(self, name: str, continuous_lines: List[str]) -> None: + def continuous(self, name: str, continuous_lines: list[str]) -> None: continuous_cursor = enumerate(continuous_lines) continuous_index, continuous_line = next(continuous_cursor) for line in self.cursor(): @@ -195,18 +198,18 @@ def format_line_report(symbol: str, cause: str, line: str) -> str: return symbol + " " + cause.ljust(15)[:15] + " | " + line -def pattern_lines(lines: str) -> List[str]: +def pattern_lines(lines: str) -> list[str]: # Remove leading whitespace, ignore empty lines. return list(filter(None, lines.splitlines())) class Pattern: name: str - library: "PatternsLib" - ops: List[Tuple[str, str, Any]] - inherited: Set[str] + library: PatternsLib + ops: list[tuple[str, str, Any]] + inherited: set[str] - def __init__(self, library: "PatternsLib", name: str): + def __init__(self, library: PatternsLib, name: str): self.name = name self.library = library self.ops = [] @@ -241,7 +244,7 @@ def refused(self, lines: str) -> None: # Internal API - def flat_ops(self) -> Iterator[Tuple[str, str, Any]]: + def flat_ops(self) -> Iterator[tuple[str, str, Any]]: for inherited_pattern in self.inherited: yield from getattr(self.library, inherited_pattern).flat_ops() yield from self.ops diff --git a/tests/test_basics.py b/tests/test_basics.py index 788bce3..5b96d87 100644 --- a/tests/test_basics.py +++ b/tests/test_basics.py @@ -1,4 +1,5 @@ import pytest + from pytest_patterns.plugin import PatternsLib GENERIC_HEADER = [ @@ -15,7 +16,7 @@ def test_patternslib_multiple_accesses(patterns: PatternsLib) -> None: assert patterns.foo is patterns.foo -def test_empty_pattern_empty_string_is_ok(patterns:PatternsLib) -> None: +def test_empty_pattern_empty_string_is_ok(patterns: PatternsLib) -> None: # This is fine IMHO. The whole general assumption is that we only reject # unexpected content and fail if required content is missing. If there is # no content, then there is no unexpected content and if you didn't expect @@ -25,7 +26,7 @@ def test_empty_pattern_empty_string_is_ok(patterns:PatternsLib) -> None: assert audit.is_ok() -def test_unexpected_lines_fail(patterns:PatternsLib) -> None: +def test_unexpected_lines_fail(patterns: PatternsLib) -> None: audit = patterns.nothing._audit("This is an unexpected line") assert list(audit.report()) == [ *GENERIC_HEADER, @@ -34,7 +35,7 @@ def test_unexpected_lines_fail(patterns:PatternsLib) -> None: assert not audit.is_ok() -def test_empty_lines_do_not_match(patterns :PatternsLib) -> None: +def test_empty_lines_do_not_match(patterns: PatternsLib) -> None: patterns.nothing.optional("") audit = patterns.nothing._audit( """ @@ -107,7 +108,9 @@ def test_comprehensive(patterns: PatternsLib) -> None: ) -def test_in_order_lines_clear_with_intermittent_input(patterns: PatternsLib) -> None: +def test_in_order_lines_clear_with_intermittent_input( + patterns: PatternsLib, +) -> None: pattern = patterns.in_order pattern.in_order( """ @@ -173,7 +176,9 @@ def test_refused_lines_fail(patterns: PatternsLib) -> None: assert not audit.is_ok() -def test_continuous_lines_only_clear_if_not_interrupted(patterns: PatternsLib) -> None: +def test_continuous_lines_only_clear_if_not_interrupted( + patterns: PatternsLib, +) -> None: pattern = patterns.focus pattern.optional("asdf") pattern.continuous( @@ -240,7 +245,9 @@ def test_continuous_lines_only_clear_if_not_interrupted(patterns: PatternsLib) - assert not audit.is_ok() -def test_continuous_lines_fail_and_report_if_first_line_isnt_matching(patterns: PatternsLib) -> None: +def test_continuous_lines_fail_and_report_if_first_line_isnt_matching( + patterns: PatternsLib, +) -> None: pattern = patterns.focus pattern.continuous( """