Add other property tests for the eager option scan step
Marco Ricci

Marco Ricci commited on 2026-08-19 21:32:12
Zeige 1 geänderte Dateien mit 52 Einfügungen und 0 Löschungen.


Add a test for non-applicability of the eager option scan step for
command-lines without eager options, and a test for idempotence of the
eager option scan step on every (normalized) command-line.  As with the
test for normalized command-lines, the test for non-applicability on
non-eager command-lines is a smoke test, combining the check for lack of
eager options and the check that the scan step also detects no eager
options into a single unit.
... ...
@@ -1735,6 +1735,41 @@ class Test010CLIMachinery:
1735 1735
         assert actual_result.state == data_tuple.expected_result.state
1736 1736
         assert actual_result == data_tuple.expected_result
1737 1737
 
1738
+    @hypothesis.given(
1739
+        starting_state=Strategies.parse_states(
1740
+            normalized=True, allow_eager=False
1741
+        )
1742
+    )
1743
+    def test_non_eager_starting_states_are_non_eager(
1744
+        self,
1745
+        starting_state: cli_machinery.ParseState,
1746
+    ) -> None:
1747
+        command_line = starting_state.command_line
1748
+        canon_map = starting_state.subcommand.canon_map
1749
+        for token in command_line:
1750
+            tokentype = cli_machinery.CommandLineTokenType.classify(token)
1751
+            assert (
1752
+                tokentype != cli_machinery.CommandLineTokenType.SHORT_OPTION
1753
+            ), f"Short option {token!r} not allowed in normalized command-line"
1754
+            assert (
1755
+                tokentype != cli_machinery.CommandLineTokenType.POSITIONAL
1756
+            ), (
1757
+                f"Positional argument {token!r} not allowed "
1758
+                "in normalized command-line without preceding "
1759
+                "end-of-options token"
1760
+            )
1761
+            if tokentype == cli_machinery.CommandLineTokenType.END_OF_OPTIONS:
1762
+                break
1763
+            assert tokentype == cli_machinery.CommandLineTokenType.LONG_OPTION
1764
+            opt_name = token.split("=")[0] if "=" in token else token
1765
+            opt = cast("cli_machinery.CLIOption", canon_map[opt_name])
1766
+            assert not opt.eager, (
1767
+                f"Eager option {token!r} not allowed in non-eager command-line"
1768
+            )
1769
+
1770
+        result = cli_machinery.scan_for_eager_options(starting_state)
1771
+        assert result == cli_machinery.ParseResult.unit(starting_state)
1772
+
1738 1773
     @hypothesis.given(
1739 1774
         data_tuple=Strategies.scan_for_eager_options_parser_states()
1740 1775
     )
... ...
@@ -1748,6 +1783,23 @@ class Test010CLIMachinery:
1748 1783
         assert actual_result.state == data_tuple.expected_result.state
1749 1784
         assert actual_result == data_tuple.expected_result
1750 1785
 
1786
+    @hypothesis.given(
1787
+        data_tuple=Strategies.scan_for_eager_options_parser_states()
1788
+    )
1789
+    def test_scan_for_eager_options_is_idempotent(
1790
+        self,
1791
+        data_tuple: ParseStateAndExpectedResult,
1792
+    ) -> None:
1793
+        first_result = cli_machinery.scan_for_eager_options(
1794
+            data_tuple.starting_state
1795
+        )
1796
+        assert first_result.state == data_tuple.expected_result.state
1797
+        assert first_result == data_tuple.expected_result
1798
+        new_start_state = data_tuple.expected_result.state
1799
+        second_result = cli_machinery.scan_for_eager_options(new_start_state)
1800
+        assert second_result.state == data_tuple.expected_result.state
1801
+        assert second_result == data_tuple.expected_result
1802
+
1751 1803
 
1752 1804
 class TestHelpOutput:
1753 1805
     """Tests for all command-line interfaces' `--help` output."""
1754 1806