Compare commits

...

70 Commits
4.2.0 ... 4.2.1

Author SHA1 Message Date
Bruno Oliveira
82cc3d8cc2 Preparing release version 4.2.1 2019-02-12 20:17:06 +00:00
Bruno Oliveira
e20e376881 Merge pull request #4347 from blueyed/pdb-recursive-capture
pdbpp: fix capturing with recursive debugging
2019-02-12 16:53:14 -02:00
Bruno Oliveira
8052d01a37 Merge pull request #4774 from sambrightman/pin-more_itertools
Constrain more_itertools for Python 2.7 compatibility
2019-02-12 16:48:51 -02:00
Sam Brightman
a8003286b5 Add CHANGELOG entry for #4770 2019-02-12 13:32:06 +00:00
Sam Brightman
b4be228330 Constrain more_itertools for Python 2.7 compatibility
Fixes #4772, #4770.
2019-02-12 11:53:23 +00:00
Bruno Oliveira
67dd10de26 Merge pull request #4763 from blueyed/lsof
--lsof: suppress stderr
2019-02-11 13:18:40 -02:00
Daniel Hahler
dc8af18a0e Merge pull request #4745 from blueyed/test_collect_pkg_init_and_file_in_args
Fix handling of pkg init and test file via args
2019-02-11 15:11:40 +01:00
Daniel Hahler
61b9246afe Fix/improve handling of pkg init and test file via args
Ref: https://github.com/pytest-dev/pytest/issues/4344#issuecomment-441095934
2019-02-11 15:04:24 +01:00
Daniel Hahler
9feb4941f4 pdb: fix capturing with recursive debugging and pdb++
While I think that pdb++ should be fixed in this regard (by using
`pdb.Pdb`, and not `self.__class__` maybe), this ensures that custom
debuggers like this are working.
2019-02-11 14:52:20 +01:00
Daniel Hahler
237f690f8b --lsof: suppress stderr
This can spam a lot of warnings (per invocation), e.g.:

> lsof: WARNING: can't stat() nsfs file system /run/docker/netns/default
        Output information may be incomplete.

Or from Travis/MacOS:

> lsof: WARNING: can't stat() vmhgfs file system /Volumes/VMware Shared Folders
>       Output information may be incomplete.
>       assuming "dev=31000003" from mount table
2019-02-11 14:08:44 +01:00
Ronny Pfannschmidt
386e801a5a Merge pull request #4762 from blueyed/pluggymaster
tox: use deps for pluggymaster testenvs
2019-02-11 05:30:30 +01:00
Bruno Oliveira
5cf05ce149 Merge pull request #4764 from kevinjfoley/doc-assert-clarify
Clarify pytest_assertrepr_compare docs per #4759
2019-02-10 17:26:50 -02:00
Kevin J. Foley
aee67bb1a7 Clarify pytest_assertrepr_compare docs per #4759 2019-02-10 08:34:35 -05:00
Daniel Hahler
5e2d740829 tox: cleanup/revisit deps 2019-02-10 14:02:56 +01:00
Daniel Hahler
82b8ec37fc Bump tox minversion
For c611a16afe
2019-02-10 13:57:49 +01:00
Daniel Hahler
f73fa47b1f Use coverage with pluggymaster 2019-02-10 13:54:58 +01:00
Daniel Hahler
fd1684e70b tox: use deps for pluggymaster testenvs
https://github.com/tox-dev/tox/issues/706 has been fixed.
2019-02-10 13:53:49 +01:00
Anthony Sottile
19501028ca Merge pull request #4743 from nicoddemus/junit-ref
Document junit_family option in the reference docs
2019-02-09 08:02:55 -08:00
Bruno Oliveira
3a366f451a Merge pull request #4741 from thisch/refactor_terminalreporter
Remove terminal_reporter workaround from logging.py
2019-02-08 21:47:53 -02:00
Bruno Oliveira
7f6108beb1 Add ref docs for junit_family option 2019-02-08 21:44:52 -02:00
Bruno Oliveira
76b0660f47 Merge pull request #4754 from nicoddemus/ignore-pip-wheel-metadata
Ignore pip-generated 'pip-wheel-metadata' folder [skip ci]
2019-02-08 21:37:04 -02:00
Bruno Oliveira
75a12b9d2b Ignore pip-generated 'pip-wheel-metadata' folder [skip ci] 2019-02-08 21:06:33 -02:00
Bruno Oliveira
32c6d4f603 Merge pull request #4738 from pstradomski/master
Fix "ValueError: Plugin already registered" exceptions when running in build directories that symlink to actual source.
2019-02-08 19:57:14 -02:00
Daniel Hahler
b4b2f58eab Merge pull request #4747 from blueyed/visit-filter
pytest.main: collect: factor out _visit_filter
2019-02-08 22:19:38 +01:00
Daniel Hahler
8b92d10fb3 Merge pull request #4751 from blueyed/fix-py-freeze
tox: py37-freeze: use --no-use-pep517 for PyInstaller
2019-02-08 22:01:59 +01:00
Bruno Oliveira
8e220f0e6f Merge pull request #4746 from nicoddemus/isolated-build
Use isolated_build option in tox.ini
2019-02-08 18:59:43 -02:00
Daniel Hahler
e191a65ebb tox: py37-freeze: use --no-use-pep517 for PyInstaller
Fixes https://github.com/pytest-dev/pytest/issues/4750.
2019-02-08 21:59:23 +01:00
Daniel Hahler
5ca81596bb Merge pull request #4744 from blueyed/fix-4592-collectfile
Fix handling of collect_ignore from parent conftest
2019-02-08 20:58:11 +01:00
Daniel Hahler
7bb504b807 pytest.main: collect: factor out _visit_filter 2019-02-08 19:28:12 +01:00
Bruno Oliveira
9be069f899 Use isolated_build option in tox.ini
As per the excellent article by gaborbernat:

https://www.bernat.tech/pep-517-518/
2019-02-08 15:50:33 -02:00
Daniel Hahler
913a2da6e5 Fix handling of collect_ignore from parent conftest
`_collectfile` should be called on files only.

Fixes https://github.com/pytest-dev/pytest/issues/4592.
2019-02-08 18:46:43 +01:00
Anthony Sottile
ea732464aa Merge pull request #4740 from asottile/bugfix_4739
Fix `parametrize(... ids=<function>)` when the function returns non-strings
2019-02-07 21:47:33 -08:00
Thomas Hisch
ddbea29c12 Remove terminal_reporter workaround from logging.py
The workaround was removed from the logging module by creating python
properties for verbosity related settings in the terminalreporter.

