Skip to content

Merge master into features #4749

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 43 commits into from
Feb 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
afe9fd5
Adds `does_not_raise` context manager
arel Jan 24, 2019
c166b80
Documenting raises/does_not_raise + parametrize
arel Jan 24, 2019
c1fe072
Adding changelog entries for `does_not_raise`
arel Jan 24, 2019
977adf1
Improving sphinx docs based on feedback
arel Jan 25, 2019
fd4289d
Adding `does_not_raise` to documentation only
arel Jan 27, 2019
8a1afe4
Including note on using nullcontext in Python 3.7+
arel Jan 28, 2019
c3d7340
Fix setUpClass being called in subclasses that were skipped
nicoddemus Jan 31, 2019
c2c9b27
Update changelog to reflect spelling change of xfail in teststatus re…
hackebrot Feb 1, 2019
e53563e
Merge pull request #4706 from hackebrot/add-xfail-note-to-4.2.0-chang…
nicoddemus Feb 1, 2019
7dbe400
Merge pull request #4703 from nicoddemus/setup-class-inheritance-4700
nicoddemus Feb 1, 2019
7ec1a14
Incorporating feedback from asottile
arel Feb 2, 2019
f0ecb25
Document custom failure messages for missing warnings
namurphy Feb 2, 2019
8003d8d
Update AUTHORS
namurphy Feb 2, 2019
4e93dc2
Update changelog for pytest.warns doc update
namurphy Feb 2, 2019
2264db7
Merge pull request #4682 from arel/parameterize-conditional-raises-do…
asottile Feb 2, 2019
726e165
Fix typo in CHANGELOG
nicoddemus Feb 4, 2019
a945734
Merge pull request #4715 from nicoddemus/fix-changelog-typo
RonnyPfannschmidt Feb 4, 2019
3153740
Remove workaround for docstrings for py38+
asottile Feb 5, 2019
584c052
Fix linting and change False to True as requested in review
nicoddemus Feb 5, 2019
c780d1f
Merge pull request #4723 from asottile/docstring_fix_py38
asottile Feb 5, 2019
0ce8b91
Only call _setup_cli_logging in __init__
twmr Feb 4, 2019
19c93d1
Do not raise UsageError when "pytest_plugins" is a module
nicoddemus Feb 6, 2019
678dfaa
Merge pull request #4728 from nicoddemus/usage-error-module
nicoddemus Feb 6, 2019
54af0f4
Call pytest_report_collectionfinish hook when --collect-only is passed
nicoddemus Feb 5, 2019
0c5e717
Add py38-dev job to Travis
nicoddemus Feb 6, 2019
52d4975
Merge pull request #4731 from nicoddemus/travis-py38
nicoddemus Feb 6, 2019
429485e
Merge pull request #4720 from thisch/removesetupclilogging
RonnyPfannschmidt Feb 6, 2019
7445b53
Mention that `pytest_plugins` should not be used as module name
kohr-h Feb 6, 2019
3384ffc
Merge pull request #4725 from nicoddemus/collection-finish
Zac-HD Feb 6, 2019
2f08350
Merge pull request #4709 from namurphy/warns-docs
nicoddemus Feb 6, 2019
526f4a9
Merge pull request #4735 from kohr-h/pytest_plugins_module_name
RonnyPfannschmidt Feb 7, 2019
7b8fd0c
Refactor _setup_cli_logging code
twmr Feb 6, 2019
a1fcd6e
Merge pull request #4734 from thisch/refactor_clilogging
twmr Feb 7, 2019
4c7ddb8
Fix `parametrize(... ids=<function>)` when the function returns non-s…
asottile Feb 7, 2019
ea73246
Merge pull request #4740 from asottile/bugfix_4739
asottile Feb 8, 2019
913a2da
Fix handling of collect_ignore from parent conftest
blueyed Feb 8, 2019
9be069f
Use isolated_build option in tox.ini
nicoddemus Feb 8, 2019
64e8185
Merge master into features
blueyed Feb 8, 2019
5ca8159
Merge pull request #4744 from blueyed/fix-4592-collectfile
blueyed Feb 8, 2019
e191a65
tox: py37-freeze: use --no-use-pep517 for PyInstaller
blueyed Feb 8, 2019
8e220f0
Merge pull request #4746 from nicoddemus/isolated-build
nicoddemus Feb 8, 2019
8b92d10
Merge pull request #4751 from blueyed/fix-py-freeze
blueyed Feb 8, 2019
9c03196
Merge master into features
blueyed Feb 8, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ env:
- TOXENV=py37-pluggymaster PYTEST_NO_COVERAGE=1
- 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.
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Anthony Shaw
Anthony Sottile
Anton Lodder
Antony Lee
Arel Cordero
Armin Rigo
Aron Coyle
Aron Curzon
Expand Down Expand Up @@ -173,6 +174,7 @@ Nathaniel Waisbrot
Ned Batchelder
Neven Mundar
Nicholas Devenish
Nicholas Murphy
Niclas Olofsson
Nicolas Delaby
Oleg Pidsadnyi
Expand Down
5 changes: 4 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ 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
Expand Down Expand Up @@ -96,6 +96,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)
=========================

