Add pyproject.toml support (#7247)
This commit is contained in:
parent
ceac6736d7
commit
c17d50829f
|
@ -29,6 +29,7 @@ doc/*/_changelog_towncrier_draft.rst
|
|||
build/
|
||||
dist/
|
||||
*.egg-info
|
||||
htmlcov/
|
||||
issue/
|
||||
env/
|
||||
.env/
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
pytest now supports ``pyproject.toml`` files for configuration.
|
||||
|
||||
The configuration options is similar to the one available in other formats, but must be defined
|
||||
in a ``[tool.pytest.ini_options]`` table to be picked up by pytest:
|
||||
|
||||
.. code-block:: toml
|
||||
|
||||
# pyproject.toml
|
||||
[tool.pytest.ini_options]
|
||||
minversion = "6.0"
|
||||
addopts = "-ra -q"
|
||||
testpaths = [
|
||||
"tests",
|
||||
"integration",
|
||||
]
|
||||
|
||||
More information can be found `in the docs <https://docs.pytest.org/en/stable/customize.html#configuration-file-formats>`__.
|
|
@ -14,15 +14,112 @@ configurations files by using the general help option:
|
|||
This will display command line and configuration file settings
|
||||
which were registered by installed plugins.
|
||||
|
||||
.. _rootdir:
|
||||
.. _inifiles:
|
||||
.. _`config file formats`:
|
||||
|
||||
Initialization: determining rootdir and inifile
|
||||
-----------------------------------------------
|
||||
Configuration file formats
|
||||
--------------------------
|
||||
|
||||
Many :ref:`pytest settings <ini options ref>` can be set in a *configuration file*, which
|
||||
by convention resides on the root of your repository or in your
|
||||
tests folder.
|
||||
|
||||
A quick example of the configuration files supported by pytest:
|
||||
|
||||
pytest.ini
|
||||
~~~~~~~~~~
|
||||
|
||||
``pytest.ini`` files take precedence over other files, even when empty.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
# pytest.ini
|
||||
[pytest]
|
||||
minversion = 6.0
|
||||
addopts = -ra -q
|
||||
testpaths =
|
||||
tests
|
||||
integration
|
||||
|
||||
|
||||
pyproject.toml
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
.. versionadded:: 6.0
|
||||
|
||||
``pyproject.toml`` are considered for configuration when they contain a ``tool.pytest.ini_options`` table.
|
||||
|
||||
.. code-block:: toml
|
||||
|
||||
# pyproject.toml
|
||||
[tool.pytest.ini_options]
|
||||
minversion = "6.0"
|
||||
addopts = "-ra -q"
|
||||
testpaths = [
|
||||
"tests",
|
||||
"integration",
|
||||
]
|
||||
|
||||
.. note::
|
||||
|
||||
One might wonder why ``[tool.pytest.ini_options]`` instead of ``[tool.pytest]`` as is the
|
||||
case with other tools.
|
||||
|
||||
The reason is that the pytest team intends to fully utilize the rich TOML data format
|
||||
for configuration in the future, reserving the ``[tool.pytest]`` table for that.
|
||||
The ``ini_options`` table is being used, for now, as a bridge between the existing
|
||||
``.ini`` configuration system and the future configuration format.
|
||||
|
||||
tox.ini
|
||||
~~~~~~~
|
||||
|
||||
``tox.ini`` files are the configuration files of the `tox <https://tox.readthedocs.io>`__ project,
|
||||
and can also be used to hold pytest configuration if they have a ``[pytest]`` section.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
# tox.ini
|
||||
[pytest]
|
||||
minversion = 6.0
|
||||
addopts = -ra -q
|
||||
testpaths =
|
||||
tests
|
||||
integration
|
||||
|
||||
|
||||
setup.cfg
|
||||
~~~~~~~~~
|
||||
|
||||
``setup.cfg`` files are general purpose configuration files, used originally by `distutils <https://docs.python.org/3/distutils/configfile.html>`__, and can also be used to hold pytest configuration
|
||||
if they have a ``[tool:pytest]`` section.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
# setup.cfg
|
||||
[tool:pytest]
|
||||
minversion = 6.0
|
||||
addopts = -ra -q
|
||||
testpaths =
|
||||
tests
|
||||
integration
|
||||
|
||||
.. warning::
|
||||
|
||||
Usage of ``setup.cfg`` is not recommended unless for very simple use cases. ``.cfg``
|
||||
files use a different parser than ``pytest.ini`` and ``tox.ini`` which might cause hard to track
|
||||
down problems.
|
||||
When possible, it is recommended to use the latter files, or ``pyproject.toml``, to hold your
|
||||
pytest configuration.
|
||||
|
||||
|
||||
.. _rootdir:
|
||||
.. _configfiles:
|
||||
|
||||
Initialization: determining rootdir and configfile
|
||||
--------------------------------------------------
|
||||
|
||||
pytest determines a ``rootdir`` for each test run which depends on
|
||||
the command line arguments (specified test files, paths) and on
|
||||
the existence of *ini-files*. The determined ``rootdir`` and *ini-file* are
|
||||
the existence of configuration files. The determined ``rootdir`` and ``configfile`` are
|
||||
printed as part of the pytest header during startup.
|
||||
|
||||
Here's a summary what ``pytest`` uses ``rootdir`` for:
|
||||
|
@ -48,48 +145,47 @@ Finding the ``rootdir``
|
|||
|
||||
Here is the algorithm which finds the rootdir from ``args``:
|
||||
|
||||
- determine the common ancestor directory for the specified ``args`` that are
|
||||
- Determine the common ancestor directory for the specified ``args`` that are
|
||||
recognised as paths that exist in the file system. If no such paths are
|
||||
found, the common ancestor directory is set to the current working directory.
|
||||
|
||||
- look for ``pytest.ini``, ``tox.ini`` and ``setup.cfg`` files in the ancestor
|
||||
directory and upwards. If one is matched, it becomes the ini-file and its
|
||||
directory becomes the rootdir.
|
||||
- Look for ``pytest.ini``, ``pyproject.toml``, ``tox.ini``, and ``setup.cfg`` files in the ancestor
|
||||
directory and upwards. If one is matched, it becomes the ``configfile`` and its
|
||||
directory becomes the ``rootdir``.
|
||||
|
||||
- if no ini-file was found, look for ``setup.py`` upwards from the common
|
||||
- If no configuration file was found, look for ``setup.py`` upwards from the common
|
||||
ancestor directory to determine the ``rootdir``.
|
||||
|
||||
- if no ``setup.py`` was found, look for ``pytest.ini``, ``tox.ini`` and
|
||||
- If no ``setup.py`` was found, look for ``pytest.ini``, ``pyproject.toml``, ``tox.ini``, and
|
||||
``setup.cfg`` in each of the specified ``args`` and upwards. If one is
|
||||
matched, it becomes the ini-file and its directory becomes the rootdir.
|
||||
matched, it becomes the ``configfile`` and its directory becomes the ``rootdir``.
|
||||
|
||||
- if no ini-file was found, use the already determined common ancestor as root
|
||||
- If no ``configfile`` was found, use the already determined common ancestor as root
|
||||
directory. This allows the use of pytest in structures that are not part of
|
||||
a package and don't have any particular ini-file configuration.
|
||||
a package and don't have any particular configuration file.
|
||||
|
||||
If no ``args`` are given, pytest collects test below the current working
|
||||
directory and also starts determining the rootdir from there.
|
||||
directory and also starts determining the ``rootdir`` from there.
|
||||
|
||||
:warning: custom pytest plugin commandline arguments may include a path, as in
|
||||
``pytest --log-output ../../test.log args``. Then ``args`` is mandatory,
|
||||
otherwise pytest uses the folder of test.log for rootdir determination
|
||||
(see also `issue 1435 <https://github.com/pytest-dev/pytest/issues/1435>`_).
|
||||
A dot ``.`` for referencing to the current working directory is also
|
||||
possible.
|
||||
Files will only be matched for configuration if:
|
||||
|
||||
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]`` 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).
|
||||
* ``pytest.ini``: will always match and take precedence, even if empty.
|
||||
* ``pyproject.toml``: contains a ``[tool.pytest.ini_options]`` table.
|
||||
* ``tox.ini``: contains a ``[pytest]`` section.
|
||||
* ``setup.cfg``: contains a ``[tool:pytest]`` section.
|
||||
|
||||
The ``config`` object will subsequently carry these attributes:
|
||||
The files are considered in the order above. Options from multiple ``configfiles`` candidates
|
||||
are never merged - the first match wins.
|
||||
|
||||
The internal :class:`Config <_pytest.config.Config>` object (accessible via hooks or through the :fixture:`pytestconfig` fixture)
|
||||
will subsequently carry these attributes:
|
||||
|
||||
- ``config.rootdir``: the determined root directory, guaranteed to exist.
|
||||
|
||||
- ``config.inifile``: the determined ini-file, may be ``None``.
|
||||
- ``config.inifile``: the determined ``configfile``, may be ``None`` (it is named ``inifile``
|
||||
for historical reasons).
|
||||
|
||||
The rootdir is used as a reference directory for constructing test
|
||||
The ``rootdir`` is used as a reference directory for constructing test
|
||||
addresses ("nodeids") and can be used also by plugins for storing
|
||||
per-testrun information.
|
||||
|
||||
|
@ -100,75 +196,38 @@ Example:
|
|||
pytest path/to/testdir path/other/
|
||||
|
||||
will determine the common ancestor as ``path`` and then
|
||||
check for ini-files as follows:
|
||||
check for configuration files as follows:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
# first look for pytest.ini files
|
||||
path/pytest.ini
|
||||
path/tox.ini # must also contain [pytest] section to match
|
||||
path/setup.cfg # must also contain [tool:pytest] section to match
|
||||
path/pyproject.toml # must contain a [tool.pytest.ini_options] table to match
|
||||
path/tox.ini # must contain [pytest] section to match
|
||||
path/setup.cfg # must contain [tool:pytest] section to match
|
||||
pytest.ini
|
||||
... # all the way down to the root
|
||||
... # all the way up to the root
|
||||
|
||||
# now look for setup.py
|
||||
path/setup.py
|
||||
setup.py
|
||||
... # all the way down to the root
|
||||
... # all the way up to the root
|
||||
|
||||
|
||||
.. warning::
|
||||
|
||||
Custom pytest plugin commandline arguments may include a path, as in
|
||||
``pytest --log-output ../../test.log args``. Then ``args`` is mandatory,
|
||||
otherwise pytest uses the folder of test.log for rootdir determination
|
||||
(see also `issue 1435 <https://github.com/pytest-dev/pytest/issues/1435>`_).
|
||||
A dot ``.`` for referencing to the current working directory is also
|
||||
possible.
|
||||
|
||||
|
||||
.. _`how to change command line options defaults`:
|
||||
.. _`adding default options`:
|
||||
|
||||
|
||||
|
||||
How to change command line options defaults
|
||||
------------------------------------------------
|
||||
|
||||
It can be tedious to type the same series of command line options
|
||||
every time you use ``pytest``. For example, if you always want to see
|
||||
detailed info on skipped and xfailed tests, as well as have terser "dot"
|
||||
progress output, you can write it into a configuration file:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
# content of pytest.ini or tox.ini
|
||||
[pytest]
|
||||
addopts = -ra -q
|
||||
|
||||
# content of setup.cfg
|
||||
[tool:pytest]
|
||||
addopts = -ra -q
|
||||
|
||||
Alternatively, you can set a ``PYTEST_ADDOPTS`` environment variable to add command
|
||||
line options while the environment is in use:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
export PYTEST_ADDOPTS="-v"
|
||||
|
||||
Here's how the command-line is built in the presence of ``addopts`` or the environment variable:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
<pytest.ini:addopts> $PYTEST_ADDOPTS <extra command-line arguments>
|
||||
|
||||
So if the user executes in the command-line:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pytest -m slow
|
||||
|
||||
The actual command line executed is:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pytest -ra -q -v -m slow
|
||||
|
||||
Note that as usual for other command-line applications, in case of conflicting options the last one wins, so the example
|
||||
above will show verbose output because ``-v`` overwrites ``-q``.
|
||||
|
||||
|
||||
Builtin configuration file options
|
||||
----------------------------------------------
|
||||
|
||||
|
|
|
@ -115,15 +115,13 @@ Changing naming conventions
|
|||
|
||||
You can configure different naming conventions by setting
|
||||
the :confval:`python_files`, :confval:`python_classes` and
|
||||
:confval:`python_functions` configuration options.
|
||||
:confval:`python_functions` in your :ref:`configuration file <config file formats>`.
|
||||
Here is an example:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
# content of pytest.ini
|
||||
# Example 1: have pytest look for "check" instead of "test"
|
||||
# can also be defined 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
|
||||
|
@ -165,8 +163,7 @@ You can check for multiple glob patterns by adding a space between the patterns:
|
|||
.. code-block:: ini
|
||||
|
||||
# Example 2: have pytest look for files with "test" and "example"
|
||||
# content of pytest.ini, tox.ini, or setup.cfg file (replace "pytest"
|
||||
# with "tool:pytest" for setup.cfg)
|
||||
# content of pytest.ini
|
||||
[pytest]
|
||||
python_files = test_*.py example_*.py
|
||||
|
||||
|
|
|
@ -3,6 +3,50 @@
|
|||
Basic patterns and examples
|
||||
==========================================================
|
||||
|
||||
How to change command line options defaults
|
||||
-------------------------------------------
|
||||
|
||||
It can be tedious to type the same series of command line options
|
||||
every time you use ``pytest``. For example, if you always want to see
|
||||
detailed info on skipped and xfailed tests, as well as have terser "dot"
|
||||
progress output, you can write it into a configuration file:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
# content of pytest.ini
|
||||
[pytest]
|
||||
addopts = -ra -q
|
||||
|
||||
|
||||
Alternatively, you can set a ``PYTEST_ADDOPTS`` environment variable to add command
|
||||
line options while the environment is in use:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
export PYTEST_ADDOPTS="-v"
|
||||
|
||||
Here's how the command-line is built in the presence of ``addopts`` or the environment variable:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
<pytest.ini:addopts> $PYTEST_ADDOPTS <extra command-line arguments>
|
||||
|
||||
So if the user executes in the command-line:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pytest -m slow
|
||||
|
||||
The actual command line executed is:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pytest -ra -q -v -m slow
|
||||
|
||||
Note that as usual for other command-line applications, in case of conflicting options the last one wins, so the example
|
||||
above will show verbose output because ``-v`` overwrites ``-q``.
|
||||
|
||||
|
||||
.. _request example:
|
||||
|
||||
Pass different values to a test function, depending on command line options
|
||||
|
|
|
@ -1019,17 +1019,17 @@ UsageError
|
|||
Configuration 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).
|
||||
Here is a list of builtin configuration options that may be written in a ``pytest.ini``, ``pyproject.toml``, ``tox.ini`` or ``setup.cfg``
|
||||
file, usually located at the root of your repository. To see each file format in details, see
|
||||
:ref:`config file formats`.
|
||||
|
||||
.. warning::
|
||||
Usage of ``setup.cfg`` is not recommended unless for very simple use cases. ``.cfg``
|
||||
Usage of ``setup.cfg`` is not recommended except for very simple use cases. ``.cfg``
|
||||
files use a different parser than ``pytest.ini`` and ``tox.ini`` which might cause hard to track
|
||||
down problems.
|
||||
When possible, it is recommended to use the latter files to hold your pytest configuration.
|
||||
When possible, it is recommended to use the latter files, or ``pyproject.toml``, to hold your pytest configuration.
|
||||
|
||||
Configuration file options may be overwritten in the command-line by using ``-o/--override-ini``, which can also be
|
||||
Configuration options may be overwritten in the command-line by using ``-o/--override-ini``, 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
|
||||
|
@ -1057,8 +1057,6 @@ passed multiple times. The expected format is ``name=value``. For example::
|
|||
|
||||
.. confval:: cache_dir
|
||||
|
||||
|
||||
|
||||
Sets a directory where stores content of cache plugin. Default directory is
|
||||
``.pytest_cache`` which is created in :ref:`rootdir <rootdir>`. Directory may be
|
||||
relative or absolute path. If setting relative path, then directory is created
|
||||
|
|
|
@ -7,6 +7,49 @@ requires = [
|
|||
]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
minversion = "2.0"
|
||||
addopts = "-rfEX -p pytester --strict-markers"
|
||||
python_files = ["test_*.py", "*_test.py", "testing/*/*.py"]
|
||||
python_classes = ["Test", "Acceptance"]
|
||||
python_functions = ["test"]
|
||||
# NOTE: "doc" is not included here, but gets tested explicitly via "doctesting".
|
||||
testpaths = ["testing"]
|
||||
norecursedirs = ["testing/example_scripts"]
|
||||
xfail_strict = true
|
||||
filterwarnings = [
|
||||
"error",
|
||||
"default:Using or importing the ABCs:DeprecationWarning:unittest2.*",
|
||||
"default:the imp module is deprecated in favour of importlib:DeprecationWarning:nose.*",
|
||||
"ignore:Module already imported so cannot be rewritten:pytest.PytestWarning",
|
||||
# produced by python3.6/site.py itself (3.6.7 on Travis, could not trigger it with 3.6.8)."
|
||||
"ignore:.*U.*mode is deprecated:DeprecationWarning:(?!(pytest|_pytest))",
|
||||
# produced by pytest-xdist
|
||||
"ignore:.*type argument to addoption.*:DeprecationWarning",
|
||||
# produced by python >=3.5 on execnet (pytest-xdist)
|
||||
"ignore:.*inspect.getargspec.*deprecated, use inspect.signature.*:DeprecationWarning",
|
||||
# pytest's own futurewarnings
|
||||
"ignore::pytest.PytestExperimentalApiWarning",
|
||||
# Do not cause SyntaxError for invalid escape sequences in py37.
|
||||
# Those are caught/handled by pyupgrade, and not easy to filter with the
|
||||
# module being the filename (with .py removed).
|
||||
"default:invalid escape sequence:DeprecationWarning",
|
||||
# ignore use of unregistered marks, because we use many to test the implementation
|
||||
"ignore::_pytest.warning_types.PytestUnknownMarkWarning",
|
||||
]
|
||||
pytester_example_dir = "testing/example_scripts"
|
||||
markers = [
|
||||
# dummy markers for testing
|
||||
"foo",
|
||||
"bar",
|
||||
"baz",
|
||||
# conftest.py reorders tests moving slow ones to the end of the list
|
||||
"slow",
|
||||
# experimental mark for all tests using pexpect
|
||||
"uses_pexpect",
|
||||
]
|
||||
|
||||
|
||||
[tool.towncrier]
|
||||
package = "pytest"
|
||||
package_dir = "src"
|
||||
|
|
|
@ -46,6 +46,7 @@ install_requires =
|
|||
packaging
|
||||
pluggy>=0.12,<1.0
|
||||
py>=1.5.0
|
||||
toml
|
||||
atomicwrites>=1.0;sys_platform=="win32"
|
||||
colorama;sys_platform=="win32"
|
||||
importlib-metadata>=0.12;python_version<"3.8"
|
||||
|
|
|
@ -34,7 +34,6 @@ import _pytest.hookspec # the extension point definitions
|
|||
from .exceptions import PrintHelp
|
||||
from .exceptions import UsageError
|
||||
from .findpaths import determine_setup
|
||||
from .findpaths import exists
|
||||
from _pytest._code import ExceptionInfo
|
||||
from _pytest._code import filter_traceback
|
||||
from _pytest._io import TerminalWriter
|
||||
|
@ -450,7 +449,7 @@ class PytestPluginManager(PluginManager):
|
|||
if i != -1:
|
||||
path = path[:i]
|
||||
anchor = current.join(path, abs=1)
|
||||
if exists(anchor): # we found some file object
|
||||
if anchor.exists(): # we found some file object
|
||||
self._try_load_conftest(anchor)
|
||||
foundanchor = True
|
||||
if not foundanchor:
|
||||
|
@ -1069,13 +1068,8 @@ class Config:
|
|||
|
||||
if Version(minver) > Version(pytest.__version__):
|
||||
raise pytest.UsageError(
|
||||
"%s:%d: requires pytest-%s, actual pytest-%s'"
|
||||
% (
|
||||
self.inicfg.config.path,
|
||||
self.inicfg.lineof("minversion"),
|
||||
minver,
|
||||
pytest.__version__,
|
||||
)
|
||||
"%s: 'minversion' requires pytest-%s, actual pytest-%s'"
|
||||
% (self.inifile, minver, pytest.__version__,)
|
||||
)
|
||||
|
||||
def _validatekeys(self):
|
||||
|
@ -1123,7 +1117,7 @@ class Config:
|
|||
x.append(line) # modifies the cached list inline
|
||||
|
||||
def getini(self, name: str):
|
||||
""" return configuration value from an :ref:`ini file <inifiles>`. If the
|
||||
""" return configuration value from an :ref:`ini file <configfiles>`. If the
|
||||
specified name hasn't been registered through a prior
|
||||
:py:func:`parser.addini <_pytest.config.argparsing.Parser.addini>`
|
||||
call (usually from a plugin), a ValueError is raised. """
|
||||
|
@ -1138,8 +1132,8 @@ class Config:
|
|||
description, type, default = self._parser._inidict[name]
|
||||
except KeyError:
|
||||
raise ValueError("unknown configuration value: {!r}".format(name))
|
||||
value = self._get_override_ini_value(name)
|
||||
if value is None:
|
||||
override_value = self._get_override_ini_value(name)
|
||||
if override_value is None:
|
||||
try:
|
||||
value = self.inicfg[name]
|
||||
except KeyError:
|
||||
|
@ -1148,18 +1142,35 @@ class Config:
|
|||
if type is None:
|
||||
return ""
|
||||
return []
|
||||
else:
|
||||
value = override_value
|
||||
# coerce the values based on types
|
||||
# note: some coercions are only required if we are reading from .ini files, because
|
||||
# the file format doesn't contain type information, but when reading from toml we will
|
||||
# get either str or list of str values (see _parse_ini_config_from_pyproject_toml).
|
||||
# for example:
|
||||
#
|
||||
# ini:
|
||||
# a_line_list = "tests acceptance"
|
||||
# in this case, we need to split the string to obtain a list of strings
|
||||
#
|
||||
# toml:
|
||||
# a_line_list = ["tests", "acceptance"]
|
||||
# in this case, we already have a list ready to use
|
||||
#
|
||||
if type == "pathlist":
|
||||
dp = py.path.local(self.inicfg.config.path).dirpath()
|
||||
values = []
|
||||
for relpath in shlex.split(value):
|
||||
values.append(dp.join(relpath, abs=True))
|
||||
return values
|
||||
dp = py.path.local(self.inifile).dirpath()
|
||||
input_values = shlex.split(value) if isinstance(value, str) else value
|
||||
return [dp.join(x, abs=True) for x in input_values]
|
||||
elif type == "args":
|
||||
return shlex.split(value)
|
||||
return shlex.split(value) if isinstance(value, str) else value
|
||||
elif type == "linelist":
|
||||
if isinstance(value, str):
|
||||
return [t for t in map(lambda x: x.strip(), value.split("\n")) if t]
|
||||
else:
|
||||
return value
|
||||
elif type == "bool":
|
||||
return bool(_strtobool(value.strip()))
|
||||
return bool(_strtobool(str(value).strip()))
|
||||
else:
|
||||
assert type is None
|
||||
return value
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import os
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
from typing import Union
|
||||
|
||||
import iniconfig
|
||||
import py
|
||||
from iniconfig import IniConfig
|
||||
from iniconfig import ParseError
|
||||
|
||||
from .exceptions import UsageError
|
||||
from _pytest.compat import TYPE_CHECKING
|
||||
|
@ -17,52 +17,95 @@ if TYPE_CHECKING:
|
|||
from . import Config
|
||||
|
||||
|
||||
def exists(path, ignore=OSError):
|
||||
def _parse_ini_config(path: py.path.local) -> iniconfig.IniConfig:
|
||||
"""Parses the given generic '.ini' file using legacy IniConfig parser, returning
|
||||
the parsed object.
|
||||
|
||||
Raises UsageError if the file cannot be parsed.
|
||||
"""
|
||||
try:
|
||||
return path.check()
|
||||
except ignore:
|
||||
return False
|
||||
return iniconfig.IniConfig(path)
|
||||
except iniconfig.ParseError as exc:
|
||||
raise UsageError(str(exc))
|
||||
|
||||
|
||||
def getcfg(args, config=None):
|
||||
def load_config_dict_from_file(
|
||||
filepath: py.path.local,
|
||||
) -> Optional[Dict[str, Union[str, List[str]]]]:
|
||||
"""Loads pytest configuration from the given file path, if supported.
|
||||
|
||||
Return None if the file does not contain valid pytest configuration.
|
||||
"""
|
||||
Search the list of arguments for a valid ini-file for pytest,
|
||||
|
||||
# configuration from ini files are obtained from the [pytest] section, if present.
|
||||
if filepath.ext == ".ini":
|
||||
iniconfig = _parse_ini_config(filepath)
|
||||
|
||||
if "pytest" in iniconfig:
|
||||
return dict(iniconfig["pytest"].items())
|
||||
else:
|
||||
# "pytest.ini" files are always the source of configuration, even if empty
|
||||
if filepath.basename == "pytest.ini":
|
||||
return {}
|
||||
|
||||
# '.cfg' files are considered if they contain a "[tool:pytest]" section
|
||||
elif filepath.ext == ".cfg":
|
||||
iniconfig = _parse_ini_config(filepath)
|
||||
|
||||
if "tool:pytest" in iniconfig.sections:
|
||||
return dict(iniconfig["tool:pytest"].items())
|
||||
elif "pytest" in iniconfig.sections:
|
||||
# If a setup.cfg contains a "[pytest]" section, we raise a failure to indicate users that
|
||||
# plain "[pytest]" sections in setup.cfg files is no longer supported (#3086).
|
||||
fail(CFG_PYTEST_SECTION.format(filename="setup.cfg"), pytrace=False)
|
||||
|
||||
# '.toml' files are considered if they contain a [tool.pytest.ini_options] table
|
||||
elif filepath.ext == ".toml":
|
||||
import toml
|
||||
|
||||
config = toml.load(filepath)
|
||||
|
||||
result = config.get("tool", {}).get("pytest", {}).get("ini_options", None)
|
||||
if result is not None:
|
||||
# TOML supports richer data types than ini files (strings, arrays, floats, ints, etc),
|
||||
# however we need to convert all scalar values to str for compatibility with the rest
|
||||
# of the configuration system, which expects strings only.
|
||||
def make_scalar(v: object) -> Union[str, List[str]]:
|
||||
return v if isinstance(v, list) else str(v)
|
||||
|
||||
return {k: make_scalar(v) for k, v in result.items()}
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def locate_config(
|
||||
args: Iterable[Union[str, py.path.local]]
|
||||
) -> Tuple[
|
||||
Optional[py.path.local], Optional[py.path.local], Dict[str, Union[str, List[str]]],
|
||||
]:
|
||||
"""
|
||||
Search in the list of arguments for a valid ini-file for pytest,
|
||||
and return a tuple of (rootdir, inifile, cfg-dict).
|
||||
|
||||
note: config is optional and used only to issue warnings explicitly (#2891).
|
||||
"""
|
||||
inibasenames = ["pytest.ini", "tox.ini", "setup.cfg"]
|
||||
config_names = [
|
||||
"pytest.ini",
|
||||
"pyproject.toml",
|
||||
"tox.ini",
|
||||
"setup.cfg",
|
||||
]
|
||||
args = [x for x in args if not str(x).startswith("-")]
|
||||
if not args:
|
||||
args = [py.path.local()]
|
||||
for arg in args:
|
||||
arg = py.path.local(arg)
|
||||
for base in arg.parts(reverse=True):
|
||||
for inibasename in inibasenames:
|
||||
p = base.join(inibasename)
|
||||
if exists(p):
|
||||
try:
|
||||
iniconfig = IniConfig(p)
|
||||
except ParseError as exc:
|
||||
raise UsageError(str(exc))
|
||||
|
||||
if (
|
||||
inibasename == "setup.cfg"
|
||||
and "tool:pytest" in iniconfig.sections
|
||||
):
|
||||
return base, p, iniconfig["tool:pytest"]
|
||||
elif "pytest" in iniconfig.sections:
|
||||
if inibasename == "setup.cfg" and config is not None:
|
||||
|
||||
fail(
|
||||
CFG_PYTEST_SECTION.format(filename=inibasename),
|
||||
pytrace=False,
|
||||
)
|
||||
return base, p, iniconfig["pytest"]
|
||||
elif inibasename == "pytest.ini":
|
||||
# allowed to be empty
|
||||
return base, p, {}
|
||||
return None, None, None
|
||||
for config_name in config_names:
|
||||
p = base.join(config_name)
|
||||
if p.isfile():
|
||||
ini_config = load_config_dict_from_file(p)
|
||||
if ini_config is not None:
|
||||
return base, p, ini_config
|
||||
return None, None, {}
|
||||
|
||||
|
||||
def get_common_ancestor(paths: Iterable[py.path.local]) -> py.path.local:
|
||||
|
@ -118,29 +161,16 @@ def determine_setup(
|
|||
args: List[str],
|
||||
rootdir_cmd_arg: Optional[str] = None,
|
||||
config: Optional["Config"] = None,
|
||||
) -> Tuple[py.path.local, Optional[str], Any]:
|
||||
) -> Tuple[py.path.local, Optional[str], Dict[str, Union[str, List[str]]]]:
|
||||
rootdir = None
|
||||
dirs = get_dirs_from_args(args)
|
||||
if inifile:
|
||||
iniconfig = IniConfig(inifile)
|
||||
is_cfg_file = str(inifile).endswith(".cfg")
|
||||
sections = ["tool:pytest", "pytest"] if is_cfg_file else ["pytest"]
|
||||
for section in sections:
|
||||
try:
|
||||
inicfg = iniconfig[
|
||||
section
|
||||
] # type: Optional[py.iniconfig._SectionWrapper]
|
||||
if is_cfg_file and section == "pytest" and config is not None:
|
||||
fail(
|
||||
CFG_PYTEST_SECTION.format(filename=str(inifile)), pytrace=False
|
||||
)
|
||||
break
|
||||
except KeyError:
|
||||
inicfg = None
|
||||
inicfg = load_config_dict_from_file(py.path.local(inifile)) or {}
|
||||
if rootdir_cmd_arg is None:
|
||||
rootdir = get_common_ancestor(dirs)
|
||||
else:
|
||||
ancestor = get_common_ancestor(dirs)
|
||||
rootdir, inifile, inicfg = getcfg([ancestor], config=config)
|
||||
rootdir, inifile, inicfg = locate_config([ancestor])
|
||||
if rootdir is None and rootdir_cmd_arg is None:
|
||||
for possible_rootdir in ancestor.parts(reverse=True):
|
||||
if possible_rootdir.join("setup.py").exists():
|
||||
|
@ -148,7 +178,7 @@ def determine_setup(
|
|||
break
|
||||
else:
|
||||
if dirs != [ancestor]:
|
||||
rootdir, inifile, inicfg = getcfg(dirs, config=config)
|
||||
rootdir, inifile, inicfg = locate_config(dirs)
|
||||
if rootdir is None:
|
||||
if config is not None:
|
||||
cwd = config.invocation_dir
|
||||
|
|
|
@ -688,6 +688,13 @@ class Testdir:
|
|||
p = self.makeini(source)
|
||||
return IniConfig(p)["pytest"]
|
||||
|
||||
def makepyprojecttoml(self, source):
|
||||
"""Write a pyproject.toml file with 'source' as contents.
|
||||
|
||||
.. versionadded:: 6.0
|
||||
"""
|
||||
return self.makefile(".toml", pyproject=source)
|
||||
|
||||
def makepyfile(self, *args, **kwargs):
|
||||
r"""Shortcut for .makefile() with a .py extension.
|
||||
Defaults to the test name with a '.py' extension, e.g test_foobar.py, overwriting
|
||||
|
|
|
@ -691,7 +691,7 @@ class TerminalReporter:
|
|||
line = "rootdir: %s" % config.rootdir
|
||||
|
||||
if config.inifile:
|
||||
line += ", inifile: " + config.rootdir.bestrelpath(config.inifile)
|
||||
line += ", configfile: " + config.rootdir.bestrelpath(config.inifile)
|
||||
|
||||
testpaths = config.getini("testpaths")
|
||||
if testpaths and config.args == testpaths:
|
||||
|
|
|
@ -18,7 +18,7 @@ from _pytest.config import ExitCode
|
|||
from _pytest.config.exceptions import UsageError
|
||||
from _pytest.config.findpaths import determine_setup
|
||||
from _pytest.config.findpaths import get_common_ancestor
|
||||
from _pytest.config.findpaths import getcfg
|
||||
from _pytest.config.findpaths import locate_config
|
||||
from _pytest.pathlib import Path
|
||||
|
||||
|
||||
|
@ -39,14 +39,14 @@ class TestParseIni:
|
|||
)
|
||||
)
|
||||
)
|
||||
_, _, cfg = getcfg([sub])
|
||||
_, _, cfg = locate_config([sub])
|
||||
assert cfg["name"] == "value"
|
||||
config = testdir.parseconfigure(sub)
|
||||
assert config.inicfg["name"] == "value"
|
||||
|
||||
def test_getcfg_empty_path(self):
|
||||
"""correctly handle zero length arguments (a la pytest '')"""
|
||||
getcfg([""])
|
||||
locate_config([""])
|
||||
|
||||
def test_setupcfg_uses_toolpytest_with_pytest(self, testdir):
|
||||
p1 = testdir.makepyfile("def test(): pass")
|
||||
|
@ -61,7 +61,7 @@ class TestParseIni:
|
|||
% p1.basename,
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines(["*, inifile: setup.cfg, *", "* 1 passed in *"])
|
||||
result.stdout.fnmatch_lines(["*, configfile: setup.cfg, *", "* 1 passed in *"])
|
||||
assert result.ret == 0
|
||||
|
||||
def test_append_parse_args(self, testdir, tmpdir, monkeypatch):
|
||||
|
@ -85,12 +85,14 @@ class TestParseIni:
|
|||
".ini",
|
||||
tox="""
|
||||
[pytest]
|
||||
minversion=9.0
|
||||
minversion=999.0
|
||||
""",
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
assert result.ret != 0
|
||||
result.stderr.fnmatch_lines(["*tox.ini:2*requires*9.0*actual*"])
|
||||
result.stderr.fnmatch_lines(
|
||||
["*tox.ini: 'minversion' requires pytest-999.0, actual pytest-*"]
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"section, name",
|
||||
|
@ -110,6 +112,16 @@ class TestParseIni:
|
|||
config = testdir.parseconfig()
|
||||
assert config.getini("minversion") == "1.0"
|
||||
|
||||
def test_pyproject_toml(self, testdir):
|
||||
testdir.makepyprojecttoml(
|
||||
"""
|
||||
[tool.pytest.ini_options]
|
||||
minversion = "1.0"
|
||||
"""
|
||||
)
|
||||
config = testdir.parseconfig()
|
||||
assert config.getini("minversion") == "1.0"
|
||||
|
||||
def test_toxini_before_lower_pytestini(self, testdir):
|
||||
sub = testdir.tmpdir.mkdir("sub")
|
||||
sub.join("tox.ini").write(
|
||||
|
@ -251,6 +263,18 @@ class TestConfigCmdlineParsing:
|
|||
config = testdir.parseconfig("-c", "custom_tool_pytest_section.cfg")
|
||||
assert config.getini("custom") == "1"
|
||||
|
||||
testdir.makefile(
|
||||
".toml",
|
||||
custom="""
|
||||
[tool.pytest.ini_options]
|
||||
custom = 1
|
||||
value = [
|
||||
] # this is here on purpose, as it makes this an invalid '.ini' file
|
||||
""",
|
||||
)
|
||||
config = testdir.parseconfig("-c", "custom.toml")
|
||||
assert config.getini("custom") == "1"
|
||||
|
||||
def test_absolute_win32_path(self, testdir):
|
||||
temp_ini_file = testdir.makefile(
|
||||
".ini",
|
||||
|
@ -350,7 +374,7 @@ class TestConfigAPI:
|
|||
assert val == "hello"
|
||||
pytest.raises(ValueError, config.getini, "other")
|
||||
|
||||
def test_addini_pathlist(self, testdir):
|
||||
def make_conftest_for_pathlist(self, testdir):
|
||||
testdir.makeconftest(
|
||||
"""
|
||||
def pytest_addoption(parser):
|
||||
|
@ -358,20 +382,36 @@ class TestConfigAPI:
|
|||
parser.addini("abc", "abc value")
|
||||
"""
|
||||
)
|
||||
|
||||
def test_addini_pathlist_ini_files(self, testdir):
|
||||
self.make_conftest_for_pathlist(testdir)
|
||||
p = testdir.makeini(
|
||||
"""
|
||||
[pytest]
|
||||
paths=hello world/sub.py
|
||||
"""
|
||||
)
|
||||
self.check_config_pathlist(testdir, p)
|
||||
|
||||
def test_addini_pathlist_pyproject_toml(self, testdir):
|
||||
self.make_conftest_for_pathlist(testdir)
|
||||
p = testdir.makepyprojecttoml(
|
||||
"""
|
||||
[tool.pytest.ini_options]
|
||||
paths=["hello", "world/sub.py"]
|
||||
"""
|
||||
)
|
||||
self.check_config_pathlist(testdir, p)
|
||||
|
||||
def check_config_pathlist(self, testdir, config_path):
|
||||
config = testdir.parseconfig()
|
||||
values = config.getini("paths")
|
||||
assert len(values) == 2
|
||||
assert values[0] == p.dirpath("hello")
|
||||
assert values[1] == p.dirpath("world/sub.py")
|
||||
assert values[0] == config_path.dirpath("hello")
|
||||
assert values[1] == config_path.dirpath("world/sub.py")
|
||||
pytest.raises(ValueError, config.getini, "other")
|
||||
|
||||
def test_addini_args(self, testdir):
|
||||
def make_conftest_for_args(self, testdir):
|
||||
testdir.makeconftest(
|
||||
"""
|
||||
def pytest_addoption(parser):
|
||||
|
@ -379,20 +419,35 @@ class TestConfigAPI:
|
|||
parser.addini("a2", "", "args", default="1 2 3".split())
|
||||
"""
|
||||
)
|
||||
|
||||
def test_addini_args_ini_files(self, testdir):
|
||||
self.make_conftest_for_args(testdir)
|
||||
testdir.makeini(
|
||||
"""
|
||||
[pytest]
|
||||
args=123 "123 hello" "this"
|
||||
"""
|
||||
)
|
||||
self.check_config_args(testdir)
|
||||
|
||||
def test_addini_args_pyproject_toml(self, testdir):
|
||||
self.make_conftest_for_args(testdir)
|
||||
testdir.makepyprojecttoml(
|
||||
"""
|
||||
[tool.pytest.ini_options]
|
||||
args = ["123", "123 hello", "this"]
|
||||
"""
|
||||
)
|
||||
self.check_config_args(testdir)
|
||||
|
||||
def check_config_args(self, testdir):
|
||||
config = testdir.parseconfig()
|
||||
values = config.getini("args")
|
||||
assert len(values) == 3
|
||||
assert values == ["123", "123 hello", "this"]
|
||||
values = config.getini("a2")
|
||||
assert values == list("123")
|
||||
|
||||
def test_addini_linelist(self, testdir):
|
||||
def make_conftest_for_linelist(self, testdir):
|
||||
testdir.makeconftest(
|
||||
"""
|
||||
def pytest_addoption(parser):
|
||||
|
@ -400,6 +455,9 @@ class TestConfigAPI:
|
|||
parser.addini("a2", "", "linelist")
|
||||
"""
|
||||
)
|
||||
|
||||
def test_addini_linelist_ini_files(self, testdir):
|
||||
self.make_conftest_for_linelist(testdir)
|
||||
testdir.makeini(
|
||||
"""
|
||||
[pytest]
|
||||
|
@ -407,6 +465,19 @@ class TestConfigAPI:
|
|||
second line
|
||||
"""
|
||||
)
|
||||
self.check_config_linelist(testdir)
|
||||
|
||||
def test_addini_linelist_pprojecttoml(self, testdir):
|
||||
self.make_conftest_for_linelist(testdir)
|
||||
testdir.makepyprojecttoml(
|
||||
"""
|
||||
[tool.pytest.ini_options]
|
||||
xy = ["123 345", "second line"]
|
||||
"""
|
||||
)
|
||||
self.check_config_linelist(testdir)
|
||||
|
||||
def check_config_linelist(self, testdir):
|
||||
config = testdir.parseconfig()
|
||||
values = config.getini("xy")
|
||||
assert len(values) == 2
|
||||
|
@ -832,7 +903,6 @@ def test_consider_args_after_options_for_rootdir(testdir, args):
|
|||
result.stdout.fnmatch_lines(["*rootdir: *myroot"])
|
||||
|
||||
|
||||
@pytest.mark.skipif("sys.platform == 'win32'")
|
||||
def test_toolongargs_issue224(testdir):
|
||||
result = testdir.runpytest("-m", "hello" * 500)
|
||||
assert result.ret == ExitCode.NO_TESTS_COLLECTED
|
||||
|
@ -964,10 +1034,20 @@ class TestRootdir:
|
|||
assert get_common_ancestor([no_path]) == tmpdir
|
||||
assert get_common_ancestor([no_path.join("a")]) == tmpdir
|
||||
|
||||
@pytest.mark.parametrize("name", "setup.cfg tox.ini pytest.ini".split())
|
||||
def test_with_ini(self, tmpdir: py.path.local, name: str) -> None:
|
||||
@pytest.mark.parametrize(
|
||||
"name, contents",
|
||||
[
|
||||
pytest.param("pytest.ini", "[pytest]\nx=10", id="pytest.ini"),
|
||||
pytest.param(
|
||||
"pyproject.toml", "[tool.pytest.ini_options]\nx=10", id="pyproject.toml"
|
||||
),
|
||||
pytest.param("tox.ini", "[pytest]\nx=10", id="tox.ini"),
|
||||
pytest.param("setup.cfg", "[tool:pytest]\nx=10", id="setup.cfg"),
|
||||
],
|
||||
)
|
||||
def test_with_ini(self, tmpdir: py.path.local, name: str, contents: str) -> None:
|
||||
inifile = tmpdir.join(name)
|
||||
inifile.write("[pytest]\n" if name != "setup.cfg" else "[tool:pytest]\n")
|
||||
inifile.write(contents)
|
||||
|
||||
a = tmpdir.mkdir("a")
|
||||
b = a.mkdir("b")
|
||||
|
@ -975,9 +1055,10 @@ class TestRootdir:
|
|||
rootdir, parsed_inifile, _ = determine_setup(None, args)
|
||||
assert rootdir == tmpdir
|
||||
assert parsed_inifile == inifile
|
||||
rootdir, parsed_inifile, _ = determine_setup(None, [str(b), str(a)])
|
||||
rootdir, parsed_inifile, ini_config = determine_setup(None, [str(b), str(a)])
|
||||
assert rootdir == tmpdir
|
||||
assert parsed_inifile == inifile
|
||||
assert ini_config == {"x": "10"}
|
||||
|
||||
@pytest.mark.parametrize("name", "setup.cfg tox.ini".split())
|
||||
def test_pytestini_overrides_empty_other(self, tmpdir: py.path.local, name) -> None:
|
||||
|
@ -1004,10 +1085,26 @@ class TestRootdir:
|
|||
assert inifile is None
|
||||
assert inicfg == {}
|
||||
|
||||
def test_with_specific_inifile(self, tmpdir: py.path.local) -> None:
|
||||
inifile = tmpdir.ensure("pytest.ini")
|
||||
rootdir, _, _ = determine_setup(str(inifile), [str(tmpdir)])
|
||||
@pytest.mark.parametrize(
|
||||
"name, contents",
|
||||
[
|
||||
# pytest.param("pytest.ini", "[pytest]\nx=10", id="pytest.ini"),
|
||||
pytest.param(
|
||||
"pyproject.toml", "[tool.pytest.ini_options]\nx=10", id="pyproject.toml"
|
||||
),
|
||||
# pytest.param("tox.ini", "[pytest]\nx=10", id="tox.ini"),
|
||||
# pytest.param("setup.cfg", "[tool:pytest]\nx=10", id="setup.cfg"),
|
||||
],
|
||||
)
|
||||
def test_with_specific_inifile(
|
||||
self, tmpdir: py.path.local, name: str, contents: str
|
||||
) -> None:
|
||||
p = tmpdir.ensure(name)
|
||||
p.write(contents)
|
||||
rootdir, inifile, ini_config = determine_setup(str(p), [str(tmpdir)])
|
||||
assert rootdir == tmpdir
|
||||
assert inifile == p
|
||||
assert ini_config == {"x": "10"}
|
||||
|
||||
def test_with_arg_outside_cwd_without_inifile(self, tmpdir, monkeypatch) -> None:
|
||||
monkeypatch.chdir(str(tmpdir))
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
from textwrap import dedent
|
||||
|
||||
import py
|
||||
|
||||
import pytest
|
||||
from _pytest.config.findpaths import get_common_ancestor
|
||||
from _pytest.config.findpaths import load_config_dict_from_file
|
||||
|
||||
|
||||
class TestLoadConfigDictFromFile:
|
||||
def test_empty_pytest_ini(self, tmpdir):
|
||||
"""pytest.ini files are always considered for configuration, even if empty"""
|
||||
fn = tmpdir.join("pytest.ini")
|
||||
fn.write("")
|
||||
assert load_config_dict_from_file(fn) == {}
|
||||
|
||||
def test_pytest_ini(self, tmpdir):
|
||||
"""[pytest] section in pytest.ini files is read correctly"""
|
||||
fn = tmpdir.join("pytest.ini")
|
||||
fn.write("[pytest]\nx=1")
|
||||
assert load_config_dict_from_file(fn) == {"x": "1"}
|
||||
|
||||
def test_custom_ini(self, tmpdir):
|
||||
"""[pytest] section in any .ini file is read correctly"""
|
||||
fn = tmpdir.join("custom.ini")
|
||||
fn.write("[pytest]\nx=1")
|
||||
assert load_config_dict_from_file(fn) == {"x": "1"}
|
||||
|
||||
def test_custom_ini_without_section(self, tmpdir):
|
||||
"""Custom .ini files without [pytest] section are not considered for configuration"""
|
||||
fn = tmpdir.join("custom.ini")
|
||||
fn.write("[custom]")
|
||||
assert load_config_dict_from_file(fn) is None
|
||||
|
||||
def test_custom_cfg_file(self, tmpdir):
|
||||
"""Custom .cfg files without [tool:pytest] section are not considered for configuration"""
|
||||
fn = tmpdir.join("custom.cfg")
|
||||
fn.write("[custom]")
|
||||
assert load_config_dict_from_file(fn) is None
|
||||
|
||||
def test_valid_cfg_file(self, tmpdir):
|
||||
"""Custom .cfg files with [tool:pytest] section are read correctly"""
|
||||
fn = tmpdir.join("custom.cfg")
|
||||
fn.write("[tool:pytest]\nx=1")
|
||||
assert load_config_dict_from_file(fn) == {"x": "1"}
|
||||
|
||||
def test_unsupported_pytest_section_in_cfg_file(self, tmpdir):
|
||||
""".cfg files with [pytest] section are no longer supported and should fail to alert users"""
|
||||
fn = tmpdir.join("custom.cfg")
|
||||
fn.write("[pytest]")
|
||||
with pytest.raises(pytest.fail.Exception):
|
||||
load_config_dict_from_file(fn)
|
||||
|
||||
def test_invalid_toml_file(self, tmpdir):
|
||||
""".toml files without [tool.pytest.ini_options] are not considered for configuration."""
|
||||
fn = tmpdir.join("myconfig.toml")
|
||||
fn.write(
|
||||
dedent(
|
||||
"""
|
||||
[build_system]
|
||||
x = 1
|
||||
"""
|
||||
)
|
||||
)
|
||||
assert load_config_dict_from_file(fn) is None
|
||||
|
||||
def test_valid_toml_file(self, tmpdir):
|
||||
""".toml files with [tool.pytest.ini_options] are read correctly, including changing
|
||||
data types to str/list for compatibility with other configuration options."""
|
||||
fn = tmpdir.join("myconfig.toml")
|
||||
fn.write(
|
||||
dedent(
|
||||
"""
|
||||
[tool.pytest.ini_options]
|
||||
x = 1
|
||||
y = 20.0
|
||||
values = ["tests", "integration"]
|
||||
name = "foo"
|
||||
"""
|
||||
)
|
||||
)
|
||||
assert load_config_dict_from_file(fn) == {
|
||||
"x": "1",
|
||||
"y": "20.0",
|
||||
"values": ["tests", "integration"],
|
||||
"name": "foo",
|
||||
}
|
||||
|
||||
|
||||
class TestCommonAncestor:
|
||||
def test_has_ancestor(self, tmpdir):
|
||||
fn1 = tmpdir.join("foo/bar/test_1.py").ensure(file=1)
|
||||
fn2 = tmpdir.join("foo/zaz/test_2.py").ensure(file=1)
|
||||
assert get_common_ancestor([fn1, fn2]) == tmpdir.join("foo")
|
||||
assert get_common_ancestor([py.path.local(fn1.dirname), fn2]) == tmpdir.join(
|
||||
"foo"
|
||||
)
|
||||
assert get_common_ancestor(
|
||||
[py.path.local(fn1.dirname), py.path.local(fn2.dirname)]
|
||||
) == tmpdir.join("foo")
|
||||
assert get_common_ancestor([fn1, py.path.local(fn2.dirname)]) == tmpdir.join(
|
||||
"foo"
|
||||
)
|
||||
|
||||
def test_single_dir(self, tmpdir):
|
||||
assert get_common_ancestor([tmpdir]) == tmpdir
|
||||
|
||||
def test_single_file(self, tmpdir):
|
||||
fn = tmpdir.join("foo.py").ensure(file=1)
|
||||
assert get_common_ancestor([fn]) == tmpdir
|
|
@ -706,10 +706,10 @@ class TestTerminalFunctional:
|
|||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines(["rootdir: *test_header0"])
|
||||
|
||||
# with inifile
|
||||
# with configfile
|
||||
testdir.makeini("""[pytest]""")
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines(["rootdir: *test_header0, inifile: tox.ini"])
|
||||
result.stdout.fnmatch_lines(["rootdir: *test_header0, configfile: tox.ini"])
|
||||
|
||||
# with testpaths option, and not passing anything in the command-line
|
||||
testdir.makeini(
|
||||
|
@ -720,12 +720,12 @@ class TestTerminalFunctional:
|
|||
)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines(
|
||||
["rootdir: *test_header0, inifile: tox.ini, testpaths: tests, gui"]
|
||||
["rootdir: *test_header0, configfile: tox.ini, testpaths: tests, gui"]
|
||||
)
|
||||
|
||||
# with testpaths option, passing directory in command-line: do not show testpaths then
|
||||
result = testdir.runpytest("tests")
|
||||
result.stdout.fnmatch_lines(["rootdir: *test_header0, inifile: tox.ini"])
|
||||
result.stdout.fnmatch_lines(["rootdir: *test_header0, configfile: tox.ini"])
|
||||
|
||||
def test_showlocals(self, testdir):
|
||||
p1 = testdir.makepyfile(
|
||||
|
|
42
tox.ini
42
tox.ini
|
@ -152,48 +152,6 @@ deps =
|
|||
pypandoc
|
||||
commands = python scripts/publish-gh-release-notes.py {posargs}
|
||||
|
||||
|
||||
[pytest]
|
||||
minversion = 2.0
|
||||
addopts = -rfEX -p pytester --strict-markers
|
||||
rsyncdirs = tox.ini doc src testing
|
||||
python_files = test_*.py *_test.py testing/*/*.py
|
||||
python_classes = Test Acceptance
|
||||
python_functions = test
|
||||
# NOTE: "doc" is not included here, but gets tested explicitly via "doctesting".
|
||||
testpaths = testing
|
||||
norecursedirs = testing/example_scripts
|
||||
xfail_strict=true
|
||||
filterwarnings =
|
||||
error
|
||||
default:Using or importing the ABCs:DeprecationWarning:unittest2.*
|
||||
default:the imp module is deprecated in favour of importlib:DeprecationWarning:nose.*
|
||||
ignore:Module already imported so cannot be rewritten:pytest.PytestWarning
|
||||
# produced by python3.6/site.py itself (3.6.7 on Travis, could not trigger it with 3.6.8).
|
||||
ignore:.*U.*mode is deprecated:DeprecationWarning:(?!(pytest|_pytest))
|
||||
# produced by pytest-xdist
|
||||
ignore:.*type argument to addoption.*:DeprecationWarning
|
||||
# produced by python >=3.5 on execnet (pytest-xdist)
|
||||
ignore:.*inspect.getargspec.*deprecated, use inspect.signature.*:DeprecationWarning
|
||||
# pytest's own futurewarnings
|
||||
ignore::pytest.PytestExperimentalApiWarning
|
||||
# Do not cause SyntaxError for invalid escape sequences in py37.
|
||||
# Those are caught/handled by pyupgrade, and not easy to filter with the
|
||||
# module being the filename (with .py removed).
|
||||
default:invalid escape sequence:DeprecationWarning
|
||||
# ignore use of unregistered marks, because we use many to test the implementation
|
||||
ignore::_pytest.warning_types.PytestUnknownMarkWarning
|
||||
pytester_example_dir = testing/example_scripts
|
||||
markers =
|
||||
# dummy markers for testing
|
||||
foo
|
||||
bar
|
||||
baz
|
||||
# conftest.py reorders tests moving slow ones to the end of the list
|
||||
slow
|
||||
# experimental mark for all tests using pexpect
|
||||
uses_pexpect
|
||||
|
||||
[flake8]
|
||||
max-line-length = 120
|
||||
extend-ignore = E203
|
||||
|
|
Loading…
Reference in New Issue