Merged in anthon_van_der_neut/pytest/argparse (pull request #46)
argparse / argcomplete
This commit is contained in:
commit
4b87810fc2
|
@ -6,7 +6,7 @@ import os
|
|||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("general")
|
||||
group._addoption('--capture', action="store", default=None,
|
||||
metavar="method", type="choice", choices=['fd', 'sys', 'no'],
|
||||
metavar="method", choices=['fd', 'sys', 'no'],
|
||||
help="per-test capturing method: one of fd (default)|sys|no.")
|
||||
group._addoption('-s', action="store_const", const="no", dest="capture",
|
||||
help="shortcut for --capture=no.")
|
||||
|
|
|
@ -5,6 +5,12 @@ import sys, os
|
|||
from _pytest.core import PluginManager
|
||||
import pytest
|
||||
|
||||
# enable after some grace period for plugin writers
|
||||
TYPE_WARN = False
|
||||
if TYPE_WARN:
|
||||
import warnings
|
||||
|
||||
|
||||
def pytest_cmdline_parse(pluginmanager, args):
|
||||
config = Config(pluginmanager)
|
||||
config.parse(args)
|
||||
|
@ -80,16 +86,20 @@ class Parser:
|
|||
for group in groups:
|
||||
if group.options:
|
||||
desc = group.description or group.name
|
||||
optgroup = py.std.optparse.OptionGroup(optparser, desc)
|
||||
optgroup.add_options(group.options)
|
||||
optparser.add_option_group(optgroup)
|
||||
arggroup = optparser.add_argument_group(desc)
|
||||
for option in group.options:
|
||||
n = option.names()
|
||||
a = option.attrs()
|
||||
arggroup.add_argument(*n, **a)
|
||||
optparser.add_argument(Config._file_or_dir, nargs='*')
|
||||
try_argcomplete(self.optparser)
|
||||
return self.optparser.parse_args([str(x) for x in args])
|
||||
|
||||
def parse_setoption(self, args, option):
|
||||
parsedoption, args = self.parse(args)
|
||||
parsedoption = self.parse(args)
|
||||
for name, value in parsedoption.__dict__.items():
|
||||
setattr(option, name, value)
|
||||
return args
|
||||
return getattr(parsedoption, Config._file_or_dir)
|
||||
|
||||
def addini(self, name, help, type=None, default=None):
|
||||
""" register an ini-file option.
|
||||
|
@ -105,6 +115,163 @@ class Parser:
|
|||
self._inidict[name] = (help, type, default)
|
||||
self._ininames.append(name)
|
||||
|
||||
def try_argcomplete(parser):
|
||||
try:
|
||||
import argcomplete
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
argcomplete.autocomplete(parser)
|
||||
|
||||
class ArgumentError(Exception):
|
||||
"""
|
||||
Raised if an Argument instance is created with invalid or
|
||||
inconsistent arguments.
|
||||
"""
|
||||
|
||||
def __init__(self, msg, option):
|
||||
self.msg = msg
|
||||
self.option_id = str(option)
|
||||
|
||||
def __str__(self):
|
||||
if self.option_id:
|
||||
return "option %s: %s" % (self.option_id, self.msg)
|
||||
else:
|
||||
return self.msg
|
||||
|
||||
|
||||
class Argument:
|
||||
"""class that mimics the necessary behaviour of py.std.optparse.Option """
|
||||
_typ_map = {
|
||||
'int': int,
|
||||
'string': str,
|
||||
}
|
||||
|
||||
def __init__(self, *names, **attrs):
|
||||
"""store parms in private vars for use in add_argument"""
|
||||
self._attrs = attrs
|
||||
self._short_opts = []
|
||||
self._long_opts = []
|
||||
self.dest = attrs.get('dest')
|
||||
if TYPE_WARN:
|
||||
try:
|
||||
help = attrs['help']
|
||||
if '%default' in help:
|
||||
warnings.warn(
|
||||
'py.test now uses argparse. "%default" should be'
|
||||
' changed to "%(default)s" ',
|
||||
FutureWarning,
|
||||
stacklevel=3)
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
typ = attrs['type']
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
# this might raise a keyerror as well, don't want to catch that
|
||||
if isinstance(typ, str):
|
||||
if typ == 'choice':
|
||||
if TYPE_WARN:
|
||||
warnings.warn(
|
||||
'type argument to addoption() is a string %r.'
|
||||
' For parsearg this is optional and when supplied '
|
||||
' should be a type.'
|
||||
' (options: %s)' % (typ, names),
|
||||
FutureWarning,
|
||||
stacklevel=3)
|
||||
# argparse expects a type here take it from
|
||||
# the type of the first element
|
||||
attrs['type'] = type(attrs['choices'][0])
|
||||
else:
|
||||
if TYPE_WARN:
|
||||
warnings.warn(
|
||||
'type argument to addoption() is a string %r.'
|
||||
' For parsearg this should be a type.'
|
||||
' (options: %s)' % (typ, names),
|
||||
FutureWarning,
|
||||
stacklevel=3)
|
||||
attrs['type'] = Argument._typ_map[typ]
|
||||
# used in test_parseopt -> test_parse_defaultgetter
|
||||
self.type = attrs['type']
|
||||
else:
|
||||
self.type = typ
|
||||
try:
|
||||
# attribute existence is tested in Config._processopt
|
||||
self.default = attrs['default']
|
||||
except KeyError:
|
||||
pass
|
||||
self._set_opt_strings(names)
|
||||
if not self.dest:
|
||||
if self._long_opts:
|
||||
self.dest = self._long_opts[0][2:].replace('-', '_')
|
||||
else:
|
||||
try:
|
||||
self.dest = self._short_opts[0][1:]
|
||||
except IndexError:
|
||||
raise ArgumentError(
|
||||
'need a long or short option', self)
|
||||
|
||||
def names(self):
|
||||
return self._short_opts + self._long_opts
|
||||
|
||||
def attrs(self):
|
||||
# update any attributes set by processopt
|
||||
attrs = 'default dest help'.split()
|
||||
if self.dest:
|
||||
attrs.append(self.dest)
|
||||
for attr in attrs:
|
||||
try:
|
||||
self._attrs[attr] = getattr(self, attr)
|
||||
except AttributeError:
|
||||
pass
|
||||
if self._attrs.get('help'):
|
||||
a = self._attrs['help']
|
||||
a = a.replace('%default', '%(default)s')
|
||||
#a = a.replace('%prog', '%(prog)s')
|
||||
self._attrs['help'] = a
|
||||
return self._attrs
|
||||
|
||||
def _set_opt_strings(self, opts):
|
||||
"""directly from optparse
|
||||
|
||||
might not be necessary as this is passed to argparse later on"""
|
||||
for opt in opts:
|
||||
if len(opt) < 2:
|
||||
raise ArgumentError(
|
||||
"invalid option string %r: "
|
||||
"must be at least two characters long" % opt, self)
|
||||
elif len(opt) == 2:
|
||||
if not (opt[0] == "-" and opt[1] != "-"):
|
||||
raise ArgumentError(
|
||||
"invalid short option string %r: "
|
||||
"must be of the form -x, (x any non-dash char)" % opt,
|
||||
self)
|
||||
self._short_opts.append(opt)
|
||||
else:
|
||||
if not (opt[0:2] == "--" and opt[2] != "-"):
|
||||
raise ArgumentError(
|
||||
"invalid long option string %r: "
|
||||
"must start with --, followed by non-dash" % opt,
|
||||
self)
|
||||
self._long_opts.append(opt)
|
||||
|
||||
def __repr__(self):
|
||||
retval = 'Argument('
|
||||
if self._short_opts:
|
||||
retval += '_short_opts: ' + repr(self._short_opts) + ', '
|
||||
if self._long_opts:
|
||||
retval += '_long_opts: ' + repr(self._long_opts) + ', '
|
||||
retval += 'dest: ' + repr(self.dest) + ', '
|
||||
if hasattr(self, 'type'):
|
||||
retval += 'type: ' + repr(self.type) + ', '
|
||||
if hasattr(self, 'default'):
|
||||
retval += 'default: ' + repr(self.default) + ', '
|
||||
if retval[-2:] == ', ': # always long enough to test ("Argument(" )
|
||||
retval = retval[:-2]
|
||||
retval += ')'
|
||||
return retval
|
||||
|
||||
|
||||
class OptionGroup:
|
||||
def __init__(self, name, description="", parser=None):
|
||||
|
@ -115,11 +282,11 @@ class OptionGroup:
|
|||
|
||||
def addoption(self, *optnames, **attrs):
|
||||
""" add an option to this group. """
|
||||
option = py.std.optparse.Option(*optnames, **attrs)
|
||||
option = Argument(*optnames, **attrs)
|
||||
self._addoption_instance(option, shortupper=False)
|
||||
|
||||
def _addoption(self, *optnames, **attrs):
|
||||
option = py.std.optparse.Option(*optnames, **attrs)
|
||||
option = Argument(*optnames, **attrs)
|
||||
self._addoption_instance(option, shortupper=True)
|
||||
|
||||
def _addoption_instance(self, option, shortupper=False):
|
||||
|
@ -132,11 +299,11 @@ class OptionGroup:
|
|||
self.options.append(option)
|
||||
|
||||
|
||||
class MyOptionParser(py.std.optparse.OptionParser):
|
||||
class MyOptionParser(py.std.argparse.ArgumentParser):
|
||||
def __init__(self, parser):
|
||||
self._parser = parser
|
||||
py.std.optparse.OptionParser.__init__(self, usage=parser._usage,
|
||||
add_help_option=False)
|
||||
py.std.argparse.ArgumentParser.__init__(self, usage=parser._usage,
|
||||
add_help=False)
|
||||
def format_epilog(self, formatter):
|
||||
hints = self._parser.hints
|
||||
if hints:
|
||||
|
@ -263,12 +430,15 @@ class CmdOptions(object):
|
|||
|
||||
class Config(object):
|
||||
""" access to configuration values, pluginmanager and plugin hooks. """
|
||||
_file_or_dir = 'file_or_dir'
|
||||
|
||||
def __init__(self, pluginmanager=None):
|
||||
#: access to command line option as attributes.
|
||||
#: (deprecated), use :py:func:`getoption() <_pytest.config.Config.getoption>` instead
|
||||
self.option = CmdOptions()
|
||||
_a = self._file_or_dir
|
||||
self._parser = Parser(
|
||||
usage="usage: %prog [options] [file_or_dir] [file_or_dir] [...]",
|
||||
usage="%%(prog)s [options] [%s] [%s] [...]" % (_a, _a),
|
||||
processopt=self._processopt,
|
||||
)
|
||||
#: a pluginmanager instance
|
||||
|
|
|
@ -62,6 +62,7 @@ def pytest_cmdline_main(config):
|
|||
def showhelp(config):
|
||||
tw = py.io.TerminalWriter()
|
||||
tw.write(config._parser.optparser.format_help())
|
||||
tw.write(config._parser.optparser.format_epilog(None))
|
||||
tw.line()
|
||||
tw.line()
|
||||
#tw.sep( "=", "config file settings")
|
||||
|
|
|
@ -23,7 +23,7 @@ def pytest_cmdline_preparse(config, args):
|
|||
"""modify command line arguments before option parsing. """
|
||||
|
||||
def pytest_addoption(parser):
|
||||
"""register optparse-style options and ini-style config values.
|
||||
"""register argparse-style options and ini-style config values.
|
||||
|
||||
This function must be implemented in a :ref:`plugin <pluginorder>` and is
|
||||
called once at the beginning of a test run.
|
||||
|
|
|
@ -35,7 +35,7 @@ def pytest_addoption(parser):
|
|||
dest="exitfirst",
|
||||
help="exit instantly on first error or failed test."),
|
||||
group._addoption('--maxfail', metavar="num",
|
||||
action="store", type="int", dest="maxfail", default=0,
|
||||
action="store", type=int, dest="maxfail", default=0,
|
||||
help="exit after first num failures or errors.")
|
||||
group._addoption('--strict', action="store_true",
|
||||
help="run pytest in strict mode, warnings become errors.")
|
||||
|
|
|
@ -10,7 +10,7 @@ def pytest_addoption(parser):
|
|||
group = parser.getgroup("terminal reporting")
|
||||
group._addoption('--pastebin', metavar="mode",
|
||||
action='store', dest="pastebin", default=None,
|
||||
type="choice", choices=['failed', 'all'],
|
||||
choices=['failed', 'all'],
|
||||
help="send failed|all info to bpaste.net pastebin service.")
|
||||
|
||||
def pytest_configure(__multicall__, config):
|
||||
|
|
|
@ -18,7 +18,7 @@ def pytest_namespace():
|
|||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("terminal reporting", "reporting", after="general")
|
||||
group.addoption('--durations',
|
||||
action="store", type="int", default=None, metavar="N",
|
||||
action="store", type=int, default=None, metavar="N",
|
||||
help="show N slowest setup/test durations (N=0 for all)."),
|
||||
|
||||
def pytest_terminal_summary(terminalreporter):
|
||||
|
|
|
@ -25,7 +25,7 @@ def pytest_addoption(parser):
|
|||
help="(deprecated, use -r)")
|
||||
group._addoption('--tb', metavar="style",
|
||||
action="store", dest="tbstyle", default='long',
|
||||
type="choice", choices=['long', 'short', 'no', 'line', 'native'],
|
||||
choices=['long', 'short', 'no', 'line', 'native'],
|
||||
help="traceback print mode (long/short/line/native/no).")
|
||||
group._addoption('--fulltrace',
|
||||
action="store_true", dest="fulltrace", default=False,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# PYTHON_ARGCOMPLETE_OK
|
||||
"""
|
||||
pytest: unit and functional testing with Python.
|
||||
"""
|
||||
|
|
|
@ -451,13 +451,23 @@ class TestKeywordSelection:
|
|||
assert 0
|
||||
test_one.mykeyword = True
|
||||
""")
|
||||
reprec = testdir.inline_run("-k", "-mykeyword", p)
|
||||
passed, skipped, failed = reprec.countoutcomes()
|
||||
assert passed + skipped + failed == 0
|
||||
reprec = testdir.inline_run("-k", "mykeyword", p)
|
||||
passed, skipped, failed = reprec.countoutcomes()
|
||||
assert failed == 1
|
||||
|
||||
@pytest.mark.xfail
|
||||
def test_keyword_extra_dash(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
def test_one():
|
||||
assert 0
|
||||
test_one.mykeyword = True
|
||||
""")
|
||||
# with argparse the argument to an option cannot
|
||||
# start with '-'
|
||||
reprec = testdir.inline_run("-k", "-mykeyword", p)
|
||||
passed, skipped, failed = reprec.countoutcomes()
|
||||
assert passed + skipped + failed == 0
|
||||
|
||||
def test_no_magic_values(self, testdir):
|
||||
"""Make sure the tests do not match on magic values,
|
||||
no double underscored values, like '__dict__',
|
||||
|
|
|
@ -7,7 +7,43 @@ class TestParser:
|
|||
parser = parseopt.Parser(usage="xyz")
|
||||
pytest.raises(SystemExit, 'parser.parse(["-h"])')
|
||||
out, err = capsys.readouterr()
|
||||
assert err.find("no such option") != -1
|
||||
assert err.find("error: unrecognized arguments") != -1
|
||||
|
||||
def test_argument(self):
|
||||
with pytest.raises(parseopt.ArgumentError):
|
||||
# need a short or long option
|
||||
argument = parseopt.Argument()
|
||||
argument = parseopt.Argument('-t')
|
||||
assert argument._short_opts == ['-t']
|
||||
assert argument._long_opts == []
|
||||
assert argument.dest == 't'
|
||||
argument = parseopt.Argument('-t', '--test')
|
||||
assert argument._short_opts == ['-t']
|
||||
assert argument._long_opts == ['--test']
|
||||
assert argument.dest == 'test'
|
||||
argument = parseopt.Argument('-t', '--test', dest='abc')
|
||||
assert argument.dest == 'abc'
|
||||
|
||||
def test_argument_type(self):
|
||||
argument = parseopt.Argument('-t', dest='abc', type='int')
|
||||
assert argument.type is int
|
||||
argument = parseopt.Argument('-t', dest='abc', type='string')
|
||||
assert argument.type is str
|
||||
argument = parseopt.Argument('-t', dest='abc', type=float)
|
||||
assert argument.type is float
|
||||
with pytest.raises(KeyError):
|
||||
argument = parseopt.Argument('-t', dest='abc', type='choice')
|
||||
argument = parseopt.Argument('-t', dest='abc', type='choice',
|
||||
choices=['red', 'blue'])
|
||||
assert argument.type is str
|
||||
|
||||
def test_argument_processopt(self):
|
||||
argument = parseopt.Argument('-t', type=int)
|
||||
argument.default = 42
|
||||
argument.dest = 'abc'
|
||||
res = argument.attrs()
|
||||
assert res['default'] == 42
|
||||
assert res['dest'] == 'abc'
|
||||
|
||||
def test_group_add_and_get(self):
|
||||
parser = parseopt.Parser()
|
||||
|
@ -36,7 +72,7 @@ class TestParser:
|
|||
group = parseopt.OptionGroup("hello")
|
||||
group.addoption("--option1", action="store_true")
|
||||
assert len(group.options) == 1
|
||||
assert isinstance(group.options[0], py.std.optparse.Option)
|
||||
assert isinstance(group.options[0], parseopt.Argument)
|
||||
|
||||
def test_group_shortopt_lowercase(self):
|
||||
parser = parseopt.Parser()
|
||||
|
@ -58,19 +94,19 @@ class TestParser:
|
|||
def test_parse(self):
|
||||
parser = parseopt.Parser()
|
||||
parser.addoption("--hello", dest="hello", action="store")
|
||||
option, args = parser.parse(['--hello', 'world'])
|
||||
assert option.hello == "world"
|
||||
assert not args
|
||||
args = parser.parse(['--hello', 'world'])
|
||||
assert args.hello == "world"
|
||||
assert not getattr(args, parseopt.Config._file_or_dir)
|
||||
|
||||
def test_parse(self):
|
||||
def test_parse2(self):
|
||||
parser = parseopt.Parser()
|
||||
option, args = parser.parse([py.path.local()])
|
||||
assert args[0] == py.path.local()
|
||||
args = parser.parse([py.path.local()])
|
||||
assert getattr(args, parseopt.Config._file_or_dir)[0] == py.path.local()
|
||||
|
||||
def test_parse_will_set_default(self):
|
||||
parser = parseopt.Parser()
|
||||
parser.addoption("--hello", dest="hello", default="x", action="store")
|
||||
option, args = parser.parse([])
|
||||
option = parser.parse([])
|
||||
assert option.hello == "x"
|
||||
del option.hello
|
||||
args = parser.parse_setoption([], option)
|
||||
|
@ -87,28 +123,37 @@ class TestParser:
|
|||
assert option.world == 42
|
||||
assert not args
|
||||
|
||||
def test_parse_special_destination(self):
|
||||
parser = parseopt.Parser()
|
||||
x = parser.addoption("--ultimate-answer", type=int)
|
||||
args = parser.parse(['--ultimate-answer', '42'])
|
||||
assert args.ultimate_answer == 42
|
||||
|
||||
def test_parse_defaultgetter(self):
|
||||
def defaultget(option):
|
||||
if option.type == "int":
|
||||
if not hasattr(option, 'type'):
|
||||
return
|
||||
if option.type is int:
|
||||
option.default = 42
|
||||
elif option.type == "string":
|
||||
elif option.type is str:
|
||||
option.default = "world"
|
||||
parser = parseopt.Parser(processopt=defaultget)
|
||||
parser.addoption("--this", dest="this", type="int", action="store")
|
||||
parser.addoption("--hello", dest="hello", type="string", action="store")
|
||||
parser.addoption("--no", dest="no", action="store_true")
|
||||
option, args = parser.parse([])
|
||||
option = parser.parse([])
|
||||
assert option.hello == "world"
|
||||
assert option.this == 42
|
||||
|
||||
assert option.no is False
|
||||
|
||||
@pytest.mark.skipif("sys.version_info < (2,5)")
|
||||
def test_addoption_parser_epilog(testdir):
|
||||
testdir.makeconftest("""
|
||||
def pytest_addoption(parser):
|
||||
parser.hints.append("hello world")
|
||||
parser.hints.append("from me too")
|
||||
""")
|
||||
result = testdir.runpytest('--help')
|
||||
#assert result.ret != 0
|
||||
result.stdout.fnmatch_lines(["*hint: hello world*"])
|
||||
result.stdout.fnmatch_lines(["hint: hello world", "hint: from me too"])
|
||||
|
||||
|
|
Loading…
Reference in New Issue