Merge pull request #3147 from nicoddemus/issue-3103

'-o' option no longer swallows all other non-options after it
This commit is contained in:
Bruno Oliveira 2018-01-25 10:41:18 -02:00 committed by GitHub
commit 2aad8c0fce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 67 additions and 17 deletions

View File

@ -18,6 +18,7 @@ Anthon van der Neut
Anthony Sottile Anthony Sottile
Antony Lee Antony Lee
Armin Rigo Armin Rigo
Aron Coyle
Aron Curzon Aron Curzon
Aviv Palivoda Aviv Palivoda
Barney Gale Barney Gale

View File

@ -1188,16 +1188,15 @@ class Config(object):
def _get_override_ini_value(self, name): def _get_override_ini_value(self, name):
value = None value = None
# override_ini is a list of list, to support both -o foo1=bar1 foo2=bar2 and # override_ini is a list of "ini=value" options
# and -o foo1=bar1 -o foo2=bar2 options # always use the last item if multiple values are set for same ini-name,
# always use the last item if multiple value set for same ini-name,
# e.g. -o foo=bar1 -o foo=bar2 will set foo to bar2 # e.g. -o foo=bar1 -o foo=bar2 will set foo to bar2
for ini_config_list in self._override_ini: for ini_config in self._override_ini:
for ini_config in ini_config_list: try:
try: key, user_ini_value = ini_config.split("=", 1)
(key, user_ini_value) = ini_config.split("=", 1) except ValueError:
except ValueError: raise UsageError("-o/--override-ini expects option=value style.")
raise UsageError("-o/--override-ini expects option=value style.") else:
if key == name: if key == name:
value = user_ini_value value = user_ini_value
return value return value

View File

@ -57,9 +57,9 @@ def pytest_addoption(parser):
action="store_true", dest="debug", default=False, action="store_true", dest="debug", default=False,
help="store internal tracing debug information in 'pytestdebug.log'.") help="store internal tracing debug information in 'pytestdebug.log'.")
group._addoption( group._addoption(
'-o', '--override-ini', nargs='*', dest="override_ini", '-o', '--override-ini', dest="override_ini",
action="append", action="append",
help="override config option with option=value style, e.g. `-o xfail_strict=True`.") help='override ini option with "option=value" style, e.g. `-o xfail_strict=True -o cache_dir=cache`.')
@pytest.hookimpl(hookwrapper=True) @pytest.hookimpl(hookwrapper=True)

1
changelog/3103.bugfix Normal file
View File

@ -0,0 +1 @@
**Incompatible change**: ``-o/--override`` option no longer eats all the remaining options, which can lead to surprising behavior: for example, ``pytest -o foo=1 /path/to/test.py`` would fail because ``/path/to/test.py`` would be considered as part of the ``-o`` command-line argument. One consequence of this is that now multiple configuration overrides need multiple ``-o`` flags: ``pytest -o foo=1 -o bar=2``.

View File

@ -152,11 +152,25 @@ above will show verbose output because ``-v`` overwrites ``-q``.
Builtin configuration file options Builtin configuration file options
---------------------------------------------- ----------------------------------------------
Here is a list of builtin configuration options that may be written in a ``pytest.ini``, ``tox.ini`` or ``setup.cfg``
file, usually located at the root of your repository. All options must be under a ``[pytest]`` section
(``[tool:pytest]`` for ``setup.cfg`` files).
Configuration file options may be overwritten in the command-line by using ``-o/--override``, which can also be
passed multiple times. The expected format is ``name=value``. For example::
pytest -o console_output_style=classic -o cache_dir=/tmp/mycache
.. confval:: minversion .. confval:: minversion
Specifies a minimal pytest version required for running tests. Specifies a minimal pytest version required for running tests.
minversion = 2.1 # will fail if we run with pytest-2.0 .. code-block:: ini
# content of pytest.ini
[pytest]
minversion = 3.0 # will fail if we run with pytest-2.8
.. confval:: addopts .. confval:: addopts
@ -165,6 +179,7 @@ Builtin configuration file options
.. code-block:: ini .. code-block:: ini
# content of pytest.ini
[pytest] [pytest]
addopts = --maxfail=2 -rf # exit after 2 failures, report fail info addopts = --maxfail=2 -rf # exit after 2 failures, report fail info

