Merge remote-tracking branch 'upstream/master' into merge-master-into-features
This commit is contained in:
		
						commit
						c64a8c9c7f
					
				
							
								
								
									
										2
									
								
								AUTHORS
								
								
								
								
							
							
						
						
									
										2
									
								
								AUTHORS
								
								
								
								
							|  | @ -182,6 +182,7 @@ Russel Winder | |||
| Ryan Wooden | ||||
| Samuel Dion-Girardeau | ||||
| Samuele Pedroni | ||||
| Sankt Petersbug | ||||
| Segev Finer | ||||
| Serhii Mozghovyi | ||||
| Simon Gomizelj | ||||
|  | @ -205,6 +206,7 @@ Trevor Bekolay | |||
| Tyler Goodlet | ||||
| Tzu-ping Chung | ||||
| Vasily Kuznetsov | ||||
| Victor Maryama | ||||
| Victor Uriarte | ||||
| Vidar T. Fauske | ||||
| Virgil Dupras | ||||
|  |  | |||
|  | @ -1,3 +1,13 @@ | |||
| ================= | ||||
| Changelog history | ||||
| ================= | ||||
| 
 | ||||
| Versions follow `Semantic Versioning <https://semver.org/>`_ (``<major>.<minor>.<patch>``). | ||||
| 
 | ||||
| Backward incompatible (breaking) changes will only be introduced in major versions | ||||
| with advance notice in the **Deprecations** section of releases. | ||||
| 
 | ||||
| 
 | ||||
| .. | ||||
|     You should *NOT* be adding new change log entries to this file, this | ||||
|     file is managed by towncrier. You *may* edit previous change logs to | ||||
|  | @ -8,6 +18,40 @@ | |||
| 
 | ||||
| .. towncrier release notes start | ||||
| 
 | ||||
| pytest 3.7.2 (2018-08-16) | ||||
| ========================= | ||||
| 
 | ||||
| Bug Fixes | ||||
| --------- | ||||
| 
 | ||||
| - `#3671 <https://github.com/pytest-dev/pytest/issues/3671>`_: Fix ``filterwarnings`` not being registered as a builtin mark. | ||||
| 
 | ||||
| 
 | ||||
| - `#3768 <https://github.com/pytest-dev/pytest/issues/3768>`_, `#3789 <https://github.com/pytest-dev/pytest/issues/3789>`_: Fix test collection from packages mixed with normal directories. | ||||
| 
 | ||||
| 
 | ||||
| - `#3771 <https://github.com/pytest-dev/pytest/issues/3771>`_: Fix infinite recursion during collection if a ``pytest_ignore_collect`` hook returns ``False`` instead of ``None``. | ||||
| 
 | ||||
| 
 | ||||
| - `#3774 <https://github.com/pytest-dev/pytest/issues/3774>`_: Fix bug where decorated fixtures would lose functionality (for example ``@mock.patch``). | ||||
| 
 | ||||
| 
 | ||||
| - `#3775 <https://github.com/pytest-dev/pytest/issues/3775>`_: Fix bug where importing modules or other objects with prefix ``pytest_`` prefix would raise a ``PluginValidationError``. | ||||
| 
 | ||||
| 
 | ||||
| - `#3788 <https://github.com/pytest-dev/pytest/issues/3788>`_: Fix ``AttributeError`` during teardown of ``TestCase`` subclasses which raise an exception during ``__init__``. | ||||
| 
 | ||||
| 
 | ||||
| - `#3804 <https://github.com/pytest-dev/pytest/issues/3804>`_: Fix traceback reporting for exceptions with ``__cause__`` cycles. | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| Improved Documentation | ||||
| ---------------------- | ||||
| 
 | ||||
| - `#3746 <https://github.com/pytest-dev/pytest/issues/3746>`_: Add documentation for ``metafunc.config`` that had been mistakenly hidden. | ||||
| 
 | ||||
| 
 | ||||
| pytest 3.7.1 (2018-08-02) | ||||
| ========================= | ||||
| 
 | ||||
|  |  | |||
|  | @ -0,0 +1 @@ | |||
| Fix ``stdout/stderr`` not getting captured when real-time cli logging is active. | ||||
|  | @ -0,0 +1 @@ | |||
| Replace broken type annotations with type comments. | ||||
|  | @ -6,6 +6,7 @@ Release announcements | |||
|    :maxdepth: 2 | ||||
| 
 | ||||
| 
 | ||||
|    release-3.7.2 | ||||
|    release-3.7.1 | ||||
|    release-3.7.0 | ||||
|    release-3.6.4 | ||||
|  |  | |||
|  | @ -0,0 +1,25 @@ | |||
| pytest-3.7.2 | ||||
| ======================================= | ||||
| 
 | ||||
| pytest 3.7.2 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 http://doc.pytest.org/en/latest/changelog.html. | ||||
| 
 | ||||
| Thanks to all who contributed to this release, among them: | ||||
| 
 | ||||
| * Anthony Sottile | ||||
| * Bruno Oliveira | ||||
| * Daniel Hahler | ||||
| * Josh Holland | ||||
| * Ronny Pfannschmidt | ||||
| * Sankt Petersbug | ||||
| * Wes Thomas | ||||
| * turturica | ||||
| 
 | ||||
| 
 | ||||
| Happy testing, | ||||
| The pytest Development Team | ||||
|  | @ -1,7 +1,4 @@ | |||
| 
 | ||||
| .. _changelog: | ||||
| 
 | ||||
| Changelog history | ||||
| ================================= | ||||
| 
 | ||||
| .. include:: ../../CHANGELOG.rst | ||||
|  |  | |||
|  | @ -200,6 +200,8 @@ You can ask which markers exist for your test suite - the list includes our just | |||
|     $ pytest --markers | ||||
|     @pytest.mark.webtest: mark a test as a webtest. | ||||
| 
 | ||||
