[pypy-svn] pypy pytest2: update to current py and pytest trunk

hpk42 commits-noreply at bitbucket.org
Sun Mar 6 09:26:36 CET 2011


Author: holger krekel <holger at merlinux.eu>
Branch: pytest2
Changeset: r42444:61aefc3c60b5
Date: 2011-03-06 09:25 +0100
http://bitbucket.org/pypy/pypy/changeset/61aefc3c60b5/

Log:	update to current py and pytest trunk

diff --git a/_pytest/mark.py b/_pytest/mark.py
--- a/_pytest/mark.py
+++ b/_pytest/mark.py
@@ -89,8 +89,8 @@
 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/tmpdir.py b/_pytest/tmpdir.py
--- a/_pytest/tmpdir.py
+++ b/_pytest/tmpdir.py
@@ -59,7 +59,7 @@
 
 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/main.py b/_pytest/main.py
--- a/_pytest/main.py
+++ b/_pytest/main.py
@@ -121,9 +121,6 @@
 
 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 @@
     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 @@
             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 @@
             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/py/__init__.py b/py/__init__.py
--- a/py/__init__.py
+++ b/py/__init__.py
@@ -8,7 +8,7 @@
 
 (c) Holger Krekel and others, 2004-2010
 """
-__version__ = '1.4.1.dev2'
+__version__ = '1.4.2.dev0'
 
 from py import _apipkg
 
@@ -145,4 +145,3 @@
     },
 
 })
-

diff --git a/_pytest/capture.py b/_pytest/capture.py
--- a/_pytest/capture.py
+++ b/_pytest/capture.py
@@ -192,18 +192,16 @@
         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/python.py b/_pytest/python.py
--- a/_pytest/python.py
+++ b/_pytest/python.py
@@ -73,7 +73,8 @@
     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 @@
             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 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 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 @@
             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 @@
             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 @@
         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 @@
 
     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 @@
             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 @@
         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/monkeypatch.py b/_pytest/monkeypatch.py
--- a/_pytest/monkeypatch.py
+++ b/_pytest/monkeypatch.py
@@ -14,8 +14,8 @@
         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/terminal.py b/_pytest/terminal.py
--- a/_pytest/terminal.py
+++ b/_pytest/terminal.py
@@ -25,7 +25,7 @@
     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/py/_path/local.py b/py/_path/local.py
--- a/py/_path/local.py
+++ b/py/_path/local.py
@@ -158,11 +158,13 @@
 
     def samefile(self, other):
         """ return True if 'other' references the same file as 'self'. """
+        if not iswin32:
+            return py.error.checked_call(
+                    os.path.samefile, str(self), str(other))
         if self == other:
             return True
-        if not iswin32:
-            return py.error.checked_call(os.path.samefile, str(self), str(other))
-        return False
+        other = os.path.abspath(str(other))
+        return self == other
 
     def remove(self, rec=1, ignore_errors=False):
         """ remove a file or directory (or a directory tree if rec=1).
@@ -747,7 +749,7 @@
             pass
         try:
             os.symlink(src, dest)
-        except (OSError, AttributeError): # AttributeError on win32
+        except (OSError, AttributeError, NotImplementedError):
             pass
 
         return udir

diff --git a/_pytest/unittest.py b/_pytest/unittest.py
--- a/_pytest/unittest.py
+++ b/_pytest/unittest.py
@@ -102,6 +102,10 @@
     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):

diff --git a/_pytest/recwarn.py b/_pytest/recwarn.py
--- a/_pytest/recwarn.py
+++ b/_pytest/recwarn.py
@@ -8,6 +8,9 @@
 
     * ``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/py/_code/source.py b/py/_code/source.py
--- a/py/_code/source.py
+++ b/py/_code/source.py
@@ -215,7 +215,7 @@
             msglines = self.lines[:ex.lineno]
             if ex.offset:
                 msglines.append(" "*ex.offset + '^')
-            msglines.append("syntax error probably generated here: %s" % filename)
+            msglines.append("(code was compiled probably from here: %s)" % filename)
             newex = SyntaxError('\n'.join(msglines))
             newex.offset = ex.offset
             newex.lineno = ex.lineno

diff --git a/_pytest/skipping.py b/_pytest/skipping.py
--- 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 @@
         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 @@
                     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 @@
             if not hasattr(self, 'expr'):
                 return ""
             else:
-                return "condition: " + self.expr
+                return "condition: " + str(self.expr)
         return expl
 
 
@@ -99,16 +130,17 @@
             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 @@
     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.py b/pytest.py
--- a/pytest.py
+++ b/pytest.py
@@ -1,7 +1,7 @@
 """
 unit and functional testing with Python.
 """
-__version__ = '2.0.1.dev9'
+__version__ = '2.0.2.dev4'
 __all__ = ['main']
 
 from _pytest.core import main, UsageError, _preloadplugins

diff --git a/_pytest/assertion.py b/_pytest/assertion.py
--- a/_pytest/assertion.py
+++ b/_pytest/assertion.py
@@ -12,7 +12,7 @@
         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 @@
 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 @@
     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/py/_code/_assertionnew.py b/py/_code/_assertionnew.py
--- a/py/_code/_assertionnew.py
+++ b/py/_code/_assertionnew.py
@@ -267,20 +267,9 @@
             result = self.frame.eval(co, **ns)
         except Exception:
             raise Failure(explanation)
-        # Only show result explanation if it's not a builtin call or returns a
-        # bool.
-        if not isinstance(call.func, ast.Name) or \
-                not self._is_builtin_name(call.func):
-            source = "isinstance(__exprinfo_value, bool)"
-            co = self._compile(source)
-            try:
-                is_bool = self.frame.eval(co, __exprinfo_value=result)
-            except Exception:
-                is_bool = False
-            if not is_bool:
-                pattern = "%s\n{%s = %s\n}"
-                rep = self.frame.repr(result)
-                explanation = pattern % (rep, rep, explanation)
+        pattern = "%s\n{%s = %s\n}"
+        rep = self.frame.repr(result)
+        explanation = pattern % (rep, rep, explanation)
         return explanation, result
 
     def _is_builtin_name(self, name):

diff --git a/py/_error.py b/py/_error.py
--- a/py/_error.py
+++ b/py/_error.py
@@ -37,6 +37,8 @@
     _errno2class = {}
 
     def __getattr__(self, name):
+        if name[0] == "_":
+            raise AttributeError(name)
         eno = getattr(errno, name)
         cls = self._geterrnoclass(eno)
         setattr(self, name, cls)


More information about the Pypy-commit mailing list