Closes: #4733
2019-02-07 22:52:07 +01:00
Anthony Sottile
4c7ddb8d9b Fix parametrize(... ids=<function>) when the function returns non-strings. 2019-02-07 12:25:59 -08:00
Thomas Hisch
a1fcd6e445 Merge pull request #4734 from thisch/refactor_clilogging
Refactor _setup_cli_logging code
2019-02-07 20:15:41 +01:00
Thomas Hisch
7b8fd0cc12 Refactor _setup_cli_logging code
Change the indentation in _setup_cli_logging by moving the
self._log_cli_enabled check outside of the _setup_cli_logging method.
2019-02-07 19:39:12 +01:00
Paweł Stradomski
391dc549c0 Add comment on why realpath is needed 2019-02-07 12:56:13 +01:00
Ronny Pfannschmidt
526f4a95cc Merge pull request #4735 from kohr-h/pytest_plugins_module_name
Mention that `pytest_plugins` should not be used as module name
2019-02-07 07:07:58 +01:00
Paweł Stradomski
59e6fb94b5 Fix "ValueError: Plugin already registered" exceptions when running in build directories that symlink to actual source. 2019-02-07 02:05:22 +01:00
Bruno Oliveira
2f083504ee Merge pull request #4709 from namurphy/warns-docs
Document how to customize test failure message for missing warnings
2019-02-06 20:51:14 -02:00
Zac Hatfield-Dodds
3384ffc6eb Merge pull request #4725 from nicoddemus/collection-finish
Call pytest_report_collectionfinish hook when --collect-only is passed
2019-02-06 12:38:12 -10:00
Holger Kohr
7445b5345f Mention that pytest_plugins should not be used as module name 2019-02-06 21:57:42 +01:00
Ronny Pfannschmidt
429485e621 Merge pull request #4720 from thisch/removesetupclilogging
Only call _setup_cli_logging in __init__
2019-02-06 20:05:41 +01:00
Bruno Oliveira
52d497570b Merge pull request #4731 from nicoddemus/travis-py38
Add py38-dev job to Travis
2019-02-06 16:24:07 -02:00
Bruno Oliveira
0c5e717f43 Add py38-dev job to Travis 2019-02-06 13:11:00 -02:00
Bruno Oliveira
54af0f4c65 Call pytest_report_collectionfinish hook when --collect-only is passed
Fix #2895
2019-02-06 12:58:23 -02:00
Bruno Oliveira
678dfaa6eb Merge pull request #4728 from nicoddemus/usage-error-module
Do not raise UsageError when "pytest_plugins" is a module
2019-02-06 12:22:12 -02:00
Bruno Oliveira
19c93d16d1 Do not raise UsageError when "pytest_plugins" is a module
Fix #3899
2019-02-06 08:24:22 -02:00
Thomas Hisch
0ce8b910ca Only call _setup_cli_logging in __init__
Supersedes #4719
2019-02-06 07:03:40 +01:00
Anthony Sottile
c780d1fa7c Merge pull request #4723 from asottile/docstring_fix_py38
Remove workaround for docstrings for py38+
2019-02-05 15:23:48 -08:00
Bruno Oliveira
584c052da4 Fix linting and change False to True as requested in review 2019-02-05 19:04:26 -02:00
Anthony Sottile
315374008b Remove workaround for docstrings for py38+ 2019-02-05 12:48:18 -08:00
Ronny Pfannschmidt
a9457345ee Merge pull request #4715 from nicoddemus/fix-changelog-typo
Fix typo in CHANGELOG
2019-02-04 13:53:48 +01:00
Bruno Oliveira
726e165932 Fix typo in CHANGELOG 2019-02-04 08:38:48 -02:00
Anthony Sottile
2264db7f4a Merge pull request #4682 from arel/parameterize-conditional-raises-document-only
Document parametrizing conditional raises
2019-02-02 13:15:26 -08:00
Nick Murphy
4e93dc2c97 Update changelog for pytest.warns doc update 2019-02-01 22:11:41 -05:00
Nick Murphy
8003d8d279 Update AUTHORS 2019-02-01 21:55:01 -05:00
Nick Murphy
f0ecb25acd Document custom failure messages for missing warnings 2019-02-01 21:48:29 -05:00
Arel Cordero
7ec1a1407a Incorporating feedback from asottile 2019-02-02 01:57:17 +00:00
Bruno Oliveira
7dbe40092d Merge pull request #4703 from nicoddemus/setup-class-inheritance-4700
Fix setUpClass being called in subclasses that were skipped
2019-02-01 16:32:27 -02:00
Bruno Oliveira
e53563ebbe Merge pull request #4706 from hackebrot/add-xfail-note-to-4.2.0-changelog
Update changelog to reflect spelling change of xfail
2019-02-01 10:35:46 -02:00
Raphael Pierzina
c2c9b27771 Update changelog to reflect spelling change of xfail in teststatus report
Resolve #4705
2019-02-01 13:15:35 +01:00
Bruno Oliveira
c3d7340542 Fix setUpClass being called in subclasses that were skipped
Fix #4700
2019-01-31 20:24:11 -02:00
Bruno Oliveira
7dcd9bf5ad Merge pull request #4696 from nicoddemus/release-4.2.0
Release 4.2.0
2019-01-30 19:37:00 -02:00
Arel Cordero
8a1afe4213 Including note on using nullcontext in Python 3.7+ 2019-01-28 13:31:08 +00:00
Arel Cordero
fd4289dae0 Adding does_not_raise to documentation only 2019-01-27 16:42:10 +00:00
Arel Cordero
977adf1354 Improving sphinx docs based on feedback 2019-01-27 16:41:23 +00:00
Arel Cordero
c1fe07276c Adding changelog entries for does_not_raise 2019-01-27 16:41:10 +00:00
Arel Cordero
c166b80a8c Documenting raises/does_not_raise + parametrize 2019-01-27 16:40:49 +00:00
Arel Cordero
afe9fd5ffd Adds does_not_raise context manager
Addressing issues #4324 and #1830
2019-01-27 16:40:35 +00:00
36 changed files with 657 additions and 157 deletions

3
.gitignore vendored
View File

@@ -45,3 +45,6 @@ coverage.xml
.project
.settings
.vscode
# generated by pip
pip-wheel-metadata/

View File

@@ -17,12 +17,17 @@ env:
# Specialized factors for py27.
- TOXENV=py27-nobyte
- TOXENV=py27-xdist
- TOXENV=py27-pluggymaster PYTEST_NO_COVERAGE=1
- TOXENV=py27-pluggymaster
# Specialized factors for py37.
- TOXENV=py37-pexpect,py37-trial,py37-numpy
- TOXENV=py37-pluggymaster PYTEST_NO_COVERAGE=1
- TOXENV=py37-pluggymaster
- TOXENV=py37-freeze PYTEST_NO_COVERAGE=1
matrix:
allow_failures:
- python: '3.8-dev'
env: TOXENV=py38
jobs:
include:
# Coverage tracking is slow with pypy, skip it.
@@ -35,6 +40,8 @@ jobs:
python: '3.5'
- env: TOXENV=py36
python: '3.6'
- env: TOXENV=py38
python: '3.8-dev'
- env: TOXENV=py37
- &test-macos
language: generic

View File

