3 new commits in pytest:
https://bitbucket.org/hpk42/pytest/commits/a3f34a534d44/
Changeset: a3f34a534d44
Branch: argparse
User: Anthon van der Neut
Date: 2013-07-25 15:33:43
Summary: moving from optparse to argparse. Major difficulty is
that argparse does not have Option objects -> added class Argument
Needed explicit call of MyOptionParser.format_epilog as argparse
does not have that. The parse_arg epilog argument wraps the text,
which is not the same (could be handled with a special formatter).
- parser.parse() now returns single argument (with positional args in
.file_or_dir)
- "file_or_dir" made a class variable Config._file_or_dir and used in help and
tests
- added code for argcomplete (because of which this all started!)
addoption:
- if option type is a string ('int' or 'string', this converted to
int resp. str
- if option type is 'count' this is changed to the type of choices[0]
testing:
- added tests for Argument
- test_mark.test_keyword_extra split as ['-k', '-mykeyword'] generates argparse
error test split in two and one marked as fail
- testing hints, multiline and more strickt (for if someone moves format_epilog
to epilog argument of parse_args without Formatter)
- test for destination derived from long option with internal dash
- renamed second test_parseopt.test_parse() to test_parse2 as it was
not tested at all (the first was tested.)
Affected #: 11 files
diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r
a3f34a534d44feee1c77f5122402c761259a11d9 _pytest/capture.py
--- a/_pytest/capture.py
+++ b/_pytest/capture.py
@@ -6,7 +6,7 @@
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.")
diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r
a3f34a534d44feee1c77f5122402c761259a11d9 _pytest/config.py
--- a/_pytest/config.py
+++ b/_pytest/config.py
@@ -80,16 +80,20 @@
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,7 +109,133 @@
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')
+ 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':
+ # argparse expects a type here take it from
+ # the type of the first element
+ attrs['type'] = type(attrs['choices'][0])
+ else:
+ 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'.split()
+ if self.dest:
+ attrs.append(self.dest)
+ for attr in attrs:
+ try:
+ self._attrs[attr] = getattr(self, attr)
+ except AttributeError:
+ pass
+ 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):
self.name = name
@@ -115,11 +245,11 @@
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 +262,11 @@
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 +393,15 @@
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
diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r
a3f34a534d44feee1c77f5122402c761259a11d9 _pytest/helpconfig.py
--- a/_pytest/helpconfig.py
+++ b/_pytest/helpconfig.py
@@ -62,6 +62,7 @@
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")
diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r
a3f34a534d44feee1c77f5122402c761259a11d9 _pytest/hookspec.py
--- a/_pytest/hookspec.py
+++ b/_pytest/hookspec.py
@@ -23,7 +23,7 @@
"""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.
diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r
a3f34a534d44feee1c77f5122402c761259a11d9 _pytest/main.py
--- a/_pytest/main.py
+++ b/_pytest/main.py
@@ -35,7 +35,7 @@
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.")
diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r
a3f34a534d44feee1c77f5122402c761259a11d9 _pytest/pastebin.py
--- a/_pytest/pastebin.py
+++ b/_pytest/pastebin.py
@@ -10,7 +10,7 @@
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):
diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r
a3f34a534d44feee1c77f5122402c761259a11d9 _pytest/runner.py
--- a/_pytest/runner.py
+++ b/_pytest/runner.py
@@ -18,7 +18,7 @@
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):
diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r
a3f34a534d44feee1c77f5122402c761259a11d9 _pytest/terminal.py
--- a/_pytest/terminal.py
+++ b/_pytest/terminal.py
@@ -25,7 +25,7 @@
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,
diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r
a3f34a534d44feee1c77f5122402c761259a11d9 pytest.py
--- a/pytest.py
+++ b/pytest.py
@@ -1,3 +1,4 @@
+# PYTHON_ARGCOMPLETE_OK
"""
pytest: unit and functional testing with Python.
"""
diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r
a3f34a534d44feee1c77f5122402c761259a11d9 testing/test_mark.py
--- a/testing/test_mark.py
+++ b/testing/test_mark.py
@@ -451,12 +451,22 @@
assert 0
test_one.mykeyword = True
""")
+ 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
- reprec = testdir.inline_run("-k", "mykeyword", p)
- passed, skipped, failed = reprec.countoutcomes()
- assert failed == 1
def test_no_magic_values(self, testdir):
"""Make sure the tests do not match on magic values,
diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r
a3f34a534d44feee1c77f5122402c761259a11d9 testing/test_parseopt.py
--- a/testing/test_parseopt.py
+++ b/testing/test_parseopt.py
@@ -7,8 +7,44 @@
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()
group = parser.getgroup("hello", description="desc")
@@ -36,7 +72,7 @@
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 @@
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 @@
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"])
https://bitbucket.org/hpk42/pytest/commits/8379d69dcb3f/
Changeset: 8379d69dcb3f
Branch: argparse
User: Anthon van der Neut
Date: 2013-07-25 17:26:48
Summary: auto change %default -> %(default)s in help parameter string (on
retrieval)
added code for warnings on optparse arguments (type, help),
which can be easily switched on with TYPE_WARN = True in config.py
installed and tested ( py.test --help )
pytest-quickcheck-0.7
pytest-gae-0.2.2
pytest-growl-0.1
pytest-bdd-0.4.7
pytest-bdd-splinter-0.4.4
pytest-cache-1.0
pytest-capturelog-0.7
pytest-codecheckers-0.2
pytest-contextfixture-0.1.1
pytest-cov-1.6
pytest-flakes-0.1
pytest-incremental-0.3.0
pytest-xdist-1.8
pytest-localserver-0.1.5
pytest-monkeyplus-1.1.0
pytest-oerp-0.2.0
pytest-pep8-1.0.4
pytest-pydev-0.1
pytest-rage-0.1
pytest-runfailed-0.3
pytest-timeout-0.3
pytest-xprocess-0.7
pytest-browsermob-proxy-0.1
pytest-mozwebqa-1.1.1
pytest-random-0.02
pytest-rerunfailures-0.03
pytest-zap-0.1
pytest-blockage-0.1
pytest-django-2.3.0
pytest-figleaf-1.0
pytest-greendots-0.1
pytest-instafail-0.1.0
pytest-konira-0.2
pytest-marker-bugzilla-0.06
pytest-marks-0.4
pytest-poo-0.2
pytest-twisted-1.4
pytest-yamlwsgi-0.6
Affected #: 1 file
diff -r a3f34a534d44feee1c77f5122402c761259a11d9 -r
8379d69dcb3fbb3349daf1ae0a6d5441f8289a18 _pytest/config.py
--- a/_pytest/config.py
+++ b/_pytest/config.py
@@ -5,6 +5,12 @@
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)
@@ -147,6 +153,17 @@
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:
@@ -155,10 +172,25 @@
# 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']
@@ -185,7 +217,7 @@
def attrs(self):
# update any attributes set by processopt
- attrs = 'default dest'.split()
+ attrs = 'default dest help'.split()
if self.dest:
attrs.append(self.dest)
for attr in attrs:
@@ -193,6 +225,11 @@
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):
https://bitbucket.org/hpk42/pytest/commits/4405d5fd6cae/
Changeset: 4405d5fd6cae
User: hpk42
Date: 2013-07-26 07:41:43
Summary: Merged in anthon_van_der_neut/pytest/argparse (pull request #46)
argparse / argcomplete
Affected #: 11 files
diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r
4405d5fd6caec4072177911d9af4e1c57fe66cec _pytest/capture.py
--- a/_pytest/capture.py
+++ b/_pytest/capture.py
@@ -6,7 +6,7 @@
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.")
diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r
4405d5fd6caec4072177911d9af4e1c57fe66cec _pytest/config.py
--- a/_pytest/config.py
+++ b/_pytest/config.py
@@ -5,6 +5,12 @@
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 @@
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,7 +115,164 @@
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):
self.name = name
@@ -115,11 +282,11 @@
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 @@
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 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
diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r
4405d5fd6caec4072177911d9af4e1c57fe66cec _pytest/helpconfig.py
--- a/_pytest/helpconfig.py
+++ b/_pytest/helpconfig.py
@@ -62,6 +62,7 @@
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")
diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r
4405d5fd6caec4072177911d9af4e1c57fe66cec _pytest/hookspec.py
--- a/_pytest/hookspec.py
+++ b/_pytest/hookspec.py
@@ -23,7 +23,7 @@
"""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.
diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r
4405d5fd6caec4072177911d9af4e1c57fe66cec _pytest/main.py
--- a/_pytest/main.py
+++ b/_pytest/main.py
@@ -35,7 +35,7 @@
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.")
diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r
4405d5fd6caec4072177911d9af4e1c57fe66cec _pytest/pastebin.py
--- a/_pytest/pastebin.py
+++ b/_pytest/pastebin.py
@@ -10,7 +10,7 @@
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):
diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r
4405d5fd6caec4072177911d9af4e1c57fe66cec _pytest/runner.py
--- a/_pytest/runner.py
+++ b/_pytest/runner.py
@@ -18,7 +18,7 @@
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):
diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r
4405d5fd6caec4072177911d9af4e1c57fe66cec _pytest/terminal.py
--- a/_pytest/terminal.py
+++ b/_pytest/terminal.py
@@ -25,7 +25,7 @@
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,
diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r
4405d5fd6caec4072177911d9af4e1c57fe66cec pytest.py
--- a/pytest.py
+++ b/pytest.py
@@ -1,3 +1,4 @@
+# PYTHON_ARGCOMPLETE_OK
"""
pytest: unit and functional testing with Python.
"""
diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r
4405d5fd6caec4072177911d9af4e1c57fe66cec testing/test_mark.py
--- a/testing/test_mark.py
+++ b/testing/test_mark.py
@@ -451,12 +451,22 @@
assert 0
test_one.mykeyword = True
""")
+ 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
- reprec = testdir.inline_run("-k", "mykeyword", p)
- passed, skipped, failed = reprec.countoutcomes()
- assert failed == 1
def test_no_magic_values(self, testdir):
"""Make sure the tests do not match on magic values,
diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r
4405d5fd6caec4072177911d9af4e1c57fe66cec testing/test_parseopt.py
--- a/testing/test_parseopt.py
+++ b/testing/test_parseopt.py
@@ -7,8 +7,44 @@
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()
group = parser.getgroup("hello", description="desc")
@@ -36,7 +72,7 @@
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 @@
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 @@
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"])
Repository URL: https://bitbucket.org/hpk42/pytest/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
_______________________________________________
pytest-commit mailing list
[email protected]
http://mail.python.org/mailman/listinfo/pytest-commit