diff options
-rw-r--r-- | coverage/env.py | 3 | ||||
-rw-r--r-- | coverage/parser.py | 21 | ||||
-rw-r--r-- | tests/test_arcs.py | 51 | ||||
-rw-r--r-- | tests/test_parser.py | 18 |
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.""" |