@@ -27,6 +27,7 @@ Anthony Shaw
Anthony Sottile
Anton Lodder
Antony Lee
Arel Cordero
Armin Rigo
Aron Coyle
Aron Curzon
@@ -173,6 +174,7 @@ Nathaniel Waisbrot
Ned Batchelder
Neven Mundar
Nicholas Devenish
Nicholas Murphy
Niclas Olofsson
Nicolas Delaby
Oleg Pidsadnyi

View File

@@ -18,13 +18,69 @@ with advance notice in the **Deprecations** section of releases.
.. towncrier release notes start
pytest 4.2.1 (2019-02-12)
=========================
Bug Fixes
---------
- `#2895 <https://github.com/pytest-dev/pytest/issues/2895>`_: The ``pytest_report_collectionfinish`` hook now is also called with ``--collect-only``.
- `#3899 <https://github.com/pytest-dev/pytest/issues/3899>`_: Do not raise ``UsageError`` when an imported package has a ``pytest_plugins.py`` child module.
- `#4347 <https://github.com/pytest-dev/pytest/issues/4347>`_: Fix output capturing when using pdb++ with recursive debugging.
- `#4592 <https://github.com/pytest-dev/pytest/issues/4592>`_: Fix handling of ``collect_ignore`` via parent ``conftest.py``.
- `#4700 <https://github.com/pytest-dev/pytest/issues/4700>`_: Fix regression where ``setUpClass`` would always be called in subclasses even if all tests
were skipped by a ``unittest.skip()`` decorator applied in the subclass.
- `#4739 <https://github.com/pytest-dev/pytest/issues/4739>`_: Fix ``parametrize(... ids=<function>)`` when the function returns non-strings.
- `#4745 <https://github.com/pytest-dev/pytest/issues/4745>`_: Fix/improve collection of args when passing in ``__init__.py`` and a test file.
- `#4770 <https://github.com/pytest-dev/pytest/issues/4770>`_: ``more_itertools`` is now constrained to <6.0.0 when required for Python 2.7 compatibility.
- `#526 <https://github.com/pytest-dev/pytest/issues/526>`_: Fix "ValueError: Plugin already registered" exceptions when running in build directories that symlink to actual source.
Improved Documentation
----------------------
- `#3899 <https://github.com/pytest-dev/pytest/issues/3899>`_: Add note to ``plugins.rst`` that ``pytest_plugins`` should not be used as a name for a user module containing plugins.
- `#4324 <https://github.com/pytest-dev/pytest/issues/4324>`_: Document how to use ``raises`` and ``does_not_raise`` to write parametrized tests with conditional raises.
- `#4709 <https://github.com/pytest-dev/pytest/issues/4709>`_: Document how to customize test failure messages when using
``pytest.warns``.
Trivial/Internal Changes
------------------------
- `#4741 <https://github.com/pytest-dev/pytest/issues/4741>`_: Some verbosity related attributes of the TerminalReporter plugin are now
read only properties.
pytest 4.2.0 (2019-01-30)
=========================
Features
--------
- `#3094 <https://github.com/pytest-dev/pytest/issues/3094>`_: `Class xunit-style <https://docs.pytest.org/en/latest/xunit_setup.html>`__ functions and methods
- `#3094 <https://github.com/pytest-dev/pytest/issues/3094>`_: `Classic xunit-style <https://docs.pytest.org/en/latest/xunit_setup.html>`__ functions and methods
now obey the scope of *autouse* fixtures.
This fixes a number of surprising issues like ``setup_method`` being called before session-scoped
@@ -96,6 +152,9 @@ Trivial/Internal Changes
- `#4657 <https://github.com/pytest-dev/pytest/issues/4657>`_: Copy saferepr from pylib
- `#4668 <https://github.com/pytest-dev/pytest/issues/4668>`_: The verbose word for expected failures in the teststatus report changes from ``xfail`` to ``XFAIL`` to be consistent with other test outcomes.
pytest 4.1.1 (2019-01-12)
=========================

View File

@@ -13,11 +13,9 @@ environment:
# Specialized factors for py27.
- TOXENV: "py27-trial,py27-numpy,py27-nobyte"
- TOXENV: "py27-pluggymaster"
PYTEST_NO_COVERAGE: "1"
# Specialized factors for py37.
- TOXENV: "py37-trial,py37-numpy"
- TOXENV: "py37-pluggymaster"
PYTEST_NO_COVERAGE: "1"
- TOXENV: "py37-freeze"
PYTEST_NO_COVERAGE: "1"

View File

@@ -6,6 +6,7 @@ Release announcements
:maxdepth: 2
release-4.2.1
release-4.2.0
release-4.1.1
release-4.1.0

View File

@@ -0,0 +1,30 @@
pytest-4.2.1
=======================================
pytest 4.2.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:
* Anthony Sottile
* Arel Cordero
* Bruno Oliveira
* Daniel Hahler
* Holger Kohr
* Kevin J. Foley
* Nick Murphy
* Paweł Stradomski
* Raphael Pierzina
* Ronny Pfannschmidt
* Sam Brightman
* Thomas Hisch
* Zac Hatfield-Dodds
Happy testing,
The pytest Development Team

View File

@@ -205,8 +205,8 @@ Special comparisons are done for a number of cases:
See the :ref:`reporting demo <tbreportdemo>` for many more examples.
Defining your own assertion comparison
----------------------------------------------
Defining your own explanation for failed assertions
---------------------------------------------------
It is possible to add your own detailed explanations by implementing
the ``pytest_assertrepr_compare`` hook.

View File

@@ -565,3 +565,50 @@ As the result:
- The test ``test_eval[1+7-8]`` passed, but the name is autogenerated and confusing.
- The test ``test_eval[basic_2+4]`` passed.
- The test ``test_eval[basic_6*9]`` was expected to fail and did fail.
.. _`parametrizing_conditional_raising`:
Parametrizing conditional raising
--------------------------------------------------------------------
Use :func:`pytest.raises` with the
:ref:`pytest.mark.parametrize ref` decorator to write parametrized tests
in which some tests raise exceptions and others do not.
It is helpful to define a no-op context manager ``does_not_raise`` to serve
as a complement to ``raises``. For example::
from contextlib import contextmanager
import pytest
@contextmanager
def does_not_raise():
yield
@pytest.mark.parametrize('example_input,expectation', [
(3, does_not_raise()),
(2, does_not_raise()),
(1, does_not_raise()),
(0, pytest.raises(ZeroDivisionError)),
])
def test_division(example_input, expectation):
"""Test how much I know division."""
with expectation:
assert (6 / example_input) is not None
In the example above, the first three test cases should run unexceptionally,
while the fourth should raise ``ZeroDivisionError``.
If you're only supporting Python 3.7+, you can simply use ``nullcontext``
to define ``does_not_raise``::
from contextlib import nullcontext as does_not_raise
Or, if you're supporting Python 3.3+ you can use::
from contextlib import ExitStack as does_not_raise
Or, if desired, you can ``pip install contextlib2`` and use::
from contextlib2 import ExitStack as does_not_raise

