diff --git a/AUTHORS b/AUTHORS index 48d9e26d6..fd543b145 100644 --- a/AUTHORS +++ b/AUTHORS @@ -100,6 +100,7 @@ Samuele Pedroni Steffen Allner Stephan Obermann Tareq Alayan +Ted Xiao Simon Gomizelj Stefano Taschini Stefan Farmbauer diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9ec08b03c..faaa65374 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -79,6 +79,10 @@ finalizer and has access to the fixture's result cache. Thanks `@d6e`_, `@sallner`_ +* New cli flag ``--override-ini`` or ``-o`` that overrides values from the ini file. + Example '-o xfail_strict=True'. A complete ini-options can be viewed + by py.test --help. Thanks `@blueyed`_ and `@fengxx`_ for the PR + **Changes** @@ -155,6 +159,8 @@ .. _@nikratio: https://github.com/nikratio .. _@RedBeardCode: https://github.com/RedBeardCode .. _@Vogtinator: https://github.com/Vogtinator +.. _@blueyed: https://github.com/blueyed +.. _@fengxx: https://github.com/fengxx * Fix `#1421`_: Exit tests if a collection error occurs and add ``--continue-on-collection-errors`` option to restore previous behaviour. diff --git a/_pytest/config.py b/_pytest/config.py index 3a8ce1c78..72613d41f 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -1003,14 +1003,16 @@ class Config(object): description, type, default = self._parser._inidict[name] except KeyError: raise ValueError("unknown configuration value: %r" %(name,)) - try: - value = self.inicfg[name] - except KeyError: - if default is not None: - return default - if type is None: - return '' - return [] + value = self._get_override_ini_value(name) + if value is None: + try: + value = self.inicfg[name] + except KeyError: + if default is not None: + return default + if type is None: + return '' + return [] if type == "pathlist": dp = py.path.local(self.inicfg.config.path).dirpath() l = [] @@ -1041,6 +1043,20 @@ class Config(object): l.append(relroot) return l + def _get_override_ini_value(self, name): + value = None + # override_ini is a list of list, to support both -o foo1=bar1 foo2=bar2 and + # and -o foo1=bar1 -o foo2=bar2 options + # 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 + if self.getoption("override_ini", None): + for ini_config_list in self.option.override_ini: + for ini_config in ini_config_list: + (key, user_ini_value) = ini_config.split("=", 1) + if key == name: + value = user_ini_value + return value + def getoption(self, name, default=notset, skip=False): """ return command line option value. diff --git a/_pytest/helpconfig.py b/_pytest/helpconfig.py index 15b0ada77..ae8daea9c 100644 --- a/_pytest/helpconfig.py +++ b/_pytest/helpconfig.py @@ -20,6 +20,10 @@ def pytest_addoption(parser): group.addoption('--debug', action="store_true", dest="debug", default=False, help="store internal tracing debug information in 'pytestdebug.log'.") + # support for "--overwrite-ini ININAME=INIVALUE" to override values from the ini file + # Example '-o xfail_strict=True'. + group._addoption('-o', '--override-ini', nargs='*', dest="override_ini", action="append", + help="overrides ini values which do not have a separate command-line flag") @pytest.hookimpl(hookwrapper=True) diff --git a/testing/test_config.py b/testing/test_config.py index 2d9ae6e0e..db007c2d1 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -583,3 +583,92 @@ class TestRootdir: inifile = tmpdir.ensure("pytest.ini") rootdir, inifile, inicfg = determine_setup(inifile, [tmpdir]) assert rootdir == tmpdir + +class TestOverrideIniArgs: + """ test --override-ini """ + @pytest.mark.parametrize("name", "setup.cfg tox.ini pytest.ini".split()) + def test_override_ini_names(self, testdir, name): + testdir.tmpdir.join(name).write(py.std.textwrap.dedent(""" + [pytest] + custom = 1.0 + """)) + testdir.makeconftest(""" + def pytest_addoption(parser): + parser.addini("custom", "") + """) + testdir.makepyfile(""" + def test_pass(pytestconfig): + ini_val = pytestconfig.getini("custom") + print('\\ncustom_option:%s\\n' % ini_val) + """) + + result = testdir.runpytest("--override-ini", "custom=2.0", "-s") + assert result.ret == 0 + result.stdout.fnmatch_lines([ + "custom_option:2.0" + ]) + + result = testdir.runpytest("--override-ini", "custom=2.0", + "--override-ini=custom=3.0", "-s") + assert result.ret == 0 + result.stdout.fnmatch_lines([ + "custom_option:3.0" + ]) + + + def test_override_ini_pathlist(self, testdir): + testdir.makeconftest(""" + def pytest_addoption(parser): + parser.addini("paths", "my new ini value", type="pathlist") + """) + testdir.makeini(""" + [pytest] + paths=blah.py + """) + testdir.makepyfile(""" + import py.path + def test_pathlist(pytestconfig): + config_paths = pytestconfig.getini("paths") + print(config_paths) + for cpf in config_paths: + print('\\nuser_path:%s' % cpf.basename) + """) + result = testdir.runpytest("--override-ini", 'paths=foo/bar1.py foo/bar2.py', "-s") + result.stdout.fnmatch_lines([ + "user_path:bar1.py", + "user_path:bar2.py" + ]) + + def test_override_multiple_and_default(self, testdir): + testdir.makeconftest(""" + def pytest_addoption(parser): + parser.addini("custom_option_1", "", default="o1") + parser.addini("custom_option_2", "", default="o2") + parser.addini("custom_option_3", "", default=False, type="bool") + parser.addini("custom_option_4", "", default=True, type="bool") + + """) + testdir.makeini(""" + [pytest] + custom_option_1=custom_option_1 + custom_option_2=custom_option_2 + """) + testdir.makepyfile(""" + def test_multiple_options(pytestconfig): + prefix="custom_option" + for x in range(1,5): + ini_value=pytestconfig.getini("%s_%d" % (prefix, x)) + print('\\nini%d:%s' % (x, ini_value)) + """) + result = testdir.runpytest("--override-ini", + 'custom_option_1=fulldir=/tmp/user1', + 'custom_option_2=url=/tmp/user2?a=b&d=e', + "-o", 'custom_option_3=True', + "-o", 'custom_option_4=no', + "-s") + result.stdout.fnmatch_lines([ + "ini1:fulldir=/tmp/user1", + "ini2:url=/tmp/user2?a=b&d=e", + "ini3:True", + "ini4:False" + ]) \ No newline at end of file