Support [tool:pytest] in setup.cfg files

Also deprecate [pytest] usage in setup.cfg files

Fix #567
This commit is contained in:
Bruno Oliveira 2016-08-16 21:30:07 -03:00
parent d3b855104c
commit ab86dea529
10 changed files with 85 additions and 33 deletions

View File

@ -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. * ``yield``-based tests are considered deprecated and will be removed in pytest-4.0.
Thanks `@nicoddemus`_ for the PR. 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]`` sections 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 * Using ``pytest_funcarg__`` prefix to declare fixtures is considered deprecated and will be
removed in pytest-4.0 (`#1684`_). removed in pytest-4.0 (`#1684`_).
Thanks `@nicoddemus`_ for the PR. 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 .. _#372: https://github.com/pytest-dev/pytest/issues/372
.. _#457: https://github.com/pytest-dev/pytest/issues/457 .. _#457: https://github.com/pytest-dev/pytest/issues/457
.. _#460: https://github.com/pytest-dev/pytest/pull/460 .. _#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 .. _#607: https://github.com/pytest-dev/pytest/issues/607
.. _#634: https://github.com/pytest-dev/pytest/issues/634 .. _#634: https://github.com/pytest-dev/pytest/issues/634
.. _#717: https://github.com/pytest-dev/pytest/issues/717 .. _#717: https://github.com/pytest-dev/pytest/issues/717

View File

@ -927,7 +927,7 @@ class Config(object):
def _initini(self, args): def _initini(self, args):
ns, unknown_args = self._parser.parse_known_and_unknown_args(args, namespace=self.option.copy()) 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.rootdir, self.inifile, self.inicfg = r
self._parser.extra_info['rootdir'] = self.rootdir self._parser.extra_info['rootdir'] = self.rootdir
self._parser.extra_info['inifile'] = self.inifile self._parser.extra_info['inifile'] = self.inifile
@ -1154,7 +1154,18 @@ def exists(path, ignore=EnvironmentError):
except ignore: except ignore:
return False 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("-")] args = [x for x in args if not str(x).startswith("-")]
if not args: if not args:
args = [py.path.local()] args = [py.path.local()]
@ -1166,7 +1177,11 @@ def getcfg(args, inibasenames):
if exists(p): if exists(p):
iniconfig = py.iniconfig.IniConfig(p) iniconfig = py.iniconfig.IniConfig(p)
if 'pytest' in iniconfig.sections: if 'pytest' in iniconfig.sections:
if inibasename == 'setup.cfg' and warnfunc:
warnfunc('C1', SETUP_CFG_PYTEST)
return base, p, iniconfig['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": elif inibasename == "pytest.ini":
# allowed to be empty # allowed to be empty
return base, p, {} return base, p, {}
@ -1207,7 +1222,7 @@ def get_dirs_from_args(args):
if d.exists()] if d.exists()]
def determine_setup(inifile, args): def determine_setup(inifile, args, warnfunc=None):
dirs = get_dirs_from_args(args) dirs = get_dirs_from_args(args)
if inifile: if inifile:
iniconfig = py.iniconfig.IniConfig(inifile) iniconfig = py.iniconfig.IniConfig(inifile)
@ -1218,15 +1233,13 @@ def determine_setup(inifile, args):
rootdir = get_common_ancestor(dirs) rootdir = get_common_ancestor(dirs)
else: else:
ancestor = get_common_ancestor(dirs) ancestor = get_common_ancestor(dirs)
rootdir, inifile, inicfg = getcfg( rootdir, inifile, inicfg = getcfg([ancestor], warnfunc=warnfunc)
[ancestor], ["pytest.ini", "tox.ini", "setup.cfg"])
if rootdir is None: if rootdir is None:
for rootdir in ancestor.parts(reverse=True): for rootdir in ancestor.parts(reverse=True):
if rootdir.join("setup.py").exists(): if rootdir.join("setup.py").exists():
break break
else: else:
rootdir, inifile, inicfg = getcfg( rootdir, inifile, inicfg = getcfg(dirs, warnfunc=warnfunc)
dirs, ["pytest.ini", "tox.ini", "setup.cfg"])
if rootdir is None: if rootdir is None:
rootdir = get_common_ancestor([py.path.local(), ancestor]) rootdir = get_common_ancestor([py.path.local(), ancestor])
is_fs_root = os.path.splitdrive(str(rootdir))[1] == os.sep is_fs_root = os.path.splitdrive(str(rootdir))[1] == os.sep

View File

@ -17,5 +17,7 @@ FUNCARG_PREFIX = (
'and scheduled to be removed in pytest 4.0. ' 'and scheduled to be removed in pytest 4.0. '
'Please remove the prefix and use the @pytest.fixture decorator instead.') '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" GETFUNCARGVALUE = "use of getfuncargvalue is deprecated, use getfixturevalue"

View File

@ -71,7 +71,6 @@ def showhelp(config):
tw.write(config._parser.optparser.format_help()) tw.write(config._parser.optparser.format_help())
tw.line() tw.line()
tw.line() tw.line()
#tw.sep( "=", "config file settings")
tw.line("[pytest] ini-options in the next " tw.line("[pytest] ini-options in the next "
"pytest.ini|tox.ini|setup.cfg file:") "pytest.ini|tox.ini|setup.cfg file:")
tw.line() tw.line()

