summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--coverage/env.py3
-rw-r--r--coverage/parser.py21
-rw-r--r--tests/test_arcs.py51
-rw-r--r--tests/test_parser.py18
4 files changed, 93 insertions, 0 deletions
diff --git a/coverage/env.py b/coverage/env.py
index 81f61794..89abbb2e 100644
--- a/coverage/env.py
+++ b/coverage/env.py
@@ -102,6 +102,9 @@ class PYBEHAVIOR:
# When leaving a with-block, do we visit the with-line again for the exit?
exit_through_with = (PYVERSION >= (3, 10, 0, 'beta'))
+ # Match-case construct.
+ match_case = (PYVERSION >= (3, 10))
+
# Coverage.py specifics.
# Are we using the C-implemented trace function?
diff --git a/coverage/parser.py b/coverage/parser.py
index ff395dad..abaa2e50 100644
--- a/coverage/parser.py
+++ b/coverage/parser.py
@@ -1018,6 +1018,27 @@ class AstArcAnalyzer:
return exits
@contract(returns='ArcStarts')
+ def _handle__Match(self, node):
+ start = self.line_for_node(node)
+ last_start = start
+ exits = set()
+ had_wildcard = False
+ for case in node.cases:
+ # The wildcard case doesn't execute the pattern.
+ case_start = self.line_for_node(case.pattern)
+ if isinstance(case.pattern, ast.MatchAs):
+ had_wildcard = True
+ if case.pattern.name is None:
+ case_start = self.line_for_node(case.body[0])
+ self.add_arc(last_start, case_start, "the pattern on line {lineno} always matched")
+ from_start = ArcStart(case_start, cause="the pattern on line {lineno} never matched")
+ exits |= self.add_body_arcs(case.body, from_start=from_start)
+ last_start = case_start
+ if not had_wildcard:
+ exits.add(from_start)
+ return exits
+
+ @contract(returns='ArcStarts')
def _handle__NodeList(self, node):
start = self.line_for_node(node)
exits = self.add_body_arcs(node.body, from_start=ArcStart(start))
diff --git a/tests/test_arcs.py b/tests/test_arcs.py
index 495a10f3..22446f6a 100644
--- a/tests/test_arcs.py
+++ b/tests/test_arcs.py
@@ -1198,6 +1198,57 @@ class YieldTest(CoverageTest):
)
+@pytest.mark.skipif(not env.PYBEHAVIOR.match_case, reason="Match-case is new in 3.10")
+class MatchCaseTest(CoverageTest):
+ """Tests of match-case."""
+ def test_match_case_with_default(self):
+ self.check_coverage("""\
+ for command in ["huh", "go home", "go n"]:
+ match command.split():
+ case ["go", direction] if direction in "nesw":
+ match = f"go: {direction}"
+ case ["go", _]:
+ match = "no go"
+ case _:
+ match = "default"
+ print(match)
+ """,
+ arcz=".1 12 23 34 49 35 56 69 58 89 91 1.",
+ )
+ assert self.stdout() == "default\nno go\ngo: n\n"
+
+ def test_match_case_with_wildcard(self):
+ self.check_coverage("""\
+ for command in ["huh", "go home", "go n"]:
+ match command.split():
+ case ["go", direction] if direction in "nesw":
+ match = f"go: {direction}"
+ case ["go", _]:
+ match = "no go"
+ case x:
+ match = f"default: {x}"
+ print(match)
+ """,
+ arcz=".1 12 23 34 49 35 56 69 57 78 89 91 1.",
+ )
+ assert self.stdout() == "default: ['huh']\nno go\ngo: n\n"
+
+ def test_match_case_without_wildcard(self):
+ self.check_coverage("""\
+ match = None
+ for command in ["huh", "go home", "go n"]:
+ match command.split():
+ case ["go", direction] if direction in "nesw":
+ match = f"go: {direction}"
+ case ["go", _]:
+ match = "no go"
+ print(match)
+ """,
+ arcz=".1 12 23 34 45 58 46 78 67 68 82 2.",
+ )
+ assert self.stdout() == "None\nno go\ngo: n\n"
+
+
class OptimizedIfTest(CoverageTest):
"""Tests of if statements being optimized away."""
diff --git a/tests/test_parser.py b/tests/test_parser.py
index 7fd87bba..1b4e8aca 100644
--- a/tests/test_parser.py
+++ b/tests/test_parser.py
@@ -394,6 +394,24 @@ class ParserMissingArcDescriptionTest(CoverageTest):
""")
assert parser.missing_arc_description(2, -3) == "line 3 didn't finish the lambda on line 3"
+ @pytest.mark.skipif(not env.PYBEHAVIOR.match_case, reason="Match-case is new in 3.10")
+ def test_match_case_with_default(self):
+ parser = self.parse_text("""\
+ for command in ["huh", "go home", "go n"]:
+ match command.split():
+ case ["go", direction] if direction in "nesw":
+ match = f"go: {direction}"
+ case ["go", _]:
+ match = "no go"
+ print(match)
+ """)
+ assert parser.missing_arc_description(3, 4) == (
+ "line 3 didn't jump to line 4, because the pattern on line 3 never matched"
+ )
+ assert parser.missing_arc_description(3, 5) == (
+ "line 3 didn't jump to line 5, because the pattern on line 3 always matched"
+ )
+
class ParserFileTest(CoverageTest):
"""Tests for coverage.py's code parsing from files."""