diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e91059b54c6..217147100c4 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/_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/compat.py b/_pytest/compat.py index 09df385d1de..0ecef03a505 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: @@ -267,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/config.py b/_pytest/config.py index 6a607c7e30d..e86ee0666f7 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/debugging.py b/_pytest/debugging.py index c21e0977d8c..9f3cb3c9510 100644 --- a/_pytest/debugging.py +++ b/_pytest/debugging.py @@ -3,7 +3,6 @@ import pdb import sys -import pytest def pytest_addoption(parser): @@ -16,8 +15,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"): @@ -37,31 +34,33 @@ 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 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): @@ -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 c4d21635f18..4fbd2f19703 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 @@ -16,8 +15,16 @@ 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': python.Class, + 'module': python.Module, + 'function': _pytest.main.Item, + }) session._fixturemanager = FixtureManager(session) @@ -44,19 +51,6 @@ def provide(self): return decoratescope -def pytest_namespace(): - scopename2class.update({ - 'class': pytest.Class, - 'module': pytest.Module, - 'function': pytest.Item, - }) - return { - 'fixture': fixture, - 'yield_fixture': yield_fixture, - 'collect': {'_fillfuncargs': fillfixtures} - } - - def get_scope_node(node, scope): cls = scopename2class.get(scope) if cls is None: @@ -104,7 +98,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: @@ -220,17 +214,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: @@ -326,7 +309,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 @@ -344,7 +327,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): @@ -507,7 +490,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) @@ -540,10 +523,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): @@ -553,7 +536,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 @@ -698,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/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/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. """ diff --git a/_pytest/main.py b/_pytest/main.py index f100b797462..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']) @@ -75,13 +75,8 @@ 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 + __import__('pytest').config = config # compatibiltiy def wrap_session(config, doit): @@ -96,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) @@ -123,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. """ @@ -137,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): @@ -156,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) @@ -203,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) @@ -287,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 @@ -370,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 @@ -558,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 @@ -622,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: @@ -651,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): @@ -710,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 @@ -735,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 @@ -757,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/mark.py b/_pytest/mark.py index 582eb12773c..b9ddac82e4d 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 = getattr(config, '_old_mark_config', None) 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/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 471a9563f02..2687987c356 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -8,19 +8,20 @@ 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, 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() @@ -48,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)." % ( @@ -125,23 +126,8 @@ 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) + +@hookimpl(trylast=True) def pytest_pyfunc_call(pyfuncitem): testfunction = pyfuncitem.obj if pyfuncitem._isyieldedfunction(): @@ -154,6 +140,7 @@ def pytest_pyfunc_call(pyfuncitem): testfunction(**testargs) return True + def pytest_collect_file(path, parent): ext = path.ext if ext == ".py": @@ -169,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() @@ -265,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) @@ -402,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): @@ -589,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, @@ -787,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 = [] @@ -807,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}] @@ -883,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: @@ -1232,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): @@ -1248,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. @@ -1519,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 43f68ed1236..d8e59e704b2 100644 --- a/_pytest/recwarn.py +++ b/_pytest/recwarn.py @@ -5,11 +5,11 @@ import py import sys import warnings -import pytest +from _pytest.fixtures import yield_fixture -@pytest.yield_fixture -def recwarn(): +@yield_fixture +def recwarn(request): """Return a WarningsRecorder instance that provides these methods: * ``pop(category=None)``: return last warning matching the category. @@ -24,11 +24,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``. @@ -195,7 +190,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 f17155dae35..f953232465e 100644 --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -4,17 +4,9 @@ from time import time import py -import pytest from _pytest._code.code import TerminalRepr, ExceptionInfo -def pytest_namespace(): - return { - 'fail' : fail, - 'skip' : skip, - 'importorskip' : importorskip, - 'exit' : exit, - } # # pytest plugin hooks @@ -262,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 86176acafe9..dd380a65981 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,11 +57,7 @@ def nop(*args, **kwargs): ) -def pytest_namespace(): - return dict(xfail=xfail) - - -class XFailed(pytest.fail.Exception): +class XFailed(fail.Exception): """ raised from an explicit call to pytest.xfail() """ @@ -100,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} @@ -140,7 +138,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 @@ -164,7 +162,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 @@ -173,23 +171,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 @@ -204,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): @@ -216,10 +214,10 @@ 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) -@pytest.hookimpl(hookwrapper=True) +@hookimpl(hookwrapper=True) def pytest_runtest_makereport(item, call): outcome = yield rep = outcome.get_result() @@ -239,7 +237,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 \ @@ -307,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") @@ -324,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'): @@ -355,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', []) @@ -370,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/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 diff --git a/pytest.py b/pytest.py index e376e417e8a..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', @@ -9,20 +35,43 @@ 'hookspec', 'hookimpl', '__version__', + 'register_assert_rewrite', + 'freeze_includes', + 'set_trace', + 'warns', + 'deprecated_call', + 'fixture', + 'yield_fixture', + 'fail', + 'skip', + 'xfail', + '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 import __version__ - -_preloadplugins() # to populate pytest.* namespace so help(pytest) works - + from _pytest.compat import _setup_collect_fakemodule + _preloadplugins() # to populate pytest.* namespace so help(pytest) works + _setup_collect_fakemodule() diff --git a/testing/test_modimport.py b/testing/test_modimport.py new file mode 100644 index 00000000000..2ab86bf7af1 --- /dev/null +++ b/testing/test_modimport.py @@ -0,0 +1,25 @@ +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): + # 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()', + modfile.strpath, + ]) + if res: + pytest.fail("command result %s" % res)