Merge remote-tracking branch 'upstream/features' into merge-features-into-master
This commit is contained in:
commit
ee0a306ee4
|
@ -20,7 +20,9 @@ repos:
|
||||||
- id: check-yaml
|
- id: check-yaml
|
||||||
- id: debug-statements
|
- id: debug-statements
|
||||||
exclude: _pytest/debugging.py
|
exclude: _pytest/debugging.py
|
||||||
|
language_version: python3
|
||||||
- id: flake8
|
- id: flake8
|
||||||
|
language_version: python3
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
rev: v1.8.0
|
rev: v1.8.0
|
||||||
hooks:
|
hooks:
|
||||||
|
@ -41,6 +43,6 @@ repos:
|
||||||
- id: changelogs-rst
|
- id: changelogs-rst
|
||||||
name: changelog filenames
|
name: changelog filenames
|
||||||
language: fail
|
language: fail
|
||||||
entry: 'changelog files must be named ####.(feature|bugfix|doc|removal|vendor|trivial).rst'
|
entry: 'changelog files must be named ####.(feature|bugfix|doc|deprecation|removal|vendor|trivial).rst'
|
||||||
exclude: changelog/(\d+\.(feature|bugfix|doc|removal|vendor|trivial).rst|README.rst|_template.rst)
|
exclude: changelog/(\d+\.(feature|bugfix|doc|deprecation|removal|vendor|trivial).rst|README.rst|_template.rst)
|
||||||
files: ^changelog/
|
files: ^changelog/
|
||||||
|
|
2
AUTHORS
2
AUTHORS
|
@ -14,6 +14,7 @@ Allan Feldman
|
||||||
Anatoly Bubenkoff
|
Anatoly Bubenkoff
|
||||||
Anders Hovmöller
|
Anders Hovmöller
|
||||||
Andras Tim
|
Andras Tim
|
||||||
|
Andrea Cimatoribus
|
||||||
Andreas Zeidler
|
Andreas Zeidler
|
||||||
Andrzej Ostrowski
|
Andrzej Ostrowski
|
||||||
Andy Freeland
|
Andy Freeland
|
||||||
|
@ -120,6 +121,7 @@ Katerina Koukiou
|
||||||
Kevin Cox
|
Kevin Cox
|
||||||
Kodi B. Arfer
|
Kodi B. Arfer
|
||||||
Kostis Anagnostopoulos
|
Kostis Anagnostopoulos
|
||||||
|
Kyle Altendorf
|
||||||
Lawrence Mitchell
|
Lawrence Mitchell
|
||||||
Lee Kamentsky
|
Lee Kamentsky
|
||||||
Lev Maximov
|
Lev Maximov
|
||||||
|
|
157
CHANGELOG.rst
157
CHANGELOG.rst
|
@ -18,6 +18,163 @@ with advance notice in the **Deprecations** section of releases.
|
||||||
|
|
||||||
.. towncrier release notes start
|
.. towncrier release notes start
|
||||||
|
|
||||||
|
pytest 3.9.1 (2018-10-16)
|
||||||
|
=========================
|
||||||
|
|
||||||
|
Features
|
||||||
|
--------
|
||||||
|
|
||||||
|
- `#4159 <https://github.com/pytest-dev/pytest/issues/4159>`_: For test-suites containing test classes, the information about the subclassed
|
||||||
|
module is now output only if a higher verbosity level is specified (at least
|
||||||
|
"-vv").
|
||||||
|
|
||||||
|
|
||||||
|
pytest 3.9.0 (2018-10-15 - not published due to a release automation bug)
|
||||||
|
=========================================================================
|
||||||
|
|
||||||
|
Deprecations
|
||||||
|
------------
|
||||||
|
|
||||||
|
- `#3616 <https://github.com/pytest-dev/pytest/issues/3616>`_: The following accesses have been documented as deprecated for years, but are now actually emitting deprecation warnings.
|
||||||
|
|
||||||
|
* Access of ``Module``, ``Function``, ``Class``, ``Instance``, ``File`` and ``Item`` through ``Node`` instances. Now
|
||||||
|
users will this warning::
|
||||||
|
|
||||||
|
usage of Function.Module is deprecated, please use pytest.Module instead
|
||||||
|
|
||||||
|
Users should just ``import pytest`` and access those objects using the ``pytest`` module.
|
||||||
|
|
||||||
|
* ``request.cached_setup``, this was the precursor of the setup/teardown mechanism available to fixtures. You can
|
||||||
|
consult `funcarg comparision section in the docs <https://docs.pytest.org/en/latest/funcarg_compare.html>`_.
|
||||||
|
|
||||||
|
* Using objects named ``"Class"`` as a way to customize the type of nodes that are collected in ``Collector``
|
||||||
|
subclasses has been deprecated. Users instead should use ``pytest_collect_make_item`` to customize node types during
|
||||||
|
collection.
|
||||||
|
|
||||||
|
This issue should affect only advanced plugins who create new collection types, so if you see this warning
|
||||||
|
message please contact the authors so they can change the code.
|
||||||
|
|
||||||
|
* The warning that produces the message below has changed to ``RemovedInPytest4Warning``::
|
||||||
|
|
||||||
|
getfuncargvalue is deprecated, use getfixturevalue
|
||||||
|
|
||||||
|
|
||||||
|
- `#3988 <https://github.com/pytest-dev/pytest/issues/3988>`_: Add a Deprecation warning for pytest.ensuretemp as it was deprecated since a while.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Features
|
||||||
|
--------
|
||||||
|
|
||||||
|
- `#2293 <https://github.com/pytest-dev/pytest/issues/2293>`_: Improve usage errors messages by hiding internal details which can be distracting and noisy.
|
||||||
|
|
||||||
|
This has the side effect that some error conditions that previously raised generic errors (such as
|
||||||
|
``ValueError`` for unregistered marks) are now raising ``Failed`` exceptions.
|
||||||
|
|
||||||
|
|
||||||
|
- `#3332 <https://github.com/pytest-dev/pytest/issues/3332>`_: Improve the error displayed when a ``conftest.py`` file could not be imported.
|
||||||
|
|
||||||
|
In order to implement this, a new ``chain`` parameter was added to ``ExceptionInfo.getrepr``
|
||||||
|
to show or hide chained tracebacks in Python 3 (defaults to ``True``).
|
||||||
|
|
||||||
|
|
||||||
|
- `#3849 <https://github.com/pytest-dev/pytest/issues/3849>`_: Add ``empty_parameter_set_mark=fail_at_collect`` ini option for raising an exception when parametrize collects an empty set.
|
||||||
|
|
||||||
|
|
||||||
|
- `#3964 <https://github.com/pytest-dev/pytest/issues/3964>`_: Log messages generated in the collection phase are shown when
|
||||||
|
live-logging is enabled and/or when they are logged to a file.
|
||||||
|
|
||||||
|
|
||||||
|
- `#3985 <https://github.com/pytest-dev/pytest/issues/3985>`_: Introduce ``tmp_path`` as a fixture providing a Path object.
|
||||||
|
|
||||||
|
|
||||||
|
- `#4013 <https://github.com/pytest-dev/pytest/issues/4013>`_: Deprecation warnings are now shown even if you customize the warnings filters yourself. In the previous version
|
||||||
|
any customization would override pytest's filters and deprecation warnings would fall back to being hidden by default.
|
||||||
|
|
||||||
|
|
||||||
|
- `#4073 <https://github.com/pytest-dev/pytest/issues/4073>`_: Allow specification of timeout for ``Testdir.runpytest_subprocess()`` and ``Testdir.run()``.
|
||||||
|
|
||||||
|
|
||||||
|
- `#4098 <https://github.com/pytest-dev/pytest/issues/4098>`_: Add returncode argument to pytest.exit() to exit pytest with a specific return code.
|
||||||
|
|
||||||
|
|
||||||
|
- `#4102 <https://github.com/pytest-dev/pytest/issues/4102>`_: Reimplement ``pytest.deprecated_call`` using ``pytest.warns`` so it supports the ``match='...'`` keyword argument.
|
||||||
|
|
||||||
|
This has the side effect that ``pytest.deprecated_call`` now raises ``pytest.fail.Exception`` instead
|
||||||
|
of ``AssertionError``.
|
||||||
|
|
||||||
|
|
||||||
|
- `#4149 <https://github.com/pytest-dev/pytest/issues/4149>`_: Require setuptools>=30.3 and move most of the metadata to ``setup.cfg``.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Bug Fixes
|
||||||
|
---------
|
||||||
|
|
||||||
|
- `#2535 <https://github.com/pytest-dev/pytest/issues/2535>`_: Improve error message when test functions of ``unittest.TestCase`` subclasses use a parametrized fixture.
|
||||||
|
|
||||||
|
|
||||||
|
- `#3057 <https://github.com/pytest-dev/pytest/issues/3057>`_: ``request.fixturenames`` now correctly returns the name of fixtures created by ``request.getfixturevalue()``.
|
||||||
|
|
||||||
|
|
||||||
|
- `#3946 <https://github.com/pytest-dev/pytest/issues/3946>`_: Warning filters passed as command line options using ``-W`` now take precedence over filters defined in ``ini``
|
||||||
|
configuration files.
|
||||||
|
|
||||||
|
|
||||||
|
- `#4066 <https://github.com/pytest-dev/pytest/issues/4066>`_: Fix source reindenting by using ``textwrap.dedent`` directly.
|
||||||
|
|
||||||
|
|
||||||
|
- `#4102 <https://github.com/pytest-dev/pytest/issues/4102>`_: ``pytest.warn`` will capture previously-warned warnings in Python 2. Previously they were never raised.
|
||||||
|
|
||||||
|
|
||||||
|
- `#4108 <https://github.com/pytest-dev/pytest/issues/4108>`_: Resolve symbolic links for args.
|
||||||
|
|
||||||
|
This fixes running ``pytest tests/test_foo.py::test_bar``, where ``tests``
|
||||||
|
is a symlink to ``project/app/tests``:
|
||||||
|
previously ``project/app/conftest.py`` would be ignored for fixtures then.
|
||||||
|
|
||||||
|
|
||||||
|
- `#4132 <https://github.com/pytest-dev/pytest/issues/4132>`_: Fix duplicate printing of internal errors when using ``--pdb``.
|
||||||
|
|
||||||
|
|
||||||
|
- `#4135 <https://github.com/pytest-dev/pytest/issues/4135>`_: pathlib based tmpdir cleanup now correctly handles symlinks in the folder.
|
||||||
|
|
||||||
|
|
||||||
|
- `#4152 <https://github.com/pytest-dev/pytest/issues/4152>`_: Display the filename when encountering ``SyntaxWarning``.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Improved Documentation
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
- `#3713 <https://github.com/pytest-dev/pytest/issues/3713>`_: Update usefixtures documentation to clarify that it can't be used with fixture functions.
|
||||||
|
|
||||||
|
|
||||||
|
- `#4058 <https://github.com/pytest-dev/pytest/issues/4058>`_: Update fixture documentation to specify that a fixture can be invoked twice in the scope it's defined for.
|
||||||
|
|
||||||
|
|
||||||
|
- `#4064 <https://github.com/pytest-dev/pytest/issues/4064>`_: According to unittest.rst, setUpModule and tearDownModule were not implemented, but it turns out they are. So updated the documentation for unittest.
|
||||||
|
|
||||||
|
|
||||||
|
- `#4151 <https://github.com/pytest-dev/pytest/issues/4151>`_: Add tempir testing example to CONTRIBUTING.rst guide
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Trivial/Internal Changes
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
- `#2293 <https://github.com/pytest-dev/pytest/issues/2293>`_: The internal ``MarkerError`` exception has been removed.
|
||||||
|
|
||||||
|
|
||||||
|
- `#3988 <https://github.com/pytest-dev/pytest/issues/3988>`_: Port the implementation of tmpdir to pathlib.
|
||||||
|
|
||||||
|
|
||||||
|
- `#4063 <https://github.com/pytest-dev/pytest/issues/4063>`_: Exclude 0.00 second entries from ``--duration`` output unless ``-vv`` is passed on the command-line.
|
||||||
|
|
||||||
|
|
||||||
|
- `#4093 <https://github.com/pytest-dev/pytest/issues/4093>`_: Fixed formatting of string literals in internal tests.
|
||||||
|
|
||||||
|
|
||||||
pytest 3.8.2 (2018-10-02)
|
pytest 3.8.2 (2018-10-02)
|
||||||
=========================
|
=========================
|
||||||
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
Improve error message when test functions of ``unittest.TestCase`` subclasses use a parametrized fixture.
|
|
|
@ -1 +0,0 @@
|
||||||
``request.fixturenames`` now correctly returns the name of fixtures created by ``request.getfixturevalue()``.
|
|
|
@ -1 +0,0 @@
|
||||||
Update usefixtures documentation to clarify that it can't be used with fixture functions.
|
|
|
@ -1 +0,0 @@
|
||||||
Update fixture documentation to specify that a fixture can be invoked twice in the scope it's defined for.
|
|
|
@ -1 +0,0 @@
|
||||||
Exclude 0.00 second entries from ``--duration`` output unless ``-vv`` is passed on the command-line.
|
|
|
@ -1 +0,0 @@
|
||||||
According to unittest.rst, setUpModule and tearDownModule were not implemented, but it turns out they are. So updated the documentation for unittest.
|
|
|
@ -1 +0,0 @@
|
||||||
Fix source reindenting by using ``textwrap.dedent`` directly.
|
|
|
@ -1 +0,0 @@
|
||||||
Fixed formatting of string literals in internal tests.
|
|
|
@ -1 +0,0 @@
|
||||||
Fix duplicate printing of internal errors when using ``--pdb``.
|
|
|
@ -1 +0,0 @@
|
||||||
Add tempir testing example to CONTRIBUTING.rst guide
|
|
|
@ -1 +0,0 @@
|
||||||
Display the filename when encountering ``SyntaxWarning``.
|
|
|
@ -14,7 +14,8 @@ Each file should be named like ``<ISSUE>.<TYPE>.rst``, where
|
||||||
* ``feature``: new user facing features, like new command-line options and new behavior.
|
* ``feature``: new user facing features, like new command-line options and new behavior.
|
||||||
* ``bugfix``: fixes a reported bug.
|
* ``bugfix``: fixes a reported bug.
|
||||||
* ``doc``: documentation improvement, like rewording an entire session or adding missing docs.
|
* ``doc``: documentation improvement, like rewording an entire session or adding missing docs.
|
||||||
* ``removal``: feature deprecation or removal.
|
* ``deprecation``: feature deprecation.
|
||||||
|
* ``removal``: feature removal.
|
||||||
* ``vendor``: changes in packages vendored in pytest.
|
* ``vendor``: changes in packages vendored in pytest.
|
||||||
* ``trivial``: fixing a small typo or internal change that might be noteworthy.
|
* ``trivial``: fixing a small typo or internal change that might be noteworthy.
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,8 @@ Release announcements
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
|
|
||||||
|
release-3.9.1
|
||||||
|
release-3.9.0
|
||||||
release-3.8.2
|
release-3.8.2
|
||||||
release-3.8.1
|
release-3.8.1
|
||||||
release-3.8.0
|
release-3.8.0
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
pytest-3.9.0
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
The pytest team is proud to announce the 3.9.0 release!
|
||||||
|
|
||||||
|
pytest is a mature Python testing tool with more than a 2000 tests
|
||||||
|
against itself, passing on many different interpreters and platforms.
|
||||||
|
|
||||||
|
This release contains a number of bugs fixes and improvements, so users are encouraged
|
||||||
|
to take a look at the CHANGELOG:
|
||||||
|
|
||||||
|
https://docs.pytest.org/en/latest/changelog.html
|
||||||
|
|
||||||
|
For complete documentation, please visit:
|
||||||
|
|
||||||
|
https://docs.pytest.org/en/latest/
|
||||||
|
|
||||||
|
As usual, you can upgrade from pypi via:
|
||||||
|
|
||||||
|
pip install -U pytest
|
||||||
|
|
||||||
|
Thanks to all who contributed to this release, among them:
|
||||||
|
|
||||||
|
* Andrea Cimatoribus
|
||||||
|
* Ankit Goel
|
||||||
|
* Anthony Sottile
|
||||||
|
* Ben Eyal
|
||||||
|
* Bruno Oliveira
|
||||||
|
* Daniel Hahler
|
||||||
|
* Jeffrey Rackauckas
|
||||||
|
* Jose Carlos Menezes
|
||||||
|
* Kyle Altendorf
|
||||||
|
* Niklas JQ
|
||||||
|
* Palash Chatterjee
|
||||||
|
* Ronny Pfannschmidt
|
||||||
|
* Thomas Hess
|
||||||
|
* Thomas Hisch
|
||||||
|
* Tomer Keren
|
||||||
|
* Victor Maryama
|
||||||
|
|
||||||
|
|
||||||
|
Happy testing,
|
||||||
|
The Pytest Development Team
|
|
@ -0,0 +1,20 @@
|
||||||
|
pytest-3.9.1
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
pytest 3.9.1 has just been released to PyPI.
|
||||||
|
|
||||||
|
This is a bug-fix release, being a drop-in replacement. To upgrade::
|
||||||
|
|
||||||
|
pip install --upgrade pytest
|
||||||
|
|
||||||
|
The full changelog is available at https://docs.pytest.org/en/latest/changelog.html.
|
||||||
|
|
||||||
|
Thanks to all who contributed to this release, among them:
|
||||||
|
|
||||||
|
* Bruno Oliveira
|
||||||
|
* Ronny Pfannschmidt
|
||||||
|
* Thomas Hisch
|
||||||
|
|
||||||
|
|
||||||
|
Happy testing,
|
||||||
|
The pytest Development Team
|
|
@ -104,7 +104,9 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||||
See http://docs.python.org/library/warnings.html for information
|
See http://docs.python.org/library/warnings.html for information
|
||||||
on warning categories.
|
on warning categories.
|
||||||
tmpdir_factory
|
tmpdir_factory
|
||||||
Return a TempdirFactory instance for the test session.
|
Return a :class:`_pytest.tmpdir.TempdirFactory` instance for the test session.
|
||||||
|
tmp_path_factory
|
||||||
|
Return a :class:`_pytest.tmpdir.TempPathFactory` instance for the test session.
|
||||||
tmpdir
|
tmpdir
|
||||||
Return a temporary directory path object
|
Return a temporary directory path object
|
||||||
which is unique to each test function invocation,
|
which is unique to each test function invocation,
|
||||||
|
@ -113,6 +115,16 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||||
path object.
|
path object.
|
||||||
|
|
||||||
.. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html
|
.. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html
|
||||||
|
tmp_path
|
||||||
|
Return a temporary directory path object
|
||||||
|
which is unique to each test function invocation,
|
||||||
|
created as a sub directory of the base temporary
|
||||||
|
directory. The returned object is a :class:`pathlib.Path`
|
||||||
|
object.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
in python < 3.6 this is a pathlib2.Path
|
||||||
|
|
||||||
no tests ran in 0.12 seconds
|
no tests ran in 0.12 seconds
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,67 @@ Below is a complete list of all pytest features which are considered deprecated.
|
||||||
:class:`_pytest.warning_types.PytestWarning` or subclasses, which can be filtered using
|
:class:`_pytest.warning_types.PytestWarning` or subclasses, which can be filtered using
|
||||||
:ref:`standard warning filters <warnings>`.
|
:ref:`standard warning filters <warnings>`.
|
||||||
|
|
||||||
|
Internal classes accessed through ``Node``
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. deprecated:: 3.9
|
||||||
|
|
||||||
|
Access of ``Module``, ``Function``, ``Class``, ``Instance``, ``File`` and ``Item`` through ``Node`` instances now issue
|
||||||
|
this warning::
|
||||||
|
|
||||||
|
usage of Function.Module is deprecated, please use pytest.Module instead
|
||||||
|
|
||||||
|
Users should just ``import pytest`` and access those objects using the ``pytest`` module.
|
||||||
|
|
||||||
|
This has been documented as deprecated for years, but only now we are actually emitting deprecation warnings.
|
||||||
|
|
||||||
|
``cached_setup``
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. deprecated:: 3.9
|
||||||
|
|
||||||
|
``request.cached_setup`` was the precursor of the setup/teardown mechanism available to fixtures.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def db_session():
|
||||||
|
return request.cached_setup(
|
||||||
|
setup=Session.create, teardown=lambda session: session.close(), scope="module"
|
||||||
|
)
|
||||||
|
|
||||||
|
This should be updated to make use of standard fixture mechanisms:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def db_session():
|
||||||
|
session = Session.create()
|
||||||
|
yield session
|
||||||
|
session.close()
|
||||||
|
|
||||||
|
|
||||||
|
You can consult `funcarg comparision section in the docs <https://docs.pytest.org/en/latest/funcarg_compare.html>`_ for
|
||||||
|
more information.
|
||||||
|
|
||||||
|
This has been documented as deprecated for years, but only now we are actually emitting deprecation warnings.
|
||||||
|
|
||||||
|
|
||||||
|
Using ``Class`` in custom Collectors
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. deprecated:: 3.9
|
||||||
|
|
||||||
|
Using objects named ``"Class"`` as a way to customize the type of nodes that are collected in ``Collector``
|
||||||
|
subclasses has been deprecated. Users instead should use ``pytest_collect_make_item`` to customize node types during
|
||||||
|
collection.
|
||||||
|
|
||||||
|
This issue should affect only advanced plugins who create new collection types, so if you see this warning
|
||||||
|
message please contact the authors so they can change the code.
|
||||||
|
|
||||||
|
|
||||||
``Config.warn`` and ``Node.warn``
|
``Config.warn`` and ``Node.warn``
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -574,7 +574,7 @@ We can run this::
|
||||||
file $REGENDOC_TMPDIR/b/test_error.py, line 1
|
file $REGENDOC_TMPDIR/b/test_error.py, line 1
|
||||||
def test_root(db): # no db here, will error out
|
def test_root(db): # no db here, will error out
|
||||||
E fixture 'db' not found
|
E fixture 'db' not found
|
||||||
> available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, monkeypatch, pytestconfig, record_property, record_xml_attribute, record_xml_property, recwarn, tmpdir, tmpdir_factory
|
> available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, monkeypatch, pytestconfig, record_property, record_xml_attribute, record_xml_property, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory
|
||||||
> use 'pytest --fixtures [testpath]' for help on them.
|
> use 'pytest --fixtures [testpath]' for help on them.
|
||||||
|
|
||||||
$REGENDOC_TMPDIR/b/test_error.py:1
|
$REGENDOC_TMPDIR/b/test_error.py:1
|
||||||
|
|
|
@ -175,3 +175,13 @@ Previous to version 2.4 to set a break point in code one needed to use ``pytest.
|
||||||
This is no longer needed and one can use the native ``import pdb;pdb.set_trace()`` call directly.
|
This is no longer needed and one can use the native ``import pdb;pdb.set_trace()`` call directly.
|
||||||
|
|
||||||
For more details see :ref:`breakpoints`.
|
For more details see :ref:`breakpoints`.
|
||||||
|
|
||||||
|
"compat" properties
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
.. deprecated:: 3.9
|
||||||
|
|
||||||
|
Access of ``Module``, ``Function``, ``Class``, ``Instance``, ``File`` and ``Item`` through ``Node`` instances have long
|
||||||
|
been documented as deprecated, but started to emit warnings from pytest ``3.9`` and onward.
|
||||||
|
|
||||||
|
Users should just ``import pytest`` and access those objects using the ``pytest`` module.
|
||||||
|
|
|
@ -982,6 +982,7 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||||
|
|
||||||
* ``skip`` skips tests with an empty parameterset (default)
|
* ``skip`` skips tests with an empty parameterset (default)
|
||||||
* ``xfail`` marks tests with an empty parameterset as xfail(run=False)
|
* ``xfail`` marks tests with an empty parameterset as xfail(run=False)
|
||||||
|
* ``fail_at_collect`` raises an exception if parametrize collects an empty parameter set
|
||||||
|
|
||||||
.. code-block:: ini
|
.. code-block:: ini
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,76 @@
|
||||||
Temporary directories and files
|
Temporary directories and files
|
||||||
================================================
|
================================================
|
||||||
|
|
||||||
|
The ``tmp_path`` fixture
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
.. versionadded:: 3.9
|
||||||
|
|
||||||
|
|
||||||
|
You can use the ``tmpdir`` fixture which will
|
||||||
|
provide a temporary directory unique to the test invocation,
|
||||||
|
created in the `base temporary directory`_.
|
||||||
|
|
||||||
|
``tmpdir`` is a ``pathlib/pathlib2.Path`` object. Here is an example test usage:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
# content of test_tmp_path.py
|
||||||
|
import os
|
||||||
|
|
||||||
|
CONTENT = u"content"
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_file(tmp_path):
|
||||||
|
d = tmp_path / "sub"
|
||||||
|
d.mkdir()
|
||||||
|
p = d / "hello.txt"
|
||||||
|
p.write_text(CONTENT)
|
||||||
|
assert p.read_text() == CONTENT
|
||||||
|
assert len(list(tmp_path.iterdir())) == 1
|
||||||
|
assert 0
|
||||||
|
|
||||||
|
Running this would result in a passed test except for the last
|
||||||
|
``assert 0`` line which we use to look at values::
|
||||||
|
|
||||||
|
$ pytest test_tmp_path.py
|
||||||
|
=========================== test session starts ============================
|
||||||
|
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||||
|
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||||
|
collected 1 item
|
||||||
|
|
||||||
|
test_tmp_path.py F [100%]
|
||||||
|
|
||||||
|
================================= FAILURES =================================
|
||||||
|
_____________________________ test_create_file _____________________________
|
||||||
|
|
||||||
|
tmp_path = PosixPath('PYTEST_TMPDIR/test_create_file0')
|
||||||
|
|
||||||
|
def test_create_file(tmp_path):
|
||||||
|
d = tmp_path / "sub"
|
||||||
|
d.mkdir()
|
||||||
|
p = d / "hello.txt"
|
||||||
|
p.write_text(CONTENT)
|
||||||
|
assert p.read_text() == CONTENT
|
||||||
|
assert len(list(tmp_path.iterdir())) == 1
|
||||||
|
> assert 0
|
||||||
|
E assert 0
|
||||||
|
|
||||||
|
test_tmp_path.py:13: AssertionError
|
||||||
|
========================= 1 failed in 0.12 seconds =========================
|
||||||
|
|
||||||
|
The ``tmp_path_factory`` fixture
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
.. versionadded:: 3.9
|
||||||
|
|
||||||
|
|
||||||
|
The ``tmp_path_facotry`` is a session-scoped fixture which can be used
|
||||||
|
to create arbitrary temporary directories from any other fixture or test.
|
||||||
|
|
||||||
|
its intended to replace ``tmpdir_factory`` and returns :class:`pathlib.Path` instances.
|
||||||
|
|
||||||
|
|
||||||
The 'tmpdir' fixture
|
The 'tmpdir' fixture
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
|
|
@ -101,22 +101,28 @@ DeprecationWarning and PendingDeprecationWarning
|
||||||
------------------------------------------------
|
------------------------------------------------
|
||||||
|
|
||||||
.. versionadded:: 3.8
|
.. versionadded:: 3.8
|
||||||
|
.. versionchanged:: 3.9
|
||||||
|
|
||||||
By default pytest will display ``DeprecationWarning`` and ``PendingDeprecationWarning`` if no other warning filters
|
By default pytest will display ``DeprecationWarning`` and ``PendingDeprecationWarning``.
|
||||||
are configured.
|
|
||||||
|
|
||||||
To disable showing ``DeprecationWarning`` and ``PendingDeprecationWarning`` warnings, you might define any warnings
|
Sometimes it is useful to hide some specific deprecation warnings that happen in code that you have no control over
|
||||||
filter either in the command-line or in the ini file, or you can use:
|
(such as third-party libraries), in which case you might use the standard warning filters options (ini or marks).
|
||||||
|
For example:
|
||||||
|
|
||||||
.. code-block:: ini
|
.. code-block:: ini
|
||||||
|
|
||||||
[pytest]
|
[pytest]
|
||||||
filterwarnings =
|
filterwarnings =
|
||||||
ignore::DeprecationWarning
|
ignore:.*U.*mode is deprecated:DeprecationWarning
|
||||||
ignore::PendingDeprecationWarning
|
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
This makes pytest more compliant with `PEP-0506 <https://www.python.org/dev/peps/pep-0565/#recommended-filter-settings-for-test-runners>`_ which suggests that those warnings should
|
If warnings are configured at the interpreter level, using
|
||||||
|
the `PYTHONWARNINGS <https://docs.python.org/3/using/cmdline.html#envvar-PYTHONWARNINGS>`_ environment variable or the
|
||||||
|
``-W`` command-line option, pytest will not configure any filters by default.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
This feature makes pytest more compliant with `PEP-0506 <https://www.python.org/dev/peps/pep-0565/#recommended-filter-settings-for-test-runners>`_ which suggests that those warnings should
|
||||||
be shown by default by test runners, but pytest doesn't follow ``PEP-0506`` completely because resetting all
|
be shown by default by test runners, but pytest doesn't follow ``PEP-0506`` completely because resetting all
|
||||||
warning filters like suggested in the PEP will break existing test suites that configure warning filters themselves
|
warning filters like suggested in the PEP will break existing test suites that configure warning filters themselves
|
||||||
by calling ``warnings.simplefilter`` (see issue `#2430 <https://github.com/pytest-dev/pytest/issues/2430>`_
|
by calling ``warnings.simplefilter`` (see issue `#2430 <https://github.com/pytest-dev/pytest/issues/2430>`_
|
||||||
|
|
|
@ -420,9 +420,21 @@ additionally it is possible to copy examples for a example folder before running
|
||||||
============================= warnings summary =============================
|
============================= warnings summary =============================
|
||||||
$REGENDOC_TMPDIR/test_example.py:4: PytestExperimentalApiWarning: testdir.copy_example is an experimental api that may change over time
|
$REGENDOC_TMPDIR/test_example.py:4: PytestExperimentalApiWarning: testdir.copy_example is an experimental api that may change over time
|
||||||
testdir.copy_example("test_example.py")
|
testdir.copy_example("test_example.py")
|
||||||
|
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:321: RemovedInPytest4Warning: usage of Session.Class is deprecated, please use pytest.Class instead
|
||||||
|
return getattr(object, name, default)
|
||||||
|
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:321: RemovedInPytest4Warning: usage of Session.File is deprecated, please use pytest.File instead
|
||||||
|
return getattr(object, name, default)
|
||||||
|
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:321: RemovedInPytest4Warning: usage of Session.Function is deprecated, please use pytest.Function instead
|
||||||
|
return getattr(object, name, default)
|
||||||
|
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:321: RemovedInPytest4Warning: usage of Session.Instance is deprecated, please use pytest.Instance instead
|
||||||
|
return getattr(object, name, default)
|
||||||
|
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:321: RemovedInPytest4Warning: usage of Session.Item is deprecated, please use pytest.Item instead
|
||||||
|
return getattr(object, name, default)
|
||||||
|
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:321: RemovedInPytest4Warning: usage of Session.Module is deprecated, please use pytest.Module instead
|
||||||
|
return getattr(object, name, default)
|
||||||
|
|
||||||
-- Docs: https://docs.pytest.org/en/latest/warnings.html
|
-- Docs: https://docs.pytest.org/en/latest/warnings.html
|
||||||
=================== 2 passed, 1 warnings in 0.12 seconds ===================
|
=================== 2 passed, 7 warnings in 0.12 seconds ===================
|
||||||
|
|
||||||
For more information about the result object that ``runpytest()`` returns, and
|
For more information about the result object that ``runpytest()`` returns, and
|
||||||
the methods that it provides please check out the :py:class:`RunResult
|
the methods that it provides please check out the :py:class:`RunResult
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = [
|
requires = [
|
||||||
"setuptools",
|
# sync with setup.py until we discard non-pep-517/518
|
||||||
|
"setuptools>=30.3",
|
||||||
"setuptools-scm",
|
"setuptools-scm",
|
||||||
"wheel",
|
"wheel",
|
||||||
]
|
]
|
||||||
|
@ -15,7 +16,12 @@ template = "changelog/_template.rst"
|
||||||
|
|
||||||
[[tool.towncrier.type]]
|
[[tool.towncrier.type]]
|
||||||
directory = "removal"
|
directory = "removal"
|
||||||
name = "Deprecations and Removals"
|
name = "Removals"
|
||||||
|
showcontent = true
|
||||||
|
|
||||||
|
[[tool.towncrier.type]]
|
||||||
|
directory = "deprecation"
|
||||||
|
name = "Deprecations"
|
||||||
showcontent = true
|
showcontent = true
|
||||||
|
|
||||||
[[tool.towncrier.type]]
|
[[tool.towncrier.type]]
|
||||||
|
|
54
setup.cfg
54
setup.cfg
|
@ -1,3 +1,55 @@
|
||||||
|
[metadata]
|
||||||
|
|
||||||
|
name = pytest
|
||||||
|
description = pytest: simple powerful testing with Python
|
||||||
|
long_description = file: README.rst
|
||||||
|
url = https://docs.pytest.org/en/latest/
|
||||||
|
project_urls =
|
||||||
|
Source=https://github.com/pytest-dev/pytest
|
||||||
|
Tracker=https://github.com/pytest-dev/pytest/issues
|
||||||
|
|
||||||
|
author = Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, Floris Bruynooghe, Brianna Laugher, Florian Bruhin and others
|
||||||
|
|
||||||
|
license = MIT license
|
||||||
|
license_file = LICENSE
|
||||||
|
keywords = test, unittest
|
||||||
|
classifiers =
|
||||||
|
Development Status :: 6 - Mature
|
||||||
|
Intended Audience :: Developers
|
||||||
|
License :: OSI Approved :: MIT License
|
||||||
|
Operating System :: POSIX
|
||||||
|
Operating System :: Microsoft :: Windows
|
||||||
|
Operating System :: MacOS :: MacOS X
|
||||||
|
Topic :: Software Development :: Testing
|
||||||
|
Topic :: Software Development :: Libraries
|
||||||
|
Topic :: Utilities
|
||||||
|
Programming Language :: Python :: 2
|
||||||
|
Programming Language :: Python :: 2.7
|
||||||
|
Programming Language :: Python :: 3
|
||||||
|
Programming Language :: Python :: 3.4
|
||||||
|
Programming Language :: Python :: 3.5
|
||||||
|
Programming Language :: Python :: 3.6
|
||||||
|
Programming Language :: Python :: 3.7
|
||||||
|
platforms = unix, linux, osx, cygwin, win32
|
||||||
|
|
||||||
|
[options]
|
||||||
|
zip_safe = no
|
||||||
|
packages =
|
||||||
|
_pytest
|
||||||
|
_pytest.assertion
|
||||||
|
_pytest._code
|
||||||
|
_pytest.mark
|
||||||
|
_pytest.config
|
||||||
|
|
||||||
|
py_modules = pytest
|
||||||
|
python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
|
||||||
|
|
||||||
|
|
||||||
|
[options.entry_points]
|
||||||
|
console_scripts =
|
||||||
|
pytest=pytest:main
|
||||||
|
py.test=pytest:main
|
||||||
|
|
||||||
[build_sphinx]
|
[build_sphinx]
|
||||||
source-dir = doc/en/
|
source-dir = doc/en/
|
||||||
build-dir = doc/build
|
build-dir = doc/build
|
||||||
|
@ -13,8 +65,6 @@ universal = 1
|
||||||
ignore =
|
ignore =
|
||||||
_pytest/_version.py
|
_pytest/_version.py
|
||||||
|
|
||||||
[metadata]
|
|
||||||
license_file = LICENSE
|
|
||||||
|
|
||||||
[devpi:upload]
|
[devpi:upload]
|
||||||
formats = sdist.tgz,bdist_wheel
|
formats = sdist.tgz,bdist_wheel
|
||||||
|
|
130
setup.py
130
setup.py
|
@ -1,126 +1,34 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import setuptools
|
|
||||||
import pkg_resources
|
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
|
||||||
classifiers = [
|
|
||||||
"Development Status :: 6 - Mature",
|
# TODO: if py gets upgrade to >=1.6,
|
||||||
"Intended Audience :: Developers",
|
# remove _width_of_current_line in terminal.py
|
||||||
"License :: OSI Approved :: MIT License",
|
INSTALL_REQUIRES = [
|
||||||
"Operating System :: POSIX",
|
"py>=1.5.0",
|
||||||
"Operating System :: Microsoft :: Windows",
|
"six>=1.10.0",
|
||||||
"Operating System :: MacOS :: MacOS X",
|
"setuptools",
|
||||||
"Topic :: Software Development :: Testing",
|
"attrs>=17.4.0",
|
||||||
"Topic :: Software Development :: Libraries",
|
"more-itertools>=4.0.0",
|
||||||
"Topic :: Utilities",
|
"atomicwrites>=1.0",
|
||||||
] + [
|
'funcsigs;python_version<"3.0"',
|
||||||
("Programming Language :: Python :: %s" % x)
|
'pathlib2>=2.2.0;python_version<"3.6"',
|
||||||
for x in "2 2.7 3 3.4 3.5 3.6 3.7".split()
|
'colorama;sys_platform=="win32"',
|
||||||
]
|
]
|
||||||
|
|
||||||
with open("README.rst") as fd:
|
|
||||||
long_description = fd.read()
|
|
||||||
|
|
||||||
|
# if _PYTEST_SETUP_SKIP_PLUGGY_DEP is set, skip installing pluggy;
|
||||||
def get_environment_marker_support_level():
|
# used by tox.ini to test with pluggy master
|
||||||
"""
|
if "_PYTEST_SETUP_SKIP_PLUGGY_DEP" not in os.environ:
|
||||||
Tests how well setuptools supports PEP-426 environment marker.
|
INSTALL_REQUIRES.append("pluggy>=0.7")
|
||||||
|
|
||||||
The first known release to support it is 0.7 (and the earliest on PyPI seems to be 0.7.2
|
|
||||||
so we're using that), see: https://setuptools.readthedocs.io/en/latest/history.html#id350
|
|
||||||
|
|
||||||
The support is later enhanced to allow direct conditional inclusions inside install_requires,
|
|
||||||
which is now recommended by setuptools. It first appeared in 36.2.0, went broken with 36.2.1, and
|
|
||||||
again worked since 36.2.2, so we're using that. See:
|
|
||||||
https://setuptools.readthedocs.io/en/latest/history.html#v36-2-2
|
|
||||||
https://github.com/pypa/setuptools/issues/1099
|
|
||||||
|
|
||||||
References:
|
|
||||||
|
|
||||||
* https://wheel.readthedocs.io/en/latest/index.html#defining-conditional-dependencies
|
|
||||||
* https://www.python.org/dev/peps/pep-0426/#environment-markers
|
|
||||||
* https://setuptools.readthedocs.io/en/latest/setuptools.html#declaring-platform-specific-dependencies
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
version = pkg_resources.parse_version(setuptools.__version__)
|
|
||||||
if version >= pkg_resources.parse_version("36.2.2"):
|
|
||||||
return 2
|
|
||||||
if version >= pkg_resources.parse_version("0.7.2"):
|
|
||||||
return 1
|
|
||||||
except Exception as exc:
|
|
||||||
sys.stderr.write("Could not test setuptool's version: %s\n" % exc)
|
|
||||||
|
|
||||||
# as of testing on 2018-05-26 fedora was on version 37* and debian was on version 33+
|
|
||||||
# we should consider erroring on those
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
extras_require = {}
|
|
||||||
install_requires = [
|
|
||||||
"py>=1.5.0", # if py gets upgrade to >=1.6, remove _width_of_current_line in terminal.py
|
|
||||||
"six>=1.10.0",
|
|
||||||
"setuptools",
|
|
||||||
"attrs>=17.4.0",
|
|
||||||
"more-itertools>=4.0.0",
|
|
||||||
"atomicwrites>=1.0",
|
|
||||||
]
|
|
||||||
# if _PYTEST_SETUP_SKIP_PLUGGY_DEP is set, skip installing pluggy;
|
|
||||||
# used by tox.ini to test with pluggy master
|
|
||||||
if "_PYTEST_SETUP_SKIP_PLUGGY_DEP" not in os.environ:
|
|
||||||
install_requires.append("pluggy>=0.7")
|
|
||||||
environment_marker_support_level = get_environment_marker_support_level()
|
|
||||||
if environment_marker_support_level >= 2:
|
|
||||||
install_requires.append('funcsigs;python_version<"3.0"')
|
|
||||||
install_requires.append('pathlib2>=2.2.0;python_version<"3.6"')
|
|
||||||
install_requires.append('colorama;sys_platform=="win32"')
|
|
||||||
elif environment_marker_support_level == 1:
|
|
||||||
extras_require[':python_version<"3.0"'] = ["funcsigs"]
|
|
||||||
extras_require[':python_version<"3.6"'] = ["pathlib2>=2.2.0"]
|
|
||||||
extras_require[':sys_platform=="win32"'] = ["colorama"]
|
|
||||||
else:
|
|
||||||
if sys.platform == "win32":
|
|
||||||
install_requires.append("colorama")
|
|
||||||
if sys.version_info < (3, 0):
|
|
||||||
install_requires.append("funcsigs")
|
|
||||||
if sys.version_info < (3, 6):
|
|
||||||
install_requires.append("pathlib2>=2.2.0")
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="pytest",
|
|
||||||
description="pytest: simple powerful testing with Python",
|
|
||||||
long_description=long_description,
|
|
||||||
use_scm_version={"write_to": "src/_pytest/_version.py"},
|
use_scm_version={"write_to": "src/_pytest/_version.py"},
|
||||||
url="https://docs.pytest.org/en/latest/",
|
setup_requires=["setuptools-scm", "setuptools>=30.3"],
|
||||||
project_urls={
|
|
||||||
"Source": "https://github.com/pytest-dev/pytest",
|
|
||||||
"Tracker": "https://github.com/pytest-dev/pytest/issues",
|
|
||||||
},
|
|
||||||
license="MIT license",
|
|
||||||
platforms=["unix", "linux", "osx", "cygwin", "win32"],
|
|
||||||
author=(
|
|
||||||
"Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, "
|
|
||||||
"Floris Bruynooghe, Brianna Laugher, Florian Bruhin and others"
|
|
||||||
),
|
|
||||||
entry_points={"console_scripts": ["pytest=pytest:main", "py.test=pytest:main"]},
|
|
||||||
classifiers=classifiers,
|
|
||||||
keywords="test unittest",
|
|
||||||
# the following should be enabled for release
|
|
||||||
setup_requires=["setuptools-scm"],
|
|
||||||
package_dir={"": "src"},
|
package_dir={"": "src"},
|
||||||
python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
|
install_requires=INSTALL_REQUIRES,
|
||||||
install_requires=install_requires,
|
|
||||||
extras_require=extras_require,
|
|
||||||
packages=[
|
|
||||||
"_pytest",
|
|
||||||
"_pytest.assertion",
|
|
||||||
"_pytest._code",
|
|
||||||
"_pytest.mark",
|
|
||||||
"_pytest.config",
|
|
||||||
],
|
|
||||||
py_modules=["pytest"],
|
|
||||||
zip_safe=False,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ from .code import Code # noqa
|
||||||
from .code import ExceptionInfo # noqa
|
from .code import ExceptionInfo # noqa
|
||||||
from .code import Frame # noqa
|
from .code import Frame # noqa
|
||||||
from .code import Traceback # noqa
|
from .code import Traceback # noqa
|
||||||
|
from .code import filter_traceback # noqa
|
||||||
from .code import getrawcode # noqa
|
from .code import getrawcode # noqa
|
||||||
from .source import Source # noqa
|
from .source import Source # noqa
|
||||||
from .source import compile_ as compile # noqa
|
from .source import compile_ as compile # noqa
|
||||||
|
|
|
@ -6,8 +6,10 @@ import traceback
|
||||||
from inspect import CO_VARARGS, CO_VARKEYWORDS
|
from inspect import CO_VARARGS, CO_VARKEYWORDS
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
|
import pluggy
|
||||||
import re
|
import re
|
||||||
from weakref import ref
|
from weakref import ref
|
||||||
|
import _pytest
|
||||||
from _pytest.compat import _PY2, _PY3, PY35, safe_str
|
from _pytest.compat import _PY2, _PY3, PY35, safe_str
|
||||||
from six import text_type
|
from six import text_type
|
||||||
import py
|
import py
|
||||||
|
@ -451,13 +453,35 @@ class ExceptionInfo(object):
|
||||||
tbfilter=True,
|
tbfilter=True,
|
||||||
funcargs=False,
|
funcargs=False,
|
||||||
truncate_locals=True,
|
truncate_locals=True,
|
||||||
|
chain=True,
|
||||||
):
|
):
|
||||||
""" return str()able representation of this exception info.
|
"""
|
||||||
showlocals: show locals per traceback entry
|
Return str()able representation of this exception info.
|
||||||
style: long|short|no|native traceback style
|
|
||||||
tbfilter: hide entries (where __tracebackhide__ is true)
|
|
||||||
|
|
||||||
in case of style==native, tbfilter and showlocals is ignored.
|
:param bool showlocals:
|
||||||
|
Show locals per traceback entry.
|
||||||
|
Ignored if ``style=="native"``.
|
||||||
|
|
||||||
|
:param str style: long|short|no|native traceback style
|
||||||
|
|
||||||
|
:param bool abspath:
|
||||||
|
If paths should be changed to absolute or left unchanged.
|
||||||
|
|
||||||
|
:param bool tbfilter:
|
||||||
|
Hide entries that contain a local variable ``__tracebackhide__==True``.
|
||||||
|
Ignored if ``style=="native"``.
|
||||||
|
|
||||||
|
:param bool funcargs:
|
||||||
|
Show fixtures ("funcargs" for legacy purposes) per traceback entry.
|
||||||
|
|
||||||
|
:param bool truncate_locals:
|
||||||
|
With ``showlocals==True``, make sure locals can be safely represented as strings.
|
||||||
|
|
||||||
|
:param bool chain: if chained exceptions in Python 3 should be shown.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.9
|
||||||
|
|
||||||
|
Added the ``chain`` parameter.
|
||||||
"""
|
"""
|
||||||
if style == "native":
|
if style == "native":
|
||||||
return ReprExceptionInfo(
|
return ReprExceptionInfo(
|
||||||
|
@ -476,6 +500,7 @@ class ExceptionInfo(object):
|
||||||
tbfilter=tbfilter,
|
tbfilter=tbfilter,
|
||||||
funcargs=funcargs,
|
funcargs=funcargs,
|
||||||
truncate_locals=truncate_locals,
|
truncate_locals=truncate_locals,
|
||||||
|
chain=chain,
|
||||||
)
|
)
|
||||||
return fmt.repr_excinfo(self)
|
return fmt.repr_excinfo(self)
|
||||||
|
|
||||||
|
@ -516,6 +541,7 @@ class FormattedExcinfo(object):
|
||||||
tbfilter = attr.ib(default=True)
|
tbfilter = attr.ib(default=True)
|
||||||
funcargs = attr.ib(default=False)
|
funcargs = attr.ib(default=False)
|
||||||
truncate_locals = attr.ib(default=True)
|
truncate_locals = attr.ib(default=True)
|
||||||
|
chain = attr.ib(default=True)
|
||||||
astcache = attr.ib(default=attr.Factory(dict), init=False, repr=False)
|
astcache = attr.ib(default=attr.Factory(dict), init=False, repr=False)
|
||||||
|
|
||||||
def _getindent(self, source):
|
def _getindent(self, source):
|
||||||
|
@ -735,7 +761,7 @@ class FormattedExcinfo(object):
|
||||||
reprcrash = None
|
reprcrash = None
|
||||||
|
|
||||||
repr_chain += [(reprtraceback, reprcrash, descr)]
|
repr_chain += [(reprtraceback, reprcrash, descr)]
|
||||||
if e.__cause__ is not None:
|
if e.__cause__ is not None and self.chain:
|
||||||
e = e.__cause__
|
e = e.__cause__
|
||||||
excinfo = (
|
excinfo = (
|
||||||
ExceptionInfo((type(e), e, e.__traceback__))
|
ExceptionInfo((type(e), e, e.__traceback__))
|
||||||
|
@ -743,7 +769,11 @@ class FormattedExcinfo(object):
|
||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
descr = "The above exception was the direct cause of the following exception:"
|
descr = "The above exception was the direct cause of the following exception:"
|
||||||
elif e.__context__ is not None and not e.__suppress_context__:
|
elif (
|
||||||
|
e.__context__ is not None
|
||||||
|
and not e.__suppress_context__
|
||||||
|
and self.chain
|
||||||
|
):
|
||||||
e = e.__context__
|
e = e.__context__
|
||||||
excinfo = (
|
excinfo = (
|
||||||
ExceptionInfo((type(e), e, e.__traceback__))
|
ExceptionInfo((type(e), e, e.__traceback__))
|
||||||
|
@ -979,3 +1009,36 @@ else:
|
||||||
return "maximum recursion depth exceeded" in str(excinfo.value)
|
return "maximum recursion depth exceeded" in str(excinfo.value)
|
||||||
except UnicodeError:
|
except UnicodeError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# relative paths that we use to filter traceback entries from appearing to the user;
|
||||||
|
# see filter_traceback
|
||||||
|
# note: if we need to add more paths than what we have now we should probably use a list
|
||||||
|
# for better maintenance
|
||||||
|
|
||||||
|
_PLUGGY_DIR = py.path.local(pluggy.__file__.rstrip("oc"))
|
||||||
|
# pluggy is either a package or a single module depending on the version
|
||||||
|
if _PLUGGY_DIR.basename == "__init__.py":
|
||||||
|
_PLUGGY_DIR = _PLUGGY_DIR.dirpath()
|
||||||
|
_PYTEST_DIR = py.path.local(_pytest.__file__).dirpath()
|
||||||
|
_PY_DIR = py.path.local(py.__file__).dirpath()
|
||||||
|
|
||||||
|
|
||||||
|
def filter_traceback(entry):
|
||||||
|
"""Return True if a TracebackEntry instance should be removed from tracebacks:
|
||||||
|
* dynamically generated code (no code to show up for it);
|
||||||
|
* internal traceback from pytest or its internal libraries, py and pluggy.
|
||||||
|
"""
|
||||||
|
# entry.path might sometimes return a str object when the entry
|
||||||
|
# points to dynamically generated code
|
||||||
|
# see https://bitbucket.org/pytest-dev/py/issues/71
|
||||||
|
raw_filename = entry.frame.code.raw.co_filename
|
||||||
|
is_generated = "<" in raw_filename and ">" in raw_filename
|
||||||
|
if is_generated:
|
||||||
|
return False
|
||||||
|
# entry.path might point to a non-existing file, in which case it will
|
||||||
|
# also return a str object. see #1133
|
||||||
|
p = py.path.local(entry.path)
|
||||||
|
return (
|
||||||
|
not p.relto(_PLUGGY_DIR) and not p.relto(_PYTEST_DIR) and not p.relto(_PY_DIR)
|
||||||
|
)
|
||||||
|
|
|
@ -17,8 +17,9 @@ import atomicwrites
|
||||||
import py
|
import py
|
||||||
|
|
||||||
from _pytest.assertion import util
|
from _pytest.assertion import util
|
||||||
from _pytest.compat import PurePath, spec_from_file_location
|
from _pytest.pathlib import PurePath
|
||||||
from _pytest.paths import fnmatch_ex
|
from _pytest.compat import spec_from_file_location
|
||||||
|
from _pytest.pathlib import fnmatch_ex
|
||||||
|
|
||||||
# pytest caches rewritten pycs in __pycache__.
|
# pytest caches rewritten pycs in __pycache__.
|
||||||
if hasattr(imp, "get_tag"):
|
if hasattr(imp, "get_tag"):
|
||||||
|
|
|
@ -13,10 +13,9 @@ import attr
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import json
|
import json
|
||||||
import shutil
|
|
||||||
|
|
||||||
from . import paths
|
from .compat import _PY2 as PY2
|
||||||
from .compat import _PY2 as PY2, Path
|
from .pathlib import Path, resolve_from_str, rmtree
|
||||||
|
|
||||||
README_CONTENT = u"""\
|
README_CONTENT = u"""\
|
||||||
# pytest cache directory #
|
# pytest cache directory #
|
||||||
|
@ -39,13 +38,13 @@ class Cache(object):
|
||||||
def for_config(cls, config):
|
def for_config(cls, config):
|
||||||
cachedir = cls.cache_dir_from_config(config)
|
cachedir = cls.cache_dir_from_config(config)
|
||||||
if config.getoption("cacheclear") and cachedir.exists():
|
if config.getoption("cacheclear") and cachedir.exists():
|
||||||
shutil.rmtree(str(cachedir))
|
rmtree(cachedir, force=True)
|
||||||
cachedir.mkdir()
|
cachedir.mkdir()
|
||||||
return cls(cachedir, config)
|
return cls(cachedir, config)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def cache_dir_from_config(config):
|
def cache_dir_from_config(config):
|
||||||
return paths.resolve_from_str(config.getini("cache_dir"), config.rootdir)
|
return resolve_from_str(config.getini("cache_dir"), config.rootdir)
|
||||||
|
|
||||||
def warn(self, fmt, **args):
|
def warn(self, fmt, **args):
|
||||||
from _pytest.warnings import _issue_config_warning
|
from _pytest.warnings import _issue_config_warning
|
||||||
|
|
|
@ -23,8 +23,6 @@ except ImportError: # pragma: no cover
|
||||||
# Only available in Python 3.4+ or as a backport
|
# Only available in Python 3.4+ or as a backport
|
||||||
enum = None
|
enum = None
|
||||||
|
|
||||||
__all__ = ["Path", "PurePath"]
|
|
||||||
|
|
||||||
_PY3 = sys.version_info > (3, 0)
|
_PY3 = sys.version_info > (3, 0)
|
||||||
_PY2 = not _PY3
|
_PY2 = not _PY3
|
||||||
|
|
||||||
|
@ -41,11 +39,6 @@ PY35 = sys.version_info[:2] >= (3, 5)
|
||||||
PY36 = sys.version_info[:2] >= (3, 6)
|
PY36 = sys.version_info[:2] >= (3, 6)
|
||||||
MODULE_NOT_FOUND_ERROR = "ModuleNotFoundError" if PY36 else "ImportError"
|
MODULE_NOT_FOUND_ERROR = "ModuleNotFoundError" if PY36 else "ImportError"
|
||||||
|
|
||||||
if PY36:
|
|
||||||
from pathlib import Path, PurePath
|
|
||||||
else:
|
|
||||||
from pathlib2 import Path, PurePath
|
|
||||||
|
|
||||||
|
|
||||||
if _PY3:
|
if _PY3:
|
||||||
from collections.abc import MutableMapping as MappingMixin
|
from collections.abc import MutableMapping as MappingMixin
|
||||||
|
|
|
@ -3,7 +3,6 @@ from __future__ import absolute_import, division, print_function
|
||||||
import argparse
|
import argparse
|
||||||
import inspect
|
import inspect
|
||||||
import shlex
|
import shlex
|
||||||
import traceback
|
|
||||||
import types
|
import types
|
||||||
import warnings
|
import warnings
|
||||||
import copy
|
import copy
|
||||||
|
@ -19,6 +18,7 @@ import _pytest._code
|
||||||
import _pytest.hookspec # the extension point definitions
|
import _pytest.hookspec # the extension point definitions
|
||||||
import _pytest.assertion
|
import _pytest.assertion
|
||||||
from pluggy import PluginManager, HookimplMarker, HookspecMarker
|
from pluggy import PluginManager, HookimplMarker, HookspecMarker
|
||||||
|
from _pytest._code import ExceptionInfo, filter_traceback
|
||||||
from _pytest.compat import safe_str
|
from _pytest.compat import safe_str
|
||||||
from .exceptions import UsageError, PrintHelp
|
from .exceptions import UsageError, PrintHelp
|
||||||
from .findpaths import determine_setup, exists
|
from .findpaths import determine_setup, exists
|
||||||
|
@ -26,9 +26,6 @@ from .findpaths import determine_setup, exists
|
||||||
hookimpl = HookimplMarker("pytest")
|
hookimpl = HookimplMarker("pytest")
|
||||||
hookspec = HookspecMarker("pytest")
|
hookspec = HookspecMarker("pytest")
|
||||||
|
|
||||||
# pytest startup
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
class ConftestImportFailure(Exception):
|
class ConftestImportFailure(Exception):
|
||||||
def __init__(self, path, excinfo):
|
def __init__(self, path, excinfo):
|
||||||
|
@ -36,12 +33,6 @@ class ConftestImportFailure(Exception):
|
||||||
self.path = path
|
self.path = path
|
||||||
self.excinfo = excinfo
|
self.excinfo = excinfo
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
etype, evalue, etb = self.excinfo
|
|
||||||
formatted = traceback.format_tb(etb)
|
|
||||||
# The level of the tracebacks we want to print is hand crafted :(
|
|
||||||
return repr(evalue) + "\n" + "".join(formatted[2:])
|
|
||||||
|
|
||||||
|
|
||||||
def main(args=None, plugins=None):
|
def main(args=None, plugins=None):
|
||||||
""" return exit code, after performing an in-process test run.
|
""" return exit code, after performing an in-process test run.
|
||||||
|
@ -57,10 +48,20 @@ def main(args=None, plugins=None):
|
||||||
try:
|
try:
|
||||||
config = _prepareconfig(args, plugins)
|
config = _prepareconfig(args, plugins)
|
||||||
except ConftestImportFailure as e:
|
except ConftestImportFailure as e:
|
||||||
|
exc_info = ExceptionInfo(e.excinfo)
|
||||||
tw = py.io.TerminalWriter(sys.stderr)
|
tw = py.io.TerminalWriter(sys.stderr)
|
||||||
for line in traceback.format_exception(*e.excinfo):
|
tw.line(
|
||||||
|
"ImportError while loading conftest '{e.path}'.".format(e=e), red=True
|
||||||
|
)
|
||||||
|
exc_info.traceback = exc_info.traceback.filter(filter_traceback)
|
||||||
|
exc_repr = (
|
||||||
|
exc_info.getrepr(style="short", chain=False)
|
||||||
|
if exc_info.traceback
|
||||||
|
else exc_info.exconly()
|
||||||
|
)
|
||||||
|
formatted_tb = safe_str(exc_repr)
|
||||||
|
for line in formatted_tb.splitlines():
|
||||||
tw.line(line.rstrip(), red=True)
|
tw.line(line.rstrip(), red=True)
|
||||||
tw.line("ERROR: could not load %s\n" % (e.path,), red=True)
|
|
||||||
return 4
|
return 4
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
|
@ -378,25 +379,27 @@ class PytestPluginManager(PluginManager):
|
||||||
def _getconftestmodules(self, path):
|
def _getconftestmodules(self, path):
|
||||||
if self._noconftest:
|
if self._noconftest:
|
||||||
return []
|
return []
|
||||||
try:
|
|
||||||
return self._path2confmods[path]
|
|
||||||
except KeyError:
|
|
||||||
if path.isfile():
|
|
||||||
clist = self._getconftestmodules(path.dirpath())
|
|
||||||
else:
|
|
||||||
# XXX these days we may rather want to use config.rootdir
|
|
||||||
# and allow users to opt into looking into the rootdir parent
|
|
||||||
# directories instead of requiring to specify confcutdir
|
|
||||||
clist = []
|
|
||||||
for parent in path.parts():
|
|
||||||
if self._confcutdir and self._confcutdir.relto(parent):
|
|
||||||
continue
|
|
||||||
conftestpath = parent.join("conftest.py")
|
|
||||||
if conftestpath.isfile():
|
|
||||||
mod = self._importconftest(conftestpath)
|
|
||||||
clist.append(mod)
|
|
||||||
|
|
||||||
self._path2confmods[path] = clist
|
if path.isfile():
|
||||||
|
directory = path.dirpath()
|
||||||
|
else:
|
||||||
|
directory = path
|
||||||
|
try:
|
||||||
|
return self._path2confmods[directory]
|
||||||
|
except KeyError:
|
||||||
|
# XXX these days we may rather want to use config.rootdir
|
||||||
|
# and allow users to opt into looking into the rootdir parent
|
||||||
|
# directories instead of requiring to specify confcutdir
|
||||||
|
clist = []
|
||||||
|
for parent in directory.parts():
|
||||||
|
if self._confcutdir and self._confcutdir.relto(parent):
|
||||||
|
continue
|
||||||
|
conftestpath = parent.join("conftest.py")
|
||||||
|
if conftestpath.isfile():
|
||||||
|
mod = self._importconftest(conftestpath)
|
||||||
|
clist.append(mod)
|
||||||
|
|
||||||
|
self._path2confmods[directory] = clist
|
||||||
return clist
|
return clist
|
||||||
|
|
||||||
def _rget_with_confmod(self, name, path):
|
def _rget_with_confmod(self, name, path):
|
||||||
|
|
|
@ -103,21 +103,18 @@ def determine_setup(inifile, args, rootdir_cmd_arg=None, config=None):
|
||||||
if inifile:
|
if inifile:
|
||||||
iniconfig = py.iniconfig.IniConfig(inifile)
|
iniconfig = py.iniconfig.IniConfig(inifile)
|
||||||
is_cfg_file = str(inifile).endswith(".cfg")
|
is_cfg_file = str(inifile).endswith(".cfg")
|
||||||
# TODO: [pytest] section in *.cfg files is depricated. Need refactoring.
|
|
||||||
sections = ["tool:pytest", "pytest"] if is_cfg_file else ["pytest"]
|
sections = ["tool:pytest", "pytest"] if is_cfg_file else ["pytest"]
|
||||||
for section in sections:
|
for section in sections:
|
||||||
try:
|
try:
|
||||||
inicfg = iniconfig[section]
|
inicfg = iniconfig[section]
|
||||||
if is_cfg_file and section == "pytest" and config is not None:
|
if is_cfg_file and section == "pytest" and config is not None:
|
||||||
from _pytest.deprecated import CFG_PYTEST_SECTION
|
from _pytest.deprecated import CFG_PYTEST_SECTION
|
||||||
from _pytest.warning_types import RemovedInPytest4Warning
|
|
||||||
from _pytest.warnings import _issue_config_warning
|
from _pytest.warnings import _issue_config_warning
|
||||||
|
|
||||||
|
# TODO: [pytest] section in *.cfg files is deprecated. Need refactoring once
|
||||||
|
# the deprecation expires.
|
||||||
_issue_config_warning(
|
_issue_config_warning(
|
||||||
RemovedInPytest4Warning(
|
CFG_PYTEST_SECTION.format(filename=str(inifile)), config
|
||||||
CFG_PYTEST_SECTION.format(filename=str(inifile))
|
|
||||||
),
|
|
||||||
config,
|
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
|
|
@ -4,10 +4,15 @@ that is planned to be removed in the next pytest release.
|
||||||
|
|
||||||
Keeping it in a central location makes it easy to track what is deprecated and should
|
Keeping it in a central location makes it easy to track what is deprecated and should
|
||||||
be removed when the time comes.
|
be removed when the time comes.
|
||||||
|
|
||||||
|
All constants defined in this module should be either PytestWarning instances or UnformattedWarning
|
||||||
|
in case of warnings which need to format their messages.
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
from _pytest.warning_types import RemovedInPytest4Warning
|
|
||||||
|
from _pytest.warning_types import UnformattedWarning, RemovedInPytest4Warning
|
||||||
|
|
||||||
|
|
||||||
MAIN_STR_ARGS = RemovedInPytest4Warning(
|
MAIN_STR_ARGS = RemovedInPytest4Warning(
|
||||||
"passing a string to pytest.main() is deprecated, "
|
"passing a string to pytest.main() is deprecated, "
|
||||||
|
@ -18,25 +23,48 @@ YIELD_TESTS = RemovedInPytest4Warning(
|
||||||
"yield tests are deprecated, and scheduled to be removed in pytest 4.0"
|
"yield tests are deprecated, and scheduled to be removed in pytest 4.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
FUNCARG_PREFIX = (
|
CACHED_SETUP = RemovedInPytest4Warning(
|
||||||
|
"cached_setup is deprecated and will be removed in a future release. "
|
||||||
|
"Use standard fixture functions instead."
|
||||||
|
)
|
||||||
|
|
||||||
|
COMPAT_PROPERTY = UnformattedWarning(
|
||||||
|
RemovedInPytest4Warning,
|
||||||
|
"usage of {owner}.{name} is deprecated, please use pytest.{name} instead",
|
||||||
|
)
|
||||||
|
|
||||||
|
CUSTOM_CLASS = UnformattedWarning(
|
||||||
|
RemovedInPytest4Warning,
|
||||||
|
'use of special named "{name}" objects in collectors of type "{type_name}" to '
|
||||||
|
"customize the created nodes is deprecated. "
|
||||||
|
"Use pytest_pycollect_makeitem(...) to create custom "
|
||||||
|
"collection nodes instead.",
|
||||||
|
)
|
||||||
|
|
||||||
|
FUNCARG_PREFIX = UnformattedWarning(
|
||||||
|
RemovedInPytest4Warning,
|
||||||
'{name}: declaring fixtures using "pytest_funcarg__" prefix is deprecated '
|
'{name}: declaring fixtures using "pytest_funcarg__" prefix is deprecated '
|
||||||
"and scheduled to be removed in pytest 4.0. "
|
"and scheduled to be removed in pytest 4.0. "
|
||||||
"Please remove the prefix and use the @pytest.fixture decorator instead."
|
"Please remove the prefix and use the @pytest.fixture decorator instead.",
|
||||||
)
|
)
|
||||||
|
|
||||||
FIXTURE_FUNCTION_CALL = (
|
FIXTURE_FUNCTION_CALL = UnformattedWarning(
|
||||||
|
RemovedInPytest4Warning,
|
||||||
'Fixture "{name}" called directly. Fixtures are not meant to be called directly, '
|
'Fixture "{name}" called directly. Fixtures are not meant to be called directly, '
|
||||||
"are created automatically when test functions request them as parameters. "
|
"are created automatically when test functions request them as parameters. "
|
||||||
"See https://docs.pytest.org/en/latest/fixture.html for more information."
|
"See https://docs.pytest.org/en/latest/fixture.html for more information.",
|
||||||
)
|
)
|
||||||
|
|
||||||
CFG_PYTEST_SECTION = (
|
CFG_PYTEST_SECTION = UnformattedWarning(
|
||||||
"[pytest] section in {filename} files is deprecated, use [tool:pytest] instead."
|
RemovedInPytest4Warning,
|
||||||
|
"[pytest] section in {filename} files is deprecated, use [tool:pytest] instead.",
|
||||||
)
|
)
|
||||||
|
|
||||||
GETFUNCARGVALUE = "getfuncargvalue is deprecated, use getfixturevalue"
|
GETFUNCARGVALUE = RemovedInPytest4Warning(
|
||||||
|
"getfuncargvalue is deprecated, use getfixturevalue"
|
||||||
|
)
|
||||||
|
|
||||||
RESULT_LOG = (
|
RESULT_LOG = RemovedInPytest4Warning(
|
||||||
"--result-log is deprecated and scheduled for removal in pytest 4.0.\n"
|
"--result-log is deprecated and scheduled for removal in pytest 4.0.\n"
|
||||||
"See https://docs.pytest.org/en/latest/usage.html#creating-resultlog-format-files for more information."
|
"See https://docs.pytest.org/en/latest/usage.html#creating-resultlog-format-files for more information."
|
||||||
)
|
)
|
||||||
|
@ -81,3 +109,8 @@ PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST = RemovedInPytest4Warning(
|
||||||
PYTEST_NAMESPACE = RemovedInPytest4Warning(
|
PYTEST_NAMESPACE = RemovedInPytest4Warning(
|
||||||
"pytest_namespace is deprecated and will be removed soon"
|
"pytest_namespace is deprecated and will be removed soon"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
PYTEST_ENSURETEMP = RemovedInPytest4Warning(
|
||||||
|
"pytest/tmpdir_factory.ensuretemp is deprecated, \n"
|
||||||
|
"please use the tmp_path fixture or tmp_path_factory.mktemp"
|
||||||
|
)
|
||||||
|
|
|
@ -32,7 +32,7 @@ from _pytest.compat import (
|
||||||
get_real_method,
|
get_real_method,
|
||||||
_PytestWrapper,
|
_PytestWrapper,
|
||||||
)
|
)
|
||||||
from _pytest.deprecated import FIXTURE_FUNCTION_CALL, RemovedInPytest4Warning
|
from _pytest.deprecated import FIXTURE_FUNCTION_CALL
|
||||||
from _pytest.outcomes import fail, TEST_OUTCOME
|
from _pytest.outcomes import fail, TEST_OUTCOME
|
||||||
|
|
||||||
FIXTURE_MSG = 'fixtures cannot have "pytest_funcarg__" prefix and be decorated with @pytest.fixture:\n{}'
|
FIXTURE_MSG = 'fixtures cannot have "pytest_funcarg__" prefix and be decorated with @pytest.fixture:\n{}'
|
||||||
|
@ -481,6 +481,9 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||||
or ``session`` indicating the caching lifecycle of the resource.
|
or ``session`` indicating the caching lifecycle of the resource.
|
||||||
:arg extrakey: added to internal caching key of (funcargname, scope).
|
:arg extrakey: added to internal caching key of (funcargname, scope).
|
||||||
"""
|
"""
|
||||||
|
from _pytest.deprecated import CACHED_SETUP
|
||||||
|
|
||||||
|
warnings.warn(CACHED_SETUP, stacklevel=2)
|
||||||
if not hasattr(self.config, "_setupcache"):
|
if not hasattr(self.config, "_setupcache"):
|
||||||
self.config._setupcache = {} # XXX weakref?
|
self.config._setupcache = {} # XXX weakref?
|
||||||
cachekey = (self.fixturename, self._getscopeitem(scope), extrakey)
|
cachekey = (self.fixturename, self._getscopeitem(scope), extrakey)
|
||||||
|
@ -514,7 +517,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||||
""" Deprecated, use getfixturevalue. """
|
""" Deprecated, use getfixturevalue. """
|
||||||
from _pytest import deprecated
|
from _pytest import deprecated
|
||||||
|
|
||||||
warnings.warn(deprecated.GETFUNCARGVALUE, DeprecationWarning, stacklevel=2)
|
warnings.warn(deprecated.GETFUNCARGVALUE, stacklevel=2)
|
||||||
return self.getfixturevalue(argname)
|
return self.getfixturevalue(argname)
|
||||||
|
|
||||||
def _get_active_fixturedef(self, argname):
|
def _get_active_fixturedef(self, argname):
|
||||||
|
@ -576,7 +579,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||||
nodeid=funcitem.nodeid,
|
nodeid=funcitem.nodeid,
|
||||||
typename=type(funcitem).__name__,
|
typename=type(funcitem).__name__,
|
||||||
)
|
)
|
||||||
fail(msg)
|
fail(msg, pytrace=False)
|
||||||
if has_params:
|
if has_params:
|
||||||
frame = inspect.stack()[3]
|
frame = inspect.stack()[3]
|
||||||
frameinfo = inspect.getframeinfo(frame[0])
|
frameinfo = inspect.getframeinfo(frame[0])
|
||||||
|
@ -597,7 +600,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||||
source_lineno,
|
source_lineno,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
fail(msg)
|
fail(msg, pytrace=False)
|
||||||
else:
|
else:
|
||||||
# indices might not be set if old-style metafunc.addcall() was used
|
# indices might not be set if old-style metafunc.addcall() was used
|
||||||
param_index = funcitem.callspec.indices.get(argname, 0)
|
param_index = funcitem.callspec.indices.get(argname, 0)
|
||||||
|
@ -715,10 +718,11 @@ def scope2index(scope, descr, where=None):
|
||||||
try:
|
try:
|
||||||
return scopes.index(scope)
|
return scopes.index(scope)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise ValueError(
|
fail(
|
||||||
"{} {}has an unsupported scope value '{}'".format(
|
"{} {}got an unexpected scope value '{}'".format(
|
||||||
descr, "from {} ".format(where) if where else "", scope
|
descr, "from {} ".format(where) if where else "", scope
|
||||||
)
|
),
|
||||||
|
pytrace=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -851,7 +855,9 @@ class FixtureDef(object):
|
||||||
self.argname = argname
|
self.argname = argname
|
||||||
self.scope = scope
|
self.scope = scope
|
||||||
self.scopenum = scope2index(
|
self.scopenum = scope2index(
|
||||||
scope or "function", descr="fixture {}".format(func.__name__), where=baseid
|
scope or "function",
|
||||||
|
descr="Fixture '{}'".format(func.__name__),
|
||||||
|
where=baseid,
|
||||||
)
|
)
|
||||||
self.params = params
|
self.params = params
|
||||||
self.argnames = getfuncargnames(func, is_method=unittest)
|
self.argnames = getfuncargnames(func, is_method=unittest)
|
||||||
|
@ -913,7 +919,7 @@ class FixtureDef(object):
|
||||||
return hook.pytest_fixture_setup(fixturedef=self, request=request)
|
return hook.pytest_fixture_setup(fixturedef=self, request=request)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<FixtureDef name=%r scope=%r baseid=%r >" % (
|
return "<FixtureDef name=%r scope=%r baseid=%r>" % (
|
||||||
self.argname,
|
self.argname,
|
||||||
self.scope,
|
self.scope,
|
||||||
self.baseid,
|
self.baseid,
|
||||||
|
@ -973,8 +979,9 @@ def wrap_function_to_warning_if_called_directly(function, fixture_marker):
|
||||||
used as an argument in a test function.
|
used as an argument in a test function.
|
||||||
"""
|
"""
|
||||||
is_yield_function = is_generator(function)
|
is_yield_function = is_generator(function)
|
||||||
msg = FIXTURE_FUNCTION_CALL.format(name=fixture_marker.name or function.__name__)
|
warning = FIXTURE_FUNCTION_CALL.format(
|
||||||
warning = RemovedInPytest4Warning(msg)
|
name=fixture_marker.name or function.__name__
|
||||||
|
)
|
||||||
|
|
||||||
if is_yield_function:
|
if is_yield_function:
|
||||||
|
|
||||||
|
@ -1168,7 +1175,7 @@ class FixtureManager(object):
|
||||||
def pytest_plugin_registered(self, plugin):
|
def pytest_plugin_registered(self, plugin):
|
||||||
nodeid = None
|
nodeid = None
|
||||||
try:
|
try:
|
||||||
p = py.path.local(plugin.__file__)
|
p = py.path.local(plugin.__file__).realpath()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
|
@ -1301,9 +1308,7 @@ class FixtureManager(object):
|
||||||
|
|
||||||
filename, lineno = getfslineno(obj)
|
filename, lineno = getfslineno(obj)
|
||||||
warnings.warn_explicit(
|
warnings.warn_explicit(
|
||||||
RemovedInPytest4Warning(
|
deprecated.FUNCARG_PREFIX.format(name=name),
|
||||||
deprecated.FUNCARG_PREFIX.format(name=name)
|
|
||||||
),
|
|
||||||
category=None,
|
category=None,
|
||||||
filename=str(filename),
|
filename=str(filename),
|
||||||
lineno=lineno + 1,
|
lineno=lineno + 1,
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from contextlib import closing, contextmanager
|
from contextlib import contextmanager
|
||||||
import re
|
import re
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
@ -415,7 +415,6 @@ class LoggingPlugin(object):
|
||||||
else:
|
else:
|
||||||
self.log_file_handler = None
|
self.log_file_handler = None
|
||||||
|
|
||||||
# initialized during pytest_runtestloop
|
|
||||||
self.log_cli_handler = None
|
self.log_cli_handler = None
|
||||||
|
|
||||||
def _log_cli_enabled(self):
|
def _log_cli_enabled(self):
|
||||||
|
@ -426,6 +425,22 @@ class LoggingPlugin(object):
|
||||||
"--log-cli-level"
|
"--log-cli-level"
|
||||||
) is not None or self._config.getini("log_cli")
|
) is not None or self._config.getini("log_cli")
|
||||||
|
|
||||||
|
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
|
||||||
|
def pytest_collection(self):
|
||||||
|
# This has to be called before the first log message is logged,
|
||||||
|
# so we can access the terminal reporter plugin.
|
||||||
|
self._setup_cli_logging()
|
||||||
|
|
||||||
|
with self.live_logs_context():
|
||||||
|
if self.log_cli_handler:
|
||||||
|
self.log_cli_handler.set_when("collection")
|
||||||
|
|
||||||
|
if self.log_file_handler is not None:
|
||||||
|
with catching_logs(self.log_file_handler, level=self.log_file_level):
|
||||||
|
yield
|
||||||
|
else:
|
||||||
|
yield
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def _runtest_for(self, item, when):
|
def _runtest_for(self, item, when):
|
||||||
"""Implements the internals of pytest_runtest_xxx() hook."""
|
"""Implements the internals of pytest_runtest_xxx() hook."""
|
||||||
|
@ -485,22 +500,15 @@ class LoggingPlugin(object):
|
||||||
@pytest.hookimpl(hookwrapper=True)
|
@pytest.hookimpl(hookwrapper=True)
|
||||||
def pytest_runtestloop(self, session):
|
def pytest_runtestloop(self, session):
|
||||||
"""Runs all collected test items."""
|
"""Runs all collected test items."""
|
||||||
self._setup_cli_logging()
|
with self.live_logs_context():
|
||||||
with self.live_logs_context:
|
|
||||||
if self.log_file_handler is not None:
|
if self.log_file_handler is not None:
|
||||||
with closing(self.log_file_handler):
|
with catching_logs(self.log_file_handler, level=self.log_file_level):
|
||||||
with catching_logs(
|
yield # run all the tests
|
||||||
self.log_file_handler, level=self.log_file_level
|
|
||||||
):
|
|
||||||
yield # run all the tests
|
|
||||||
else:
|
else:
|
||||||
yield # run all the tests
|
yield # run all the tests
|
||||||
|
|
||||||
def _setup_cli_logging(self):
|
def _setup_cli_logging(self):
|
||||||
"""Sets up the handler and logger for the Live Logs feature, if enabled.
|
"""Sets up the handler and logger for the Live Logs feature, if enabled."""
|
||||||
|
|
||||||
This must be done right before starting the loop so we can access the terminal reporter plugin.
|
|
||||||
"""
|
|
||||||
terminal_reporter = self._config.pluginmanager.get_plugin("terminalreporter")
|
terminal_reporter = self._config.pluginmanager.get_plugin("terminalreporter")
|
||||||
if self._log_cli_enabled() and terminal_reporter is not None:
|
if self._log_cli_enabled() and terminal_reporter is not None:
|
||||||
capture_manager = self._config.pluginmanager.get_plugin("capturemanager")
|
capture_manager = self._config.pluginmanager.get_plugin("capturemanager")
|
||||||
|
@ -530,11 +538,14 @@ class LoggingPlugin(object):
|
||||||
self._config, "log_cli_level", "log_level"
|
self._config, "log_cli_level", "log_level"
|
||||||
)
|
)
|
||||||
self.log_cli_handler = log_cli_handler
|
self.log_cli_handler = log_cli_handler
|
||||||
self.live_logs_context = catching_logs(
|
self.live_logs_context = lambda: catching_logs(
|
||||||
log_cli_handler, formatter=log_cli_formatter, level=log_cli_level
|
log_cli_handler, formatter=log_cli_formatter, level=log_cli_level
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.live_logs_context = dummy_context_manager()
|
self.live_logs_context = lambda: dummy_context_manager()
|
||||||
|
# Note that the lambda for the live_logs_context is needed because
|
||||||
|
# live_logs_context can otherwise not be entered multiple times due
|
||||||
|
# to limitations of contextlib.contextmanager
|
||||||
|
|
||||||
|
|
||||||
class _LiveLoggingStreamHandler(logging.StreamHandler):
|
class _LiveLoggingStreamHandler(logging.StreamHandler):
|
||||||
|
|
|
@ -156,7 +156,10 @@ def pytest_addoption(parser):
|
||||||
dest="basetemp",
|
dest="basetemp",
|
||||||
default=None,
|
default=None,
|
||||||
metavar="dir",
|
metavar="dir",
|
||||||
help="base temporary directory for this test run.",
|
help=(
|
||||||
|
"base temporary directory for this test run."
|
||||||
|
"(warning: this directory is removed if it exists)"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -182,10 +185,13 @@ def wrap_session(config, doit):
|
||||||
session.exitstatus = EXIT_TESTSFAILED
|
session.exitstatus = EXIT_TESTSFAILED
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
excinfo = _pytest._code.ExceptionInfo()
|
excinfo = _pytest._code.ExceptionInfo()
|
||||||
if initstate < 2 and isinstance(excinfo.value, exit.Exception):
|
exitstatus = EXIT_INTERRUPTED
|
||||||
|
if initstate <= 2 and isinstance(excinfo.value, exit.Exception):
|
||||||
sys.stderr.write("{}: {}\n".format(excinfo.typename, excinfo.value.msg))
|
sys.stderr.write("{}: {}\n".format(excinfo.typename, excinfo.value.msg))
|
||||||
|
if excinfo.value.returncode is not None:
|
||||||
|
exitstatus = excinfo.value.returncode
|
||||||
config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
|
config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
|
||||||
session.exitstatus = EXIT_INTERRUPTED
|
session.exitstatus = exitstatus
|
||||||
except: # noqa
|
except: # noqa
|
||||||
excinfo = _pytest._code.ExceptionInfo()
|
excinfo = _pytest._code.ExceptionInfo()
|
||||||
config.notify_exception(excinfo, config.option)
|
config.notify_exception(excinfo, config.option)
|
||||||
|
@ -487,7 +493,7 @@ class Session(nodes.FSCollector):
|
||||||
from _pytest.python import Package
|
from _pytest.python import Package
|
||||||
|
|
||||||
names = self._parsearg(arg)
|
names = self._parsearg(arg)
|
||||||
argpath = names.pop(0)
|
argpath = names.pop(0).realpath()
|
||||||
paths = []
|
paths = []
|
||||||
|
|
||||||
root = self
|
root = self
|
||||||
|
|
|
@ -24,11 +24,6 @@ __all__ = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class MarkerError(Exception):
|
|
||||||
|
|
||||||
"""Error in use of a pytest marker/attribute."""
|
|
||||||
|
|
||||||
|
|
||||||
def param(*values, **kw):
|
def param(*values, **kw):
|
||||||
"""Specify a parameter in `pytest.mark.parametrize`_ calls or
|
"""Specify a parameter in `pytest.mark.parametrize`_ calls or
|
||||||
:ref:`parametrized fixtures <fixture-parametrize-marks>`.
|
:ref:`parametrized fixtures <fixture-parametrize-marks>`.
|
||||||
|
@ -163,9 +158,9 @@ def pytest_configure(config):
|
||||||
|
|
||||||
empty_parameterset = config.getini(EMPTY_PARAMETERSET_OPTION)
|
empty_parameterset = config.getini(EMPTY_PARAMETERSET_OPTION)
|
||||||
|
|
||||||
if empty_parameterset not in ("skip", "xfail", None, ""):
|
if empty_parameterset not in ("skip", "xfail", "fail_at_collect", None, ""):
|
||||||
raise UsageError(
|
raise UsageError(
|
||||||
"{!s} must be one of skip and xfail,"
|
"{!s} must be one of skip, xfail or fail_at_collect"
|
||||||
" but it is {!r}".format(EMPTY_PARAMETERSET_OPTION, empty_parameterset)
|
" but it is {!r}".format(EMPTY_PARAMETERSET_OPTION, empty_parameterset)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ from operator import attrgetter
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
|
|
||||||
|
from _pytest.outcomes import fail
|
||||||
from ..deprecated import MARK_PARAMETERSET_UNPACKING, MARK_INFO_ATTRIBUTE
|
from ..deprecated import MARK_PARAMETERSET_UNPACKING, MARK_INFO_ATTRIBUTE
|
||||||
from ..compat import NOTSET, getfslineno, MappingMixin
|
from ..compat import NOTSET, getfslineno, MappingMixin
|
||||||
from six.moves import map
|
from six.moves import map
|
||||||
|
@ -32,11 +33,19 @@ def istestfunc(func):
|
||||||
|
|
||||||
|
|
||||||
def get_empty_parameterset_mark(config, argnames, func):
|
def get_empty_parameterset_mark(config, argnames, func):
|
||||||
|
from ..nodes import Collector
|
||||||
|
|
||||||
requested_mark = config.getini(EMPTY_PARAMETERSET_OPTION)
|
requested_mark = config.getini(EMPTY_PARAMETERSET_OPTION)
|
||||||
if requested_mark in ("", None, "skip"):
|
if requested_mark in ("", None, "skip"):
|
||||||
mark = MARK_GEN.skip
|
mark = MARK_GEN.skip
|
||||||
elif requested_mark == "xfail":
|
elif requested_mark == "xfail":
|
||||||
mark = MARK_GEN.xfail(run=False)
|
mark = MARK_GEN.xfail(run=False)
|
||||||
|
elif requested_mark == "fail_at_collect":
|
||||||
|
f_name = func.__name__
|
||||||
|
_, lineno = getfslineno(func)
|
||||||
|
raise Collector.CollectError(
|
||||||
|
"Empty parameter set in '%s' at line %d" % (f_name, lineno)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
raise LookupError(requested_mark)
|
raise LookupError(requested_mark)
|
||||||
fs, lineno = getfslineno(func)
|
fs, lineno = getfslineno(func)
|
||||||
|
@ -307,7 +316,7 @@ def _marked(func, mark):
|
||||||
return any(mark == info.combined for info in func_mark)
|
return any(mark == info.combined for info in func_mark)
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
@attr.s(repr=False)
|
||||||
class MarkInfo(object):
|
class MarkInfo(object):
|
||||||
""" Marking object created by :class:`MarkDecorator` instances. """
|
""" Marking object created by :class:`MarkDecorator` instances. """
|
||||||
|
|
||||||
|
@ -385,7 +394,7 @@ class MarkGenerator(object):
|
||||||
x = marker.split("(", 1)[0]
|
x = marker.split("(", 1)[0]
|
||||||
values.add(x)
|
values.add(x)
|
||||||
if name not in self._markers:
|
if name not in self._markers:
|
||||||
raise AttributeError("%r not a registered marker" % (name,))
|
fail("{!r} not a registered marker".format(name), pytrace=False)
|
||||||
|
|
||||||
|
|
||||||
MARK_GEN = MarkGenerator()
|
MARK_GEN = MarkGenerator()
|
||||||
|
|
|
@ -9,6 +9,7 @@ import attr
|
||||||
import _pytest
|
import _pytest
|
||||||
import _pytest._code
|
import _pytest._code
|
||||||
from _pytest.compat import getfslineno
|
from _pytest.compat import getfslineno
|
||||||
|
from _pytest.outcomes import fail
|
||||||
|
|
||||||
from _pytest.mark.structures import NodeKeywords, MarkInfo
|
from _pytest.mark.structures import NodeKeywords, MarkInfo
|
||||||
|
|
||||||
|
@ -61,11 +62,11 @@ class _CompatProperty(object):
|
||||||
if obj is None:
|
if obj is None:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
# TODO: reenable in the features branch
|
from _pytest.deprecated import COMPAT_PROPERTY
|
||||||
# warnings.warn(
|
|
||||||
# "usage of {owner!r}.{name} is deprecated, please use pytest.{name} instead".format(
|
warnings.warn(
|
||||||
# name=self.name, owner=type(owner).__name__),
|
COMPAT_PROPERTY.format(name=self.name, owner=owner.__name__), stacklevel=2
|
||||||
# PendingDeprecationWarning, stacklevel=2)
|
)
|
||||||
return getattr(__import__("pytest"), self.name)
|
return getattr(__import__("pytest"), self.name)
|
||||||
|
|
||||||
|
|
||||||
|
@ -126,11 +127,10 @@ class Node(object):
|
||||||
if isinstance(maybe_compatprop, _CompatProperty):
|
if isinstance(maybe_compatprop, _CompatProperty):
|
||||||
return getattr(__import__("pytest"), name)
|
return getattr(__import__("pytest"), name)
|
||||||
else:
|
else:
|
||||||
|
from _pytest.deprecated import CUSTOM_CLASS
|
||||||
|
|
||||||
cls = getattr(self, name)
|
cls = getattr(self, name)
|
||||||
# TODO: reenable in the features branch
|
self.warn(CUSTOM_CLASS.format(name=name, type_name=type(self).__name__))
|
||||||
# warnings.warn("use of node.%s is deprecated, "
|
|
||||||
# "use pytest_pycollect_makeitem(...) to create custom "
|
|
||||||
# "collection nodes" % name, category=DeprecationWarning)
|
|
||||||
return cls
|
return cls
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
@ -347,6 +347,9 @@ class Node(object):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _repr_failure_py(self, excinfo, style=None):
|
def _repr_failure_py(self, excinfo, style=None):
|
||||||
|
if excinfo.errisinstance(fail.Exception):
|
||||||
|
if not excinfo.value.pytrace:
|
||||||
|
return six.text_type(excinfo.value)
|
||||||
fm = self.session._fixturemanager
|
fm = self.session._fixturemanager
|
||||||
if excinfo.errisinstance(fm.FixtureLookupError):
|
if excinfo.errisinstance(fm.FixtureLookupError):
|
||||||
return excinfo.value.formatrepr()
|
return excinfo.value.formatrepr()
|
||||||
|
|
|
@ -49,18 +49,24 @@ class Failed(OutcomeException):
|
||||||
class Exit(KeyboardInterrupt):
|
class Exit(KeyboardInterrupt):
|
||||||
""" raised for immediate program exits (no tracebacks/summaries)"""
|
""" raised for immediate program exits (no tracebacks/summaries)"""
|
||||||
|
|
||||||
def __init__(self, msg="unknown reason"):
|
def __init__(self, msg="unknown reason", returncode=None):
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
|
self.returncode = returncode
|
||||||
KeyboardInterrupt.__init__(self, msg)
|
KeyboardInterrupt.__init__(self, msg)
|
||||||
|
|
||||||
|
|
||||||
# exposed helper methods
|
# exposed helper methods
|
||||||
|
|
||||||
|
|
||||||
def exit(msg):
|
def exit(msg, returncode=None):
|
||||||
""" exit testing process as if KeyboardInterrupt was triggered. """
|
"""
|
||||||
|
Exit testing process as if KeyboardInterrupt was triggered.
|
||||||
|
|
||||||
|
:param str msg: message to display upon exit.
|
||||||
|
:param int returncode: return code to be used when exiting pytest.
|
||||||
|
"""
|
||||||
__tracebackhide__ = True
|
__tracebackhide__ = True
|
||||||
raise Exit(msg)
|
raise Exit(msg, returncode)
|
||||||
|
|
||||||
|
|
||||||
exit.Exception = Exit
|
exit.Exception = Exit
|
||||||
|
|
|
@ -0,0 +1,283 @@
|
||||||
|
|
||||||
|
import os
|
||||||
|
import errno
|
||||||
|
import atexit
|
||||||
|
import operator
|
||||||
|
import six
|
||||||
|
import sys
|
||||||
|
from functools import reduce
|
||||||
|
import uuid
|
||||||
|
from six.moves import map
|
||||||
|
import itertools
|
||||||
|
import shutil
|
||||||
|
from os.path import expanduser, expandvars, isabs, sep
|
||||||
|
from posixpath import sep as posix_sep
|
||||||
|
import fnmatch
|
||||||
|
import stat
|
||||||
|
|
||||||
|
from .compat import PY36
|
||||||
|
|
||||||
|
|
||||||
|
if PY36:
|
||||||
|
from pathlib import Path, PurePath
|
||||||
|
else:
|
||||||
|
from pathlib2 import Path, PurePath
|
||||||
|
|
||||||
|
__all__ = ["Path", "PurePath"]
|
||||||
|
|
||||||
|
|
||||||
|
LOCK_TIMEOUT = 60 * 60 * 3
|
||||||
|
|
||||||
|
get_lock_path = operator.methodcaller("joinpath", ".lock")
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_reset_dir(path):
|
||||||
|
"""
|
||||||
|
ensures the given path is a empty directory
|
||||||
|
"""
|
||||||
|
if path.exists():
|
||||||
|
rmtree(path, force=True)
|
||||||
|
path.mkdir()
|
||||||
|
|
||||||
|
|
||||||
|
def _shutil_rmtree_remove_writable(func, fspath, _):
|
||||||
|
"Clear the readonly bit and reattempt the removal"
|
||||||
|
os.chmod(fspath, stat.S_IWRITE)
|
||||||
|
func(fspath)
|
||||||
|
|
||||||
|
|
||||||
|
def rmtree(path, force=False):
|
||||||
|
if force:
|
||||||
|
# ignore_errors leaves dead folders around
|
||||||
|
# python needs a rm -rf as a followup
|
||||||
|
# the trick with _shutil_rmtree_remove_writable is unreliable
|
||||||
|
shutil.rmtree(str(path), ignore_errors=True)
|
||||||
|
else:
|
||||||
|
shutil.rmtree(str(path))
|
||||||
|
|
||||||
|
|
||||||
|
def find_prefixed(root, prefix):
|
||||||
|
"""finds all elements in root that begin with the prefix, case insensitive"""
|
||||||
|
l_prefix = prefix.lower()
|
||||||
|
for x in root.iterdir():
|
||||||
|
if x.name.lower().startswith(l_prefix):
|
||||||
|
yield x
|
||||||
|
|
||||||
|
|
||||||
|
def extract_suffixes(iter, prefix):
|
||||||
|
"""
|
||||||
|
:param iter: iterator over path names
|
||||||
|
:param prefix: expected prefix of the path names
|
||||||
|
:returns: the parts of the paths following the prefix
|
||||||
|
"""
|
||||||
|
p_len = len(prefix)
|
||||||
|
for p in iter:
|
||||||
|
yield p.name[p_len:]
|
||||||
|
|
||||||
|
|
||||||
|
def find_suffixes(root, prefix):
|
||||||
|
"""combines find_prefixes and extract_suffixes
|
||||||
|
"""
|
||||||
|
return extract_suffixes(find_prefixed(root, prefix), prefix)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_num(maybe_num):
|
||||||
|
"""parses number path suffixes, returns -1 on error"""
|
||||||
|
try:
|
||||||
|
return int(maybe_num)
|
||||||
|
except ValueError:
|
||||||
|
return -1
|
||||||
|
|
||||||
|
|
||||||
|
if six.PY2:
|
||||||
|
|
||||||
|
def _max(iterable, default):
|
||||||
|
"""needed due to python2.7 lacking the default argument for max"""
|
||||||
|
return reduce(max, iterable, default)
|
||||||
|
|
||||||
|
|
||||||
|
else:
|
||||||
|
_max = max
|
||||||
|
|
||||||
|
|
||||||
|
def make_numbered_dir(root, prefix):
|
||||||
|
"""create a directory with a increased number as suffix for the given prefix"""
|
||||||
|
for i in range(10):
|
||||||
|
# try up to 10 times to create the folder
|
||||||
|
max_existing = _max(map(parse_num, find_suffixes(root, prefix)), default=-1)
|
||||||
|
new_number = max_existing + 1
|
||||||
|
new_path = root.joinpath("{}{}".format(prefix, new_number))
|
||||||
|
try:
|
||||||
|
new_path.mkdir()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return new_path
|
||||||
|
else:
|
||||||
|
raise EnvironmentError(
|
||||||
|
"could not create numbered dir with prefix "
|
||||||
|
"{prefix} in {root} after 10 tries".format(prefix=prefix, root=root)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def create_cleanup_lock(p):
|
||||||
|
"""crates a lock to prevent premature folder cleanup"""
|
||||||
|
lock_path = get_lock_path(p)
|
||||||
|
try:
|
||||||
|
fd = os.open(str(lock_path), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o644)
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno == errno.EEXIST:
|
||||||
|
six.raise_from(
|
||||||
|
EnvironmentError("cannot create lockfile in {path}".format(path=p)), e
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
pid = os.getpid()
|
||||||
|
spid = str(pid)
|
||||||
|
if not isinstance(spid, six.binary_type):
|
||||||
|
spid = spid.encode("ascii")
|
||||||
|
os.write(fd, spid)
|
||||||
|
os.close(fd)
|
||||||
|
if not lock_path.is_file():
|
||||||
|
raise EnvironmentError("lock path got renamed after sucessfull creation")
|
||||||
|
return lock_path
|
||||||
|
|
||||||
|
|
||||||
|
def register_cleanup_lock_removal(lock_path, register=atexit.register):
|
||||||
|
"""registers a cleanup function for removing a lock, by default on atexit"""
|
||||||
|
pid = os.getpid()
|
||||||
|
|
||||||
|
def cleanup_on_exit(lock_path=lock_path, original_pid=pid):
|
||||||
|
current_pid = os.getpid()
|
||||||
|
if current_pid != original_pid:
|
||||||
|
# fork
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
lock_path.unlink()
|
||||||
|
except (OSError, IOError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
return register(cleanup_on_exit)
|
||||||
|
|
||||||
|
|
||||||
|
def delete_a_numbered_dir(path):
|
||||||
|
"""removes a numbered directory"""
|
||||||
|
create_cleanup_lock(path)
|
||||||
|
parent = path.parent
|
||||||
|
|
||||||
|
garbage = parent.joinpath("garbage-{}".format(uuid.uuid4()))
|
||||||
|
path.rename(garbage)
|
||||||
|
rmtree(garbage, force=True)
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_deletable(path, consider_lock_dead_if_created_before):
|
||||||
|
"""checks if a lock exists and breaks it if its considered dead"""
|
||||||
|
if path.is_symlink():
|
||||||
|
return False
|
||||||
|
lock = get_lock_path(path)
|
||||||
|
if not lock.exists():
|
||||||
|
return True
|
||||||
|
try:
|
||||||
|
lock_time = lock.stat().st_mtime
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
if lock_time < consider_lock_dead_if_created_before:
|
||||||
|
lock.unlink()
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def try_cleanup(path, consider_lock_dead_if_created_before):
|
||||||
|
"""tries to cleanup a folder if we can ensure its deletable"""
|
||||||
|
if ensure_deletable(path, consider_lock_dead_if_created_before):
|
||||||
|
delete_a_numbered_dir(path)
|
||||||
|
|
||||||
|
|
||||||
|
def cleanup_candidates(root, prefix, keep):
|
||||||
|
"""lists candidates for numbered directories to be removed - follows py.path"""
|
||||||
|
max_existing = _max(map(parse_num, find_suffixes(root, prefix)), default=-1)
|
||||||
|
max_delete = max_existing - keep
|
||||||
|
paths = find_prefixed(root, prefix)
|
||||||
|
paths, paths2 = itertools.tee(paths)
|
||||||
|
numbers = map(parse_num, extract_suffixes(paths2, prefix))
|
||||||
|
for path, number in zip(paths, numbers):
|
||||||
|
if number <= max_delete:
|
||||||
|
yield path
|
||||||
|
|
||||||
|
|
||||||
|
def cleanup_numbered_dir(root, prefix, keep, consider_lock_dead_if_created_before):
|
||||||
|
"""cleanup for lock driven numbered directories"""
|
||||||
|
for path in cleanup_candidates(root, prefix, keep):
|
||||||
|
try_cleanup(path, consider_lock_dead_if_created_before)
|
||||||
|
for path in root.glob("garbage-*"):
|
||||||
|
try_cleanup(path, consider_lock_dead_if_created_before)
|
||||||
|
|
||||||
|
|
||||||
|
def make_numbered_dir_with_cleanup(root, prefix, keep, lock_timeout):
|
||||||
|
"""creates a numbered dir with a cleanup lock and removes old ones"""
|
||||||
|
e = None
|
||||||
|
for i in range(10):
|
||||||
|
try:
|
||||||
|
p = make_numbered_dir(root, prefix)
|
||||||
|
lock_path = create_cleanup_lock(p)
|
||||||
|
register_cleanup_lock_removal(lock_path)
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
consider_lock_dead_if_created_before = p.stat().st_mtime - lock_timeout
|
||||||
|
cleanup_numbered_dir(
|
||||||
|
root=root,
|
||||||
|
prefix=prefix,
|
||||||
|
keep=keep,
|
||||||
|
consider_lock_dead_if_created_before=consider_lock_dead_if_created_before,
|
||||||
|
)
|
||||||
|
return p
|
||||||
|
assert e is not None
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_from_str(input, root):
|
||||||
|
assert not isinstance(input, Path), "would break on py2"
|
||||||
|
root = Path(root)
|
||||||
|
input = expanduser(input)
|
||||||
|
input = expandvars(input)
|
||||||
|
if isabs(input):
|
||||||
|
return Path(input)
|
||||||
|
else:
|
||||||
|
return root.joinpath(input)
|
||||||
|
|
||||||
|
|
||||||
|
def fnmatch_ex(pattern, path):
|
||||||
|
"""FNMatcher port from py.path.common which works with PurePath() instances.
|
||||||
|
|
||||||
|
The difference between this algorithm and PurePath.match() is that the latter matches "**" glob expressions
|
||||||
|
for each part of the path, while this algorithm uses the whole path instead.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
"tests/foo/bar/doc/test_foo.py" matches pattern "tests/**/doc/test*.py" with this algorithm, but not with
|
||||||
|
PurePath.match().
|
||||||
|
|
||||||
|
This algorithm was ported to keep backward-compatibility with existing settings which assume paths match according
|
||||||
|
this logic.
|
||||||
|
|
||||||
|
References:
|
||||||
|
* https://bugs.python.org/issue29249
|
||||||
|
* https://bugs.python.org/issue34731
|
||||||
|
"""
|
||||||
|
path = PurePath(path)
|
||||||
|
iswin32 = sys.platform.startswith("win")
|
||||||
|
|
||||||
|
if iswin32 and sep not in pattern and posix_sep in pattern:
|
||||||
|
# Running on Windows, the pattern has no Windows path separators,
|
||||||
|
# and the pattern has one or more Posix path separators. Replace
|
||||||
|
# the Posix path separators with the Windows path separator.
|
||||||
|
pattern = pattern.replace(posix_sep, sep)
|
||||||
|
|
||||||
|
if sep not in pattern:
|
||||||
|
name = path.name
|
||||||
|
else:
|
||||||
|
name = six.text_type(path)
|
||||||
|
return fnmatch.fnmatch(name, pattern)
|
|
@ -1,52 +0,0 @@
|
||||||
from os.path import expanduser, expandvars, isabs, sep
|
|
||||||
from posixpath import sep as posix_sep
|
|
||||||
import fnmatch
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import six
|
|
||||||
|
|
||||||
from .compat import Path, PurePath
|
|
||||||
|
|
||||||
|
|
||||||
def resolve_from_str(input, root):
|
|
||||||
assert not isinstance(input, Path), "would break on py2"
|
|
||||||
root = Path(root)
|
|
||||||
input = expanduser(input)
|
|
||||||
input = expandvars(input)
|
|
||||||
if isabs(input):
|
|
||||||
return Path(input)
|
|
||||||
else:
|
|
||||||
return root.joinpath(input)
|
|
||||||
|
|
||||||
|
|
||||||
def fnmatch_ex(pattern, path):
|
|
||||||
"""FNMatcher port from py.path.common which works with PurePath() instances.
|
|
||||||
|
|
||||||
The difference between this algorithm and PurePath.match() is that the latter matches "**" glob expressions
|
|
||||||
for each part of the path, while this algorithm uses the whole path instead.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
"tests/foo/bar/doc/test_foo.py" matches pattern "tests/**/doc/test*.py" with this algorithm, but not with
|
|
||||||
PurePath.match().
|
|
||||||
|
|
||||||
This algorithm was ported to keep backward-compatibility with existing settings which assume paths match according
|
|
||||||
this logic.
|
|
||||||
|
|
||||||
References:
|
|
||||||
* https://bugs.python.org/issue29249
|
|
||||||
* https://bugs.python.org/issue34731
|
|
||||||
"""
|
|
||||||
path = PurePath(path)
|
|
||||||
iswin32 = sys.platform.startswith("win")
|
|
||||||
|
|
||||||
if iswin32 and sep not in pattern and posix_sep in pattern:
|
|
||||||
# Running on Windows, the pattern has no Windows path separators,
|
|
||||||
# and the pattern has one or more Posix path separators. Replace
|
|
||||||
# the Posix path separators with the Windows path separator.
|
|
||||||
pattern = pattern.replace(posix_sep, sep)
|
|
||||||
|
|
||||||
if sep not in pattern:
|
|
||||||
name = path.name
|
|
||||||
else:
|
|
||||||
name = six.text_type(path)
|
|
||||||
return fnmatch.fnmatch(name, pattern)
|
|
|
@ -17,13 +17,14 @@ from weakref import WeakKeyDictionary
|
||||||
|
|
||||||
from _pytest.capture import MultiCapture, SysCapture
|
from _pytest.capture import MultiCapture, SysCapture
|
||||||
from _pytest._code import Source
|
from _pytest._code import Source
|
||||||
import py
|
|
||||||
import pytest
|
|
||||||
from _pytest.main import Session, EXIT_INTERRUPTED, EXIT_OK
|
from _pytest.main import Session, EXIT_INTERRUPTED, EXIT_OK
|
||||||
from _pytest.assertion.rewrite import AssertionRewritingHook
|
from _pytest.assertion.rewrite import AssertionRewritingHook
|
||||||
from _pytest.compat import Path
|
from _pytest.pathlib import Path
|
||||||
from _pytest.compat import safe_str
|
from _pytest.compat import safe_str
|
||||||
|
|
||||||
|
import py
|
||||||
|
import pytest
|
||||||
|
|
||||||
IGNORE_PAM = [ # filenames added when obtaining details about the current user
|
IGNORE_PAM = [ # filenames added when obtaining details about the current user
|
||||||
u"/var/lib/sss/mc/passwd"
|
u"/var/lib/sss/mc/passwd"
|
||||||
]
|
]
|
||||||
|
@ -61,6 +62,11 @@ def pytest_configure(config):
|
||||||
config.pluginmanager.register(checker)
|
config.pluginmanager.register(checker)
|
||||||
|
|
||||||
|
|
||||||
|
def raise_on_kwargs(kwargs):
|
||||||
|
if kwargs:
|
||||||
|
raise TypeError("Unexpected arguments: {}".format(", ".join(sorted(kwargs))))
|
||||||
|
|
||||||
|
|
||||||
class LsofFdLeakChecker(object):
|
class LsofFdLeakChecker(object):
|
||||||
def get_open_files(self):
|
def get_open_files(self):
|
||||||
out = self._exec_lsof()
|
out = self._exec_lsof()
|
||||||
|
@ -482,11 +488,16 @@ class Testdir(object):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
class TimeoutExpired(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
def __init__(self, request, tmpdir_factory):
|
def __init__(self, request, tmpdir_factory):
|
||||||
self.request = request
|
self.request = request
|
||||||
self._mod_collections = WeakKeyDictionary()
|
self._mod_collections = WeakKeyDictionary()
|
||||||
name = request.function.__name__
|
name = request.function.__name__
|
||||||
self.tmpdir = tmpdir_factory.mktemp(name, numbered=True)
|
self.tmpdir = tmpdir_factory.mktemp(name, numbered=True)
|
||||||
|
self.test_tmproot = tmpdir_factory.mktemp("tmp-" + name, numbered=True)
|
||||||
|
os.environ["PYTEST_DEBUG_TEMPROOT"] = str(self.test_tmproot)
|
||||||
self.plugins = []
|
self.plugins = []
|
||||||
self._cwd_snapshot = CwdSnapshot()
|
self._cwd_snapshot = CwdSnapshot()
|
||||||
self._sys_path_snapshot = SysPathsSnapshot()
|
self._sys_path_snapshot = SysPathsSnapshot()
|
||||||
|
@ -513,6 +524,7 @@ class Testdir(object):
|
||||||
self._sys_modules_snapshot.restore()
|
self._sys_modules_snapshot.restore()
|
||||||
self._sys_path_snapshot.restore()
|
self._sys_path_snapshot.restore()
|
||||||
self._cwd_snapshot.restore()
|
self._cwd_snapshot.restore()
|
||||||
|
os.environ.pop("PYTEST_DEBUG_TEMPROOT", None)
|
||||||
|
|
||||||
def __take_sys_modules_snapshot(self):
|
def __take_sys_modules_snapshot(self):
|
||||||
# some zope modules used by twisted-related tests keep internal state
|
# some zope modules used by twisted-related tests keep internal state
|
||||||
|
@ -1039,14 +1051,23 @@ class Testdir(object):
|
||||||
|
|
||||||
return popen
|
return popen
|
||||||
|
|
||||||
def run(self, *cmdargs):
|
def run(self, *cmdargs, **kwargs):
|
||||||
"""Run a command with arguments.
|
"""Run a command with arguments.
|
||||||
|
|
||||||
Run a process using subprocess.Popen saving the stdout and stderr.
|
Run a process using subprocess.Popen saving the stdout and stderr.
|
||||||
|
|
||||||
|
:param args: the sequence of arguments to pass to `subprocess.Popen()`
|
||||||
|
:param timeout: the period in seconds after which to timeout and raise
|
||||||
|
:py:class:`Testdir.TimeoutExpired`
|
||||||
|
|
||||||
Returns a :py:class:`RunResult`.
|
Returns a :py:class:`RunResult`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
__tracebackhide__ = True
|
||||||
|
|
||||||
|
timeout = kwargs.pop("timeout", None)
|
||||||
|
raise_on_kwargs(kwargs)
|
||||||
|
|
||||||
cmdargs = [
|
cmdargs = [
|
||||||
str(arg) if isinstance(arg, py.path.local) else arg for arg in cmdargs
|
str(arg) if isinstance(arg, py.path.local) else arg for arg in cmdargs
|
||||||
]
|
]
|
||||||
|
@ -1061,7 +1082,40 @@ class Testdir(object):
|
||||||
popen = self.popen(
|
popen = self.popen(
|
||||||
cmdargs, stdout=f1, stderr=f2, close_fds=(sys.platform != "win32")
|
cmdargs, stdout=f1, stderr=f2, close_fds=(sys.platform != "win32")
|
||||||
)
|
)
|
||||||
ret = popen.wait()
|
|
||||||
|
def handle_timeout():
|
||||||
|
__tracebackhide__ = True
|
||||||
|
|
||||||
|
timeout_message = (
|
||||||
|
"{seconds} second timeout expired running:"
|
||||||
|
" {command}".format(seconds=timeout, command=cmdargs)
|
||||||
|
)
|
||||||
|
|
||||||
|
popen.kill()
|
||||||
|
popen.wait()
|
||||||
|
raise self.TimeoutExpired(timeout_message)
|
||||||
|
|
||||||
|
if timeout is None:
|
||||||
|
ret = popen.wait()
|
||||||
|
elif six.PY3:
|
||||||
|
try:
|
||||||
|
ret = popen.wait(timeout)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
handle_timeout()
|
||||||
|
else:
|
||||||
|
end = time.time() + timeout
|
||||||
|
|
||||||
|
resolution = min(0.1, timeout / 10)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
ret = popen.poll()
|
||||||
|
if ret is not None:
|
||||||
|
break
|
||||||
|
|
||||||
|
if time.time() > end:
|
||||||
|
handle_timeout()
|
||||||
|
|
||||||
|
time.sleep(resolution)
|
||||||
finally:
|
finally:
|
||||||
f1.close()
|
f1.close()
|
||||||
f2.close()
|
f2.close()
|
||||||
|
@ -1108,9 +1162,15 @@ class Testdir(object):
|
||||||
with "runpytest-" so they do not conflict with the normal numbered
|
with "runpytest-" so they do not conflict with the normal numbered
|
||||||
pytest location for temporary files and directories.
|
pytest location for temporary files and directories.
|
||||||
|
|
||||||
|
:param args: the sequence of arguments to pass to the pytest subprocess
|
||||||
|
:param timeout: the period in seconds after which to timeout and raise
|
||||||
|
:py:class:`Testdir.TimeoutExpired`
|
||||||
|
|
||||||
Returns a :py:class:`RunResult`.
|
Returns a :py:class:`RunResult`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
__tracebackhide__ = True
|
||||||
|
|
||||||
p = py.path.local.make_numbered_dir(
|
p = py.path.local.make_numbered_dir(
|
||||||
prefix="runpytest-", keep=None, rootdir=self.tmpdir
|
prefix="runpytest-", keep=None, rootdir=self.tmpdir
|
||||||
)
|
)
|
||||||
|
@ -1119,7 +1179,7 @@ class Testdir(object):
|
||||||
if plugins:
|
if plugins:
|
||||||
args = ("-p", plugins[0]) + args
|
args = ("-p", plugins[0]) + args
|
||||||
args = self._getpytestargs() + args
|
args = self._getpytestargs() + args
|
||||||
return self.run(*args)
|
return self.run(*args, timeout=kwargs.get("timeout"))
|
||||||
|
|
||||||
def spawn_pytest(self, string, expect_timeout=10.0):
|
def spawn_pytest(self, string, expect_timeout=10.0):
|
||||||
"""Run pytest using pexpect.
|
"""Run pytest using pexpect.
|
||||||
|
@ -1267,6 +1327,7 @@ class LineMatcher(object):
|
||||||
matches and non-matches are also printed on stdout.
|
matches and non-matches are also printed on stdout.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
__tracebackhide__ = True
|
||||||
self._match_lines(lines2, fnmatch, "fnmatch")
|
self._match_lines(lines2, fnmatch, "fnmatch")
|
||||||
|
|
||||||
def re_match_lines(self, lines2):
|
def re_match_lines(self, lines2):
|
||||||
|
@ -1278,6 +1339,7 @@ class LineMatcher(object):
|
||||||
The matches and non-matches are also printed on stdout.
|
The matches and non-matches are also printed on stdout.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
__tracebackhide__ = True
|
||||||
self._match_lines(lines2, lambda name, pat: re.match(pat, name), "re.match")
|
self._match_lines(lines2, lambda name, pat: re.match(pat, name), "re.match")
|
||||||
|
|
||||||
def _match_lines(self, lines2, match_func, match_nickname):
|
def _match_lines(self, lines2, match_func, match_nickname):
|
||||||
|
|
|
@ -13,11 +13,10 @@ from textwrap import dedent
|
||||||
import py
|
import py
|
||||||
import six
|
import six
|
||||||
from _pytest.main import FSHookProxy
|
from _pytest.main import FSHookProxy
|
||||||
from _pytest.mark import MarkerError
|
|
||||||
from _pytest.config import hookimpl
|
from _pytest.config import hookimpl
|
||||||
|
|
||||||
import _pytest
|
import _pytest
|
||||||
import pluggy
|
from _pytest._code import filter_traceback
|
||||||
from _pytest import fixtures
|
from _pytest import fixtures
|
||||||
from _pytest import nodes
|
from _pytest import nodes
|
||||||
from _pytest import deprecated
|
from _pytest import deprecated
|
||||||
|
@ -47,37 +46,6 @@ from _pytest.mark.structures import (
|
||||||
)
|
)
|
||||||
from _pytest.warning_types import RemovedInPytest4Warning, PytestWarning
|
from _pytest.warning_types import RemovedInPytest4Warning, PytestWarning
|
||||||
|
|
||||||
# relative paths that we use to filter traceback entries from appearing to the user;
|
|
||||||
# see filter_traceback
|
|
||||||
# note: if we need to add more paths than what we have now we should probably use a list
|
|
||||||
# for better maintenance
|
|
||||||
_pluggy_dir = py.path.local(pluggy.__file__.rstrip("oc"))
|
|
||||||
# pluggy is either a package or a single module depending on the version
|
|
||||||
if _pluggy_dir.basename == "__init__.py":
|
|
||||||
_pluggy_dir = _pluggy_dir.dirpath()
|
|
||||||
_pytest_dir = py.path.local(_pytest.__file__).dirpath()
|
|
||||||
_py_dir = py.path.local(py.__file__).dirpath()
|
|
||||||
|
|
||||||
|
|
||||||
def filter_traceback(entry):
|
|
||||||
"""Return True if a TracebackEntry instance should be removed from tracebacks:
|
|
||||||
* dynamically generated code (no code to show up for it);
|
|
||||||
* internal traceback from pytest or its internal libraries, py and pluggy.
|
|
||||||
"""
|
|
||||||
# entry.path might sometimes return a str object when the entry
|
|
||||||
# points to dynamically generated code
|
|
||||||
# see https://bitbucket.org/pytest-dev/py/issues/71
|
|
||||||
raw_filename = entry.frame.code.raw.co_filename
|
|
||||||
is_generated = "<" in raw_filename and ">" in raw_filename
|
|
||||||
if is_generated:
|
|
||||||
return False
|
|
||||||
# entry.path might point to a non-existing file, in which case it will
|
|
||||||
# also return a str object. see #1133
|
|
||||||
p = py.path.local(entry.path)
|
|
||||||
return (
|
|
||||||
not p.relto(_pluggy_dir) and not p.relto(_pytest_dir) and not p.relto(_py_dir)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def pyobj_property(name):
|
def pyobj_property(name):
|
||||||
def get(self):
|
def get(self):
|
||||||
|
@ -159,8 +127,8 @@ def pytest_generate_tests(metafunc):
|
||||||
alt_spellings = ["parameterize", "parametrise", "parameterise"]
|
alt_spellings = ["parameterize", "parametrise", "parameterise"]
|
||||||
for attr in alt_spellings:
|
for attr in alt_spellings:
|
||||||
if hasattr(metafunc.function, attr):
|
if hasattr(metafunc.function, attr):
|
||||||
msg = "{0} has '{1}', spelling should be 'parametrize'"
|
msg = "{0} has '{1}' mark, spelling should be 'parametrize'"
|
||||||
raise MarkerError(msg.format(metafunc.function.__name__, attr))
|
fail(msg.format(metafunc.function.__name__, attr), pytrace=False)
|
||||||
for marker in metafunc.definition.iter_markers(name="parametrize"):
|
for marker in metafunc.definition.iter_markers(name="parametrize"):
|
||||||
metafunc.parametrize(*marker.args, **marker.kwargs)
|
metafunc.parametrize(*marker.args, **marker.kwargs)
|
||||||
|
|
||||||
|
@ -760,12 +728,6 @@ class FunctionMixin(PyobjMixin):
|
||||||
for entry in excinfo.traceback[1:-1]:
|
for entry in excinfo.traceback[1:-1]:
|
||||||
entry.set_repr_style("short")
|
entry.set_repr_style("short")
|
||||||
|
|
||||||
def _repr_failure_py(self, excinfo, style="long"):
|
|
||||||
if excinfo.errisinstance(fail.Exception):
|
|
||||||
if not excinfo.value.pytrace:
|
|
||||||
return six.text_type(excinfo.value)
|
|
||||||
return super(FunctionMixin, self)._repr_failure_py(excinfo, style=style)
|
|
||||||
|
|
||||||
def repr_failure(self, excinfo, outerr=None):
|
def repr_failure(self, excinfo, outerr=None):
|
||||||
assert outerr is None, "XXX outerr usage is deprecated"
|
assert outerr is None, "XXX outerr usage is deprecated"
|
||||||
style = self.config.option.tbstyle
|
style = self.config.option.tbstyle
|
||||||
|
@ -799,7 +761,10 @@ class Generator(FunctionMixin, PyCollector):
|
||||||
"%r generated tests with non-unique name %r" % (self, name)
|
"%r generated tests with non-unique name %r" % (self, name)
|
||||||
)
|
)
|
||||||
seen[name] = True
|
seen[name] = True
|
||||||
values.append(self.Function(name, self, args=args, callobj=call))
|
with warnings.catch_warnings():
|
||||||
|
# ignore our own deprecation warning
|
||||||
|
function_class = self.Function
|
||||||
|
values.append(function_class(name, self, args=args, callobj=call))
|
||||||
self.warn(deprecated.YIELD_TESTS)
|
self.warn(deprecated.YIELD_TESTS)
|
||||||
return values
|
return values
|
||||||
|
|
||||||
|
@ -984,7 +949,9 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
|
||||||
|
|
||||||
ids = self._resolve_arg_ids(argnames, ids, parameters, item=self.definition)
|
ids = self._resolve_arg_ids(argnames, ids, parameters, item=self.definition)
|
||||||
|
|
||||||
scopenum = scope2index(scope, descr="call to {}".format(self.parametrize))
|
scopenum = scope2index(
|
||||||
|
scope, descr="parametrize() call in {}".format(self.function.__name__)
|
||||||
|
)
|
||||||
|
|
||||||
# create the new calls: if we are parametrize() multiple times (by applying the decorator
|
# create the new calls: if we are parametrize() multiple times (by applying the decorator
|
||||||
# more than once) then we accumulate those calls generating the cartesian product
|
# more than once) then we accumulate those calls generating the cartesian product
|
||||||
|
@ -1023,15 +990,16 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
|
||||||
idfn = ids
|
idfn = ids
|
||||||
ids = None
|
ids = None
|
||||||
if ids:
|
if ids:
|
||||||
|
func_name = self.function.__name__
|
||||||
if len(ids) != len(parameters):
|
if len(ids) != len(parameters):
|
||||||
raise ValueError(
|
msg = "In {}: {} parameter sets specified, with different number of ids: {}"
|
||||||
"%d tests specified with %d ids" % (len(parameters), len(ids))
|
fail(msg.format(func_name, len(parameters), len(ids)), pytrace=False)
|
||||||
)
|
|
||||||
for id_value in ids:
|
for id_value in ids:
|
||||||
if id_value is not None and not isinstance(id_value, six.string_types):
|
if id_value is not None and not isinstance(id_value, six.string_types):
|
||||||
msg = "ids must be list of strings, found: %s (type: %s)"
|
msg = "In {}: ids must be list of strings, found: {} (type: {!r})"
|
||||||
raise ValueError(
|
fail(
|
||||||
msg % (saferepr(id_value), type(id_value).__name__)
|
msg.format(func_name, saferepr(id_value), type(id_value)),
|
||||||
|
pytrace=False,
|
||||||
)
|
)
|
||||||
ids = idmaker(argnames, parameters, idfn, ids, self.config, item=item)
|
ids = idmaker(argnames, parameters, idfn, ids, self.config, item=item)
|
||||||
return ids
|
return ids
|
||||||
|
@ -1056,9 +1024,11 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
|
||||||
valtypes = dict.fromkeys(argnames, "funcargs")
|
valtypes = dict.fromkeys(argnames, "funcargs")
|
||||||
for arg in indirect:
|
for arg in indirect:
|
||||||
if arg not in argnames:
|
if arg not in argnames:
|
||||||
raise ValueError(
|
fail(
|
||||||
"indirect given to %r: fixture %r doesn't exist"
|
"In {}: indirect fixture '{}' doesn't exist".format(
|
||||||
% (self.function, arg)
|
self.function.__name__, arg
|
||||||
|
),
|
||||||
|
pytrace=False,
|
||||||
)
|
)
|
||||||
valtypes[arg] = "params"
|
valtypes[arg] = "params"
|
||||||
return valtypes
|
return valtypes
|
||||||
|
@ -1072,19 +1042,25 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
|
||||||
:raise ValueError: if validation fails.
|
:raise ValueError: if validation fails.
|
||||||
"""
|
"""
|
||||||
default_arg_names = set(get_default_arg_names(self.function))
|
default_arg_names = set(get_default_arg_names(self.function))
|
||||||
|
func_name = self.function.__name__
|
||||||
for arg in argnames:
|
for arg in argnames:
|
||||||
if arg not in self.fixturenames:
|
if arg not in self.fixturenames:
|
||||||
if arg in default_arg_names:
|
if arg in default_arg_names:
|
||||||
raise ValueError(
|
fail(
|
||||||
"%r already takes an argument %r with a default value"
|
"In {}: function already takes an argument '{}' with a default value".format(
|
||||||
% (self.function, arg)
|
func_name, arg
|
||||||
|
),
|
||||||
|
pytrace=False,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
if isinstance(indirect, (tuple, list)):
|
if isinstance(indirect, (tuple, list)):
|
||||||
name = "fixture" if arg in indirect else "argument"
|
name = "fixture" if arg in indirect else "argument"
|
||||||
else:
|
else:
|
||||||
name = "fixture" if indirect else "argument"
|
name = "fixture" if indirect else "argument"
|
||||||
raise ValueError("%r uses no %s %r" % (self.function, name, arg))
|
fail(
|
||||||
|
"In {}: function uses no {} '{}'".format(func_name, name, arg),
|
||||||
|
pytrace=False,
|
||||||
|
)
|
||||||
|
|
||||||
def addcall(self, funcargs=None, id=NOTSET, param=NOTSET):
|
def addcall(self, funcargs=None, id=NOTSET, param=NOTSET):
|
||||||
""" Add a new call to the underlying test function during the collection phase of a test run.
|
""" Add a new call to the underlying test function during the collection phase of a test run.
|
||||||
|
|
|
@ -43,45 +43,10 @@ def deprecated_call(func=None, *args, **kwargs):
|
||||||
in which case it will ensure calling ``func(*args, **kwargs)`` produces one of the warnings
|
in which case it will ensure calling ``func(*args, **kwargs)`` produces one of the warnings
|
||||||
types above.
|
types above.
|
||||||
"""
|
"""
|
||||||
if not func:
|
__tracebackhide__ = True
|
||||||
return _DeprecatedCallContext()
|
if func is not None:
|
||||||
else:
|
args = (func,) + args
|
||||||
__tracebackhide__ = True
|
return warns((DeprecationWarning, PendingDeprecationWarning), *args, **kwargs)
|
||||||
with _DeprecatedCallContext():
|
|
||||||
return func(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class _DeprecatedCallContext(object):
|
|
||||||
"""Implements the logic to capture deprecation warnings as a context manager."""
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
self._captured_categories = []
|
|
||||||
self._old_warn = warnings.warn
|
|
||||||
self._old_warn_explicit = warnings.warn_explicit
|
|
||||||
warnings.warn_explicit = self._warn_explicit
|
|
||||||
warnings.warn = self._warn
|
|
||||||
|
|
||||||
def _warn_explicit(self, message, category, *args, **kwargs):
|
|
||||||
self._captured_categories.append(category)
|
|
||||||
|
|
||||||
def _warn(self, message, category=None, *args, **kwargs):
|
|
||||||
if isinstance(message, Warning):
|
|
||||||
self._captured_categories.append(message.__class__)
|
|
||||||
else:
|
|
||||||
self._captured_categories.append(category)
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
||||||
warnings.warn_explicit = self._old_warn_explicit
|
|
||||||
warnings.warn = self._old_warn
|
|
||||||
|
|
||||||
if exc_type is None:
|
|
||||||
deprecation_categories = (DeprecationWarning, PendingDeprecationWarning)
|
|
||||||
if not any(
|
|
||||||
issubclass(c, deprecation_categories) for c in self._captured_categories
|
|
||||||
):
|
|
||||||
__tracebackhide__ = True
|
|
||||||
msg = "Did not produce DeprecationWarning or PendingDeprecationWarning"
|
|
||||||
raise AssertionError(msg)
|
|
||||||
|
|
||||||
|
|
||||||
def warns(expected_warning, *args, **kwargs):
|
def warns(expected_warning, *args, **kwargs):
|
||||||
|
@ -116,6 +81,7 @@ def warns(expected_warning, *args, **kwargs):
|
||||||
Failed: DID NOT WARN. No warnings of type ...UserWarning... was emitted...
|
Failed: DID NOT WARN. No warnings of type ...UserWarning... was emitted...
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
__tracebackhide__ = True
|
||||||
match_expr = None
|
match_expr = None
|
||||||
if not args:
|
if not args:
|
||||||
if "match" in kwargs:
|
if "match" in kwargs:
|
||||||
|
@ -183,12 +149,25 @@ class WarningsRecorder(warnings.catch_warnings):
|
||||||
raise RuntimeError("Cannot enter %r twice" % self)
|
raise RuntimeError("Cannot enter %r twice" % self)
|
||||||
self._list = super(WarningsRecorder, self).__enter__()
|
self._list = super(WarningsRecorder, self).__enter__()
|
||||||
warnings.simplefilter("always")
|
warnings.simplefilter("always")
|
||||||
|
# python3 keeps track of a "filter version", when the filters are
|
||||||
|
# updated previously seen warnings can be re-warned. python2 has no
|
||||||
|
# concept of this so we must reset the warnings registry manually.
|
||||||
|
# trivial patching of `warnings.warn` seems to be enough somehow?
|
||||||
|
if six.PY2:
|
||||||
|
|
||||||
|
def warn(*args, **kwargs):
|
||||||
|
return self._saved_warn(*args, **kwargs)
|
||||||
|
|
||||||
|
warnings.warn, self._saved_warn = warn, warnings.warn
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, *exc_info):
|
def __exit__(self, *exc_info):
|
||||||
if not self._entered:
|
if not self._entered:
|
||||||
__tracebackhide__ = True
|
__tracebackhide__ = True
|
||||||
raise RuntimeError("Cannot exit %r without entering first" % self)
|
raise RuntimeError("Cannot exit %r without entering first" % self)
|
||||||
|
# see above where `self._saved_warn` is assigned
|
||||||
|
if six.PY2:
|
||||||
|
warnings.warn = self._saved_warn
|
||||||
super(WarningsRecorder, self).__exit__(*exc_info)
|
super(WarningsRecorder, self).__exit__(*exc_info)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -31,10 +31,9 @@ def pytest_configure(config):
|
||||||
config.pluginmanager.register(config._resultlog)
|
config.pluginmanager.register(config._resultlog)
|
||||||
|
|
||||||
from _pytest.deprecated import RESULT_LOG
|
from _pytest.deprecated import RESULT_LOG
|
||||||
from _pytest.warning_types import RemovedInPytest4Warning
|
|
||||||
from _pytest.warnings import _issue_config_warning
|
from _pytest.warnings import _issue_config_warning
|
||||||
|
|
||||||
_issue_config_warning(RemovedInPytest4Warning(RESULT_LOG), config)
|
_issue_config_warning(RESULT_LOG, config)
|
||||||
|
|
||||||
|
|
||||||
def pytest_unconfigure(config):
|
def pytest_unconfigure(config):
|
||||||
|
|
|
@ -676,7 +676,9 @@ class TerminalReporter(object):
|
||||||
|
|
||||||
if fspath:
|
if fspath:
|
||||||
res = mkrel(nodeid).replace("::()", "") # parens-normalization
|
res = mkrel(nodeid).replace("::()", "") # parens-normalization
|
||||||
if nodeid.split("::")[0] != fspath.replace("\\", nodes.SEP):
|
if self.verbosity >= 2 and nodeid.split("::")[0] != fspath.replace(
|
||||||
|
"\\", nodes.SEP
|
||||||
|
):
|
||||||
res += " <- " + self.startdir.bestrelpath(fspath)
|
res += " <- " + self.startdir.bestrelpath(fspath)
|
||||||
else:
|
else:
|
||||||
res = "[location]"
|
res = "[location]"
|
||||||
|
|
|
@ -1,22 +1,86 @@
|
||||||
""" support for providing temporary directories to test functions. """
|
""" support for providing temporary directories to test functions. """
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import py
|
import py
|
||||||
from _pytest.monkeypatch import MonkeyPatch
|
from _pytest.monkeypatch import MonkeyPatch
|
||||||
|
import attr
|
||||||
|
import tempfile
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
from .pathlib import (
|
||||||
|
Path,
|
||||||
|
make_numbered_dir,
|
||||||
|
make_numbered_dir_with_cleanup,
|
||||||
|
ensure_reset_dir,
|
||||||
|
LOCK_TIMEOUT,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TempdirFactory(object):
|
@attr.s
|
||||||
|
class TempPathFactory(object):
|
||||||
"""Factory for temporary directories under the common base temp directory.
|
"""Factory for temporary directories under the common base temp directory.
|
||||||
|
|
||||||
The base directory can be configured using the ``--basetemp`` option.
|
The base directory can be configured using the ``--basetemp`` option."""
|
||||||
|
|
||||||
|
_given_basetemp = attr.ib()
|
||||||
|
_trace = attr.ib()
|
||||||
|
_basetemp = attr.ib(default=None)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_config(cls, config):
|
||||||
|
"""
|
||||||
|
:param config: a pytest configuration
|
||||||
|
"""
|
||||||
|
return cls(
|
||||||
|
given_basetemp=config.option.basetemp, trace=config.trace.get("tmpdir")
|
||||||
|
)
|
||||||
|
|
||||||
|
def mktemp(self, basename, numbered=True):
|
||||||
|
"""makes a temporary directory managed by the factory"""
|
||||||
|
if not numbered:
|
||||||
|
p = self.getbasetemp().joinpath(basename)
|
||||||
|
p.mkdir()
|
||||||
|
else:
|
||||||
|
p = make_numbered_dir(root=self.getbasetemp(), prefix=basename)
|
||||||
|
self._trace("mktemp", p)
|
||||||
|
return p
|
||||||
|
|
||||||
|
def getbasetemp(self):
|
||||||
|
""" return base temporary directory. """
|
||||||
|
if self._basetemp is None:
|
||||||
|
if self._given_basetemp is not None:
|
||||||
|
basetemp = Path(self._given_basetemp)
|
||||||
|
ensure_reset_dir(basetemp)
|
||||||
|
else:
|
||||||
|
from_env = os.environ.get("PYTEST_DEBUG_TEMPROOT")
|
||||||
|
temproot = Path(from_env or tempfile.gettempdir())
|
||||||
|
user = get_user() or "unknown"
|
||||||
|
# use a sub-directory in the temproot to speed-up
|
||||||
|
# make_numbered_dir() call
|
||||||
|
rootdir = temproot.joinpath("pytest-of-{}".format(user))
|
||||||
|
rootdir.mkdir(exist_ok=True)
|
||||||
|
basetemp = make_numbered_dir_with_cleanup(
|
||||||
|
prefix="pytest-", root=rootdir, keep=3, lock_timeout=LOCK_TIMEOUT
|
||||||
|
)
|
||||||
|
assert basetemp is not None
|
||||||
|
self._basetemp = t = basetemp
|
||||||
|
self._trace("new basetemp", t)
|
||||||
|
return t
|
||||||
|
else:
|
||||||
|
return self._basetemp
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s
|
||||||
|
class TempdirFactory(object):
|
||||||
|
"""
|
||||||
|
backward comptibility wrapper that implements
|
||||||
|
:class:``py.path.local`` for :class:``TempPathFactory``
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, config):
|
_tmppath_factory = attr.ib()
|
||||||
self.config = config
|
|
||||||
self.trace = config.trace.get("tmpdir")
|
|
||||||
|
|
||||||
def ensuretemp(self, string, dir=1):
|
def ensuretemp(self, string, dir=1):
|
||||||
""" (deprecated) return temporary directory path with
|
""" (deprecated) return temporary directory path with
|
||||||
|
@ -26,6 +90,9 @@ class TempdirFactory(object):
|
||||||
and is guaranteed to be empty.
|
and is guaranteed to be empty.
|
||||||
"""
|
"""
|
||||||
# py.log._apiwarn(">1.1", "use tmpdir function argument")
|
# py.log._apiwarn(">1.1", "use tmpdir function argument")
|
||||||
|
from .deprecated import PYTEST_ENSURETEMP
|
||||||
|
|
||||||
|
warnings.warn(PYTEST_ENSURETEMP, stacklevel=2)
|
||||||
return self.getbasetemp().ensure(string, dir=dir)
|
return self.getbasetemp().ensure(string, dir=dir)
|
||||||
|
|
||||||
def mktemp(self, basename, numbered=True):
|
def mktemp(self, basename, numbered=True):
|
||||||
|
@ -33,46 +100,11 @@ class TempdirFactory(object):
|
||||||
If ``numbered``, ensure the directory is unique by adding a number
|
If ``numbered``, ensure the directory is unique by adding a number
|
||||||
prefix greater than any existing one.
|
prefix greater than any existing one.
|
||||||
"""
|
"""
|
||||||
basetemp = self.getbasetemp()
|
return py.path.local(self._tmppath_factory.mktemp(basename, numbered).resolve())
|
||||||
if not numbered:
|
|
||||||
p = basetemp.mkdir(basename)
|
|
||||||
else:
|
|
||||||
p = py.path.local.make_numbered_dir(
|
|
||||||
prefix=basename, keep=0, rootdir=basetemp, lock_timeout=None
|
|
||||||
)
|
|
||||||
self.trace("mktemp", p)
|
|
||||||
return p
|
|
||||||
|
|
||||||
def getbasetemp(self):
|
def getbasetemp(self):
|
||||||
""" return base temporary directory. """
|
"""backward compat wrapper for ``_tmppath_factory.getbasetemp``"""
|
||||||
try:
|
return py.path.local(self._tmppath_factory.getbasetemp().resolve())
|
||||||
return self._basetemp
|
|
||||||
except AttributeError:
|
|
||||||
basetemp = self.config.option.basetemp
|
|
||||||
if basetemp:
|
|
||||||
basetemp = py.path.local(basetemp)
|
|
||||||
if basetemp.check():
|
|
||||||
basetemp.remove()
|
|
||||||
basetemp.mkdir()
|
|
||||||
else:
|
|
||||||
temproot = py.path.local.get_temproot()
|
|
||||||
user = get_user()
|
|
||||||
if user:
|
|
||||||
# use a sub-directory in the temproot to speed-up
|
|
||||||
# make_numbered_dir() call
|
|
||||||
rootdir = temproot.join("pytest-of-%s" % user)
|
|
||||||
else:
|
|
||||||
rootdir = temproot
|
|
||||||
rootdir.ensure(dir=1)
|
|
||||||
basetemp = py.path.local.make_numbered_dir(
|
|
||||||
prefix="pytest-", rootdir=rootdir
|
|
||||||
)
|
|
||||||
self._basetemp = t = basetemp.realpath()
|
|
||||||
self.trace("new basetemp", t)
|
|
||||||
return t
|
|
||||||
|
|
||||||
def finish(self):
|
|
||||||
self.trace("finish")
|
|
||||||
|
|
||||||
|
|
||||||
def get_user():
|
def get_user():
|
||||||
|
@ -87,10 +119,6 @@ def get_user():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
# backward compatibility
|
|
||||||
TempdirHandler = TempdirFactory
|
|
||||||
|
|
||||||
|
|
||||||
def pytest_configure(config):
|
def pytest_configure(config):
|
||||||
"""Create a TempdirFactory and attach it to the config object.
|
"""Create a TempdirFactory and attach it to the config object.
|
||||||
|
|
||||||
|
@ -99,19 +127,36 @@ def pytest_configure(config):
|
||||||
to the tmpdir_factory session fixture.
|
to the tmpdir_factory session fixture.
|
||||||
"""
|
"""
|
||||||
mp = MonkeyPatch()
|
mp = MonkeyPatch()
|
||||||
t = TempdirFactory(config)
|
tmppath_handler = TempPathFactory.from_config(config)
|
||||||
config._cleanup.extend([mp.undo, t.finish])
|
t = TempdirFactory(tmppath_handler)
|
||||||
|
config._cleanup.append(mp.undo)
|
||||||
|
mp.setattr(config, "_tmp_path_factory", tmppath_handler, raising=False)
|
||||||
mp.setattr(config, "_tmpdirhandler", t, raising=False)
|
mp.setattr(config, "_tmpdirhandler", t, raising=False)
|
||||||
mp.setattr(pytest, "ensuretemp", t.ensuretemp, raising=False)
|
mp.setattr(pytest, "ensuretemp", t.ensuretemp, raising=False)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def tmpdir_factory(request):
|
def tmpdir_factory(request):
|
||||||
"""Return a TempdirFactory instance for the test session.
|
"""Return a :class:`_pytest.tmpdir.TempdirFactory` instance for the test session.
|
||||||
"""
|
"""
|
||||||
return request.config._tmpdirhandler
|
return request.config._tmpdirhandler
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def tmp_path_factory(request):
|
||||||
|
"""Return a :class:`_pytest.tmpdir.TempPathFactory` instance for the test session.
|
||||||
|
"""
|
||||||
|
return request.config._tmp_path_factory
|
||||||
|
|
||||||
|
|
||||||
|
def _mk_tmp(request, factory):
|
||||||
|
name = request.node.name
|
||||||
|
name = re.sub(r"[\W]", "_", name)
|
||||||
|
MAXVAL = 30
|
||||||
|
name = name[:MAXVAL]
|
||||||
|
return factory.mktemp(name, numbered=True)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def tmpdir(request, tmpdir_factory):
|
def tmpdir(request, tmpdir_factory):
|
||||||
"""Return a temporary directory path object
|
"""Return a temporary directory path object
|
||||||
|
@ -122,10 +167,20 @@ def tmpdir(request, tmpdir_factory):
|
||||||
|
|
||||||
.. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html
|
.. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html
|
||||||
"""
|
"""
|
||||||
name = request.node.name
|
return _mk_tmp(request, tmpdir_factory)
|
||||||
name = re.sub(r"[\W]", "_", name)
|
|
||||||
MAXVAL = 30
|
|
||||||
if len(name) > MAXVAL:
|
@pytest.fixture
|
||||||
name = name[:MAXVAL]
|
def tmp_path(request, tmp_path_factory):
|
||||||
x = tmpdir_factory.mktemp(name, numbered=True)
|
"""Return a temporary directory path object
|
||||||
return x
|
which is unique to each test function invocation,
|
||||||
|
created as a sub directory of the base temporary
|
||||||
|
directory. The returned object is a :class:`pathlib.Path`
|
||||||
|
object.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
in python < 3.6 this is a pathlib2.Path
|
||||||
|
"""
|
||||||
|
|
||||||
|
return _mk_tmp(request, tmp_path_factory)
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
import attr
|
||||||
|
|
||||||
|
|
||||||
class PytestWarning(UserWarning):
|
class PytestWarning(UserWarning):
|
||||||
"""
|
"""
|
||||||
Bases: :class:`UserWarning`.
|
Bases: :class:`UserWarning`.
|
||||||
|
@ -39,4 +42,19 @@ class PytestExperimentalApiWarning(PytestWarning, FutureWarning):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s
|
||||||
|
class UnformattedWarning(object):
|
||||||
|
"""Used to hold warnings that need to format their message at runtime, as opposed to a direct message.
|
||||||
|
|
||||||
|
Using this class avoids to keep all the warning types and messages in this module, avoiding misuse.
|
||||||
|
"""
|
||||||
|
|
||||||
|
category = attr.ib()
|
||||||
|
template = attr.ib()
|
||||||
|
|
||||||
|
def format(self, **kwargs):
|
||||||
|
"""Returns an instance of the warning category, formatted with given kwargs"""
|
||||||
|
return self.category(self.template.format(**kwargs))
|
||||||
|
|
||||||
|
|
||||||
PYTESTER_COPY_EXAMPLE = PytestExperimentalApiWarning.simple("testdir.copy_example")
|
PYTESTER_COPY_EXAMPLE = PytestExperimentalApiWarning.simple("testdir.copy_example")
|
||||||
|
|
|
@ -67,27 +67,27 @@ def catch_warnings_for_item(config, ihook, when, item):
|
||||||
|
|
||||||
Each warning captured triggers the ``pytest_warning_captured`` hook.
|
Each warning captured triggers the ``pytest_warning_captured`` hook.
|
||||||
"""
|
"""
|
||||||
args = config.getoption("pythonwarnings") or []
|
cmdline_filters = config.getoption("pythonwarnings") or []
|
||||||
inifilters = config.getini("filterwarnings")
|
inifilters = config.getini("filterwarnings")
|
||||||
with warnings.catch_warnings(record=True) as log:
|
with warnings.catch_warnings(record=True) as log:
|
||||||
filters_configured = args or inifilters or sys.warnoptions
|
|
||||||
|
|
||||||
for arg in args:
|
if not sys.warnoptions:
|
||||||
warnings._setoption(arg)
|
# if user is not explicitly configuring warning filters, show deprecation warnings by default (#2908)
|
||||||
|
warnings.filterwarnings("always", category=DeprecationWarning)
|
||||||
|
warnings.filterwarnings("always", category=PendingDeprecationWarning)
|
||||||
|
|
||||||
|
# filters should have this precedence: mark, cmdline options, ini
|
||||||
|
# filters should be applied in the inverse order of precedence
|
||||||
for arg in inifilters:
|
for arg in inifilters:
|
||||||
_setoption(warnings, arg)
|
_setoption(warnings, arg)
|
||||||
|
|
||||||
|
for arg in cmdline_filters:
|
||||||
|
warnings._setoption(arg)
|
||||||
|
|
||||||
if item is not None:
|
if item is not None:
|
||||||
for mark in item.iter_markers(name="filterwarnings"):
|
for mark in item.iter_markers(name="filterwarnings"):
|
||||||
for arg in mark.args:
|
for arg in mark.args:
|
||||||
_setoption(warnings, arg)
|
_setoption(warnings, arg)
|
||||||
filters_configured = True
|
|
||||||
|
|
||||||
if not filters_configured:
|
|
||||||
# if user is not explicitly configuring warning filters, show deprecation warnings by default (#2908)
|
|
||||||
warnings.filterwarnings("always", category=DeprecationWarning)
|
|
||||||
warnings.filterwarnings("always", category=PendingDeprecationWarning)
|
|
||||||
|
|
||||||
yield
|
yield
|
||||||
|
|
||||||
|
|
|
@ -140,9 +140,16 @@ class TestGeneralUsage(object):
|
||||||
assert result.ret
|
assert result.ret
|
||||||
result.stderr.fnmatch_lines(["*ERROR: not found:*{}".format(p2.basename)])
|
result.stderr.fnmatch_lines(["*ERROR: not found:*{}".format(p2.basename)])
|
||||||
|
|
||||||
def test_issue486_better_reporting_on_conftest_load_failure(self, testdir):
|
def test_better_reporting_on_conftest_load_failure(self, testdir, request):
|
||||||
|
"""Show a user-friendly traceback on conftest import failures (#486, #3332)"""
|
||||||
testdir.makepyfile("")
|
testdir.makepyfile("")
|
||||||
testdir.makeconftest("import qwerty")
|
testdir.makeconftest(
|
||||||
|
"""
|
||||||
|
def foo():
|
||||||
|
import qwerty
|
||||||
|
foo()
|
||||||
|
"""
|
||||||
|
)
|
||||||
result = testdir.runpytest("--help")
|
result = testdir.runpytest("--help")
|
||||||
result.stdout.fnmatch_lines(
|
result.stdout.fnmatch_lines(
|
||||||
"""
|
"""
|
||||||
|
@ -151,10 +158,23 @@ class TestGeneralUsage(object):
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
result = testdir.runpytest()
|
result = testdir.runpytest()
|
||||||
|
dirname = request.node.name + "0"
|
||||||
|
exc_name = (
|
||||||
|
"ModuleNotFoundError" if sys.version_info >= (3, 6) else "ImportError"
|
||||||
|
)
|
||||||
result.stderr.fnmatch_lines(
|
result.stderr.fnmatch_lines(
|
||||||
"""
|
[
|
||||||
*ERROR*could not load*conftest.py*
|
"ImportError while loading conftest '*{sep}{dirname}{sep}conftest.py'.".format(
|
||||||
"""
|
dirname=dirname, sep=os.sep
|
||||||
|
),
|
||||||
|
"conftest.py:3: in <module>",
|
||||||
|
" foo()",
|
||||||
|
"conftest.py:2: in foo",
|
||||||
|
" import qwerty",
|
||||||
|
"E {}: No module named {q}qwerty{q}".format(
|
||||||
|
exc_name, q="'" if six.PY3 else ""
|
||||||
|
),
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_early_skip(self, testdir):
|
def test_early_skip(self, testdir):
|
||||||
|
@ -723,16 +743,26 @@ class TestInvocationVariants(object):
|
||||||
monkeypatch.syspath_prepend(p)
|
monkeypatch.syspath_prepend(p)
|
||||||
|
|
||||||
# module picked up in symlink-ed directory:
|
# module picked up in symlink-ed directory:
|
||||||
|
# It picks up local/lib/foo/bar (symlink) via sys.path.
|
||||||
result = testdir.runpytest("--pyargs", "-v", "foo.bar")
|
result = testdir.runpytest("--pyargs", "-v", "foo.bar")
|
||||||
testdir.chdir()
|
testdir.chdir()
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
result.stdout.fnmatch_lines(
|
if hasattr(py.path.local, "mksymlinkto"):
|
||||||
[
|
result.stdout.fnmatch_lines(
|
||||||
"*lib/foo/bar/test_bar.py::test_bar PASSED*",
|
[
|
||||||
"*lib/foo/bar/test_bar.py::test_other PASSED*",
|
"lib/foo/bar/test_bar.py::test_bar PASSED*",
|
||||||
"*2 passed*",
|
"lib/foo/bar/test_bar.py::test_other PASSED*",
|
||||||
]
|
"*2 passed*",
|
||||||
)
|
]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
result.stdout.fnmatch_lines(
|
||||||
|
[
|
||||||
|
"*lib/foo/bar/test_bar.py::test_bar PASSED*",
|
||||||
|
"*lib/foo/bar/test_bar.py::test_other PASSED*",
|
||||||
|
"*2 passed*",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
def test_cmdline_python_package_not_exists(self, testdir):
|
def test_cmdline_python_package_not_exists(self, testdir):
|
||||||
result = testdir.runpytest("--pyargs", "tpkgwhatv")
|
result = testdir.runpytest("--pyargs", "tpkgwhatv")
|
||||||
|
|
|
@ -1184,20 +1184,28 @@ raise ValueError()
|
||||||
assert tw.lines[47] == ":15: AttributeError"
|
assert tw.lines[47] == ":15: AttributeError"
|
||||||
|
|
||||||
@pytest.mark.skipif("sys.version_info[0] < 3")
|
@pytest.mark.skipif("sys.version_info[0] < 3")
|
||||||
def test_exc_repr_with_raise_from_none_chain_suppression(self, importasmod):
|
@pytest.mark.parametrize("mode", ["from_none", "explicit_suppress"])
|
||||||
|
def test_exc_repr_chain_suppression(self, importasmod, mode):
|
||||||
|
"""Check that exc repr does not show chained exceptions in Python 3.
|
||||||
|
- When the exception is raised with "from None"
|
||||||
|
- Explicitly suppressed with "chain=False" to ExceptionInfo.getrepr().
|
||||||
|
"""
|
||||||
|
raise_suffix = " from None" if mode == "from_none" else ""
|
||||||
mod = importasmod(
|
mod = importasmod(
|
||||||
"""
|
"""
|
||||||
def f():
|
def f():
|
||||||
try:
|
try:
|
||||||
g()
|
g()
|
||||||
except Exception:
|
except Exception:
|
||||||
raise AttributeError() from None
|
raise AttributeError(){raise_suffix}
|
||||||
def g():
|
def g():
|
||||||
raise ValueError()
|
raise ValueError()
|
||||||
"""
|
""".format(
|
||||||
|
raise_suffix=raise_suffix
|
||||||
|
)
|
||||||
)
|
)
|
||||||
excinfo = pytest.raises(AttributeError, mod.f)
|
excinfo = pytest.raises(AttributeError, mod.f)
|
||||||
r = excinfo.getrepr(style="long")
|
r = excinfo.getrepr(style="long", chain=mode != "explicit_suppress")
|
||||||
tw = TWMock()
|
tw = TWMock()
|
||||||
r.toterminal(tw)
|
r.toterminal(tw)
|
||||||
for line in tw.lines:
|
for line in tw.lines:
|
||||||
|
@ -1207,7 +1215,9 @@ raise ValueError()
|
||||||
assert tw.lines[2] == " try:"
|
assert tw.lines[2] == " try:"
|
||||||
assert tw.lines[3] == " g()"
|
assert tw.lines[3] == " g()"
|
||||||
assert tw.lines[4] == " except Exception:"
|
assert tw.lines[4] == " except Exception:"
|
||||||
assert tw.lines[5] == "> raise AttributeError() from None"
|
assert tw.lines[5] == "> raise AttributeError(){}".format(
|
||||||
|
raise_suffix
|
||||||
|
)
|
||||||
assert tw.lines[6] == "E AttributeError"
|
assert tw.lines[6] == "E AttributeError"
|
||||||
assert tw.lines[7] == ""
|
assert tw.lines[7] == ""
|
||||||
line = tw.get_write_msg(8)
|
line = tw.get_write_msg(8)
|
||||||
|
|
|
@ -30,6 +30,74 @@ def test_yield_tests_deprecation(testdir):
|
||||||
assert result.stdout.str().count("yield tests are deprecated") == 2
|
assert result.stdout.str().count("yield tests are deprecated") == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_compat_properties_deprecation(testdir):
|
||||||
|
testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
def test_foo(request):
|
||||||
|
print(request.node.Module)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = testdir.runpytest()
|
||||||
|
result.stdout.fnmatch_lines(
|
||||||
|
[
|
||||||
|
"*test_compat_properties_deprecation.py:2:*usage of Function.Module is deprecated, "
|
||||||
|
"please use pytest.Module instead*",
|
||||||
|
"*1 passed, 1 warnings in*",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_cached_setup_deprecation(testdir):
|
||||||
|
testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
@pytest.fixture
|
||||||
|
def fix(request):
|
||||||
|
return request.cached_setup(lambda: 1)
|
||||||
|
|
||||||
|
def test_foo(fix):
|
||||||
|
assert fix == 1
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = testdir.runpytest()
|
||||||
|
result.stdout.fnmatch_lines(
|
||||||
|
[
|
||||||
|
"*test_cached_setup_deprecation.py:4:*cached_setup is deprecated*",
|
||||||
|
"*1 passed, 1 warnings in*",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_custom_class_deprecation(testdir):
|
||||||
|
testdir.makeconftest(
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
class MyModule(pytest.Module):
|
||||||
|
|
||||||
|
class Class(pytest.Class):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def pytest_pycollect_makemodule(path, parent):
|
||||||
|
return MyModule(path, parent)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
class Test:
|
||||||
|
def test_foo(self):
|
||||||
|
pass
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = testdir.runpytest()
|
||||||
|
result.stdout.fnmatch_lines(
|
||||||
|
[
|
||||||
|
'*test_custom_class_deprecation.py:1:*"Class" objects in collectors of type "MyModule*',
|
||||||
|
"*1 passed, 1 warnings in*",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.filterwarnings("default")
|
@pytest.mark.filterwarnings("default")
|
||||||
def test_funcarg_prefix_deprecation(testdir):
|
def test_funcarg_prefix_deprecation(testdir):
|
||||||
testdir.makepyfile(
|
testdir.makepyfile(
|
||||||
|
|
|
@ -908,3 +908,61 @@ def test_live_logging_suspends_capture(has_capture_manager, request):
|
||||||
else:
|
else:
|
||||||
assert MockCaptureManager.calls == []
|
assert MockCaptureManager.calls == []
|
||||||
assert out_file.getvalue() == "\nsome message\n"
|
assert out_file.getvalue() == "\nsome message\n"
|
||||||
|
|
||||||
|
|
||||||
|
def test_collection_live_logging(testdir):
|
||||||
|
testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logging.getLogger().info("Normal message")
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
result = testdir.runpytest("--log-cli-level=INFO")
|
||||||
|
result.stdout.fnmatch_lines(
|
||||||
|
[
|
||||||
|
"collecting*",
|
||||||
|
"*--- live log collection ---*",
|
||||||
|
"*Normal message*",
|
||||||
|
"collected 0 items",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_collection_logging_to_file(testdir):
|
||||||
|
log_file = testdir.tmpdir.join("pytest.log").strpath
|
||||||
|
|
||||||
|
testdir.makeini(
|
||||||
|
"""
|
||||||
|
[pytest]
|
||||||
|
log_file={}
|
||||||
|
log_file_level = INFO
|
||||||
|
""".format(
|
||||||
|
log_file
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logging.getLogger().info("Normal message")
|
||||||
|
|
||||||
|
def test_simple():
|
||||||
|
logging.getLogger().debug("debug message in test_simple")
|
||||||
|
logging.getLogger().info("info message in test_simple")
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
result = testdir.runpytest()
|
||||||
|
|
||||||
|
assert "--- live log collection ---" not in result.stdout.str()
|
||||||
|
|
||||||
|
assert result.ret == 0
|
||||||
|
assert os.path.isfile(log_file)
|
||||||
|
with open(log_file, encoding="utf-8") as rfh:
|
||||||
|
contents = rfh.read()
|
||||||
|
assert "Normal message" in contents
|
||||||
|
assert "debug message in test_simple" not in contents
|
||||||
|
assert "info message in test_simple" in contents
|
||||||
|
|
|
@ -240,6 +240,9 @@ class TestClass(object):
|
||||||
assert result.ret == EXIT_NOTESTSCOLLECTED
|
assert result.ret == EXIT_NOTESTSCOLLECTED
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.filterwarnings(
|
||||||
|
"ignore:usage of Generator.Function is deprecated, please use pytest.Function instead"
|
||||||
|
)
|
||||||
class TestGenerator(object):
|
class TestGenerator(object):
|
||||||
def test_generative_functions(self, testdir):
|
def test_generative_functions(self, testdir):
|
||||||
modcol = testdir.getmodulecol(
|
modcol = testdir.getmodulecol(
|
||||||
|
@ -1255,6 +1258,9 @@ class TestReportInfo(object):
|
||||||
assert lineno == 1
|
assert lineno == 1
|
||||||
assert msg == "TestClass"
|
assert msg == "TestClass"
|
||||||
|
|
||||||
|
@pytest.mark.filterwarnings(
|
||||||
|
"ignore:usage of Generator.Function is deprecated, please use pytest.Function instead"
|
||||||
|
)
|
||||||
def test_generator_reportinfo(self, testdir):
|
def test_generator_reportinfo(self, testdir):
|
||||||
modcol = testdir.getmodulecol(
|
modcol = testdir.getmodulecol(
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -6,7 +6,7 @@ import pytest
|
||||||
from _pytest.pytester import get_public_names
|
from _pytest.pytester import get_public_names
|
||||||
from _pytest.fixtures import FixtureLookupError, FixtureRequest
|
from _pytest.fixtures import FixtureLookupError, FixtureRequest
|
||||||
from _pytest import fixtures
|
from _pytest import fixtures
|
||||||
from _pytest.compat import Path
|
from _pytest.pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
def test_getfuncargnames():
|
def test_getfuncargnames():
|
||||||
|
@ -993,6 +993,7 @@ class TestRequestCachedSetup(object):
|
||||||
)
|
)
|
||||||
reprec.assertoutcome(passed=4)
|
reprec.assertoutcome(passed=4)
|
||||||
|
|
||||||
|
@pytest.mark.filterwarnings("ignore:cached_setup is deprecated")
|
||||||
def test_request_cachedsetup_extrakey(self, testdir):
|
def test_request_cachedsetup_extrakey(self, testdir):
|
||||||
item1 = testdir.getitem("def test_func(): pass")
|
item1 = testdir.getitem("def test_func(): pass")
|
||||||
req1 = fixtures.FixtureRequest(item1)
|
req1 = fixtures.FixtureRequest(item1)
|
||||||
|
@ -1010,6 +1011,7 @@ class TestRequestCachedSetup(object):
|
||||||
assert ret1 == ret1b
|
assert ret1 == ret1b
|
||||||
assert ret2 == ret2b
|
assert ret2 == ret2b
|
||||||
|
|
||||||
|
@pytest.mark.filterwarnings("ignore:cached_setup is deprecated")
|
||||||
def test_request_cachedsetup_cache_deletion(self, testdir):
|
def test_request_cachedsetup_cache_deletion(self, testdir):
|
||||||
item1 = testdir.getitem("def test_func(): pass")
|
item1 = testdir.getitem("def test_func(): pass")
|
||||||
req1 = fixtures.FixtureRequest(item1)
|
req1 = fixtures.FixtureRequest(item1)
|
||||||
|
@ -1221,8 +1223,7 @@ class TestFixtureUsages(object):
|
||||||
result = testdir.runpytest_inprocess()
|
result = testdir.runpytest_inprocess()
|
||||||
result.stdout.fnmatch_lines(
|
result.stdout.fnmatch_lines(
|
||||||
(
|
(
|
||||||
"*ValueError: fixture badscope from test_invalid_scope.py has an unsupported"
|
"*Fixture 'badscope' from test_invalid_scope.py got an unexpected scope value 'functions'"
|
||||||
" scope value 'functions'"
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -3611,16 +3612,15 @@ class TestParameterizedSubRequest(object):
|
||||||
)
|
)
|
||||||
result = testdir.runpytest()
|
result = testdir.runpytest()
|
||||||
result.stdout.fnmatch_lines(
|
result.stdout.fnmatch_lines(
|
||||||
"""
|
[
|
||||||
E*Failed: The requested fixture has no parameter defined for test:
|
"The requested fixture has no parameter defined for test:",
|
||||||
E* test_call_from_fixture.py::test_foo
|
" test_call_from_fixture.py::test_foo",
|
||||||
E*
|
"Requested fixture 'fix_with_param' defined in:",
|
||||||
E*Requested fixture 'fix_with_param' defined in:
|
"test_call_from_fixture.py:4",
|
||||||
E*test_call_from_fixture.py:4
|
"Requested here:",
|
||||||
E*Requested here:
|
"test_call_from_fixture.py:9",
|
||||||
E*test_call_from_fixture.py:9
|
"*1 error in*",
|
||||||
*1 error*
|
]
|
||||||
"""
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_call_from_test(self, testdir):
|
def test_call_from_test(self, testdir):
|
||||||
|
@ -3638,16 +3638,15 @@ class TestParameterizedSubRequest(object):
|
||||||
)
|
)
|
||||||
result = testdir.runpytest()
|
result = testdir.runpytest()
|
||||||
result.stdout.fnmatch_lines(
|
result.stdout.fnmatch_lines(
|
||||||
"""
|
[
|
||||||
E*Failed: The requested fixture has no parameter defined for test:
|
"The requested fixture has no parameter defined for test:",
|
||||||
E* test_call_from_test.py::test_foo
|
" test_call_from_test.py::test_foo",
|
||||||
E*
|
"Requested fixture 'fix_with_param' defined in:",
|
||||||
E*Requested fixture 'fix_with_param' defined in:
|
"test_call_from_test.py:4",
|
||||||
E*test_call_from_test.py:4
|
"Requested here:",
|
||||||
E*Requested here:
|
"test_call_from_test.py:8",
|
||||||
E*test_call_from_test.py:8
|
"*1 failed*",
|
||||||
*1 failed*
|
]
|
||||||
"""
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_external_fixture(self, testdir):
|
def test_external_fixture(self, testdir):
|
||||||
|
@ -3669,16 +3668,16 @@ class TestParameterizedSubRequest(object):
|
||||||
)
|
)
|
||||||
result = testdir.runpytest()
|
result = testdir.runpytest()
|
||||||
result.stdout.fnmatch_lines(
|
result.stdout.fnmatch_lines(
|
||||||
"""
|
[
|
||||||
E*Failed: The requested fixture has no parameter defined for test:
|
"The requested fixture has no parameter defined for test:",
|
||||||
E* test_external_fixture.py::test_foo
|
" test_external_fixture.py::test_foo",
|
||||||
E*
|
"",
|
||||||
E*Requested fixture 'fix_with_param' defined in:
|
"Requested fixture 'fix_with_param' defined in:",
|
||||||
E*conftest.py:4
|
"conftest.py:4",
|
||||||
E*Requested here:
|
"Requested here:",
|
||||||
E*test_external_fixture.py:2
|
"test_external_fixture.py:2",
|
||||||
*1 failed*
|
"*1 failed*",
|
||||||
"""
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_non_relative_path(self, testdir):
|
def test_non_relative_path(self, testdir):
|
||||||
|
@ -3713,16 +3712,16 @@ class TestParameterizedSubRequest(object):
|
||||||
testdir.syspathinsert(fixdir)
|
testdir.syspathinsert(fixdir)
|
||||||
result = testdir.runpytest()
|
result = testdir.runpytest()
|
||||||
result.stdout.fnmatch_lines(
|
result.stdout.fnmatch_lines(
|
||||||
"""
|
[
|
||||||
E*Failed: The requested fixture has no parameter defined for test:
|
"The requested fixture has no parameter defined for test:",
|
||||||
E* test_foos.py::test_foo
|
" test_foos.py::test_foo",
|
||||||
E*
|
"",
|
||||||
E*Requested fixture 'fix_with_param' defined in:
|
"Requested fixture 'fix_with_param' defined in:",
|
||||||
E*fix.py:4
|
"*fix.py:4",
|
||||||
E*Requested here:
|
"Requested here:",
|
||||||
E*test_foos.py:4
|
"test_foos.py:4",
|
||||||
*1 failed*
|
"*1 failed*",
|
||||||
"""
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -127,10 +127,11 @@ class TestMetafunc(object):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
metafunc = self.Metafunc(func)
|
metafunc = self.Metafunc(func)
|
||||||
try:
|
with pytest.raises(
|
||||||
|
pytest.fail.Exception,
|
||||||
|
match=r"parametrize\(\) call in func got an unexpected scope value 'doggy'",
|
||||||
|
):
|
||||||
metafunc.parametrize("x", [1], scope="doggy")
|
metafunc.parametrize("x", [1], scope="doggy")
|
||||||
except ValueError as ve:
|
|
||||||
assert "has an unsupported scope value 'doggy'" in str(ve)
|
|
||||||
|
|
||||||
def test_find_parametrized_scope(self):
|
def test_find_parametrized_scope(self):
|
||||||
"""unittest for _find_parametrized_scope (#3941)"""
|
"""unittest for _find_parametrized_scope (#3941)"""
|
||||||
|
@ -206,16 +207,13 @@ class TestMetafunc(object):
|
||||||
|
|
||||||
metafunc = self.Metafunc(func)
|
metafunc = self.Metafunc(func)
|
||||||
|
|
||||||
pytest.raises(
|
with pytest.raises(pytest.fail.Exception):
|
||||||
ValueError, lambda: metafunc.parametrize("x", [1, 2], ids=["basic"])
|
metafunc.parametrize("x", [1, 2], ids=["basic"])
|
||||||
)
|
|
||||||
|
|
||||||
pytest.raises(
|
with pytest.raises(pytest.fail.Exception):
|
||||||
ValueError,
|
metafunc.parametrize(
|
||||||
lambda: metafunc.parametrize(
|
|
||||||
("x", "y"), [("abc", "def"), ("ghi", "jkl")], ids=["one"]
|
("x", "y"), [("abc", "def"), ("ghi", "jkl")], ids=["one"]
|
||||||
),
|
)
|
||||||
)
|
|
||||||
|
|
||||||
@pytest.mark.issue510
|
@pytest.mark.issue510
|
||||||
def test_parametrize_empty_list(self):
|
def test_parametrize_empty_list(self):
|
||||||
|
@ -573,7 +571,7 @@ class TestMetafunc(object):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
metafunc = self.Metafunc(func)
|
metafunc = self.Metafunc(func)
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(pytest.fail.Exception):
|
||||||
metafunc.parametrize("x, y", [("a", "b")], indirect=["x", "z"])
|
metafunc.parametrize("x, y", [("a", "b")], indirect=["x", "z"])
|
||||||
|
|
||||||
@pytest.mark.issue714
|
@pytest.mark.issue714
|
||||||
|
@ -1189,7 +1187,9 @@ class TestMetafuncFunctional(object):
|
||||||
)
|
)
|
||||||
result = testdir.runpytest()
|
result = testdir.runpytest()
|
||||||
result.stdout.fnmatch_lines(
|
result.stdout.fnmatch_lines(
|
||||||
["*ids must be list of strings, found: 2 (type: int)*"]
|
[
|
||||||
|
"*In test_ids_numbers: ids must be list of strings, found: 2 (type: *'int'>)*"
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_parametrize_with_identical_ids_get_unique_names(self, testdir):
|
def test_parametrize_with_identical_ids_get_unique_names(self, testdir):
|
||||||
|
@ -1326,13 +1326,13 @@ class TestMetafuncFunctional(object):
|
||||||
attr
|
attr
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
reprec = testdir.inline_run("--collectonly")
|
result = testdir.runpytest("--collectonly")
|
||||||
failures = reprec.getfailures()
|
result.stdout.fnmatch_lines(
|
||||||
assert len(failures) == 1
|
[
|
||||||
expectederror = "MarkerError: test_foo has '{}', spelling should be 'parametrize'".format(
|
"test_foo has '{}' mark, spelling should be 'parametrize'".format(attr),
|
||||||
attr
|
"*1 error in*",
|
||||||
|
]
|
||||||
)
|
)
|
||||||
assert expectederror in failures[0].longrepr.reprcrash.message
|
|
||||||
|
|
||||||
|
|
||||||
class TestMetafuncFunctionalAuto(object):
|
class TestMetafuncFunctionalAuto(object):
|
||||||
|
|
|
@ -4,7 +4,7 @@ import textwrap
|
||||||
import py
|
import py
|
||||||
import pytest
|
import pytest
|
||||||
from _pytest.config import PytestPluginManager
|
from _pytest.config import PytestPluginManager
|
||||||
from _pytest.main import EXIT_NOTESTSCOLLECTED, EXIT_USAGEERROR
|
from _pytest.main import EXIT_NOTESTSCOLLECTED, EXIT_OK, EXIT_USAGEERROR
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="module", params=["global", "inpackage"])
|
@pytest.fixture(scope="module", params=["global", "inpackage"])
|
||||||
|
@ -186,6 +186,52 @@ def test_conftest_confcutdir(testdir):
|
||||||
assert "warning: could not load initial" not in result.stdout.str()
|
assert "warning: could not load initial" not in result.stdout.str()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
not hasattr(py.path.local, "mksymlinkto"),
|
||||||
|
reason="symlink not available on this platform",
|
||||||
|
)
|
||||||
|
def test_conftest_symlink(testdir):
|
||||||
|
"""Ensure that conftest.py is used for resolved symlinks."""
|
||||||
|
realtests = testdir.tmpdir.mkdir("real").mkdir("app").mkdir("tests")
|
||||||
|
testdir.tmpdir.join("symlinktests").mksymlinkto(realtests)
|
||||||
|
testdir.makepyfile(
|
||||||
|
**{
|
||||||
|
"real/app/tests/test_foo.py": "def test1(fixture): pass",
|
||||||
|
"real/conftest.py": textwrap.dedent(
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
print("conftest_loaded")
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def fixture():
|
||||||
|
print("fixture_used")
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
result = testdir.runpytest("-vs", "symlinktests")
|
||||||
|
result.stdout.fnmatch_lines(
|
||||||
|
[
|
||||||
|
"*conftest_loaded*",
|
||||||
|
"real/app/tests/test_foo.py::test1 fixture_used",
|
||||||
|
"PASSED",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
assert result.ret == EXIT_OK
|
||||||
|
|
||||||
|
realtests.ensure("__init__.py")
|
||||||
|
result = testdir.runpytest("-vs", "symlinktests/test_foo.py::test1")
|
||||||
|
result.stdout.fnmatch_lines(
|
||||||
|
[
|
||||||
|
"*conftest_loaded*",
|
||||||
|
"real/app/tests/test_foo.py::test1 fixture_used",
|
||||||
|
"PASSED",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
assert result.ret == EXIT_OK
|
||||||
|
|
||||||
|
|
||||||
def test_no_conftest(testdir):
|
def test_no_conftest(testdir):
|
||||||
testdir.makeconftest("assert 0")
|
testdir.makeconftest("assert 0")
|
||||||
result = testdir.runpytest("--noconftest")
|
result = testdir.runpytest("--noconftest")
|
||||||
|
|
|
@ -13,7 +13,7 @@ from _pytest.mark import (
|
||||||
transfer_markers,
|
transfer_markers,
|
||||||
EMPTY_PARAMETERSET_OPTION,
|
EMPTY_PARAMETERSET_OPTION,
|
||||||
)
|
)
|
||||||
from _pytest.nodes import Node
|
from _pytest.nodes import Node, Collector
|
||||||
|
|
||||||
ignore_markinfo = pytest.mark.filterwarnings(
|
ignore_markinfo = pytest.mark.filterwarnings(
|
||||||
"ignore:MarkInfo objects:pytest.RemovedInPytest4Warning"
|
"ignore:MarkInfo objects:pytest.RemovedInPytest4Warning"
|
||||||
|
@ -247,7 +247,7 @@ def test_marker_without_description(testdir):
|
||||||
)
|
)
|
||||||
ftdir = testdir.mkdir("ft1_dummy")
|
ftdir = testdir.mkdir("ft1_dummy")
|
||||||
testdir.tmpdir.join("conftest.py").move(ftdir.join("conftest.py"))
|
testdir.tmpdir.join("conftest.py").move(ftdir.join("conftest.py"))
|
||||||
rec = testdir.runpytest_subprocess("--strict")
|
rec = testdir.runpytest("--strict")
|
||||||
rec.assert_outcomes()
|
rec.assert_outcomes()
|
||||||
|
|
||||||
|
|
||||||
|
@ -302,7 +302,7 @@ def test_strict_prohibits_unregistered_markers(testdir):
|
||||||
)
|
)
|
||||||
result = testdir.runpytest("--strict")
|
result = testdir.runpytest("--strict")
|
||||||
assert result.ret != 0
|
assert result.ret != 0
|
||||||
result.stdout.fnmatch_lines(["*unregisteredmark*not*registered*"])
|
result.stdout.fnmatch_lines(["'unregisteredmark' not a registered marker"])
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
@ -1103,7 +1103,14 @@ class TestMarkDecorator(object):
|
||||||
@pytest.mark.parametrize("mark", [None, "", "skip", "xfail"])
|
@pytest.mark.parametrize("mark", [None, "", "skip", "xfail"])
|
||||||
def test_parameterset_for_parametrize_marks(testdir, mark):
|
def test_parameterset_for_parametrize_marks(testdir, mark):
|
||||||
if mark is not None:
|
if mark is not None:
|
||||||
testdir.makeini("[pytest]\n{}={}".format(EMPTY_PARAMETERSET_OPTION, mark))
|
testdir.makeini(
|
||||||
|
"""
|
||||||
|
[pytest]
|
||||||
|
{}={}
|
||||||
|
""".format(
|
||||||
|
EMPTY_PARAMETERSET_OPTION, mark
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
config = testdir.parseconfig()
|
config = testdir.parseconfig()
|
||||||
from _pytest.mark import pytest_configure, get_empty_parameterset_mark
|
from _pytest.mark import pytest_configure, get_empty_parameterset_mark
|
||||||
|
@ -1119,6 +1126,34 @@ def test_parameterset_for_parametrize_marks(testdir, mark):
|
||||||
assert result_mark.kwargs.get("run") is False
|
assert result_mark.kwargs.get("run") is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_parameterset_for_fail_at_collect(testdir):
|
||||||
|
testdir.makeini(
|
||||||
|
"""
|
||||||
|
[pytest]
|
||||||
|
{}=fail_at_collect
|
||||||
|
""".format(
|
||||||
|
EMPTY_PARAMETERSET_OPTION
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
config = testdir.parseconfig()
|
||||||
|
from _pytest.mark import pytest_configure, get_empty_parameterset_mark
|
||||||
|
from _pytest.compat import getfslineno
|
||||||
|
|
||||||
|
pytest_configure(config)
|
||||||
|
|
||||||
|
test_func = all
|
||||||
|
func_name = test_func.__name__
|
||||||
|
_, func_lineno = getfslineno(test_func)
|
||||||
|
expected_errmsg = r"Empty parameter set in '%s' at line %d" % (
|
||||||
|
func_name,
|
||||||
|
func_lineno,
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(Collector.CollectError, match=expected_errmsg):
|
||||||
|
get_empty_parameterset_mark(config, ["a"], test_func)
|
||||||
|
|
||||||
|
|
||||||
def test_parameterset_for_parametrize_bad_markname(testdir):
|
def test_parameterset_for_parametrize_bad_markname(testdir):
|
||||||
with pytest.raises(pytest.UsageError):
|
with pytest.raises(pytest.UsageError):
|
||||||
test_parameterset_for_parametrize_marks(testdir, "bad")
|
test_parameterset_for_parametrize_marks(testdir, "bad")
|
||||||
|
|
|
@ -4,7 +4,7 @@ import py
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from _pytest.paths import fnmatch_ex
|
from _pytest.pathlib import fnmatch_ex
|
||||||
|
|
||||||
|
|
||||||
class TestPort:
|
class TestPort:
|
||||||
|
|
|
@ -4,6 +4,7 @@ import os
|
||||||
import py.path
|
import py.path
|
||||||
import pytest
|
import pytest
|
||||||
import sys
|
import sys
|
||||||
|
import time
|
||||||
import _pytest.pytester as pytester
|
import _pytest.pytester as pytester
|
||||||
from _pytest.pytester import HookRecorder
|
from _pytest.pytester import HookRecorder
|
||||||
from _pytest.pytester import CwdSnapshot, SysModulesSnapshot, SysPathsSnapshot
|
from _pytest.pytester import CwdSnapshot, SysModulesSnapshot, SysPathsSnapshot
|
||||||
|
@ -401,3 +402,34 @@ def test_testdir_subprocess(testdir):
|
||||||
def test_unicode_args(testdir):
|
def test_unicode_args(testdir):
|
||||||
result = testdir.runpytest("-k", u"💩")
|
result = testdir.runpytest("-k", u"💩")
|
||||||
assert result.ret == EXIT_NOTESTSCOLLECTED
|
assert result.ret == EXIT_NOTESTSCOLLECTED
|
||||||
|
|
||||||
|
|
||||||
|
def test_testdir_run_no_timeout(testdir):
|
||||||
|
testfile = testdir.makepyfile("def test_no_timeout(): pass")
|
||||||
|
assert testdir.runpytest_subprocess(testfile).ret == EXIT_OK
|
||||||
|
|
||||||
|
|
||||||
|
def test_testdir_run_with_timeout(testdir):
|
||||||
|
testfile = testdir.makepyfile("def test_no_timeout(): pass")
|
||||||
|
|
||||||
|
timeout = 120
|
||||||
|
|
||||||
|
start = time.time()
|
||||||
|
result = testdir.runpytest_subprocess(testfile, timeout=timeout)
|
||||||
|
end = time.time()
|
||||||
|
duration = end - start
|
||||||
|
|
||||||
|
assert result.ret == EXIT_OK
|
||||||
|
assert duration < timeout
|
||||||
|
|
||||||
|
|
||||||
|
def test_testdir_run_timeout_expires(testdir):
|
||||||
|
testfile = testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
import time
|
||||||
|
|
||||||
|
def test_timeout():
|
||||||
|
time.sleep(10)"""
|
||||||
|
)
|
||||||
|
with pytest.raises(testdir.TimeoutExpired):
|
||||||
|
testdir.runpytest_subprocess(testfile, timeout=1)
|
||||||
|
|
|
@ -76,9 +76,8 @@ class TestDeprecatedCall(object):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_deprecated_call_raises(self):
|
def test_deprecated_call_raises(self):
|
||||||
with pytest.raises(AssertionError) as excinfo:
|
with pytest.raises(pytest.fail.Exception, match="No warnings of type"):
|
||||||
pytest.deprecated_call(self.dep, 3, 5)
|
pytest.deprecated_call(self.dep, 3, 5)
|
||||||
assert "Did not produce" in str(excinfo)
|
|
||||||
|
|
||||||
def test_deprecated_call(self):
|
def test_deprecated_call(self):
|
||||||
pytest.deprecated_call(self.dep, 0, 5)
|
pytest.deprecated_call(self.dep, 0, 5)
|
||||||
|
@ -100,7 +99,7 @@ class TestDeprecatedCall(object):
|
||||||
assert warn_explicit is warnings.warn_explicit
|
assert warn_explicit is warnings.warn_explicit
|
||||||
|
|
||||||
def test_deprecated_explicit_call_raises(self):
|
def test_deprecated_explicit_call_raises(self):
|
||||||
with pytest.raises(AssertionError):
|
with pytest.raises(pytest.fail.Exception):
|
||||||
pytest.deprecated_call(self.dep_explicit, 3)
|
pytest.deprecated_call(self.dep_explicit, 3)
|
||||||
|
|
||||||
def test_deprecated_explicit_call(self):
|
def test_deprecated_explicit_call(self):
|
||||||
|
@ -116,8 +115,8 @@ class TestDeprecatedCall(object):
|
||||||
def f():
|
def f():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
msg = "Did not produce DeprecationWarning or PendingDeprecationWarning"
|
msg = "No warnings of type (.*DeprecationWarning.*, .*PendingDeprecationWarning.*)"
|
||||||
with pytest.raises(AssertionError, match=msg):
|
with pytest.raises(pytest.fail.Exception, match=msg):
|
||||||
if mode == "call":
|
if mode == "call":
|
||||||
pytest.deprecated_call(f)
|
pytest.deprecated_call(f)
|
||||||
else:
|
else:
|
||||||
|
@ -179,12 +178,20 @@ class TestDeprecatedCall(object):
|
||||||
def f():
|
def f():
|
||||||
warnings.warn(warning("hi"))
|
warnings.warn(warning("hi"))
|
||||||
|
|
||||||
with pytest.raises(AssertionError):
|
with pytest.raises(pytest.fail.Exception):
|
||||||
pytest.deprecated_call(f)
|
pytest.deprecated_call(f)
|
||||||
with pytest.raises(AssertionError):
|
with pytest.raises(pytest.fail.Exception):
|
||||||
with pytest.deprecated_call():
|
with pytest.deprecated_call():
|
||||||
f()
|
f()
|
||||||
|
|
||||||
|
def test_deprecated_call_supports_match(self):
|
||||||
|
with pytest.deprecated_call(match=r"must be \d+$"):
|
||||||
|
warnings.warn("value must be 42", DeprecationWarning)
|
||||||
|
|
||||||
|
with pytest.raises(pytest.fail.Exception):
|
||||||
|
with pytest.deprecated_call(match=r"must be \d+$"):
|
||||||
|
warnings.warn("this is not here", DeprecationWarning)
|
||||||
|
|
||||||
|
|
||||||
class TestWarns(object):
|
class TestWarns(object):
|
||||||
def test_strings(self):
|
def test_strings(self):
|
||||||
|
@ -343,3 +350,13 @@ class TestWarns(object):
|
||||||
with pytest.warns(UserWarning, match=r"aaa"):
|
with pytest.warns(UserWarning, match=r"aaa"):
|
||||||
warnings.warn("bbbbbbbbbb", UserWarning)
|
warnings.warn("bbbbbbbbbb", UserWarning)
|
||||||
warnings.warn("cccccccccc", UserWarning)
|
warnings.warn("cccccccccc", UserWarning)
|
||||||
|
|
||||||
|
@pytest.mark.filterwarnings("ignore")
|
||||||
|
def test_can_capture_previously_warned(self):
|
||||||
|
def f():
|
||||||
|
warnings.warn(UserWarning("ohai"))
|
||||||
|
return 10
|
||||||
|
|
||||||
|
assert f() == 10
|
||||||
|
assert pytest.warns(UserWarning, f) == 10
|
||||||
|
assert pytest.warns(UserWarning, f) == 10
|
||||||
|
|
|
@ -570,7 +570,20 @@ def test_pytest_exit_msg(testdir):
|
||||||
result.stderr.fnmatch_lines(["Exit: oh noes"])
|
result.stderr.fnmatch_lines(["Exit: oh noes"])
|
||||||
|
|
||||||
|
|
||||||
def test_pytest_fail_notrace(testdir):
|
def test_pytest_exit_returncode(testdir):
|
||||||
|
testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
def test_foo():
|
||||||
|
pytest.exit("some exit msg", 99)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = testdir.runpytest()
|
||||||
|
assert result.ret == 99
|
||||||
|
|
||||||
|
|
||||||
|
def test_pytest_fail_notrace_runtest(testdir):
|
||||||
|
"""Test pytest.fail(..., pytrace=False) does not show tracebacks during test run."""
|
||||||
testdir.makepyfile(
|
testdir.makepyfile(
|
||||||
"""
|
"""
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -585,6 +598,21 @@ def test_pytest_fail_notrace(testdir):
|
||||||
assert "def teardown_function" not in result.stdout.str()
|
assert "def teardown_function" not in result.stdout.str()
|
||||||
|
|
||||||
|
|
||||||
|
def test_pytest_fail_notrace_collection(testdir):
|
||||||
|
"""Test pytest.fail(..., pytrace=False) does not show tracebacks during collection."""
|
||||||
|
testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
def some_internal_function():
|
||||||
|
pytest.fail("hello", pytrace=False)
|
||||||
|
some_internal_function()
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = testdir.runpytest()
|
||||||
|
result.stdout.fnmatch_lines(["hello"])
|
||||||
|
assert "def some_internal_function()" not in result.stdout.str()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("str_prefix", ["u", ""])
|
@pytest.mark.parametrize("str_prefix", ["u", ""])
|
||||||
def test_pytest_fail_notrace_non_ascii(testdir, str_prefix):
|
def test_pytest_fail_notrace_non_ascii(testdir, str_prefix):
|
||||||
"""Fix pytest.fail with pytrace=False with non-ascii characters (#1178).
|
"""Fix pytest.fail with pytrace=False with non-ascii characters (#1178).
|
||||||
|
|
|
@ -154,7 +154,7 @@ class TestTerminal(object):
|
||||||
)
|
)
|
||||||
result = testdir.runpytest(p2)
|
result = testdir.runpytest(p2)
|
||||||
result.stdout.fnmatch_lines(["*test_p2.py .*", "*1 passed*"])
|
result.stdout.fnmatch_lines(["*test_p2.py .*", "*1 passed*"])
|
||||||
result = testdir.runpytest("-v", p2)
|
result = testdir.runpytest("-vv", p2)
|
||||||
result.stdout.fnmatch_lines(
|
result.stdout.fnmatch_lines(
|
||||||
["*test_p2.py::TestMore::test_p1* <- *test_p1.py*PASSED*"]
|
["*test_p2.py::TestMore::test_p1* <- *test_p1.py*PASSED*"]
|
||||||
)
|
)
|
||||||
|
@ -170,7 +170,7 @@ class TestTerminal(object):
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
result = testdir.runpytest("-v")
|
result = testdir.runpytest("-vv")
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
result.stdout.fnmatch_lines(["*a123/test_hello123.py*PASS*"])
|
result.stdout.fnmatch_lines(["*a123/test_hello123.py*PASS*"])
|
||||||
assert " <- " not in result.stdout.str()
|
assert " <- " not in result.stdout.str()
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import absolute_import, division, print_function
|
||||||
import sys
|
import sys
|
||||||
import py
|
|
||||||
|
import six
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from _pytest.pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
def test_tmpdir_fixture(testdir):
|
def test_tmpdir_fixture(testdir):
|
||||||
|
@ -19,11 +22,11 @@ def test_ensuretemp(recwarn):
|
||||||
|
|
||||||
class TestTempdirHandler(object):
|
class TestTempdirHandler(object):
|
||||||
def test_mktemp(self, testdir):
|
def test_mktemp(self, testdir):
|
||||||
from _pytest.tmpdir import TempdirFactory
|
from _pytest.tmpdir import TempdirFactory, TempPathFactory
|
||||||
|
|
||||||
config = testdir.parseconfig()
|
config = testdir.parseconfig()
|
||||||
config.option.basetemp = testdir.mkdir("hello")
|
config.option.basetemp = testdir.mkdir("hello")
|
||||||
t = TempdirFactory(config)
|
t = TempdirFactory(TempPathFactory.from_config(config))
|
||||||
tmp = t.mktemp("world")
|
tmp = t.mktemp("world")
|
||||||
assert tmp.relto(t.getbasetemp()) == "world0"
|
assert tmp.relto(t.getbasetemp()) == "world0"
|
||||||
tmp = t.mktemp("this")
|
tmp = t.mktemp("this")
|
||||||
|
@ -65,10 +68,6 @@ def test_basetemp(testdir):
|
||||||
assert mytemp.join("hello").check()
|
assert mytemp.join("hello").check()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
|
||||||
not hasattr(py.path.local, "mksymlinkto"),
|
|
||||||
reason="symlink not available on this platform",
|
|
||||||
)
|
|
||||||
def test_tmpdir_always_is_realpath(testdir):
|
def test_tmpdir_always_is_realpath(testdir):
|
||||||
# the reason why tmpdir should be a realpath is that
|
# the reason why tmpdir should be a realpath is that
|
||||||
# when you cd to it and do "os.getcwd()" you will anyway
|
# when you cd to it and do "os.getcwd()" you will anyway
|
||||||
|
@ -78,7 +77,7 @@ def test_tmpdir_always_is_realpath(testdir):
|
||||||
# os.environ["PWD"]
|
# os.environ["PWD"]
|
||||||
realtemp = testdir.tmpdir.mkdir("myrealtemp")
|
realtemp = testdir.tmpdir.mkdir("myrealtemp")
|
||||||
linktemp = testdir.tmpdir.join("symlinktemp")
|
linktemp = testdir.tmpdir.join("symlinktemp")
|
||||||
linktemp.mksymlinkto(realtemp)
|
attempt_symlink_to(linktemp, str(realtemp))
|
||||||
p = testdir.makepyfile(
|
p = testdir.makepyfile(
|
||||||
"""
|
"""
|
||||||
def test_1(tmpdir):
|
def test_1(tmpdir):
|
||||||
|
@ -111,7 +110,7 @@ def test_tmpdir_factory(testdir):
|
||||||
def session_dir(tmpdir_factory):
|
def session_dir(tmpdir_factory):
|
||||||
return tmpdir_factory.mktemp('data', numbered=False)
|
return tmpdir_factory.mktemp('data', numbered=False)
|
||||||
def test_some(session_dir):
|
def test_some(session_dir):
|
||||||
session_dir.isdir()
|
assert session_dir.isdir()
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
reprec = testdir.inline_run()
|
reprec = testdir.inline_run()
|
||||||
|
@ -184,3 +183,113 @@ def test_get_user(monkeypatch):
|
||||||
monkeypatch.delenv("USER", raising=False)
|
monkeypatch.delenv("USER", raising=False)
|
||||||
monkeypatch.delenv("USERNAME", raising=False)
|
monkeypatch.delenv("USERNAME", raising=False)
|
||||||
assert get_user() is None
|
assert get_user() is None
|
||||||
|
|
||||||
|
|
||||||
|
class TestNumberedDir(object):
|
||||||
|
PREFIX = "fun-"
|
||||||
|
|
||||||
|
def test_make(self, tmp_path):
|
||||||
|
from _pytest.pathlib import make_numbered_dir
|
||||||
|
|
||||||
|
for i in range(10):
|
||||||
|
d = make_numbered_dir(root=tmp_path, prefix=self.PREFIX)
|
||||||
|
assert d.name.startswith(self.PREFIX)
|
||||||
|
assert d.name.endswith(str(i))
|
||||||
|
|
||||||
|
def test_cleanup_lock_create(self, tmp_path):
|
||||||
|
d = tmp_path.joinpath("test")
|
||||||
|
d.mkdir()
|
||||||
|
from _pytest.pathlib import create_cleanup_lock
|
||||||
|
|
||||||
|
lockfile = create_cleanup_lock(d)
|
||||||
|
with pytest.raises(EnvironmentError, match="cannot create lockfile in .*"):
|
||||||
|
create_cleanup_lock(d)
|
||||||
|
|
||||||
|
lockfile.unlink()
|
||||||
|
|
||||||
|
def test_lock_register_cleanup_removal(self, tmp_path):
|
||||||
|
from _pytest.pathlib import create_cleanup_lock, register_cleanup_lock_removal
|
||||||
|
|
||||||
|
lock = create_cleanup_lock(tmp_path)
|
||||||
|
|
||||||
|
registry = []
|
||||||
|
register_cleanup_lock_removal(lock, register=registry.append)
|
||||||
|
|
||||||
|
cleanup_func, = registry
|
||||||
|
|
||||||
|
assert lock.is_file()
|
||||||
|
|
||||||
|
cleanup_func(original_pid="intentionally_different")
|
||||||
|
|
||||||
|
assert lock.is_file()
|
||||||
|
|
||||||
|
cleanup_func()
|
||||||
|
|
||||||
|
assert not lock.exists()
|
||||||
|
|
||||||
|
cleanup_func()
|
||||||
|
|
||||||
|
assert not lock.exists()
|
||||||
|
|
||||||
|
def _do_cleanup(self, tmp_path):
|
||||||
|
self.test_make(tmp_path)
|
||||||
|
from _pytest.pathlib import cleanup_numbered_dir
|
||||||
|
|
||||||
|
cleanup_numbered_dir(
|
||||||
|
root=tmp_path,
|
||||||
|
prefix=self.PREFIX,
|
||||||
|
keep=2,
|
||||||
|
consider_lock_dead_if_created_before=0,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_cleanup_keep(self, tmp_path):
|
||||||
|
self._do_cleanup(tmp_path)
|
||||||
|
a, b = tmp_path.iterdir()
|
||||||
|
print(a, b)
|
||||||
|
|
||||||
|
def test_cleanup_locked(self, tmp_path):
|
||||||
|
|
||||||
|
from _pytest import pathlib
|
||||||
|
|
||||||
|
p = pathlib.make_numbered_dir(root=tmp_path, prefix=self.PREFIX)
|
||||||
|
|
||||||
|
pathlib.create_cleanup_lock(p)
|
||||||
|
|
||||||
|
assert not pathlib.ensure_deletable(
|
||||||
|
p, consider_lock_dead_if_created_before=p.stat().st_mtime - 1
|
||||||
|
)
|
||||||
|
assert pathlib.ensure_deletable(
|
||||||
|
p, consider_lock_dead_if_created_before=p.stat().st_mtime + 1
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_rmtree(self, tmp_path):
|
||||||
|
from _pytest.pathlib import rmtree
|
||||||
|
|
||||||
|
adir = tmp_path / "adir"
|
||||||
|
adir.mkdir()
|
||||||
|
rmtree(adir)
|
||||||
|
|
||||||
|
assert not adir.exists()
|
||||||
|
|
||||||
|
adir.mkdir()
|
||||||
|
afile = adir / "afile"
|
||||||
|
afile.write_bytes(b"aa")
|
||||||
|
|
||||||
|
rmtree(adir, force=True)
|
||||||
|
assert not adir.exists()
|
||||||
|
|
||||||
|
def test_cleanup_symlink(self, tmp_path):
|
||||||
|
the_symlink = tmp_path / (self.PREFIX + "current")
|
||||||
|
attempt_symlink_to(the_symlink, tmp_path / (self.PREFIX + "5"))
|
||||||
|
self._do_cleanup(tmp_path)
|
||||||
|
|
||||||
|
|
||||||
|
def attempt_symlink_to(path, to_path):
|
||||||
|
"""Try to make a symlink from "path" to "to_path", skipping in case this platform
|
||||||
|
does not support it or we don't have sufficient privileges (common on Windows)."""
|
||||||
|
if sys.platform.startswith("win") and six.PY2:
|
||||||
|
pytest.skip("pathlib for some reason cannot make symlinks on Python 2")
|
||||||
|
try:
|
||||||
|
Path(path).symlink_to(Path(to_path))
|
||||||
|
except OSError:
|
||||||
|
pytest.skip("could not create symbolic link")
|
||||||
|
|
|
@ -430,6 +430,50 @@ def test_hide_pytest_internal_warnings(testdir, ignore_pytest_warnings):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("ignore_on_cmdline", [True, False])
|
||||||
|
def test_option_precedence_cmdline_over_ini(testdir, ignore_on_cmdline):
|
||||||
|
"""filters defined in the command-line should take precedence over filters in ini files (#3946)."""
|
||||||
|
testdir.makeini(
|
||||||
|
"""
|
||||||
|
[pytest]
|
||||||
|
filterwarnings = error
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
import warnings
|
||||||
|
def test():
|
||||||
|
warnings.warn(UserWarning('hello'))
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
args = ["-W", "ignore"] if ignore_on_cmdline else []
|
||||||
|
result = testdir.runpytest(*args)
|
||||||
|
if ignore_on_cmdline:
|
||||||
|
result.stdout.fnmatch_lines(["* 1 passed in*"])
|
||||||
|
else:
|
||||||
|
result.stdout.fnmatch_lines(["* 1 failed in*"])
|
||||||
|
|
||||||
|
|
||||||
|
def test_option_precedence_mark(testdir):
|
||||||
|
"""Filters defined by marks should always take precedence (#3946)."""
|
||||||
|
testdir.makeini(
|
||||||
|
"""
|
||||||
|
[pytest]
|
||||||
|
filterwarnings = ignore
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
import pytest, warnings
|
||||||
|
@pytest.mark.filterwarnings('error')
|
||||||
|
def test():
|
||||||
|
warnings.warn(UserWarning('hello'))
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = testdir.runpytest("-W", "ignore")
|
||||||
|
result.stdout.fnmatch_lines(["* 1 failed in*"])
|
||||||
|
|
||||||
|
|
||||||
class TestDeprecationWarningsByDefault:
|
class TestDeprecationWarningsByDefault:
|
||||||
"""
|
"""
|
||||||
Note: all pytest runs are executed in a subprocess so we don't inherit warning filters
|
Note: all pytest runs are executed in a subprocess so we don't inherit warning filters
|
||||||
|
@ -451,8 +495,18 @@ class TestDeprecationWarningsByDefault:
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_shown_by_default(self, testdir):
|
@pytest.mark.parametrize("customize_filters", [True, False])
|
||||||
|
def test_shown_by_default(self, testdir, customize_filters):
|
||||||
|
"""Show deprecation warnings by default, even if user has customized the warnings filters (#4013)."""
|
||||||
self.create_file(testdir)
|
self.create_file(testdir)
|
||||||
|
if customize_filters:
|
||||||
|
testdir.makeini(
|
||||||
|
"""
|
||||||
|
[pytest]
|
||||||
|
filterwarnings =
|
||||||
|
once::UserWarning
|
||||||
|
"""
|
||||||
|
)
|
||||||
result = testdir.runpytest_subprocess()
|
result = testdir.runpytest_subprocess()
|
||||||
result.stdout.fnmatch_lines(
|
result.stdout.fnmatch_lines(
|
||||||
[
|
[
|
||||||
|
@ -468,7 +522,9 @@ class TestDeprecationWarningsByDefault:
|
||||||
testdir.makeini(
|
testdir.makeini(
|
||||||
"""
|
"""
|
||||||
[pytest]
|
[pytest]
|
||||||
filterwarnings = once::UserWarning
|
filterwarnings =
|
||||||
|
ignore::DeprecationWarning
|
||||||
|
ignore::PendingDeprecationWarning
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
result = testdir.runpytest_subprocess()
|
result = testdir.runpytest_subprocess()
|
||||||
|
@ -479,7 +535,8 @@ class TestDeprecationWarningsByDefault:
|
||||||
be displayed normally.
|
be displayed normally.
|
||||||
"""
|
"""
|
||||||
self.create_file(
|
self.create_file(
|
||||||
testdir, mark='@pytest.mark.filterwarnings("once::UserWarning")'
|
testdir,
|
||||||
|
mark='@pytest.mark.filterwarnings("ignore::PendingDeprecationWarning")',
|
||||||
)
|
)
|
||||||
result = testdir.runpytest_subprocess()
|
result = testdir.runpytest_subprocess()
|
||||||
result.stdout.fnmatch_lines(
|
result.stdout.fnmatch_lines(
|
||||||
|
@ -492,7 +549,12 @@ class TestDeprecationWarningsByDefault:
|
||||||
|
|
||||||
def test_hidden_by_cmdline(self, testdir):
|
def test_hidden_by_cmdline(self, testdir):
|
||||||
self.create_file(testdir)
|
self.create_file(testdir)
|
||||||
result = testdir.runpytest_subprocess("-W", "once::UserWarning")
|
result = testdir.runpytest_subprocess(
|
||||||
|
"-W",
|
||||||
|
"ignore::DeprecationWarning",
|
||||||
|
"-W",
|
||||||
|
"ignore::PendingDeprecationWarning",
|
||||||
|
)
|
||||||
assert WARNINGS_SUMMARY_HEADER not in result.stdout.str()
|
assert WARNINGS_SUMMARY_HEADER not in result.stdout.str()
|
||||||
|
|
||||||
def test_hidden_by_system(self, testdir, monkeypatch):
|
def test_hidden_by_system(self, testdir, monkeypatch):
|
||||||
|
|
9
tox.ini
9
tox.ini
|
@ -194,13 +194,14 @@ commands = python scripts/release.py {posargs}
|
||||||
|
|
||||||
[pytest]
|
[pytest]
|
||||||
minversion = 2.0
|
minversion = 2.0
|
||||||
plugins = pytester
|
addopts = -ra -p pytester
|
||||||
addopts = -ra -p pytester --ignore=testing/cx_freeze
|
rsyncdirs = tox.ini doc src testing
|
||||||
rsyncdirs = tox.ini pytest.py _pytest testing
|
|
||||||
python_files = test_*.py *_test.py testing/*/*.py
|
python_files = test_*.py *_test.py testing/*/*.py
|
||||||
python_classes = Test Acceptance
|
python_classes = Test Acceptance
|
||||||
python_functions = test
|
python_functions = test
|
||||||
norecursedirs = .tox ja .hg cx_freeze_source testing/example_scripts
|
# NOTE: "doc" is not included here, but gets tested explicitly via "doctesting".
|
||||||
|
testpaths = testing
|
||||||
|
norecursedirs = testing/example_scripts
|
||||||
xfail_strict=true
|
xfail_strict=true
|
||||||
filterwarnings =
|
filterwarnings =
|
||||||
error
|
error
|
||||||
|
|
Loading…
Reference in New Issue