View File

@ -781,16 +781,18 @@ class TestOverrideIniArgs(object):
testdir.makeini(""" testdir.makeini("""
[pytest] [pytest]
custom_option_1=custom_option_1 custom_option_1=custom_option_1
custom_option_2=custom_option_2""") custom_option_2=custom_option_2
""")
testdir.makepyfile(""" testdir.makepyfile("""
def test_multiple_options(pytestconfig): def test_multiple_options(pytestconfig):
prefix = "custom_option" prefix = "custom_option"
for x in range(1, 5): for x in range(1, 5):
ini_value=pytestconfig.getini("%s_%d" % (prefix, x)) ini_value=pytestconfig.getini("%s_%d" % (prefix, x))
print('\\nini%d:%s' % (x, ini_value))""") print('\\nini%d:%s' % (x, ini_value))
""")
result = testdir.runpytest( result = testdir.runpytest(
"--override-ini", 'custom_option_1=fulldir=/tmp/user1', "--override-ini", 'custom_option_1=fulldir=/tmp/user1',
'custom_option_2=url=/tmp/user2?a=b&d=e', '-o', 'custom_option_2=url=/tmp/user2?a=b&d=e',
"-o", 'custom_option_3=True', "-o", 'custom_option_3=True',
"-o", 'custom_option_4=no', "-s") "-o", 'custom_option_4=no', "-s")
result.stdout.fnmatch_lines(["ini1:fulldir=/tmp/user1", result.stdout.fnmatch_lines(["ini1:fulldir=/tmp/user1",
@ -853,10 +855,42 @@ class TestOverrideIniArgs(object):
assert rootdir == tmpdir assert rootdir == tmpdir
assert inifile is None assert inifile is None
def test_addopts_before_initini(self, testdir, tmpdir, monkeypatch): def test_addopts_before_initini(self, monkeypatch):
cache_dir = '.custom_cache' cache_dir = '.custom_cache'
monkeypatch.setenv('PYTEST_ADDOPTS', '-o cache_dir=%s' % cache_dir) monkeypatch.setenv('PYTEST_ADDOPTS', '-o cache_dir=%s' % cache_dir)
from _pytest.config import get_config from _pytest.config import get_config
config = get_config() config = get_config()
config._preparse([], addopts=True) config._preparse([], addopts=True)
assert config._override_ini == [['cache_dir=%s' % cache_dir]] assert config._override_ini == ['cache_dir=%s' % cache_dir]
def test_override_ini_does_not_contain_paths(self):
"""Check that -o no longer swallows all options after it (#3103)"""
from _pytest.config import get_config
config = get_config()
config._preparse(['-o', 'cache_dir=/cache', '/some/test/path'])
assert config._override_ini == ['cache_dir=/cache']
def test_multiple_override_ini_options(self, testdir, request):
"""Ensure a file path following a '-o' option does not generate an error (#3103)"""
testdir.makepyfile(**{
"conftest.py": """
def pytest_addoption(parser):
parser.addini('foo', default=None, help='some option')
parser.addini('bar', default=None, help='some option')
""",
"test_foo.py": """
def test(pytestconfig):
assert pytestconfig.getini('foo') == '1'
assert pytestconfig.getini('bar') == '0'
""",
"test_bar.py": """
def test():
assert False
""",
})
result = testdir.runpytest('-o', 'foo=1', '-o', 'bar=0', 'test_foo.py')
assert 'ERROR:' not in result.stderr.str()
result.stdout.fnmatch_lines([
'collected 1 item',
'*= 1 passed in *=',
])