diff --git a/.github/config.yml b/.github/config.yml new file mode 100644 index 000000000..86a8a97e7 --- /dev/null +++ b/.github/config.yml @@ -0,0 +1,2 @@ +rtd: + project: pytest diff --git a/AUTHORS b/AUTHORS index fd98f8141..06947d17b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -24,6 +24,7 @@ Andy Freeland Anthon van der Neut Anthony Shaw Anthony Sottile +Anton Lodder Antony Lee Armin Rigo Aron Coyle @@ -176,6 +177,7 @@ Oliver Bestwalter Omar Kohl Omer Hadari Ondřej Súkup +Oscar Benjamin Patrick Hayes Paweł Adamczak Pedro Algarvio diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e7784b931..02ea6e84f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -18,6 +18,38 @@ with advance notice in the **Deprecations** section of releases. .. towncrier release notes start +pytest 4.1.1 (2019-01-12) +========================= + +Bug Fixes +--------- + +- `#2256 `_: Show full repr with ``assert a==b`` and ``-vv``. + + +- `#3456 `_: Extend Doctest-modules to ignore mock objects. + + +- `#4617 `_: Fixed ``pytest.warns`` bug when context manager is reused (e.g. multiple parametrization). + + +- `#4631 `_: Don't rewrite assertion when ``__getattr__`` is broken + + + +Improved Documentation +---------------------- + +- `#3375 `_: Document that using ``setup.cfg`` may crash other tools or cause hard to track down problems because it uses a different parser than ``pytest.ini`` or ``tox.ini`` files. + + + +Trivial/Internal Changes +------------------------ + +- `#4602 `_: Uninstall ``hypothesis`` in regen tox env. + + pytest 4.1.0 (2019-01-05) ========================= diff --git a/changelog/4602.trivial.rst b/changelog/4602.trivial.rst deleted file mode 100644 index 619b40601..000000000 --- a/changelog/4602.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Uninstall ``hypothesis`` in regen tox env. diff --git a/changelog/4617.bugfix.rst b/changelog/4617.bugfix.rst deleted file mode 100644 index 166378efd..000000000 --- a/changelog/4617.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed ``pytest.warns`` bug when context manager is reused (e.g. multiple parametrization). diff --git a/changelog/4643.trivial.rst b/changelog/4643.trivial.rst new file mode 100644 index 000000000..75385f4b3 --- /dev/null +++ b/changelog/4643.trivial.rst @@ -0,0 +1,3 @@ +Use ``a.item()`` instead of the deprecated ``np.asscalar(a)`` in ``pytest.approx``. + +``np.asscalar`` has been `deprecated `__ in ``numpy 1.16.``. diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index 40734e5b3..51a3227ea 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,7 @@ Release announcements :maxdepth: 2 + release-4.1.1 release-4.1.0 release-4.0.2 release-4.0.1 diff --git a/doc/en/announce/release-4.1.1.rst b/doc/en/announce/release-4.1.1.rst new file mode 100644 index 000000000..80644fc84 --- /dev/null +++ b/doc/en/announce/release-4.1.1.rst @@ -0,0 +1,27 @@ +pytest-4.1.1 +======================================= + +pytest 4.1.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 +* Anton Lodder +* Bruno Oliveira +* Daniel Hahler +* David Vo +* Oscar Benjamin +* Ronny Pfannschmidt +* Victor Maryama +* Yoav Caspi +* dmitry.dygalo + + +Happy testing, +The pytest Development Team diff --git a/doc/en/cache.rst b/doc/en/cache.rst index ba9d87a5f..2d2d67047 100644 --- a/doc/en/cache.rst +++ b/doc/en/cache.rst @@ -214,6 +214,8 @@ If you run this command for the first time, you can see the print statement: def test_function(mydata): > assert mydata == 23 E assert 42 == 23 + E -42 + E +23 test_caching.py:17: AssertionError -------------------------- Captured stdout setup --------------------------- @@ -235,6 +237,8 @@ the cache and nothing will be printed: def test_function(mydata): > assert mydata == 23 E assert 42 == 23 + E -42 + E +23 test_caching.py:17: AssertionError 1 failed in 0.12 seconds diff --git a/doc/en/conf.py b/doc/en/conf.py index 53600e616..74a43596e 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -42,10 +42,11 @@ todo_include_todos = 1 extensions = [ "pygments_pytest", "sphinx.ext.autodoc", - "sphinx.ext.todo", "sphinx.ext.autosummary", "sphinx.ext.intersphinx", + "sphinx.ext.todo", "sphinx.ext.viewcode", + "sphinx_removed_in", "sphinxcontrib_trio", ] diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index f3240cec7..ebde15734 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -101,7 +101,7 @@ an appropriate period of deprecation has passed. Using ``Class`` in custom Collectors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*Removed in version 4.0.* +.. versionremoved:: 4.0 Using objects named ``"Class"`` as a way to customize the type of nodes that are collected in ``Collector`` subclasses has been deprecated. Users instead should use ``pytest_pycollect_makeitem`` to customize node types during @@ -114,7 +114,7 @@ message please contact the authors so they can change the code. marks in ``pytest.mark.parametrize`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*Removed in version 4.0.* +.. versionremoved:: 4.0 Applying marks to values of a ``pytest.mark.parametrize`` call is now deprecated. For example: @@ -162,7 +162,7 @@ To update the code, use ``pytest.param``: ``pytest_funcarg__`` prefix ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*Removed in version 4.0.* +.. versionremoved:: 4.0 In very early pytest versions fixtures could be defined using the ``pytest_funcarg__`` prefix: @@ -184,7 +184,7 @@ Switch over to the ``@pytest.fixture`` decorator: [pytest] section in setup.cfg files ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*Removed in version 4.0.* +.. versionremoved:: 4.0 ``[pytest]`` sections in ``setup.cfg`` files should now be named ``[tool:pytest]`` to avoid conflicts with other distutils commands. @@ -193,7 +193,7 @@ to avoid conflicts with other distutils commands. Metafunc.addcall ~~~~~~~~~~~~~~~~ -*Removed in version 4.0.* +.. versionremoved:: 4.0 :meth:`_pytest.python.Metafunc.addcall` was a precursor to the current parametrized mechanism. Users should use :meth:`_pytest.python.Metafunc.parametrize` instead. @@ -217,7 +217,7 @@ Becomes: ``cached_setup`` ~~~~~~~~~~~~~~~~ -*Removed in version 4.0.* +.. versionremoved:: 4.0 ``request.cached_setup`` was the precursor of the setup/teardown mechanism available to fixtures. @@ -249,7 +249,7 @@ more information. pytest_plugins in non-top-level conftest files ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*Removed in version 4.0.* +.. versionremoved:: 4.0 Defining ``pytest_plugins`` is now deprecated in non-top-level conftest.py files because they will activate referenced plugins *globally*, which is surprising because for all other pytest @@ -259,7 +259,7 @@ features ``conftest.py`` files are only *active* for tests at or below it. ``Config.warn`` and ``Node.warn`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*Removed in version 4.0.* +.. versionremoved:: 4.0 Those methods were part of the internal pytest warnings system, but since ``3.8`` pytest is using the builtin warning system for its own warnings, so those two functions are now deprecated. @@ -286,7 +286,7 @@ Becomes: record_xml_property ~~~~~~~~~~~~~~~~~~~ -*Removed in version 4.0.* +.. versionremoved:: 4.0 The ``record_xml_property`` fixture is now deprecated in favor of the more generic ``record_property``, which can be used by other consumers (for example ``pytest-html``) to obtain custom information about the test run. @@ -309,7 +309,7 @@ Change to: Passing command-line string to ``pytest.main()`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*Removed in version 4.0.* +.. versionremoved:: 4.0 Passing a command-line string to ``pytest.main()`` is deprecated: @@ -331,7 +331,7 @@ on (for example ``bash`` or ``Powershell``), but this is very hard/impossible to Calling fixtures directly ~~~~~~~~~~~~~~~~~~~~~~~~~ -*Removed in version 4.0.* +.. versionremoved:: 4.0 Calling a fixture function directly, as opposed to request them in a test function, is deprecated. @@ -384,7 +384,7 @@ with the ``name`` parameter: ``yield`` tests ~~~~~~~~~~~~~~~ -*Removed in version 4.0.* +.. versionremoved:: 4.0 pytest supported ``yield``-style tests, where a test function actually ``yield`` functions and values that are then turned into proper test methods. Example: @@ -412,7 +412,7 @@ This form of test function doesn't support fixtures properly, and users should s Internal classes accessed through ``Node`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*Removed in version 4.0.* +.. versionremoved:: 4.0 Access of ``Module``, ``Function``, ``Class``, ``Instance``, ``File`` and ``Item`` through ``Node`` instances now issue this warning:: @@ -423,10 +423,28 @@ Users should just ``import pytest`` and access those objects using the ``pytest` This has been documented as deprecated for years, but only now we are actually emitting deprecation warnings. +``Node.get_marker`` +~~~~~~~~~~~~~~~~~~~ + +.. versionremoved:: 4.0 + +As part of a large :ref:`marker-revamp`, :meth:`_pytest.nodes.Node.get_marker` is deprecated. See +:ref:`the documentation ` on tips on how to update your code. + + +``somefunction.markname`` +~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionremoved:: 4.0 + +As part of a large :ref:`marker-revamp` we already deprecated using ``MarkInfo`` +the only correct way to get markers of an element is via ``node.iter_markers(name)``. + + ``pytest_namespace`` ~~~~~~~~~~~~~~~~~~~~ -*Removed in version 4.0.* +.. versionremoved:: 4.0 This hook is deprecated because it greatly complicates the pytest internals regarding configuration and initialization, making some bug fixes and refactorings impossible. @@ -461,7 +479,7 @@ As a stopgap measure, plugin authors may still inject their names into pytest's Reinterpretation mode (``--assert=reinterp``) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*Removed in version 3.0.* +.. versionremoved:: 3.0 Reinterpretation mode has now been removed and only plain and rewrite mode are available, consequently the ``--assert=reinterp`` option is @@ -473,7 +491,7 @@ explicitly turn on assertion rewriting for those files. Removed command-line options ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*Removed in version 3.0.* +.. versionremoved:: 3.0 The following deprecated commandline options were removed: @@ -485,27 +503,9 @@ The following deprecated commandline options were removed: py.test-X* entry points ~~~~~~~~~~~~~~~~~~~~~~~ -*Removed in version 3.0.* +.. versionremoved:: 3.0 Removed all ``py.test-X*`` entry points. The versioned, suffixed entry points were never documented and a leftover from a pre-virtualenv era. These entry points also created broken entry points in wheels, so removing them also removes a source of confusion for users. - - -``Node.get_marker`` -~~~~~~~~~~~~~~~~~~~ - -*Removed in version 4.0* - -As part of a large :ref:`marker-revamp`, :meth:`_pytest.nodes.Node.get_marker` is deprecated. See -:ref:`the documentation ` on tips on how to update your code. - - -``somefunction.markname`` -~~~~~~~~~~~~~~~~~~~~~~~~~ - -*Removed in version 4.0* - -As part of a large :ref:`marker-revamp` we already deprecated using ``MarkInfo`` -the only correct way to get markers of an element is via ``node.iter_markers(name)``. diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index 92756e492..98aaeae3b 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -406,6 +406,8 @@ argument sets to use for each test function. Let's run it: def test_equals(self, a, b): > assert a == b E assert 1 == 2 + E -1 + E +2 test_parametrize.py:18: AssertionError 1 failed, 2 passed in 0.12 seconds diff --git a/doc/en/goodpractices.rst b/doc/en/goodpractices.rst index 70c369244..080685a4c 100644 --- a/doc/en/goodpractices.rst +++ b/doc/en/goodpractices.rst @@ -72,8 +72,18 @@ to keep tests separate from actual application code (often a good idea):: test_view.py ... -This way your tests can run easily against an installed version -of ``mypkg``. +This has the following benefits: + +* Your tests can run against an installed version after executing ``pip install .``. +* Your tests can run against the local copy with an editable install after executing ``pip install --editable .``. +* If you don't have a ``setup.py`` file and are relying on the fact that Python by default puts the current + directory in ``sys.path`` to import your package, you can execute ``python -m pytest`` to execute the tests against the + local copy directly, without using ``pip``. + +.. note:: + + See :ref:`pythonpath` for more information about the difference between calling ``pytest`` and + ``python -m pytest``. Note that using this scheme your test files must have **unique names**, because ``pytest`` will import them as *top-level* modules since there are no packages diff --git a/doc/en/reference.rst b/doc/en/reference.rst index 9305cbb95..92e298a88 100644 --- a/doc/en/reference.rst +++ b/doc/en/reference.rst @@ -889,6 +889,12 @@ Here is a list of builtin configuration options that may be written in a ``pytes file, usually located at the root of your repository. All options must be under a ``[pytest]`` section (``[tool:pytest]`` for ``setup.cfg`` files). +.. warning:: + Usage of ``setup.cfg`` is not recommended unless for very simple use cases. ``.cfg`` + files use a different parser than ``pytest.ini`` and ``tox.ini`` which might cause hard to track + down problems. + When possible, it is recommended to use the latter files to hold your pytest configuration. + Configuration file options may be overwritten in the command-line by using ``-o/--override``, which can also be passed multiple times. The expected format is ``name=value``. For example:: diff --git a/doc/en/requirements.txt b/doc/en/requirements.txt index 80c57422f..4e2b8ce1a 100644 --- a/doc/en/requirements.txt +++ b/doc/en/requirements.txt @@ -1,3 +1,4 @@ pygments-pytest>=1.1.0 sphinx>=1.8.2 sphinxcontrib-trio +sphinx-removed-in>=0.1.3 diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index eae63acee..16f6fa243 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -512,7 +512,13 @@ def _format_assertmsg(obj): def _should_repr_global_name(obj): - return not hasattr(obj, "__name__") and not callable(obj) + if callable(obj): + return False + + try: + return not hasattr(obj, "__name__") + except Exception: + return True def _format_boolop(explanations, is_or): diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index cb220def3..b35b6abc5 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -151,6 +151,8 @@ def assertrepr_compare(config, op, left, right): elif type(left) == type(right) and (isdatacls(left) or isattrs(left)): type_fn = (isdatacls, isattrs) explanation = _compare_eq_cls(left, right, verbose, type_fn) + elif verbose: + explanation = _compare_eq_verbose(left, right) if isiterable(left) and isiterable(right): expl = _compare_eq_iterable(left, right, verbose) if explanation is not None: @@ -236,6 +238,18 @@ def _diff_text(left, right, verbose=False): return explanation +def _compare_eq_verbose(left, right): + keepends = True + left_lines = repr(left).splitlines(keepends) + right_lines = repr(right).splitlines(keepends) + + explanation = [] + explanation += [u"-" + line for line in left_lines] + explanation += [u"+" + line for line in right_lines] + + return explanation + + def _compare_eq_iterable(left, right, verbose=False): if not verbose: return [u"Use -v to get the full diff"] diff --git a/src/_pytest/doctest.py b/src/_pytest/doctest.py index dbf7df823..d34cb638c 100644 --- a/src/_pytest/doctest.py +++ b/src/_pytest/doctest.py @@ -3,17 +3,19 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +import inspect import platform import sys import traceback +from contextlib import contextmanager import pytest from _pytest._code.code import ExceptionInfo from _pytest._code.code import ReprFileLocation from _pytest._code.code import TerminalRepr +from _pytest.compat import safe_getattr from _pytest.fixtures import FixtureRequest - DOCTEST_REPORT_CHOICE_NONE = "none" DOCTEST_REPORT_CHOICE_CDIFF = "cdiff" DOCTEST_REPORT_CHOICE_NDIFF = "ndiff" @@ -346,10 +348,61 @@ def _check_all_skipped(test): pytest.skip("all tests skipped by +SKIP option") +def _is_mocked(obj): + """ + returns if a object is possibly a mock object by checking the existence of a highly improbable attribute + """ + return ( + safe_getattr(obj, "pytest_mock_example_attribute_that_shouldnt_exist", None) + is not None + ) + + +@contextmanager +def _patch_unwrap_mock_aware(): + """ + contextmanager which replaces ``inspect.unwrap`` with a version + that's aware of mock objects and doesn't recurse on them + """ + real_unwrap = getattr(inspect, "unwrap", None) + if real_unwrap is None: + yield + else: + + def _mock_aware_unwrap(obj, stop=None): + if stop is None: + return real_unwrap(obj, stop=_is_mocked) + else: + return real_unwrap(obj, stop=lambda obj: _is_mocked(obj) or stop(obj)) + + inspect.unwrap = _mock_aware_unwrap + try: + yield + finally: + inspect.unwrap = real_unwrap + + class DoctestModule(pytest.Module): def collect(self): import doctest + class MockAwareDocTestFinder(doctest.DocTestFinder): + """ + a hackish doctest finder that overrides stdlib internals to fix a stdlib bug + + https://github.com/pytest-dev/pytest/issues/3456 + https://bugs.python.org/issue25532 + """ + + def _find(self, tests, obj, name, module, source_lines, globs, seen): + if _is_mocked(obj): + return + with _patch_unwrap_mock_aware(): + + doctest.DocTestFinder._find( + self, tests, obj, name, module, source_lines, globs, seen + ) + if self.fspath.basename == "conftest.py": module = self.config.pluginmanager._importconftest(self.fspath) else: @@ -361,7 +414,7 @@ class DoctestModule(pytest.Module): else: raise # uses internal doctest module parsing mechanism - finder = doctest.DocTestFinder() + finder = MockAwareDocTestFinder() optionflags = get_optionflags(self) runner = _get_runner( verbose=0, diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index 4e4740192..9b31d4e68 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -150,10 +150,10 @@ class ApproxNumpy(ApproxBase): if np.isscalar(actual): for i in np.ndindex(self.expected.shape): - yield actual, np.asscalar(self.expected[i]) + yield actual, self.expected[i].item() else: for i in np.ndindex(self.expected.shape): - yield np.asscalar(actual[i]), np.asscalar(self.expected[i]) + yield actual[i].item(), self.expected[i].item() class ApproxMapping(ApproxBase): diff --git a/testing/test_assertion.py b/testing/test_assertion.py index fcefdbb11..cbd0d9068 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -488,6 +488,30 @@ class TestAssert_reprcompare(object): expl = callequal([(1, 2)], []) assert len(expl) > 1 + def test_repr_verbose(self): + class Nums: + def __init__(self, nums): + self.nums = nums + + def __repr__(self): + return str(self.nums) + + list_x = list(range(5000)) + list_y = list(range(5000)) + list_y[len(list_y) // 2] = 3 + nums_x = Nums(list_x) + nums_y = Nums(list_y) + + assert callequal(nums_x, nums_y) is None + + expl = callequal(nums_x, nums_y, verbose=1) + assert "-" + repr(nums_x) in expl + assert "+" + repr(nums_y) in expl + + expl = callequal(nums_x, nums_y, verbose=2) + assert "-" + repr(nums_x) in expl + assert "+" + repr(nums_y) in expl + def test_list_bad_repr(self): class A(object): def __repr__(self): diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index 4187e365b..840fda2ca 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -180,6 +180,27 @@ class TestAssertionRewrite(object): assert getmsg(f, {"cls": X}) == "assert cls == 42" + def test_dont_rewrite_if_hasattr_fails(self): + class Y(object): + """ A class whos getattr fails, but not with `AttributeError` """ + + def __getattr__(self, attribute_name): + raise KeyError() + + def __repr__(self): + return "Y" + + def __init__(self): + self.foo = 3 + + def f(): + assert cls().foo == 2 # noqa + + message = getmsg(f, {"cls": Y}) + assert "assert 3 == 2" in message + assert "+ where 3 = Y.foo" in message + assert "+ where Y = cls()" in message + def test_assert_already_has_message(self): def f(): assert False, "something bad!" diff --git a/testing/test_doctest.py b/testing/test_doctest.py index cccfdabe6..e7b6b060f 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -1206,3 +1206,22 @@ class TestDoctestReportingOption(object): "*error: argument --doctest-report: invalid choice: 'obviously_invalid_format' (choose from*" ] ) + + +@pytest.mark.parametrize("mock_module", ["mock", "unittest.mock"]) +def test_doctest_mock_objects_dont_recurse_missbehaved(mock_module, testdir): + pytest.importorskip(mock_module) + testdir.makepyfile( + """ + from {mock_module} import call + class Example(object): + ''' + >>> 1 + 1 + 2 + ''' + """.format( + mock_module=mock_module + ) + ) + result = testdir.runpytest("--doctest-modules") + result.stdout.fnmatch_lines(["* 1 passed *"]) diff --git a/testing/test_warnings.py b/testing/test_warnings.py index 45e196149..e8075b617 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals import sys +import warnings import six @@ -685,25 +686,10 @@ class TestAssertionWarnings: result.stdout.fnmatch_lines(["*1 failed in*"]) -def test_warningschecker_twice(testdir): +def test_warnings_checker_twice(): """Issue #4617""" - - testdir.makepyfile( - """ - import pytest - import warnings - - @pytest.mark.parametrize("other", [1, 2]) - @pytest.mark.parametrize("expectation", [ - pytest.warns(DeprecationWarning, - match="Message A"), - pytest.warns(DeprecationWarning, - match="Message A"), - ]) - def test_parametrized_warnings(other, expectation): - with expectation: - warnings.warn("Message A", DeprecationWarning) - """ - ) - result = testdir.runpytest() - result.stdout.fnmatch_lines(["* 4 passed in *"]) + expectation = pytest.warns(UserWarning) + with expectation: + warnings.warn("Message A", UserWarning) + with expectation: + warnings.warn("Message B", UserWarning)