summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--changelog/1149.removal.rst7
-rw-r--r--extra/get_issues.py2
-rw-r--r--scripts/release.py2
-rw-r--r--src/_pytest/config/argparsing.py39
-rw-r--r--testing/acceptance_test.py2
-rw-r--r--testing/example_scripts/perf_examples/collect_stats/generate_folders.py2
-rw-r--r--testing/test_capture.py2
-rw-r--r--testing/test_parseopt.py8
-rw-r--r--testing/test_pastebin.py2
9 files changed, 55 insertions, 11 deletions
diff --git a/changelog/1149.removal.rst b/changelog/1149.removal.rst
new file mode 100644
index 000000000..f507014d9
--- /dev/null
+++ b/changelog/1149.removal.rst
@@ -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>`__.
diff --git a/extra/get_issues.py b/extra/get_issues.py
index 9407aeded..ae99c9aa6 100644
--- a/extra/get_issues.py
+++ b/extra/get_issues.py
@@ -74,7 +74,7 @@ def report(issues):
if __name__ == "__main__":
import argparse
- parser = argparse.ArgumentParser("process bitbucket issues")
+ parser = argparse.ArgumentParser("process bitbucket issues", allow_abbrev=False)
parser.add_argument(
"--refresh", action="store_true", help="invalidate cache, refresh issues"
)
diff --git a/scripts/release.py b/scripts/release.py
index 5009df359..d2a51e25a 100644
--- a/scripts/release.py
+++ b/scripts/release.py
@@ -105,7 +105,7 @@ def changelog(version, write_out=False):
def main():
init(autoreset=True)
- parser = argparse.ArgumentParser()
+ parser = argparse.ArgumentParser(allow_abbrev=False)
parser.add_argument("version", help="Release version")
options = parser.parse_args()
pre_release(options.version)
diff --git a/src/_pytest/config/argparsing.py b/src/_pytest/config/argparsing.py
index fb36c7985..d62ed0d03 100644
--- a/src/_pytest/config/argparsing.py
+++ b/src/_pytest/config/argparsing.py
@@ -1,5 +1,7 @@
import argparse
+import sys
import warnings
+from gettext import gettext
import py
@@ -328,6 +330,7 @@ class MyOptionParser(argparse.ArgumentParser):
usage=parser._usage,
add_help=False,
formatter_class=DropShorterLongHelpFormatter,
+ allow_abbrev=False,
)
# extra_info is a dict of (param -> value) to display if there's
# 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)
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):
"""shorten help for long options that differ only in extra hyphens
diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py
index 9d903f802..bac7d4f3a 100644
--- a/testing/acceptance_test.py
+++ b/testing/acceptance_test.py
@@ -984,7 +984,7 @@ def test_zipimport_hook(testdir, tmpdir):
"app/foo.py": """
import pytest
def main():
- pytest.main(['--pyarg', 'foo'])
+ pytest.main(['--pyargs', 'foo'])
"""
}
)
diff --git a/testing/example_scripts/perf_examples/collect_stats/generate_folders.py b/testing/example_scripts/perf_examples/collect_stats/generate_folders.py
index ff1eaf7d6..d2c1a30b2 100644
--- a/testing/example_scripts/perf_examples/collect_stats/generate_folders.py
+++ b/testing/example_scripts/perf_examples/collect_stats/generate_folders.py
@@ -4,7 +4,7 @@ import pathlib
HERE = pathlib.Path(__file__).parent
TEST_CONTENT = (HERE / "template_test.py").read_bytes()
-parser = argparse.ArgumentParser()
+parser = argparse.ArgumentParser(allow_abbrev=False)
parser.add_argument("numbers", nargs="*", type=int)
diff --git a/testing/test_capture.py b/testing/test_capture.py
index 0825745ad..3f75089f4 100644
--- a/testing/test_capture.py
+++ b/testing/test_capture.py
@@ -735,7 +735,7 @@ def test_capture_badoutput_issue412(testdir):
assert 0
"""
)
- result = testdir.runpytest("--cap=fd")
+ result = testdir.runpytest("--capture=fd")
result.stdout.fnmatch_lines(
"""
*def test_func*
diff --git a/testing/test_parseopt.py b/testing/test_parseopt.py
index 7c581cce1..dd7bc8753 100644
--- a/testing/test_parseopt.py
+++ b/testing/test_parseopt.py
@@ -200,7 +200,7 @@ class TestParser:
def test_drop_short_helper(self):
parser = argparse.ArgumentParser(
- formatter_class=parseopt.DropShorterLongHelpFormatter
+ formatter_class=parseopt.DropShorterLongHelpFormatter, allow_abbrev=False
)
parser.add_argument(
"-t", "--twoword", "--duo", "--two-word", "--two", help="foo"
@@ -239,10 +239,8 @@ class TestParser:
parser.addoption("--funcarg", "--func-arg", action="store_true")
parser.addoption("--abc-def", "--abc-def", action="store_true")
parser.addoption("--klm-hij", action="store_true")
- args = parser.parse(["--funcarg", "--k"])
- assert args.funcarg is True
- assert args.abc_def is False
- assert args.klm_hij is True
+ with pytest.raises(UsageError):
+ parser.parse(["--funcarg", "--k"])
def test_drop_short_2(self, parser):
parser.addoption("--func-arg", "--doit", action="store_true")
diff --git a/testing/test_pastebin.py b/testing/test_pastebin.py
index 48dea14bd..fd443ed40 100644
--- a/testing/test_pastebin.py
+++ b/testing/test_pastebin.py
@@ -21,7 +21,7 @@ class TestPasteCapture:
pytest.skip("")
"""
)
- reprec = testdir.inline_run(testpath, "--paste=failed")
+ reprec = testdir.inline_run(testpath, "--pastebin=failed")
assert len(pastebinlist) == 1
s = pastebinlist[0]
assert s.find("def test_fail") != -1