From f74f14f038285b030ce34f3b0c8f9c59eea08e2d Mon Sep 17 00:00:00 2001 From: Segev Finer Date: Wed, 31 May 2017 23:55:30 +0300 Subject: [PATCH 1/2] Fix --help with required options This works by adding an argparse Action that will raise an exception in order to skip the rest of the argument parsing. This prevents argparse from quitting due to missing required arguments, similar to the way that the builtin argparse --help option is implemented by raising SystemExit. Fixes: #1999 --- AUTHORS | 1 + _pytest/config.py | 24 +++++++++++++++++------- _pytest/helpconfig.py | 27 ++++++++++++++++++++++++++- changelog/1999.bugfix | 2 ++ testing/test_conftest.py | 12 ++++++++++++ 5 files changed, 58 insertions(+), 8 deletions(-) create mode 100644 changelog/1999.bugfix diff --git a/AUTHORS b/AUTHORS index 25829a4644e..ca282870fb9 100644 --- a/AUTHORS +++ b/AUTHORS @@ -144,6 +144,7 @@ Ross Lawley Russel Winder Ryan Wooden Samuele Pedroni +Segev Finer Simon Gomizelj Skylar Downes Stefan Farmbauer diff --git a/_pytest/config.py b/_pytest/config.py index c687e3df99c..9d68deaa3b8 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -71,6 +71,12 @@ class UsageError(Exception): """ error in pytest usage or invocation""" +class PrintHelp(Exception): + """Raised when pytest should print it's help to skip the rest of the + argument parsing and validation.""" + pass + + def filename_arg(path, optname): """ Argparse type validator for filename arguments. @@ -1100,14 +1106,18 @@ def parse(self, args, addopts=True): self._preparse(args, addopts=addopts) # XXX deprecated hook: self.hook.pytest_cmdline_preparse(config=self, args=args) - args = self._parser.parse_setoption(args, self.option, namespace=self.option) - if not args: - cwd = os.getcwd() - if cwd == self.rootdir: - args = self.getini('testpaths') + self._parser.after_preparse = True + try: + args = self._parser.parse_setoption(args, self.option, namespace=self.option) if not args: - args = [cwd] - self.args = args + cwd = os.getcwd() + if cwd == self.rootdir: + args = self.getini('testpaths') + if not args: + args = [cwd] + self.args = args + except PrintHelp: + pass def addinivalue_line(self, name, line): """ add a line to an ini-file option. The option must have been diff --git a/_pytest/helpconfig.py b/_pytest/helpconfig.py index abc792f7ef8..dee092b49c1 100644 --- a/_pytest/helpconfig.py +++ b/_pytest/helpconfig.py @@ -3,13 +3,38 @@ import py import pytest +from _pytest.config import PrintHelp import os, sys +from argparse import Action + + +class HelpAction(Action): + def __init__(self, + option_strings, + dest=None, + default=False, + help=None): + super(HelpAction, self).__init__( + option_strings=option_strings, + dest=dest, + const=True, + default=default, + nargs=0, + help=help) + + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, self.const) + + # We should only skip the rest of the parsing after preparse is done + if getattr(parser._parser, 'after_preparse', False): + raise PrintHelp + def pytest_addoption(parser): group = parser.getgroup('debugconfig') group.addoption('--version', action="store_true", help="display pytest lib version and import information.") - group._addoption("-h", "--help", action="store_true", dest="help", + group._addoption("-h", "--help", action=HelpAction, dest="help", help="show help message and configuration info") group._addoption('-p', action="append", dest="plugins", default = [], metavar="name", diff --git a/changelog/1999.bugfix b/changelog/1999.bugfix new file mode 100644 index 00000000000..5321b6c2197 --- /dev/null +++ b/changelog/1999.bugfix @@ -0,0 +1,2 @@ +Required options added via ``pytest_addoption`` will no longer prevent +using --help without passing them. diff --git a/testing/test_conftest.py b/testing/test_conftest.py index db67a0cc8b4..b6fd7814cdc 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -449,3 +449,15 @@ def pytest_ignore_collect(path, config): '*test_foo4.py*', '*3 passed*', ]) + + +def test_required_option_help(testdir): + testdir.makeconftest("assert 0") + x = testdir.mkdir("x") + x.join("conftest.py").write(_pytest._code.Source(""" + def pytest_addoption(parser): + parser.addoption("--xyz", action="store_true", required=True) + """)) + result = testdir.runpytest("-h", x) + assert 'argument --xyz is required' not in result.stdout.str() + assert 'general:' in result.stdout.str() From 9abff7f72f37b15df74a883b8cff41e9c1db5989 Mon Sep 17 00:00:00 2001 From: Segev Finer Date: Thu, 1 Jun 2017 22:24:43 +0300 Subject: [PATCH 2/2] Add a docstring to HelpAction --- _pytest/helpconfig.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/_pytest/helpconfig.py b/_pytest/helpconfig.py index dee092b49c1..e3c6b6e9977 100644 --- a/_pytest/helpconfig.py +++ b/_pytest/helpconfig.py @@ -9,6 +9,14 @@ class HelpAction(Action): + """This is an argparse Action that will raise an exception in + order to skip the rest of the argument parsing when --help is passed. + This prevents argparse from quitting due to missing required arguments + when any are defined, for example by ``pytest_addoption``. + This is similar to the way that the builtin argparse --help option is + implemented by raising SystemExit. + """ + def __init__(self, option_strings, dest=None,