Merge pull request #5469 from Zac-HD/disallow-abbrev
Disallow abbreviated command-line options
This commit is contained in:
		
						commit
						4d5780facf
					
				|  | @ -0,0 +1,7 @@ | ||||||
|  | Pytest no longer accepts prefixes of command-line arguments, for example | ||||||
|  | typing ``pytest --doctest-mod`` inplace of ``--doctest-modules``. | ||||||
|  | This was previously allowed where the ``ArgumentParser`` thought it was unambiguous, | ||||||
|  | but this could be incorrect due to delayed parsing of options for plugins. | ||||||
|  | See for example issues `#1149 <https://github.com/pytest-dev/pytest/issues/1149>`__, | ||||||
|  | `#3413 <https://github.com/pytest-dev/pytest/issues/3413>`__, and | ||||||
|  | `#4009 <https://github.com/pytest-dev/pytest/issues/4009>`__. | ||||||
|  | @ -74,7 +74,7 @@ def report(issues): | ||||||
| if __name__ == "__main__": | if __name__ == "__main__": | ||||||
|     import argparse |     import argparse | ||||||
| 
 | 
 | ||||||
|     parser = argparse.ArgumentParser("process bitbucket issues") |     parser = argparse.ArgumentParser("process bitbucket issues", allow_abbrev=False) | ||||||
|     parser.add_argument( |     parser.add_argument( | ||||||
|         "--refresh", action="store_true", help="invalidate cache, refresh issues" |         "--refresh", action="store_true", help="invalidate cache, refresh issues" | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  | @ -105,7 +105,7 @@ def changelog(version, write_out=False): | ||||||
| 
 | 
 | ||||||
| def main(): | def main(): | ||||||
|     init(autoreset=True) |     init(autoreset=True) | ||||||
|     parser = argparse.ArgumentParser() |     parser = argparse.ArgumentParser(allow_abbrev=False) | ||||||
|     parser.add_argument("version", help="Release version") |     parser.add_argument("version", help="Release version") | ||||||
|     options = parser.parse_args() |     options = parser.parse_args() | ||||||
|     pre_release(options.version) |     pre_release(options.version) | ||||||
|  |  | ||||||
|  | @ -1,5 +1,7 @@ | ||||||
| import argparse | import argparse | ||||||
|  | import sys | ||||||
| import warnings | import warnings | ||||||
|  | from gettext import gettext | ||||||
| 
 | 
 | ||||||
| import py | import py | ||||||
| 
 | 
 | ||||||
|  | @ -328,6 +330,7 @@ class MyOptionParser(argparse.ArgumentParser): | ||||||
|             usage=parser._usage, |             usage=parser._usage, | ||||||
|             add_help=False, |             add_help=False, | ||||||
|             formatter_class=DropShorterLongHelpFormatter, |             formatter_class=DropShorterLongHelpFormatter, | ||||||
|  |             allow_abbrev=False, | ||||||
|         ) |         ) | ||||||
|         # extra_info is a dict of (param -> value) to display if there's |         # extra_info is a dict of (param -> value) to display if there's | ||||||
|         # an usage error to provide more contextual information to the user |         # an usage error to provide more contextual information to the user | ||||||
|  | @ -355,6 +358,42 @@ class MyOptionParser(argparse.ArgumentParser): | ||||||
|             getattr(args, FILE_OR_DIR).extend(argv) |             getattr(args, FILE_OR_DIR).extend(argv) | ||||||
|         return args |         return args | ||||||
| 
 | 
 | ||||||
