Skip to content

Commit a131cd6

Browse files
authored
Merge pull request #4749 from blueyed/merge-master-into-features
Merge master into features
2 parents 4cd268d + 9c03196 commit a131cd6

33 files changed

+348
-109
lines changed

.travis.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ env:
2323
- TOXENV=py37-pluggymaster PYTEST_NO_COVERAGE=1
2424
- TOXENV=py37-freeze PYTEST_NO_COVERAGE=1
2525

26+
matrix:
27+
allow_failures:
28+
- python: '3.8-dev'
29+
env: TOXENV=py38
30+
2631
jobs:
2732
include:
2833
# Coverage tracking is slow with pypy, skip it.
@@ -35,6 +40,8 @@ jobs:
3540
python: '3.5'
3641
- env: TOXENV=py36
3742
python: '3.6'
43+
- env: TOXENV=py38
44+
python: '3.8-dev'
3845
- env: TOXENV=py37
3946
- &test-macos
4047
language: generic

AUTHORS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Anthony Shaw
2727
Anthony Sottile
2828
Anton Lodder
2929
Antony Lee
30+
Arel Cordero
3031
Armin Rigo
3132
Aron Coyle
3233
Aron Curzon
@@ -173,6 +174,7 @@ Nathaniel Waisbrot
173174
Ned Batchelder
174175
Neven Mundar
175176
Nicholas Devenish
177+
Nicholas Murphy
176178
Niclas Olofsson
177179
Nicolas Delaby
178180
Oleg Pidsadnyi

CHANGELOG.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ pytest 4.2.0 (2019-01-30)
2424
Features
2525
--------
2626

27-
- `#3094 <https://github.com/pytest-dev/pytest/issues/3094>`_: `Class xunit-style <https://docs.pytest.org/en/latest/xunit_setup.html>`__ functions and methods
27+
- `#3094 <https://github.com/pytest-dev/pytest/issues/3094>`_: `Classic xunit-style <https://docs.pytest.org/en/latest/xunit_setup.html>`__ functions and methods
2828
now obey the scope of *autouse* fixtures.
2929

3030
This fixes a number of surprising issues like ``setup_method`` being called before session-scoped
@@ -96,6 +96,9 @@ Trivial/Internal Changes
9696
- `#4657 <https://github.com/pytest-dev/pytest/issues/4657>`_: Copy saferepr from pylib
9797

9898