View File

@@ -84,6 +84,11 @@ will be loaded as well.
:ref:`full explanation <requiring plugins in non-root conftests>`
in the Writing plugins section.
.. note::
The name ``pytest_plugins`` is reserved and should not be used as a
name for a custom plugin module.
.. _`findpluginname`:
Finding out which plugins are active

View File

@@ -1015,6 +1015,20 @@ passed multiple times. The expected format is ``name=value``. For example::
This tells pytest to ignore deprecation warnings and turn all other warnings
into errors. For more information please refer to :ref:`warnings`.
.. confval:: junit_family
.. versionadded:: 4.2
Configures the format of the generated JUnit XML file. The possible options are:
* ``xunit1`` (or ``legacy``): produces old style output, compatible with the xunit 1.0 format. **This is the default**.
* ``xunit2``: produces `xunit 2.0 style output <https://github.com/jenkinsci/xunit-plugin/blob/xunit-2.3.2/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd>`__,
which should be more compatible with latest Jenkins versions.
.. code-block:: ini
[pytest]
junit_family = xunit2
.. confval:: junit_suite_name

View File

@@ -233,7 +233,7 @@ You can also use it as a contextmanager::
.. _warns:
Asserting warnings with the warns function
-----------------------------------------------
------------------------------------------
.. versionadded:: 2.8
@@ -291,7 +291,7 @@ Alternatively, you can examine raised warnings in detail using the
.. _recwarn:
Recording warnings
------------------------
------------------
You can record raised warnings either using ``pytest.warns`` or with
the ``recwarn`` fixture.
@@ -329,6 +329,26 @@ warnings, or index into it to get a particular recorded warning.
Full API: :class:`WarningsRecorder`.
.. _custom_failure_messages:
Custom failure messages
-----------------------
Recording warnings provides an opportunity to produce custom test
failure messages for when no warnings are issued or other conditions
are met.
.. code-block:: python
def test():
with pytest.warns(Warning) as record:
f()
if not record:
pytest.fail("Expected a warning!")
If no warnings are issued when calling ``f``, then ``not record`` will
evaluate to ``True``. You can then call ``pytest.fail`` with a
custom error message.
.. _internal-warnings:

View File

@@ -5,6 +5,7 @@ requires = [
"setuptools-scm",
"wheel",
]
build-backend = "setuptools.build_meta"
[tool.towncrier]
package = "pytest"

View File

