commit
f0a9f9042f
2
AUTHORS
2
AUTHORS
|
@ -16,6 +16,7 @@ Allan Feldman
|
||||||
Aly Sivji
|
Aly Sivji
|
||||||
Anatoly Bubenkoff
|
Anatoly Bubenkoff
|
||||||
Anders Hovmöller
|
Anders Hovmöller
|
||||||
|
Andras Mitzki
|
||||||
Andras Tim
|
Andras Tim
|
||||||
Andrea Cimatoribus
|
Andrea Cimatoribus
|
||||||
Andreas Zeidler
|
Andreas Zeidler
|
||||||
|
@ -51,6 +52,7 @@ Charles Cloud
|
||||||
Charnjit SiNGH (CCSJ)
|
Charnjit SiNGH (CCSJ)
|
||||||
Chris Lamb
|
Chris Lamb
|
||||||
Christian Boelsen
|
Christian Boelsen
|
||||||
|
Christian Fetzer
|
||||||
Christian Theunert
|
Christian Theunert
|
||||||
Christian Tismer
|
Christian Tismer
|
||||||
Christopher Gilling
|
Christopher Gilling
|
||||||
|
|
|
@ -18,6 +18,48 @@ with advance notice in the **Deprecations** section of releases.
|
||||||
|
|
||||||
.. towncrier release notes start
|
.. towncrier release notes start
|
||||||
|
|
||||||
|
pytest 4.3.0 (2019-02-16)
|
||||||
|
=========================
|
||||||
|
|
||||||
|
Deprecations
|
||||||
|
------------
|
||||||
|
|
||||||
|
- `#4724 <https://github.com/pytest-dev/pytest/issues/4724>`_: ``pytest.warns()`` now emits a warning when it receives unknown keyword arguments.
|
||||||
|
|
||||||
|
This will be changed into an error in the future.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Features
|
||||||
|
--------
|
||||||
|
|
||||||
|
- `#2753 <https://github.com/pytest-dev/pytest/issues/2753>`_: Usage errors from argparse are mapped to pytest's ``UsageError``.
|
||||||
|
|
||||||
|
|
||||||
|
- `#3711 <https://github.com/pytest-dev/pytest/issues/3711>`_: Add the ``--ignore-glob`` parameter to exclude test-modules with Unix shell-style wildcards.
|
||||||
|
Add the ``collect_ignore_glob`` for ``conftest.py`` to exclude test-modules with Unix shell-style wildcards.
|
||||||
|
|
||||||
|
|
||||||
|
- `#4698 <https://github.com/pytest-dev/pytest/issues/4698>`_: The warning about Python 2.7 and 3.4 not being supported in pytest 5.0 has been removed.
|
||||||
|
|
||||||
|
In the end it was considered to be more
|
||||||
|
of a nuisance than actual utility and users of those Python versions shouldn't have problems as ``pip`` will not
|
||||||
|
install pytest 5.0 on those interpreters.
|
||||||
|
|
||||||
|
|
||||||
|
- `#4707 <https://github.com/pytest-dev/pytest/issues/4707>`_: With the help of new ``set_log_path()`` method there is a way to set ``log_file`` paths from hooks.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Bug Fixes
|
||||||
|
---------
|
||||||
|
|
||||||
|
- `#4651 <https://github.com/pytest-dev/pytest/issues/4651>`_: ``--help`` and ``--version`` are handled with ``UsageError``.
|
||||||
|
|
||||||
|
|
||||||
|
- `#4782 <https://github.com/pytest-dev/pytest/issues/4782>`_: Fix ``AssertionError`` with collection of broken symlinks with packages.
|
||||||
|
|
||||||
|
|
||||||
pytest 4.2.1 (2019-02-12)
|
pytest 4.2.1 (2019-02-12)
|
||||||
=========================
|
=========================
|
||||||
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
Fix ``AssertionError`` with collection of broken symlinks with packages.
|
|
|
@ -6,6 +6,7 @@ Release announcements
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
|
|
||||||
|
release-4.3.0
|
||||||
release-4.2.1
|
release-4.2.1
|
||||||
release-4.2.0
|
release-4.2.0
|
||||||
release-4.1.1
|
release-4.1.1
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
pytest-4.3.0
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
The pytest team is proud to announce the 4.3.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:
|
||||||
|
|
||||||
|
* Andras Mitzki
|
||||||
|
* Anthony Sottile
|
||||||
|
* Bruno Oliveira
|
||||||
|
* Christian Fetzer
|
||||||
|
* Daniel Hahler
|
||||||
|
* Grygorii Iermolenko
|
||||||
|
* R. Alex Matevish
|
||||||
|
* Ronny Pfannschmidt
|
||||||
|
* cclauss
|
||||||
|
|
||||||
|
|
||||||
|
Happy testing,
|
||||||
|
The Pytest Development Team
|
|
@ -436,10 +436,8 @@ Running it results in some skips if we don't have all the python interpreters in
|
||||||
.. code-block:: pytest
|
.. code-block:: pytest
|
||||||
|
|
||||||
. $ pytest -rs -q multipython.py
|
. $ pytest -rs -q multipython.py
|
||||||
...sss...sssssssss...sss... [100%]
|
........................... [100%]
|
||||||
========================= short test summary info ==========================
|
27 passed in 0.12 seconds
|
||||||
SKIPPED [15] $REGENDOC_TMPDIR/CWD/multipython.py:30: 'python3.4' not found
|
|
||||||
12 passed, 15 skipped in 0.12 seconds
|
|
||||||
|
|
||||||
Indirect parametrization of optional implementations/imports
|
Indirect parametrization of optional implementations/imports
|
||||||
--------------------------------------------------------------------
|
--------------------------------------------------------------------
|
||||||
|
|
|
@ -41,6 +41,9 @@ you will see that ``pytest`` only collects test-modules, which do not match the
|
||||||
|
|
||||||
========================= 5 passed in 0.02 seconds =========================
|
========================= 5 passed in 0.02 seconds =========================
|
||||||
|
|
||||||
|
The ``--ignore-glob`` option allows to ignore test file paths based on Unix shell-style wildcards.
|
||||||
|
If you want to exclude test-modules that end with ``_01.py``, execute ``pytest`` with ``--ignore-glob='*_01.py'``.
|
||||||
|
|
||||||
Deselect tests during test collection
|
Deselect tests during test collection
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
|
|
||||||
|
@ -266,3 +269,17 @@ file will be left out:
|
||||||
collected 0 items
|
collected 0 items
|
||||||
|
|
||||||
======================= no tests ran in 0.12 seconds =======================
|
======================= no tests ran in 0.12 seconds =======================
|
||||||
|
|
||||||
|
It's also possible to ignore files based on Unix shell-style wildcards by adding
|
||||||
|
patterns to ``collect_ignore_glob``.
|
||||||
|
|
||||||
|
The following example ``conftest.py`` ignores the file ``setup.py`` and in
|
||||||
|
addition all files that end with ``*_py2.py`` when executed with a Python 3
|
||||||
|
interpreter::
|
||||||
|
|
||||||
|
# content of conftest.py
|
||||||
|
import sys
|
||||||
|
|
||||||
|
collect_ignore = ["setup.py"]
|
||||||
|
if sys.version_info[0] > 2:
|
||||||
|
collect_ignore_glob = ["*_py2.py"]
|
||||||
|
|
|
@ -198,6 +198,9 @@ option names are:
|
||||||
* ``log_file_format``
|
* ``log_file_format``
|
||||||
* ``log_file_date_format``
|
* ``log_file_date_format``
|
||||||
|
|
||||||
|
You can call ``set_log_path()`` to customize the log_file path dynamically. This functionality
|
||||||
|
is considered **experimental**.
|
||||||
|
|
||||||
.. _log_release_notes:
|
.. _log_release_notes:
|
||||||
|
|
||||||
Release notes
|
Release notes
|
||||||
|
|
|
@ -797,6 +797,33 @@ Special Variables
|
||||||
pytest treats some global variables in a special manner when defined in a test module.
|
pytest treats some global variables in a special manner when defined in a test module.
|
||||||
|
|
||||||
|
|
||||||
|
collect_ignore
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
**Tutorial**: :ref:`customizing-test-collection`
|
||||||
|
|
||||||
|
Can be declared in *conftest.py files* to exclude test directories or modules.
|
||||||
|
Needs to be ``list[str]``.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
collect_ignore = ["setup.py"]
|
||||||
|
|
||||||
|
|
||||||
|
collect_ignore_glob
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
**Tutorial**: :ref:`customizing-test-collection`
|
||||||
|
|
||||||
|
Can be declared in *conftest.py files* to exclude test directories or modules
|
||||||
|
with Unix shell-style wildcards. Needs to be ``list[str]`` where ``str`` can
|
||||||
|
contain glob patterns.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
collect_ignore_glob = ["*_ignore.py"]
|
||||||
|
|
||||||
|
|
||||||
pytest_plugins
|
pytest_plugins
|
||||||
~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -651,8 +651,27 @@ class Config(object):
|
||||||
return self.pluginmanager.get_plugin("terminalreporter")._tw
|
return self.pluginmanager.get_plugin("terminalreporter")._tw
|
||||||
|
|
||||||
def pytest_cmdline_parse(self, pluginmanager, args):
|
def pytest_cmdline_parse(self, pluginmanager, args):
|
||||||
# REF1 assert self == pluginmanager.config, (self, pluginmanager.config)
|
try:
|
||||||
self.parse(args)
|
self.parse(args)
|
||||||
|
except UsageError:
|
||||||
|
|
||||||
|
# Handle --version and --help here in a minimal fashion.
|
||||||
|
# This gets done via helpconfig normally, but its
|
||||||
|
# pytest_cmdline_main is not called in case of errors.
|
||||||
|
if getattr(self.option, "version", False) or "--version" in args:
|
||||||
|
from _pytest.helpconfig import showversion
|
||||||
|
|
||||||
|
showversion(self)
|
||||||
|
elif (
|
||||||
|
getattr(self.option, "help", False) or "--help" in args or "-h" in args
|
||||||
|
):
|
||||||
|
self._parser._getparser().print_help()
|
||||||
|
sys.stdout.write(
|
||||||
|
"\nNOTE: displaying only minimal help due to UsageError.\n\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
raise
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def notify_exception(self, excinfo, option=None):
|
def notify_exception(self, excinfo, option=None):
|
||||||
|
@ -763,21 +782,32 @@ class Config(object):
|
||||||
for name in _iter_rewritable_modules(package_files):
|
for name in _iter_rewritable_modules(package_files):
|
||||||
hook.mark_rewrite(name)
|
hook.mark_rewrite(name)
|
||||||
|
|
||||||
def _validate_args(self, args):
|
def _validate_args(self, args, via):
|
||||||
"""Validate known args."""
|
"""Validate known args."""
|
||||||
self._parser.parse_known_and_unknown_args(
|
self._parser._config_source_hint = via
|
||||||
args, namespace=copy.copy(self.option)
|
try:
|
||||||
)
|
self._parser.parse_known_and_unknown_args(
|
||||||
|
args, namespace=copy.copy(self.option)
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
del self._parser._config_source_hint
|
||||||
|
|
||||||
return args
|
return args
|
||||||
|
|
||||||
def _preparse(self, args, addopts=True):
|
def _preparse(self, args, addopts=True):
|
||||||
if addopts:
|
if addopts:
|
||||||
env_addopts = os.environ.get("PYTEST_ADDOPTS", "")
|
env_addopts = os.environ.get("PYTEST_ADDOPTS", "")
|
||||||
if len(env_addopts):
|
if len(env_addopts):
|
||||||
args[:] = self._validate_args(shlex.split(env_addopts)) + args
|
args[:] = (
|
||||||
|
self._validate_args(shlex.split(env_addopts), "via PYTEST_ADDOPTS")
|
||||||
|
+ args
|
||||||
|
)
|
||||||
self._initini(args)
|
self._initini(args)
|
||||||
if addopts:
|
if addopts:
|
||||||
args[:] = self._validate_args(self.getini("addopts")) + args
|
args[:] = (
|
||||||
|
self._validate_args(self.getini("addopts"), "via addopts config") + args
|
||||||
|
)
|
||||||
|
|
||||||
self._checkversion()
|
self._checkversion()
|
||||||
self._consider_importhook(args)
|
self._consider_importhook(args)
|
||||||
self.pluginmanager.consider_preparse(args)
|
self.pluginmanager.consider_preparse(args)
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
import argparse
|
import argparse
|
||||||
import sys as _sys
|
|
||||||
import warnings
|
import warnings
|
||||||
from gettext import gettext as _
|
|
||||||
|
|
||||||
import py
|
import py
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from ..main import EXIT_USAGEERROR
|
from _pytest.config.exceptions import UsageError
|
||||||
|
|
||||||
FILE_OR_DIR = "file_or_dir"
|
FILE_OR_DIR = "file_or_dir"
|
||||||
|
|
||||||
|
@ -337,14 +335,13 @@ class MyOptionParser(argparse.ArgumentParser):
|
||||||
self.extra_info = extra_info
|
self.extra_info = extra_info
|
||||||
|
|
||||||
def error(self, message):
|
def error(self, message):
|
||||||
"""error(message: string)
|
"""Transform argparse error message into UsageError."""
|
||||||
|
msg = "%s: error: %s" % (self.prog, message)
|
||||||
|
|
||||||
Prints a usage message incorporating the message to stderr and
|
if hasattr(self._parser, "_config_source_hint"):
|
||||||
exits.
|
msg = "%s (%s)" % (msg, self._parser._config_source_hint)
|
||||||
Overrides the method in parent class to change exit code"""
|
|
||||||
self.print_usage(_sys.stderr)
|
raise UsageError(self.format_usage() + msg)
|
||||||
args = {"prog": self.prog, "message": message}
|
|
||||||
self.exit(EXIT_USAGEERROR, _("%(prog)s: error: %(message)s\n") % args)
|
|
||||||
|
|
||||||
def parse_args(self, args=None, namespace=None):
|
def parse_args(self, args=None, namespace=None):
|
||||||
"""allow splitting of positional arguments"""
|
"""allow splitting of positional arguments"""
|
||||||
|
|
|
@ -14,6 +14,7 @@ from __future__ import print_function
|
||||||
|
|
||||||
from _pytest.warning_types import PytestDeprecationWarning
|
from _pytest.warning_types import PytestDeprecationWarning
|
||||||
from _pytest.warning_types import RemovedInPytest4Warning
|
from _pytest.warning_types import RemovedInPytest4Warning
|
||||||
|
from _pytest.warning_types import UnformattedWarning
|
||||||
|
|
||||||
|
|
||||||
YIELD_TESTS = "yield tests were removed in pytest 4.0 - {name} will be ignored"
|
YIELD_TESTS = "yield tests were removed in pytest 4.0 - {name} will be ignored"
|
||||||
|
@ -87,3 +88,9 @@ PYTEST_LOGWARNING = PytestDeprecationWarning(
|
||||||
"pytest_logwarning is deprecated, no longer being called, and will be removed soon\n"
|
"pytest_logwarning is deprecated, no longer being called, and will be removed soon\n"
|
||||||
"please use pytest_warning_captured instead"
|
"please use pytest_warning_captured instead"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
PYTEST_WARNS_UNKNOWN_KWARGS = UnformattedWarning(
|
||||||
|
PytestDeprecationWarning,
|
||||||
|
"pytest.warns() got unexpected keyword arguments: {args!r}.\n"
|
||||||
|
"This will be an error in future versions.",
|
||||||
|
)
|
||||||
|
|
|
@ -118,16 +118,20 @@ def pytest_cmdline_parse():
|
||||||
config.add_cleanup(unset_tracing)
|
config.add_cleanup(unset_tracing)
|
||||||
|
|
||||||
|
|
||||||
|
def showversion(config):
|
||||||
|
p = py.path.local(pytest.__file__)
|
||||||
|
sys.stderr.write(
|
||||||
|
"This is pytest version %s, imported from %s\n" % (pytest.__version__, p)
|
||||||
|
)
|
||||||
|
plugininfo = getpluginversioninfo(config)
|
||||||
|
if plugininfo:
|
||||||
|
for line in plugininfo:
|
||||||
|
sys.stderr.write(line + "\n")
|
||||||
|
|
||||||
|
|
||||||
def pytest_cmdline_main(config):
|
def pytest_cmdline_main(config):
|
||||||
if config.option.version:
|
if config.option.version:
|
||||||
p = py.path.local(pytest.__file__)
|
showversion(config)
|
||||||
sys.stderr.write(
|
|
||||||
"This is pytest version %s, imported from %s\n" % (pytest.__version__, p)
|
|
||||||
)
|
|
||||||
plugininfo = getpluginversioninfo(config)
|
|
||||||
if plugininfo:
|
|
||||||
for line in plugininfo:
|
|
||||||
sys.stderr.write(line + "\n")
|
|
||||||
return 0
|
return 0
|
||||||
elif config.option.help:
|
elif config.option.help:
|
||||||
config._do_configure()
|
config._do_configure()
|
||||||
|
|
|
@ -13,6 +13,7 @@ import six
|
||||||
import pytest
|
import pytest
|
||||||
from _pytest.compat import dummy_context_manager
|
from _pytest.compat import dummy_context_manager
|
||||||
from _pytest.config import create_terminal_writer
|
from _pytest.config import create_terminal_writer
|
||||||
|
from _pytest.pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_LOG_FORMAT = "%(filename)-25s %(lineno)4d %(levelname)-8s %(message)s"
|
DEFAULT_LOG_FORMAT = "%(filename)-25s %(lineno)4d %(levelname)-8s %(message)s"
|
||||||
|
@ -399,22 +400,21 @@ class LoggingPlugin(object):
|
||||||
)
|
)
|
||||||
self.log_level = get_actual_log_level(config, "log_level")
|
self.log_level = get_actual_log_level(config, "log_level")
|
||||||
|
|
||||||
|
self.log_file_level = get_actual_log_level(config, "log_file_level")
|
||||||
|
self.log_file_format = get_option_ini(config, "log_file_format", "log_format")
|
||||||
|
self.log_file_date_format = get_option_ini(
|
||||||
|
config, "log_file_date_format", "log_date_format"
|
||||||
|
)
|
||||||
|
self.log_file_formatter = logging.Formatter(
|
||||||
|
self.log_file_format, datefmt=self.log_file_date_format
|
||||||
|
)
|
||||||
|
|
||||||
log_file = get_option_ini(config, "log_file")
|
log_file = get_option_ini(config, "log_file")
|
||||||
if log_file:
|
if log_file:
|
||||||
self.log_file_level = get_actual_log_level(config, "log_file_level")
|
|
||||||
|
|
||||||
log_file_format = get_option_ini(config, "log_file_format", "log_format")
|
|
||||||
log_file_date_format = get_option_ini(
|
|
||||||
config, "log_file_date_format", "log_date_format"
|
|
||||||
)
|
|
||||||
# Each pytest runtests session will write to a clean logfile
|
|
||||||
self.log_file_handler = logging.FileHandler(
|
self.log_file_handler = logging.FileHandler(
|
||||||
log_file, mode="w", encoding="UTF-8"
|
log_file, mode="w", encoding="UTF-8"
|
||||||
)
|
)
|
||||||
log_file_formatter = logging.Formatter(
|
self.log_file_handler.setFormatter(self.log_file_formatter)
|
||||||
log_file_format, datefmt=log_file_date_format
|
|
||||||
)
|
|
||||||
self.log_file_handler.setFormatter(log_file_formatter)
|
|
||||||
else:
|
else:
|
||||||
self.log_file_handler = None
|
self.log_file_handler = None
|
||||||
|
|
||||||
|
@ -461,6 +461,27 @@ class LoggingPlugin(object):
|
||||||
log_cli_handler, formatter=log_cli_formatter, level=log_cli_level
|
log_cli_handler, formatter=log_cli_formatter, level=log_cli_level
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def set_log_path(self, fname):
|
||||||
|
"""Public method, which can set filename parameter for
|
||||||
|
Logging.FileHandler(). Also creates parent directory if
|
||||||
|
it does not exist.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
Please considered as an experimental API.
|
||||||
|
"""
|
||||||
|
fname = Path(fname)
|
||||||
|
|
||||||
|
if not fname.is_absolute():
|
||||||
|
fname = Path(self._config.rootdir, fname)
|
||||||
|
|
||||||
|
if not fname.parent.exists():
|
||||||
|
fname.parent.mkdir(exist_ok=True, parents=True)
|
||||||
|
|
||||||
|
self.log_file_handler = logging.FileHandler(
|
||||||
|
str(fname), mode="w", encoding="UTF-8"
|
||||||
|
)
|
||||||
|
self.log_file_handler.setFormatter(self.log_file_formatter)
|
||||||
|
|
||||||
def _log_cli_enabled(self):
|
def _log_cli_enabled(self):
|
||||||
"""Return True if log_cli should be considered enabled, either explicitly
|
"""Return True if log_cli should be considered enabled, either explicitly
|
||||||
or because --log-cli-level was given in the command-line.
|
or because --log-cli-level was given in the command-line.
|
||||||
|
@ -483,6 +504,15 @@ class LoggingPlugin(object):
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def _runtest_for(self, item, when):
|
def _runtest_for(self, item, when):
|
||||||
|
with self._runtest_for_main(item, when):
|
||||||
|
if self.log_file_handler is not None:
|
||||||
|
with catching_logs(self.log_file_handler, level=self.log_file_level):
|
||||||
|
yield
|
||||||
|
else:
|
||||||
|
yield
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def _runtest_for_main(self, item, when):
|
||||||
"""Implements the internals of pytest_runtest_xxx() hook."""
|
"""Implements the internals of pytest_runtest_xxx() hook."""
|
||||||
with catching_logs(
|
with catching_logs(
|
||||||
LogCaptureHandler(), formatter=self.formatter, level=self.log_level
|
LogCaptureHandler(), formatter=self.formatter, level=self.log_level
|
||||||
|
|
|
@ -4,6 +4,7 @@ from __future__ import division
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
import contextlib
|
import contextlib
|
||||||
|
import fnmatch
|
||||||
import functools
|
import functools
|
||||||
import os
|
import os
|
||||||
import pkgutil
|
import pkgutil
|
||||||
|
@ -117,6 +118,12 @@ def pytest_addoption(parser):
|
||||||
metavar="path",
|
metavar="path",
|
||||||
help="ignore path during collection (multi-allowed).",
|
help="ignore path during collection (multi-allowed).",
|
||||||
)
|
)
|
||||||
|
group.addoption(
|
||||||
|
"--ignore-glob",
|
||||||
|
action="append",
|
||||||
|
metavar="path",
|
||||||
|
help="ignore path pattern during collection (multi-allowed).",
|
||||||
|
)
|
||||||
group.addoption(
|
group.addoption(
|
||||||
"--deselect",
|
"--deselect",
|
||||||
action="append",
|
action="append",
|
||||||
|
@ -296,6 +303,20 @@ def pytest_ignore_collect(path, config):
|
||||||
if py.path.local(path) in ignore_paths:
|
if py.path.local(path) in ignore_paths:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
ignore_globs = config._getconftest_pathlist(
|
||||||
|
"collect_ignore_glob", path=path.dirpath()
|
||||||
|
)
|
||||||
|
ignore_globs = ignore_globs or []
|
||||||
|
excludeglobopt = config.getoption("ignore_glob")
|
||||||
|
if excludeglobopt:
|
||||||
|
ignore_globs.extend([py.path.local(x) for x in excludeglobopt])
|
||||||
|
|
||||||
|
if any(
|
||||||
|
fnmatch.fnmatch(six.text_type(path), six.text_type(glob))
|
||||||
|
for glob in ignore_globs
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
|
||||||
allow_in_venv = config.getoption("collect_in_virtualenv")
|
allow_in_venv = config.getoption("collect_in_virtualenv")
|
||||||
if not allow_in_venv and _in_venv(path):
|
if not allow_in_venv and _in_venv(path):
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -402,6 +402,12 @@ class RunResult(object):
|
||||||
self.stderr = LineMatcher(errlines)
|
self.stderr = LineMatcher(errlines)
|
||||||
self.duration = duration
|
self.duration = duration
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return (
|
||||||
|
"<RunResult ret=%r len(stdout.lines)=%d len(stderr.lines)=%d duration=%.2fs>"
|
||||||
|
% (self.ret, len(self.stdout.lines), len(self.stderr.lines), self.duration)
|
||||||
|
)
|
||||||
|
|
||||||
def parseoutcomes(self):
|
def parseoutcomes(self):
|
||||||
"""Return a dictionary of outcomestring->num from parsing the terminal
|
"""Return a dictionary of outcomestring->num from parsing the terminal
|
||||||
output that the test process produced.
|
output that the test process produced.
|
||||||
|
|
|
@ -11,6 +11,7 @@ import warnings
|
||||||
import six
|
import six
|
||||||
|
|
||||||
import _pytest._code
|
import _pytest._code
|
||||||
|
from _pytest.deprecated import PYTEST_WARNS_UNKNOWN_KWARGS
|
||||||
from _pytest.deprecated import WARNS_EXEC
|
from _pytest.deprecated import WARNS_EXEC
|
||||||
from _pytest.fixtures import yield_fixture
|
from _pytest.fixtures import yield_fixture
|
||||||
from _pytest.outcomes import fail
|
from _pytest.outcomes import fail
|
||||||
|
@ -84,10 +85,12 @@ def warns(expected_warning, *args, **kwargs):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
__tracebackhide__ = True
|
__tracebackhide__ = True
|
||||||
match_expr = None
|
|
||||||
if not args:
|
if not args:
|
||||||
if "match" in kwargs:
|
match_expr = kwargs.pop("match", None)
|
||||||
match_expr = kwargs.pop("match")
|
if kwargs:
|
||||||
|
warnings.warn(
|
||||||
|
PYTEST_WARNS_UNKNOWN_KWARGS.format(args=sorted(kwargs)), stacklevel=2
|
||||||
|
)
|
||||||
return WarningsChecker(expected_warning, match_expr=match_expr)
|
return WarningsChecker(expected_warning, match_expr=match_expr)
|
||||||
elif isinstance(args[0], str):
|
elif isinstance(args[0], str):
|
||||||
warnings.warn(WARNS_EXEC, stacklevel=2)
|
warnings.warn(WARNS_EXEC, stacklevel=2)
|
||||||
|
@ -97,12 +100,12 @@ def warns(expected_warning, *args, **kwargs):
|
||||||
loc = frame.f_locals.copy()
|
loc = frame.f_locals.copy()
|
||||||
loc.update(kwargs)
|
loc.update(kwargs)
|
||||||
|
|
||||||
with WarningsChecker(expected_warning, match_expr=match_expr):
|
with WarningsChecker(expected_warning):
|
||||||
code = _pytest._code.Source(code).compile()
|
code = _pytest._code.Source(code).compile()
|
||||||
six.exec_(code, frame.f_globals, loc)
|
six.exec_(code, frame.f_globals, loc)
|
||||||
else:
|
else:
|
||||||
func = args[0]
|
func = args[0]
|
||||||
with WarningsChecker(expected_warning, match_expr=match_expr):
|
with WarningsChecker(expected_warning):
|
||||||
return func(*args[1:], **kwargs)
|
return func(*args[1:], **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -674,7 +674,6 @@ class TerminalReporter(object):
|
||||||
self.summary_passes()
|
self.summary_passes()
|
||||||
# Display any extra warnings from teardown here (if any).
|
# Display any extra warnings from teardown here (if any).
|
||||||
self.summary_warnings()
|
self.summary_warnings()
|
||||||
self.summary_deprecated_python()
|
|
||||||
|
|
||||||
def pytest_keyboard_interrupt(self, excinfo):
|
def pytest_keyboard_interrupt(self, excinfo):
|
||||||
self._keyboardinterrupt_memo = excinfo.getrepr(funcargs=True)
|
self._keyboardinterrupt_memo = excinfo.getrepr(funcargs=True)
|
||||||
|
@ -796,20 +795,6 @@ class TerminalReporter(object):
|
||||||
self.write_sep("_", msg)
|
self.write_sep("_", msg)
|
||||||
self._outrep_summary(rep)
|
self._outrep_summary(rep)
|
||||||
|
|
||||||
def summary_deprecated_python(self):
|
|
||||||
if sys.version_info[:2] <= (3, 4) and self.verbosity >= 0:
|
|
||||||
self.write_sep("=", "deprecated python version", yellow=True, bold=False)
|
|
||||||
using_version = ".".join(str(x) for x in sys.version_info[:3])
|
|
||||||
self.line(
|
|
||||||
"You are using Python {}, which will no longer be supported in pytest 5.0".format(
|
|
||||||
using_version
|
|
||||||
),
|
|
||||||
yellow=True,
|
|
||||||
bold=False,
|
|
||||||
)
|
|
||||||
self.line("For more information, please read:")
|
|
||||||
self.line(" https://docs.pytest.org/en/latest/py27-py34-deprecation.html")
|
|
||||||
|
|
||||||
def print_teardown_sections(self, rep):
|
def print_teardown_sections(self, rep):
|
||||||
showcapture = self.config.option.showcapture
|
showcapture = self.config.option.showcapture
|
||||||
if showcapture == "no":
|
if showcapture == "no":
|
||||||
|
|
|
@ -854,9 +854,7 @@ class TestDurations(object):
|
||||||
result = testdir.runpytest("--durations=2")
|
result = testdir.runpytest("--durations=2")
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
lines = result.stdout.get_lines_after("*slowest*durations*")
|
lines = result.stdout.get_lines_after("*slowest*durations*")
|
||||||
# account for the "deprecated python version" header
|
assert "4 passed" in lines[2]
|
||||||
index = 2 if sys.version_info[:2] > (3, 4) else 6
|
|
||||||
assert "4 passed" in lines[index]
|
|
||||||
|
|
||||||
def test_calls_showall(self, testdir):
|
def test_calls_showall(self, testdir):
|
||||||
testdir.makepyfile(self.source)
|
testdir.makepyfile(self.source)
|
||||||
|
|
|
@ -3,9 +3,9 @@ from __future__ import division
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from _pytest.warning_types import PytestDeprecationWarning
|
||||||
from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG
|
from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG
|
||||||
|
|
||||||
pytestmark = pytest.mark.pytester_example_path("deprecated")
|
pytestmark = pytest.mark.pytester_example_path("deprecated")
|
||||||
|
@ -222,19 +222,9 @@ def test_fixture_named_request(testdir):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_python_deprecation(testdir):
|
def test_pytest_warns_unknown_kwargs():
|
||||||
result = testdir.runpytest()
|
with pytest.warns(
|
||||||
python_ver = ".".join(str(x) for x in sys.version_info[:3])
|
PytestDeprecationWarning,
|
||||||
msg = "You are using Python {}, which will no longer be supported in pytest 5.0".format(
|
match=r"pytest.warns\(\) got unexpected keyword arguments: \['foo'\]",
|
||||||
python_ver
|
):
|
||||||
)
|
pytest.warns(UserWarning, foo="hello")
|
||||||
if sys.version_info[:2] <= (3, 4):
|
|
||||||
result.stdout.fnmatch_lines(
|
|
||||||
[
|
|
||||||
msg,
|
|
||||||
"For more information, please read:",
|
|
||||||
" https://docs.pytest.org/en/latest/py27-py34-deprecation.html",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
assert msg not in result.stdout.str()
|
|
||||||
|
|
|
@ -1002,3 +1002,51 @@ def test_log_in_hooks(testdir):
|
||||||
assert "sessionstart" in contents
|
assert "sessionstart" in contents
|
||||||
assert "runtestloop" in contents
|
assert "runtestloop" in contents
|
||||||
assert "sessionfinish" in contents
|
assert "sessionfinish" in contents
|
||||||
|
|
||||||
|
|
||||||
|
def test_log_set_path(testdir):
|
||||||
|
report_dir_base = testdir.tmpdir.strpath
|
||||||
|
|
||||||
|
testdir.makeini(
|
||||||
|
"""
|
||||||
|
[pytest]
|
||||||
|
log_file_level = DEBUG
|
||||||
|
log_cli=true
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
testdir.makeconftest(
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import pytest
|
||||||
|
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
|
||||||
|
def pytest_runtest_setup(item):
|
||||||
|
config = item.config
|
||||||
|
logging_plugin = config.pluginmanager.get_plugin("logging-plugin")
|
||||||
|
report_file = os.path.join({}, item._request.node.name)
|
||||||
|
logging_plugin.set_log_path(report_file)
|
||||||
|
yield
|
||||||
|
""".format(
|
||||||
|
repr(report_dir_base)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
logger = logging.getLogger("testcase-logger")
|
||||||
|
def test_first():
|
||||||
|
logger.info("message from test 1")
|
||||||
|
assert True
|
||||||
|
|
||||||
|
def test_second():
|
||||||
|
logger.debug("message from test 2")
|
||||||
|
assert True
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
testdir.runpytest()
|
||||||
|
with open(os.path.join(report_dir_base, "test_first"), "r") as rfh:
|
||||||
|
content = rfh.read()
|
||||||
|
assert "message from test 1" in content
|
||||||
|
|
||||||
|
with open(os.path.join(report_dir_base, "test_second"), "r") as rfh:
|
||||||
|
content = rfh.read()
|
||||||
|
assert "message from test 2" in content
|
||||||
|
|
|
@ -374,6 +374,26 @@ class TestCustomConftests(object):
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
assert "passed" in result.stdout.str()
|
assert "passed" in result.stdout.str()
|
||||||
|
|
||||||
|
def test_collectignoreglob_exclude_on_option(self, testdir):
|
||||||
|
testdir.makeconftest(
|
||||||
|
"""
|
||||||
|
collect_ignore_glob = ['*w*l[dt]*']
|
||||||
|
def pytest_addoption(parser):
|
||||||
|
parser.addoption("--XX", action="store_true", default=False)
|
||||||
|
def pytest_configure(config):
|
||||||
|
if config.getvalue("XX"):
|
||||||
|
collect_ignore_glob[:] = []
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
testdir.makepyfile(test_world="def test_hello(): pass")
|
||||||
|
testdir.makepyfile(test_welt="def test_hallo(): pass")
|
||||||
|
result = testdir.runpytest()
|
||||||
|
assert result.ret == EXIT_NOTESTSCOLLECTED
|
||||||
|
result.stdout.fnmatch_lines("*collected 0 items*")
|
||||||
|
result = testdir.runpytest("--XX")
|
||||||
|
assert result.ret == 0
|
||||||
|
result.stdout.fnmatch_lines("*2 passed*")
|
||||||
|
|
||||||
def test_pytest_fs_collect_hooks_are_seen(self, testdir):
|
def test_pytest_fs_collect_hooks_are_seen(self, testdir):
|
||||||
testdir.makeconftest(
|
testdir.makeconftest(
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -8,10 +8,12 @@ import textwrap
|
||||||
import _pytest._code
|
import _pytest._code
|
||||||
import pytest
|
import pytest
|
||||||
from _pytest.config import _iter_rewritable_modules
|
from _pytest.config import _iter_rewritable_modules
|
||||||
|
from _pytest.config.exceptions import UsageError
|
||||||
from _pytest.config.findpaths import determine_setup
|
from _pytest.config.findpaths import determine_setup
|
||||||
from _pytest.config.findpaths import get_common_ancestor
|
from _pytest.config.findpaths import get_common_ancestor
|
||||||
from _pytest.config.findpaths import getcfg
|
from _pytest.config.findpaths import getcfg
|
||||||
from _pytest.main import EXIT_NOTESTSCOLLECTED
|
from _pytest.main import EXIT_NOTESTSCOLLECTED
|
||||||
|
from _pytest.main import EXIT_USAGEERROR
|
||||||
|
|
||||||
|
|
||||||
class TestParseIni(object):
|
class TestParseIni(object):
|
||||||
|
@ -1031,9 +1033,12 @@ class TestOverrideIniArgs(object):
|
||||||
|
|
||||||
monkeypatch.setenv("PYTEST_ADDOPTS", "-o")
|
monkeypatch.setenv("PYTEST_ADDOPTS", "-o")
|
||||||
config = get_config()
|
config = get_config()
|
||||||
with pytest.raises(SystemExit) as excinfo:
|
with pytest.raises(UsageError) as excinfo:
|
||||||
config._preparse(["cache_dir=ignored"], addopts=True)
|
config._preparse(["cache_dir=ignored"], addopts=True)
|
||||||
assert excinfo.value.args[0] == _pytest.main.EXIT_USAGEERROR
|
assert (
|
||||||
|
"error: argument -o/--override-ini: expected one argument (via PYTEST_ADDOPTS)"
|
||||||
|
in excinfo.value.args[0]
|
||||||
|
)
|
||||||
|
|
||||||
def test_addopts_from_ini_not_concatenated(self, testdir):
|
def test_addopts_from_ini_not_concatenated(self, testdir):
|
||||||
"""addopts from ini should not take values from normal args (#4265)."""
|
"""addopts from ini should not take values from normal args (#4265)."""
|
||||||
|
@ -1046,7 +1051,7 @@ class TestOverrideIniArgs(object):
|
||||||
result = testdir.runpytest("cache_dir=ignored")
|
result = testdir.runpytest("cache_dir=ignored")
|
||||||
result.stderr.fnmatch_lines(
|
result.stderr.fnmatch_lines(
|
||||||
[
|
[
|
||||||
"%s: error: argument -o/--override-ini: expected one argument"
|
"%s: error: argument -o/--override-ini: expected one argument (via addopts config)"
|
||||||
% (testdir.request.config._parser.optparser.prog,)
|
% (testdir.request.config._parser.optparser.prog,)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -1083,3 +1088,68 @@ class TestOverrideIniArgs(object):
|
||||||
result = testdir.runpytest("-o", "foo=1", "-o", "bar=0", "test_foo.py")
|
result = testdir.runpytest("-o", "foo=1", "-o", "bar=0", "test_foo.py")
|
||||||
assert "ERROR:" not in result.stderr.str()
|
assert "ERROR:" not in result.stderr.str()
|
||||||
result.stdout.fnmatch_lines(["collected 1 item", "*= 1 passed in *="])
|
result.stdout.fnmatch_lines(["collected 1 item", "*= 1 passed in *="])
|
||||||
|
|
||||||
|
|
||||||
|
def test_help_via_addopts(testdir):
|
||||||
|
testdir.makeini(
|
||||||
|
"""
|
||||||
|
[pytest]
|
||||||
|
addopts = --unknown-option-should-allow-for-help --help
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = testdir.runpytest()
|
||||||
|
assert result.ret == 0
|
||||||
|
result.stdout.fnmatch_lines(
|
||||||
|
[
|
||||||
|
"usage: *",
|
||||||
|
"positional arguments:",
|
||||||
|
# Displays full/default help.
|
||||||
|
"to see available markers type: pytest --markers",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_help_and_version_after_argument_error(testdir):
|
||||||
|
testdir.makeconftest(
|
||||||
|
"""
|
||||||
|
def validate(arg):
|
||||||
|
raise argparse.ArgumentTypeError("argerror")
|
||||||
|
|
||||||
|
def pytest_addoption(parser):
|
||||||
|
group = parser.getgroup('cov')
|
||||||
|
group.addoption(
|
||||||
|
"--invalid-option-should-allow-for-help",
|
||||||
|
type=validate,
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
testdir.makeini(
|
||||||
|
"""
|
||||||
|
[pytest]
|
||||||
|
addopts = --invalid-option-should-allow-for-help
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = testdir.runpytest("--help")
|
||||||
|
result.stdout.fnmatch_lines(
|
||||||
|
[
|
||||||
|
"usage: *",
|
||||||
|
"positional arguments:",
|
||||||
|
"NOTE: displaying only minimal help due to UsageError.",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
result.stderr.fnmatch_lines(
|
||||||
|
[
|
||||||
|
"ERROR: usage: *",
|
||||||
|
"%s: error: argument --invalid-option-should-allow-for-help: expected one argument"
|
||||||
|
% (testdir.request.config._parser.optparser.prog,),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
# Does not display full/default help.
|
||||||
|
assert "to see available markers type: pytest --markers" not in result.stdout.lines
|
||||||
|
assert result.ret == EXIT_USAGEERROR
|
||||||
|
|
||||||
|
result = testdir.runpytest("--version")
|
||||||
|
result.stderr.fnmatch_lines(
|
||||||
|
["*pytest*{}*imported from*".format(pytest.__version__)]
|
||||||
|
)
|
||||||
|
assert result.ret == EXIT_USAGEERROR
|
||||||
|
|
|
@ -11,6 +11,7 @@ import py
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from _pytest.config import argparsing as parseopt
|
from _pytest.config import argparsing as parseopt
|
||||||
|
from _pytest.config.exceptions import UsageError
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
@ -19,11 +20,9 @@ def parser():
|
||||||
|
|
||||||
|
|
||||||
class TestParser(object):
|
class TestParser(object):
|
||||||
def test_no_help_by_default(self, capsys):
|
def test_no_help_by_default(self):
|
||||||
parser = parseopt.Parser(usage="xyz")
|
parser = parseopt.Parser(usage="xyz")
|
||||||
pytest.raises(SystemExit, lambda: parser.parse(["-h"]))
|
pytest.raises(UsageError, lambda: parser.parse(["-h"]))
|
||||||
out, err = capsys.readouterr()
|
|
||||||
assert err.find("error: unrecognized arguments") != -1
|
|
||||||
|
|
||||||
def test_custom_prog(self, parser):
|
def test_custom_prog(self, parser):
|
||||||
"""Custom prog can be set for `argparse.ArgumentParser`."""
|
"""Custom prog can be set for `argparse.ArgumentParser`."""
|
||||||
|
|
|
@ -127,6 +127,17 @@ def test_runresult_assertion_on_xpassed(testdir):
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_runresult_repr():
|
||||||
|
from _pytest.pytester import RunResult
|
||||||
|
|
||||||
|
assert (
|
||||||
|
repr(
|
||||||
|
RunResult(ret="ret", outlines=[""], errlines=["some", "errors"], duration=1)
|
||||||
|
)
|
||||||
|
== "<RunResult ret='ret' len(stdout.lines)=1 len(stderr.lines)=2 duration=1.00s>"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_xpassed_with_strict_is_considered_a_failure(testdir):
|
def test_xpassed_with_strict_is_considered_a_failure(testdir):
|
||||||
testdir.makepyfile(
|
testdir.makepyfile(
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -253,6 +253,21 @@ def test_exclude(testdir):
|
||||||
result.stdout.fnmatch_lines(["*1 passed*"])
|
result.stdout.fnmatch_lines(["*1 passed*"])
|
||||||
|
|
||||||
|
|
||||||
|
def test_exclude_glob(testdir):
|
||||||
|
hellodir = testdir.mkdir("hello")
|
||||||
|
hellodir.join("test_hello.py").write("x y syntaxerror")
|
||||||
|
hello2dir = testdir.mkdir("hello2")
|
||||||
|
hello2dir.join("test_hello2.py").write("x y syntaxerror")
|
||||||
|
hello3dir = testdir.mkdir("hallo3")
|
||||||
|
hello3dir.join("test_hello3.py").write("x y syntaxerror")
|
||||||
|
subdir = testdir.mkdir("sub")
|
||||||
|
subdir.join("test_hello4.py").write("x y syntaxerror")
|
||||||
|
testdir.makepyfile(test_ok="def test_pass(): pass")
|
||||||
|
result = testdir.runpytest("--ignore-glob=*h[ea]llo*")
|
||||||
|
assert result.ret == 0
|
||||||
|
result.stdout.fnmatch_lines(["*1 passed*"])
|
||||||
|
|
||||||
|
|
||||||
def test_deselect(testdir):
|
def test_deselect(testdir):
|
||||||
testdir.makepyfile(
|
testdir.makepyfile(
|
||||||
test_a="""
|
test_a="""
|
||||||
|
|
Loading…
Reference in New Issue