Add pyproject.toml support (#7247)
This commit is contained in:
parent
ceac6736d7
commit
c17d50829f
|
@ -29,6 +29,7 @@ doc/*/_changelog_towncrier_draft.rst
|
||||||
build/
|
build/
|
||||||
dist/
|
dist/
|
||||||
*.egg-info
|
*.egg-info
|
||||||
|
htmlcov/
|
||||||
issue/
|
issue/
|
||||||
env/
|
env/
|
||||||
.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
|
This will display command line and configuration file settings
|
||||||
which were registered by installed plugins.
|
which were registered by installed plugins.
|
||||||
|
|
||||||
.. _rootdir:
|
.. _`config file formats`:
|
||||||
.. _inifiles:
|
|
||||||
|
|
||||||
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
|
pytest determines a ``rootdir`` for each test run which depends on
|
||||||
the command line arguments (specified test files, paths) and 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.
|
printed as part of the pytest header during startup.
|
||||||
|
|
||||||
Here's a summary what ``pytest`` uses ``rootdir`` for:
|
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``:
|
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
|
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.
|
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
|
- 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 ini-file and its
|
directory and upwards. If one is matched, it becomes the ``configfile`` and its
|
||||||
directory becomes the rootdir.
|
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``.
|
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
|
``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
|
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
|
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
|
Files will only be matched for configuration if:
|
||||||
``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.
|
|
||||||
|
|
||||||
Note that an existing ``pytest.ini`` file will always be considered a match,
|
* ``pytest.ini``: will always match and take precedence, even if empty.
|
||||||
whereas ``tox.ini`` and ``setup.cfg`` will only match if they contain a
|
* ``pyproject.toml``: contains a ``[tool.pytest.ini_options]`` table.
|
||||||
``[pytest]`` or ``[tool:pytest]`` section, respectively. Options from multiple ini-files candidates are never
|
* ``tox.ini``: contains a ``[pytest]`` section.
|
||||||
merged - the first one wins (``pytest.ini`` always wins, even if it does not
|
* ``setup.cfg``: contains a ``[tool:pytest]`` section.
|
||||||
contain a ``[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.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
|
addresses ("nodeids") and can be used also by plugins for storing
|
||||||
per-testrun information.
|
per-testrun information.
|
||||||
|
|
||||||
|
@ -100,75 +196,38 @@ Example:
|
||||||
pytest path/to/testdir path/other/
|
pytest path/to/testdir path/other/
|
||||||
|
|
||||||
will determine the common ancestor as ``path`` and then
|
will determine the common ancestor as ``path`` and then
|
||||||
check for ini-files as follows:
|
check for configuration files as follows:
|
||||||
|
|
||||||
.. code-block:: text
|
.. code-block:: text
|
||||||
|
|
||||||
# first look for pytest.ini files
|
# first look for pytest.ini files
|
||||||
path/pytest.ini
|
path/pytest.ini
|
||||||
path/tox.ini # must also contain [pytest] section to match
|
path/pyproject.toml # must contain a [tool.pytest.ini_options] table to match
|
||||||
path/setup.cfg # must also contain [tool:pytest] section to match
|
path/tox.ini # must contain [pytest] section to match
|
||||||
|
path/setup.cfg # must contain [tool:pytest] section to match
|
||||||
pytest.ini
|
pytest.ini
|
||||||
... # all the way down to the root
|
... # all the way up to the root
|
||||||
|
|
||||||
# now look for setup.py
|
# now look for setup.py
|
||||||
path/setup.py
|
path/setup.py
|
||||||
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`:
|
.. _`how to change command line options defaults`:
|
||||||
.. _`adding default options`:
|
.. _`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
|
Builtin configuration file options
|
||||||
----------------------------------------------
|
----------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -115,15 +115,13 @@ Changing naming conventions
|
||||||
|
|
||||||
You can configure different naming conventions by setting
|
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.
|
:confval:`python_functions` in your :ref:`configuration file <config file formats>`.
|
||||||
Here is an example:
|
Here is an example:
|
||||||
|
|
||||||
.. code-block:: ini
|
.. code-block:: ini
|
||||||
|
|
||||||
# content of pytest.ini
|
# content of pytest.ini
|
||||||
# Example 1: have pytest look for "check" instead of "test"
|
# 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]
|
[pytest]
|
||||||
python_files = check_*.py
|
python_files = check_*.py
|
||||||
python_classes = Check
|
python_classes = Check
|
||||||
|
@ -165,8 +163,7 @@ You can check for multiple glob patterns by adding a space between the patterns:
|
||||||
.. code-block:: ini
|
.. code-block:: ini
|
||||||
|
|
||||||
# Example 2: have pytest look for files with "test" and "example"
|
# Example 2: have pytest look for files with "test" and "example"
|
||||||
# content of pytest.ini, tox.ini, or setup.cfg file (replace "pytest"
|
# content of pytest.ini
|
||||||
# with "tool:pytest" for setup.cfg)
|
|
||||||
[pytest]
|
[pytest]
|
||||||
python_files = test_*.py example_*.py
|
python_files = test_*.py example_*.py
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,50 @@
|
||||||
Basic patterns and examples
|
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:
|
.. _request example:
|
||||||
|
|
||||||
Pass different values to a test function, depending on command line options
|
Pass different values to a test function, depending on command line options
|
||||||
|
|
|
@ -1019,17 +1019,17 @@ UsageError
|
||||||
Configuration Options
|
Configuration Options
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
Here is a list of builtin configuration options that may be written in a ``pytest.ini``, ``tox.ini`` or ``setup.cfg``
|
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. All options must be under a ``[pytest]`` section
|
file, usually located at the root of your repository. To see each file format in details, see
|
||||||
(``[tool:pytest]`` for ``setup.cfg`` files).
|
:ref:`config file formats`.
|
||||||
|
|
||||||
.. warning::
|
.. 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
|
files use a different parser than ``pytest.ini`` and ``tox.ini`` which might cause hard to track
|
||||||
down problems.
|
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::
|
passed multiple times. The expected format is ``name=value``. For example::
|
||||||
|
|
||||||
pytest -o console_output_style=classic -o cache_dir=/tmp/mycache
|
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
|
.. confval:: cache_dir
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Sets a directory where stores content of cache plugin. Default directory is
|
Sets a directory where stores content of cache plugin. Default directory is
|
||||||
``.pytest_cache`` which is created in :ref:`rootdir <rootdir>`. Directory may be
|
``.pytest_cache`` which is created in :ref:`rootdir <rootdir>`. Directory may be
|
||||||
relative or absolute path. If setting relative path, then directory is created
|
relative or absolute path. If setting relative path, then directory is created
|
||||||
|
|
|
@ -7,6 +7,49 @@ requires = [
|
||||||
]
|
]
|
||||||
build-backend = "setuptools.build_meta"
|
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]
|
[tool.towncrier]
|
||||||
package = "pytest"
|
package = "pytest"
|
||||||
package_dir = "src"
|
package_dir = "src"
|
||||||
|
|
|
@ -46,6 +46,7 @@ install_requires =
|
||||||
packaging
|
packaging
|
||||||
pluggy>=0.12,<1.0
|
pluggy>=0.12,<1.0
|
||||||
py>=1.5.0
|
py>=1.5.0
|
||||||
|
toml
|
||||||
atomicwrites>=1.0;sys_platform=="win32"
|
atomicwrites>=1.0;sys_platform=="win32"
|
||||||
colorama;sys_platform=="win32"
|
colorama;sys_platform=="win32"
|
||||||
importlib-metadata>=0.12;python_version<"3.8"
|
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 PrintHelp
|
||||||
from .exceptions import UsageError
|
from .exceptions import UsageError
|
||||||
from .findpaths import determine_setup
|
from .findpaths import determine_setup
|
||||||
from .findpaths import exists
|
|
||||||
from _pytest._code import ExceptionInfo
|
from _pytest._code import ExceptionInfo
|
||||||
from _pytest._code import filter_traceback
|
from _pytest._code import filter_traceback
|
||||||
from _pytest._io import TerminalWriter
|
from _pytest._io import TerminalWriter
|
||||||
|
@ -450,7 +449,7 @@ class PytestPluginManager(PluginManager):
|
||||||
if i != -1:
|
if i != -1:
|
||||||
path = path[:i]
|
path = path[:i]
|
||||||
anchor = current.join(path, abs=1)
|
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)
|
self._try_load_conftest(anchor)
|
||||||
foundanchor = True
|
foundanchor = True
|
||||||
if not foundanchor:
|
if not foundanchor:
|
||||||
|
@ -1069,13 +1068,8 @@ class Config:
|
||||||
|
|
||||||
if Version(minver) > Version(pytest.__version__):
|
if Version(minver) > Version(pytest.__version__):
|
||||||
raise pytest.UsageError(
|
raise pytest.UsageError(
|
||||||
"%s:%d: requires pytest-%s, actual pytest-%s'"
|
"%s: 'minversion' requires pytest-%s, actual pytest-%s'"
|
||||||
% (
|
% (self.inifile, minver, pytest.__version__,)
|
||||||
self.inicfg.config.path,
|
|
||||||
self.inicfg.lineof("minversion"),
|
|
||||||
minver,
|
|
||||||
pytest.__version__,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def _validatekeys(self):
|
def _validatekeys(self):
|
||||||
|
@ -1123,7 +1117,7 @@ class Config:
|
||||||
x.append(line) # modifies the cached list inline
|
x.append(line) # modifies the cached list inline
|
||||||
|
|
||||||
def getini(self, name: str):
|
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
|
specified name hasn't been registered through a prior
|
||||||
:py:func:`parser.addini <_pytest.config.argparsing.Parser.addini>`
|
:py:func:`parser.addini <_pytest.config.argparsing.Parser.addini>`
|
||||||
call (usually from a plugin), a ValueError is raised. """
|
call (usually from a plugin), a ValueError is raised. """
|
||||||
|
@ -1138,8 +1132,8 @@ class Config:
|
||||||
description, type, default = self._parser._inidict[name]
|
description, type, default = self._parser._inidict[name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise ValueError("unknown configuration value: {!r}".format(name))
|
raise ValueError("unknown configuration value: {!r}".format(name))
|
||||||
value = self._get_override_ini_value(name)
|
override_value = self._get_override_ini_value(name)
|
||||||
if value is None:
|
if override_value is None:
|
||||||
try:
|
try:
|
||||||
value = self.inicfg[name]
|
value = self.inicfg[name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -1148,18 +1142,35 @@ class Config:
|
||||||
if type is None:
|
if type is None:
|
||||||
return ""
|
return ""
|
||||||
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":
|
if type == "pathlist":
|
||||||
dp = py.path.local(self.inicfg.config.path).dirpath()
|
dp = py.path.local(self.inifile).dirpath()
|
||||||
values = []
|
input_values = shlex.split(value) if isinstance(value, str) else value
|
||||||
for relpath in shlex.split(value):
|
return [dp.join(x, abs=True) for x in input_values]
|
||||||
values.append(dp.join(relpath, abs=True))
|
|
||||||
return values
|
|
||||||
elif type == "args":
|
elif type == "args":
|
||||||
return shlex.split(value)
|
return shlex.split(value) if isinstance(value, str) else value
|
||||||
elif type == "linelist":
|
elif type == "linelist":
|
||||||
return [t for t in map(lambda x: x.strip(), value.split("\n")) if t]
|
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":
|
elif type == "bool":
|
||||||
return bool(_strtobool(value.strip()))
|
return bool(_strtobool(str(value).strip()))
|
||||||
else:
|
else:
|
||||||
assert type is None
|
assert type is None
|
||||||
return value
|
return value
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import os
|
import os
|
||||||
from typing import Any
|
from typing import Dict
|
||||||
from typing import Iterable
|
from typing import Iterable
|
||||||
from typing import List
|
from typing import List
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
import iniconfig
|
||||||
import py
|
import py
|
||||||
from iniconfig import IniConfig
|
|
||||||
from iniconfig import ParseError
|
|
||||||
|
|
||||||
from .exceptions import UsageError
|
from .exceptions import UsageError
|
||||||
from _pytest.compat import TYPE_CHECKING
|
from _pytest.compat import TYPE_CHECKING
|
||||||
|
@ -17,52 +17,95 @@ if TYPE_CHECKING:
|
||||||
from . import Config
|
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:
|
try:
|
||||||
return path.check()
|
return iniconfig.IniConfig(path)
|
||||||
except ignore:
|
except iniconfig.ParseError as exc:
|
||||||
return False
|
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).
|
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("-")]
|
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()]
|
||||||
for arg in args:
|
for arg in args:
|
||||||
arg = py.path.local(arg)
|
arg = py.path.local(arg)
|
||||||
for base in arg.parts(reverse=True):
|
for base in arg.parts(reverse=True):
|
||||||
for inibasename in inibasenames:
|
for config_name in config_names:
|
||||||
p = base.join(inibasename)
|
p = base.join(config_name)
|
||||||
if exists(p):
|
if p.isfile():
|
||||||
try:
|
ini_config = load_config_dict_from_file(p)
|
||||||
iniconfig = IniConfig(p)
|
if ini_config is not None:
|
||||||
except ParseError as exc:
|
return base, p, ini_config
|
||||||
raise UsageError(str(exc))
|
return None, None, {}
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def get_common_ancestor(paths: Iterable[py.path.local]) -> py.path.local:
|
def get_common_ancestor(paths: Iterable[py.path.local]) -> py.path.local:
|
||||||
|
@ -118,29 +161,16 @@ def determine_setup(
|
||||||
args: List[str],
|
args: List[str],
|
||||||
rootdir_cmd_arg: Optional[str] = None,
|
rootdir_cmd_arg: Optional[str] = None,
|
||||||
config: Optional["Config"] = 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)
|
dirs = get_dirs_from_args(args)
|
||||||
if inifile:
|
if inifile:
|
||||||
iniconfig = IniConfig(inifile)
|
inicfg = load_config_dict_from_file(py.path.local(inifile)) or {}
|
||||||
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
|
|
||||||
if rootdir_cmd_arg is None:
|
if rootdir_cmd_arg is None:
|
||||||
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([ancestor], config=config)
|
rootdir, inifile, inicfg = locate_config([ancestor])
|
||||||
if rootdir is None and rootdir_cmd_arg is None:
|
if rootdir is None and rootdir_cmd_arg is None:
|
||||||
for possible_rootdir in ancestor.parts(reverse=True):
|
for possible_rootdir in ancestor.parts(reverse=True):
|
||||||
if possible_rootdir.join("setup.py").exists():
|
if possible_rootdir.join("setup.py").exists():
|
||||||
|
@ -148,7 +178,7 @@ def determine_setup(
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
if dirs != [ancestor]:
|
if dirs != [ancestor]:
|
||||||
rootdir, inifile, inicfg = getcfg(dirs, config=config)
|
rootdir, inifile, inicfg = locate_config(dirs)
|
||||||
if rootdir is None:
|
if rootdir is None:
|
||||||
if config is not None:
|
if config is not None:
|
||||||
cwd = config.invocation_dir
|
cwd = config.invocation_dir
|
||||||
|
|
|
@ -688,6 +688,13 @@ class Testdir:
|
||||||
p = self.makeini(source)
|
p = self.makeini(source)
|
||||||
return IniConfig(p)["pytest"]
|
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):
|
def makepyfile(self, *args, **kwargs):
|
||||||
r"""Shortcut for .makefile() with a .py extension.
|
r"""Shortcut for .makefile() with a .py extension.
|
||||||
Defaults to the test name with a '.py' extension, e.g test_foobar.py, overwriting
|
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
|
line = "rootdir: %s" % config.rootdir
|
||||||
|
|
||||||
if config.inifile:
|
if config.inifile:
|
||||||
line += ", inifile: " + config.rootdir.bestrelpath(config.inifile)
|
line += ", configfile: " + config.rootdir.bestrelpath(config.inifile)
|
||||||
|
|
||||||
testpaths = config.getini("testpaths")
|
testpaths = config.getini("testpaths")
|
||||||
if testpaths and config.args == 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.exceptions import UsageError
|
||||||
from _pytest.config.findpaths import determine_setup
|
from _pytest.config.findpaths import determine_setup
|
||||||
from _pytest.config.findpaths import get_common_ancestor
|
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
|
from _pytest.pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
@ -39,14 +39,14 @@ class TestParseIni:
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
_, _, cfg = getcfg([sub])
|
_, _, cfg = locate_config([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):
|
def test_getcfg_empty_path(self):
|
||||||
"""correctly handle zero length arguments (a la pytest '')"""
|
"""correctly handle zero length arguments (a la pytest '')"""
|
||||||
getcfg([""])
|
locate_config([""])
|
||||||
|
|
||||||
def test_setupcfg_uses_toolpytest_with_pytest(self, testdir):
|
def test_setupcfg_uses_toolpytest_with_pytest(self, testdir):
|
||||||
p1 = testdir.makepyfile("def test(): pass")
|
p1 = testdir.makepyfile("def test(): pass")
|
||||||
|
@ -61,7 +61,7 @@ class TestParseIni:
|
||||||
% p1.basename,
|
% p1.basename,
|
||||||
)
|
)
|
||||||
result = testdir.runpytest()
|
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
|
assert result.ret == 0
|
||||||
|
|
||||||
def test_append_parse_args(self, testdir, tmpdir, monkeypatch):
|
def test_append_parse_args(self, testdir, tmpdir, monkeypatch):
|
||||||
|
@ -85,12 +85,14 @@ class TestParseIni:
|
||||||
".ini",
|
".ini",
|
||||||
tox="""
|
tox="""
|
||||||
[pytest]
|
[pytest]
|
||||||
minversion=9.0
|
minversion=999.0
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
result = testdir.runpytest()
|
result = testdir.runpytest()
|
||||||
assert result.ret != 0
|
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(
|
@pytest.mark.parametrize(
|
||||||
"section, name",
|
"section, name",
|
||||||
|
@ -110,6 +112,16 @@ class TestParseIni:
|
||||||
config = testdir.parseconfig()
|
config = testdir.parseconfig()
|
||||||
assert config.getini("minversion") == "1.0"
|
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):
|
def test_toxini_before_lower_pytestini(self, testdir):
|
||||||
sub = testdir.tmpdir.mkdir("sub")
|
sub = testdir.tmpdir.mkdir("sub")
|
||||||
sub.join("tox.ini").write(
|
sub.join("tox.ini").write(
|
||||||
|
@ -251,6 +263,18 @@ class TestConfigCmdlineParsing:
|
||||||
config = testdir.parseconfig("-c", "custom_tool_pytest_section.cfg")
|
config = testdir.parseconfig("-c", "custom_tool_pytest_section.cfg")
|
||||||
assert config.getini("custom") == "1"
|
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):
|
def test_absolute_win32_path(self, testdir):
|
||||||
temp_ini_file = testdir.makefile(
|
temp_ini_file = testdir.makefile(
|
||||||
".ini",
|
".ini",
|
||||||
|
@ -350,7 +374,7 @@ class TestConfigAPI:
|
||||||
assert val == "hello"
|
assert val == "hello"
|
||||||
pytest.raises(ValueError, config.getini, "other")
|
pytest.raises(ValueError, config.getini, "other")
|
||||||
|
|
||||||
def test_addini_pathlist(self, testdir):
|
def make_conftest_for_pathlist(self, testdir):
|
||||||
testdir.makeconftest(
|
testdir.makeconftest(
|
||||||
"""
|
"""
|
||||||
def pytest_addoption(parser):
|
def pytest_addoption(parser):
|
||||||
|
@ -358,20 +382,36 @@ class TestConfigAPI:
|
||||||
parser.addini("abc", "abc value")
|
parser.addini("abc", "abc value")
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_addini_pathlist_ini_files(self, testdir):
|
||||||
|
self.make_conftest_for_pathlist(testdir)
|
||||||
p = testdir.makeini(
|
p = testdir.makeini(
|
||||||
"""
|
"""
|
||||||
[pytest]
|
[pytest]
|
||||||
paths=hello world/sub.py
|
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()
|
config = testdir.parseconfig()
|
||||||
values = config.getini("paths")
|
values = config.getini("paths")
|
||||||
assert len(values) == 2
|
assert len(values) == 2
|
||||||
assert values[0] == p.dirpath("hello")
|
assert values[0] == config_path.dirpath("hello")
|
||||||
assert values[1] == p.dirpath("world/sub.py")
|
assert values[1] == config_path.dirpath("world/sub.py")
|
||||||
pytest.raises(ValueError, config.getini, "other")
|
pytest.raises(ValueError, config.getini, "other")
|
||||||
|
|
||||||
def test_addini_args(self, testdir):
|
def make_conftest_for_args(self, testdir):
|
||||||
testdir.makeconftest(
|
testdir.makeconftest(
|
||||||
"""
|
"""
|
||||||
def pytest_addoption(parser):
|
def pytest_addoption(parser):
|
||||||
|
@ -379,20 +419,35 @@ class TestConfigAPI:
|
||||||
parser.addini("a2", "", "args", default="1 2 3".split())
|
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(
|
testdir.makeini(
|
||||||
"""
|
"""
|
||||||
[pytest]
|
[pytest]
|
||||||
args=123 "123 hello" "this"
|
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()
|
config = testdir.parseconfig()
|
||||||
values = config.getini("args")
|
values = config.getini("args")
|
||||||
assert len(values) == 3
|
|
||||||
assert values == ["123", "123 hello", "this"]
|
assert values == ["123", "123 hello", "this"]
|
||||||
values = config.getini("a2")
|
values = config.getini("a2")
|
||||||
assert values == list("123")
|
assert values == list("123")
|
||||||
|
|
||||||
def test_addini_linelist(self, testdir):
|
def make_conftest_for_linelist(self, testdir):
|
||||||
testdir.makeconftest(
|
testdir.makeconftest(
|
||||||
"""
|
"""
|
||||||
def pytest_addoption(parser):
|
def pytest_addoption(parser):
|
||||||
|
@ -400,6 +455,9 @@ class TestConfigAPI:
|
||||||
parser.addini("a2", "", "linelist")
|
parser.addini("a2", "", "linelist")
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_addini_linelist_ini_files(self, testdir):
|
||||||
|
self.make_conftest_for_linelist(testdir)
|
||||||
testdir.makeini(
|
testdir.makeini(
|
||||||
"""
|
"""
|
||||||
[pytest]
|
[pytest]
|
||||||
|
@ -407,6 +465,19 @@ class TestConfigAPI:
|
||||||
second line
|
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()
|
config = testdir.parseconfig()
|
||||||
values = config.getini("xy")
|
values = config.getini("xy")
|
||||||
assert len(values) == 2
|
assert len(values) == 2
|
||||||
|
@ -832,7 +903,6 @@ def test_consider_args_after_options_for_rootdir(testdir, args):
|
||||||
result.stdout.fnmatch_lines(["*rootdir: *myroot"])
|
result.stdout.fnmatch_lines(["*rootdir: *myroot"])
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif("sys.platform == 'win32'")
|
|
||||||
def test_toolongargs_issue224(testdir):
|
def test_toolongargs_issue224(testdir):
|
||||||
result = testdir.runpytest("-m", "hello" * 500)
|
result = testdir.runpytest("-m", "hello" * 500)
|
||||||
assert result.ret == ExitCode.NO_TESTS_COLLECTED
|
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]) == tmpdir
|
||||||
assert get_common_ancestor([no_path.join("a")]) == tmpdir
|
assert get_common_ancestor([no_path.join("a")]) == tmpdir
|
||||||
|
|
||||||
@pytest.mark.parametrize("name", "setup.cfg tox.ini pytest.ini".split())
|
@pytest.mark.parametrize(
|
||||||
def test_with_ini(self, tmpdir: py.path.local, name: str) -> None:
|
"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 = tmpdir.join(name)
|
||||||
inifile.write("[pytest]\n" if name != "setup.cfg" else "[tool:pytest]\n")
|
inifile.write(contents)
|
||||||
|
|
||||||
a = tmpdir.mkdir("a")
|
a = tmpdir.mkdir("a")
|
||||||
b = a.mkdir("b")
|
b = a.mkdir("b")
|
||||||
|
@ -975,9 +1055,10 @@ class TestRootdir:
|
||||||
rootdir, parsed_inifile, _ = determine_setup(None, args)
|
rootdir, parsed_inifile, _ = determine_setup(None, args)
|
||||||
assert rootdir == tmpdir
|
assert rootdir == tmpdir
|
||||||
assert parsed_inifile == inifile
|
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 rootdir == tmpdir
|
||||||
assert parsed_inifile == inifile
|
assert parsed_inifile == inifile
|
||||||
|
assert ini_config == {"x": "10"}
|
||||||
|
|
||||||
@pytest.mark.parametrize("name", "setup.cfg tox.ini".split())
|
@pytest.mark.parametrize("name", "setup.cfg tox.ini".split())
|
||||||
def test_pytestini_overrides_empty_other(self, tmpdir: py.path.local, name) -> None:
|
def test_pytestini_overrides_empty_other(self, tmpdir: py.path.local, name) -> None:
|
||||||
|
@ -1004,10 +1085,26 @@ class TestRootdir:
|
||||||
assert inifile is None
|
assert inifile is None
|
||||||
assert inicfg == {}
|
assert inicfg == {}
|
||||||
|
|
||||||
def test_with_specific_inifile(self, tmpdir: py.path.local) -> None:
|
@pytest.mark.parametrize(
|
||||||
inifile = tmpdir.ensure("pytest.ini")
|
"name, contents",
|
||||||
rootdir, _, _ = determine_setup(str(inifile), [str(tmpdir)])
|
[
|
||||||
|
# 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 rootdir == tmpdir
|
||||||
|
assert inifile == p
|
||||||
|
assert ini_config == {"x": "10"}
|
||||||
|
|
||||||
def test_with_arg_outside_cwd_without_inifile(self, tmpdir, monkeypatch) -> None:
|
def test_with_arg_outside_cwd_without_inifile(self, tmpdir, monkeypatch) -> None:
|
||||||
monkeypatch.chdir(str(tmpdir))
|
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 = testdir.runpytest()
|
||||||
result.stdout.fnmatch_lines(["rootdir: *test_header0"])
|
result.stdout.fnmatch_lines(["rootdir: *test_header0"])
|
||||||
|
|
||||||
# with inifile
|
# with configfile
|
||||||
testdir.makeini("""[pytest]""")
|
testdir.makeini("""[pytest]""")
|
||||||
result = testdir.runpytest()
|
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
|
# with testpaths option, and not passing anything in the command-line
|
||||||
testdir.makeini(
|
testdir.makeini(
|
||||||
|
@ -720,12 +720,12 @@ class TestTerminalFunctional:
|
||||||
)
|
)
|
||||||
result = testdir.runpytest()
|
result = testdir.runpytest()
|
||||||
result.stdout.fnmatch_lines(
|
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
|
# with testpaths option, passing directory in command-line: do not show testpaths then
|
||||||
result = testdir.runpytest("tests")
|
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):
|
def test_showlocals(self, testdir):
|
||||||
p1 = testdir.makepyfile(
|
p1 = testdir.makepyfile(
|
||||||
|
|
42
tox.ini
42
tox.ini
|
@ -152,48 +152,6 @@ deps =
|
||||||
pypandoc
|
pypandoc
|
||||||
commands = python scripts/publish-gh-release-notes.py {posargs}
|
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]
|
[flake8]
|
||||||
max-line-length = 120
|
max-line-length = 120
|
||||||
extend-ignore = E203
|
extend-ignore = E203
|
||||||
|
|
Loading…
Reference in New Issue