Skip to content

Commit 4d5780f

Browse files
authored
Merge pull request #5469 from Zac-HD/disallow-abbrev
Disallow abbreviated command-line options
2 parents 4f57d40 + d72fb73 commit 4d5780f

File tree

9 files changed

+55
-11
lines changed

9 files changed

+55
-11
lines changed

changelog/1149.removal.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Pytest no longer accepts prefixes of command-line arguments, for example
2+
typing ``pytest --doctest-mod`` inplace of ``--doctest-modules``.
3+
This was previously allowed where the ``ArgumentParser`` thought it was unambiguous,
4+
but this could be incorrect due to delayed parsing of options for plugins.
5+
See for example issues `#1149 <https://github.com/pytest-dev/pytest/issues/1149>`__,
6+
`#3413 <https://github.com/pytest-dev/pytest/issues/3413>`__, and
7+
`#4009 <https://github.com/pytest-dev/pytest/issues/4009>`__.

extra/get_issues.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def report(issues):
7474
if __name__ == "__main__":
7575
import argparse
7676

77-
parser = argparse.ArgumentParser("process bitbucket issues")
77+
parser = argparse.ArgumentParser("process bitbucket issues", allow_abbrev=False)
7878
parser.add_argument(
7979
"--refresh", action="store_true", help="invalidate cache, refresh issues"
8080
)

scripts/release.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ def changelog(version, write_out=False):
105105

106106
def main():
107107
init(autoreset=True)
108-
parser = argparse.ArgumentParser()
108+
parser = argparse.ArgumentParser(allow_abbrev=False)
109109
parser.add_argument("version", help="Release version")
110110
options = parser.parse_args()
111111
pre_release(options.version)

src/_pytest/config/argparsing.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import argparse
2+
import sys
23
import warnings
4+
from gettext import gettext
35

46
import py
57

@@ -328,6 +330,7 @@ def __init__(self, parser, extra_info=None, prog=None):
328330
usage=parser._usage,
329331
add_help=False,
330332
formatter_class=DropShorterLongHelpFormatter,
333+
allow_abbrev=False,
331334
)
332335
# extra_info is a dict of (param -> value) to display if there's
333336
# an usage error to provide more contextual information to the user
@@ -355,6 +358,42 @@ def parse_args(self, args=None, namespace=None):
355358
getattr(args, FILE_OR_DIR).extend(argv)
356359
return args
357360

361+
if sys.version_info[:2] < (3, 8): # pragma: no cover
362+
# Backport of https://github.com/python/cpython/pull/14316 so we can
363+
# disable long --argument abbreviations without breaking short flags.
364+
def _parse_optional(self, arg_string):
365+
if not arg_string:
366+
return None
367+
if not arg_string[0] in self.prefix_chars:
368+
return None
369+
if arg_string in self._option_string_actions:
370+
action = self._option_string_actions[arg_string]
371+
return action, arg_string, None
372+
if len(arg_string) == 1:
373+
return None
374+
if "=" in arg_string:
375+
option_string, explicit_arg = arg_string.split("=", 1)
376+
if option_string in self._option_string_actions:
377+
action = self._option_string_actions[option_string]
378+
return action, option_string, explicit_arg
379+
if self.allow_abbrev or not arg_string.startswith("--"):
380+
option_tuples = self._get_option_tuples(arg_string)
381+
if len(option_tuples) > 1:
382+
msg = gettext(
383+
"ambiguous option: %(option)s could match %(matches)s"
384+
)
385+
options = ", ".join(option for _, option, _ in option_tuples)
386+
self.error(msg % {"option": arg_string, "matches": options})
387+
elif len(option_tuples) == 1:
388+
option_tuple, = option_tuples
389+
return option_tuple
390+
if self._negative_number_matcher.match(arg_string):
391+
if not self._has_negative_number_optionals:
392+
return None
393+
if " " in arg_string:
394+
return None
395+
return None, arg_string, None
396+
358397

359398
class DropShorterLongHelpFormatter(argparse.HelpFormatter):
360399
"""shorten help for long options that differ only in extra hyphens

testing/acceptance_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -984,7 +984,7 @@ def test_zipimport_hook(testdir, tmpdir):
984984
"app/foo.py": """
985985
import pytest
986986
def main():
987-
pytest.main(['--pyarg', 'foo'])
987+
pytest.main(['--pyargs', 'foo'])
988988
"""
989989
}
990990
)

testing/example_scripts/perf_examples/collect_stats/generate_folders.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
HERE = pathlib.Path(__file__).parent
55
TEST_CONTENT = (HERE / "template_test.py").read_bytes()
66

7-
parser = argparse.ArgumentParser()
7+
parser = argparse.ArgumentParser(allow_abbrev=False)
88
parser.add_argument("numbers", nargs="*", type=int)
99

1010

testing/test_capture.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -735,7 +735,7 @@ def test_func():
735735
assert 0
736736
"""
737737
)
738-
result = testdir.runpytest("--cap=fd")
738+
result = testdir.runpytest("--capture=fd")
739739
result.stdout.fnmatch_lines(
740740
"""
741741
*def test_func*

testing/test_parseopt.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ def defaultget(option):
200200

201201
def test_drop_short_helper(self):
202202
parser = argparse.ArgumentParser(
203-
formatter_class=parseopt.DropShorterLongHelpFormatter
203+
formatter_class=parseopt.DropShorterLongHelpFormatter, allow_abbrev=False
204204
)
205205
parser.add_argument(
206206
"-t", "--twoword", "--duo", "--two-word", "--two", help="foo"
@@ -239,10 +239,8 @@ def test_drop_short_0(self, parser):
239239
parser.addoption("--funcarg", "--func-arg", action="store_true")
240240
parser.addoption("--abc-def", "--abc-def", action="store_true")
241241
parser.addoption("--klm-hij", action="store_true")
242-
args = parser.parse(["--funcarg", "--k"])
243-
assert args.funcarg is True
244-
assert args.abc_def is False
245-
assert args.klm_hij is True
242+
with pytest.raises(UsageError):
243+
parser.parse(["--funcarg", "--k"])
246244

247245
def test_drop_short_2(self, parser):
248246
parser.addoption("--func-arg", "--doit", action="store_true")

testing/test_pastebin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def test_skip():
2121
pytest.skip("")
2222
"""
2323
)
24-
reprec = testdir.inline_run(testpath, "--paste=failed")
24+
reprec = testdir.inline_run(testpath, "--pastebin=failed")
2525
assert len(pastebinlist) == 1
2626
s = pastebinlist[0]
2727
assert s.find("def test_fail") != -1

0 commit comments

Comments
 (0)