From b79238405c720c0e725f709894fba03181c07c2c Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 16 Aug 2016 21:30:07 -0300 Subject: [PATCH] Support [tool:pytest] in setup.cfg files Also deprecate [pytest] usage in setup.cfg files Fix #567 --- CHANGELOG.rst | 6 +++++ _pytest/config.py | 27 ++++++++++++++++------ _pytest/deprecated.py | 2 ++ _pytest/helpconfig.py | 1 - doc/en/customize.rst | 6 ++--- doc/en/example/pythoncollection.rst | 9 ++++---- doc/en/goodpractices.rst | 18 +++++++++++++++ doc/en/xdist.rst | 4 ++-- testing/deprecated_test.py | 9 ++++++++ testing/test_config.py | 36 ++++++++++++++++------------- 10 files changed, 85 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fd585029995..f5f0fdfc47f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -232,6 +232,11 @@ time or change existing behaviors in order to make them less surprising/more use * ``yield``-based tests are considered deprecated and will be removed in pytest-4.0. Thanks `@nicoddemus`_ for the PR. +* ``[pytest]`` sections in ``setup.cfg`` files should now be named ``[tool:pytest]`` + to avoid conflicts with other distutils commands (see `#567`_). ``[pytest]`` in + ``pytest.ini`` or ``tox.ini`` files are supported and unchanged. + Thanks `@nicoddemus`_ for the PR. + * Using ``pytest_funcarg__`` prefix to declare fixtures is considered deprecated and will be removed in pytest-4.0 (`#1684`_). Thanks `@nicoddemus`_ for the PR. @@ -374,6 +379,7 @@ time or change existing behaviors in order to make them less surprising/more use .. _#372: https://github.com/pytest-dev/pytest/issues/372 .. _#457: https://github.com/pytest-dev/pytest/issues/457 .. _#460: https://github.com/pytest-dev/pytest/pull/460 +.. _#567: https://github.com/pytest-dev/pytest/pull/567 .. _#607: https://github.com/pytest-dev/pytest/issues/607 .. _#634: https://github.com/pytest-dev/pytest/issues/634 .. _#717: https://github.com/pytest-dev/pytest/issues/717 diff --git a/_pytest/config.py b/_pytest/config.py index 4756df1d1d3..2a121581148 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -927,7 +927,7 @@ def pytest_load_initial_conftests(self, early_config): def _initini(self, args): ns, unknown_args = self._parser.parse_known_and_unknown_args(args, namespace=self.option.copy()) - r = determine_setup(ns.inifilename, ns.file_or_dir + unknown_args) + r = determine_setup(ns.inifilename, ns.file_or_dir + unknown_args, warnfunc=self.warn) self.rootdir, self.inifile, self.inicfg = r self._parser.extra_info['rootdir'] = self.rootdir self._parser.extra_info['inifile'] = self.inifile @@ -1154,7 +1154,18 @@ def exists(path, ignore=EnvironmentError): except ignore: return False -def getcfg(args, inibasenames): +def getcfg(args, warnfunc=None): + """ + Search the list of arguments for a valid ini-file for pytest, + and return a tuple of (rootdir, inifile, cfg-dict). + + note: warnfunc is an optional function used to warn + about ini-files that use deprecated features. + This parameter should be removed when pytest + adopts standard deprecation warnings (#1804). + """ + from _pytest.deprecated import SETUP_CFG_PYTEST + inibasenames = ["pytest.ini", "tox.ini", "setup.cfg"] args = [x for x in args if not str(x).startswith("-")] if not args: args = [py.path.local()] @@ -1166,7 +1177,11 @@ def getcfg(args, inibasenames): if exists(p): iniconfig = py.iniconfig.IniConfig(p) if 'pytest' in iniconfig.sections: + if inibasename == 'setup.cfg' and warnfunc: + warnfunc('C1', SETUP_CFG_PYTEST) return base, p, iniconfig['pytest'] + if inibasename == 'setup.cfg' and 'tool:pytest' in iniconfig.sections: + return base, p, iniconfig['tool:pytest'] elif inibasename == "pytest.ini": # allowed to be empty return base, p, {} @@ -1207,7 +1222,7 @@ def get_dirs_from_args(args): if d.exists()] -def determine_setup(inifile, args): +def determine_setup(inifile, args, warnfunc=None): dirs = get_dirs_from_args(args) if inifile: iniconfig = py.iniconfig.IniConfig(inifile) @@ -1218,15 +1233,13 @@ def determine_setup(inifile, args): rootdir = get_common_ancestor(dirs) else: ancestor = get_common_ancestor(dirs) - rootdir, inifile, inicfg = getcfg( - [ancestor], ["pytest.ini", "tox.ini", "setup.cfg"]) + rootdir, inifile, inicfg = getcfg([ancestor], warnfunc=warnfunc) if rootdir is None: for rootdir in ancestor.parts(reverse=True): if rootdir.join("setup.py").exists(): break else: - rootdir, inifile, inicfg = getcfg( - dirs, ["pytest.ini", "tox.ini", "setup.cfg"]) + rootdir, inifile, inicfg = getcfg(dirs, warnfunc=warnfunc) if rootdir is None: rootdir = get_common_ancestor([py.path.local(), ancestor]) is_fs_root = os.path.splitdrive(str(rootdir))[1] == os.sep diff --git a/_pytest/deprecated.py b/_pytest/deprecated.py index 2972c77a5f4..8f9a54347c1 100644 --- a/_pytest/deprecated.py +++ b/_pytest/deprecated.py @@ -17,5 +17,7 @@ 'and scheduled to be removed in pytest 4.0. ' 'Please remove the prefix and use the @pytest.fixture decorator instead.') +SETUP_CFG_PYTEST = '[pytest] section in setup.cfg files is deprecated, use [tool:pytest] instead.' + GETFUNCARGVALUE = "use of getfuncargvalue is deprecated, use getfixturevalue" diff --git a/_pytest/helpconfig.py b/_pytest/helpconfig.py index f92ce1c04a1..84f419a08b0 100644 --- a/_pytest/helpconfig.py +++ b/_pytest/helpconfig.py @@ -71,7 +71,6 @@ def showhelp(config): tw.write(config._parser.optparser.format_help()) tw.line() tw.line() - #tw.sep( "=", "config file settings") tw.line("[pytest] ini-options in the next " "pytest.ini|tox.ini|setup.cfg file:") tw.line() diff --git a/doc/en/customize.rst b/doc/en/customize.rst index 54cf43249a6..7ca8d121565 100644 --- a/doc/en/customize.rst +++ b/doc/en/customize.rst @@ -50,7 +50,7 @@ Here is the algorithm which finds the rootdir from ``args``: Note that an existing ``pytest.ini`` file will always be considered a match, whereas ``tox.ini`` and ``setup.cfg`` will only match if they contain a -``[pytest]`` section. Options from multiple ini-files candidates are never +``[pytest]`` or ``[tool:pytest]`` section, respectively. Options from multiple ini-files candidates are never merged - the first one wins (``pytest.ini`` always wins, even if it does not contain a ``[pytest]`` section). @@ -73,7 +73,7 @@ check for ini-files as follows:: # first look for pytest.ini files path/pytest.ini - path/setup.cfg # must also contain [pytest] section to match + path/setup.cfg # must also contain [tool:pytest] section to match path/tox.ini # must also contain [pytest] section to match pytest.ini ... # all the way down to the root @@ -154,7 +154,7 @@ Builtin configuration file options .. code-block:: ini - # content of setup.cfg + # content of pytest.ini [pytest] norecursedirs = .svn _build tmp* diff --git a/doc/en/example/pythoncollection.rst b/doc/en/example/pythoncollection.rst index 0e481271a69..c565506c413 100644 --- a/doc/en/example/pythoncollection.rst +++ b/doc/en/example/pythoncollection.rst @@ -77,9 +77,9 @@ Example:: Changing directory recursion ----------------------------------------------------- -You can set the :confval:`norecursedirs` option in an ini-file, for example your ``setup.cfg`` in the project root directory:: +You can set the :confval:`norecursedirs` option in an ini-file, for example your ``pytest.ini`` in the project root directory:: - # content of setup.cfg + # content of pytest.ini [pytest] norecursedirs = .svn _build tmp* @@ -94,8 +94,9 @@ You can configure different naming conventions by setting the :confval:`python_files`, :confval:`python_classes` and :confval:`python_functions` configuration options. Example:: - # content of setup.cfg - # can also be defined in in tox.ini or pytest.ini file + # content of pytest.ini + # can also be defined in in tox.ini or setup.cfg file, although the section + # name in setup.cfg files should be "tool:pytest" [pytest] python_files=check_*.py python_classes=Check diff --git a/doc/en/goodpractices.rst b/doc/en/goodpractices.rst index e1c4b0b5cd5..bc938ed9fce 100644 --- a/doc/en/goodpractices.rst +++ b/doc/en/goodpractices.rst @@ -196,6 +196,24 @@ required for calling the test command. You can also pass additional arguments to pytest such as your test directory or other options using ``--addopts``. +You can also specify other pytest-ini options in your ``setup.cfg`` file +by putting them into a ``[tool:pytest]`` version: + +.. code-block:: ini + + [tool:pytest] + addopts = --verbose + python_files = testing/*/*.py + + +.. note:: + Prior to 3.0, the supported section name was ``[pytest]``. Due to how + this conflicts may collide with some distutils commands, the recommended + section name for ``setup.cfg`` files is now ``[tool:pytest]``. + + Note that for ``pytest.ini`` and ``tox.ini`` files the section + name is ``[pytest]``. + Manual Integration ^^^^^^^^^^^^^^^^^^ diff --git a/doc/en/xdist.rst b/doc/en/xdist.rst index fa662b7d3cf..a33a1525a57 100644 --- a/doc/en/xdist.rst +++ b/doc/en/xdist.rst @@ -90,7 +90,7 @@ and ``pytest`` will run your tests. Assuming you have failures it will then wait for file changes and re-run the failing test set. File changes are detected by looking at ``looponfailingroots`` root directories and all of their contents (recursively). If the default for this value does not work for you you can change it in your project by setting a configuration option:: - # content of a pytest.ini, setup.cfg or tox.ini file + # content of a pytest.ini or tox.ini file [pytest] looponfailroots = mypkg testdir @@ -181,7 +181,7 @@ to run tests in each of the environments. Specifying "rsync" dirs in an ini-file +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -In a ``tox.ini`` or ``setup.cfg`` file in your root project directory +In a ``pytest.ini`` or ``tox.ini`` file in your root project directory you may specify directories to include or to exclude in synchronisation:: [pytest] diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index edbd5d1cda7..b79cc270ad6 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -34,6 +34,15 @@ def test_funcarg_prefix(value): ]) +def test_pytest_setup_cfg_deprecated(testdir): + testdir.makefile('.cfg', setup=''' + [pytest] + addopts = --verbose + ''') + result = testdir.runpytest() + result.stdout.fnmatch_lines(['*pytest*section in setup.cfg files is deprecated*use*tool:pytest*instead*']) + + def test_str_args_deprecated(tmpdir, testdir): """Deprecate passing strings to pytest.main(). Scheduled for removal in pytest-4.0.""" from _pytest.main import EXIT_NOTESTSCOLLECTED diff --git a/testing/test_config.py b/testing/test_config.py index 3ceb9cd70d3..dc7db6fd275 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -5,24 +5,28 @@ from _pytest.main import EXIT_NOTESTSCOLLECTED class TestParseIni: - def test_getcfg_and_config(self, testdir, tmpdir): + + @pytest.mark.parametrize('section, filename', + [('pytest', 'pytest.ini'), ('tool:pytest', 'setup.cfg')]) + def test_getcfg_and_config(self, testdir, tmpdir, section, filename): sub = tmpdir.mkdir("sub") sub.chdir() - tmpdir.join("setup.cfg").write(_pytest._code.Source(""" - [pytest] + tmpdir.join(filename).write(_pytest._code.Source(""" + [{section}] name = value - """)) - rootdir, inifile, cfg = getcfg([sub], ["setup.cfg"]) + """.format(section=section))) + rootdir, inifile, cfg = getcfg([sub]) assert cfg['name'] == "value" config = testdir.parseconfigure(sub) assert config.inicfg['name'] == 'value' - def test_getcfg_empty_path(self, tmpdir): - getcfg([''], ['setup.cfg']) #happens on pytest "" + def test_getcfg_empty_path(self): + """correctly handle zero length arguments (a la pytest '')""" + getcfg(['']) def test_append_parse_args(self, testdir, tmpdir, monkeypatch): monkeypatch.setenv('PYTEST_ADDOPTS', '--color no -rs --tb="short"') - tmpdir.join("setup.cfg").write(_pytest._code.Source(""" + tmpdir.join("pytest.ini").write(_pytest._code.Source(""" [pytest] addopts = --verbose """)) @@ -31,10 +35,6 @@ def test_append_parse_args(self, testdir, tmpdir, monkeypatch): assert config.option.reportchars == 's' assert config.option.tbstyle == 'short' assert config.option.verbose - #config = testdir.Config() - #args = [tmpdir,] - #config._preparse(args, addopts=False) - #assert len(args) == 1 def test_tox_ini_wrong_version(self, testdir): testdir.makefile('.ini', tox=""" @@ -47,12 +47,16 @@ def test_tox_ini_wrong_version(self, testdir): "*tox.ini:2*requires*9.0*actual*" ]) - @pytest.mark.parametrize("name", "setup.cfg tox.ini pytest.ini".split()) - def test_ini_names(self, testdir, name): + @pytest.mark.parametrize("section, name", [ + ('tool:pytest', 'setup.cfg'), + ('pytest', 'tox.ini'), + ('pytest', 'pytest.ini')], + ) + def test_ini_names(self, testdir, name, section): testdir.tmpdir.join(name).write(py.std.textwrap.dedent(""" - [pytest] + [{section}] minversion = 1.0 - """)) + """.format(section=section))) config = testdir.parseconfig() assert config.getini("minversion") == "1.0"