@@ -10,7 +10,8 @@ INSTALL_REQUIRES = [
"six>=1.10.0",
"setuptools",
"attrs>=17.4.0",
"more-itertools>=4.0.0",
'more-itertools>=4.0.0,<6.0.0;python_version<="2.7"',
'more-itertools>=4.0.0;python_version>"2.7"',
"atomicwrites>=1.0",
'funcsigs;python_version<"3.0"',
'pathlib2>=2.2.0;python_version<"3.6"',

View File

@@ -408,7 +408,10 @@ class PytestPluginManager(PluginManager):
continue
conftestpath = parent.join("conftest.py")
if conftestpath.isfile():
mod = self._importconftest(conftestpath)
# Use realpath to avoid loading the same conftest twice
# with build systems that create build directories containing
# symlinks to actual files.
mod = self._importconftest(conftestpath.realpath())
clist.append(mod)
self._dirpath2confmods[directory] = clist
return clist
@@ -559,8 +562,8 @@ def _get_plugin_specs_as_list(specs):
which case it is returned as a list. Specs can also be `None` in which case an
empty list is returned.
"""
if specs is not None:
if isinstance(specs, str):
if specs is not None and not isinstance(specs, types.ModuleType):
if isinstance(specs, six.string_types):
specs = specs.split(",") if specs else []
if not isinstance(specs, (list, tuple)):
raise UsageError(

View File

@@ -75,6 +75,7 @@ class pytestPDB(object):
_config = None
_pdb_cls = pdb.Pdb
_saved = []
_recursive_debug = 0
@classmethod
def _init_pdb(cls, *args, **kwargs):
@@ -87,29 +88,37 @@ class pytestPDB(object):
capman.suspend_global_capture(in_=True)
tw = _pytest.config.create_terminal_writer(cls._config)
tw.line()
# Handle header similar to pdb.set_trace in py37+.
header = kwargs.pop("header", None)
if header is not None:
tw.sep(">", header)
elif capman and capman.is_globally_capturing():
tw.sep(">", "PDB set_trace (IO-capturing turned off)")
else:
tw.sep(">", "PDB set_trace")
if cls._recursive_debug == 0:
# Handle header similar to pdb.set_trace in py37+.
header = kwargs.pop("header", None)
if header is not None:
tw.sep(">", header)
elif capman and capman.is_globally_capturing():
tw.sep(">", "PDB set_trace (IO-capturing turned off)")
else:
tw.sep(">", "PDB set_trace")
class _PdbWrapper(cls._pdb_cls, object):
_pytest_capman = capman
_continued = False
def do_debug(self, arg):
cls._recursive_debug += 1
ret = super(_PdbWrapper, self).do_debug(arg)
cls._recursive_debug -= 1
return ret
def do_continue(self, arg):
ret = super(_PdbWrapper, self).do_continue(arg)
if self._pytest_capman:
tw = _pytest.config.create_terminal_writer(cls._config)
tw.line()
if self._pytest_capman.is_globally_capturing():
tw.sep(">", "PDB continue (IO-capturing resumed)")
else:
tw.sep(">", "PDB continue")
self._pytest_capman.resume_global_capture()
if cls._recursive_debug == 0:
if self._pytest_capman.is_globally_capturing():
tw.sep(">", "PDB continue (IO-capturing resumed)")
else:
tw.sep(">", "PDB continue")
self._pytest_capman.resume_global_capture()
cls._pluginmanager.hook.pytest_leave_pdb(
config=cls._config, pdb=self
)

View File

@@ -370,6 +370,8 @@ def get_actual_log_level(config, *setting_names):
)
# run after terminalreporter/capturemanager are configured
@pytest.hookimpl(trylast=True)
def pytest_configure(config):
config.pluginmanager.register(LoggingPlugin(config), "logging-plugin")
@@ -388,8 +390,6 @@ class LoggingPlugin(object):
# enable verbose output automatically if live logging is enabled
if self._log_cli_enabled() and not config.getoption("verbose"):
# sanity check: terminal reporter should not have been loaded at this point
assert self._config.pluginmanager.get_plugin("terminalreporter") is None
config.option.verbose = 1
self.print_logs = get_option_ini(config, "log_print")
@@ -420,6 +420,47 @@ class LoggingPlugin(object):
self.log_cli_handler = None
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.
if self._log_cli_enabled():
self._setup_cli_logging()
def _setup_cli_logging(self):
config = self._config
terminal_reporter = config.pluginmanager.get_plugin("terminalreporter")
if terminal_reporter is None:
# terminal reporter is disabled e.g. by pytest-xdist.
return
capture_manager = config.pluginmanager.get_plugin("capturemanager")
# if capturemanager plugin is disabled, live logging still works.
log_cli_handler = _LiveLoggingStreamHandler(terminal_reporter, capture_manager)
log_cli_format = get_option_ini(config, "log_cli_format", "log_format")
log_cli_date_format = get_option_ini(
config, "log_cli_date_format", "log_date_format"
)
if (
config.option.color != "no"
and ColoredLevelFormatter.LEVELNAME_FMT_REGEX.search(log_cli_format)
):
log_cli_formatter = ColoredLevelFormatter(
create_terminal_writer(config),
log_cli_format,
datefmt=log_cli_date_format,
)
else:
log_cli_formatter = logging.Formatter(
log_cli_format, datefmt=log_cli_date_format
)
log_cli_level = get_actual_log_level(config, "log_cli_level", "log_level")
self.log_cli_handler = log_cli_handler
self.live_logs_context = lambda: catching_logs(
log_cli_handler, formatter=log_cli_formatter, level=log_cli_level
)
def _log_cli_enabled(self):
"""Return True if log_cli should be considered enabled, either explicitly
or because --log-cli-level was given in the command-line.
@@ -430,10 +471,6 @@ class LoggingPlugin(object):
@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")
@@ -513,7 +550,6 @@ class LoggingPlugin(object):
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
def pytest_sessionstart(self):
self._setup_cli_logging()
with self.live_logs_context():
if self.log_cli_handler:
self.log_cli_handler.set_when("sessionstart")
@@ -533,46 +569,6 @@ class LoggingPlugin(object):
else:
yield # run all the tests
def _setup_cli_logging(self):
"""Sets up the handler and logger for the Live Logs feature, if enabled."""
terminal_reporter = self._config.pluginmanager.get_plugin("terminalreporter")
if self._log_cli_enabled() and terminal_reporter is not None:
capture_manager = self._config.pluginmanager.get_plugin("capturemanager")
log_cli_handler = _LiveLoggingStreamHandler(
terminal_reporter, capture_manager
)
log_cli_format = get_option_ini(
self._config, "log_cli_format", "log_format"
)
log_cli_date_format = get_option_ini(
self._config, "log_cli_date_format", "log_date_format"
)
if (
self._config.option.color != "no"
and ColoredLevelFormatter.LEVELNAME_FMT_REGEX.search(log_cli_format)
):
log_cli_formatter = ColoredLevelFormatter(
create_terminal_writer(self._config),
log_cli_format,
datefmt=log_cli_date_format,
)
else:
log_cli_formatter = logging.Formatter(
log_cli_format, datefmt=log_cli_date_format
)
log_cli_level = get_actual_log_level(
self._config, "log_cli_level", "log_level"
)
self.log_cli_handler = log_cli_handler
self.live_logs_context = lambda: catching_logs(
log_cli_handler, formatter=log_cli_formatter, level=log_cli_level
)
else:
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):
"""

View File

@@ -550,19 +550,9 @@ class Session(nodes.FSCollector):
if argpath.check(dir=1):
assert not names, "invalid arg %r" % (arg,)
if six.PY2:
def filter_(f):
return f.check(file=1) and not f.strpath.endswith("*.pyc")
else:
def filter_(f):
return f.check(file=1)
seen_dirs = set()
for path in argpath.visit(
fil=filter_, rec=self._recurse, bf=True, sort=True
fil=self._visit_filter, rec=self._recurse, bf=True, sort=True
):
dirpath = path.dirpath()
if dirpath not in seen_dirs:
@@ -592,7 +582,7 @@ class Session(nodes.FSCollector):
col = self._node_cache[argpath]
else:
collect_root = self._pkg_roots.get(argpath.dirname, self)
col = collect_root._collectfile(argpath)
col = collect_root._collectfile(argpath, handle_dupes=False)
if col:
self._node_cache[argpath] = col
m = self.matchnodes(col, names)
@@ -607,6 +597,7 @@ class Session(nodes.FSCollector):
yield y
def _collectfile(self, path, handle_dupes=True):
assert path.isfile()
ihook = self.gethookproxy(path)
if not self.isinitpath(path):
if ihook.pytest_ignore_collect(path=path, config=self.config):
@@ -636,6 +627,18 @@ class Session(nodes.FSCollector):
ihook.pytest_collect_directory(path=dirpath, parent=self)
return True
if six.PY2:
@staticmethod
def _visit_filter(f):
return f.check(file=1) and not f.strpath.endswith("*.pyc")
else:
@staticmethod
def _visit_filter(f):
return f.check(file=1)
def _tryconvertpyarg(self, x):
"""Convert a dotted module name to path."""
try:

View File

@@ -81,7 +81,11 @@ class LsofFdLeakChecker(object):
def _exec_lsof(self):
pid = os.getpid()
return subprocess.check_output(("lsof", "-Ffn0", "-p", str(pid))).decode()
# py3: use subprocess.DEVNULL directly.
with open(os.devnull, "wb") as devnull:
return subprocess.check_output(
("lsof", "-Ffn0", "-p", str(pid)), stderr=devnull
).decode()
def _parse_lsof_output(self, out):
def isopen(line):

View File

@@ -599,6 +599,7 @@ class Package(Module):
return proxy
def _collectfile(self, path, handle_dupes=True):
assert path.isfile()
ihook = self.gethookproxy(path)
if not self.isinitpath(path):
if ihook.pytest_ignore_collect(path=path, config=self.config):
@@ -642,11 +643,12 @@ class Package(Module):
):
continue
if path.isdir() and path.join("__init__.py").check(file=1):
pkg_prefixes.add(path)
for x in self._collectfile(path):
yield x
if path.isdir():
if path.join("__init__.py").check(file=1):
pkg_prefixes.add(path)
else:
for x in self._collectfile(path):
yield x
def _get_xunit_setup_teardown(holder, attr_name, param_obj=None):
@@ -1144,9 +1146,10 @@ def _find_parametrized_scope(argnames, arg2fixturedefs, indirect):
def _idval(val, argname, idx, idfn, item, config):
if idfn:
s = None
try:
s = idfn(val)
generated_id = idfn(val)
if generated_id is not None:
val = generated_id
except Exception as e:
# See issue https://github.com/pytest-dev/pytest/issues/2169
msg = "{}: error raised while trying to determine id of parameter '{}' at position {}\n"
@@ -1154,10 +1157,7 @@ def _idval(val, argname, idx, idfn, item, config):
# we only append the exception type and message because on Python 2 reraise does nothing
msg += " {}: {}\n".format(type(e).__name__, e)
six.raise_from(ValueError(msg), e)
if s:
return ascii_escaped(s)
if config:
elif config:
hook_id = config.hook.pytest_make_parametrize_id(
config=config, val=val, argname=argname
)

View File

@@ -621,6 +621,14 @@ def raises(expected_exception, *args, **kwargs):
...
>>> assert exc_info.type is ValueError
**Using with** ``pytest.mark.parametrize``
When using :ref:`pytest.mark.parametrize ref`
it is possible to parametrize tests such that
some runs raise an exception and others do not.
See :ref:`parametrizing_conditional_raising` for an example.
**Legacy form**
It is possible to specify a callable by passing a to-be-called lambda::

View File

@@ -222,12 +222,9 @@ class TerminalReporter(object):
import _pytest.config
self.config = config
self.verbosity = self.config.option.verbose
self.showheader = self.verbosity >= 0
self.showfspath = self.verbosity >= 0
self.showlongtestinfo = self.verbosity > 0
self._numcollected = 0
self._session = None
self._showfspath = None
self.stats = {}
self.startdir = py.path.local()
@@ -255,6 +252,28 @@ class TerminalReporter(object):
return False
return self.config.getini("console_output_style") in ("progress", "count")
@property
def verbosity(self):
return self.config.option.verbose
@property
def showheader(self):
return self.verbosity >= 0
@property
def showfspath(self):
if self._showfspath is None:
return self.verbosity >= 0
return self._showfspath
@showfspath.setter
def showfspath(self, value):
self._showfspath = value
@property
def showlongtestinfo(self):
return self.verbosity > 0
def hasopt(self, char):
char = {"xfailed": "x", "skipped": "s"}.get(char, char)
return char in self.reportchars
@@ -574,19 +593,20 @@ class TerminalReporter(object):
return lines
def pytest_collection_finish(self, session):
if self.config.option.collectonly:
if self.config.getoption("collectonly"):
self._printcollecteditems(session.items)
if self.stats.get("failed"):
self._tw.sep("!", "collection failures")
for rep in self.stats.get("failed"):
rep.toterminal(self._tw)
return 1
return 0
lines = self.config.hook.pytest_report_collectionfinish(
config=self.config, startdir=self.startdir, items=session.items
)
self._write_report_lines_from_hooks(lines)
if self.config.getoption("collectonly"):
if self.stats.get("failed"):
self._tw.sep("!", "collection failures")
for rep in self.stats.get("failed"):
rep.toterminal(self._tw)
def _printcollecteditems(self, items):
# to print out items and their parent collectors
# we take care to leave out Instances aka ()

View File

@@ -87,6 +87,9 @@ def _make_xunit_fixture(obj, setup_name, teardown_name, scope, pass_self):
@pytest.fixture(scope=scope, autouse=True)
def fixture(self, request):
if getattr(self, "__unittest_skip__", None):
reason = self.__unittest_skip_why__
pytest.skip(reason)
if setup is not None:
if pass_self:
setup(self, request.function)

View File

@@ -969,6 +969,20 @@ def test_import_plugin_unicode_name(testdir):
assert r.ret == 0
def test_pytest_plugins_as_module(testdir):
"""Do not raise an error if pytest_plugins attribute is a module (#3899)"""
testdir.makepyfile(
**{
"__init__.py": "",
"pytest_plugins.py": "",
"conftest.py": "from . import pytest_plugins",
"test_foo.py": "def test(): pass",
}
)
result = testdir.runpytest()
result.stdout.fnmatch_lines("* 1 passed in *")
def test_deferred_hook_checking(testdir):
"""
Check hooks as late as possible (#1821).

View File

@@ -0,0 +1,13 @@
"""Skipping an entire subclass with unittest.skip() should *not* call setUp from a base class."""
import unittest
class Base(unittest.TestCase):
def setUp(self):
assert 0
@unittest.skip("skip all tests")
class Test(Base):
def test_foo(self):
assert 0

View File

@@ -0,0 +1,14 @@
"""Skipping an entire subclass with unittest.skip() should *not* call setUpClass from a base class."""
import unittest
class Base(unittest.TestCase):
@classmethod
def setUpClass(cls):
assert 0
@unittest.skip("skip all tests")
class Test(Base):
def test_foo(self):
assert 0

View File

@@ -0,0 +1,12 @@
"""setUpModule is always called, even if all tests in the module are skipped"""
import unittest
def setUpModule():
assert 0
@unittest.skip("skip all tests")
class Base(unittest.TestCase):
def test(self):
assert 0

View File

@@ -418,6 +418,21 @@ class TestMetafunc(object):
]
)
def test_parametrize_ids_returns_non_string(self, testdir):
testdir.makepyfile(
"""\
import pytest
def ids(d):
return d
@pytest.mark.parametrize("arg", ({1: 2}, {3, 4}), ids=ids)
def test(arg):
assert arg
"""
)
assert testdir.runpytest().ret == 0
def test_idmaker_with_ids(self):
from _pytest.python import idmaker

View File

@@ -94,6 +94,54 @@ class TestRaises(object):
result = testdir.runpytest()
result.stdout.fnmatch_lines(["*3 passed*"])
def test_does_not_raise(self, testdir):
testdir.makepyfile(
"""
from contextlib import contextmanager
import pytest
@contextmanager
def does_not_raise():
yield
@pytest.mark.parametrize('example_input,expectation', [
(3, does_not_raise()),
(2, does_not_raise()),
(1, does_not_raise()),
(0, pytest.raises(ZeroDivisionError)),
])
def test_division(example_input, expectation):
'''Test how much I know division.'''
with expectation:
assert (6 / example_input) is not None
"""
)
result = testdir.runpytest()
result.stdout.fnmatch_lines(["*4 passed*"])
def test_does_not_raise_does_raise(self, testdir):
testdir.makepyfile(
"""
from contextlib import contextmanager
import pytest
@contextmanager
def does_not_raise():
yield
@pytest.mark.parametrize('example_input,expectation', [
(0, does_not_raise()),
(1, pytest.raises(ZeroDivisionError)),
])
def test_division(example_input, expectation):
'''Test how much I know division.'''
with expectation:
assert (6 / example_input) is not None
"""
)
result = testdir.runpytest()
result.stdout.fnmatch_lines(["*2 failed*"])
def test_noclass(self):
with pytest.raises(TypeError):
pytest.raises("wrong", lambda: None)

View File

@@ -68,38 +68,16 @@ def getmsg(f, extra_ns=None, must_pass=False):
pytest.fail("function didn't raise at all")
def adjust_body_for_new_docstring_in_module_node(m):
"""Module docstrings in 3.8 are part of Module node.
This was briefly in 3.7 as well but got reverted in beta 5.
It's not in the body so we remove it so the following body items have
the same indexes on all Python versions:
TODO:
We have a complicated sys.version_info if in here to ease testing on
various Python 3.7 versions, but we should remove the 3.7 check after
3.7 is released as stable to make this check more straightforward.
"""
if sys.version_info < (3, 8) and not (
(3, 7) <= sys.version_info <= (3, 7, 0, "beta", 4)
):
assert len(m.body) > 1
assert isinstance(m.body[0], ast.Expr)
assert isinstance(m.body[0].value, ast.Str)
del m.body[0]
class TestAssertionRewrite(object):
def test_place_initial_imports(self):
s = """'Doc string'\nother = stuff"""
m = rewrite(s)
adjust_body_for_new_docstring_in_module_node(m)
for imp in m.body[0:2]:
assert isinstance(m.body[0], ast.Expr)
for imp in m.body[1:3]:
assert isinstance(imp, ast.Import)
assert imp.lineno == 2
assert imp.col_offset == 0
assert isinstance(m.body[2], ast.Assign)
assert isinstance(m.body[3], ast.Assign)
s = """from __future__ import division\nother_stuff"""
m = rewrite(s)
assert isinstance(m.body[0], ast.ImportFrom)
@@ -110,24 +88,24 @@ class TestAssertionRewrite(object):
assert isinstance(m.body[3], ast.Expr)
s = """'doc string'\nfrom __future__ import division"""
m = rewrite(s)
adjust_body_for_new_docstring_in_module_node(m)
assert isinstance(m.body[0], ast.ImportFrom)
for imp in m.body[1:3]:
assert isinstance(m.body[0], ast.Expr)
assert isinstance(m.body[1], ast.ImportFrom)
for imp in m.body[2:4]:
assert isinstance(imp, ast.Import)
assert imp.lineno == 2
assert imp.col_offset == 0
s = """'doc string'\nfrom __future__ import division\nother"""
m = rewrite(s)
adjust_body_for_new_docstring_in_module_node(m)
assert isinstance(m.body[0], ast.ImportFrom)
for imp in m.body[1:3]:
assert isinstance(m.body[0], ast.Expr)
assert isinstance(m.body[1], ast.ImportFrom)
for imp in m.body[2:4]:
assert isinstance(imp, ast.Import)
assert imp.lineno == 3
assert imp.col_offset == 0
assert isinstance(m.body[3], ast.Expr)
assert isinstance(m.body[4], ast.Expr)
s = """from . import relative\nother_stuff"""
m = rewrite(s)
for imp in m.body[0:2]:
for imp in m.body[:2]:
assert isinstance(imp, ast.Import)
assert imp.lineno == 1
assert imp.col_offset == 0
@@ -136,9 +114,8 @@ class TestAssertionRewrite(object):
def test_dont_rewrite(self):
s = """'PYTEST_DONT_REWRITE'\nassert 14"""
m = rewrite(s)
adjust_body_for_new_docstring_in_module_node(m)
assert len(m.body) == 1
assert m.body[0].msg is None
assert len(m.body) == 2
assert m.body[1].msg is None
def test_dont_rewrite_plugin(self, testdir):
contents = {

View File

@@ -1144,3 +1144,45 @@ def test_collect_symlink_out_of_tree(testdir):
]
)
assert result.ret == 0
def test_collectignore_via_conftest(testdir, monkeypatch):
"""collect_ignore in parent conftest skips importing child (issue #4592)."""
tests = testdir.mkpydir("tests")
tests.ensure("conftest.py").write("collect_ignore = ['ignore_me']")
ignore_me = tests.mkdir("ignore_me")
ignore_me.ensure("__init__.py")
ignore_me.ensure("conftest.py").write("assert 0, 'should_not_be_called'")
result = testdir.runpytest()
assert result.ret == EXIT_NOTESTSCOLLECTED
def test_collect_pkg_init_and_file_in_args(testdir):
subdir = testdir.mkdir("sub")
init = subdir.ensure("__init__.py")
init.write("def test_init(): pass")
p = subdir.ensure("test_file.py")
p.write("def test_file(): pass")
# NOTE: without "-o python_files=*.py" this collects test_file.py twice.
# This changed/broke with "Add package scoped fixtures #2283" (2b1410895)
# initially (causing a RecursionError).
result = testdir.runpytest("-v", str(init), str(p))
result.stdout.fnmatch_lines(
[
"sub/test_file.py::test_file PASSED*",
"sub/test_file.py::test_file PASSED*",
"*2 passed in*",
]
)
result = testdir.runpytest("-v", "-o", "python_files=*.py", str(init), str(p))
result.stdout.fnmatch_lines(
[
"sub/__init__.py::test_init PASSED*",
"sub/test_file.py::test_file PASSED*",
"*2 passed in*",
]
)

View File

@@ -244,6 +244,42 @@ def test_conftest_symlink(testdir):
assert result.ret == EXIT_OK
@pytest.mark.skipif(
not hasattr(py.path.local, "mksymlinkto"),
reason="symlink not available on this platform",
)
def test_conftest_symlink_files(testdir):
"""Check conftest.py loading when running in directory with symlinks."""
real = testdir.tmpdir.mkdir("real")
source = {
"app/test_foo.py": "def test1(fixture): pass",
"app/__init__.py": "",
"app/conftest.py": textwrap.dedent(
"""
import pytest
print("conftest_loaded")
@pytest.fixture
def fixture():
print("fixture_used")
"""
),
}
testdir.makepyfile(**{"real/%s" % k: v for k, v in source.items()})
# Create a build directory that contains symlinks to actual files
# but doesn't symlink actual directories.
build = testdir.tmpdir.mkdir("build")
build.mkdir("app")
for f in source:
build.join(f).mksymlinkto(real.join(f))
build.chdir()
result = testdir.runpytest("-vs", "app/test_foo.py")
result.stdout.fnmatch_lines(["*conftest_loaded*", "PASSED"])
assert result.ret == EXIT_OK
def test_no_conftest(testdir):
testdir.makeconftest("assert 0")
result = testdir.runpytest("--noconftest")

View File

@@ -513,6 +513,76 @@ class TestPDB(object):
assert "1 failed" in rest
self.flush(child)
def test_pdb_interaction_continue_recursive(self, testdir):
p1 = testdir.makepyfile(
mytest="""
import pdb
import pytest
count_continue = 0
# Simulates pdbpp, which injects Pdb into do_debug, and uses
# self.__class__ in do_continue.
class CustomPdb(pdb.Pdb, object):
def do_debug(self, arg):
import sys
import types
newglobals = {
'Pdb': self.__class__, # NOTE: different with pdb.Pdb
'sys': sys,
}
if sys.version_info < (3, ):
do_debug_func = pdb.Pdb.do_debug.im_func
else:
do_debug_func = pdb.Pdb.do_debug
orig_do_debug = types.FunctionType(
do_debug_func.__code__, newglobals,
do_debug_func.__name__, do_debug_func.__defaults__,
)
return orig_do_debug(self, arg)
do_debug.__doc__ = pdb.Pdb.do_debug.__doc__
def do_continue(self, *args, **kwargs):
global count_continue
count_continue += 1
return super(CustomPdb, self).do_continue(*args, **kwargs)
def foo():
print("print_from_foo")
def test_1():
i = 0
print("hello17")
pytest.set_trace()
x = 3
print("hello18")
assert count_continue == 2, "unexpected_failure: %d != 2" % count_continue
pytest.fail("expected_failure")
"""
)
child = testdir.spawn_pytest("--pdbcls=mytest:CustomPdb %s" % str(p1))
child.expect(r"PDB set_trace \(IO-capturing turned off\)")
child.expect(r"\n\(Pdb")
child.sendline("debug foo()")
child.expect("ENTERING RECURSIVE DEBUGGER")
child.expect(r"\n\(\(Pdb")
child.sendline("c")
child.expect("LEAVING RECURSIVE DEBUGGER")
assert b"PDB continue" not in child.before
assert b"print_from_foo" in child.before
child.sendline("c")
child.expect(r"PDB continue \(IO-capturing resumed\)")
rest = child.read().decode("utf8")
assert "hello17" in rest # out is captured
assert "hello18" in rest # out is captured
assert "1 failed" in rest
assert "Failed: expected_failure" in rest
assert "AssertionError: unexpected_failure" not in rest
self.flush(child)
def test_pdb_without_capture(self, testdir):
p1 = testdir.makepyfile(
"""

View File

@@ -649,7 +649,10 @@ class TestTerminalFunctional(object):
assert "===" not in s
assert "passed" not in s
def test_report_collectionfinish_hook(self, testdir):
@pytest.mark.parametrize(
"params", [(), ("--collect-only",)], ids=["no-params", "collect-only"]
)
def test_report_collectionfinish_hook(self, testdir, params):
testdir.makeconftest(
"""
def pytest_report_collectionfinish(config, startdir, items):
@@ -664,7 +667,7 @@ class TestTerminalFunctional(object):
pass
"""
)
result = testdir.runpytest()
result = testdir.runpytest(*params)
result.stdout.fnmatch_lines(["collected 3 items", "hello from hook: 3 items"])

View File

@@ -1026,3 +1026,18 @@ def test_error_message_with_parametrized_fixtures(testdir):
"*Function type: TestCaseFunction",
]
)
@pytest.mark.parametrize(
"test_name, expected_outcome",
[
("test_setup_skip.py", "1 skipped"),
("test_setup_skip_class.py", "1 skipped"),
("test_setup_skip_module.py", "1 error"),
],
)
def test_setup_inheritance_skipping(testdir, test_name, expected_outcome):
"""Issue #4700"""
testdir.copy_example("unittest/{}".format(test_name))
result = testdir.runpytest()
result.stdout.fnmatch_lines("* {} in *".format(expected_outcome))

29
tox.ini
View File

@@ -1,5 +1,6 @@
[tox]
minversion = 2.0
isolated_build = True
minversion = 3.5.3
distshare = {homedir}/.tox/distshare
# make sure to update environment list in travis.yml and appveyor.yml
envlist =
@@ -9,6 +10,7 @@ envlist =
py35
py36
py37
py38
pypy
{py27,py37}-{pexpect,xdist,trial,numpy,pluggymaster}
py27-nobyte
@@ -23,7 +25,8 @@ commands =
coverage: coverage report
passenv = USER USERNAME COVERAGE_* TRAVIS
setenv =
# configuration if a user runs tox with a "coverage" factor, for example "tox -e py37-coverage"
# Configuration to run with coverage similar to Travis/Appveyor, e.g.
# "tox -e py37-coverage".
coverage: _PYTEST_TOX_COVERAGE_RUN=coverage run -m
coverage: _PYTEST_TOX_EXTRA_DEP=coverage-enable-subprocess
coverage: COVERAGE_FILE={toxinidir}/.coverage
@@ -50,8 +53,8 @@ commands = pre-commit run --all-files --show-diff-on-failure
[testenv:py27-xdist]
extras = testing
deps =
{[testenv]deps}
pytest-xdist>=1.13
{env:_PYTEST_TOX_EXTRA_DEP:}
commands =
{env:_PYTEST_TOX_COVERAGE_RUN:} pytest -n auto {posargs}
@@ -59,15 +62,15 @@ commands =
# NOTE: copied from above due to https://github.com/tox-dev/tox/issues/706.
extras = testing
deps =
{[testenv]deps}
pytest-xdist>=1.13
{env:_PYTEST_TOX_EXTRA_DEP:}
commands = {[testenv:py27-xdist]commands}
[testenv:py27-pexpect]
platform = linux|darwin
deps =
{[testenv]deps}
pexpect
{env:_PYTEST_TOX_EXTRA_DEP:}
commands =
{env:_PYTEST_TOX_COVERAGE_RUN:} pytest {posargs:testing/test_pdb.py testing/test_terminal.py testing/test_unittest.py}
@@ -79,8 +82,8 @@ commands = {[testenv:py27-pexpect]commands}
[testenv:py27-nobyte]
extras = testing
deps =
{[testenv]deps}
pytest-xdist>=1.13
{env:_PYTEST_TOX_EXTRA_DEP:}
distribute = true
setenv =
{[testenv]setenv}
@@ -90,8 +93,8 @@ commands =
[testenv:py27-trial]
deps =
{[testenv]deps}
twisted
{env:_PYTEST_TOX_EXTRA_DEP:}
commands =
{env:_PYTEST_TOX_COVERAGE_RUN:} pytest {posargs:testing/test_unittest.py}
@@ -101,8 +104,8 @@ commands = {[testenv:py27-trial]commands}
[testenv:py27-numpy]
deps =
{[testenv]deps}
numpy
{env:_PYTEST_TOX_EXTRA_DEP:}
commands=
{env:_PYTEST_TOX_COVERAGE_RUN:} pytest {posargs:testing/python/approx.py}
@@ -114,11 +117,13 @@ commands = {[testenv:py27-numpy]commands}
setenv=
{[testenv]setenv}
_PYTEST_SETUP_SKIP_PLUGGY_DEP=1
# NOTE: using env instead of "{[testenv]deps}", because of https://github.com/tox-dev/tox/issues/706.
_PYTEST_TOX_EXTRA_DEP=git+https://github.com/pytest-dev/pluggy.git@master
deps =
{[testenv]deps}
git+https://github.com/pytest-dev/pluggy.git@master
[testenv:py37-pluggymaster]
setenv = {[testenv:py27-pluggymaster]setenv}
deps = {[testenv:py27-pluggymaster]deps}
[testenv:docs]
basepython = python3
@@ -134,8 +139,8 @@ commands =
basepython = python3
skipsdist = True
deps =
{[testenv]deps}
PyYAML
{env:_PYTEST_TOX_EXTRA_DEP:}
commands =
{env:_PYTEST_TOX_COVERAGE_RUN:} pytest doc/en
{env:_PYTEST_TOX_COVERAGE_RUN:} pytest --doctest-modules --pyargs _pytest
@@ -165,7 +170,9 @@ commands =
[testenv:py37-freeze]
changedir = testing/freeze
# Disable PEP 517 with pip, which does not work with PyInstaller currently.
deps =
--no-use-pep517
pyinstaller
commands =
{envpython} create_executable.py