From 5818e65cf3407f672bfc9b50c54385e45a1ef98b Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 13:30:57 +0100 Subject: [PATCH 01/25] remove pytest_namespace from _pytest/assertion --- _pytest/assertion/__init__.py | 3 --- pytest.py | 2 ++ 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/_pytest/assertion/__init__.py b/_pytest/assertion/__init__.py index edda0634506..cf9265330e4 100644 --- a/_pytest/assertion/__init__.py +++ b/_pytest/assertion/__init__.py @@ -25,9 +25,6 @@ def pytest_addoption(parser): expression information.""") -def pytest_namespace(): - return {'register_assert_rewrite': register_assert_rewrite} - def register_assert_rewrite(*names): """Register one or more module names to be rewritten on import. diff --git a/pytest.py b/pytest.py index e376e417e8a..26667f5e5cb 100644 --- a/pytest.py +++ b/pytest.py @@ -9,6 +9,7 @@ 'hookspec', 'hookimpl', '__version__', + 'register_assert_rewrite' ] if __name__ == '__main__': # if run as a script or by 'python -m pytest' @@ -22,6 +23,7 @@ main, UsageError, _preloadplugins, cmdline, hookspec, hookimpl ) +from _pytest.assertion import register_assert_rewrite from _pytest import __version__ _preloadplugins() # to populate pytest.* namespace so help(pytest) works From fab9b993f80ce696e7be41bcd76a415a9fdce7c6 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 13:34:38 +0100 Subject: [PATCH 02/25] remove pytest_namespace from _pytest.freeze_support --- _pytest/config.py | 1 + _pytest/freeze_support.py | 3 --- pytest.py | 5 ++++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/_pytest/config.py b/_pytest/config.py index db37280caae..751098b3654 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -101,6 +101,7 @@ def directory_arg(path, optname): "junitxml resultlog doctest cacheprovider freeze_support " "setuponly setupplan warnings").split() + builtin_plugins = set(default_plugins) builtin_plugins.add("pytester") diff --git a/_pytest/freeze_support.py b/_pytest/freeze_support.py index 6c212055969..52f86087fff 100644 --- a/_pytest/freeze_support.py +++ b/_pytest/freeze_support.py @@ -5,9 +5,6 @@ from __future__ import absolute_import, division, print_function -def pytest_namespace(): - return {'freeze_includes': freeze_includes} - def freeze_includes(): """ diff --git a/pytest.py b/pytest.py index 26667f5e5cb..741e2d7f6b2 100644 --- a/pytest.py +++ b/pytest.py @@ -9,7 +9,8 @@ 'hookspec', 'hookimpl', '__version__', - 'register_assert_rewrite' + 'register_assert_rewrite', + 'freeze_includes', ] if __name__ == '__main__': # if run as a script or by 'python -m pytest' @@ -24,7 +25,9 @@ hookspec, hookimpl ) from _pytest.assertion import register_assert_rewrite +from _pytest.freeze_support import freeze_includes from _pytest import __version__ + _preloadplugins() # to populate pytest.* namespace so help(pytest) works From 794fd5658c53a22c304c94a8f703d3e05edaa7ac Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 13:40:38 +0100 Subject: [PATCH 03/25] remove pytest_namespace from _pytest/debugging.py --- _pytest/debugging.py | 16 ++++++++-------- pytest.py | 6 +++++- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/_pytest/debugging.py b/_pytest/debugging.py index 1e7dcad5d67..6c6bd6a02ff 100644 --- a/_pytest/debugging.py +++ b/_pytest/debugging.py @@ -16,8 +16,6 @@ def pytest_addoption(parser): help="start a custom interactive Python debugger on errors. " "For example: --pdbcls=IPython.terminal.debugger:TerminalPdb") -def pytest_namespace(): - return {'set_trace': pytestPDB().set_trace} def pytest_configure(config): if config.getvalue("usepdb_cls"): @@ -43,25 +41,27 @@ def fin(): pytestPDB._pdb_cls = pdb_cls config._cleanup.append(fin) + class pytestPDB(object): """ Pseudo PDB that defers to the real pdb. """ _pluginmanager = None _config = None _pdb_cls = pdb.Pdb - def set_trace(self): + @classmethod + def set_trace(cls): """ invoke PDB set_trace debugging, dropping any IO capturing. """ import _pytest.config frame = sys._getframe().f_back - if self._pluginmanager is not None: - capman = self._pluginmanager.getplugin("capturemanager") + if cls._pluginmanager is not None: + capman = cls._pluginmanager.getplugin("capturemanager") if capman: capman.suspendcapture(in_=True) - tw = _pytest.config.create_terminal_writer(self._config) + tw = _pytest.config.create_terminal_writer(cls._config) tw.line() tw.sep(">", "PDB set_trace (IO-capturing turned off)") - self._pluginmanager.hook.pytest_enter_pdb(config=self._config) - self._pdb_cls().set_trace(frame) + cls._pluginmanager.hook.pytest_enter_pdb(config=cls._config) + cls._pdb_cls().set_trace(frame) class PdbInvoke(object): diff --git a/pytest.py b/pytest.py index 741e2d7f6b2..d9073f01ca6 100644 --- a/pytest.py +++ b/pytest.py @@ -11,6 +11,7 @@ '__version__', 'register_assert_rewrite', 'freeze_includes', + 'set_trace', ] if __name__ == '__main__': # if run as a script or by 'python -m pytest' @@ -27,7 +28,10 @@ from _pytest.assertion import register_assert_rewrite from _pytest.freeze_support import freeze_includes from _pytest import __version__ +from _pytest.debugging import pytestPDB as __pytestPDB -_preloadplugins() # to populate pytest.* namespace so help(pytest) works +set_trace = __pytestPDB.set_trace + +_preloadplugins() # to populate pytest.* namespace so help(pytest) works From c74103f395f5ff1d338c79fe8daa50ce472c5552 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 13:51:29 +0100 Subject: [PATCH 04/25] remove pytest_namespace from recwarn and fixture decorators --- _pytest/fixtures.py | 2 -- _pytest/recwarn.py | 5 ----- pytest.py | 7 ++++++- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py index 9196e4fdd39..0116890c2f5 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -52,8 +52,6 @@ def pytest_namespace(): 'function': pytest.Item, }) return { - 'fixture': fixture, - 'yield_fixture': yield_fixture, 'collect': {'_fillfuncargs': fillfixtures} } diff --git a/_pytest/recwarn.py b/_pytest/recwarn.py index 579439fe4f0..43d3607366e 100644 --- a/_pytest/recwarn.py +++ b/_pytest/recwarn.py @@ -26,11 +26,6 @@ def recwarn(): yield wrec -def pytest_namespace(): - return {'deprecated_call': deprecated_call, - 'warns': warns} - - def deprecated_call(func=None, *args, **kwargs): """ assert that calling ``func(*args, **kwargs)`` triggers a ``DeprecationWarning`` or ``PendingDeprecationWarning``. diff --git a/pytest.py b/pytest.py index d9073f01ca6..aca96c143c8 100644 --- a/pytest.py +++ b/pytest.py @@ -12,6 +12,10 @@ 'register_assert_rewrite', 'freeze_includes', 'set_trace', + 'warns', + 'deprecated_call', + 'fixture', + 'yield_fixture' ] if __name__ == '__main__': # if run as a script or by 'python -m pytest' @@ -25,11 +29,12 @@ main, UsageError, _preloadplugins, cmdline, hookspec, hookimpl ) +from _pytest.fixtures import fixture, yield_fixture from _pytest.assertion import register_assert_rewrite from _pytest.freeze_support import freeze_includes from _pytest import __version__ from _pytest.debugging import pytestPDB as __pytestPDB - +from _pytest.recwarn import warns, deprecated_call set_trace = __pytestPDB.set_trace From 6a02cdbb35cdd37518ff6522deb38ba8e3b97b97 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 14:09:39 +0100 Subject: [PATCH 05/25] remove pytest_namespace from _pytest/runner.py --- _pytest/runner.py | 7 ------- pytest.py | 10 +++++++--- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/_pytest/runner.py b/_pytest/runner.py index 07f4be019a3..ee1683042e6 100644 --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -10,13 +10,6 @@ from _pytest._code.code import TerminalRepr, ExceptionInfo -def pytest_namespace(): - return { - 'fail' : fail, - 'skip' : skip, - 'importorskip' : importorskip, - 'exit' : exit, - } # # pytest plugin hooks diff --git a/pytest.py b/pytest.py index aca96c143c8..518b65a3893 100644 --- a/pytest.py +++ b/pytest.py @@ -15,7 +15,12 @@ 'warns', 'deprecated_call', 'fixture', - 'yield_fixture' + 'yield_fixture', + 'fail', + 'skip', + 'importorskip', + 'exit', + ] if __name__ == '__main__': # if run as a script or by 'python -m pytest' @@ -35,8 +40,7 @@ from _pytest import __version__ from _pytest.debugging import pytestPDB as __pytestPDB from _pytest.recwarn import warns, deprecated_call - - +from _pytest.runner import fail, skip, importorskip, exit set_trace = __pytestPDB.set_trace _preloadplugins() # to populate pytest.* namespace so help(pytest) works From 90788defb24076663f8f0a89828885fd49e16076 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 16:58:29 +0100 Subject: [PATCH 06/25] remove pytest_namespace from _pytest.mark and fix latent pytest nesting bug --- _pytest/mark.py | 18 +++++++++++++----- pytest.py | 2 ++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/_pytest/mark.py b/_pytest/mark.py index a06b02b144a..ccc90d6e8b0 100644 --- a/_pytest/mark.py +++ b/_pytest/mark.py @@ -66,10 +66,8 @@ class MarkerError(Exception): """Error in use of a pytest marker/attribute.""" - def pytest_namespace(): return { - 'mark': MarkGenerator(), 'param': ParameterSet.param, } @@ -225,9 +223,13 @@ def matchkeyword(colitem, keywordexpr): def pytest_configure(config): - import pytest + config._old_mark_config = MARK_GEN._config if config.option.strict: - pytest.mark._config = config + MARK_GEN._config = config + + +def pytest_unconfigure(config): + MARK_GEN._config = config._old_mark_config class MarkGenerator(object): @@ -241,11 +243,13 @@ def test_function(): will set a 'slowtest' :class:`MarkInfo` object on the ``test_function`` object. """ + _config = None + def __getattr__(self, name): if name[0] == "_": raise AttributeError("Marker name must NOT start with underscore") - if hasattr(self, '_config'): + if self._config is not None: self._check(name) return MarkDecorator(Mark(name, (), {})) @@ -263,6 +267,7 @@ def _check(self, name): if name not in self._markers: raise AttributeError("%r not a registered marker" % (name,)) + def istestfunc(func): return hasattr(func, "__call__") and \ getattr(func, "__name__", "") != "" @@ -384,3 +389,6 @@ def add_mark(self, mark): def __iter__(self): """ yield MarkInfo objects each relating to a marking-call. """ return imap(MarkInfo, self._marks) + + +MARK_GEN = MarkGenerator() diff --git a/pytest.py b/pytest.py index 518b65a3893..95cb8ccc664 100644 --- a/pytest.py +++ b/pytest.py @@ -20,6 +20,7 @@ 'skip', 'importorskip', 'exit', + 'mark', ] @@ -41,6 +42,7 @@ from _pytest.debugging import pytestPDB as __pytestPDB from _pytest.recwarn import warns, deprecated_call from _pytest.runner import fail, skip, importorskip, exit +from _pytest.mark import MARK_GEN as mark set_trace = __pytestPDB.set_trace _preloadplugins() # to populate pytest.* namespace so help(pytest) works From 9b755f6ec6077c2e6d49b2d893f147cc7d35cce2 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 17:03:23 +0100 Subject: [PATCH 07/25] remove pytest_namespace from _pytest.skipping --- _pytest/skipping.py | 4 ---- pytest.py | 2 ++ 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/_pytest/skipping.py b/_pytest/skipping.py index 228c529359e..f2ee415188a 100644 --- a/_pytest/skipping.py +++ b/_pytest/skipping.py @@ -57,10 +57,6 @@ def nop(*args, **kwargs): ) -def pytest_namespace(): - return dict(xfail=xfail) - - class XFailed(pytest.fail.Exception): """ raised from an explicit call to pytest.xfail() """ diff --git a/pytest.py b/pytest.py index 95cb8ccc664..c05ed77de2c 100644 --- a/pytest.py +++ b/pytest.py @@ -18,6 +18,7 @@ 'yield_fixture', 'fail', 'skip', + 'xfail', 'importorskip', 'exit', 'mark', @@ -43,6 +44,7 @@ from _pytest.recwarn import warns, deprecated_call from _pytest.runner import fail, skip, importorskip, exit from _pytest.mark import MARK_GEN as mark +from _pytest.skipping import xfail set_trace = __pytestPDB.set_trace _preloadplugins() # to populate pytest.* namespace so help(pytest) works From 7d797b7dbfc07915c421c91a007e29ea7467bdc6 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 17:12:16 +0100 Subject: [PATCH 08/25] add a note about the deprecation of the pytest_namespace hook --- _pytest/hookspec.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/_pytest/hookspec.py b/_pytest/hookspec.py index 403f823a027..91763266973 100644 --- a/_pytest/hookspec.py +++ b/_pytest/hookspec.py @@ -16,7 +16,9 @@ def pytest_addhooks(pluginmanager): @hookspec(historic=True) def pytest_namespace(): - """return dict of name->object to be made globally available in + """ + DEPRECATED: this hook causes direct monkeypatching on pytest, its use is strongly discouraged + return dict of name->object to be made globally available in the pytest namespace. This hook is called at plugin registration time. """ From 839c9361539aec8bf90a56cdea89ddccc8c40754 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 17:30:52 +0100 Subject: [PATCH 09/25] _pytest.mark: fix unconfigure after bad configure, still potential bug --- _pytest/mark.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_pytest/mark.py b/_pytest/mark.py index ccc90d6e8b0..23ec44c291c 100644 --- a/_pytest/mark.py +++ b/_pytest/mark.py @@ -229,7 +229,7 @@ def pytest_configure(config): def pytest_unconfigure(config): - MARK_GEN._config = config._old_mark_config + MARK_GEN._config = getattr(config, '_old_mark_config', None) class MarkGenerator(object): From 9b58d6eaca17758551929f0ebb390ad900e03581 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 17:31:34 +0100 Subject: [PATCH 10/25] prepare a own pytest.collect fake module in oder to remove the nested builtin namespaces --- _pytest/compat.py | 23 +++++++++++++++++++++++ pytest.py | 3 +++ 2 files changed, 26 insertions(+) diff --git a/_pytest/compat.py b/_pytest/compat.py index b688ae50934..5be7f7a2a83 100644 --- a/_pytest/compat.py +++ b/_pytest/compat.py @@ -254,6 +254,29 @@ def safe_str(v): return v.encode('ascii', errors) +COLLECT_FAKEMODULE_ATTRIBUTES = ( + 'Collector', + 'Module', + 'Generator', + 'Function', + 'Instance', + 'Session', + 'Item', + 'Class', + 'File', + '_fillfuncargs', +) + + +def _setup_collect_fakemodule(): + from types import ModuleType + import pytest + pytest.collect = ModuleType('pytest.collect') + pytest.collect.__all__ = [] # used for setns + for attr in COLLECT_FAKEMODULE_ATTRIBUTES: + setattr(pytest.collect, attr, getattr(pytest, attr)) + + if _PY2: from py.io import TextIO as CaptureIO else: diff --git a/pytest.py b/pytest.py index c05ed77de2c..c6146e2916f 100644 --- a/pytest.py +++ b/pytest.py @@ -47,4 +47,7 @@ from _pytest.skipping import xfail set_trace = __pytestPDB.set_trace + +from _pytest.compat import _setup_collect_fakemodule _preloadplugins() # to populate pytest.* namespace so help(pytest) works +_setup_collect_fakemodule() \ No newline at end of file From 61f418a26750bd89d26aff9ad3f3cb1eb534b838 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 17:33:54 +0100 Subject: [PATCH 11/25] hollow out pytest_namespace in _pytest.fixtures --- _pytest/fixtures.py | 4 +--- pytest.py | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py index 0116890c2f5..621e38057ad 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -51,9 +51,7 @@ def pytest_namespace(): 'module': pytest.Module, 'function': pytest.Item, }) - return { - 'collect': {'_fillfuncargs': fillfixtures} - } + return {} def get_scope_node(node, scope): diff --git a/pytest.py b/pytest.py index c6146e2916f..bac816b1121 100644 --- a/pytest.py +++ b/pytest.py @@ -45,6 +45,7 @@ from _pytest.runner import fail, skip, importorskip, exit from _pytest.mark import MARK_GEN as mark from _pytest.skipping import xfail +from _pytest.fixtures import fillfixtures as _fillfuncargs set_trace = __pytestPDB.set_trace From 99c8f2d4035a15543fefc6cef2c3abb883a97d23 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 17:41:20 +0100 Subject: [PATCH 12/25] remove pytest_namespace from _pytest.main --- _pytest/main.py | 5 ----- pytest.py | 11 +++++++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/_pytest/main.py b/_pytest/main.py index 3d7b456d2fd..da4ae8e41db 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -77,11 +77,6 @@ def pytest_addoption(parser): help="base temporary directory for this test run.") -def pytest_namespace(): - collect = dict(Item=Item, Collector=Collector, File=File, Session=Session) - return dict(collect=collect) - - def pytest_configure(config): pytest.config = config # compatibility diff --git a/pytest.py b/pytest.py index bac816b1121..a072b49ae57 100644 --- a/pytest.py +++ b/pytest.py @@ -23,6 +23,14 @@ 'exit', 'mark', + '_fillfuncargs', + + 'Item', + 'File', + 'Collector', + 'Session', + + ] if __name__ == '__main__': # if run as a script or by 'python -m pytest' @@ -45,7 +53,10 @@ from _pytest.runner import fail, skip, importorskip, exit from _pytest.mark import MARK_GEN as mark from _pytest.skipping import xfail +from _pytest.main import Item, Collector, File, Session from _pytest.fixtures import fillfixtures as _fillfuncargs + + set_trace = __pytestPDB.set_trace From ae234786eabeae89ece706f826dcac5dcdaca012 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 17:56:12 +0100 Subject: [PATCH 13/25] remove pytest_namespace from _pytest.python --- _pytest/python.py | 22 ++++++---------------- pytest.py | 4 ++++ 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/_pytest/python.py b/_pytest/python.py index e763aa8882b..61000676e29 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -22,6 +22,7 @@ get_real_func, getfslineno, safe_getattr, getlocation, enum, ) +from _pytest.runner import fail cutdir1 = py.path.local(pluggy.__file__.rstrip("oc")) cutdir2 = py.path.local(_pytest.__file__).dirpath() @@ -126,21 +127,6 @@ def pytest_configure(config): "all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures " ) -@pytest.hookimpl(trylast=True) -def pytest_namespace(): - raises.Exception = pytest.fail.Exception - return { - 'raises': raises, - 'approx': approx, - 'collect': { - 'Module': Module, - 'Class': Class, - 'Instance': Instance, - 'Function': Function, - 'Generator': Generator, - } - } - @pytest.hookimpl(trylast=True) def pytest_pyfunc_call(pyfuncitem): @@ -1234,7 +1220,11 @@ def raises(expected_exception, *args, **kwargs): func(*args[1:], **kwargs) except expected_exception: return _pytest._code.ExceptionInfo() - pytest.fail(message) + fail(message) + +raises.Exception = fail.Exception + + class RaisesContext(object): def __init__(self, expected_exception, message, match_expr): diff --git a/pytest.py b/pytest.py index a072b49ae57..8185d650341 100644 --- a/pytest.py +++ b/pytest.py @@ -55,6 +55,10 @@ from _pytest.skipping import xfail from _pytest.main import Item, Collector, File, Session from _pytest.fixtures import fillfixtures as _fillfuncargs +from _pytest.python import ( + raises, approx, + Module, Class, Instance, Function, Generator, +) set_trace = __pytestPDB.set_trace From 23bc9815c4aaf6f247df462dab665f9311e875ca Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 17:59:48 +0100 Subject: [PATCH 14/25] remove pytest_namespace from _pytest.fixtures --- _pytest/fixtures.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py index 621e38057ad..16481ca1c2a 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -19,6 +19,11 @@ ) def pytest_sessionstart(session): + scopename2class.update({ + 'class': pytest.Class, + 'module': pytest.Module, + 'function': pytest.Item, + }) session._fixturemanager = FixtureManager(session) @@ -45,15 +50,6 @@ def provide(self): return decoratescope -def pytest_namespace(): - scopename2class.update({ - 'class': pytest.Class, - 'module': pytest.Module, - 'function': pytest.Item, - }) - return {} - - def get_scope_node(node, scope): cls = scopename2class.get(scope) if cls is None: From 809c36e1f6d5cd837da798026f20aa62c6c7a4e2 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Wed, 1 Mar 2017 19:57:47 +0100 Subject: [PATCH 15/25] add a changelog note for pytest_namespace --- CHANGELOG.rst | 4 ++++ doc/en/writing_plugins.rst | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 05c8680798d..91341bad5af 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -27,6 +27,10 @@ New Features Changes ------- +* remove all internal uses of pytest_namespace hooks, + this is to prepare the removal of preloadconfig in pytest 4.0 + Thanks to `@RonnyPfannschmidt`_ for the PR. + * Old-style classes have been changed to new-style classes in order to improve compatibility with Python 2. Thanks to `@MichalTHEDUDE`_ and `@mandeep`_ for the PR (`#2147`_). diff --git a/doc/en/writing_plugins.rst b/doc/en/writing_plugins.rst index f6ed6e4e3aa..bb07ba0df09 100644 --- a/doc/en/writing_plugins.rst +++ b/doc/en/writing_plugins.rst @@ -517,7 +517,6 @@ Initialization, command line and configuration hooks .. autofunction:: pytest_load_initial_conftests .. autofunction:: pytest_cmdline_preparse .. autofunction:: pytest_cmdline_parse -.. autofunction:: pytest_namespace .. autofunction:: pytest_addoption .. autofunction:: pytest_cmdline_main .. autofunction:: pytest_configure From 92f6ab188137a44479b0c6bcf209c3bf2ea31b41 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Wed, 15 Mar 2017 18:00:59 +0100 Subject: [PATCH 16/25] fix all singular internal module imports and add a test for them --- _pytest/compat.py | 9 ++++++++ _pytest/debugging.py | 5 ++--- _pytest/fixtures.py | 39 +++++++++++++------------------- _pytest/main.py | 50 +++++++++++++++++++++++------------------- _pytest/monkeypatch.py | 8 +++---- _pytest/python.py | 21 +++++++++--------- _pytest/recwarn.py | 14 +++++++----- _pytest/runner.py | 3 +-- _pytest/skipping.py | 38 +++++++++++++++++--------------- 9 files changed, 98 insertions(+), 89 deletions(-) diff --git a/_pytest/compat.py b/_pytest/compat.py index 5be7f7a2a83..c06e3f4cafb 100644 --- a/_pytest/compat.py +++ b/_pytest/compat.py @@ -291,3 +291,12 @@ def __init__(self): def getvalue(self): return self.buffer.getvalue().decode('UTF-8') + +class FuncargnamesCompatAttr(object): + """ helper class so that Metafunc, Function and FixtureRequest + don't need to each define the "funcargnames" compatibility attribute. + """ + @property + def funcargnames(self): + """ alias attribute for ``fixturenames`` for pre-2.3 compatibility""" + return self.fixturenames diff --git a/_pytest/debugging.py b/_pytest/debugging.py index 6c6bd6a02ff..ad81fedc0c8 100644 --- a/_pytest/debugging.py +++ b/_pytest/debugging.py @@ -3,7 +3,6 @@ import pdb import sys -import pytest def pytest_addoption(parser): @@ -35,7 +34,7 @@ def fin(): pytestPDB._config = None pytestPDB._pdb_cls = pdb.Pdb - pdb.set_trace = pytest.set_trace + pdb.set_trace = pytestPDB.set_trace pytestPDB._pluginmanager = config.pluginmanager pytestPDB._config = config pytestPDB._pdb_cls = pdb_cls @@ -75,7 +74,7 @@ def pytest_exception_interact(self, node, call, report): def pytest_internalerror(self, excrepr, excinfo): for line in str(excrepr).split("\n"): - sys.stderr.write("INTERNALERROR> %s\n" %line) + sys.stderr.write("INTERNALERROR> %s\n" % line) sys.stderr.flush() tb = _postmortem_traceback(excinfo) post_mortem(tb) diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py index 16481ca1c2a..2710da7927c 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -4,7 +4,6 @@ from py._code.code import FormattedExcinfo import py -import pytest import warnings import inspect @@ -17,12 +16,15 @@ getlocation, getfuncargnames, safe_getattr, ) +from _pytest.runner import fail +from _pytest.compat import FuncargnamesCompatAttr +from _pytest import python def pytest_sessionstart(session): scopename2class.update({ - 'class': pytest.Class, - 'module': pytest.Module, - 'function': pytest.Item, + 'class': python.Class, + 'module': python.Module, + 'function': _pytest.main.Item, }) session._fixturemanager = FixtureManager(session) @@ -97,7 +99,7 @@ def add_funcarg_pseudo_fixture_def(collector, metafunc, fixturemanager): if scope != "function": node = get_scope_node(collector, scope) if node is None: - assert scope == "class" and isinstance(collector, pytest.Module) + assert scope == "class" and isinstance(collector, _pytest.python.Module) # use module-level collector for class-scope (for now) node = collector if node and argname in node._name2pseudofixturedef: @@ -213,17 +215,6 @@ def slice_items(items, ignore, scoped_argkeys_cache): return items, None, None, None - -class FuncargnamesCompatAttr(object): - """ helper class so that Metafunc, Function and FixtureRequest - don't need to each define the "funcargnames" compatibility attribute. - """ - @property - def funcargnames(self): - """ alias attribute for ``fixturenames`` for pre-2.3 compatibility""" - return self.fixturenames - - def fillfixtures(function): """ fill missing funcargs for a test function. """ try: @@ -319,7 +310,7 @@ def function(self): @scopeproperty("class") def cls(self): """ class (can be None) where the test function was collected. """ - clscol = self._pyfuncitem.getparent(pytest.Class) + clscol = self._pyfuncitem.getparent(_pytest.python.Class) if clscol: return clscol.obj @@ -337,7 +328,7 @@ def instance(self): @scopeproperty() def module(self): """ python module object where the test function was collected. """ - return self._pyfuncitem.getparent(pytest.Module).obj + return self._pyfuncitem.getparent(_pytest.python.Module).obj @scopeproperty() def fspath(self): @@ -500,7 +491,7 @@ def _getfixturevalue(self, fixturedef): source_lineno, ) ) - pytest.fail(msg) + fail(msg) else: # indices might not be set if old-style metafunc.addcall() was used param_index = funcitem.callspec.indices.get(argname, 0) @@ -533,10 +524,10 @@ def _check_scope(self, argname, invoking_scope, requested_scope): if scopemismatch(invoking_scope, requested_scope): # try to report something helpful lines = self._factorytraceback() - pytest.fail("ScopeMismatch: You tried to access the %r scoped " - "fixture %r with a %r scoped request object, " - "involved factories\n%s" %( - (requested_scope, argname, invoking_scope, "\n".join(lines))), + fail("ScopeMismatch: You tried to access the %r scoped " + "fixture %r with a %r scoped request object, " + "involved factories\n%s" % ( + (requested_scope, argname, invoking_scope, "\n".join(lines))), pytrace=False) def _factorytraceback(self): @@ -546,7 +537,7 @@ def _factorytraceback(self): fs, lineno = getfslineno(factory) p = self._pyfuncitem.session.fspath.bestrelpath(fs) args = _format_args(factory) - lines.append("%s:%d: def %s%s" %( + lines.append("%s:%d: def %s%s" % ( p, lineno, factory.__name__, args)) return lines diff --git a/_pytest/main.py b/_pytest/main.py index da4ae8e41db..62327365529 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -8,14 +8,13 @@ import _pytest import _pytest._code import py -import pytest try: from collections import MutableMapping as MappingMixin except ImportError: from UserDict import DictMixin as MappingMixin -from _pytest.config import directory_arg -from _pytest.runner import collect_one_node +from _pytest.config import directory_arg, UsageError, hookimpl +from _pytest.runner import collect_one_node, exit tracebackcutdir = py.path.local(_pytest.__file__).dirpath() @@ -27,6 +26,7 @@ EXIT_USAGEERROR = 4 EXIT_NOTESTSCOLLECTED = 5 + def pytest_addoption(parser): parser.addini("norecursedirs", "directory patterns to avoid for recursion", type="args", default=['.*', 'build', 'dist', 'CVS', '_darcs', '{arch}', '*.egg', 'venv']) @@ -78,7 +78,7 @@ def pytest_addoption(parser): def pytest_configure(config): - pytest.config = config # compatibility + pytest.config = config # compatibiltiy def wrap_session(config, doit): @@ -93,12 +93,11 @@ def wrap_session(config, doit): config.hook.pytest_sessionstart(session=session) initstate = 2 session.exitstatus = doit(config, session) or 0 - except pytest.UsageError: + except UsageError: raise except KeyboardInterrupt: excinfo = _pytest._code.ExceptionInfo() - if initstate < 2 and isinstance( - excinfo.value, pytest.exit.Exception): + if initstate < 2 and isinstance(excinfo.value, exit.Exception): sys.stderr.write('{0}: {1}\n'.format( excinfo.typename, excinfo.value.msg)) config.hook.pytest_keyboard_interrupt(excinfo=excinfo) @@ -120,9 +119,11 @@ def wrap_session(config, doit): config._ensure_unconfigure() return session.exitstatus + def pytest_cmdline_main(config): return wrap_session(config, _main) + def _main(config, session): """ default command line protocol for initialization, session, running tests and reporting. """ @@ -134,9 +135,11 @@ def _main(config, session): elif session.testscollected == 0: return EXIT_NOTESTSCOLLECTED + def pytest_collection(session): return session.perform_collect() + def pytest_runtestloop(session): if (session.testsfailed and not session.config.option.continue_on_collection_errors): @@ -153,6 +156,7 @@ def pytest_runtestloop(session): raise session.Interrupted(session.shouldstop) return True + def pytest_ignore_collect(path, config): p = path.dirpath() ignore_paths = config._getconftest_pathlist("collect_ignore", path=p) @@ -200,7 +204,7 @@ def __get__(self, obj, owner): # "usage of {owner!r}.{name} is deprecated, please use pytest.{name} instead".format( # name=self.name, owner=type(owner).__name__), # PendingDeprecationWarning, stacklevel=2) - return getattr(pytest, self.name) + return getattr(__import__('pytest'), self.name) @@ -284,7 +288,7 @@ def ihook(self): def _getcustomclass(self, name): maybe_compatprop = getattr(type(self), name) if isinstance(maybe_compatprop, _CompatProperty): - return getattr(pytest, name) + return getattr(__import__('pytest'), name) else: cls = getattr(self, name) # TODO: reenable in the features branch @@ -367,9 +371,9 @@ def add_marker(self, marker): ``marker`` can be a string or pytest.mark.* instance. """ - from _pytest.mark import MarkDecorator + from _pytest.mark import MarkDecorator, MARK_GEN if isinstance(marker, py.builtin._basestring): - marker = getattr(pytest.mark, marker) + marker = getattr(MARK_GEN, marker) elif not isinstance(marker, MarkDecorator): raise ValueError("is not a string or pytest.mark.* Marker") self.keywords[marker.name] = marker @@ -555,12 +559,12 @@ def __init__(self, config): def _makeid(self): return "" - @pytest.hookimpl(tryfirst=True) + @hookimpl(tryfirst=True) def pytest_collectstart(self): if self.shouldstop: raise self.Interrupted(self.shouldstop) - @pytest.hookimpl(tryfirst=True) + @hookimpl(tryfirst=True) def pytest_runtest_logreport(self, report): if report.failed and not hasattr(report, 'wasxfail'): self.testsfailed += 1 @@ -619,8 +623,8 @@ def _perform_collect(self, args, genitems): for arg, exc in self._notfound: line = "(no name %r in any of %r)" % (arg, exc.args[0]) errors.append("not found: %s\n%s" % (arg, line)) - #XXX: test this - raise pytest.UsageError(*errors) + # XXX: test this + raise UsageError(*errors) if not genitems: return rep.result else: @@ -648,7 +652,7 @@ def _collect(self, arg): names = self._parsearg(arg) path = names.pop(0) if path.check(dir=1): - assert not names, "invalid arg %r" %(arg,) + assert not names, "invalid arg %r" % (arg,) for path in path.visit(fil=lambda x: x.check(file=1), rec=self._recurse, bf=True, sort=True): for x in self._collectfile(path): @@ -707,9 +711,11 @@ def _parsearg(self, arg): path = self.config.invocation_dir.join(relpath, abs=True) if not path.check(): if self.config.option.pyargs: - raise pytest.UsageError("file or package not found: " + arg + " (missing __init__.py?)") + raise UsageError( + "file or package not found: " + arg + + " (missing __init__.py?)") else: - raise pytest.UsageError("file not found: " + arg) + raise UsageError("file not found: " + arg) parts[0] = path return parts @@ -732,11 +738,11 @@ def _matchnodes(self, matching, names): nextnames = names[1:] resultnodes = [] for node in matching: - if isinstance(node, pytest.Item): + if isinstance(node, Item): if not names: resultnodes.append(node) continue - assert isinstance(node, pytest.Collector) + assert isinstance(node, Collector) rep = collect_one_node(node) if rep.passed: has_matched = False @@ -754,11 +760,11 @@ def _matchnodes(self, matching, names): def genitems(self, node): self.trace("genitems", node) - if isinstance(node, pytest.Item): + if isinstance(node, Item): node.ihook.pytest_itemcollected(item=node) yield node else: - assert isinstance(node, pytest.Collector) + assert isinstance(node, Collector) rep = collect_one_node(node) if rep.passed: for subnode in rep.result: diff --git a/_pytest/monkeypatch.py b/_pytest/monkeypatch.py index 9151db3adc3..ec846f910bb 100644 --- a/_pytest/monkeypatch.py +++ b/_pytest/monkeypatch.py @@ -1,17 +1,17 @@ """ monkeypatching and mocking functionality. """ from __future__ import absolute_import, division, print_function -import os, sys +import os +import sys import re from py.builtin import _basestring - -import pytest +from _pytest.fixtures import fixture RE_IMPORT_ERROR_NAME = re.compile("^No module named (.*)$") -@pytest.fixture +@fixture def monkeypatch(): """The returned ``monkeypatch`` fixture provides these helper methods to modify objects, dictionaries or os.environ:: diff --git a/_pytest/python.py b/_pytest/python.py index 61000676e29..985b476ca3c 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -9,13 +9,13 @@ from itertools import count import py -import pytest from _pytest.mark import MarkerError - +from _pytest.config import hookimpl import _pytest import _pytest._pluggy as pluggy from _pytest import fixtures +from _pytest import main from _pytest.compat import ( isclass, isfunction, is_generator, _escape_strings, REGEX_TYPE, STRING_TYPES, NoneType, NOTSET, @@ -50,7 +50,7 @@ def filter_traceback(entry): def pyobj_property(name): def get(self): - node = self.getparent(getattr(pytest, name)) + node = self.getparent(getattr(__import__('pytest'), name)) if node is not None: return node.obj doc = "python %s object this node was collected from (can be None)." % ( @@ -128,7 +128,7 @@ def pytest_configure(config): ) -@pytest.hookimpl(trylast=True) +@hookimpl(trylast=True) def pytest_pyfunc_call(pyfuncitem): testfunction = pyfuncitem.obj if pyfuncitem._isyieldedfunction(): @@ -141,6 +141,7 @@ def pytest_pyfunc_call(pyfuncitem): testfunction(**testargs) return True + def pytest_collect_file(path, parent): ext = path.ext if ext == ".py": @@ -156,7 +157,7 @@ def pytest_collect_file(path, parent): def pytest_pycollect_makemodule(path, parent): return Module(path, parent) -@pytest.hookimpl(hookwrapper=True) +@hookimpl(hookwrapper=True) def pytest_pycollect_makeitem(collector, name, obj): outcome = yield res = outcome.get_result() @@ -252,7 +253,7 @@ def reportinfo(self): assert isinstance(lineno, int) return fspath, lineno, modpath -class PyCollector(PyobjMixin, pytest.Collector): +class PyCollector(PyobjMixin, main.Collector): def funcnamefilter(self, name): return self._matches_prefix_or_glob_option('python_functions', name) @@ -576,7 +577,7 @@ def _prunetraceback(self, excinfo): entry.set_repr_style('short') def _repr_failure_py(self, excinfo, style="long"): - if excinfo.errisinstance(pytest.fail.Exception): + if excinfo.errisinstance(fail.Exception): if not excinfo.value.pytrace: return py._builtin._totext(excinfo.value) return super(FunctionMixin, self)._repr_failure_py(excinfo, @@ -774,7 +775,7 @@ def parametrize(self, argnames, argvalues, indirect=False, ids=None, to set a dynamic scope using test context or configuration. """ from _pytest.fixtures import scope2index - from _pytest.mark import ParameterSet + from _pytest.mark import extract_argvalue, MARK_GEN from py.io import saferepr if not isinstance(argnames, (tuple, list)): @@ -1240,7 +1241,7 @@ def __enter__(self): def __exit__(self, *tp): __tracebackhide__ = True if tp[0] is None: - pytest.fail(self.message) + fail(self.message) if sys.version_info < (2, 7): # py26: on __exit__() exc_value often does not contain the # exception value. @@ -1511,7 +1512,7 @@ def tolerance(self): # the basic pytest Function item # -class Function(FunctionMixin, pytest.Item, fixtures.FuncargnamesCompatAttr): +class Function(FunctionMixin, main.Item, fixtures.FuncargnamesCompatAttr): """ a Function Item is responsible for setting up and executing a Python test function. """ diff --git a/_pytest/recwarn.py b/_pytest/recwarn.py index 43d3607366e..73325cb8e9a 100644 --- a/_pytest/recwarn.py +++ b/_pytest/recwarn.py @@ -7,10 +7,11 @@ import py import sys import warnings -import pytest +from _pytest.fixtures import yield_fixture -@pytest.yield_fixture + +@yield_fixture def recwarn(): """Return a WarningsRecorder instance that provides these methods: @@ -190,7 +191,8 @@ def __exit__(self, *exc_info): if not any(issubclass(r.category, self.expected_warning) for r in self): __tracebackhide__ = True - pytest.fail("DID NOT WARN. No warnings of type {0} was emitted. " - "The list of emitted warnings is: {1}.".format( - self.expected_warning, - [each.message for each in self])) + from _pytest.runner import fail + fail("DID NOT WARN. No warnings of type {0} was emitted. " + "The list of emitted warnings is: {1}.".format( + self.expected_warning, + [each.message for each in self])) diff --git a/_pytest/runner.py b/_pytest/runner.py index ee1683042e6..87dd240c818 100644 --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -6,7 +6,6 @@ from time import time import py -import pytest from _pytest._code.code import TerminalRepr, ExceptionInfo @@ -257,7 +256,7 @@ def pytest_runtest_makereport(item, call): if not isinstance(excinfo, ExceptionInfo): outcome = "failed" longrepr = excinfo - elif excinfo.errisinstance(pytest.skip.Exception): + elif excinfo.errisinstance(skip.Exception): outcome = "skipped" r = excinfo._getreprcrash() longrepr = (str(r.path), r.lineno, r.message) diff --git a/_pytest/skipping.py b/_pytest/skipping.py index f2ee415188a..c68cdea470d 100644 --- a/_pytest/skipping.py +++ b/_pytest/skipping.py @@ -6,9 +6,9 @@ import traceback import py -import pytest +from _pytest.config import hookimpl from _pytest.mark import MarkInfo, MarkDecorator - +from _pytest.runner import fail, skip def pytest_addoption(parser): group = parser.getgroup("general") @@ -25,6 +25,8 @@ def pytest_addoption(parser): def pytest_configure(config): if config.option.runxfail: + # yay a hack + import pytest old = pytest.xfail config._cleanup.append(lambda: setattr(pytest, "xfail", old)) @@ -57,7 +59,7 @@ def nop(*args, **kwargs): ) -class XFailed(pytest.fail.Exception): +class XFailed(fail.Exception): """ raised from an explicit call to pytest.xfail() """ @@ -98,15 +100,15 @@ def istrue(self): except Exception: self.exc = sys.exc_info() if isinstance(self.exc[1], SyntaxError): - msg = [" " * (self.exc[1].offset + 4) + "^",] + msg = [" " * (self.exc[1].offset + 4) + "^", ] msg.append("SyntaxError: invalid syntax") else: msg = traceback.format_exception_only(*self.exc[:2]) - pytest.fail("Error evaluating %r expression\n" - " %s\n" - "%s" - %(self.name, self.expr, "\n".join(msg)), - pytrace=False) + fail("Error evaluating %r expression\n" + " %s\n" + "%s" + % (self.name, self.expr, "\n".join(msg)), + pytrace=False) def _getglobals(self): d = {'os': os, 'sys': sys, 'config': self.item.config} @@ -138,7 +140,7 @@ def _istrue(self): # XXX better be checked at collection time msg = "you need to specify reason=STRING " \ "when using booleans as conditions." - pytest.fail(msg) + fail(msg) result = bool(expr) if result: self.result = True @@ -162,7 +164,7 @@ def getexplanation(self): return expl -@pytest.hookimpl(tryfirst=True) +@hookimpl(tryfirst=True) def pytest_runtest_setup(item): # Check if skip or skipif are specified as pytest marks @@ -171,23 +173,23 @@ def pytest_runtest_setup(item): eval_skipif = MarkEvaluator(item, 'skipif') if eval_skipif.istrue(): item._evalskip = eval_skipif - pytest.skip(eval_skipif.getexplanation()) + skip(eval_skipif.getexplanation()) skip_info = item.keywords.get('skip') if isinstance(skip_info, (MarkInfo, MarkDecorator)): item._evalskip = True if 'reason' in skip_info.kwargs: - pytest.skip(skip_info.kwargs['reason']) + skip(skip_info.kwargs['reason']) elif skip_info.args: - pytest.skip(skip_info.args[0]) + skip(skip_info.args[0]) else: - pytest.skip("unconditional skip") + skip("unconditional skip") item._evalxfail = MarkEvaluator(item, 'xfail') check_xfail_no_run(item) -@pytest.mark.hookwrapper +@hookimpl(hookwrapper=True) def pytest_pyfunc_call(pyfuncitem): check_xfail_no_run(pyfuncitem) outcome = yield @@ -217,7 +219,7 @@ def check_strict_xfail(pyfuncitem): pytest.fail('[XPASS(strict)] ' + explanation, pytrace=False) -@pytest.hookimpl(hookwrapper=True) +@hookimpl(hookwrapper=True) def pytest_runtest_makereport(item, call): outcome = yield rep = outcome.get_result() @@ -237,7 +239,7 @@ def pytest_runtest_makereport(item, call): rep.wasxfail = rep.longrepr elif item.config.option.runxfail: pass # don't interefere - elif call.excinfo and call.excinfo.errisinstance(pytest.xfail.Exception): + elif call.excinfo and call.excinfo.errisinstance(xfail.Exception): rep.wasxfail = "reason: " + call.excinfo.value.msg rep.outcome = "skipped" elif evalxfail and not rep.skipped and evalxfail.wasvalid() and \ From bb750a794566c2e2b10066d37ceba49cdfba7238 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Wed, 15 Mar 2017 18:11:19 +0100 Subject: [PATCH 17/25] add missed file --- testing/test_modimport.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 testing/test_modimport.py diff --git a/testing/test_modimport.py b/testing/test_modimport.py new file mode 100644 index 00000000000..9c7293bb879 --- /dev/null +++ b/testing/test_modimport.py @@ -0,0 +1,21 @@ +import py +import subprocess +import sys +import pytest +import _pytest + +MODSET = [ + x for x in py.path.local(_pytest.__file__).dirpath().visit('*.py') + if x.purebasename != '__init__' +] + + +@pytest.mark.parametrize('modfile', MODSET, ids=lambda x: x.purebasename) +def test_fileimport(modfile): + res = subprocess.call([ + sys.executable, + '-c', 'import sys, py; py.path.local(sys.argv[1]).pyimport()', + modfile.strpath, + ]) + if res: + pytest.fail("command result %s" % res) From 4d31ea83160331d8eb97de477bc3476d1a39653f Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Wed, 15 Mar 2017 18:26:01 +0100 Subject: [PATCH 18/25] add a comment explaining the modimport tests --- testing/test_modimport.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/testing/test_modimport.py b/testing/test_modimport.py index 9c7293bb879..2ab86bf7af1 100644 --- a/testing/test_modimport.py +++ b/testing/test_modimport.py @@ -12,6 +12,10 @@ @pytest.mark.parametrize('modfile', MODSET, ids=lambda x: x.purebasename) def test_fileimport(modfile): + # this test ensures all internal packages can import + # without needing the pytest namespace being set + # this is critical for the initialization of xdist + res = subprocess.call([ sys.executable, '-c', 'import sys, py; py.path.local(sys.argv[1]).pyimport()', From 7cdefce656119ec023bf371f14076c76f6782ceb Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Thu, 16 Mar 2017 11:35:00 +0100 Subject: [PATCH 19/25] fix up oversights --- _pytest/fixtures.py | 5 ++-- _pytest/python.py | 2 +- _pytest/recwarn.py | 1 - _pytest/skipping.py | 16 +++++++---- pytest.py | 70 +++++++++++++++++++++++++-------------------- 5 files changed, 54 insertions(+), 40 deletions(-) diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py index 2710da7927c..714a8b406c8 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -682,8 +682,9 @@ def fail_fixturefunc(fixturefunc, msg): fs, lineno = getfslineno(fixturefunc) location = "%s:%s" % (fs, lineno+1) source = _pytest._code.Source(fixturefunc) - pytest.fail(msg + ":\n\n" + str(source.indent()) + "\n" + location, - pytrace=False) + fail(msg + ":\n\n" + str(source.indent()) + "\n" + location, + pytrace=False) + def call_fixture_func(fixturefunc, request, kwargs): yieldctx = is_generator(fixturefunc) diff --git a/_pytest/python.py b/_pytest/python.py index 985b476ca3c..4f33a356b68 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -1223,8 +1223,8 @@ def raises(expected_exception, *args, **kwargs): return _pytest._code.ExceptionInfo() fail(message) -raises.Exception = fail.Exception +raises.Exception = fail.Exception class RaisesContext(object): diff --git a/_pytest/recwarn.py b/_pytest/recwarn.py index 73325cb8e9a..7dce842f680 100644 --- a/_pytest/recwarn.py +++ b/_pytest/recwarn.py @@ -7,7 +7,6 @@ import py import sys import warnings - from _pytest.fixtures import yield_fixture diff --git a/_pytest/skipping.py b/_pytest/skipping.py index c68cdea470d..e7922da7d2e 100644 --- a/_pytest/skipping.py +++ b/_pytest/skipping.py @@ -204,7 +204,7 @@ def check_xfail_no_run(item): evalxfail = item._evalxfail if evalxfail.istrue(): if not evalxfail.get('run', True): - pytest.xfail("[NOTRUN] " + evalxfail.getexplanation()) + xfail("[NOTRUN] " + evalxfail.getexplanation()) def check_strict_xfail(pyfuncitem): @@ -216,7 +216,7 @@ def check_strict_xfail(pyfuncitem): if is_strict_xfail: del pyfuncitem._evalxfail explanation = evalxfail.getexplanation() - pytest.fail('[XPASS(strict)] ' + explanation, pytrace=False) + fail('[XPASS(strict)] ' + explanation, pytrace=False) @hookimpl(hookwrapper=True) @@ -307,12 +307,14 @@ def pytest_terminal_summary(terminalreporter): for line in lines: tr._tw.line(line) + def show_simple(terminalreporter, lines, stat, format): failed = terminalreporter.stats.get(stat) if failed: for rep in failed: pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid) - lines.append(format %(pos,)) + lines.append(format % (pos,)) + def show_xfailed(terminalreporter, lines): xfailed = terminalreporter.stats.get("xfailed") @@ -324,13 +326,15 @@ def show_xfailed(terminalreporter, lines): if reason: lines.append(" " + str(reason)) + def show_xpassed(terminalreporter, lines): xpassed = terminalreporter.stats.get("xpassed") if xpassed: for rep in xpassed: pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid) reason = rep.wasxfail - lines.append("XPASS %s %s" %(pos, reason)) + lines.append("XPASS %s %s" % (pos, reason)) + def cached_eval(config, expr, d): if not hasattr(config, '_evalcache'): @@ -355,6 +359,7 @@ def folded_skips(skipped): l.append((len(events),) + key) return l + def show_skipped(terminalreporter, lines): tr = terminalreporter skipped = tr.stats.get('skipped', []) @@ -370,5 +375,6 @@ def show_skipped(terminalreporter, lines): for num, fspath, lineno, reason in fskips: if reason.startswith("Skipped: "): reason = reason[9:] - lines.append("SKIP [%d] %s:%d: %s" % + lines.append( + "SKIP [%d] %s:%d: %s" % (num, fspath, lineno, reason)) diff --git a/pytest.py b/pytest.py index 8185d650341..d720ee86666 100644 --- a/pytest.py +++ b/pytest.py @@ -2,6 +2,32 @@ """ pytest: unit and functional testing with Python. """ + + +# else we are imported + +from _pytest.config import ( + main, UsageError, _preloadplugins, cmdline, + hookspec, hookimpl +) +from _pytest.fixtures import fixture, yield_fixture +from _pytest.assertion import register_assert_rewrite +from _pytest.freeze_support import freeze_includes +from _pytest import __version__ +from _pytest.debugging import pytestPDB as __pytestPDB +from _pytest.recwarn import warns, deprecated_call +from _pytest.runner import fail, skip, importorskip, exit +from _pytest.mark import MARK_GEN as mark +from _pytest.skipping import xfail +from _pytest.main import Item, Collector, File, Session +from _pytest.fixtures import fillfixtures as _fillfuncargs +from _pytest.python import ( + raises, approx, + Module, Class, Instance, Function, Generator, +) + +set_trace = __pytestPDB.set_trace + __all__ = [ 'main', 'UsageError', @@ -22,48 +48,30 @@ 'importorskip', 'exit', 'mark', - + 'approx', '_fillfuncargs', 'Item', 'File', 'Collector', 'Session', + 'Module', + 'Class', + 'Instance', + 'Function', + 'Generator', + 'raises', ] -if __name__ == '__main__': # if run as a script or by 'python -m pytest' +if __name__ == '__main__': + # if run as a script or by 'python -m pytest' # we trigger the below "else" condition by the following import import pytest raise SystemExit(pytest.main()) +else: -# else we are imported - -from _pytest.config import ( - main, UsageError, _preloadplugins, cmdline, - hookspec, hookimpl -) -from _pytest.fixtures import fixture, yield_fixture -from _pytest.assertion import register_assert_rewrite -from _pytest.freeze_support import freeze_includes -from _pytest import __version__ -from _pytest.debugging import pytestPDB as __pytestPDB -from _pytest.recwarn import warns, deprecated_call -from _pytest.runner import fail, skip, importorskip, exit -from _pytest.mark import MARK_GEN as mark -from _pytest.skipping import xfail -from _pytest.main import Item, Collector, File, Session -from _pytest.fixtures import fillfixtures as _fillfuncargs -from _pytest.python import ( - raises, approx, - Module, Class, Instance, Function, Generator, -) - - -set_trace = __pytestPDB.set_trace - - -from _pytest.compat import _setup_collect_fakemodule -_preloadplugins() # to populate pytest.* namespace so help(pytest) works -_setup_collect_fakemodule() \ No newline at end of file + from _pytest.compat import _setup_collect_fakemodule + _preloadplugins() # to populate pytest.* namespace so help(pytest) works + _setup_collect_fakemodule() From 147bb8aea5b4c50f0ce432bf6d0cdf547c24cf90 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Thu, 16 Mar 2017 20:05:22 +0100 Subject: [PATCH 20/25] correct setting pytest.config --- _pytest/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_pytest/main.py b/_pytest/main.py index 62327365529..f1846616223 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -78,7 +78,7 @@ def pytest_addoption(parser): def pytest_configure(config): - pytest.config = config # compatibiltiy + __import__('pytest').config = config # compatibiltiy def wrap_session(config, doit): From c9ab4213986c5f074a69b7abd68f0dfca8db89ba Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Thu, 16 Mar 2017 20:31:58 +0100 Subject: [PATCH 21/25] fix python2 only import loop failure --- _pytest/fixtures.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py index 714a8b406c8..2c7c9e7717a 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -18,12 +18,12 @@ ) from _pytest.runner import fail from _pytest.compat import FuncargnamesCompatAttr -from _pytest import python def pytest_sessionstart(session): + import _pytest.python scopename2class.update({ - 'class': python.Class, - 'module': python.Module, + 'class': _pytest.python.Class, + 'module': _pytest.python.Module, 'function': _pytest.main.Item, }) session._fixturemanager = FixtureManager(session) From efe03400d8a1a32666fdbc8b9f1c38d7c085322d Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Thu, 16 Mar 2017 20:51:22 +0100 Subject: [PATCH 22/25] fixup nose/pytest plugins --- _pytest/nose.py | 18 +++++++++--------- _pytest/unittest.py | 46 ++++++++++++++++++++++++--------------------- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/_pytest/nose.py b/_pytest/nose.py index 828a919f9b7..9d4fc0b6e15 100644 --- a/_pytest/nose.py +++ b/_pytest/nose.py @@ -4,8 +4,8 @@ import sys import py -import pytest -from _pytest import unittest +from _pytest import unittest, runner, python +from _pytest.config import hookimpl def get_skip_exceptions(): @@ -20,19 +20,19 @@ def get_skip_exceptions(): def pytest_runtest_makereport(item, call): if call.excinfo and call.excinfo.errisinstance(get_skip_exceptions()): # let's substitute the excinfo with a pytest.skip one - call2 = call.__class__(lambda: - pytest.skip(str(call.excinfo.value)), call.when) + call2 = call.__class__( + lambda: runner.skip(str(call.excinfo.value)), call.when) call.excinfo = call2.excinfo -@pytest.hookimpl(trylast=True) +@hookimpl(trylast=True) def pytest_runtest_setup(item): if is_potential_nosetest(item): - if isinstance(item.parent, pytest.Generator): + if isinstance(item.parent, python.Generator): gen = item.parent if not hasattr(gen, '_nosegensetup'): call_optional(gen.obj, 'setup') - if isinstance(gen.parent, pytest.Instance): + if isinstance(gen.parent, python.Instance): call_optional(gen.parent.obj, 'setup') gen._nosegensetup = True if not call_optional(item.obj, 'setup'): @@ -51,14 +51,14 @@ def teardown_nose(item): def pytest_make_collect_report(collector): - if isinstance(collector, pytest.Generator): + if isinstance(collector, python.Generator): call_optional(collector.obj, 'setup') def is_potential_nosetest(item): # extra check needed since we do not do nose style setup/teardown # on direct unittest style classes - return isinstance(item, pytest.Function) and \ + return isinstance(item, python.Function) and \ not isinstance(item, unittest.TestCaseFunction) diff --git a/_pytest/unittest.py b/_pytest/unittest.py index 5a8cb9d663a..0cf0f1726af 100644 --- a/_pytest/unittest.py +++ b/_pytest/unittest.py @@ -4,11 +4,12 @@ import sys import traceback -import pytest # for transferring markers import _pytest._code -from _pytest.python import transfer_markers -from _pytest.skipping import MarkEvaluator +from _pytest.config import hookimpl +from _pytest.runner import fail, skip +from _pytest.python import transfer_markers, Class, Module, Function +from _pytest.skipping import MarkEvaluator, xfail def pytest_pycollect_makeitem(collector, name, obj): @@ -22,11 +23,11 @@ def pytest_pycollect_makeitem(collector, name, obj): return UnitTestCase(name, parent=collector) -class UnitTestCase(pytest.Class): +class UnitTestCase(Class): # marker for fixturemanger.getfixtureinfo() # to declare that our children do not support funcargs nofuncargs = True - + def setup(self): cls = self.obj if getattr(cls, '__unittest_skip__', False): @@ -46,7 +47,7 @@ def collect(self): return self.session._fixturemanager.parsefactories(self, unittest=True) loader = TestLoader() - module = self.getparent(pytest.Module).obj + module = self.getparent(Module).obj foundsomething = False for name in loader.getTestCaseNames(self.obj): x = getattr(self.obj, name) @@ -65,7 +66,7 @@ def collect(self): yield TestCaseFunction('runTest', parent=self) -class TestCaseFunction(pytest.Function): +class TestCaseFunction(Function): _excinfo = None def setup(self): @@ -110,36 +111,37 @@ def _addexcinfo(self, rawexcinfo): try: l = traceback.format_exception(*rawexcinfo) l.insert(0, "NOTE: Incompatible Exception Representation, " - "displaying natively:\n\n") - pytest.fail("".join(l), pytrace=False) - except (pytest.fail.Exception, KeyboardInterrupt): + "displaying natively:\n\n") + fail("".join(l), pytrace=False) + except (fail.Exception, KeyboardInterrupt): raise except: - pytest.fail("ERROR: Unknown Incompatible Exception " - "representation:\n%r" %(rawexcinfo,), pytrace=False) + fail("ERROR: Unknown Incompatible Exception " + "representation:\n%r" % (rawexcinfo,), pytrace=False) except KeyboardInterrupt: raise - except pytest.fail.Exception: + except fail.Exception: excinfo = _pytest._code.ExceptionInfo() self.__dict__.setdefault('_excinfo', []).append(excinfo) def addError(self, testcase, rawexcinfo): self._addexcinfo(rawexcinfo) + def addFailure(self, testcase, rawexcinfo): self._addexcinfo(rawexcinfo) def addSkip(self, testcase, reason): try: - pytest.skip(reason) - except pytest.skip.Exception: + skip(reason) + except skip.Exception: self._evalskip = MarkEvaluator(self, 'SkipTest') self._evalskip.result = True self._addexcinfo(sys.exc_info()) def addExpectedFailure(self, testcase, rawexcinfo, reason=""): try: - pytest.xfail(str(reason)) - except pytest.xfail.Exception: + xfail(str(reason)) + except xfail.Exception: self._addexcinfo(sys.exc_info()) def addUnexpectedSuccess(self, testcase, reason=""): @@ -179,13 +181,14 @@ def runtest(self): self._testcase.debug() def _prunetraceback(self, excinfo): - pytest.Function._prunetraceback(self, excinfo) + Function._prunetraceback(self, excinfo) traceback = excinfo.traceback.filter( - lambda x:not x.frame.f_globals.get('__unittest')) + lambda x: not x.frame.f_globals.get('__unittest')) if traceback: excinfo.traceback = traceback -@pytest.hookimpl(tryfirst=True) + +@hookimpl(tryfirst=True) def pytest_runtest_makereport(item, call): if isinstance(item, TestCaseFunction): if item._excinfo: @@ -197,7 +200,8 @@ def pytest_runtest_makereport(item, call): # twisted trial support -@pytest.hookimpl(hookwrapper=True) + +@hookimpl(hookwrapper=True) def pytest_runtest_protocol(item): if isinstance(item, TestCaseFunction) and \ 'twisted.trial.unittest' in sys.modules: From 6165939b0d957497208a93e0737ec0d937cd791b Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Mon, 20 Mar 2017 15:47:25 +0100 Subject: [PATCH 23/25] fix rebase mistakes --- _pytest/python.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/_pytest/python.py b/_pytest/python.py index 4f33a356b68..1f540c84fea 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -390,7 +390,8 @@ def transfer_markers(funcobj, cls, mod): if not _marked(funcobj, pytestmark): pytestmark(funcobj) -class Module(pytest.File, PyCollector): + +class Module(main.File, PyCollector): """ Collector for test classes and functions. """ def _getobj(self): @@ -775,7 +776,7 @@ def parametrize(self, argnames, argvalues, indirect=False, ids=None, to set a dynamic scope using test context or configuration. """ from _pytest.fixtures import scope2index - from _pytest.mark import extract_argvalue, MARK_GEN + from _pytest.mark import MARK_GEN, ParameterSet from py.io import saferepr if not isinstance(argnames, (tuple, list)): @@ -788,12 +789,11 @@ def parametrize(self, argnames, argvalues, indirect=False, ids=None, for x in argvalues] del argvalues - if not parameters: fs, lineno = getfslineno(self.function) reason = "got empty parameter set %r, function %s at %s:%d" % ( argnames, self.function.__name__, fs, lineno) - mark = pytest.mark.skip(reason=reason) + mark = MARK_GEN.skip(reason=reason) parameters.append(ParameterSet( values=(NOTSET,) * len(argnames), marks=[mark], @@ -870,7 +870,7 @@ def addcall(self, funcargs=None, id=NOTSET, param=NOTSET): if funcargs is not None: for name in funcargs: if name not in self.fixturenames: - pytest.fail("funcarg %r not used in this function." % name) + fail("funcarg %r not used in this function." % name) else: funcargs = {} if id is None: @@ -945,6 +945,7 @@ def _idval(val, argname, idx, idfn, config=None): return val.__name__ return str(argname)+str(idx) + def _idvalset(idx, parameterset, argnames, idfn, ids, config=None): if parameterset.id is not None: return parameterset.id @@ -955,6 +956,7 @@ def _idvalset(idx, parameterset, argnames, idfn, ids, config=None): else: return _escape_strings(ids[idx]) + def idmaker(argnames, parametersets, idfn=None, ids=None, config=None): ids = [_idvalset(valindex, parameterset, argnames, idfn, ids, config) for valindex, parameterset in enumerate(parametersets)] @@ -1033,6 +1035,7 @@ def showfixtures(config): from _pytest.main import wrap_session return wrap_session(config, _showfixtures_main) + def _showfixtures_main(config, session): import _pytest.config session.perform_collect() From ebeba79be373a06b4a5fccce3fe1fadff60eddaa Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Mon, 20 Mar 2017 17:58:34 +0100 Subject: [PATCH 24/25] remove the namespace hook from mark after the param feature merge --- _pytest/mark.py | 6 ++---- pytest.py | 3 ++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/_pytest/mark.py b/_pytest/mark.py index 23ec44c291c..c34df1239f9 100644 --- a/_pytest/mark.py +++ b/_pytest/mark.py @@ -66,10 +66,8 @@ class MarkerError(Exception): """Error in use of a pytest marker/attribute.""" -def pytest_namespace(): - return { - 'param': ParameterSet.param, - } +def param(*values, **kw): + return ParameterSet.param(*values, **kw) def pytest_addoption(parser): diff --git a/pytest.py b/pytest.py index d720ee86666..4e4ccb32dd4 100644 --- a/pytest.py +++ b/pytest.py @@ -17,7 +17,7 @@ from _pytest.debugging import pytestPDB as __pytestPDB from _pytest.recwarn import warns, deprecated_call from _pytest.runner import fail, skip, importorskip, exit -from _pytest.mark import MARK_GEN as mark +from _pytest.mark import MARK_GEN as mark, param from _pytest.skipping import xfail from _pytest.main import Item, Collector, File, Session from _pytest.fixtures import fillfixtures as _fillfuncargs @@ -48,6 +48,7 @@ 'importorskip', 'exit', 'mark', + 'param', 'approx', '_fillfuncargs', From afb1778294bf4d4cd93d8695873d0101c3db18bf Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Mar 2017 10:28:43 +0200 Subject: [PATCH 25/25] put in a singular namespace hook to work around the strange issue --- _pytest/main.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/_pytest/main.py b/_pytest/main.py index f1846616223..ec6657ae96d 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -77,6 +77,16 @@ def pytest_addoption(parser): help="base temporary directory for this test run.") + +def pytest_namespace(): + """keeping this one works around a deeper startup issue in pytest + + i tried to find it for a while but the amount of time turned unsustainable, + so i put a hack in to revisit later + """ + return {} + + def pytest_configure(config): __import__('pytest').config = config # compatibiltiy