Expand Down
1 change: 1 addition & 0 deletions changelog/2895.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The ``pytest_report_collectionfinish`` hook now is also called with ``--collect-only``.
1 change: 1 addition & 0 deletions changelog/3899.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Do not raise ``UsageError`` when an imported package has a ``pytest_plugins.py`` child module.
1 change: 1 addition & 0 deletions changelog/3899.doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add note to ``plugins.rst`` that ``pytest_plugins`` should not be used as a name for a user module containing plugins.
1 change: 1 addition & 0 deletions changelog/4324.doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Document how to use ``raises`` and ``does_not_raise`` to write parametrized tests with conditional raises.
1 change: 1 addition & 0 deletions changelog/4592.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix handling of ``collect_ignore`` via parent ``conftest.py``.
2 changes: 2 additions & 0 deletions changelog/4700.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
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.
2 changes: 2 additions & 0 deletions changelog/4709.doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Document how to customize test failure messages when using
``pytest.warns``.
1 change: 1 addition & 0 deletions changelog/4739.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix ``parametrize(... ids=<function>)`` when the function returns non-strings.
47 changes: 47 additions & 0 deletions doc/en/example/parametrize.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
5 changes: 5 additions & 0 deletions doc/en/plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 22 additions & 2 deletions doc/en/warnings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ You can also use it as a contextmanager::
.. _warns:

Asserting warnings with the warns function
-----------------------------------------------
------------------------------------------

.. versionadded:: 2.8

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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:

Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ requires = [
"setuptools-scm",
"wheel",
]
build-backend = "setuptools.build_meta"

[tool.towncrier]
package = "pytest"
Expand Down
4 changes: 2 additions & 2 deletions src/_pytest/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -559,8 +559,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(
Expand Down
97 changes: 50 additions & 47 deletions src/_pytest/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand All @@ -388,8 +390,6 @@ def __init__(self, config):

# 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")
Expand Down Expand Up @@ -420,6 +420,54 @@ def __init__(self, config):

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

# FIXME don't set verbosity level and derived attributes of
# terminalwriter directly
terminal_reporter.verbosity = config.option.verbose
terminal_reporter.showheader = terminal_reporter.verbosity >= 0
terminal_reporter.showfspath = terminal_reporter.verbosity >= 0
terminal_reporter.showlongtestinfo = terminal_reporter.verbosity > 0

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.
Expand All @@ -430,10 +478,6 @@ def _log_cli_enabled(self):

@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")
Expand Down Expand Up @@ -513,7 +557,6 @@ def pytest_sessionfinish(self):

@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")
Expand All @@ -533,46 +576,6 @@ def pytest_runtestloop(self, session):
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):
"""
Expand Down
1 change: 1 addition & 0 deletions src/_pytest/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,7 @@ def filter_(f):
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):
Expand Down
22 changes: 11 additions & 11 deletions src/_pytest/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,7 @@ def gethookproxy(self, fspath):
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):
Expand Down Expand Up @@ -642,11 +643,12 @@ def collect(self):
):
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):
Expand Down Expand Up @@ -1144,20 +1146,18 @@ 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"
msg = msg.format(item.nodeid, argname, idx)
# 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
)
Expand Down
Loading