|     @pytest.mark.filterwarnings(warning): add a warning filter to the given test. see http://pytest.org/latest/warnings.html#pytest-mark-filterwarnings | ||||
| 
 | ||||
|     @pytest.mark.skip(reason=None): skip the given test function with an optional reason. Example: skip(reason="no way of currently testing this") skips the test. | ||||
| 
 | ||||
|     @pytest.mark.skipif(condition): skip the given test function if eval(condition) results in a True value.  Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. see http://pytest.org/latest/skipping.html | ||||
|  | @ -374,6 +376,8 @@ The ``--markers`` option always gives you a list of available markers:: | |||
|     $ pytest --markers | ||||
|     @pytest.mark.env(name): mark test to run only on named environment | ||||
| 
 | ||||
|     @pytest.mark.filterwarnings(warning): add a warning filter to the given test. see http://pytest.org/latest/warnings.html#pytest-mark-filterwarnings | ||||
| 
 | ||||
|     @pytest.mark.skip(reason=None): skip the given test function with an optional reason. Example: skip(reason="no way of currently testing this") skips the test. | ||||
| 
 | ||||
|     @pytest.mark.skipif(condition): skip the given test function if eval(condition) results in a True value.  Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. see http://pytest.org/latest/skipping.html | ||||
|  |  | |||
|  | @ -84,8 +84,9 @@ interesting to just look at the collection tree:: | |||
|     platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y | ||||
|     rootdir: $REGENDOC_TMPDIR/nonpython, inifile: | ||||
|     collected 2 items | ||||
|     <YamlFile 'test_simple.yml'> | ||||
|       <YamlItem 'hello'> | ||||
|       <YamlItem 'ok'> | ||||
|     <Package '$REGENDOC_TMPDIR/nonpython'> | ||||
|       <YamlFile 'test_simple.yml'> | ||||
|         <YamlItem 'hello'> | ||||
|         <YamlItem 'ok'> | ||||
| 
 | ||||
|     ======================= no tests ran in 0.12 seconds ======================= | ||||
|  |  | |||
|  | @ -411,11 +411,10 @@ is to be run with different sets of arguments for its three arguments: | |||
| Running it results in some skips if we don't have all the python interpreters installed and otherwise runs all combinations (5 interpreters times 5 interpreters times 3 objects to serialize/deserialize):: | ||||
| 
 | ||||
|    . $ pytest -rs -q multipython.py | ||||
|    ...ssssssssssssssssssssssss                                          [100%] | ||||
|    ...sss...sssssssss...sss...                                          [100%] | ||||
|    ========================= short test summary info ========================== | ||||
|    SKIP [12] $REGENDOC_TMPDIR/CWD/multipython.py:28: 'python3.4' not found | ||||
|    SKIP [12] $REGENDOC_TMPDIR/CWD/multipython.py:28: 'python3.5' not found | ||||
|    3 passed, 24 skipped in 0.12 seconds | ||||
|    SKIP [15] $REGENDOC_TMPDIR/CWD/multipython.py:28: 'python3.4' not found | ||||
|    12 passed, 15 skipped in 0.12 seconds | ||||
| 
 | ||||
| Indirect parametrization of optional implementations/imports | ||||
| -------------------------------------------------------------------- | ||||
|  |  | |||
|  | @ -719,7 +719,9 @@ class FormattedExcinfo(object): | |||
|             repr_chain = [] | ||||
|             e = excinfo.value | ||||
|             descr = None | ||||
|             while e is not None: | ||||
|             seen = set() | ||||
|             while e is not None and id(e) not in seen: | ||||
|                 seen.add(id(e)) | ||||
|                 if excinfo: | ||||
|                     reprtraceback = self.repr_traceback(excinfo) | ||||
|                     reprcrash = excinfo._getreprcrash() | ||||
|  |  | |||
|  | @ -14,8 +14,7 @@ from tempfile import TemporaryFile | |||
| 
 | ||||
| import six | ||||
| import pytest | ||||
| from _pytest.compat import CaptureIO | ||||
| 
 | ||||
| from _pytest.compat import CaptureIO, dummy_context_manager | ||||
| 
 | ||||
| patchsysdict = {0: "stdin", 1: "stdout", 2: "stderr"} | ||||
| 
 | ||||
|  | @ -85,6 +84,7 @@ class CaptureManager(object): | |||
|     def __init__(self, method): | ||||
|         self._method = method | ||||
|         self._global_capturing = None | ||||
|         self._current_item = None | ||||
| 
 | ||||
|     def _getcapture(self, method): | ||||
|         if method == "fd": | ||||
|  | @ -121,6 +121,19 @@ class CaptureManager(object): | |||
|                 cap.suspend_capturing(in_=in_) | ||||
|             return outerr | ||||
| 
 | ||||
|     @contextlib.contextmanager | ||||
|     def global_and_fixture_disabled(self): | ||||
|         """Context manager to temporarily disables global and current fixture capturing.""" | ||||
|         # Need to undo local capsys-et-al if exists before disabling global capture | ||||
|         fixture = getattr(self._current_item, "_capture_fixture", None) | ||||
|         ctx_manager = fixture._suspend() if fixture else dummy_context_manager() | ||||
|         with ctx_manager: | ||||
|             self.suspend_global_capture(item=None, in_=False) | ||||
|             try: | ||||
|                 yield | ||||
|             finally: | ||||
|                 self.resume_global_capture() | ||||
| 
 | ||||