99+
- `#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.
100+
101+
99102
pytest 4.1.1 (2019-01-12)
100103
=========================
101104

changelog/2895.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
The ``pytest_report_collectionfinish`` hook now is also called with ``--collect-only``.

changelog/3899.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Do not raise ``UsageError`` when an imported package has a ``pytest_plugins.py`` child module.

changelog/3899.doc.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add note to ``plugins.rst`` that ``pytest_plugins`` should not be used as a name for a user module containing plugins.

changelog/4324.doc.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Document how to use ``raises`` and ``does_not_raise`` to write parametrized tests with conditional raises.

changelog/4592.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix handling of ``collect_ignore`` via parent ``conftest.py``.

changelog/4700.bugfix.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix regression where ``setUpClass`` would always be called in subclasses even if all tests
2+
were skipped by a ``unittest.skip()`` decorator applied in the subclass.

changelog/4709.doc.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Document how to customize test failure messages when using
2+
``pytest.warns``.

changelog/4739.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix ``parametrize(... ids=<function>)`` when the function returns non-strings.

doc/en/example/parametrize.rst

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,3 +565,50 @@ As the result:
565565
- The test ``test_eval[1+7-8]`` passed, but the name is autogenerated and confusing.
566566
- The test ``test_eval[basic_2+4]`` passed.
567567
- The test ``test_eval[basic_6*9]`` was expected to fail and did fail.
568+
569+
.. _`parametrizing_conditional_raising`:
570+
571+
Parametrizing conditional raising
572+
--------------------------------------------------------------------
573+
574+
Use :func:`pytest.raises` with the
575+
:ref:`pytest.mark.parametrize ref` decorator to write parametrized tests
576+
in which some tests raise exceptions and others do not.
577+
578+
It is helpful to define a no-op context manager ``does_not_raise`` to serve
579+
as a complement to ``raises``. For example::
580+
581+
from contextlib import contextmanager
582+
import pytest
583+
584+
@contextmanager
585+
def does_not_raise():
586+
yield
587+
588+
589+
@pytest.mark.parametrize('example_input,expectation', [
590+
(3, does_not_raise()),
591+
(2, does_not_raise()),
592+
(1, does_not_raise()),
593+
(0, pytest.raises(ZeroDivisionError)),
594+
])
595+
def test_division(example_input, expectation):
596+
"""Test how much I know division."""
597+
with expectation:
598+
assert (6 / example_input) is not None
599+
600+
In the example above, the first three test cases should run unexceptionally,
601+
while the fourth should raise ``ZeroDivisionError``.
602+
603+
If you're only supporting Python 3.7+, you can simply use ``nullcontext``
604+
to define ``does_not_raise``::
605+
606+
from contextlib import nullcontext as does_not_raise
607+
608+
Or, if you're supporting Python 3.3+ you can use::
609+
610+
from contextlib import ExitStack as does_not_raise
611+
612+
Or, if desired, you can ``pip install contextlib2`` and use::
613+
614+
from contextlib2 import ExitStack as does_not_raise

doc/en/plugins.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@ will be loaded as well.
8484
:ref:`full explanation <requiring plugins in non-root conftests>`
8585
in the Writing plugins section.
8686

87+
.. note::
88+
The name ``pytest_plugins`` is reserved and should not be used as a
89+
name for a custom plugin module.
90+
91+
8792
.. _`findpluginname`:
8893

8994
Finding out which plugins are active

doc/en/warnings.rst

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ You can also use it as a contextmanager::
233233
.. _warns:
234234

235235
Asserting warnings with the warns function
236-
-----------------------------------------------
236+
------------------------------------------
237237

238238
.. versionadded:: 2.8
239239

@@ -291,7 +291,7 @@ Alternatively, you can examine raised warnings in detail using the
291291
.. _recwarn:
292292

293293
Recording warnings
294-
------------------------
294+
------------------
295295

296296
You can record raised warnings either using ``pytest.warns`` or with
297297
the ``recwarn`` fixture.
@@ -329,6 +329,26 @@ warnings, or index into it to get a particular recorded warning.
329329

330330
Full API: :class:`WarningsRecorder`.
331331

332+
.. _custom_failure_messages:
333+
334+
Custom failure messages
335+
-----------------------
336+
337+
Recording warnings provides an opportunity to produce custom test
338+
failure messages for when no warnings are issued or other conditions
339+
are met.
340+
341+
.. code-block:: python
342+
343+
def test():
344+
with pytest.warns(Warning) as record:
345+
f()
346+
if not record:
347+
pytest.fail("Expected a warning!")
348+
349+
If no warnings are issued when calling ``f``, then ``not record`` will
350+
evaluate to ``True``. You can then call ``pytest.fail`` with a
351+
custom error message.
332352

333353
.. _internal-warnings:
334354

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ requires = [
55
"setuptools-scm",
66
"wheel",
77
]
8+
build-backend = "setuptools.build_meta"
89

910
[tool.towncrier]
1011
package = "pytest"

src/_pytest/config/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -559,8 +559,8 @@ def _get_plugin_specs_as_list(specs):
559559
which case it is returned as a list. Specs can also be `None` in which case an
560560
empty list is returned.
561561
"""
562-
if specs is not None:
563-
if isinstance(specs, str):
562+
if specs is not None and not isinstance(specs, types.ModuleType):
563+
if isinstance(specs, six.string_types):
564564
specs = specs.split(",") if specs else []
565565
if not isinstance(specs, (list, tuple)):
566566
raise UsageError(

src/_pytest/logging.py

Lines changed: 50 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,8 @@ def get_actual_log_level(config, *setting_names):
370370
)
371371

372372

373+
# run after terminalreporter/capturemanager are configured
374+
@pytest.hookimpl(trylast=True)
373375
def pytest_configure(config):
374376
config.pluginmanager.register(LoggingPlugin(config), "logging-plugin")
375377

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

389391
# enable verbose output automatically if live logging is enabled
390392
if self._log_cli_enabled() and not config.getoption("verbose"):
391-
# sanity check: terminal reporter should not have been loaded at this point
392-
assert self._config.pluginmanager.get_plugin("terminalreporter") is None
393393
config.option.verbose = 1
394394

395395
self.print_logs = get_option_ini(config, "log_print")
@@ -420,6 +420,54 @@ def __init__(self, config):
420420

421421
self.log_cli_handler = None
422422

423+
self.live_logs_context = lambda: dummy_context_manager()
424+
# Note that the lambda for the live_logs_context is needed because
425+
# live_logs_context can otherwise not be entered multiple times due
426+
# to limitations of contextlib.contextmanager.
427+
428+
if self._log_cli_enabled():
429+
self._setup_cli_logging()
430+
431+
def _setup_cli_logging(self):
432+
config = self._config
433+
terminal_reporter = config.pluginmanager.get_plugin("terminalreporter")
434+
if terminal_reporter is None:
435+
# terminal reporter is disabled e.g. by pytest-xdist.
436+
return
437+
438+
# FIXME don't set verbosity level and derived attributes of
439+
# terminalwriter directly
440+
terminal_reporter.verbosity = config.option.verbose
441+
terminal_reporter.showheader = terminal_reporter.verbosity >= 0
442+
terminal_reporter.showfspath = terminal_reporter.verbosity >= 0
443+
terminal_reporter.showlongtestinfo = terminal_reporter.verbosity > 0
444+
445+
capture_manager = config.pluginmanager.get_plugin("capturemanager")
446+
# if capturemanager plugin is disabled, live logging still works.
447+
log_cli_handler = _LiveLoggingStreamHandler(terminal_reporter, capture_manager)
448+
log_cli_format = get_option_ini(config, "log_cli_format", "log_format")
449+
log_cli_date_format = get_option_ini(
450+
config, "log_cli_date_format", "log_date_format"
451+
)
452+
if (
453+
config.option.color != "no"
454+
and ColoredLevelFormatter.LEVELNAME_FMT_REGEX.search(log_cli_format)
455+
):
456+
log_cli_formatter = ColoredLevelFormatter(
457+
create_terminal_writer(config),
458+
log_cli_format,
459+
datefmt=log_cli_date_format,
460+
)
461+
else:
462+
log_cli_formatter = logging.Formatter(
463+
log_cli_format, datefmt=log_cli_date_format
464+
)
465+
log_cli_level = get_actual_log_level(config, "log_cli_level", "log_level")
466+
self.log_cli_handler = log_cli_handler
467+
self.live_logs_context = lambda: catching_logs(
468+
log_cli_handler, formatter=log_cli_formatter, level=log_cli_level
469+
)
470+
423471
def _log_cli_enabled(self):
424472
"""Return True if log_cli should be considered enabled, either explicitly
425473
or because --log-cli-level was given in the command-line.
@@ -430,10 +478,6 @@ def _log_cli_enabled(self):
430478

431479
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
432480
def pytest_collection(self):
433-
# This has to be called before the first log message is logged,
434-
# so we can access the terminal reporter plugin.
435-
self._setup_cli_logging()
436-
437481
with self.live_logs_context():
438482
if self.log_cli_handler:
439483
self.log_cli_handler.set_when("collection")
@@ -513,7 +557,6 @@ def pytest_sessionfinish(self):
513557

514558
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
515559
def pytest_sessionstart(self):
516-
self._setup_cli_logging()
517560
with self.live_logs_context():
518561
if self.log_cli_handler:
519562
self.log_cli_handler.set_when("sessionstart")
@@ -533,46 +576,6 @@ def pytest_runtestloop(self, session):
533576
else:
534577
yield # run all the tests
535578

536-
def _setup_cli_logging(self):
537-
"""Sets up the handler and logger for the Live Logs feature, if enabled."""
538-
terminal_reporter = self._config.pluginmanager.get_plugin("terminalreporter")
539-
if self._log_cli_enabled() and terminal_reporter is not None:
540-
capture_manager = self._config.pluginmanager.get_plugin("capturemanager")
541-
log_cli_handler = _LiveLoggingStreamHandler(
542-
terminal_reporter, capture_manager
543-
)
544-
log_cli_format = get_option_ini(
545-
self._config, "log_cli_format", "log_format"
546-
)
547-
log_cli_date_format = get_option_ini(
548-
self._config, "log_cli_date_format", "log_date_format"
549-
)
550-
if (
551-
self._config.option.color != "no"
552-
and ColoredLevelFormatter.LEVELNAME_FMT_REGEX.search(log_cli_format)
553-
):
554-
log_cli_formatter = ColoredLevelFormatter(
555-
create_terminal_writer(self._config),
556-
log_cli_format,
557-
datefmt=log_cli_date_format,
558-
)
559-
else:
560-
log_cli_formatter = logging.Formatter(
561-
log_cli_format, datefmt=log_cli_date_format
562-
)
563-
log_cli_level = get_actual_log_level(
564-
self._config, "log_cli_level", "log_level"
565-
)
566-
self.log_cli_handler = log_cli_handler
567-
self.live_logs_context = lambda: catching_logs(
568-
log_cli_handler, formatter=log_cli_formatter, level=log_cli_level
569-
)
570-
else:
571-
self.live_logs_context = lambda: dummy_context_manager()
572-
# Note that the lambda for the live_logs_context is needed because
573-
# live_logs_context can otherwise not be entered multiple times due
574-
# to limitations of contextlib.contextmanager
575-
576579

577580
class _LiveLoggingStreamHandler(logging.StreamHandler):
578581
"""

src/_pytest/main.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,7 @@ def filter_(f):
607607
yield y
608608

609609
def _collectfile(self, path, handle_dupes=True):
610+
assert path.isfile()
610611
ihook = self.gethookproxy(path)
611612
if not self.isinitpath(path):
612613
if ihook.pytest_ignore_collect(path=path, config=self.config):

src/_pytest/python.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,7 @@ def gethookproxy(self, fspath):
599599
return proxy
600600

601601
def _collectfile(self, path, handle_dupes=True):
602+
assert path.isfile()
602603
ihook = self.gethookproxy(path)
603604
if not self.isinitpath(path):
604605
if ihook.pytest_ignore_collect(path=path, config=self.config):
@@ -642,11 +643,12 @@ def collect(self):
642643
):
643644
continue
644645

645-
if path.isdir() and path.join("__init__.py").check(file=1):
646-
pkg_prefixes.add(path)
647-
648-
for x in self._collectfile(path):
649-
yield x
646+
if path.isdir():
647+
if path.join("__init__.py").check(file=1):
648+
pkg_prefixes.add(path)
649+
else:
650+
for x in self._collectfile(path):
651+
yield x
650652

651653

652654
def _get_xunit_setup_teardown(holder, attr_name, param_obj=None):
@@ -1144,20 +1146,18 @@ def _find_parametrized_scope(argnames, arg2fixturedefs, indirect):
11441146

11451147
def _idval(val, argname, idx, idfn, item, config):
11461148
if idfn:
1147-
s = None
11481149
try:
1149-
s = idfn(val)
1150+
generated_id = idfn(val)
1151+
if generated_id is not None:
1152+
val = generated_id
11501153
except Exception as e:
11511154
# See issue https://github.com/pytest-dev/pytest/issues/2169
11521155
msg = "{}: error raised while trying to determine id of parameter '{}' at position {}\n"
11531156
msg = msg.format(item.nodeid, argname, idx)
11541157
# we only append the exception type and message because on Python 2 reraise does nothing
11551158
msg += " {}: {}\n".format(type(e).__name__, e)
11561159
six.raise_from(ValueError(msg), e)
1157-
if s:
1158-
return ascii_escaped(s)
1159-
1160-
if config:
1160+
elif config:
11611161
hook_id = config.hook.pytest_make_parametrize_id(
11621162
config=config, val=val, argname=argname
11631163
)

0 commit comments

Comments
 (0)