View File

@ -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, 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 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 merged - the first one wins (``pytest.ini`` always wins, even if it does not
contain a ``[pytest]`` section). contain a ``[pytest]`` section).
@ -73,7 +73,7 @@ check for ini-files as follows::
# first look for pytest.ini files # first look for pytest.ini files
path/pytest.ini 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 path/tox.ini # must also contain [pytest] section to match
pytest.ini pytest.ini
... # all the way down to the root ... # all the way down to the root
@ -154,7 +154,7 @@ Builtin configuration file options
.. code-block:: ini .. code-block:: ini
# content of setup.cfg # content of pytest.ini
[pytest] [pytest]
norecursedirs = .svn _build tmp* norecursedirs = .svn _build tmp*

View File

@ -77,9 +77,9 @@ Example::
Changing directory recursion 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] [pytest]
norecursedirs = .svn _build tmp* norecursedirs = .svn _build tmp*
@ -94,8 +94,9 @@ You can configure different naming conventions by setting
the :confval:`python_files`, :confval:`python_classes` and the :confval:`python_files`, :confval:`python_classes` and
:confval:`python_functions` configuration options. Example:: :confval:`python_functions` configuration options. Example::
# content of setup.cfg # content of pytest.ini
# can also be defined in in tox.ini or pytest.ini file # 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] [pytest]
python_files=check_*.py python_files=check_*.py
python_classes=Check python_classes=Check

View File

@ -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 arguments to pytest such as your test directory or other
options using ``--addopts``. options using ``--addopts``.
You can also specify other pytest-ini options in your ``setup.cfg`` file
by putting them into a ``[tool:pytest]`` section:
.. 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 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 Manual Integration
^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^

View File

@ -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 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:: 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] [pytest]
looponfailroots = mypkg testdir looponfailroots = mypkg testdir
@ -181,7 +181,7 @@ to run tests in each of the environments.
Specifying "rsync" dirs in an ini-file 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:: you may specify directories to include or to exclude in synchronisation::
[pytest] [pytest]

View File

@ -34,6 +34,15 @@ def test_funcarg_prefix_deprecation(testdir):
]) ])
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): def test_str_args_deprecated(tmpdir, testdir):
"""Deprecate passing strings to pytest.main(). Scheduled for removal in pytest-4.0.""" """Deprecate passing strings to pytest.main(). Scheduled for removal in pytest-4.0."""
from _pytest.main import EXIT_NOTESTSCOLLECTED from _pytest.main import EXIT_NOTESTSCOLLECTED

View File

@ -5,24 +5,28 @@ from _pytest.config import getcfg, get_common_ancestor, determine_setup
from _pytest.main import EXIT_NOTESTSCOLLECTED from _pytest.main import EXIT_NOTESTSCOLLECTED
class TestParseIni: 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 = tmpdir.mkdir("sub")
sub.chdir() sub.chdir()
tmpdir.join("setup.cfg").write(_pytest._code.Source(""" tmpdir.join(filename).write(_pytest._code.Source("""
[pytest] [{section}]
name = value name = value
""")) """.format(section=section)))
rootdir, inifile, cfg = getcfg([sub], ["setup.cfg"]) rootdir, inifile, cfg = getcfg([sub])
assert cfg['name'] == "value" assert cfg['name'] == "value"
config = testdir.parseconfigure(sub) config = testdir.parseconfigure(sub)
assert config.inicfg['name'] == 'value' assert config.inicfg['name'] == 'value'
def test_getcfg_empty_path(self, tmpdir): def test_getcfg_empty_path(self):
getcfg([''], ['setup.cfg']) #happens on pytest "" """correctly handle zero length arguments (a la pytest '')"""
getcfg([''])
def test_append_parse_args(self, testdir, tmpdir, monkeypatch): def test_append_parse_args(self, testdir, tmpdir, monkeypatch):
monkeypatch.setenv('PYTEST_ADDOPTS', '--color no -rs --tb="short"') 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] [pytest]
addopts = --verbose addopts = --verbose
""")) """))
@ -31,10 +35,6 @@ class TestParseIni:
assert config.option.reportchars == 's' assert config.option.reportchars == 's'
assert config.option.tbstyle == 'short' assert config.option.tbstyle == 'short'
assert config.option.verbose 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): def test_tox_ini_wrong_version(self, testdir):
testdir.makefile('.ini', tox=""" testdir.makefile('.ini', tox="""
@ -47,12 +47,16 @@ class TestParseIni:
"*tox.ini:2*requires*9.0*actual*" "*tox.ini:2*requires*9.0*actual*"
]) ])
@pytest.mark.parametrize("name", "setup.cfg tox.ini pytest.ini".split()) @pytest.mark.parametrize("section, name", [
def test_ini_names(self, testdir, 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(""" testdir.tmpdir.join(name).write(py.std.textwrap.dedent("""
[pytest] [{section}]
minversion = 1.0 minversion = 1.0
""")) """.format(section=section)))
config = testdir.parseconfig() config = testdir.parseconfig()
assert config.getini("minversion") == "1.0" assert config.getini("minversion") == "1.0"