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/_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/compat.py b/_pytest/compat.py index b688ae50934..c06e3f4cafb 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: @@ -268,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/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/debugging.py b/_pytest/debugging.py index 1e7dcad5d67..ad81fedc0c8 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 9196e4fdd39..2c7c9e7717a 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,8 +16,16 @@ getlocation, getfuncargnames, safe_getattr, ) +from _pytest.runner import fail +from _pytest.compat import FuncargnamesCompatAttr def pytest_sessionstart(session): + import _pytest.python + scopename2class.update({ + 'class': _pytest.python.Class, + 'module': _pytest.python.Module, + 'function': _pytest.main.Item, + }) session._fixturemanager = FixtureManager(session) @@ -45,19 +52,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: @@ -105,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: @@ -221,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: @@ -327,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 @@ -345,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): @@ -508,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) @@ -541,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): @@ -554,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 @@ -699,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/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/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 3d7b456d2fd..ec6657ae96d 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']) @@ -77,13 +77,18 @@ 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) + """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): - pytest.config = config # compatibility + __import__('pytest').config = config # compatibiltiy def wrap_session(config, doit): @@ -98,12 +103,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) @@ -125,9 +129,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. """ @@ -139,9 +145,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): @@ -158,6 +166,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) @@ -205,7 +214,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) @@ -289,7 +298,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 @@ -372,9 +381,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 @@ -560,12 +569,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 @@ -624,8 +633,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: @@ -653,7 +662,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): @@ -712,9 +721,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 @@ -737,11 +748,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 @@ -759,11 +770,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 a06b02b144a..c34df1239f9 100644 --- a/_pytest/mark.py +++ b/_pytest/mark.py @@ -66,12 +66,8 @@ class MarkerError(Exception): """Error in use of a pytest marker/attribute.""" - -def pytest_namespace(): - return { - 'mark': MarkGenerator(), - 'param': ParameterSet.param, - } +def param(*values, **kw): + return ParameterSet.param(*values, **kw) def pytest_addoption(parser): @@ -225,9 +221,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): @@ -241,11 +241,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 +265,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 +387,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 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/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/python.py b/_pytest/python.py index e763aa8882b..1f540c84fea 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -9,19 +9,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() @@ -49,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)." % ( @@ -126,23 +127,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(): @@ -155,6 +141,7 @@ def pytest_pyfunc_call(pyfuncitem): testfunction(**testargs) return True + def pytest_collect_file(path, parent): ext = path.ext if ext == ".py": @@ -170,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() @@ -266,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) @@ -403,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): @@ -590,7 +578,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, @@ -788,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 ParameterSet + from _pytest.mark import MARK_GEN, ParameterSet from py.io import saferepr if not isinstance(argnames, (tuple, list)): @@ -801,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], @@ -883,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: @@ -958,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 @@ -968,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)] @@ -1046,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() @@ -1234,7 +1224,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): @@ -1250,7 +1244,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. @@ -1521,7 +1515,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 579439fe4f0..7dce842f680 100644 --- a/_pytest/recwarn.py +++ b/_pytest/recwarn.py @@ -7,10 +7,10 @@ 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: @@ -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``. @@ -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 07f4be019a3..87dd240c818 100644 --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -6,17 +6,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 @@ -264,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 228c529359e..e7922da7d2e 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,11 +59,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() """ @@ -102,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} @@ -142,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 @@ -166,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 @@ -175,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 @@ -206,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): @@ -218,10 +216,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() @@ -241,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 \ @@ -309,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") @@ -326,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'): @@ -357,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', []) @@ -372,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/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: 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..4e4ccb32dd4 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, param +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,44 @@ 'hookspec', 'hookimpl', '__version__', + 'register_assert_rewrite', + 'freeze_includes', + 'set_trace', + 'warns', + 'deprecated_call', + 'fixture', + 'yield_fixture', + 'fail', + 'skip', + 'xfail', + 'importorskip', + 'exit', + 'mark', + 'param', + '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)