diff options
author | holger krekel <holger@merlinux.eu> | 2011-03-06 09:25:40 +0100 |
---|---|---|
committer | holger krekel <holger@merlinux.eu> | 2011-03-06 09:25:40 +0100 |
commit | 1ba1e3fabee7550dbec4fbaa29509f26028922b5 (patch) | |
tree | 96340fc090285cddbb8a6ba12f69bc70b996b0c0 /_pytest | |
parent | merge default (diff) | |
download | pypy-1ba1e3fabee7550dbec4fbaa29509f26028922b5.tar.gz pypy-1ba1e3fabee7550dbec4fbaa29509f26028922b5.tar.bz2 pypy-1ba1e3fabee7550dbec4fbaa29509f26028922b5.zip |
update to current py and pytest trunk
Diffstat (limited to '_pytest')
-rw-r--r-- | _pytest/assertion.py | 17 | ||||
-rw-r--r-- | _pytest/capture.py | 14 | ||||
-rw-r--r-- | _pytest/main.py | 19 | ||||
-rw-r--r-- | _pytest/mark.py | 4 | ||||
-rw-r--r-- | _pytest/monkeypatch.py | 4 | ||||
-rw-r--r-- | _pytest/python.py | 55 | ||||
-rw-r--r-- | _pytest/recwarn.py | 3 | ||||
-rw-r--r-- | _pytest/skipping.py | 61 | ||||
-rw-r--r-- | _pytest/terminal.py | 2 | ||||
-rw-r--r-- | _pytest/tmpdir.py | 2 | ||||
-rw-r--r-- | _pytest/unittest.py | 4 |
11 files changed, 130 insertions, 55 deletions
diff --git a/_pytest/assertion.py b/_pytest/assertion.py index 8f09e66612..e2578242a9 100644 --- a/_pytest/assertion.py +++ b/_pytest/assertion.py @@ -12,7 +12,7 @@ def pytest_addoption(parser): help="disable python assert expression reinterpretation."), def pytest_configure(config): - # The _pytesthook attribute on the AssertionError is used by + # The _reprcompare attribute on the py.code module is used by # py._code._assertionnew to detect this plugin was loaded and in # turn call the hooks defined here as part of the # DebugInterpreter. @@ -51,7 +51,7 @@ except NameError: def pytest_assertrepr_compare(op, left, right): """return specialised explanations for some operators/operands""" width = 80 - 15 - len(op) - 2 # 15 chars indentation, 1 space around op - left_repr = py.io.saferepr(left, maxsize=width/2) + left_repr = py.io.saferepr(left, maxsize=int(width/2)) right_repr = py.io.saferepr(right, maxsize=width-len(left_repr)) summary = '%s %s %s' % (left_repr, op, right_repr) @@ -165,4 +165,15 @@ def _notin_text(term, text): head = text[:index] tail = text[index+len(term):] correct_text = head + tail - return _diff_text(correct_text, text) + diff = _diff_text(correct_text, text) + newdiff = ['%s is contained here:' % py.io.saferepr(term, maxsize=42)] + for line in diff: + if line.startswith('Skipping'): + continue + if line.startswith('- '): + continue + if line.startswith('+ '): + newdiff.append(' ' + line[2:]) + else: + newdiff.append(line) + return newdiff diff --git a/_pytest/capture.py b/_pytest/capture.py index 2da398383d..b7594be319 100644 --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -192,18 +192,16 @@ class CaptureManager: return rep def pytest_funcarg__capsys(request): - """captures writes to sys.stdout/sys.stderr and makes - them available successively via a ``capsys.readouterr()`` method - which returns a ``(out, err)`` tuple of captured snapshot strings. + """enables capturing of writes to sys.stdout/sys.stderr and makes + captured output available via ``capsys.readouterr()`` method calls + which return a ``(out, err)`` tuple. """ return CaptureFuncarg(py.io.StdCapture) def pytest_funcarg__capfd(request): - """captures writes to file descriptors 1 and 2 and makes - snapshotted ``(out, err)`` string tuples available - via the ``capsys.readouterr()`` method. If the underlying - platform does not have ``os.dup`` (e.g. Jython) tests using - this funcarg will automatically skip. + """enables capturing of writes to file descriptors 1 and 2 and makes + captured output available via ``capsys.readouterr()`` method calls + which return a ``(out, err)`` tuple. """ if not hasattr(os, 'dup'): py.test.skip("capfd funcarg needs os.dup") diff --git a/_pytest/main.py b/_pytest/main.py index f1be30601c..a257ea56f2 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -121,9 +121,6 @@ class HookProxy: def compatproperty(name): def fget(self): - #print "retrieving %r property from %s" %(name, self.fspath) - py.log._apiwarn("2.0", "use pytest.%s for " - "test collection and item classes" % name) return getattr(pytest, name) return property(fget, None, None, "deprecated attribute %r, use pytest.%s" % (name,name)) @@ -157,6 +154,14 @@ class Node(object): File = compatproperty("File") Item = compatproperty("Item") + def _getcustomclass(self, name): + cls = getattr(self, name) + if cls != getattr(pytest, name): + py.log._apiwarn("2.0", "use of node.%s is deprecated, " + "use pytest_pycollect_makeitem(...) to create custom " + "collection nodes" % name) + return cls + def __repr__(self): return "<%s %r>" %(self.__class__.__name__, getattr(self, 'name', None)) @@ -449,7 +454,7 @@ class Session(FSCollector): p = p.dirpath() else: p = p.new(basename=p.purebasename+".py") - return p + return str(p) def _parsearg(self, arg): """ return (fspath, names) tuple after checking the file exists. """ @@ -495,9 +500,15 @@ class Session(FSCollector): node.ihook.pytest_collectstart(collector=node) rep = node.ihook.pytest_make_collect_report(collector=node) if rep.passed: + has_matched = False for x in rep.result: if x.name == name: resultnodes.extend(self.matchnodes([x], nextnames)) + has_matched = True + # XXX accept IDs that don't have "()" for class instances + if not has_matched and len(rep.result) == 1 and x.name == "()": + nextnames.insert(0, name) + resultnodes.extend(self.matchnodes([x], nextnames)) node.ihook.pytest_collectreport(report=rep) return resultnodes diff --git a/_pytest/mark.py b/_pytest/mark.py index 5383e3a9d5..d9a45930c9 100644 --- a/_pytest/mark.py +++ b/_pytest/mark.py @@ -89,8 +89,8 @@ class MarkGenerator: class MarkDecorator: """ A decorator for test functions and test classes. When applied it will create :class:`MarkInfo` objects which may be - :ref:`retrieved by hooks as item keywords` MarkDecorator instances - are usually created by writing:: + :ref:`retrieved by hooks as item keywords <excontrolskip>`. + MarkDecorator instances are often created like this:: mark1 = py.test.mark.NAME # simple MarkDecorator mark2 = py.test.mark.NAME(name1=value) # parametrized MarkDecorator diff --git a/_pytest/monkeypatch.py b/_pytest/monkeypatch.py index 1ab0b382f6..0a530b717b 100644 --- a/_pytest/monkeypatch.py +++ b/_pytest/monkeypatch.py @@ -14,8 +14,8 @@ def pytest_funcarg__monkeypatch(request): monkeypatch.delenv(name, value, raising=True) monkeypatch.syspath_prepend(path) - All modifications will be undone when the requesting - test function finished its execution. The ``raising`` + All modifications will be undone after the requesting + test function has finished. The ``raising`` parameter determines if a KeyError or AttributeError will be raised if the set/deletion operation has no target. """ diff --git a/_pytest/python.py b/_pytest/python.py index a2b7bb35d1..61a3f63c90 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -73,7 +73,8 @@ def pytest_pycollect_makeitem(__multicall__, collector, name, obj): if collector._istestclasscandidate(name, obj): #if hasattr(collector.obj, 'unittest'): # return # we assume it's a mixin class for a TestCase derived one - return collector.Class(name, parent=collector) + Class = collector._getcustomclass("Class") + return Class(name, parent=collector) elif collector.funcnamefilter(name) and hasattr(obj, '__call__'): if is_generator(obj): return Generator(name, parent=collector) @@ -213,16 +214,18 @@ class PyCollectorMixin(PyobjMixin, pytest.Collector): extra.append(cls()) plugins = self.getplugins() + extra gentesthook.pcall(plugins, metafunc=metafunc) + Function = self._getcustomclass("Function") if not metafunc._calls: - return self.Function(name, parent=self) + return Function(name, parent=self) l = [] for callspec in metafunc._calls: subname = "%s[%s]" %(name, callspec.id) - function = self.Function(name=subname, parent=self, + function = Function(name=subname, parent=self, callspec=callspec, callobj=funcobj, keywords={callspec.id:True}) l.append(function) return l + class Module(pytest.File, PyCollectorMixin): def _getobj(self): return self._memoizedcall('_obj', self._importtestmodule) @@ -272,7 +275,7 @@ class Module(pytest.File, PyCollectorMixin): class Class(PyCollectorMixin, pytest.Collector): def collect(self): - return [self.Instance(name="()", parent=self)] + return [self._getcustomclass("Instance")(name="()", parent=self)] def setup(self): setup_class = getattr(self.obj, 'setup_class', None) @@ -297,13 +300,8 @@ class Instance(PyCollectorMixin, pytest.Collector): class FunctionMixin(PyobjMixin): """ mixin for the code common to Function and Generator. """ - def setup(self): """ perform setup for this test function. """ - if inspect.ismethod(self.obj): - name = 'setup_method' - else: - name = 'setup_function' if hasattr(self, '_preservedparent'): obj = self._preservedparent elif isinstance(self.parent, Instance): @@ -311,6 +309,10 @@ class FunctionMixin(PyobjMixin): self.obj = self._getobj() else: obj = self.parent.obj + if inspect.ismethod(self.obj): + name = 'setup_method' + else: + name = 'setup_function' setup_func_or_method = getattr(obj, name, None) if setup_func_or_method is not None: setup_func_or_method(self.obj) @@ -487,10 +489,11 @@ def hasinit(obj): return True -def getfuncargnames(function): +def getfuncargnames(function, startindex=None): # XXX merge with main.py's varnames argnames = py.std.inspect.getargs(py.code.getrawcode(function))[0] - startindex = py.std.inspect.ismethod(function) and 1 or 0 + if startindex is None: + startindex = py.std.inspect.ismethod(function) and 1 or 0 defaults = getattr(function, 'func_defaults', getattr(function, '__defaults__', None)) or () numdefaults = len(defaults) @@ -519,7 +522,8 @@ class Metafunc: self.config = config self.module = module self.function = function - self.funcargnames = getfuncargnames(function) + self.funcargnames = getfuncargnames(function, + startindex=int(cls is not None)) self.cls = cls self.module = module self._calls = [] @@ -527,7 +531,11 @@ class Metafunc: def addcall(self, funcargs=None, id=_notexists, param=_notexists): """ add a new call to the underlying test function during the - collection phase of a test run. + collection phase of a test run. Note that request.addcall() is + called during the test collection phase prior and independently + to actual test execution. Therefore you should perform setup + of resources in a funcarg factory which can be instrumented + with the ``param``. :arg funcargs: argument keyword dictionary used when invoking the test function. @@ -537,14 +545,15 @@ class Metafunc: list of calls to the test function will be used. :arg param: will be exposed to a later funcarg factory invocation - through the ``request.param`` attribute. Setting it (instead of - directly providing a ``funcargs`` ditionary) is called - *indirect parametrization*. Indirect parametrization is - preferable if test values are expensive to setup or can - only be created after certain fixtures or test-run related - initialization code has been run. + through the ``request.param`` attribute. It allows to + defer test fixture setup activities to when an actual + test is run. """ assert funcargs is None or isinstance(funcargs, dict) + if funcargs is not None: + for name in funcargs: + if name not in self.funcargnames: + pytest.fail("funcarg %r not used in this function." % name) if id is None: raise ValueError("id=None not allowed") if id is _notexists: @@ -556,7 +565,13 @@ class Metafunc: self._calls.append(CallSpec(funcargs, id, param)) class FuncargRequest: - """ A request for function arguments from a test function. """ + """ A request for function arguments from a test function. + + Note that there is an optional ``param`` attribute in case + there was an invocation to metafunc.addcall(param=...). + If no such call was done in a ``pytest_generate_tests`` + hook, the attribute will not be present. + """ _argprefix = "pytest_funcarg__" _argname = None diff --git a/_pytest/recwarn.py b/_pytest/recwarn.py index e2fb2b17e4..5e5a92aa6b 100644 --- a/_pytest/recwarn.py +++ b/_pytest/recwarn.py @@ -8,6 +8,9 @@ def pytest_funcarg__recwarn(request): * ``pop(category=None)``: return last warning matching the category. * ``clear()``: clear list of warnings + + See http://docs.python.org/library/warnings.html for information + on warning categories. """ if sys.version_info >= (2,7): import warnings diff --git a/_pytest/skipping.py b/_pytest/skipping.py index 106c8518c3..fe69f295e7 100644 --- a/_pytest/skipping.py +++ b/_pytest/skipping.py @@ -1,6 +1,7 @@ """ support for skip/xfail functions and markers. """ import py, pytest +import sys def pytest_addoption(parser): group = parser.getgroup("general") @@ -32,9 +33,39 @@ class MarkEvaluator: return bool(self.holder) __nonzero__ = __bool__ + def wasvalid(self): + return not hasattr(self, 'exc') + def istrue(self): + try: + return self._istrue() + except KeyboardInterrupt: + raise + except: + self.exc = sys.exc_info() + if isinstance(self.exc[1], SyntaxError): + msg = [" " * (self.exc[1].offset + 4) + "^",] + msg.append("SyntaxError: invalid syntax") + else: + msg = py.std.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) + + def _getglobals(self): + d = {'os': py.std.os, 'sys': py.std.sys, 'config': self.item.config} + func = self.item.obj + try: + d.update(func.__globals__) + except AttributeError: + d.update(func.func_globals) + return d + + def _istrue(self): if self.holder: - d = {'os': py.std.os, 'sys': py.std.sys, 'config': self.item.config} + d = self._getglobals() if self.holder.args: self.result = False for expr in self.holder.args: @@ -42,7 +73,7 @@ class MarkEvaluator: if isinstance(expr, str): result = cached_eval(self.item.config, expr, d) else: - result = expr + pytest.fail("expression is not a string") if result: self.result = True self.expr = expr @@ -60,7 +91,7 @@ class MarkEvaluator: if not hasattr(self, 'expr'): return "" else: - return "condition: " + self.expr + return "condition: " + str(self.expr) return expl @@ -99,16 +130,17 @@ def pytest_runtest_makereport(__multicall__, item, call): return rep rep = __multicall__.execute() evalxfail = item._evalxfail - if not item.config.option.runxfail and evalxfail.istrue(): - if call.excinfo: - rep.outcome = "skipped" - rep.keywords['xfail'] = evalxfail.getexplanation() - elif call.when == "call": - rep.outcome = "failed" - rep.keywords['xfail'] = evalxfail.getexplanation() - else: - if 'xfail' in rep.keywords: - del rep.keywords['xfail'] + if not item.config.option.runxfail: + if evalxfail.wasvalid() and evalxfail.istrue(): + if call.excinfo: + rep.outcome = "skipped" + rep.keywords['xfail'] = evalxfail.getexplanation() + elif call.when == "call": + rep.outcome = "failed" + rep.keywords['xfail'] = evalxfail.getexplanation() + return rep + if 'xfail' in rep.keywords: + del rep.keywords['xfail'] return rep # called by terminalreporter progress reporting @@ -179,7 +211,8 @@ def cached_eval(config, expr, d): except KeyError: #import sys #print >>sys.stderr, ("cache-miss: %r" % expr) - config._evalcache[expr] = x = eval(expr, d) + exprcode = py.code.compile(expr, mode="eval") + config._evalcache[expr] = x = eval(exprcode, d) return x diff --git a/_pytest/terminal.py b/_pytest/terminal.py index 0b0ab1ee2f..2172ce6a58 100644 --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -25,7 +25,7 @@ def pytest_addoption(parser): group._addoption('--tb', metavar="style", action="store", dest="tbstyle", default='long', type="choice", choices=['long', 'short', 'no', 'line', 'native'], - help="traceback print mode (long/short/line/no).") + help="traceback print mode (long/short/line/native/no).") group._addoption('--fulltrace', action="store_true", dest="fulltrace", default=False, help="don't cut any tracebacks (default is to cut).") diff --git a/_pytest/tmpdir.py b/_pytest/tmpdir.py index 9eab52d545..2fd9992ccf 100644 --- a/_pytest/tmpdir.py +++ b/_pytest/tmpdir.py @@ -59,7 +59,7 @@ def pytest_unconfigure(config): def pytest_funcarg__tmpdir(request): """return a temporary directory path object - unique to each test function invocation, + which is unique to each test function invocation, created as a sub directory of the base temporary directory. The returned object is a `py.path.local`_ path object. diff --git a/_pytest/unittest.py b/_pytest/unittest.py index 5131a93e35..3bcf91e901 100644 --- a/_pytest/unittest.py +++ b/_pytest/unittest.py @@ -102,6 +102,10 @@ class TestCaseFunction(pytest.Function): def runtest(self): self._testcase(result=self) + def _prunetraceback(self, excinfo): + pytest.Function._prunetraceback(self, excinfo) + excinfo.traceback = excinfo.traceback.filter(lambda x:not x.frame.f_globals.get('__unittest')) + @pytest.mark.tryfirst def pytest_runtest_makereport(item, call): if isinstance(item, TestCaseFunction): |