|     def activate_fixture(self, item): | ||||
|         """If the current item is using ``capsys`` or ``capfd``, activate them so they take precedence over | ||||
|         the global capture. | ||||
|  | @ -151,28 +164,34 @@ class CaptureManager(object): | |||
| 
 | ||||
|     @pytest.hookimpl(hookwrapper=True) | ||||
|     def pytest_runtest_setup(self, item): | ||||
|         self._current_item = item | ||||
|         self.resume_global_capture() | ||||
|         # no need to activate a capture fixture because they activate themselves during creation; this | ||||
|         # only makes sense when a fixture uses a capture fixture, otherwise the capture fixture will | ||||
|         # be activated during pytest_runtest_call | ||||
|         yield | ||||
|         self.suspend_capture_item(item, "setup") | ||||
|         self._current_item = None | ||||
| 
 | ||||
|     @pytest.hookimpl(hookwrapper=True) | ||||
|     def pytest_runtest_call(self, item): | ||||
|         self._current_item = item | ||||
|         self.resume_global_capture() | ||||
|         # it is important to activate this fixture during the call phase so it overwrites the "global" | ||||
|         # capture | ||||
|         self.activate_fixture(item) | ||||
|         yield | ||||
|         self.suspend_capture_item(item, "call") | ||||
|         self._current_item = None | ||||
| 
 | ||||
|     @pytest.hookimpl(hookwrapper=True) | ||||
|     def pytest_runtest_teardown(self, item): | ||||
|         self._current_item = item | ||||
|         self.resume_global_capture() | ||||
|         self.activate_fixture(item) | ||||
|         yield | ||||
|         self.suspend_capture_item(item, "teardown") | ||||
|         self._current_item = None | ||||
| 
 | ||||
|     @pytest.hookimpl(tryfirst=True) | ||||
|     def pytest_keyboard_interrupt(self, excinfo): | ||||
|  | @ -314,17 +333,21 @@ class CaptureFixture(object): | |||
|             return self._outerr | ||||
| 
 | ||||
|     @contextlib.contextmanager | ||||
|     def disabled(self): | ||||
|         """Temporarily disables capture while inside the 'with' block.""" | ||||
|     def _suspend(self): | ||||
|         """Suspends this fixture's own capturing temporarily.""" | ||||
|         self._capture.suspend_capturing() | ||||
|         capmanager = self.request.config.pluginmanager.getplugin("capturemanager") | ||||
|         capmanager.suspend_global_capture(item=None, in_=False) | ||||
|         try: | ||||
|             yield | ||||
|         finally: | ||||
|             capmanager.resume_global_capture() | ||||
|             self._capture.resume_capturing() | ||||
| 
 | ||||
|     @contextlib.contextmanager | ||||
|     def disabled(self): | ||||
|         """Temporarily disables capture while inside the 'with' block.""" | ||||
|         capmanager = self.request.config.pluginmanager.getplugin("capturemanager") | ||||
|         with capmanager.global_and_fixture_disabled(): | ||||
|             yield | ||||
| 
 | ||||
| 
 | ||||
| def safe_text_dupfile(f, mode, default_encoding="UTF8"): | ||||
|     """ return an open text file object that's a duplicate of f on the | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ import functools | |||
| import inspect | ||||
| import re | ||||
| import sys | ||||
| from contextlib import contextmanager | ||||
| 
 | ||||
| import py | ||||
| 
 | ||||
|  | @ -151,6 +152,13 @@ def getfuncargnames(function, is_method=False, cls=None): | |||
|     return arg_names | ||||
| 
 | ||||
| 
 | ||||
| @contextmanager | ||||
| def dummy_context_manager(): | ||||
|     """Context manager that does nothing, useful in situations where you might need an actual context manager or not | ||||
|     depending on some condition. Using this allow to keep the same code""" | ||||
|     yield | ||||
| 
 | ||||
| 
 | ||||
| def get_default_arg_names(function): | ||||
|     # Note: this code intentionally mirrors the code at the beginning of getfuncargnames, | ||||
|     # to get the arguments which were excluded from its result because they had default values | ||||
|  | @ -228,12 +236,31 @@ else: | |||
|             return val.encode("unicode-escape") | ||||
| 
 | ||||
| 
 | ||||
| class _PytestWrapper(object): | ||||
|     """Dummy wrapper around a function object for internal use only. | ||||
| 
 | ||||
|     Used to correctly unwrap the underlying function object | ||||
|     when we are creating fixtures, because we wrap the function object ourselves with a decorator | ||||
|     to issue warnings when the fixture function is called directly. | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, obj): | ||||
|         self.obj = obj | ||||
| 
 | ||||
| 
 | ||||
| def get_real_func(obj): | ||||
|     """ gets the real function object of the (possibly) wrapped object by | ||||
|     functools.wraps or functools.partial. | ||||
|     """ | ||||
|     start_obj = obj | ||||
|     for i in range(100): | ||||
|         # __pytest_wrapped__ is set by @pytest.fixture when wrapping the fixture function | ||||
|         # to trigger a warning if it gets called directly instead of by pytest: we don't | ||||
|         # want to unwrap further than this otherwise we lose useful wrappings like @mock.patch (#3774) | ||||
|         new_obj = getattr(obj, "__pytest_wrapped__", None) | ||||
|         if isinstance(new_obj, _PytestWrapper): | ||||
|             obj = new_obj.obj | ||||
|             break | ||||
|         new_obj = getattr(obj, "__wrapped__", None) | ||||
|         if new_obj is None: | ||||
|             break | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| """ command line options, ini-file and conftest.py processing. """ | ||||
| from __future__ import absolute_import, division, print_function | ||||
| import argparse | ||||
| import inspect | ||||
| import shlex | ||||
| import traceback | ||||
| import types | ||||
|  | @ -252,6 +253,10 @@ class PytestPluginManager(PluginManager): | |||
|         method = getattr(plugin, name) | ||||
|         opts = super(PytestPluginManager, self).parse_hookimpl_opts(plugin, name) | ||||
| 
 | ||||
|         # consider only actual functions for hooks (#3775) | ||||
|         if not inspect.isroutine(method): | ||||
|             return | ||||
| 
 | ||||
|         # collect unmarked hooks as long as they have the `pytest_' prefix | ||||
|         if opts is None and name.startswith("pytest_"): | ||||
|             opts = {} | ||||
|  |  | |||
|  | @ -174,23 +174,23 @@ class Argument(object): | |||
|             if isinstance(typ, six.string_types): | ||||
|                 if typ == "choice": | ||||
|                     warnings.warn( | ||||
|                         "type argument to addoption() is a string %r." | ||||
|                         " For parsearg this is optional and when supplied" | ||||
|                         " should be a type." | ||||
|                         "`type` argument to addoption() is the string %r." | ||||
|                         " For choices this is optional and can be omitted, " | ||||
|                         " but when supplied should be a type (for example `str` or `int`)." | ||||
|                         " (options: %s)" % (typ, names), | ||||
|                         DeprecationWarning, | ||||
|                         stacklevel=3, | ||||
|                         stacklevel=4, | ||||
|                     ) | ||||
|                     # argparse expects a type here take it from | ||||
|                     # the type of the first element | ||||
|                     attrs["type"] = type(attrs["choices"][0]) | ||||
|                 else: | ||||
|                     warnings.warn( | ||||
|                         "type argument to addoption() is a string %r." | ||||
|                         " For parsearg this should be a type." | ||||
|                         "`type` argument to addoption() is the string %r, " | ||||
|                         " but when supplied should be a type (for example `str` or `int`)." | ||||
|                         " (options: %s)" % (typ, names), | ||||
|                         DeprecationWarning, | ||||
|                         stacklevel=3, | ||||
|                         stacklevel=4, | ||||
|                     ) | ||||
|                     attrs["type"] = Argument._typ_map[typ] | ||||
|                 # used in test_parseopt -> test_parse_defaultgetter | ||||
|  |  | |||
|  | @ -31,6 +31,7 @@ from _pytest.compat import ( | |||
|     safe_getattr, | ||||
|     FuncargnamesCompatAttr, | ||||
|     get_real_method, | ||||
|     _PytestWrapper, | ||||
| ) | ||||
| from _pytest.deprecated import FIXTURE_FUNCTION_CALL, RemovedInPytest4Warning | ||||
| from _pytest.outcomes import fail, TEST_OUTCOME | ||||
|  | @ -306,8 +307,8 @@ class FuncFixtureInfo(object): | |||
|     # fixture names specified via usefixtures and via autouse=True in fixture | ||||
|     # definitions. | ||||
|     initialnames = attr.ib(type=tuple) | ||||
|     names_closure = attr.ib(type="List[str]") | ||||
|     name2fixturedefs = attr.ib(type="List[str, List[FixtureDef]]") | ||||
|     names_closure = attr.ib()  # type: List[str] | ||||
|     name2fixturedefs = attr.ib()  # type: List[str, List[FixtureDef]] | ||||
| 
 | ||||
|     def prune_dependency_tree(self): | ||||
|         """Recompute names_closure from initialnames and name2fixturedefs | ||||
|  | @ -954,9 +955,6 @@ def _ensure_immutable_ids(ids): | |||
| def wrap_function_to_warning_if_called_directly(function, fixture_marker): | ||||
|     """Wrap the given fixture function so we can issue warnings about it being called directly, instead of | ||||
|     used as an argument in a test function. | ||||
| 
 | ||||
|     The warning is emitted only in Python 3, because I didn't find a reliable way to make the wrapper function | ||||
|     keep the original signature, and we probably will drop Python 2 in Pytest 4 anyway. | ||||
|     """ | ||||
|     is_yield_function = is_generator(function) | ||||
|     msg = FIXTURE_FUNCTION_CALL.format(name=fixture_marker.name or function.__name__) | ||||
|  | @ -982,6 +980,10 @@ def wrap_function_to_warning_if_called_directly(function, fixture_marker): | |||
|     if six.PY2: | ||||
|         result.__wrapped__ = function | ||||
| 
 | ||||
|     # keep reference to the original function in our own custom attribute so we don't unwrap | ||||
|     # further than this point and lose useful wrappings like @mock.patch (#3774) | ||||
|     result.__pytest_wrapped__ = _PytestWrapper(function) | ||||
| 
 | ||||
|     return result | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ from contextlib import closing, contextmanager | |||
| import re | ||||
| import six | ||||
| 
 | ||||
| from _pytest.compat import dummy_context_manager | ||||
| from _pytest.config import create_terminal_writer | ||||
| import pytest | ||||
| import py | ||||
|  | @ -369,11 +370,6 @@ def pytest_configure(config): | |||
|     config.pluginmanager.register(LoggingPlugin(config), "logging-plugin") | ||||
| 
 | ||||
| 
 | ||||
| @contextmanager | ||||
| def _dummy_context_manager(): | ||||
|     yield | ||||
| 
 | ||||
| 
 | ||||
| class LoggingPlugin(object): | ||||
|     """Attaches to the logging module and captures log messages for each test. | ||||
|     """ | ||||
|  | @ -537,7 +533,7 @@ class LoggingPlugin(object): | |||
|                 log_cli_handler, formatter=log_cli_formatter, level=log_cli_level | ||||
|             ) | ||||
|         else: | ||||
|             self.live_logs_context = _dummy_context_manager() | ||||
|             self.live_logs_context = dummy_context_manager() | ||||
| 
 | ||||
| 
 | ||||
| class _LiveLoggingStreamHandler(logging.StreamHandler): | ||||
|  | @ -572,9 +568,12 @@ class _LiveLoggingStreamHandler(logging.StreamHandler): | |||
|             self._test_outcome_written = False | ||||
| 
 | ||||
|     def emit(self, record): | ||||
|         if self.capture_manager is not None: | ||||
|             self.capture_manager.suspend_global_capture() | ||||
|         try: | ||||
|         ctx_manager = ( | ||||
|             self.capture_manager.global_and_fixture_disabled() | ||||
|             if self.capture_manager | ||||
|             else dummy_context_manager() | ||||
|         ) | ||||
|         with ctx_manager: | ||||
|             if not self._first_record_emitted: | ||||
|                 self.stream.write("\n") | ||||
|                 self._first_record_emitted = True | ||||
|  | @ -586,6 +585,3 @@ class _LiveLoggingStreamHandler(logging.StreamHandler): | |||
|                 self.stream.section("live log " + self._when, sep="-", bold=True) | ||||
|                 self._section_name_shown = True | ||||
|             logging.StreamHandler.emit(self, record) | ||||
|         finally: | ||||
|             if self.capture_manager is not None: | ||||
|                 self.capture_manager.resume_global_capture() | ||||
|  |  | |||
|  | @ -505,8 +505,9 @@ class Session(nodes.FSCollector): | |||
|                             root = self._node_cache[pkginit] | ||||
|                         else: | ||||
|                             col = root._collectfile(pkginit) | ||||
|                             if col and isinstance(col, Package): | ||||
|                                 root = col[0] | ||||
|                             if col: | ||||
|                                 if isinstance(col[0], Package): | ||||
|                                     root = col[0] | ||||
|                                 self._node_cache[root.fspath] = root | ||||
| 
 | ||||
|         # If it's a directory argument, recurse and look for any Subpackages. | ||||
|  |  | |||
|  | @ -216,18 +216,6 @@ def pytest_pycollect_makemodule(path, parent): | |||
|     return Module(path, parent) | ||||
| 
 | ||||
| 
 | ||||
| def pytest_ignore_collect(path, config): | ||||
|     # Skip duplicate packages. | ||||
|     keepduplicates = config.getoption("keepduplicates") | ||||
|     if keepduplicates: | ||||
|         duplicate_paths = config.pluginmanager._duplicatepaths | ||||
|         if path.basename == "__init__.py": | ||||
|             if path in duplicate_paths: | ||||
|                 return True | ||||
|             else: | ||||
|                 duplicate_paths.add(path) | ||||
| 
 | ||||
| 
 | ||||
| @hookimpl(hookwrapper=True) | ||||
| def pytest_pycollect_makeitem(collector, name, obj): | ||||
|     outcome = yield | ||||
|  | @ -554,14 +542,12 @@ class Package(Module): | |||
|         self.name = fspath.dirname | ||||
|         self.trace = session.trace | ||||
|         self._norecursepatterns = session._norecursepatterns | ||||
|         for path in list(session.config.pluginmanager._duplicatepaths): | ||||
|             if path.dirname == fspath.dirname and path != fspath: | ||||
|                 session.config.pluginmanager._duplicatepaths.remove(path) | ||||
|         self.fspath = fspath | ||||
| 
 | ||||
|     def _recurse(self, path): | ||||
|         ihook = self.gethookproxy(path.dirpath()) | ||||
|         if ihook.pytest_ignore_collect(path=path, config=self.config): | ||||
|             return | ||||
|             return False | ||||
|         for pat in self._norecursepatterns: | ||||
|             if path.check(fnmatch=pat): | ||||
|                 return False | ||||
|  | @ -594,9 +580,21 @@ class Package(Module): | |||
|         return path in self.session._initialpaths | ||||
| 
 | ||||
|     def collect(self): | ||||
|         path = self.fspath.dirpath() | ||||
|         # XXX: HACK! | ||||
|         # Before starting to collect any files from this package we need | ||||
|         # to cleanup the duplicate paths added by the session's collect(). | ||||
|         # Proper fix is to not track these as duplicates in the first place. | ||||
|         for path in list(self.session.config.pluginmanager._duplicatepaths): | ||||
|             # if path.parts()[:len(self.fspath.dirpath().parts())] == self.fspath.dirpath().parts(): | ||||
|             if path.dirname.startswith(self.name): | ||||
|                 self.session.config.pluginmanager._duplicatepaths.remove(path) | ||||
| 
 | ||||
|         this_path = self.fspath.dirpath() | ||||
|         pkg_prefix = None | ||||
|         for path in path.visit(fil=lambda x: 1, rec=self._recurse, bf=True, sort=True): | ||||
|         for path in this_path.visit(rec=self._recurse, bf=True, sort=True): | ||||
|             # we will visit our own __init__.py file, in which case we skip it | ||||
|             if path.basename == "__init__.py" and path.dirpath() == this_path: | ||||
|                 continue | ||||
|             if pkg_prefix and pkg_prefix in path.parts(): | ||||
|                 continue | ||||
|             for x in self._collectfile(path): | ||||
|  | @ -880,12 +878,13 @@ class Metafunc(fixtures.FuncargnamesCompatAttr): | |||
|     """ | ||||
| 
 | ||||
|     def __init__(self, definition, fixtureinfo, config, cls=None, module=None): | ||||
|         #: access to the :class:`_pytest.config.Config` object for the test session | ||||
|         assert ( | ||||
|             isinstance(definition, FunctionDefinition) | ||||
|             or type(definition).__name__ == "DefinitionMock" | ||||
|         ) | ||||
|         self.definition = definition | ||||
| 
 | ||||
|         #: access to the :class:`_pytest.config.Config` object for the test session | ||||
|         self.config = config | ||||
| 
 | ||||
|         #: the module object where the test function is defined in. | ||||
|  |  | |||
|  | @ -69,6 +69,7 @@ class UnitTestCase(Class): | |||
| class TestCaseFunction(Function): | ||||
|     nofuncargs = True | ||||
|     _excinfo = None | ||||
|     _testcase = None | ||||
| 
 | ||||
|     def setup(self): | ||||
|         self._testcase = self.parent.obj(self.name) | ||||
|  |  | |||
|  | @ -49,6 +49,14 @@ def pytest_addoption(parser): | |||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| def pytest_configure(config): | ||||
|     config.addinivalue_line( | ||||
|         "markers", | ||||
|         "filterwarnings(warning): add a warning filter to the given test. " | ||||
|         "see http://pytest.org/latest/warnings.html#pytest-mark-filterwarnings ", | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| @contextmanager | ||||
| def catch_warnings_for_item(item): | ||||
|     """ | ||||
|  |  | |||
|  | @ -1044,3 +1044,10 @@ def test_frame_leak_on_failing_test(testdir): | |||
|     ) | ||||
|     result = testdir.runpytest_subprocess() | ||||
|     result.stdout.fnmatch_lines(["*1 failed, 1 passed in*"]) | ||||
| 
 | ||||
| 
 | ||||
| def test_fixture_mock_integration(testdir): | ||||
|     """Test that decorators applied to fixture are left working (#3774)""" | ||||
|     p = testdir.copy_example("acceptance/fixture_mock_integration.py") | ||||
|     result = testdir.runpytest(p) | ||||
|     result.stdout.fnmatch_lines("*1 passed*") | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ from __future__ import absolute_import, division, print_function | |||
| import operator | ||||
| import os | ||||
| import sys | ||||
| import textwrap | ||||
| import _pytest | ||||
| import py | ||||
| import pytest | ||||
|  | @ -1265,6 +1266,50 @@ raise ValueError() | |||
|             ] | ||||
|         ) | ||||
| 
 | ||||
|     @pytest.mark.skipif("sys.version_info[0] < 3") | ||||
|     def test_exc_chain_repr_cycle(self, importasmod): | ||||
|         mod = importasmod( | ||||
|             """ | ||||
|             class Err(Exception): | ||||
|                 pass | ||||
|             def fail(): | ||||
|                 return 0 / 0 | ||||
|             def reraise(): | ||||
|                 try: | ||||
|                     fail() | ||||
|                 except ZeroDivisionError as e: | ||||
|                     raise Err() from e | ||||
|             def unreraise(): | ||||
|                 try: | ||||
|                     reraise() | ||||
|                 except Err as e: | ||||
|                     raise e.__cause__ | ||||
|         """ | ||||
|         ) | ||||
|         excinfo = pytest.raises(ZeroDivisionError, mod.unreraise) | ||||
|         r = excinfo.getrepr(style="short") | ||||
|         tw = TWMock() | ||||
|         r.toterminal(tw) | ||||
|         out = "\n".join(line for line in tw.lines if isinstance(line, str)) | ||||
|         expected_out = textwrap.dedent( | ||||
|             """\ | ||||
|             :13: in unreraise | ||||
|                 reraise() | ||||
|             :10: in reraise | ||||
|                 raise Err() from e | ||||
|             E   test_exc_chain_repr_cycle0.mod.Err | ||||
| 
 | ||||
|             During handling of the above exception, another exception occurred: | ||||
|             :15: in unreraise | ||||
|                 raise e.__cause__ | ||||
|             :8: in reraise | ||||
|                 fail() | ||||
|             :5: in fail | ||||
|                 return 0 / 0 | ||||
|             E   ZeroDivisionError: division by zero""" | ||||
|         ) | ||||
|         assert out == expected_out | ||||
| 
 | ||||
| 
 | ||||
| @pytest.mark.parametrize("style", ["short", "long"]) | ||||
| @pytest.mark.parametrize("encoding", [None, "utf8", "utf16"]) | ||||
|  |  | |||
|  | @ -0,0 +1,17 @@ | |||
| """Reproduces issue #3774""" | ||||
| 
 | ||||
| import mock | ||||
| 
 | ||||
| import pytest | ||||
| 
 | ||||
| config = {"mykey": "ORIGINAL"} | ||||
| 
 | ||||
| 
 | ||||
| @pytest.fixture(scope="function") | ||||
| @mock.patch.dict(config, {"mykey": "MOCKED"}) | ||||
| def my_fixture(): | ||||
|     return config["mykey"] | ||||
| 
 | ||||
| 
 | ||||
| def test_foobar(my_fixture): | ||||
|     assert my_fixture == "MOCKED" | ||||
|  | @ -0,0 +1,2 @@ | |||
| def pytest_ignore_collect(path): | ||||
|     return False | ||||
|  | @ -0,0 +1,2 @@ | |||
| def test(): | ||||
|     pass | ||||
|  | @ -0,0 +1,2 @@ | |||
| class pytest_something(object): | ||||
|     pass | ||||
|  | @ -0,0 +1,2 @@ | |||
| def test_foo(): | ||||
|     pass | ||||
|  | @ -876,6 +876,7 @@ def test_live_logging_suspends_capture(has_capture_manager, request): | |||
|     is installed. | ||||
|     """ | ||||
|     import logging | ||||
|     import contextlib | ||||
|     from functools import partial | ||||
|     from _pytest.capture import CaptureManager | ||||
|     from _pytest.logging import _LiveLoggingStreamHandler | ||||
|  | @ -883,11 +884,11 @@ def test_live_logging_suspends_capture(has_capture_manager, request): | |||
|     class MockCaptureManager: | ||||
|         calls = [] | ||||
| 
 | ||||
|         def suspend_global_capture(self): | ||||
|             self.calls.append("suspend_global_capture") | ||||
| 
 | ||||
|         def resume_global_capture(self): | ||||
|             self.calls.append("resume_global_capture") | ||||
|         @contextlib.contextmanager | ||||
|         def global_and_fixture_disabled(self): | ||||
|             self.calls.append("enter disabled") | ||||
|             yield | ||||
|             self.calls.append("exit disabled") | ||||
| 
 | ||||
|     # sanity check | ||||
|     assert CaptureManager.suspend_capture_item | ||||
|  | @ -908,10 +909,7 @@ def test_live_logging_suspends_capture(has_capture_manager, request): | |||
| 
 | ||||
|     logger.critical("some message") | ||||
|     if has_capture_manager: | ||||
|         assert MockCaptureManager.calls == [ | ||||
|             "suspend_global_capture", | ||||
|             "resume_global_capture", | ||||
|         ] | ||||
|         assert MockCaptureManager.calls == ["enter disabled", "exit disabled"] | ||||
|     else: | ||||
|         assert MockCaptureManager.calls == [] | ||||
|     assert out_file.getvalue() == "\nsome message\n" | ||||
|  |  | |||
|  | @ -1577,3 +1577,49 @@ def test_keep_duplicates(testdir): | |||
|     ) | ||||
|     result = testdir.runpytest("--keep-duplicates", a.strpath, a.strpath) | ||||
|     result.stdout.fnmatch_lines(["*collected 2 item*"]) | ||||
| 
 | ||||
| 
 | ||||
| def test_package_collection_infinite_recursion(testdir): | ||||
|     testdir.copy_example("collect/package_infinite_recursion") | ||||
|     result = testdir.runpytest() | ||||
|     result.stdout.fnmatch_lines("*1 passed*") | ||||
| 
 | ||||
| 
 | ||||
| def test_package_with_modules(testdir): | ||||
|     """ | ||||
|     . | ||||
|     └── root | ||||
|         ├── __init__.py | ||||
|         ├── sub1 | ||||
|         │   ├── __init__.py | ||||
|         │   └── sub1_1 | ||||
|         │       ├── __init__.py | ||||
|         │       └── test_in_sub1.py | ||||
|         └── sub2 | ||||
|             └── test | ||||
|                 └── test_in_sub2.py | ||||
| 
 | ||||
|     """ | ||||
|     root = testdir.mkpydir("root") | ||||
|     sub1 = root.mkdir("sub1") | ||||
|     sub1.ensure("__init__.py") | ||||
|     sub1_test = sub1.mkdir("sub1_1") | ||||
|     sub1_test.ensure("__init__.py") | ||||
|     sub2 = root.mkdir("sub2") | ||||
|     sub2_test = sub2.mkdir("sub2") | ||||
| 
 | ||||
|     sub1_test.join("test_in_sub1.py").write("def test_1(): pass") | ||||
|     sub2_test.join("test_in_sub2.py").write("def test_2(): pass") | ||||
| 
 | ||||
|     # Execute from . | ||||
|     result = testdir.runpytest("-v", "-s") | ||||
|     result.assert_outcomes(passed=2) | ||||
| 
 | ||||
|     # Execute from . with one argument "root" | ||||
|     result = testdir.runpytest("-v", "-s", "root") | ||||
|     result.assert_outcomes(passed=2) | ||||
| 
 | ||||
|     # Chdir into package's root and execute with no args | ||||
|     root.chdir() | ||||
|     result = testdir.runpytest("-v", "-s") | ||||
|     result.assert_outcomes(passed=2) | ||||
|  |  | |||
|  | @ -1385,3 +1385,34 @@ def test_pickling_and_unpickling_encoded_file(): | |||
|     ef = capture.EncodedFile(None, None) | ||||
|     ef_as_str = pickle.dumps(ef) | ||||
|     pickle.loads(ef_as_str) | ||||
| 
 | ||||
| 
 | ||||
| def test_capsys_with_cli_logging(testdir): | ||||
|     # Issue 3819 | ||||
|     # capsys should work with real-time cli logging | ||||
|     testdir.makepyfile( | ||||
|         """ | ||||
|         import logging | ||||
|         import sys | ||||
| 
 | ||||
|         logger = logging.getLogger(__name__) | ||||
| 
 | ||||
|         def test_myoutput(capsys):  # or use "capfd" for fd-level | ||||
|             print("hello") | ||||
|             sys.stderr.write("world\\n") | ||||
|             captured = capsys.readouterr() | ||||
|             assert captured.out == "hello\\n" | ||||
|             assert captured.err == "world\\n" | ||||
| 
 | ||||
|             logging.info("something") | ||||
| 
 | ||||
|             print("next") | ||||
| 
 | ||||
|             logging.info("something") | ||||
| 
 | ||||
|             captured = capsys.readouterr() | ||||
|             assert captured.out == "next\\n" | ||||
|         """ | ||||
|     ) | ||||
|     result = testdir.runpytest_subprocess("--log-cli-level=INFO") | ||||
|     assert result.ret == 0 | ||||
|  |  | |||
|  | @ -638,6 +638,10 @@ class Test_getinitialnodes(object): | |||
|             assert col.config is config | ||||
| 
 | ||||
|     def test_pkgfile(self, testdir): | ||||
|         """Verify nesting when a module is within a package. | ||||
|         The parent chain should match: Module<x.py> -> Package<subdir> -> Session. | ||||
|             Session's parent should always be None. | ||||
|         """ | ||||
|         tmpdir = testdir.tmpdir | ||||
|         subdir = tmpdir.join("subdir") | ||||
|         x = subdir.ensure("x.py") | ||||
|  | @ -645,9 +649,12 @@ class Test_getinitialnodes(object): | |||
|         with subdir.as_cwd(): | ||||
|             config = testdir.parseconfigure(x) | ||||
|         col = testdir.getnode(config, x) | ||||
|         assert isinstance(col, pytest.Module) | ||||
|         assert col.name == "x.py" | ||||
|         assert col.parent.parent is None | ||||
|         assert isinstance(col, pytest.Module) | ||||
|         assert isinstance(col.parent, pytest.Package) | ||||
|         assert isinstance(col.parent.parent, pytest.Session) | ||||
|         # session is batman (has no parents) | ||||
|         assert col.parent.parent.parent is None | ||||
|         for col in col.listchain(): | ||||
|             assert col.config is config | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,8 +1,11 @@ | |||
| from __future__ import absolute_import, division, print_function | ||||
| import sys | ||||
| from functools import wraps | ||||
| 
 | ||||
| import six | ||||
| 
 | ||||
| import pytest | ||||
| from _pytest.compat import is_generator, get_real_func, safe_getattr | ||||
| from _pytest.compat import is_generator, get_real_func, safe_getattr, _PytestWrapper | ||||
| from _pytest.outcomes import OutcomeException | ||||
| 
 | ||||
| 
 | ||||
|  | @ -38,6 +41,33 @@ def test_real_func_loop_limit(): | |||
|         print(res) | ||||
| 
 | ||||
| 
 | ||||
| def test_get_real_func(): | ||||
|     """Check that get_real_func correctly unwraps decorators until reaching the real function""" | ||||
| 
 | ||||
|     def decorator(f): | ||||
|         @wraps(f) | ||||
|         def inner(): | ||||
|             pass | ||||
| 
 | ||||
|         if six.PY2: | ||||
|             inner.__wrapped__ = f | ||||
|         return inner | ||||
| 
 | ||||
|     def func(): | ||||
|         pass | ||||
| 
 | ||||
|     wrapped_func = decorator(decorator(func)) | ||||
|     assert get_real_func(wrapped_func) is func | ||||
| 
 | ||||
|     wrapped_func2 = decorator(decorator(wrapped_func)) | ||||
|     assert get_real_func(wrapped_func2) is func | ||||
| 
 | ||||
|     # special case for __pytest_wrapped__ attribute: used to obtain the function up until the point | ||||
|     # a function was wrapped by pytest itself | ||||
|     wrapped_func2.__pytest_wrapped__ = _PytestWrapper(wrapped_func) | ||||
|     assert get_real_func(wrapped_func2) is wrapped_func | ||||
| 
 | ||||
| 
 | ||||
| @pytest.mark.skipif( | ||||
|     sys.version_info < (3, 4), reason="asyncio available in Python 3.4+" | ||||
| ) | ||||
|  |  | |||
|  | @ -765,6 +765,24 @@ def test_get_plugin_specs_as_list(): | |||
|     assert _get_plugin_specs_as_list(("foo", "bar")) == ["foo", "bar"] | ||||
| 
 | ||||
| 
 | ||||
| def test_collect_pytest_prefix_bug_integration(testdir): | ||||
|     """Integration test for issue #3775""" | ||||
|     p = testdir.copy_example("config/collect_pytest_prefix") | ||||
|     result = testdir.runpytest(p) | ||||
|     result.stdout.fnmatch_lines("* 1 passed *") | ||||
| 
 | ||||
| 
 | ||||
| def test_collect_pytest_prefix_bug(pytestconfig): | ||||
|     """Ensure we collect only actual functions from conftest files (#3775)""" | ||||
| 
 | ||||
|     class Dummy(object): | ||||
|         class pytest_something(object): | ||||
|             pass | ||||
| 
 | ||||
|     pm = pytestconfig.pluginmanager | ||||
|     assert pm.parse_hookimpl_opts(Dummy(), "pytest_something") is None | ||||
| 
 | ||||
| 
 | ||||
| class TestWarning(object): | ||||
|     def test_warn_config(self, testdir): | ||||
|         testdir.makeconftest( | ||||
|  |  | |||
|  | @ -989,3 +989,24 @@ def test_usefixtures_marker_on_unittest(base, testdir): | |||
| 
 | ||||
|     result = testdir.runpytest("-s") | ||||
|     result.assert_outcomes(passed=2) | ||||
| 
 | ||||
| 
 | ||||
| def test_testcase_handles_init_exceptions(testdir): | ||||
|     """ | ||||
|     Regression test to make sure exceptions in the __init__ method are bubbled up correctly. | ||||
|     See https://github.com/pytest-dev/pytest/issues/3788 | ||||
|     """ | ||||
|     testdir.makepyfile( | ||||
|         """ | ||||
|         from unittest import TestCase | ||||
|         import pytest | ||||
|         class MyTestCase(TestCase): | ||||
|             def __init__(self, *args, **kwargs): | ||||
|                 raise Exception("should raise this exception") | ||||
|             def test_hello(self): | ||||
|                 pass | ||||
|     """ | ||||
|     ) | ||||
|     result = testdir.runpytest() | ||||
|     assert "should raise this exception" in result.stdout.str() | ||||
|     assert "ERROR at teardown of MyTestCase.test_hello" not in result.stdout.str() | ||||
|  |  | |||
|  | @ -287,3 +287,18 @@ def test_non_string_warning_argument(testdir): | |||
|     ) | ||||
|     result = testdir.runpytest("-W", "always") | ||||
|     result.stdout.fnmatch_lines(["*= 1 passed, 1 warnings in *"]) | ||||
| 
 | ||||
| 
 | ||||
| def test_filterwarnings_mark_registration(testdir): | ||||
|     """Ensure filterwarnings mark is registered""" | ||||
|     testdir.makepyfile( | ||||
|         """ | ||||
|         import pytest | ||||
| 
 | ||||
|         @pytest.mark.filterwarnings('error') | ||||
|         def test_func(): | ||||
|             pass | ||||
|     """ | ||||
|     ) | ||||
|     result = testdir.runpytest("--strict") | ||||
|     assert result.ret == 0 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue