Merge pull request #2315 from RonnyPfannschmidt/namespace-hook

remove pytest internal usage of the namespace hook
This commit is contained in:
Bruno Oliveira 2017-04-21 16:24:20 -03:00 committed by GitHub
commit 25371ddbfd
20 changed files with 309 additions and 212 deletions

View File

@ -27,6 +27,10 @@ New Features
Changes 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 * 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`_). compatibility with Python 2. Thanks to `@MichalTHEDUDE`_ and `@mandeep`_ for the PR (`#2147`_).

View File

@ -25,9 +25,6 @@ def pytest_addoption(parser):
expression information.""") expression information.""")
def pytest_namespace():
return {'register_assert_rewrite': register_assert_rewrite}
def register_assert_rewrite(*names): def register_assert_rewrite(*names):
"""Register one or more module names to be rewritten on import. """Register one or more module names to be rewritten on import.

View File

@ -254,6 +254,29 @@ else:
return v.encode('ascii', errors) 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: if _PY2:
from py.io import TextIO as CaptureIO from py.io import TextIO as CaptureIO
else: else:
@ -268,3 +291,12 @@ else:
def getvalue(self): def getvalue(self):
return self.buffer.getvalue().decode('UTF-8') 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

View File

@ -100,6 +100,7 @@ default_plugins = (
"junitxml resultlog doctest cacheprovider freeze_support " "junitxml resultlog doctest cacheprovider freeze_support "
"setuponly setupplan warnings").split() "setuponly setupplan warnings").split()
builtin_plugins = set(default_plugins) builtin_plugins = set(default_plugins)
builtin_plugins.add("pytester") builtin_plugins.add("pytester")

View File

@ -3,7 +3,6 @@ from __future__ import absolute_import, division, print_function
import pdb import pdb
import sys import sys
import pytest
def pytest_addoption(parser): def pytest_addoption(parser):
@ -16,8 +15,6 @@ def pytest_addoption(parser):
help="start a custom interactive Python debugger on errors. " help="start a custom interactive Python debugger on errors. "
"For example: --pdbcls=IPython.terminal.debugger:TerminalPdb") "For example: --pdbcls=IPython.terminal.debugger:TerminalPdb")
def pytest_namespace():
return {'set_trace': pytestPDB().set_trace}
def pytest_configure(config): def pytest_configure(config):
if config.getvalue("usepdb_cls"): if config.getvalue("usepdb_cls"):
@ -37,31 +34,33 @@ def pytest_configure(config):
pytestPDB._config = None pytestPDB._config = None
pytestPDB._pdb_cls = pdb.Pdb pytestPDB._pdb_cls = pdb.Pdb
pdb.set_trace = pytest.set_trace pdb.set_trace = pytestPDB.set_trace
pytestPDB._pluginmanager = config.pluginmanager pytestPDB._pluginmanager = config.pluginmanager
pytestPDB._config = config pytestPDB._config = config
pytestPDB._pdb_cls = pdb_cls pytestPDB._pdb_cls = pdb_cls
config._cleanup.append(fin) config._cleanup.append(fin)
class pytestPDB(object): class pytestPDB(object):
""" Pseudo PDB that defers to the real pdb. """ """ Pseudo PDB that defers to the real pdb. """
_pluginmanager = None _pluginmanager = None
_config = None _config = None
_pdb_cls = pdb.Pdb _pdb_cls = pdb.Pdb
def set_trace(self): @classmethod
def set_trace(cls):
""" invoke PDB set_trace debugging, dropping any IO capturing. """ """ invoke PDB set_trace debugging, dropping any IO capturing. """
import _pytest.config import _pytest.config
frame = sys._getframe().f_back frame = sys._getframe().f_back
if self._pluginmanager is not None: if cls._pluginmanager is not None:
capman = self._pluginmanager.getplugin("capturemanager") capman = cls._pluginmanager.getplugin("capturemanager")
if capman: if capman:
capman.suspendcapture(in_=True) capman.suspendcapture(in_=True)
tw = _pytest.config.create_terminal_writer(self._config) tw = _pytest.config.create_terminal_writer(cls._config)
tw.line() tw.line()
tw.sep(">", "PDB set_trace (IO-capturing turned off)") tw.sep(">", "PDB set_trace (IO-capturing turned off)")
self._pluginmanager.hook.pytest_enter_pdb(config=self._config) cls._pluginmanager.hook.pytest_enter_pdb(config=cls._config)
self._pdb_cls().set_trace(frame) cls._pdb_cls().set_trace(frame)
class PdbInvoke(object): class PdbInvoke(object):
@ -75,7 +74,7 @@ class PdbInvoke(object):
def pytest_internalerror(self, excrepr, excinfo): def pytest_internalerror(self, excrepr, excinfo):
for line in str(excrepr).split("\n"): for line in str(excrepr).split("\n"):
sys.stderr.write("INTERNALERROR> %s\n" %line) sys.stderr.write("INTERNALERROR> %s\n" % line)
sys.stderr.flush() sys.stderr.flush()
tb = _postmortem_traceback(excinfo) tb = _postmortem_traceback(excinfo)
post_mortem(tb) post_mortem(tb)

View File

@ -4,7 +4,6 @@ import sys
from py._code.code import FormattedExcinfo from py._code.code import FormattedExcinfo
import py import py
import pytest
import warnings import warnings
import inspect import inspect
@ -17,8 +16,16 @@ from _pytest.compat import (
getlocation, getfuncargnames, getlocation, getfuncargnames,
safe_getattr, safe_getattr,
) )
from _pytest.runner import fail
from _pytest.compat import FuncargnamesCompatAttr
def pytest_sessionstart(session): 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) session._fixturemanager = FixtureManager(session)
@ -45,19 +52,6 @@ def scopeproperty(name=None, doc=None):
return decoratescope 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): def get_scope_node(node, scope):
cls = scopename2class.get(scope) cls = scopename2class.get(scope)
if cls is None: if cls is None:
@ -105,7 +99,7 @@ def add_funcarg_pseudo_fixture_def(collector, metafunc, fixturemanager):
if scope != "function": if scope != "function":
node = get_scope_node(collector, scope) node = get_scope_node(collector, scope)
if node is None: 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) # use module-level collector for class-scope (for now)
node = collector node = collector
if node and argname in node._name2pseudofixturedef: if node and argname in node._name2pseudofixturedef:
@ -221,17 +215,6 @@ def slice_items(items, ignore, scoped_argkeys_cache):
return items, None, None, None 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): def fillfixtures(function):
""" fill missing funcargs for a test function. """ """ fill missing funcargs for a test function. """
try: try:
@ -327,7 +310,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
@scopeproperty("class") @scopeproperty("class")
def cls(self): def cls(self):
""" class (can be None) where the test function was collected. """ """ 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: if clscol:
return clscol.obj return clscol.obj
@ -345,7 +328,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
@scopeproperty() @scopeproperty()
def module(self): def module(self):
""" python module object where the test function was collected. """ """ 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() @scopeproperty()
def fspath(self): def fspath(self):
@ -508,7 +491,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
source_lineno, source_lineno,
) )
) )
pytest.fail(msg) fail(msg)
else: else:
# indices might not be set if old-style metafunc.addcall() was used # indices might not be set if old-style metafunc.addcall() was used
param_index = funcitem.callspec.indices.get(argname, 0) param_index = funcitem.callspec.indices.get(argname, 0)
@ -541,9 +524,9 @@ class FixtureRequest(FuncargnamesCompatAttr):
if scopemismatch(invoking_scope, requested_scope): if scopemismatch(invoking_scope, requested_scope):
# try to report something helpful # try to report something helpful
lines = self._factorytraceback() lines = self._factorytraceback()
pytest.fail("ScopeMismatch: You tried to access the %r scoped " fail("ScopeMismatch: You tried to access the %r scoped "
"fixture %r with a %r scoped request object, " "fixture %r with a %r scoped request object, "
"involved factories\n%s" %( "involved factories\n%s" % (
(requested_scope, argname, invoking_scope, "\n".join(lines))), (requested_scope, argname, invoking_scope, "\n".join(lines))),
pytrace=False) pytrace=False)
@ -554,7 +537,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
fs, lineno = getfslineno(factory) fs, lineno = getfslineno(factory)
p = self._pyfuncitem.session.fspath.bestrelpath(fs) p = self._pyfuncitem.session.fspath.bestrelpath(fs)
args = _format_args(factory) args = _format_args(factory)
lines.append("%s:%d: def %s%s" %( lines.append("%s:%d: def %s%s" % (
p, lineno, factory.__name__, args)) p, lineno, factory.__name__, args))
return lines return lines
@ -699,9 +682,10 @@ def fail_fixturefunc(fixturefunc, msg):
fs, lineno = getfslineno(fixturefunc) fs, lineno = getfslineno(fixturefunc)
location = "%s:%s" % (fs, lineno+1) location = "%s:%s" % (fs, lineno+1)
source = _pytest._code.Source(fixturefunc) source = _pytest._code.Source(fixturefunc)
pytest.fail(msg + ":\n\n" + str(source.indent()) + "\n" + location, fail(msg + ":\n\n" + str(source.indent()) + "\n" + location,
pytrace=False) pytrace=False)
def call_fixture_func(fixturefunc, request, kwargs): def call_fixture_func(fixturefunc, request, kwargs):
yieldctx = is_generator(fixturefunc) yieldctx = is_generator(fixturefunc)
if yieldctx: if yieldctx:

View File

@ -5,9 +5,6 @@ pytest
from __future__ import absolute_import, division, print_function from __future__ import absolute_import, division, print_function
def pytest_namespace():
return {'freeze_includes': freeze_includes}
def freeze_includes(): def freeze_includes():
""" """

View File

@ -16,7 +16,9 @@ def pytest_addhooks(pluginmanager):
@hookspec(historic=True) @hookspec(historic=True)
def pytest_namespace(): 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 the pytest namespace. This hook is called at plugin registration
time. time.
""" """

View File

@ -8,14 +8,13 @@ import sys
import _pytest import _pytest
import _pytest._code import _pytest._code
import py import py
import pytest
try: try:
from collections import MutableMapping as MappingMixin from collections import MutableMapping as MappingMixin
except ImportError: except ImportError:
from UserDict import DictMixin as MappingMixin from UserDict import DictMixin as MappingMixin
from _pytest.config import directory_arg from _pytest.config import directory_arg, UsageError, hookimpl
from _pytest.runner import collect_one_node from _pytest.runner import collect_one_node, exit
tracebackcutdir = py.path.local(_pytest.__file__).dirpath() tracebackcutdir = py.path.local(_pytest.__file__).dirpath()
@ -27,6 +26,7 @@ EXIT_INTERNALERROR = 3
EXIT_USAGEERROR = 4 EXIT_USAGEERROR = 4
EXIT_NOTESTSCOLLECTED = 5 EXIT_NOTESTSCOLLECTED = 5
def pytest_addoption(parser): def pytest_addoption(parser):
parser.addini("norecursedirs", "directory patterns to avoid for recursion", parser.addini("norecursedirs", "directory patterns to avoid for recursion",
type="args", default=['.*', 'build', 'dist', 'CVS', '_darcs', '{arch}', '*.egg', 'venv']) 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.") help="base temporary directory for this test run.")
def pytest_namespace(): def pytest_namespace():
collect = dict(Item=Item, Collector=Collector, File=File, Session=Session) """keeping this one works around a deeper startup issue in pytest
return dict(collect=collect)
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): def pytest_configure(config):
pytest.config = config # compatibility __import__('pytest').config = config # compatibiltiy
def wrap_session(config, doit): def wrap_session(config, doit):
@ -98,12 +103,11 @@ def wrap_session(config, doit):
config.hook.pytest_sessionstart(session=session) config.hook.pytest_sessionstart(session=session)
initstate = 2 initstate = 2
session.exitstatus = doit(config, session) or 0 session.exitstatus = doit(config, session) or 0
except pytest.UsageError: except UsageError:
raise raise
except KeyboardInterrupt: except KeyboardInterrupt:
excinfo = _pytest._code.ExceptionInfo() excinfo = _pytest._code.ExceptionInfo()
if initstate < 2 and isinstance( if initstate < 2 and isinstance(excinfo.value, exit.Exception):
excinfo.value, pytest.exit.Exception):
sys.stderr.write('{0}: {1}\n'.format( sys.stderr.write('{0}: {1}\n'.format(
excinfo.typename, excinfo.value.msg)) excinfo.typename, excinfo.value.msg))
config.hook.pytest_keyboard_interrupt(excinfo=excinfo) config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
@ -125,9 +129,11 @@ def wrap_session(config, doit):
config._ensure_unconfigure() config._ensure_unconfigure()
return session.exitstatus return session.exitstatus
def pytest_cmdline_main(config): def pytest_cmdline_main(config):
return wrap_session(config, _main) return wrap_session(config, _main)
def _main(config, session): def _main(config, session):
""" default command line protocol for initialization, session, """ default command line protocol for initialization, session,
running tests and reporting. """ running tests and reporting. """
@ -139,9 +145,11 @@ def _main(config, session):
elif session.testscollected == 0: elif session.testscollected == 0:
return EXIT_NOTESTSCOLLECTED return EXIT_NOTESTSCOLLECTED
def pytest_collection(session): def pytest_collection(session):
return session.perform_collect() return session.perform_collect()
def pytest_runtestloop(session): def pytest_runtestloop(session):
if (session.testsfailed and if (session.testsfailed and
not session.config.option.continue_on_collection_errors): not session.config.option.continue_on_collection_errors):
@ -158,6 +166,7 @@ def pytest_runtestloop(session):
raise session.Interrupted(session.shouldstop) raise session.Interrupted(session.shouldstop)
return True return True
def pytest_ignore_collect(path, config): def pytest_ignore_collect(path, config):
p = path.dirpath() p = path.dirpath()
ignore_paths = config._getconftest_pathlist("collect_ignore", path=p) ignore_paths = config._getconftest_pathlist("collect_ignore", path=p)
@ -205,7 +214,7 @@ class _CompatProperty(object):
# "usage of {owner!r}.{name} is deprecated, please use pytest.{name} instead".format( # "usage of {owner!r}.{name} is deprecated, please use pytest.{name} instead".format(
# name=self.name, owner=type(owner).__name__), # name=self.name, owner=type(owner).__name__),
# PendingDeprecationWarning, stacklevel=2) # PendingDeprecationWarning, stacklevel=2)
return getattr(pytest, self.name) return getattr(__import__('pytest'), self.name)
@ -289,7 +298,7 @@ class Node(object):
def _getcustomclass(self, name): def _getcustomclass(self, name):
maybe_compatprop = getattr(type(self), name) maybe_compatprop = getattr(type(self), name)
if isinstance(maybe_compatprop, _CompatProperty): if isinstance(maybe_compatprop, _CompatProperty):
return getattr(pytest, name) return getattr(__import__('pytest'), name)
else: else:
cls = getattr(self, name) cls = getattr(self, name)
# TODO: reenable in the features branch # TODO: reenable in the features branch
@ -369,9 +378,9 @@ class Node(object):
``marker`` can be a string or pytest.mark.* instance. ``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): if isinstance(marker, py.builtin._basestring):
marker = getattr(pytest.mark, marker) marker = getattr(MARK_GEN, marker)
elif not isinstance(marker, MarkDecorator): elif not isinstance(marker, MarkDecorator):
raise ValueError("is not a string or pytest.mark.* Marker") raise ValueError("is not a string or pytest.mark.* Marker")
self.keywords[marker.name] = marker self.keywords[marker.name] = marker
@ -557,12 +566,12 @@ class Session(FSCollector):
def _makeid(self): def _makeid(self):
return "" return ""
@pytest.hookimpl(tryfirst=True) @hookimpl(tryfirst=True)
def pytest_collectstart(self): def pytest_collectstart(self):
if self.shouldstop: if self.shouldstop:
raise self.Interrupted(self.shouldstop) raise self.Interrupted(self.shouldstop)
@pytest.hookimpl(tryfirst=True) @hookimpl(tryfirst=True)
def pytest_runtest_logreport(self, report): def pytest_runtest_logreport(self, report):
if report.failed and not hasattr(report, 'wasxfail'): if report.failed and not hasattr(report, 'wasxfail'):
self.testsfailed += 1 self.testsfailed += 1
@ -622,8 +631,8 @@ class Session(FSCollector):
for arg, exc in self._notfound: for arg, exc in self._notfound:
line = "(no name %r in any of %r)" % (arg, exc.args[0]) line = "(no name %r in any of %r)" % (arg, exc.args[0])
errors.append("not found: %s\n%s" % (arg, line)) errors.append("not found: %s\n%s" % (arg, line))
#XXX: test this # XXX: test this
raise pytest.UsageError(*errors) raise UsageError(*errors)
if not genitems: if not genitems:
return rep.result return rep.result
else: else:
@ -651,7 +660,7 @@ class Session(FSCollector):
names = self._parsearg(arg) names = self._parsearg(arg)
path = names.pop(0) path = names.pop(0)
if path.check(dir=1): 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), for path in path.visit(fil=lambda x: x.check(file=1),
rec=self._recurse, bf=True, sort=True): rec=self._recurse, bf=True, sort=True):
for x in self._collectfile(path): for x in self._collectfile(path):
@ -710,9 +719,11 @@ class Session(FSCollector):
path = self.config.invocation_dir.join(relpath, abs=True) path = self.config.invocation_dir.join(relpath, abs=True)
if not path.check(): if not path.check():
if self.config.option.pyargs: 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: else:
raise pytest.UsageError("file not found: " + arg) raise UsageError("file not found: " + arg)
parts[0] = path parts[0] = path
return parts return parts
@ -735,11 +746,11 @@ class Session(FSCollector):
nextnames = names[1:] nextnames = names[1:]
resultnodes = [] resultnodes = []
for node in matching: for node in matching:
if isinstance(node, pytest.Item): if isinstance(node, Item):
if not names: if not names:
resultnodes.append(node) resultnodes.append(node)
continue continue
assert isinstance(node, pytest.Collector) assert isinstance(node, Collector)
rep = collect_one_node(node) rep = collect_one_node(node)
if rep.passed: if rep.passed:
has_matched = False has_matched = False
@ -757,11 +768,11 @@ class Session(FSCollector):
def genitems(self, node): def genitems(self, node):
self.trace("genitems", node) self.trace("genitems", node)
if isinstance(node, pytest.Item): if isinstance(node, Item):
node.ihook.pytest_itemcollected(item=node) node.ihook.pytest_itemcollected(item=node)
yield node yield node
else: else:
assert isinstance(node, pytest.Collector) assert isinstance(node, Collector)
rep = collect_one_node(node) rep = collect_one_node(node)
if rep.passed: if rep.passed:
for subnode in rep.result: for subnode in rep.result:

View File

@ -66,12 +66,8 @@ class MarkerError(Exception):
"""Error in use of a pytest marker/attribute.""" """Error in use of a pytest marker/attribute."""
def param(*values, **kw):
def pytest_namespace(): return ParameterSet.param(*values, **kw)
return {
'mark': MarkGenerator(),
'param': ParameterSet.param,
}
def pytest_addoption(parser): def pytest_addoption(parser):
@ -225,9 +221,13 @@ def matchkeyword(colitem, keywordexpr):
def pytest_configure(config): def pytest_configure(config):
import pytest config._old_mark_config = MARK_GEN._config
if config.option.strict: 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): class MarkGenerator(object):
@ -241,11 +241,13 @@ class MarkGenerator(object):
will set a 'slowtest' :class:`MarkInfo` object will set a 'slowtest' :class:`MarkInfo` object
on the ``test_function`` object. """ on the ``test_function`` object. """
_config = None
def __getattr__(self, name): def __getattr__(self, name):
if name[0] == "_": if name[0] == "_":
raise AttributeError("Marker name must NOT start with underscore") raise AttributeError("Marker name must NOT start with underscore")
if hasattr(self, '_config'): if self._config is not None:
self._check(name) self._check(name)
return MarkDecorator(Mark(name, (), {})) return MarkDecorator(Mark(name, (), {}))
@ -263,6 +265,7 @@ class MarkGenerator(object):
if name not in self._markers: if name not in self._markers:
raise AttributeError("%r not a registered marker" % (name,)) raise AttributeError("%r not a registered marker" % (name,))
def istestfunc(func): def istestfunc(func):
return hasattr(func, "__call__") and \ return hasattr(func, "__call__") and \
getattr(func, "__name__", "<lambda>") != "<lambda>" getattr(func, "__name__", "<lambda>") != "<lambda>"
@ -384,3 +387,6 @@ class MarkInfo(object):
def __iter__(self): def __iter__(self):
""" yield MarkInfo objects each relating to a marking-call. """ """ yield MarkInfo objects each relating to a marking-call. """
return imap(MarkInfo, self._marks) return imap(MarkInfo, self._marks)
MARK_GEN = MarkGenerator()

View File

@ -1,17 +1,17 @@
""" monkeypatching and mocking functionality. """ """ monkeypatching and mocking functionality. """
from __future__ import absolute_import, division, print_function from __future__ import absolute_import, division, print_function
import os, sys import os
import sys
import re import re
from py.builtin import _basestring from py.builtin import _basestring
from _pytest.fixtures import fixture
import pytest
RE_IMPORT_ERROR_NAME = re.compile("^No module named (.*)$") RE_IMPORT_ERROR_NAME = re.compile("^No module named (.*)$")
@pytest.fixture @fixture
def monkeypatch(): def monkeypatch():
"""The returned ``monkeypatch`` fixture provides these """The returned ``monkeypatch`` fixture provides these
helper methods to modify objects, dictionaries or os.environ:: helper methods to modify objects, dictionaries or os.environ::

View File

@ -4,8 +4,8 @@ from __future__ import absolute_import, division, print_function
import sys import sys
import py import py
import pytest from _pytest import unittest, runner, python
from _pytest import unittest from _pytest.config import hookimpl
def get_skip_exceptions(): def get_skip_exceptions():
@ -20,19 +20,19 @@ def get_skip_exceptions():
def pytest_runtest_makereport(item, call): def pytest_runtest_makereport(item, call):
if call.excinfo and call.excinfo.errisinstance(get_skip_exceptions()): if call.excinfo and call.excinfo.errisinstance(get_skip_exceptions()):
# let's substitute the excinfo with a pytest.skip one # let's substitute the excinfo with a pytest.skip one
call2 = call.__class__(lambda: call2 = call.__class__(
pytest.skip(str(call.excinfo.value)), call.when) lambda: runner.skip(str(call.excinfo.value)), call.when)
call.excinfo = call2.excinfo call.excinfo = call2.excinfo
@pytest.hookimpl(trylast=True) @hookimpl(trylast=True)
def pytest_runtest_setup(item): def pytest_runtest_setup(item):
if is_potential_nosetest(item): if is_potential_nosetest(item):
if isinstance(item.parent, pytest.Generator): if isinstance(item.parent, python.Generator):
gen = item.parent gen = item.parent
if not hasattr(gen, '_nosegensetup'): if not hasattr(gen, '_nosegensetup'):
call_optional(gen.obj, 'setup') call_optional(gen.obj, 'setup')
if isinstance(gen.parent, pytest.Instance): if isinstance(gen.parent, python.Instance):
call_optional(gen.parent.obj, 'setup') call_optional(gen.parent.obj, 'setup')
gen._nosegensetup = True gen._nosegensetup = True
if not call_optional(item.obj, 'setup'): if not call_optional(item.obj, 'setup'):
@ -51,14 +51,14 @@ def teardown_nose(item):
def pytest_make_collect_report(collector): def pytest_make_collect_report(collector):
if isinstance(collector, pytest.Generator): if isinstance(collector, python.Generator):
call_optional(collector.obj, 'setup') call_optional(collector.obj, 'setup')
def is_potential_nosetest(item): def is_potential_nosetest(item):
# extra check needed since we do not do nose style setup/teardown # extra check needed since we do not do nose style setup/teardown
# on direct unittest style classes # on direct unittest style classes
return isinstance(item, pytest.Function) and \ return isinstance(item, python.Function) and \
not isinstance(item, unittest.TestCaseFunction) not isinstance(item, unittest.TestCaseFunction)

View File

@ -9,19 +9,20 @@ import math
from itertools import count from itertools import count
import py import py
import pytest
from _pytest.mark import MarkerError from _pytest.mark import MarkerError
from _pytest.config import hookimpl
import _pytest import _pytest
import _pytest._pluggy as pluggy import _pytest._pluggy as pluggy
from _pytest import fixtures from _pytest import fixtures
from _pytest import main
from _pytest.compat import ( from _pytest.compat import (
isclass, isfunction, is_generator, _escape_strings, isclass, isfunction, is_generator, _escape_strings,
REGEX_TYPE, STRING_TYPES, NoneType, NOTSET, REGEX_TYPE, STRING_TYPES, NoneType, NOTSET,
get_real_func, getfslineno, safe_getattr, get_real_func, getfslineno, safe_getattr,
getlocation, enum, getlocation, enum,
) )
from _pytest.runner import fail
cutdir1 = py.path.local(pluggy.__file__.rstrip("oc")) cutdir1 = py.path.local(pluggy.__file__.rstrip("oc"))
cutdir2 = py.path.local(_pytest.__file__).dirpath() cutdir2 = py.path.local(_pytest.__file__).dirpath()
@ -49,7 +50,7 @@ def filter_traceback(entry):
def pyobj_property(name): def pyobj_property(name):
def get(self): def get(self):
node = self.getparent(getattr(pytest, name)) node = self.getparent(getattr(__import__('pytest'), name))
if node is not None: if node is not None:
return node.obj return node.obj
doc = "python %s object this node was collected from (can be None)." % ( 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 " "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,
}
}
@hookimpl(trylast=True)
@pytest.hookimpl(trylast=True)
def pytest_pyfunc_call(pyfuncitem): def pytest_pyfunc_call(pyfuncitem):
testfunction = pyfuncitem.obj testfunction = pyfuncitem.obj
if pyfuncitem._isyieldedfunction(): if pyfuncitem._isyieldedfunction():
@ -155,6 +141,7 @@ def pytest_pyfunc_call(pyfuncitem):
testfunction(**testargs) testfunction(**testargs)
return True return True
def pytest_collect_file(path, parent): def pytest_collect_file(path, parent):
ext = path.ext ext = path.ext
if ext == ".py": if ext == ".py":
@ -170,7 +157,7 @@ def pytest_collect_file(path, parent):
def pytest_pycollect_makemodule(path, parent): def pytest_pycollect_makemodule(path, parent):
return Module(path, parent) return Module(path, parent)
@pytest.hookimpl(hookwrapper=True) @hookimpl(hookwrapper=True)
def pytest_pycollect_makeitem(collector, name, obj): def pytest_pycollect_makeitem(collector, name, obj):
outcome = yield outcome = yield
res = outcome.get_result() res = outcome.get_result()
@ -266,7 +253,7 @@ class PyobjMixin(PyobjContext):
assert isinstance(lineno, int) assert isinstance(lineno, int)
return fspath, lineno, modpath return fspath, lineno, modpath
class PyCollector(PyobjMixin, pytest.Collector): class PyCollector(PyobjMixin, main.Collector):
def funcnamefilter(self, name): def funcnamefilter(self, name):
return self._matches_prefix_or_glob_option('python_functions', 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): if not _marked(funcobj, pytestmark):
pytestmark(funcobj) pytestmark(funcobj)
class Module(pytest.File, PyCollector):
class Module(main.File, PyCollector):
""" Collector for test classes and functions. """ """ Collector for test classes and functions. """
def _getobj(self): def _getobj(self):
@ -590,7 +578,7 @@ class FunctionMixin(PyobjMixin):
entry.set_repr_style('short') entry.set_repr_style('short')
def _repr_failure_py(self, excinfo, style="long"): def _repr_failure_py(self, excinfo, style="long"):
if excinfo.errisinstance(pytest.fail.Exception): if excinfo.errisinstance(fail.Exception):
if not excinfo.value.pytrace: if not excinfo.value.pytrace:
return py._builtin._totext(excinfo.value) return py._builtin._totext(excinfo.value)
return super(FunctionMixin, self)._repr_failure_py(excinfo, return super(FunctionMixin, self)._repr_failure_py(excinfo,
@ -788,7 +776,7 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
to set a dynamic scope using test context or configuration. to set a dynamic scope using test context or configuration.
""" """
from _pytest.fixtures import scope2index from _pytest.fixtures import scope2index
from _pytest.mark import ParameterSet from _pytest.mark import MARK_GEN, ParameterSet
from py.io import saferepr from py.io import saferepr
if not isinstance(argnames, (tuple, list)): if not isinstance(argnames, (tuple, list)):
@ -801,12 +789,11 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
for x in argvalues] for x in argvalues]
del argvalues del argvalues
if not parameters: if not parameters:
fs, lineno = getfslineno(self.function) fs, lineno = getfslineno(self.function)
reason = "got empty parameter set %r, function %s at %s:%d" % ( reason = "got empty parameter set %r, function %s at %s:%d" % (
argnames, self.function.__name__, fs, lineno) argnames, self.function.__name__, fs, lineno)
mark = pytest.mark.skip(reason=reason) mark = MARK_GEN.skip(reason=reason)
parameters.append(ParameterSet( parameters.append(ParameterSet(
values=(NOTSET,) * len(argnames), values=(NOTSET,) * len(argnames),
marks=[mark], marks=[mark],
@ -883,7 +870,7 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
if funcargs is not None: if funcargs is not None:
for name in funcargs: for name in funcargs:
if name not in self.fixturenames: 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: else:
funcargs = {} funcargs = {}
if id is None: if id is None:
@ -958,6 +945,7 @@ def _idval(val, argname, idx, idfn, config=None):
return val.__name__ return val.__name__
return str(argname)+str(idx) return str(argname)+str(idx)
def _idvalset(idx, parameterset, argnames, idfn, ids, config=None): def _idvalset(idx, parameterset, argnames, idfn, ids, config=None):
if parameterset.id is not None: if parameterset.id is not None:
return parameterset.id return parameterset.id
@ -968,6 +956,7 @@ def _idvalset(idx, parameterset, argnames, idfn, ids, config=None):
else: else:
return _escape_strings(ids[idx]) return _escape_strings(ids[idx])
def idmaker(argnames, parametersets, idfn=None, ids=None, config=None): def idmaker(argnames, parametersets, idfn=None, ids=None, config=None):
ids = [_idvalset(valindex, parameterset, argnames, idfn, ids, config) ids = [_idvalset(valindex, parameterset, argnames, idfn, ids, config)
for valindex, parameterset in enumerate(parametersets)] for valindex, parameterset in enumerate(parametersets)]
@ -1046,6 +1035,7 @@ def showfixtures(config):
from _pytest.main import wrap_session from _pytest.main import wrap_session
return wrap_session(config, _showfixtures_main) return wrap_session(config, _showfixtures_main)
def _showfixtures_main(config, session): def _showfixtures_main(config, session):
import _pytest.config import _pytest.config
session.perform_collect() session.perform_collect()
@ -1234,7 +1224,11 @@ def raises(expected_exception, *args, **kwargs):
func(*args[1:], **kwargs) func(*args[1:], **kwargs)
except expected_exception: except expected_exception:
return _pytest._code.ExceptionInfo() return _pytest._code.ExceptionInfo()
pytest.fail(message) fail(message)
raises.Exception = fail.Exception
class RaisesContext(object): class RaisesContext(object):
def __init__(self, expected_exception, message, match_expr): def __init__(self, expected_exception, message, match_expr):
@ -1250,7 +1244,7 @@ class RaisesContext(object):
def __exit__(self, *tp): def __exit__(self, *tp):
__tracebackhide__ = True __tracebackhide__ = True
if tp[0] is None: if tp[0] is None:
pytest.fail(self.message) fail(self.message)
if sys.version_info < (2, 7): if sys.version_info < (2, 7):
# py26: on __exit__() exc_value often does not contain the # py26: on __exit__() exc_value often does not contain the
# exception value. # exception value.
@ -1521,7 +1515,7 @@ class ApproxNonIterable(object):
# the basic pytest Function item # 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 """ a Function Item is responsible for setting up and executing a
Python test function. Python test function.
""" """

View File

@ -7,10 +7,10 @@ import _pytest._code
import py import py
import sys import sys
import warnings import warnings
import pytest from _pytest.fixtures import yield_fixture
@pytest.yield_fixture @yield_fixture
def recwarn(): def recwarn():
"""Return a WarningsRecorder instance that provides these methods: """Return a WarningsRecorder instance that provides these methods:
@ -26,11 +26,6 @@ def recwarn():
yield wrec yield wrec
def pytest_namespace():
return {'deprecated_call': deprecated_call,
'warns': warns}
def deprecated_call(func=None, *args, **kwargs): def deprecated_call(func=None, *args, **kwargs):
""" assert that calling ``func(*args, **kwargs)`` triggers a """ assert that calling ``func(*args, **kwargs)`` triggers a
``DeprecationWarning`` or ``PendingDeprecationWarning``. ``DeprecationWarning`` or ``PendingDeprecationWarning``.
@ -195,7 +190,8 @@ class WarningsChecker(WarningsRecorder):
if not any(issubclass(r.category, self.expected_warning) if not any(issubclass(r.category, self.expected_warning)
for r in self): for r in self):
__tracebackhide__ = True __tracebackhide__ = True
pytest.fail("DID NOT WARN. No warnings of type {0} was emitted. " from _pytest.runner import fail
fail("DID NOT WARN. No warnings of type {0} was emitted. "
"The list of emitted warnings is: {1}.".format( "The list of emitted warnings is: {1}.".format(
self.expected_warning, self.expected_warning,
[each.message for each in self])) [each.message for each in self]))

View File

@ -6,17 +6,9 @@ import sys
from time import time from time import time
import py import py
import pytest
from _pytest._code.code import TerminalRepr, ExceptionInfo from _pytest._code.code import TerminalRepr, ExceptionInfo
def pytest_namespace():
return {
'fail' : fail,
'skip' : skip,
'importorskip' : importorskip,
'exit' : exit,
}
# #
# pytest plugin hooks # pytest plugin hooks
@ -264,7 +256,7 @@ def pytest_runtest_makereport(item, call):
if not isinstance(excinfo, ExceptionInfo): if not isinstance(excinfo, ExceptionInfo):
outcome = "failed" outcome = "failed"
longrepr = excinfo longrepr = excinfo
elif excinfo.errisinstance(pytest.skip.Exception): elif excinfo.errisinstance(skip.Exception):
outcome = "skipped" outcome = "skipped"
r = excinfo._getreprcrash() r = excinfo._getreprcrash()
longrepr = (str(r.path), r.lineno, r.message) longrepr = (str(r.path), r.lineno, r.message)

View File

@ -6,9 +6,9 @@ import sys
import traceback import traceback
import py import py
import pytest from _pytest.config import hookimpl
from _pytest.mark import MarkInfo, MarkDecorator from _pytest.mark import MarkInfo, MarkDecorator
from _pytest.runner import fail, skip
def pytest_addoption(parser): def pytest_addoption(parser):
group = parser.getgroup("general") group = parser.getgroup("general")
@ -25,6 +25,8 @@ def pytest_addoption(parser):
def pytest_configure(config): def pytest_configure(config):
if config.option.runxfail: if config.option.runxfail:
# yay a hack
import pytest
old = pytest.xfail old = pytest.xfail
config._cleanup.append(lambda: setattr(pytest, "xfail", old)) config._cleanup.append(lambda: setattr(pytest, "xfail", old))
@ -57,11 +59,7 @@ def pytest_configure(config):
) )
def pytest_namespace(): class XFailed(fail.Exception):
return dict(xfail=xfail)
class XFailed(pytest.fail.Exception):
""" raised from an explicit call to pytest.xfail() """ """ raised from an explicit call to pytest.xfail() """
@ -102,14 +100,14 @@ class MarkEvaluator(object):
except Exception: except Exception:
self.exc = sys.exc_info() self.exc = sys.exc_info()
if isinstance(self.exc[1], SyntaxError): if isinstance(self.exc[1], SyntaxError):
msg = [" " * (self.exc[1].offset + 4) + "^",] msg = [" " * (self.exc[1].offset + 4) + "^", ]
msg.append("SyntaxError: invalid syntax") msg.append("SyntaxError: invalid syntax")
else: else:
msg = traceback.format_exception_only(*self.exc[:2]) msg = traceback.format_exception_only(*self.exc[:2])
pytest.fail("Error evaluating %r expression\n" fail("Error evaluating %r expression\n"
" %s\n" " %s\n"
"%s" "%s"
%(self.name, self.expr, "\n".join(msg)), % (self.name, self.expr, "\n".join(msg)),
pytrace=False) pytrace=False)
def _getglobals(self): def _getglobals(self):
@ -142,7 +140,7 @@ class MarkEvaluator(object):
# XXX better be checked at collection time # XXX better be checked at collection time
msg = "you need to specify reason=STRING " \ msg = "you need to specify reason=STRING " \
"when using booleans as conditions." "when using booleans as conditions."
pytest.fail(msg) fail(msg)
result = bool(expr) result = bool(expr)
if result: if result:
self.result = True self.result = True
@ -166,7 +164,7 @@ class MarkEvaluator(object):
return expl return expl
@pytest.hookimpl(tryfirst=True) @hookimpl(tryfirst=True)
def pytest_runtest_setup(item): def pytest_runtest_setup(item):
# Check if skip or skipif are specified as pytest marks # Check if skip or skipif are specified as pytest marks
@ -175,23 +173,23 @@ def pytest_runtest_setup(item):
eval_skipif = MarkEvaluator(item, 'skipif') eval_skipif = MarkEvaluator(item, 'skipif')
if eval_skipif.istrue(): if eval_skipif.istrue():
item._evalskip = eval_skipif item._evalskip = eval_skipif
pytest.skip(eval_skipif.getexplanation()) skip(eval_skipif.getexplanation())
skip_info = item.keywords.get('skip') skip_info = item.keywords.get('skip')
if isinstance(skip_info, (MarkInfo, MarkDecorator)): if isinstance(skip_info, (MarkInfo, MarkDecorator)):
item._evalskip = True item._evalskip = True
if 'reason' in skip_info.kwargs: if 'reason' in skip_info.kwargs:
pytest.skip(skip_info.kwargs['reason']) skip(skip_info.kwargs['reason'])
elif skip_info.args: elif skip_info.args:
pytest.skip(skip_info.args[0]) skip(skip_info.args[0])
else: else:
pytest.skip("unconditional skip") skip("unconditional skip")
item._evalxfail = MarkEvaluator(item, 'xfail') item._evalxfail = MarkEvaluator(item, 'xfail')
check_xfail_no_run(item) check_xfail_no_run(item)
@pytest.mark.hookwrapper @hookimpl(hookwrapper=True)
def pytest_pyfunc_call(pyfuncitem): def pytest_pyfunc_call(pyfuncitem):
check_xfail_no_run(pyfuncitem) check_xfail_no_run(pyfuncitem)
outcome = yield outcome = yield
@ -206,7 +204,7 @@ def check_xfail_no_run(item):
evalxfail = item._evalxfail evalxfail = item._evalxfail
if evalxfail.istrue(): if evalxfail.istrue():
if not evalxfail.get('run', True): if not evalxfail.get('run', True):
pytest.xfail("[NOTRUN] " + evalxfail.getexplanation()) xfail("[NOTRUN] " + evalxfail.getexplanation())
def check_strict_xfail(pyfuncitem): def check_strict_xfail(pyfuncitem):
@ -218,10 +216,10 @@ def check_strict_xfail(pyfuncitem):
if is_strict_xfail: if is_strict_xfail:
del pyfuncitem._evalxfail del pyfuncitem._evalxfail
explanation = evalxfail.getexplanation() 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): def pytest_runtest_makereport(item, call):
outcome = yield outcome = yield
rep = outcome.get_result() rep = outcome.get_result()
@ -241,7 +239,7 @@ def pytest_runtest_makereport(item, call):
rep.wasxfail = rep.longrepr rep.wasxfail = rep.longrepr
elif item.config.option.runxfail: elif item.config.option.runxfail:
pass # don't interefere 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.wasxfail = "reason: " + call.excinfo.value.msg
rep.outcome = "skipped" rep.outcome = "skipped"
elif evalxfail and not rep.skipped and evalxfail.wasvalid() and \ elif evalxfail and not rep.skipped and evalxfail.wasvalid() and \
@ -309,12 +307,14 @@ def pytest_terminal_summary(terminalreporter):
for line in lines: for line in lines:
tr._tw.line(line) tr._tw.line(line)
def show_simple(terminalreporter, lines, stat, format): def show_simple(terminalreporter, lines, stat, format):
failed = terminalreporter.stats.get(stat) failed = terminalreporter.stats.get(stat)
if failed: if failed:
for rep in failed: for rep in failed:
pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid) pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
lines.append(format %(pos,)) lines.append(format % (pos,))
def show_xfailed(terminalreporter, lines): def show_xfailed(terminalreporter, lines):
xfailed = terminalreporter.stats.get("xfailed") xfailed = terminalreporter.stats.get("xfailed")
@ -326,13 +326,15 @@ def show_xfailed(terminalreporter, lines):
if reason: if reason:
lines.append(" " + str(reason)) lines.append(" " + str(reason))
def show_xpassed(terminalreporter, lines): def show_xpassed(terminalreporter, lines):
xpassed = terminalreporter.stats.get("xpassed") xpassed = terminalreporter.stats.get("xpassed")
if xpassed: if xpassed:
for rep in xpassed: for rep in xpassed:
pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid) pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
reason = rep.wasxfail reason = rep.wasxfail
lines.append("XPASS %s %s" %(pos, reason)) lines.append("XPASS %s %s" % (pos, reason))
def cached_eval(config, expr, d): def cached_eval(config, expr, d):
if not hasattr(config, '_evalcache'): if not hasattr(config, '_evalcache'):
@ -357,6 +359,7 @@ def folded_skips(skipped):
l.append((len(events),) + key) l.append((len(events),) + key)
return l return l
def show_skipped(terminalreporter, lines): def show_skipped(terminalreporter, lines):
tr = terminalreporter tr = terminalreporter
skipped = tr.stats.get('skipped', []) skipped = tr.stats.get('skipped', [])
@ -372,5 +375,6 @@ def show_skipped(terminalreporter, lines):
for num, fspath, lineno, reason in fskips: for num, fspath, lineno, reason in fskips:
if reason.startswith("Skipped: "): if reason.startswith("Skipped: "):
reason = reason[9:] reason = reason[9:]
lines.append("SKIP [%d] %s:%d: %s" % lines.append(
"SKIP [%d] %s:%d: %s" %
(num, fspath, lineno, reason)) (num, fspath, lineno, reason))

View File

@ -4,11 +4,12 @@ from __future__ import absolute_import, division, print_function
import sys import sys
import traceback import traceback
import pytest
# for transferring markers # for transferring markers
import _pytest._code import _pytest._code
from _pytest.python import transfer_markers from _pytest.config import hookimpl
from _pytest.skipping import MarkEvaluator 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): def pytest_pycollect_makeitem(collector, name, obj):
@ -22,7 +23,7 @@ def pytest_pycollect_makeitem(collector, name, obj):
return UnitTestCase(name, parent=collector) return UnitTestCase(name, parent=collector)
class UnitTestCase(pytest.Class): class UnitTestCase(Class):
# marker for fixturemanger.getfixtureinfo() # marker for fixturemanger.getfixtureinfo()
# to declare that our children do not support funcargs # to declare that our children do not support funcargs
nofuncargs = True nofuncargs = True
@ -46,7 +47,7 @@ class UnitTestCase(pytest.Class):
return return
self.session._fixturemanager.parsefactories(self, unittest=True) self.session._fixturemanager.parsefactories(self, unittest=True)
loader = TestLoader() loader = TestLoader()
module = self.getparent(pytest.Module).obj module = self.getparent(Module).obj
foundsomething = False foundsomething = False
for name in loader.getTestCaseNames(self.obj): for name in loader.getTestCaseNames(self.obj):
x = getattr(self.obj, name) x = getattr(self.obj, name)
@ -65,7 +66,7 @@ class UnitTestCase(pytest.Class):
yield TestCaseFunction('runTest', parent=self) yield TestCaseFunction('runTest', parent=self)
class TestCaseFunction(pytest.Function): class TestCaseFunction(Function):
_excinfo = None _excinfo = None
def setup(self): def setup(self):
@ -111,35 +112,36 @@ class TestCaseFunction(pytest.Function):
l = traceback.format_exception(*rawexcinfo) l = traceback.format_exception(*rawexcinfo)
l.insert(0, "NOTE: Incompatible Exception Representation, " l.insert(0, "NOTE: Incompatible Exception Representation, "
"displaying natively:\n\n") "displaying natively:\n\n")
pytest.fail("".join(l), pytrace=False) fail("".join(l), pytrace=False)
except (pytest.fail.Exception, KeyboardInterrupt): except (fail.Exception, KeyboardInterrupt):
raise raise
except: except:
pytest.fail("ERROR: Unknown Incompatible Exception " fail("ERROR: Unknown Incompatible Exception "
"representation:\n%r" %(rawexcinfo,), pytrace=False) "representation:\n%r" % (rawexcinfo,), pytrace=False)
except KeyboardInterrupt: except KeyboardInterrupt:
raise raise
except pytest.fail.Exception: except fail.Exception:
excinfo = _pytest._code.ExceptionInfo() excinfo = _pytest._code.ExceptionInfo()
self.__dict__.setdefault('_excinfo', []).append(excinfo) self.__dict__.setdefault('_excinfo', []).append(excinfo)
def addError(self, testcase, rawexcinfo): def addError(self, testcase, rawexcinfo):
self._addexcinfo(rawexcinfo) self._addexcinfo(rawexcinfo)
def addFailure(self, testcase, rawexcinfo): def addFailure(self, testcase, rawexcinfo):
self._addexcinfo(rawexcinfo) self._addexcinfo(rawexcinfo)
def addSkip(self, testcase, reason): def addSkip(self, testcase, reason):
try: try:
pytest.skip(reason) skip(reason)
except pytest.skip.Exception: except skip.Exception:
self._evalskip = MarkEvaluator(self, 'SkipTest') self._evalskip = MarkEvaluator(self, 'SkipTest')
self._evalskip.result = True self._evalskip.result = True
self._addexcinfo(sys.exc_info()) self._addexcinfo(sys.exc_info())
def addExpectedFailure(self, testcase, rawexcinfo, reason=""): def addExpectedFailure(self, testcase, rawexcinfo, reason=""):
try: try:
pytest.xfail(str(reason)) xfail(str(reason))
except pytest.xfail.Exception: except xfail.Exception:
self._addexcinfo(sys.exc_info()) self._addexcinfo(sys.exc_info())
def addUnexpectedSuccess(self, testcase, reason=""): def addUnexpectedSuccess(self, testcase, reason=""):
@ -179,13 +181,14 @@ class TestCaseFunction(pytest.Function):
self._testcase.debug() self._testcase.debug()
def _prunetraceback(self, excinfo): def _prunetraceback(self, excinfo):
pytest.Function._prunetraceback(self, excinfo) Function._prunetraceback(self, excinfo)
traceback = excinfo.traceback.filter( traceback = excinfo.traceback.filter(
lambda x:not x.frame.f_globals.get('__unittest')) lambda x: not x.frame.f_globals.get('__unittest'))
if traceback: if traceback:
excinfo.traceback = traceback excinfo.traceback = traceback
@pytest.hookimpl(tryfirst=True)
@hookimpl(tryfirst=True)
def pytest_runtest_makereport(item, call): def pytest_runtest_makereport(item, call):
if isinstance(item, TestCaseFunction): if isinstance(item, TestCaseFunction):
if item._excinfo: if item._excinfo:
@ -197,7 +200,8 @@ def pytest_runtest_makereport(item, call):
# twisted trial support # twisted trial support
@pytest.hookimpl(hookwrapper=True)
@hookimpl(hookwrapper=True)
def pytest_runtest_protocol(item): def pytest_runtest_protocol(item):
if isinstance(item, TestCaseFunction) and \ if isinstance(item, TestCaseFunction) and \
'twisted.trial.unittest' in sys.modules: 'twisted.trial.unittest' in sys.modules:

View File

@ -517,7 +517,6 @@ Initialization, command line and configuration hooks
.. autofunction:: pytest_load_initial_conftests .. autofunction:: pytest_load_initial_conftests
.. autofunction:: pytest_cmdline_preparse .. autofunction:: pytest_cmdline_preparse
.. autofunction:: pytest_cmdline_parse .. autofunction:: pytest_cmdline_parse
.. autofunction:: pytest_namespace
.. autofunction:: pytest_addoption .. autofunction:: pytest_addoption
.. autofunction:: pytest_cmdline_main .. autofunction:: pytest_cmdline_main
.. autofunction:: pytest_configure .. autofunction:: pytest_configure

View File

@ -2,19 +2,7 @@
""" """
pytest: unit and functional testing with Python. pytest: unit and functional testing with Python.
""" """
__all__ = [
'main',
'UsageError',
'cmdline',
'hookspec',
'hookimpl',
'__version__',
]
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 we are imported # else we are imported
@ -22,7 +10,69 @@ from _pytest.config import (
main, UsageError, _preloadplugins, cmdline, main, UsageError, _preloadplugins, cmdline,
hookspec, hookimpl 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 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,
)
_preloadplugins() # to populate pytest.* namespace so help(pytest) works set_trace = __pytestPDB.set_trace
__all__ = [
'main',
'UsageError',
'cmdline',
'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'
# we trigger the below "else" condition by the following import
import pytest
raise SystemExit(pytest.main())
else:
from _pytest.compat import _setup_collect_fakemodule
_preloadplugins() # to populate pytest.* namespace so help(pytest) works
_setup_collect_fakemodule()

25
testing/test_modimport.py Normal file
View File

@ -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)