From 618f3994ddeda4079e6de26dd47b2a6c88a082fa Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 13:30:57 +0100 Subject: [PATCH 01/19] 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 fe0653bb71e..faae6b39cce 100644 --- a/_pytest/assertion/__init__.py +++ b/_pytest/assertion/__init__.py @@ -24,9 +24,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 803123a2477acf562d03c605affd11dbb56c6175 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 13:34:38 +0100 Subject: [PATCH 02/19] remove pytest_namespace from _pytest.freeze_support --- _pytest/config.py | 2 +- _pytest/freeze_support.py | 3 --- pytest.py | 5 ++++- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/_pytest/config.py b/_pytest/config.py index 81f39cda485..38deb8c60de 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -97,7 +97,7 @@ def directory_arg(path, optname): default_plugins = ( "mark main terminal runner python fixtures debugging unittest capture skipping " "tmpdir monkeypatch recwarn pastebin helpconfig nose assertion " - "junitxml resultlog doctest cacheprovider freeze_support " + "junitxml resultlog doctest cacheprovider " "setuponly setupplan").split() builtin_plugins = set(default_plugins) diff --git a/_pytest/freeze_support.py b/_pytest/freeze_support.py index f78ccd298ef..1ef3f5a9d0e 100644 --- a/_pytest/freeze_support.py +++ b/_pytest/freeze_support.py @@ -3,9 +3,6 @@ pytest """ -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 a4c382b8af6e0ec34efb242a58a1436efe0e3faa Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 13:40:38 +0100 Subject: [PATCH 03/19] 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 c21e0977d8c..af28599383b 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 f69972d286155f55279ec8a81da42716a0f8096f Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 13:51:29 +0100 Subject: [PATCH 04/19] 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 f675217f839..e7de636bf3e 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -50,8 +50,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 9031bdbdae4..4024cbb2ce0 100644 --- a/_pytest/recwarn.py +++ b/_pytest/recwarn.py @@ -25,11 +25,6 @@ def recwarn(request): 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 5796182d4083a49f576fa1a5e69a212822be22b7 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 14:09:39 +0100 Subject: [PATCH 05/19] 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 f17155dae35..ca8862a08f8 100644 --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -8,13 +8,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 69c34db0c5e6eb47f00c1ad227b2060b96b47310 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 16:58:29 +0100 Subject: [PATCH 06/19] remove pytest_namespace from _pytest.mark and fix latent pytest nesting bug --- _pytest/mark.py | 21 ++++++++++++++------- pytest.py | 2 ++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/_pytest/mark.py b/_pytest/mark.py index ef9a94ad9a7..d6b8422100c 100644 --- a/_pytest/mark.py +++ b/_pytest/mark.py @@ -13,10 +13,6 @@ class MarkerError(Exception): """Error in use of a pytest marker/attribute.""" -def pytest_namespace(): - return {'mark': MarkGenerator()} - - def pytest_addoption(parser): group = parser.getgroup("general") group._addoption( @@ -168,9 +164,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): @@ -184,11 +184,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, (), {})) @@ -206,10 +208,12 @@ 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__", "") != "" + class MarkDecorator(object): """ A decorator for test functions and test classes. When applied it will create :class:`MarkInfo` objects which may be @@ -335,3 +339,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 43a382b43be592f841dbf26e3c008179a0af075f Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 17:03:23 +0100 Subject: [PATCH 07/19] 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 a005bc7c28e..97f639f6045 100644 --- a/_pytest/skipping.py +++ b/_pytest/skipping.py @@ -55,10 +55,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 b3990a8a61c83f6220eac2db03bfde6f1cee478c Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 17:12:16 +0100 Subject: [PATCH 08/19] 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 bb382a597a6..c808d16b4d9 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 3a53c0b0732830b97cfd1c7a2c99ac5a63c0c12d Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 17:30:52 +0100 Subject: [PATCH 09/19] _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 d6b8422100c..e9465ad9af4 100644 --- a/_pytest/mark.py +++ b/_pytest/mark.py @@ -170,7 +170,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 c097a1701ae10c99644c54aa561b4df9e805f76c Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 17:31:34 +0100 Subject: [PATCH 10/19] 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 09df385d1de..2dbaa50aaef 100644 --- a/_pytest/compat.py +++ b/_pytest/compat.py @@ -253,6 +253,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 cb77eca2ce6018877a61e99d28ff0d4277b7ddee Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 17:33:54 +0100 Subject: [PATCH 11/19] 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 e7de636bf3e..7e840e39cd9 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -49,9 +49,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 c7d1d218b954dc71410fe79a26e7f1cf6fc2c51e Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 17:41:20 +0100 Subject: [PATCH 12/19] 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 73858e0994b..b67491394dc 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -75,11 +75,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 # compatibiltiy 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 f20275920a1ee429a94f1ed7952988b73fb42c08 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 17:56:12 +0100 Subject: [PATCH 13/19] 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 ff4edff5b23..9e390eeaa83 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -21,6 +21,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() @@ -125,21 +126,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): @@ -1232,7 +1218,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 eb0ce3667761b0466daebcb121ab037090d90a5f Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 17:59:48 +0100 Subject: [PATCH 14/19] 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 7e840e39cd9..1cdf09e8194 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -17,6 +17,11 @@ ) def pytest_sessionstart(session): + scopename2class.update({ + 'class': pytest.Class, + 'module': pytest.Module, + 'function': pytest.Item, + }) session._fixturemanager = FixtureManager(session) @@ -43,15 +48,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 3d36d57ec6dec267494fec77895bade821ac234d Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Wed, 1 Mar 2017 19:57:47 +0100 Subject: [PATCH 15/19] 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 ee3eae09700..52cb73702a3 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -20,6 +20,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 770f81e463c..31ee0c1929f 100644 --- a/doc/en/writing_plugins.rst +++ b/doc/en/writing_plugins.rst @@ -506,7 +506,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 aea8fb3065cf490661883980854e2c463828a7eb Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Wed, 15 Mar 2017 18:00:59 +0100 Subject: [PATCH 16/19] 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 | 28 ++++++++++++----------- _pytest/recwarn.py | 13 ++++++----- _pytest/runner.py | 3 +-- _pytest/skipping.py | 38 +++++++++++++++++--------------- 9 files changed, 101 insertions(+), 92 deletions(-) diff --git a/_pytest/compat.py b/_pytest/compat.py index 2dbaa50aaef..0ecef03a505 100644 --- a/_pytest/compat.py +++ b/_pytest/compat.py @@ -290,3 +290,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 af28599383b..9f3cb3c9510 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 1cdf09e8194..0cb0a9a619f 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -3,7 +3,6 @@ from py._code.code import FormattedExcinfo import py -import pytest import warnings import inspect @@ -15,12 +14,15 @@ is_generator, isclass, getimfunc, getlocation, getfuncargnames, ) +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) @@ -95,7 +97,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 b67491394dc..496441edcf8 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -6,14 +6,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() @@ -25,6 +24,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']) @@ -76,7 +76,7 @@ def pytest_addoption(parser): def pytest_configure(config): - pytest.config = config # compatibiltiy + __import__('pytest').config = config # compatibiltiy def wrap_session(config, doit): @@ -91,12 +91,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) @@ -118,9 +117,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. """ @@ -132,9 +133,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): @@ -151,6 +154,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) @@ -198,7 +202,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) @@ -282,7 +286,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 @@ -365,9 +369,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 @@ -553,12 +557,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 @@ -617,8 +621,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: @@ -646,7 +650,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): @@ -705,9 +709,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 @@ -730,11 +736,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 @@ -752,11 +758,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 30618cc5761..e769bb8ff0c 100644 --- a/_pytest/monkeypatch.py +++ b/_pytest/monkeypatch.py @@ -1,16 +1,16 @@ """ monkeypatching and mocking functionality. """ -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 9e390eeaa83..a8f2ce02b28 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -8,13 +8,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, @@ -49,7 +49,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)." % ( @@ -127,7 +127,7 @@ def pytest_configure(config): ) -@pytest.hookimpl(trylast=True) +@hookimpl(trylast=True) def pytest_pyfunc_call(pyfuncitem): testfunction = pyfuncitem.obj if pyfuncitem._isyieldedfunction(): @@ -140,6 +140,7 @@ def pytest_pyfunc_call(pyfuncitem): testfunction(**testargs) return True + def pytest_collect_file(path, parent): ext = path.ext if ext == ".py": @@ -155,7 +156,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() @@ -251,7 +252,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) @@ -388,7 +389,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): @@ -575,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, @@ -773,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 extract_argvalue + from _pytest.mark import extract_argvalue, MARK_GEN from py.io import saferepr unwrapped_argvalues = [] @@ -793,7 +795,7 @@ def parametrize(self, argnames, argvalues, indirect=False, ids=None, # we passed a empty list to parameterize, skip that test # fs, lineno = getfslineno(self.function) - newmark = pytest.mark.skip( + newmark = MARK_GEN.skip( reason="got empty parameter set %r, function %s at %s:%d" % ( argnames, self.function.__name__, fs, lineno)) newkeywords = [{newmark.markname: newmark}] @@ -869,7 +871,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: @@ -1238,7 +1240,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. @@ -1509,7 +1511,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 4024cbb2ce0..56341d400c8 100644 --- a/_pytest/recwarn.py +++ b/_pytest/recwarn.py @@ -5,11 +5,11 @@ import py import sys import warnings -import pytest from collections import namedtuple +from _pytest.fixtures import yield_fixture -@pytest.yield_fixture +@yield_fixture def recwarn(request): """Return a WarningsRecorder instance that provides these methods: @@ -214,7 +214,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 ca8862a08f8..f953232465e 100644 --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -4,7 +4,6 @@ from time import time import py -import pytest from _pytest._code.code import TerminalRepr, ExceptionInfo @@ -255,7 +254,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 97f639f6045..e3dac4ecd7b 100644 --- a/_pytest/skipping.py +++ b/_pytest/skipping.py @@ -4,9 +4,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") @@ -23,6 +23,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)) @@ -55,7 +57,7 @@ def nop(*args, **kwargs): ) -class XFailed(pytest.fail.Exception): +class XFailed(fail.Exception): """ raised from an explicit call to pytest.xfail() """ @@ -96,15 +98,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} @@ -135,7 +137,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 @@ -159,7 +161,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 @@ -168,23 +170,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 @@ -214,7 +216,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() @@ -234,7 +236,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 fea9d0e1d756543020aaab60ca82f676aa9426da Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Wed, 15 Mar 2017 18:11:19 +0100 Subject: [PATCH 17/19] 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 f70d5227c2f1dd69c172bb61bb9cf2a657b5290d Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Wed, 15 Mar 2017 18:26:01 +0100 Subject: [PATCH 18/19] 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 ffc26af9d24a48ca5fde01efe0901a7bca9f3001 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Thu, 16 Mar 2017 11:35:00 +0100 Subject: [PATCH 19/19] 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 3d130c24179..4fbd2f19703 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -681,8 +681,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 ebc71e81dea..2687987c356 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -1222,8 +1222,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 adce7e08a3e..d8e59e704b2 100644 --- a/_pytest/recwarn.py +++ b/_pytest/recwarn.py @@ -5,7 +5,6 @@ import py import sys import warnings -from collections import namedtuple from _pytest.fixtures import yield_fixture diff --git a/_pytest/skipping.py b/_pytest/skipping.py index 64e7523c81a..dd380a65981 100644 --- a/_pytest/skipping.py +++ b/_pytest/skipping.py @@ -202,7 +202,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): @@ -214,7 +214,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) @@ -305,12 +305,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") @@ -322,13 +324,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'): @@ -353,6 +357,7 @@ def folded_skips(skipped): l.append((len(events),) + key) return l + def show_skipped(terminalreporter, lines): tr = terminalreporter skipped = tr.stats.get('skipped', []) @@ -368,5 +373,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()