|  |     if sys.version_info[:2] < (3, 8):  # pragma: no cover | ||||||
|  |         # Backport of https://github.com/python/cpython/pull/14316 so we can | ||||||
|  |         # disable long --argument abbreviations without breaking short flags. | ||||||
|  |         def _parse_optional(self, arg_string): | ||||||
|  |             if not arg_string: | ||||||
|  |                 return None | ||||||
|  |             if not arg_string[0] in self.prefix_chars: | ||||||
|  |                 return None | ||||||
|  |             if arg_string in self._option_string_actions: | ||||||
|  |                 action = self._option_string_actions[arg_string] | ||||||
|  |                 return action, arg_string, None | ||||||
|  |             if len(arg_string) == 1: | ||||||
|  |                 return None | ||||||
|  |             if "=" in arg_string: | ||||||
|  |                 option_string, explicit_arg = arg_string.split("=", 1) | ||||||
|  |                 if option_string in self._option_string_actions: | ||||||
|  |                     action = self._option_string_actions[option_string] | ||||||
|  |                     return action, option_string, explicit_arg | ||||||
|  |             if self.allow_abbrev or not arg_string.startswith("--"): | ||||||
|  |                 option_tuples = self._get_option_tuples(arg_string) | ||||||
|  |                 if len(option_tuples) > 1: | ||||||
|  |                     msg = gettext( | ||||||
|  |                         "ambiguous option: %(option)s could match %(matches)s" | ||||||
|  |                     ) | ||||||
|  |                     options = ", ".join(option for _, option, _ in option_tuples) | ||||||
|  |                     self.error(msg % {"option": arg_string, "matches": options}) | ||||||
|  |                 elif len(option_tuples) == 1: | ||||||
|  |                     option_tuple, = option_tuples | ||||||
|  |                     return option_tuple | ||||||
|  |             if self._negative_number_matcher.match(arg_string): | ||||||
|  |                 if not self._has_negative_number_optionals: | ||||||
|  |                     return None | ||||||
|  |             if " " in arg_string: | ||||||
|  |                 return None | ||||||
|  |             return None, arg_string, None | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| class DropShorterLongHelpFormatter(argparse.HelpFormatter): | class DropShorterLongHelpFormatter(argparse.HelpFormatter): | ||||||
|     """shorten help for long options that differ only in extra hyphens |     """shorten help for long options that differ only in extra hyphens | ||||||
|  |  | ||||||
|  | @ -984,7 +984,7 @@ def test_zipimport_hook(testdir, tmpdir): | ||||||
|             "app/foo.py": """ |             "app/foo.py": """ | ||||||
|             import pytest |             import pytest | ||||||
|             def main(): |             def main(): | ||||||
|                 pytest.main(['--pyarg', 'foo']) |                 pytest.main(['--pyargs', 'foo']) | ||||||
|         """ |         """ | ||||||
|         } |         } | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ import pathlib | ||||||
| HERE = pathlib.Path(__file__).parent | HERE = pathlib.Path(__file__).parent | ||||||
| TEST_CONTENT = (HERE / "template_test.py").read_bytes() | TEST_CONTENT = (HERE / "template_test.py").read_bytes() | ||||||
| 
 | 
 | ||||||
| parser = argparse.ArgumentParser() | parser = argparse.ArgumentParser(allow_abbrev=False) | ||||||
| parser.add_argument("numbers", nargs="*", type=int) | parser.add_argument("numbers", nargs="*", type=int) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -735,7 +735,7 @@ def test_capture_badoutput_issue412(testdir): | ||||||
|             assert 0 |             assert 0 | ||||||
|         """ |         """ | ||||||
|     ) |     ) | ||||||
|     result = testdir.runpytest("--cap=fd") |     result = testdir.runpytest("--capture=fd") | ||||||
|     result.stdout.fnmatch_lines( |     result.stdout.fnmatch_lines( | ||||||
|         """ |         """ | ||||||
|         *def test_func* |         *def test_func* | ||||||
|  |  | ||||||
|  | @ -200,7 +200,7 @@ class TestParser: | ||||||
| 
 | 
 | ||||||
|     def test_drop_short_helper(self): |     def test_drop_short_helper(self): | ||||||
|         parser = argparse.ArgumentParser( |         parser = argparse.ArgumentParser( | ||||||
|             formatter_class=parseopt.DropShorterLongHelpFormatter |             formatter_class=parseopt.DropShorterLongHelpFormatter, allow_abbrev=False | ||||||
|         ) |         ) | ||||||
|         parser.add_argument( |         parser.add_argument( | ||||||
|             "-t", "--twoword", "--duo", "--two-word", "--two", help="foo" |             "-t", "--twoword", "--duo", "--two-word", "--two", help="foo" | ||||||
|  | @ -239,10 +239,8 @@ class TestParser: | ||||||
|         parser.addoption("--funcarg", "--func-arg", action="store_true") |         parser.addoption("--funcarg", "--func-arg", action="store_true") | ||||||
|         parser.addoption("--abc-def", "--abc-def", action="store_true") |         parser.addoption("--abc-def", "--abc-def", action="store_true") | ||||||
|         parser.addoption("--klm-hij", action="store_true") |         parser.addoption("--klm-hij", action="store_true") | ||||||
|         args = parser.parse(["--funcarg", "--k"]) |         with pytest.raises(UsageError): | ||||||
|         assert args.funcarg is True |             parser.parse(["--funcarg", "--k"]) | ||||||
|         assert args.abc_def is False |  | ||||||
|         assert args.klm_hij is True |  | ||||||
| 
 | 
 | ||||||
|     def test_drop_short_2(self, parser): |     def test_drop_short_2(self, parser): | ||||||
|         parser.addoption("--func-arg", "--doit", action="store_true") |         parser.addoption("--func-arg", "--doit", action="store_true") | ||||||
|  |  | ||||||
|  | @ -21,7 +21,7 @@ class TestPasteCapture: | ||||||
|                 pytest.skip("") |                 pytest.skip("") | ||||||
|         """ |         """ | ||||||
|         ) |         ) | ||||||
|         reprec = testdir.inline_run(testpath, "--paste=failed") |         reprec = testdir.inline_run(testpath, "--pastebin=failed") | ||||||
|         assert len(pastebinlist) == 1 |         assert len(pastebinlist) == 1 | ||||||
|         s = pastebinlist[0] |         s = pastebinlist[0] | ||||||
|         assert s.find("def test_fail") != -1 |         assert s.find("def test_fail") != -1 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue