commit
						24fbbbef1f
					
				|  | @ -143,6 +143,9 @@ time or change existing behaviors in order to make them less surprising/more use | ||||||
| 
 | 
 | ||||||
| **Changes** | **Changes** | ||||||
| 
 | 
 | ||||||
|  | * Plugins now benefit from assertion rewriting.  Thanks | ||||||
|  |   `@sober7`_, `@nicoddemus`_ and `@flub`_ for the PR. | ||||||
|  | 
 | ||||||
| * Fixtures marked with ``@pytest.fixture`` can now use ``yield`` statements exactly like | * Fixtures marked with ``@pytest.fixture`` can now use ``yield`` statements exactly like | ||||||
|   those marked with the ``@pytest.yield_fixture`` decorator. This change renders |   those marked with the ``@pytest.yield_fixture`` decorator. This change renders | ||||||
|   ``@pytest.yield_fixture`` deprecated and makes ``@pytest.fixture`` with ``yield`` statements |   ``@pytest.yield_fixture`` deprecated and makes ``@pytest.fixture`` with ``yield`` statements | ||||||
|  |  | ||||||
|  | @ -5,9 +5,8 @@ import py | ||||||
| import os | import os | ||||||
| import sys | import sys | ||||||
| 
 | 
 | ||||||
| from _pytest.config import hookimpl |  | ||||||
| from _pytest.monkeypatch import MonkeyPatch |  | ||||||
| from _pytest.assertion import util | from _pytest.assertion import util | ||||||
|  | from _pytest.assertion import rewrite | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def pytest_addoption(parser): | def pytest_addoption(parser): | ||||||
|  | @ -27,6 +26,34 @@ def pytest_addoption(parser): | ||||||
|                             provide assert expression information. """) |                             provide assert expression information. """) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def pytest_namespace(): | ||||||
|  |     return {'register_assert_rewrite': register_assert_rewrite} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def register_assert_rewrite(*names): | ||||||
|  |     """Register a module name to be rewritten on import. | ||||||
|  | 
 | ||||||
|  |     This function will make sure that the module will get it's assert | ||||||
|  |     statements rewritten when it is imported.  Thus you should make | ||||||
|  |     sure to call this before the module is actually imported, usually | ||||||
|  |     in your __init__.py if you are a plugin using a package. | ||||||
|  |     """ | ||||||
|  |     for hook in sys.meta_path: | ||||||
|  |         if isinstance(hook, rewrite.AssertionRewritingHook): | ||||||
|  |             importhook = hook | ||||||
|  |             break | ||||||
|  |     else: | ||||||
|  |         importhook = DummyRewriteHook() | ||||||
|  |     importhook.mark_rewrite(*names) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class DummyRewriteHook(object): | ||||||
|  |     """A no-op import hook for when rewriting is disabled.""" | ||||||
|  | 
 | ||||||
|  |     def mark_rewrite(self, *names): | ||||||
|  |         pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class AssertionState: | class AssertionState: | ||||||
|     """State for the assertion plugin.""" |     """State for the assertion plugin.""" | ||||||
| 
 | 
 | ||||||
|  | @ -35,10 +62,7 @@ class AssertionState: | ||||||
|         self.trace = config.trace.root.get("assertion") |         self.trace = config.trace.root.get("assertion") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @hookimpl(tryfirst=True) | def install_importhook(config, mode): | ||||||
| def pytest_load_initial_conftests(early_config, parser, args): |  | ||||||
|     ns, ns_unknown_args = parser.parse_known_and_unknown_args(args) |  | ||||||
|     mode = ns.assertmode |  | ||||||
|     if mode == "rewrite": |     if mode == "rewrite": | ||||||
|         try: |         try: | ||||||
|             import ast  # noqa |             import ast  # noqa | ||||||
|  | @ -51,37 +75,38 @@ def pytest_load_initial_conftests(early_config, parser, args): | ||||||
|                     sys.version_info[:3] == (2, 6, 0)): |                     sys.version_info[:3] == (2, 6, 0)): | ||||||
|                 mode = "reinterp" |                 mode = "reinterp" | ||||||
| 
 | 
 | ||||||
|     early_config._assertstate = AssertionState(early_config, mode) |     config._assertstate = AssertionState(config, mode) | ||||||
|     warn_about_missing_assertion(mode, early_config.pluginmanager) |  | ||||||
| 
 | 
 | ||||||
|     if mode != "plain": |     _load_modules(mode) | ||||||
|         _load_modules(mode) |     from _pytest.monkeypatch import MonkeyPatch | ||||||
|         m = MonkeyPatch() |     m = MonkeyPatch() | ||||||
|         early_config._cleanup.append(m.undo) |     config._cleanup.append(m.undo) | ||||||
|         m.setattr(py.builtin.builtins, 'AssertionError', |     m.setattr(py.builtin.builtins, 'AssertionError', | ||||||
|                   reinterpret.AssertionError)  # noqa |               reinterpret.AssertionError)  # noqa | ||||||
| 
 | 
 | ||||||
|     hook = None |     hook = None | ||||||
|     if mode == "rewrite": |     if mode == "rewrite": | ||||||
|         hook = rewrite.AssertionRewritingHook(early_config)  # noqa |         hook = rewrite.AssertionRewritingHook(config)  # noqa | ||||||
|         sys.meta_path.insert(0, hook) |         sys.meta_path.insert(0, hook) | ||||||
| 
 | 
 | ||||||
|     early_config._assertstate.hook = hook |     config._assertstate.hook = hook | ||||||
|     early_config._assertstate.trace("configured with mode set to %r" % (mode,)) |     config._assertstate.trace("configured with mode set to %r" % (mode,)) | ||||||
|     def undo(): |     def undo(): | ||||||
|         hook = early_config._assertstate.hook |         hook = config._assertstate.hook | ||||||
|         if hook is not None and hook in sys.meta_path: |         if hook is not None and hook in sys.meta_path: | ||||||
|             sys.meta_path.remove(hook) |             sys.meta_path.remove(hook) | ||||||
|     early_config.add_cleanup(undo) |     config.add_cleanup(undo) | ||||||
|  |     return hook | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def pytest_collection(session): | def pytest_collection(session): | ||||||
|     # this hook is only called when test modules are collected |     # this hook is only called when test modules are collected | ||||||
|     # so for example not in the master process of pytest-xdist |     # so for example not in the master process of pytest-xdist | ||||||
|     # (which does not collect test modules) |     # (which does not collect test modules) | ||||||
|     hook = session.config._assertstate.hook |     assertstate = getattr(session.config, '_assertstate', None) | ||||||
|     if hook is not None: |     if assertstate: | ||||||
|         hook.set_session(session) |         if assertstate.hook is not None: | ||||||
|  |             assertstate.hook.set_session(session) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def _running_on_ci(): | def _running_on_ci(): | ||||||
|  | @ -138,9 +163,10 @@ def pytest_runtest_teardown(item): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def pytest_sessionfinish(session): | def pytest_sessionfinish(session): | ||||||
|     hook = session.config._assertstate.hook |     assertstate = getattr(session.config, '_assertstate', None) | ||||||
|     if hook is not None: |     if assertstate: | ||||||
|         hook.session = None |         if assertstate.hook is not None: | ||||||
|  |             assertstate.hook.set_session(None) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def _load_modules(mode): | def _load_modules(mode): | ||||||
|  | @ -151,31 +177,5 @@ def _load_modules(mode): | ||||||
|         from _pytest.assertion import rewrite  # noqa |         from _pytest.assertion import rewrite  # noqa | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def warn_about_missing_assertion(mode, pluginmanager): |  | ||||||
|     try: |  | ||||||
|         assert False |  | ||||||
|     except AssertionError: |  | ||||||
|         pass |  | ||||||
|     else: |  | ||||||
|         if mode == "rewrite": |  | ||||||
|             specifically = ("assertions which are not in test modules " |  | ||||||
|                             "will be ignored") |  | ||||||
|         else: |  | ||||||
|             specifically = "failing tests may report as passing" |  | ||||||
| 
 |  | ||||||
|         # temporarily disable capture so we can print our warning |  | ||||||
|         capman = pluginmanager.getplugin('capturemanager') |  | ||||||
|         try: |  | ||||||
|             out, err = capman.suspendcapture() |  | ||||||
|             sys.stderr.write("WARNING: " + specifically + |  | ||||||
|                              " because assert statements are not executed " |  | ||||||
|                              "by the underlying Python interpreter " |  | ||||||
|                              "(are you using python -O?)\n") |  | ||||||
|         finally: |  | ||||||
|             capman.resumecapture() |  | ||||||
|             sys.stdout.write(out) |  | ||||||
|             sys.stderr.write(err) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| # Expose this plugin's implementation for the pytest_assertrepr_compare hook | # Expose this plugin's implementation for the pytest_assertrepr_compare hook | ||||||
| pytest_assertrepr_compare = util.assertrepr_compare | pytest_assertrepr_compare = util.assertrepr_compare | ||||||
|  |  | ||||||
|  | @ -51,6 +51,7 @@ class AssertionRewritingHook(object): | ||||||
|         self.session = None |         self.session = None | ||||||
|         self.modules = {} |         self.modules = {} | ||||||
|         self._register_with_pkg_resources() |         self._register_with_pkg_resources() | ||||||
|  |         self._must_rewrite = set() | ||||||
| 
 | 
 | ||||||
|     def set_session(self, session): |     def set_session(self, session): | ||||||
|         self.session = session |         self.session = session | ||||||
|  | @ -87,7 +88,7 @@ class AssertionRewritingHook(object): | ||||||
|             fn = os.path.join(pth, name.rpartition(".")[2] + ".py") |             fn = os.path.join(pth, name.rpartition(".")[2] + ".py") | ||||||
| 
 | 
 | ||||||
|         fn_pypath = py.path.local(fn) |         fn_pypath = py.path.local(fn) | ||||||
|         if not self._should_rewrite(fn_pypath, state): |         if not self._should_rewrite(name, fn_pypath, state): | ||||||
|             return None |             return None | ||||||
| 
 | 
 | ||||||
|         # The requested module looks like a test file, so rewrite it. This is |         # The requested module looks like a test file, so rewrite it. This is | ||||||
|  | @ -137,7 +138,7 @@ class AssertionRewritingHook(object): | ||||||
|         self.modules[name] = co, pyc |         self.modules[name] = co, pyc | ||||||
|         return self |         return self | ||||||
| 
 | 
 | ||||||
|     def _should_rewrite(self, fn_pypath, state): |     def _should_rewrite(self, name, fn_pypath, state): | ||||||
|         # always rewrite conftest files |         # always rewrite conftest files | ||||||
|         fn = str(fn_pypath) |         fn = str(fn_pypath) | ||||||
|         if fn_pypath.basename == 'conftest.py': |         if fn_pypath.basename == 'conftest.py': | ||||||
|  | @ -161,8 +162,29 @@ class AssertionRewritingHook(object): | ||||||
|                 finally: |                 finally: | ||||||
|                     self.session = session |                     self.session = session | ||||||
|                     del session |                     del session | ||||||
|  |         else: | ||||||
|  |             for marked in self._must_rewrite: | ||||||
|  |                 if marked.startswith(name): | ||||||
|  |                     return True | ||||||
|         return False |         return False | ||||||
| 
 | 
 | ||||||
|  |     def mark_rewrite(self, *names): | ||||||
|  |         """Mark import names as needing to be re-written. | ||||||
|  | 
 | ||||||
|  |         The named module or package as well as any nested modules will | ||||||
|  |         be re-written on import. | ||||||
|  |         """ | ||||||
|  |         already_imported = set(names).intersection(set(sys.modules)) | ||||||
|  |         if already_imported: | ||||||
|  |             self._warn_already_imported(already_imported) | ||||||
|  |         self._must_rewrite.update(names) | ||||||
|  | 
 | ||||||
|  |     def _warn_already_imported(self, names): | ||||||
|  |         self.config.warn( | ||||||
|  |             'P1', | ||||||
|  |             'Modules are already imported so can not be re-written: %s' % | ||||||
|  |             ','.join(names)) | ||||||
|  | 
 | ||||||
|     def load_module(self, name): |     def load_module(self, name): | ||||||
|         # If there is an existing module object named 'fullname' in |         # If there is an existing module object named 'fullname' in | ||||||
|         # sys.modules, the loader must use that existing module. (Otherwise, |         # sys.modules, the loader must use that existing module. (Otherwise, | ||||||
|  |  | ||||||
|  | @ -5,11 +5,13 @@ import traceback | ||||||
| import types | import types | ||||||
| import warnings | import warnings | ||||||
| 
 | 
 | ||||||
|  | import pkg_resources | ||||||
| import py | import py | ||||||
| # DON't import pytest here because it causes import cycle troubles | # DON't import pytest here because it causes import cycle troubles | ||||||
| import sys, os | import sys, os | ||||||
| import _pytest._code | import _pytest._code | ||||||
| import _pytest.hookspec  # the extension point definitions | import _pytest.hookspec  # the extension point definitions | ||||||
|  | import _pytest.assertion | ||||||
| from _pytest._pluggy import PluginManager, HookimplMarker, HookspecMarker | from _pytest._pluggy import PluginManager, HookimplMarker, HookspecMarker | ||||||
| 
 | 
 | ||||||
| hookimpl = HookimplMarker("pytest") | hookimpl = HookimplMarker("pytest") | ||||||
|  | @ -160,6 +162,9 @@ class PytestPluginManager(PluginManager): | ||||||
|             self.trace.root.setwriter(err.write) |             self.trace.root.setwriter(err.write) | ||||||
|             self.enable_tracing() |             self.enable_tracing() | ||||||
| 
 | 
 | ||||||
|  |         # Config._consider_importhook will set a real object if required. | ||||||
|  |         self.rewrite_hook = _pytest.assertion.DummyRewriteHook() | ||||||
|  | 
 | ||||||
|     def addhooks(self, module_or_class): |     def addhooks(self, module_or_class): | ||||||
|         """ |         """ | ||||||
|         .. deprecated:: 2.8 |         .. deprecated:: 2.8 | ||||||
|  | @ -368,7 +373,9 @@ class PytestPluginManager(PluginManager): | ||||||
|         self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS")) |         self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS")) | ||||||
| 
 | 
 | ||||||
|     def consider_module(self, mod): |     def consider_module(self, mod): | ||||||
|         self._import_plugin_specs(getattr(mod, "pytest_plugins", None)) |         plugins = getattr(mod, 'pytest_plugins', []) | ||||||
|  |         self.rewrite_hook.mark_rewrite(*plugins) | ||||||
|  |         self._import_plugin_specs(plugins) | ||||||
| 
 | 
 | ||||||
|     def _import_plugin_specs(self, spec): |     def _import_plugin_specs(self, spec): | ||||||
|         if spec: |         if spec: | ||||||
|  | @ -925,14 +932,58 @@ class Config(object): | ||||||
|         self._parser.addini('addopts', 'extra command line options', 'args') |         self._parser.addini('addopts', 'extra command line options', 'args') | ||||||
|         self._parser.addini('minversion', 'minimally required pytest version') |         self._parser.addini('minversion', 'minimally required pytest version') | ||||||
| 
 | 
 | ||||||
|  |     def _consider_importhook(self, args, entrypoint_name): | ||||||
|  |         """Install the PEP 302 import hook if using assertion re-writing. | ||||||
|  | 
 | ||||||
|  |         Needs to parse the --assert=<mode> option from the commandline | ||||||
|  |         and find all the installed plugins to mark them for re-writing | ||||||
|  |         by the importhook. | ||||||
|  |         """ | ||||||
|  |         ns, unknown_args = self._parser.parse_known_and_unknown_args(args) | ||||||
|  |         mode = ns.assertmode | ||||||
|  |         self._warn_about_missing_assertion(mode) | ||||||
|  |         if mode != 'plain': | ||||||
|  |             hook = _pytest.assertion.install_importhook(self, mode) | ||||||
|  |             if hook: | ||||||
|  |                 self.pluginmanager.rewrite_hook = hook | ||||||
|  |                 for entrypoint in pkg_resources.iter_entry_points('pytest11'): | ||||||
|  |                     for entry in entrypoint.dist._get_metadata('RECORD'): | ||||||
|  |                         fn = entry.split(',')[0] | ||||||
|  |                         is_simple_module = os.sep not in fn and fn.endswith('.py') | ||||||
|  |                         is_package = fn.count(os.sep) == 1 and fn.endswith('__init__.py') | ||||||
|  |                         if is_simple_module: | ||||||
|  |                             module_name, ext = os.path.splitext(fn) | ||||||
|  |                             hook.mark_rewrite(module_name) | ||||||
|  |                         elif is_package: | ||||||
|  |                             package_name = os.path.dirname(fn) | ||||||
|  |                             hook.mark_rewrite(package_name) | ||||||
|  | 
 | ||||||
|  |     def _warn_about_missing_assertion(self, mode): | ||||||
|  |         try: | ||||||
|  |             assert False | ||||||
|  |         except AssertionError: | ||||||
|  |             pass | ||||||
|  |         else: | ||||||
|  |             if mode == "rewrite": | ||||||
|  |                 specifically = ("assertions not in test modules or plugins" | ||||||
|  |                                 "will be ignored") | ||||||
|  |             else: | ||||||
|  |                 specifically = "failing tests may report as passing" | ||||||
|  |             sys.stderr.write("WARNING: " + specifically + | ||||||
|  |                              " because assert statements are not executed " | ||||||
|  |                              "by the underlying Python interpreter " | ||||||
|  |                              "(are you using python -O?)\n") | ||||||
|  | 
 | ||||||
|     def _preparse(self, args, addopts=True): |     def _preparse(self, args, addopts=True): | ||||||
|         self._initini(args) |         self._initini(args) | ||||||
|         if addopts: |         if addopts: | ||||||
|             args[:] = shlex.split(os.environ.get('PYTEST_ADDOPTS', '')) + args |             args[:] = shlex.split(os.environ.get('PYTEST_ADDOPTS', '')) + args | ||||||
|             args[:] = self.getini("addopts") + args |             args[:] = self.getini("addopts") + args | ||||||
|         self._checkversion() |         self._checkversion() | ||||||
|  |         entrypoint_name = 'pytest11' | ||||||
|  |         self._consider_importhook(args, entrypoint_name) | ||||||
|         self.pluginmanager.consider_preparse(args) |         self.pluginmanager.consider_preparse(args) | ||||||
|         self.pluginmanager.load_setuptools_entrypoints("pytest11") |         self.pluginmanager.load_setuptools_entrypoints(entrypoint_name) | ||||||
|         self.pluginmanager.consider_env() |         self.pluginmanager.consider_env() | ||||||
|         self.known_args_namespace = ns = self._parser.parse_known_args(args, namespace=self.option.copy()) |         self.known_args_namespace = ns = self._parser.parse_known_args(args, namespace=self.option.copy()) | ||||||
|         if self.known_args_namespace.confcutdir is None and self.inifile: |         if self.known_args_namespace.confcutdir is None and self.inifile: | ||||||
|  |  | ||||||
|  | @ -16,6 +16,7 @@ from _pytest._code import Source | ||||||
| import py | import py | ||||||
| import pytest | import pytest | ||||||
| from _pytest.main import Session, EXIT_OK | from _pytest.main import Session, EXIT_OK | ||||||
|  | from _pytest.assertion.rewrite import AssertionRewritingHook | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def pytest_addoption(parser): | def pytest_addoption(parser): | ||||||
|  | @ -685,8 +686,17 @@ class Testdir: | ||||||
|            ``pytest.main()`` instance should use. |            ``pytest.main()`` instance should use. | ||||||
| 
 | 
 | ||||||
|         :return: A :py:class:`HookRecorder` instance. |         :return: A :py:class:`HookRecorder` instance. | ||||||
| 
 |  | ||||||
|         """ |         """ | ||||||
|  |         # When running py.test inline any plugins active in the main | ||||||
|  |         # test process are already imported.  So this disables the | ||||||
|  |         # warning which will trigger to say they can no longer be | ||||||
|  |         # re-written, which is fine as they are already re-written. | ||||||
|  |         orig_warn = AssertionRewritingHook._warn_already_imported | ||||||
|  |         def revert(): | ||||||
|  |             AssertionRewritingHook._warn_already_imported = orig_warn | ||||||
|  |         self.request.addfinalizer(revert) | ||||||
|  |         AssertionRewritingHook._warn_already_imported = lambda *a: None | ||||||
|  | 
 | ||||||
|         rec = [] |         rec = [] | ||||||
|         class Collect: |         class Collect: | ||||||
|             def pytest_configure(x, config): |             def pytest_configure(x, config): | ||||||
|  |  | ||||||
|  | @ -26,6 +26,189 @@ def mock_config(): | ||||||
| def interpret(expr): | def interpret(expr): | ||||||
|     return reinterpret.reinterpret(expr, _pytest._code.Frame(sys._getframe(1))) |     return reinterpret.reinterpret(expr, _pytest._code.Frame(sys._getframe(1))) | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | class TestImportHookInstallation: | ||||||
|  | 
 | ||||||
|  |     @pytest.mark.parametrize('initial_conftest', [True, False]) | ||||||
|  |     @pytest.mark.parametrize('mode', ['plain', 'rewrite', 'reinterp']) | ||||||
|  |     def test_conftest_assertion_rewrite(self, testdir, initial_conftest, mode): | ||||||
|  |         """Test that conftest files are using assertion rewrite on import. | ||||||
|  |         (#1619) | ||||||
|  |         """ | ||||||
|  |         testdir.tmpdir.join('foo/tests').ensure(dir=1) | ||||||
|  |         conftest_path = 'conftest.py' if initial_conftest else 'foo/conftest.py' | ||||||
|  |         contents = { | ||||||
|  |             conftest_path: """ | ||||||
|  |                 import pytest | ||||||
|  |                 @pytest.fixture | ||||||
|  |                 def check_first(): | ||||||
|  |                     def check(values, value): | ||||||
|  |                         assert values.pop(0) == value | ||||||
|  |                     return check | ||||||
|  |             """, | ||||||
|  |             'foo/tests/test_foo.py': """ | ||||||
|  |                 def test(check_first): | ||||||
|  |                     check_first([10, 30], 30) | ||||||
|  |             """ | ||||||
|  |         } | ||||||
|  |         testdir.makepyfile(**contents) | ||||||
|  |         result = testdir.runpytest_subprocess('--assert=%s' % mode) | ||||||
|  |         if mode == 'plain': | ||||||
|  |             expected = 'E       AssertionError' | ||||||
|  |         elif mode == 'rewrite': | ||||||
|  |             expected = '*assert 10 == 30*' | ||||||
|  |         elif mode == 'reinterp': | ||||||
|  |             expected = '*AssertionError:*was re-run*' | ||||||
|  |         else: | ||||||
|  |             assert 0 | ||||||
|  |         result.stdout.fnmatch_lines([expected]) | ||||||
|  | 
 | ||||||
|  |     @pytest.mark.parametrize('mode', ['plain', 'rewrite', 'reinterp']) | ||||||
|  |     def test_pytest_plugins_rewrite(self, testdir, mode): | ||||||
|  |         contents = { | ||||||
|  |             'conftest.py': """ | ||||||
|  |                 pytest_plugins = ['ham'] | ||||||
|  |             """, | ||||||
|  |             'ham.py': """ | ||||||
|  |                 import pytest | ||||||
|  |                 @pytest.fixture | ||||||
|  |                 def check_first(): | ||||||
|  |                     def check(values, value): | ||||||
|  |                         assert values.pop(0) == value | ||||||
|  |                     return check | ||||||
|  |             """, | ||||||
|  |             'test_foo.py': """ | ||||||
|  |                 def test_foo(check_first): | ||||||
|  |                     check_first([10, 30], 30) | ||||||
|  |             """, | ||||||
|  |         } | ||||||
|  |         testdir.makepyfile(**contents) | ||||||
|  |         result = testdir.runpytest_subprocess('--assert=%s' % mode) | ||||||
|  |         if mode == 'plain': | ||||||
|  |             expected = 'E       AssertionError' | ||||||
|  |         elif mode == 'rewrite': | ||||||
|  |             expected = '*assert 10 == 30*' | ||||||
|  |         elif mode == 'reinterp': | ||||||
|  |             expected = '*AssertionError:*was re-run*' | ||||||
|  |         else: | ||||||
|  |             assert 0 | ||||||
|  |         result.stdout.fnmatch_lines([expected]) | ||||||
|  | 
 | ||||||
|  |     @pytest.mark.parametrize('mode', ['plain', 'rewrite', 'reinterp']) | ||||||
|  |     def test_installed_plugin_rewrite(self, testdir, mode): | ||||||
|  |         # Make sure the hook is installed early enough so that plugins | ||||||
|  |         # installed via setuptools are re-written. | ||||||
|  |         testdir.tmpdir.join('hampkg').ensure(dir=1) | ||||||
|  |         contents = { | ||||||
|  |             'hampkg/__init__.py': """ | ||||||
|  |                 import pytest | ||||||
|  | 
 | ||||||
|  |                 @pytest.fixture | ||||||
|  |                 def check_first2(): | ||||||
|  |                     def check(values, value): | ||||||
|  |                         assert values.pop(0) == value | ||||||
|  |                     return check | ||||||
|  |             """, | ||||||
|  |             'spamplugin.py': """ | ||||||
|  |             import pytest | ||||||
|  |             from hampkg import check_first2 | ||||||
|  | 
 | ||||||
|  |             @pytest.fixture | ||||||
|  |             def check_first(): | ||||||
|  |                 def check(values, value): | ||||||
|  |                     assert values.pop(0) == value | ||||||
|  |                 return check | ||||||
|  |             """, | ||||||
|  |             'mainwrapper.py': """ | ||||||
|  |             import pytest, pkg_resources | ||||||
|  | 
 | ||||||
|  |             class DummyDistInfo: | ||||||
|  |                 project_name = 'spam' | ||||||
|  |                 version = '1.0' | ||||||
|  | 
 | ||||||
|  |                 def _get_metadata(self, name): | ||||||
|  |                     return ['spamplugin.py,sha256=abc,123', | ||||||
|  |                             'hampkg/__init__.py,sha256=abc,123'] | ||||||
|  | 
 | ||||||
|  |             class DummyEntryPoint: | ||||||
|  |                 name = 'spam' | ||||||
|  |                 module_name = 'spam.py' | ||||||
|  |                 attrs = () | ||||||
|  |                 extras = None | ||||||
|  |                 dist = DummyDistInfo() | ||||||
|  | 
 | ||||||
|  |                 def load(self, require=True, *args, **kwargs): | ||||||
|  |                     import spamplugin | ||||||
|  |                     return spamplugin | ||||||
|  | 
 | ||||||
|  |             def iter_entry_points(name): | ||||||
|  |                 yield DummyEntryPoint() | ||||||
|  | 
 | ||||||
|  |             pkg_resources.iter_entry_points = iter_entry_points | ||||||
|  |             pytest.main() | ||||||
|  |             """, | ||||||
|  |             'test_foo.py': """ | ||||||
|  |             def test(check_first): | ||||||
|  |                 check_first([10, 30], 30) | ||||||
|  | 
 | ||||||
|  |             def test2(check_first2): | ||||||
|  |                 check_first([10, 30], 30) | ||||||
|  |             """, | ||||||
|  |         } | ||||||
|  |         testdir.makepyfile(**contents) | ||||||
|  |         result = testdir.run(sys.executable, 'mainwrapper.py', '-s', '--assert=%s' % mode) | ||||||
|  |         if mode == 'plain': | ||||||
|  |             expected = 'E       AssertionError' | ||||||
|  |         elif mode == 'rewrite': | ||||||
|  |             expected = '*assert 10 == 30*' | ||||||
|  |         elif mode == 'reinterp': | ||||||
|  |             expected = '*AssertionError:*was re-run*' | ||||||
|  |         else: | ||||||
|  |             assert 0 | ||||||
|  |         result.stdout.fnmatch_lines([expected]) | ||||||
|  | 
 | ||||||
|  |     def test_rewrite_ast(self, testdir): | ||||||
|  |         testdir.tmpdir.join('pkg').ensure(dir=1) | ||||||
|  |         contents = { | ||||||
|  |             'pkg/__init__.py': """ | ||||||
|  |                 import pytest | ||||||
|  |                 pytest.register_assert_rewrite('pkg.helper') | ||||||
|  |             """, | ||||||
|  |             'pkg/helper.py': """ | ||||||
|  |                 def tool(): | ||||||
|  |                     a, b = 2, 3 | ||||||
|  |                     assert a == b | ||||||
|  |             """, | ||||||
|  |             'pkg/plugin.py': """ | ||||||
|  |                 import pytest, pkg.helper | ||||||
|  |                 @pytest.fixture | ||||||
|  |                 def tool(): | ||||||
|  |                     return pkg.helper.tool | ||||||
|  |             """, | ||||||
|  |             'pkg/other.py': """ | ||||||
|  |                 l = [3, 2] | ||||||
|  |                 def tool(): | ||||||
|  |                     assert l.pop() == 3 | ||||||
|  |             """, | ||||||
|  |             'conftest.py': """ | ||||||
|  |                 pytest_plugins = ['pkg.plugin'] | ||||||
|  |             """, | ||||||
|  |             'test_pkg.py': """ | ||||||
|  |                 import pkg.other | ||||||
|  |                 def test_tool(tool): | ||||||
|  |                     tool() | ||||||
|  |                 def test_other(): | ||||||
|  |                     pkg.other.tool() | ||||||
|  |             """, | ||||||
|  |         } | ||||||
|  |         testdir.makepyfile(**contents) | ||||||
|  |         result = testdir.runpytest_subprocess('--assert=rewrite') | ||||||
|  |         result.stdout.fnmatch_lines(['>*assert a == b*', | ||||||
|  |                                      'E*assert 2 == 3*', | ||||||
|  |                                      '>*assert l.pop() == 3*', | ||||||
|  |                                      'E*AssertionError*re-run*']) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class TestBinReprIntegration: | class TestBinReprIntegration: | ||||||
| 
 | 
 | ||||||
|     def test_pytest_assertrepr_compare_called(self, testdir): |     def test_pytest_assertrepr_compare_called(self, testdir): | ||||||
|  |  | ||||||
|  | @ -12,7 +12,7 @@ if sys.platform.startswith("java"): | ||||||
| 
 | 
 | ||||||
| import _pytest._code | import _pytest._code | ||||||
| from _pytest.assertion import util | from _pytest.assertion import util | ||||||
| from _pytest.assertion.rewrite import rewrite_asserts, PYTEST_TAG | from _pytest.assertion.rewrite import rewrite_asserts, PYTEST_TAG, AssertionRewritingHook | ||||||
| from _pytest.main import EXIT_NOTESTSCOLLECTED | from _pytest.main import EXIT_NOTESTSCOLLECTED | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -524,6 +524,16 @@ def test_rewritten(): | ||||||
|         testdir.makepyfile("import a_package_without_init_py.module") |         testdir.makepyfile("import a_package_without_init_py.module") | ||||||
|         assert testdir.runpytest().ret == EXIT_NOTESTSCOLLECTED |         assert testdir.runpytest().ret == EXIT_NOTESTSCOLLECTED | ||||||
| 
 | 
 | ||||||
|  |     def test_rewrite_warning(self, pytestconfig, monkeypatch): | ||||||
|  |         hook = AssertionRewritingHook(pytestconfig) | ||||||
|  |         warnings = [] | ||||||
|  |         def mywarn(code, msg): | ||||||
|  |             warnings.append((code, msg)) | ||||||
|  |         monkeypatch.setattr(hook.config, 'warn', mywarn) | ||||||
|  |         hook.mark_rewrite('_pytest') | ||||||
|  |         assert '_pytest' in warnings[0][1] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class TestAssertionRewriteHookDetails(object): | class TestAssertionRewriteHookDetails(object): | ||||||
|     def test_loader_is_package_false_for_module(self, testdir): |     def test_loader_is_package_false_for_module(self, testdir): | ||||||
|         testdir.makepyfile(test_fun=""" |         testdir.makepyfile(test_fun=""" | ||||||
|  | @ -704,40 +714,6 @@ class TestAssertionRewriteHookDetails(object): | ||||||
|         result = testdir.runpytest() |         result = testdir.runpytest() | ||||||
|         result.stdout.fnmatch_lines('*1 passed*') |         result.stdout.fnmatch_lines('*1 passed*') | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.parametrize('initial_conftest', [True, False]) |  | ||||||
|     @pytest.mark.parametrize('mode', ['plain', 'rewrite', 'reinterp']) |  | ||||||
|     def test_conftest_assertion_rewrite(self, testdir, initial_conftest, mode): |  | ||||||
|         """Test that conftest files are using assertion rewrite on import. |  | ||||||
|         (#1619) |  | ||||||
|         """ |  | ||||||
|         testdir.tmpdir.join('foo/tests').ensure(dir=1) |  | ||||||
|         conftest_path = 'conftest.py' if initial_conftest else 'foo/conftest.py' |  | ||||||
|         contents = { |  | ||||||
|             conftest_path: """ |  | ||||||
|                 import pytest |  | ||||||
|                 @pytest.fixture |  | ||||||
|                 def check_first(): |  | ||||||
|                     def check(values, value): |  | ||||||
|                         assert values.pop(0) == value |  | ||||||
|                     return check |  | ||||||
|             """, |  | ||||||
|             'foo/tests/test_foo.py': """ |  | ||||||
|                 def test(check_first): |  | ||||||
|                     check_first([10, 30], 30) |  | ||||||
|             """ |  | ||||||
|         } |  | ||||||
|         testdir.makepyfile(**contents) |  | ||||||
|         result = testdir.runpytest_subprocess('--assert=%s' % mode) |  | ||||||
|         if mode == 'plain': |  | ||||||
|             expected = 'E       AssertionError' |  | ||||||
|         elif mode == 'rewrite': |  | ||||||
|             expected = '*assert 10 == 30*' |  | ||||||
|         elif mode == 'reinterp': |  | ||||||
|             expected = '*AssertionError:*was re-run*' |  | ||||||
|         else: |  | ||||||
|             assert 0 |  | ||||||
|         result.stdout.fnmatch_lines([expected]) |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| def test_issue731(testdir): | def test_issue731(testdir): | ||||||
|     testdir.makepyfile(""" |     testdir.makepyfile(""" | ||||||
|  |  | ||||||
|  | @ -373,10 +373,14 @@ def test_preparse_ordering_with_setuptools(testdir, monkeypatch): | ||||||
|     pkg_resources = pytest.importorskip("pkg_resources") |     pkg_resources = pytest.importorskip("pkg_resources") | ||||||
|     def my_iter(name): |     def my_iter(name): | ||||||
|         assert name == "pytest11" |         assert name == "pytest11" | ||||||
|  |         class Dist: | ||||||
|  |             project_name = 'spam' | ||||||
|  |             version = '1.0' | ||||||
|  |             def _get_metadata(self, name): | ||||||
|  |                 return ['foo.txt,sha256=abc,123'] | ||||||
|         class EntryPoint: |         class EntryPoint: | ||||||
|             name = "mytestplugin" |             name = "mytestplugin" | ||||||
|             class dist: |             dist = Dist() | ||||||
|                 pass |  | ||||||
|             def load(self): |             def load(self): | ||||||
|                 class PseudoPlugin: |                 class PseudoPlugin: | ||||||
|                     x = 42 |                     x = 42 | ||||||
|  | @ -396,9 +400,14 @@ def test_setuptools_importerror_issue1479(testdir, monkeypatch): | ||||||
|     pkg_resources = pytest.importorskip("pkg_resources") |     pkg_resources = pytest.importorskip("pkg_resources") | ||||||
|     def my_iter(name): |     def my_iter(name): | ||||||
|         assert name == "pytest11" |         assert name == "pytest11" | ||||||
|  |         class Dist: | ||||||
|  |             project_name = 'spam' | ||||||
|  |             version = '1.0' | ||||||
|  |             def _get_metadata(self, name): | ||||||
|  |                 return ['foo.txt,sha256=abc,123'] | ||||||
|         class EntryPoint: |         class EntryPoint: | ||||||
|             name = "mytestplugin" |             name = "mytestplugin" | ||||||
|             dist = None |             dist = Dist() | ||||||
|             def load(self): |             def load(self): | ||||||
|                 raise ImportError("Don't hide me!") |                 raise ImportError("Don't hide me!") | ||||||
|         return iter([EntryPoint()]) |         return iter([EntryPoint()]) | ||||||
|  | @ -412,8 +421,14 @@ def test_plugin_preparse_prevents_setuptools_loading(testdir, monkeypatch): | ||||||
|     pkg_resources = pytest.importorskip("pkg_resources") |     pkg_resources = pytest.importorskip("pkg_resources") | ||||||
|     def my_iter(name): |     def my_iter(name): | ||||||
|         assert name == "pytest11" |         assert name == "pytest11" | ||||||
|  |         class Dist: | ||||||
|  |             project_name = 'spam' | ||||||
|  |             version = '1.0' | ||||||
|  |             def _get_metadata(self, name): | ||||||
|  |                 return ['foo.txt,sha256=abc,123'] | ||||||
|         class EntryPoint: |         class EntryPoint: | ||||||
|             name = "mytestplugin" |             name = "mytestplugin" | ||||||
|  |             dist = Dist() | ||||||
|             def load(self): |             def load(self): | ||||||
|                 assert 0, "should not arrive here" |                 assert 0, "should not arrive here" | ||||||
|         return iter([EntryPoint()]) |         return iter([EntryPoint()]) | ||||||
|  | @ -505,7 +520,6 @@ def test_load_initial_conftest_last_ordering(testdir): | ||||||
|     expected = [ |     expected = [ | ||||||
|         "_pytest.config", |         "_pytest.config", | ||||||
|         'test_config', |         'test_config', | ||||||
|         '_pytest.assertion', |  | ||||||
|         '_pytest.capture', |         '_pytest.capture', | ||||||
|     ] |     ] | ||||||
|     assert [x.function.__module__ for x in l] == expected |     assert [x.function.__module__ for x in l] == expected | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue