[pypy-commit] pypy list-strategies: Merge default
l.diekmann
noreply at buildbot.pypy.org
Fri Sep 23 13:12:40 CEST 2011
Author: Lukas Diekmann <lukas.diekmann at uni-duesseldorf.de>
Branch: list-strategies
Changeset: r47475:f7da3f99894b
Date: 2011-03-16 14:48 +0100
http://bitbucket.org/pypy/pypy/changeset/f7da3f99894b/
Log: Merge default
diff too long, truncating to 10000 out of 39738 lines
diff --git a/.gitignore b/.gitignore
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,11 @@
include/*.h
lib_pypy/ctypes_config_cache/_[^_]*_*.py
pypy/_cache
+pypy/doc/*.html
+pypy/doc/config/*.html
+pypy/doc/discussion/*.html
+pypy/module/test_lib_pypy/ctypes_tests/*.o
+pypy/translator/c/src/dtoa.o
pypy/translator/goal/pypy-c
pypy/translator/goal/target*-c
release/
\ No newline at end of file
diff --git a/.hgignore b/.hgignore
--- a/.hgignore
+++ b/.hgignore
@@ -13,6 +13,7 @@
^pypy/module/cpyext/test/.+\.o$
^pypy/module/cpyext/test/.+\.obj$
^pypy/module/cpyext/test/.+\.manifest$
+^pypy/module/test_lib_pypy/ctypes_tests/.+\.o$
^pypy/doc/.+\.html$
^pypy/doc/basicblock\.asc$
^pypy/doc/.+\.svninfo$
@@ -61,3 +62,5 @@
^pypy/doc/image/stackless_informal\.png$
^pypy/doc/image/parsing_example.+\.png$
^compiled
+^.git/
+^release/
\ No newline at end of file
diff --git a/.hgsubstate b/.hgsubstate
--- a/.hgsubstate
+++ b/.hgsubstate
@@ -1,3 +1,3 @@
80037 greenlet
80348 lib_pypy/pyrepl
-80037 testrunner
+80409 testrunner
diff --git a/README b/README
--- a/README
+++ b/README
@@ -1,6 +1,6 @@
-======================================
-PyPy: Python in Python implementation
-======================================
+=====================================
+PyPy: Python in Python Implementation
+=====================================
Welcome to PyPy!
@@ -13,18 +13,12 @@
http://pypy.org/
-We invite you to head over to our detailed getting-started document:
-
- pypy/doc/getting-started.html or
- pypy/doc/getting-started.txt
- (local if you got a source tarball or svn checkout)
+The getting-started document will help guide you:
http://codespeak.net/pypy/dist/pypy/doc/getting-started.html
-which gives you many good starting and entry points into playing with
-PyPy. It will also point you to our documentation section which is
-generated from information in the pypy/doc directory.
-
-Enjoy and send us feedback!
+It will also point you to the rest of the documentation which is generated
+from files in the pypy/doc directory within the source repositories. Enjoy
+and send us feedback!
the pypy-dev team <pypy-dev at codespeak.net>
diff --git a/_pytest/__init__.py b/_pytest/__init__.py
new file mode 100644
--- /dev/null
+++ b/_pytest/__init__.py
@@ -0,0 +1,1 @@
+#
diff --git a/_pytest/assertion.py b/_pytest/assertion.py
new file mode 100644
--- /dev/null
+++ b/_pytest/assertion.py
@@ -0,0 +1,179 @@
+"""
+support for presented detailed information in failing assertions.
+"""
+import py
+import sys
+from _pytest.monkeypatch import monkeypatch
+
+def pytest_addoption(parser):
+ group = parser.getgroup("debugconfig")
+ group._addoption('--no-assert', action="store_true", default=False,
+ dest="noassert",
+ help="disable python assert expression reinterpretation."),
+
+def pytest_configure(config):
+ # 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.
+ config._monkeypatch = m = monkeypatch()
+ warn_about_missing_assertion()
+ if not config.getvalue("noassert") and not config.getvalue("nomagic"):
+ def callbinrepr(op, left, right):
+ hook_result = config.hook.pytest_assertrepr_compare(
+ config=config, op=op, left=left, right=right)
+ for new_expl in hook_result:
+ if new_expl:
+ return '\n~'.join(new_expl)
+ m.setattr(py.builtin.builtins,
+ 'AssertionError', py.code._AssertionError)
+ m.setattr(py.code, '_reprcompare', callbinrepr)
+
+def pytest_unconfigure(config):
+ config._monkeypatch.undo()
+
+def warn_about_missing_assertion():
+ try:
+ assert False
+ except AssertionError:
+ pass
+ else:
+ sys.stderr.write("WARNING: failing tests may report as passing because "
+ "assertions are turned off! (are you using python -O?)\n")
+
+# Provide basestring in python3
+try:
+ basestring = basestring
+except NameError:
+ basestring = str
+
+
+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=int(width/2))
+ right_repr = py.io.saferepr(right, maxsize=width-len(left_repr))
+ summary = '%s %s %s' % (left_repr, op, right_repr)
+
+ issequence = lambda x: isinstance(x, (list, tuple))
+ istext = lambda x: isinstance(x, basestring)
+ isdict = lambda x: isinstance(x, dict)
+ isset = lambda x: isinstance(x, set)
+
+ explanation = None
+ try:
+ if op == '==':
+ if istext(left) and istext(right):
+ explanation = _diff_text(left, right)
+ elif issequence(left) and issequence(right):
+ explanation = _compare_eq_sequence(left, right)
+ elif isset(left) and isset(right):
+ explanation = _compare_eq_set(left, right)
+ elif isdict(left) and isdict(right):
+ explanation = _diff_text(py.std.pprint.pformat(left),
+ py.std.pprint.pformat(right))
+ elif op == 'not in':
+ if istext(left) and istext(right):
+ explanation = _notin_text(left, right)
+ except py.builtin._sysex:
+ raise
+ except:
+ excinfo = py.code.ExceptionInfo()
+ explanation = ['(pytest_assertion plugin: representation of '
+ 'details failed. Probably an object has a faulty __repr__.)',
+ str(excinfo)
+ ]
+
+
+ if not explanation:
+ return None
+
+ # Don't include pageloads of data, should be configurable
+ if len(''.join(explanation)) > 80*8:
+ explanation = ['Detailed information too verbose, truncated']
+
+ return [summary] + explanation
+
+
+def _diff_text(left, right):
+ """Return the explanation for the diff between text
+
+ This will skip leading and trailing characters which are
+ identical to keep the diff minimal.
+ """
+ explanation = []
+ i = 0 # just in case left or right has zero length
+ for i in range(min(len(left), len(right))):
+ if left[i] != right[i]:
+ break
+ if i > 42:
+ i -= 10 # Provide some context
+ explanation = ['Skipping %s identical '
+ 'leading characters in diff' % i]
+ left = left[i:]
+ right = right[i:]
+ if len(left) == len(right):
+ for i in range(len(left)):
+ if left[-i] != right[-i]:
+ break
+ if i > 42:
+ i -= 10 # Provide some context
+ explanation += ['Skipping %s identical '
+ 'trailing characters in diff' % i]
+ left = left[:-i]
+ right = right[:-i]
+ explanation += [line.strip('\n')
+ for line in py.std.difflib.ndiff(left.splitlines(),
+ right.splitlines())]
+ return explanation
+
+
+def _compare_eq_sequence(left, right):
+ explanation = []
+ for i in range(min(len(left), len(right))):
+ if left[i] != right[i]:
+ explanation += ['At index %s diff: %r != %r' %
+ (i, left[i], right[i])]
+ break
+ if len(left) > len(right):
+ explanation += ['Left contains more items, '
+ 'first extra item: %s' % py.io.saferepr(left[len(right)],)]
+ elif len(left) < len(right):
+ explanation += ['Right contains more items, '
+ 'first extra item: %s' % py.io.saferepr(right[len(left)],)]
+ return explanation # + _diff_text(py.std.pprint.pformat(left),
+ # py.std.pprint.pformat(right))
+
+
+def _compare_eq_set(left, right):
+ explanation = []
+ diff_left = left - right
+ diff_right = right - left
+ if diff_left:
+ explanation.append('Extra items in the left set:')
+ for item in diff_left:
+ explanation.append(py.io.saferepr(item))
+ if diff_right:
+ explanation.append('Extra items in the right set:')
+ for item in diff_right:
+ explanation.append(py.io.saferepr(item))
+ return explanation
+
+
+def _notin_text(term, text):
+ index = text.find(term)
+ head = text[:index]
+ tail = text[index+len(term):]
+ correct_text = head + tail
+ 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
new file mode 100644
--- /dev/null
+++ b/_pytest/capture.py
@@ -0,0 +1,226 @@
+""" per-test stdout/stderr capturing mechanisms, ``capsys`` and ``capfd`` function arguments. """
+
+import pytest, py
+import os
+
+def pytest_addoption(parser):
+ group = parser.getgroup("general")
+ group._addoption('--capture', action="store", default=None,
+ metavar="method", type="choice", choices=['fd', 'sys', 'no'],
+ help="per-test capturing method: one of fd (default)|sys|no.")
+ group._addoption('-s', action="store_const", const="no", dest="capture",
+ help="shortcut for --capture=no.")
+
+def addouterr(rep, outerr):
+ repr = getattr(rep, 'longrepr', None)
+ if not hasattr(repr, 'addsection'):
+ return
+ for secname, content in zip(["out", "err"], outerr):
+ if content:
+ repr.addsection("Captured std%s" % secname, content.rstrip())
+
+def pytest_unconfigure(config):
+ # registered in config.py during early conftest.py loading
+ capman = config.pluginmanager.getplugin('capturemanager')
+ while capman._method2capture:
+ name, cap = capman._method2capture.popitem()
+ # XXX logging module may wants to close it itself on process exit
+ # otherwise we could do finalization here and call "reset()".
+ cap.suspend()
+
+class NoCapture:
+ def startall(self):
+ pass
+ def resume(self):
+ pass
+ def reset(self):
+ pass
+ def suspend(self):
+ return "", ""
+
+class CaptureManager:
+ def __init__(self):
+ self._method2capture = {}
+
+ def _maketempfile(self):
+ f = py.std.tempfile.TemporaryFile()
+ newf = py.io.dupfile(f, encoding="UTF-8")
+ f.close()
+ return newf
+
+ def _makestringio(self):
+ return py.io.TextIO()
+
+ def _getcapture(self, method):
+ if method == "fd":
+ return py.io.StdCaptureFD(now=False,
+ out=self._maketempfile(), err=self._maketempfile()
+ )
+ elif method == "sys":
+ return py.io.StdCapture(now=False,
+ out=self._makestringio(), err=self._makestringio()
+ )
+ elif method == "no":
+ return NoCapture()
+ else:
+ raise ValueError("unknown capturing method: %r" % method)
+
+ def _getmethod_preoptionparse(self, args):
+ if '-s' in args or "--capture=no" in args:
+ return "no"
+ elif hasattr(os, 'dup') and '--capture=sys' not in args:
+ return "fd"
+ else:
+ return "sys"
+
+ def _getmethod(self, config, fspath):
+ if config.option.capture:
+ method = config.option.capture
+ else:
+ try:
+ method = config._conftest.rget("option_capture", path=fspath)
+ except KeyError:
+ method = "fd"
+ if method == "fd" and not hasattr(os, 'dup'): # e.g. jython
+ method = "sys"
+ return method
+
+ def resumecapture_item(self, item):
+ method = self._getmethod(item.config, item.fspath)
+ if not hasattr(item, 'outerr'):
+ item.outerr = ('', '') # we accumulate outerr on the item
+ return self.resumecapture(method)
+
+ def resumecapture(self, method):
+ if hasattr(self, '_capturing'):
+ raise ValueError("cannot resume, already capturing with %r" %
+ (self._capturing,))
+ cap = self._method2capture.get(method)
+ self._capturing = method
+ if cap is None:
+ self._method2capture[method] = cap = self._getcapture(method)
+ cap.startall()
+ else:
+ cap.resume()
+
+ def suspendcapture(self, item=None):
+ self.deactivate_funcargs()
+ if hasattr(self, '_capturing'):
+ method = self._capturing
+ cap = self._method2capture.get(method)
+ if cap is not None:
+ outerr = cap.suspend()
+ del self._capturing
+ if item:
+ outerr = (item.outerr[0] + outerr[0],
+ item.outerr[1] + outerr[1])
+ return outerr
+ if hasattr(item, 'outerr'):
+ return item.outerr
+ return "", ""
+
+ def activate_funcargs(self, pyfuncitem):
+ if not hasattr(pyfuncitem, 'funcargs'):
+ return
+ assert not hasattr(self, '_capturing_funcargs')
+ self._capturing_funcargs = capturing_funcargs = []
+ for name, capfuncarg in pyfuncitem.funcargs.items():
+ if name in ('capsys', 'capfd'):
+ capturing_funcargs.append(capfuncarg)
+ capfuncarg._start()
+
+ def deactivate_funcargs(self):
+ capturing_funcargs = getattr(self, '_capturing_funcargs', None)
+ if capturing_funcargs is not None:
+ while capturing_funcargs:
+ capfuncarg = capturing_funcargs.pop()
+ capfuncarg._finalize()
+ del self._capturing_funcargs
+
+ def pytest_make_collect_report(self, __multicall__, collector):
+ method = self._getmethod(collector.config, collector.fspath)
+ try:
+ self.resumecapture(method)
+ except ValueError:
+ return # recursive collect, XXX refactor capturing
+ # to allow for more lightweight recursive capturing
+ try:
+ rep = __multicall__.execute()
+ finally:
+ outerr = self.suspendcapture()
+ addouterr(rep, outerr)
+ return rep
+
+ @pytest.mark.tryfirst
+ def pytest_runtest_setup(self, item):
+ self.resumecapture_item(item)
+
+ @pytest.mark.tryfirst
+ def pytest_runtest_call(self, item):
+ self.resumecapture_item(item)
+ self.activate_funcargs(item)
+
+ @pytest.mark.tryfirst
+ def pytest_runtest_teardown(self, item):
+ self.resumecapture_item(item)
+
+ def pytest__teardown_final(self, __multicall__, session):
+ method = self._getmethod(session.config, None)
+ self.resumecapture(method)
+ try:
+ rep = __multicall__.execute()
+ finally:
+ outerr = self.suspendcapture()
+ if rep:
+ addouterr(rep, outerr)
+ return rep
+
+ def pytest_keyboard_interrupt(self, excinfo):
+ if hasattr(self, '_capturing'):
+ self.suspendcapture()
+
+ @pytest.mark.tryfirst
+ def pytest_runtest_makereport(self, __multicall__, item, call):
+ self.deactivate_funcargs()
+ rep = __multicall__.execute()
+ outerr = self.suspendcapture(item)
+ if not rep.passed:
+ addouterr(rep, outerr)
+ if not rep.passed or rep.when == "teardown":
+ outerr = ('', '')
+ item.outerr = outerr
+ return rep
+
+def pytest_funcarg__capsys(request):
+ """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):
+ """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")
+ return CaptureFuncarg(py.io.StdCaptureFD)
+
+class CaptureFuncarg:
+ def __init__(self, captureclass):
+ self.capture = captureclass(now=False)
+
+ def _start(self):
+ self.capture.startall()
+
+ def _finalize(self):
+ if hasattr(self, 'capture'):
+ self.capture.reset()
+ del self.capture
+
+ def readouterr(self):
+ return self.capture.readouterr()
+
+ def close(self):
+ self._finalize()
diff --git a/_pytest/config.py b/_pytest/config.py
new file mode 100644
--- /dev/null
+++ b/_pytest/config.py
@@ -0,0 +1,434 @@
+""" command line options, ini-file and conftest.py processing. """
+
+import py
+import sys, os
+from _pytest.core import PluginManager
+import pytest
+
+def pytest_cmdline_parse(pluginmanager, args):
+ config = Config(pluginmanager)
+ config.parse(args)
+ if config.option.debug:
+ config.trace.root.setwriter(sys.stderr.write)
+ return config
+
+class Parser:
+ """ Parser for command line arguments. """
+
+ def __init__(self, usage=None, processopt=None):
+ self._anonymous = OptionGroup("custom options", parser=self)
+ self._groups = []
+ self._processopt = processopt
+ self._usage = usage
+ self._inidict = {}
+ self._ininames = []
+ self.hints = []
+
+ def processoption(self, option):
+ if self._processopt:
+ if option.dest:
+ self._processopt(option)
+
+ def addnote(self, note):
+ self._notes.append(note)
+
+ def getgroup(self, name, description="", after=None):
+ """ get (or create) a named option Group.
+
+ :name: unique name of the option group.
+ :description: long description for --help output.
+ :after: name of other group, used for ordering --help output.
+ """
+ for group in self._groups:
+ if group.name == name:
+ return group
+ group = OptionGroup(name, description, parser=self)
+ i = 0
+ for i, grp in enumerate(self._groups):
+ if grp.name == after:
+ break
+ self._groups.insert(i+1, group)
+ return group
+
+ def addoption(self, *opts, **attrs):
+ """ add an optparse-style option. """
+ self._anonymous.addoption(*opts, **attrs)
+
+ def parse(self, args):
+ self.optparser = optparser = MyOptionParser(self)
+ groups = self._groups + [self._anonymous]
+ for group in groups:
+ if group.options:
+ desc = group.description or group.name
+ optgroup = py.std.optparse.OptionGroup(optparser, desc)
+ optgroup.add_options(group.options)
+ optparser.add_option_group(optgroup)
+ return self.optparser.parse_args([str(x) for x in args])
+
+ def parse_setoption(self, args, option):
+ parsedoption, args = self.parse(args)
+ for name, value in parsedoption.__dict__.items():
+ setattr(option, name, value)
+ return args
+
+ def addini(self, name, help, type=None, default=None):
+ """ add an ini-file option with the given name and description. """
+ assert type in (None, "pathlist", "args", "linelist")
+ self._inidict[name] = (help, type, default)
+ self._ininames.append(name)
+
+class OptionGroup:
+ def __init__(self, name, description="", parser=None):
+ self.name = name
+ self.description = description
+ self.options = []
+ self.parser = parser
+
+ def addoption(self, *optnames, **attrs):
+ """ add an option to this group. """
+ option = py.std.optparse.Option(*optnames, **attrs)
+ self._addoption_instance(option, shortupper=False)
+
+ def _addoption(self, *optnames, **attrs):
+ option = py.std.optparse.Option(*optnames, **attrs)
+ self._addoption_instance(option, shortupper=True)
+
+ def _addoption_instance(self, option, shortupper=False):
+ if not shortupper:
+ for opt in option._short_opts:
+ if opt[0] == '-' and opt[1].islower():
+ raise ValueError("lowercase shortoptions reserved")
+ if self.parser:
+ self.parser.processoption(option)
+ self.options.append(option)
+
+
+class MyOptionParser(py.std.optparse.OptionParser):
+ def __init__(self, parser):
+ self._parser = parser
+ py.std.optparse.OptionParser.__init__(self, usage=parser._usage,
+ add_help_option=False)
+ def format_epilog(self, formatter):
+ hints = self._parser.hints
+ if hints:
+ s = "\n".join(["hint: " + x for x in hints]) + "\n"
+ s = "\n" + s + "\n"
+ return s
+ return ""
+
+class Conftest(object):
+ """ the single place for accessing values and interacting
+ towards conftest modules from py.test objects.
+ """
+ def __init__(self, onimport=None, confcutdir=None):
+ self._path2confmods = {}
+ self._onimport = onimport
+ self._conftestpath2mod = {}
+ self._confcutdir = confcutdir
+
+ def setinitial(self, args):
+ """ try to find a first anchor path for looking up global values
+ from conftests. This function is usually called _before_
+ argument parsing. conftest files may add command line options
+ and we thus have no completely safe way of determining
+ which parts of the arguments are actually related to options
+ and which are file system paths. We just try here to get
+ bootstrapped ...
+ """
+ current = py.path.local()
+ opt = '--confcutdir'
+ for i in range(len(args)):
+ opt1 = str(args[i])
+ if opt1.startswith(opt):
+ if opt1 == opt:
+ if len(args) > i:
+ p = current.join(args[i+1], abs=True)
+ elif opt1.startswith(opt + "="):
+ p = current.join(opt1[len(opt)+1:], abs=1)
+ self._confcutdir = p
+ break
+ for arg in args + [current]:
+ if hasattr(arg, 'startswith') and arg.startswith("--"):
+ continue
+ anchor = current.join(arg, abs=1)
+ if anchor.check(): # we found some file object
+ self._path2confmods[None] = self.getconftestmodules(anchor)
+ # let's also consider test* dirs
+ if anchor.check(dir=1):
+ for x in anchor.listdir("test*"):
+ if x.check(dir=1):
+ self.getconftestmodules(x)
+ break
+ else:
+ assert 0, "no root of filesystem?"
+
+ def getconftestmodules(self, path):
+ """ return a list of imported conftest modules for the given path. """
+ try:
+ clist = self._path2confmods[path]
+ except KeyError:
+ if path is None:
+ raise ValueError("missing default confest.")
+ dp = path.dirpath()
+ clist = []
+ if dp != path:
+ cutdir = self._confcutdir
+ if cutdir and path != cutdir and not path.relto(cutdir):
+ pass
+ else:
+ conftestpath = path.join("conftest.py")
+ if conftestpath.check(file=1):
+ clist.append(self.importconftest(conftestpath))
+ clist[:0] = self.getconftestmodules(dp)
+ self._path2confmods[path] = clist
+ # be defensive: avoid changes from caller side to
+ # affect us by always returning a copy of the actual list
+ return clist[:]
+
+ def rget(self, name, path=None):
+ mod, value = self.rget_with_confmod(name, path)
+ return value
+
+ def rget_with_confmod(self, name, path=None):
+ modules = self.getconftestmodules(path)
+ modules.reverse()
+ for mod in modules:
+ try:
+ return mod, getattr(mod, name)
+ except AttributeError:
+ continue
+ raise KeyError(name)
+
+ def importconftest(self, conftestpath):
+ assert conftestpath.check(), conftestpath
+ try:
+ return self._conftestpath2mod[conftestpath]
+ except KeyError:
+ pkgpath = conftestpath.pypkgpath()
+ if pkgpath is None:
+ _ensure_removed_sysmodule(conftestpath.purebasename)
+ self._conftestpath2mod[conftestpath] = mod = conftestpath.pyimport()
+ dirpath = conftestpath.dirpath()
+ if dirpath in self._path2confmods:
+ for path, mods in self._path2confmods.items():
+ if path and path.relto(dirpath) or path == dirpath:
+ assert mod not in mods
+ mods.append(mod)
+ self._postimport(mod)
+ return mod
+
+ def _postimport(self, mod):
+ if self._onimport:
+ self._onimport(mod)
+ return mod
+
+def _ensure_removed_sysmodule(modname):
+ try:
+ del sys.modules[modname]
+ except KeyError:
+ pass
+
+class CmdOptions(object):
+ """ holds cmdline options as attributes."""
+ def __init__(self, **kwargs):
+ self.__dict__.update(kwargs)
+ def __repr__(self):
+ return "<CmdOptions %r>" %(self.__dict__,)
+
+class Config(object):
+ """ access to configuration values, pluginmanager and plugin hooks. """
+ def __init__(self, pluginmanager=None):
+ #: command line option values, usually added via parser.addoption(...)
+ #: or parser.getgroup(...).addoption(...) calls
+ self.option = CmdOptions()
+ self._parser = Parser(
+ usage="usage: %prog [options] [file_or_dir] [file_or_dir] [...]",
+ processopt=self._processopt,
+ )
+ #: a pluginmanager instance
+ self.pluginmanager = pluginmanager or PluginManager(load=True)
+ self.trace = self.pluginmanager.trace.root.get("config")
+ self._conftest = Conftest(onimport=self._onimportconftest)
+ self.hook = self.pluginmanager.hook
+ self._inicache = {}
+
+ def _onimportconftest(self, conftestmodule):
+ self.trace("loaded conftestmodule %r" %(conftestmodule,))
+ self.pluginmanager.consider_conftest(conftestmodule)
+
+ def _processopt(self, opt):
+ if hasattr(opt, 'default') and opt.dest:
+ if not hasattr(self.option, opt.dest):
+ setattr(self.option, opt.dest, opt.default)
+
+ def _getmatchingplugins(self, fspath):
+ allconftests = self._conftest._conftestpath2mod.values()
+ plugins = [x for x in self.pluginmanager.getplugins()
+ if x not in allconftests]
+ plugins += self._conftest.getconftestmodules(fspath)
+ return plugins
+
+ def _setinitialconftest(self, args):
+ # capture output during conftest init (#issue93)
+ from _pytest.capture import CaptureManager
+ capman = CaptureManager()
+ self.pluginmanager.register(capman, 'capturemanager')
+ # will be unregistered in capture.py's unconfigure()
+ capman.resumecapture(capman._getmethod_preoptionparse(args))
+ try:
+ try:
+ self._conftest.setinitial(args)
+ finally:
+ out, err = capman.suspendcapture() # logging might have got it
+ except:
+ sys.stdout.write(out)
+ sys.stderr.write(err)
+ raise
+
+ def _initini(self, args):
+ self.inicfg = getcfg(args, ["pytest.ini", "tox.ini", "setup.cfg"])
+ self._parser.addini('addopts', 'extra command line options', 'args')
+ self._parser.addini('minversion', 'minimally required pytest version')
+
+ def _preparse(self, args, addopts=True):
+ self._initini(args)
+ if addopts:
+ args[:] = self.getini("addopts") + args
+ self._checkversion()
+ self.pluginmanager.consider_preparse(args)
+ self.pluginmanager.consider_setuptools_entrypoints()
+ self.pluginmanager.consider_env()
+ self._setinitialconftest(args)
+ self.pluginmanager.do_addoption(self._parser)
+ if addopts:
+ self.hook.pytest_cmdline_preparse(config=self, args=args)
+
+ def _checkversion(self):
+ minver = self.inicfg.get('minversion', None)
+ if minver:
+ ver = minver.split(".")
+ myver = pytest.__version__.split(".")
+ if myver < ver:
+ raise pytest.UsageError(
+ "%s:%d: requires pytest-%s, actual pytest-%s'" %(
+ self.inicfg.config.path, self.inicfg.lineof('minversion'),
+ minver, pytest.__version__))
+
+ def parse(self, args):
+ # parse given cmdline arguments into this config object.
+ # Note that this can only be called once per testing process.
+ assert not hasattr(self, 'args'), (
+ "can only parse cmdline args at most once per Config object")
+ self._preparse(args)
+ self._parser.hints.extend(self.pluginmanager._hints)
+ args = self._parser.parse_setoption(args, self.option)
+ if not args:
+ args.append(py.std.os.getcwd())
+ self.args = args
+
+ def getini(self, name):
+ """ return configuration value from an ini file. If the
+ specified name hasn't been registered through a prior ``parse.addini``
+ call (usually from a plugin), a ValueError is raised. """
+ try:
+ return self._inicache[name]
+ except KeyError:
+ self._inicache[name] = val = self._getini(name)
+ return val
+
+ def _getini(self, name):
+ try:
+ description, type, default = self._parser._inidict[name]
+ except KeyError:
+ raise ValueError("unknown configuration value: %r" %(name,))
+ try:
+ value = self.inicfg[name]
+ except KeyError:
+ if default is not None:
+ return default
+ if type is None:
+ return ''
+ return []
+ if type == "pathlist":
+ dp = py.path.local(self.inicfg.config.path).dirpath()
+ l = []
+ for relpath in py.std.shlex.split(value):
+ l.append(dp.join(relpath, abs=True))
+ return l
+ elif type == "args":
+ return py.std.shlex.split(value)
+ elif type == "linelist":
+ return [t for t in map(lambda x: x.strip(), value.split("\n")) if t]
+ else:
+ assert type is None
+ return value
+
+ def _getconftest_pathlist(self, name, path=None):
+ try:
+ mod, relroots = self._conftest.rget_with_confmod(name, path)
+ except KeyError:
+ return None
+ modpath = py.path.local(mod.__file__).dirpath()
+ l = []
+ for relroot in relroots:
+ if not isinstance(relroot, py.path.local):
+ relroot = relroot.replace("/", py.path.local.sep)
+ relroot = modpath.join(relroot, abs=True)
+ l.append(relroot)
+ return l
+
+ def _getconftest(self, name, path=None, check=False):
+ if check:
+ self._checkconftest(name)
+ return self._conftest.rget(name, path)
+
+ def getvalue(self, name, path=None):
+ """ return ``name`` value looked set from command line options.
+
+ (deprecated) if we can't find the option also lookup
+ the name in a matching conftest file.
+ """
+ try:
+ return getattr(self.option, name)
+ except AttributeError:
+ return self._getconftest(name, path, check=False)
+
+ def getvalueorskip(self, name, path=None):
+ """ (deprecated) return getvalue(name) or call
+ py.test.skip if no value exists. """
+ __tracebackhide__ = True
+ try:
+ val = self.getvalue(name, path)
+ if val is None:
+ raise KeyError(name)
+ return val
+ except KeyError:
+ py.test.skip("no %r value found" %(name,))
+
+
+def getcfg(args, inibasenames):
+ args = [x for x in args if str(x)[0] != "-"]
+ if not args:
+ args = [py.path.local()]
+ for arg in args:
+ arg = py.path.local(arg)
+ for base in arg.parts(reverse=True):
+ for inibasename in inibasenames:
+ p = base.join(inibasename)
+ if p.check():
+ iniconfig = py.iniconfig.IniConfig(p)
+ if 'pytest' in iniconfig.sections:
+ return iniconfig['pytest']
+ return {}
+
+def findupwards(current, basename):
+ current = py.path.local(current)
+ while 1:
+ p = current.join(basename)
+ if p.check():
+ return p
+ p = current.dirpath()
+ if p == current:
+ return
+ current = p
+
diff --git a/_pytest/core.py b/_pytest/core.py
new file mode 100644
--- /dev/null
+++ b/_pytest/core.py
@@ -0,0 +1,457 @@
+"""
+pytest PluginManager, basic initialization and tracing.
+(c) Holger Krekel 2004-2010
+"""
+import sys, os
+import inspect
+import py
+from _pytest import hookspec # the extension point definitions
+
+assert py.__version__.split(".")[:2] >= ['1', '4'], ("installation problem: "
+ "%s is too old, remove or upgrade 'py'" % (py.__version__))
+
+default_plugins = (
+ "config mark main terminal runner python pdb unittest capture skipping "
+ "tmpdir monkeypatch recwarn pastebin helpconfig nose assertion genscript "
+ "junitxml resultlog doctest").split()
+
+class TagTracer:
+ def __init__(self, prefix="[pytest] "):
+ self._tag2proc = {}
+ self.writer = None
+ self.indent = 0
+ self.prefix = prefix
+
+ def get(self, name):
+ return TagTracerSub(self, (name,))
+
+ def processmessage(self, tags, args):
+ if self.writer is not None:
+ if args:
+ indent = " " * self.indent
+ content = " ".join(map(str, args))
+ self.writer("%s%s%s\n" %(self.prefix, indent, content))
+ try:
+ self._tag2proc[tags](tags, args)
+ except KeyError:
+ pass
+
+ def setwriter(self, writer):
+ self.writer = writer
+
+ def setprocessor(self, tags, processor):
+ if isinstance(tags, str):
+ tags = tuple(tags.split(":"))
+ else:
+ assert isinstance(tags, tuple)
+ self._tag2proc[tags] = processor
+
+class TagTracerSub:
+ def __init__(self, root, tags):
+ self.root = root
+ self.tags = tags
+ def __call__(self, *args):
+ self.root.processmessage(self.tags, args)
+ def setmyprocessor(self, processor):
+ self.root.setprocessor(self.tags, processor)
+ def get(self, name):
+ return self.__class__(self.root, self.tags + (name,))
+
+class PluginManager(object):
+ def __init__(self, load=False):
+ self._name2plugin = {}
+ self._listattrcache = {}
+ self._plugins = []
+ self._hints = []
+ self.trace = TagTracer().get("pluginmanage")
+ self._plugin_distinfo = []
+ if os.environ.get('PYTEST_DEBUG'):
+ err = sys.stderr
+ encoding = getattr(err, 'encoding', 'utf8')
+ try:
+ err = py.io.dupfile(err, encoding=encoding)
+ except Exception:
+ pass
+ self.trace.root.setwriter(err.write)
+ self.hook = HookRelay([hookspec], pm=self)
+ self.register(self)
+ if load:
+ for spec in default_plugins:
+ self.import_plugin(spec)
+
+ def register(self, plugin, name=None, prepend=False):
+ assert not self.isregistered(plugin), plugin
+ name = name or getattr(plugin, '__name__', str(id(plugin)))
+ if name in self._name2plugin:
+ return False
+ #self.trace("registering", name, plugin)
+ self._name2plugin[name] = plugin
+ self.call_plugin(plugin, "pytest_addhooks", {'pluginmanager': self})
+ self.hook.pytest_plugin_registered(manager=self, plugin=plugin)
+ if not prepend:
+ self._plugins.append(plugin)
+ else:
+ self._plugins.insert(0, plugin)
+ return True
+
+ def unregister(self, plugin=None, name=None):
+ if plugin is None:
+ plugin = self.getplugin(name=name)
+ self._plugins.remove(plugin)
+ self.hook.pytest_plugin_unregistered(plugin=plugin)
+ for name, value in list(self._name2plugin.items()):
+ if value == plugin:
+ del self._name2plugin[name]
+
+ def isregistered(self, plugin, name=None):
+ if self.getplugin(name) is not None:
+ return True
+ for val in self._name2plugin.values():
+ if plugin == val:
+ return True
+
+ def addhooks(self, spec):
+ self.hook._addhooks(spec, prefix="pytest_")
+
+ def getplugins(self):
+ return list(self._plugins)
+
+ def skipifmissing(self, name):
+ if not self.hasplugin(name):
+ py.test.skip("plugin %r is missing" % name)
+
+ def hasplugin(self, name):
+ return bool(self.getplugin(name))
+
+ def getplugin(self, name):
+ if name is None:
+ return None
+ try:
+ return self._name2plugin[name]
+ except KeyError:
+ return self._name2plugin.get("_pytest." + name, None)
+
+ # API for bootstrapping
+ #
+ def _envlist(self, varname):
+ val = py.std.os.environ.get(varname, None)
+ if val is not None:
+ return val.split(',')
+ return ()
+
+ def consider_env(self):
+ for spec in self._envlist("PYTEST_PLUGINS"):
+ self.import_plugin(spec)
+
+ def consider_setuptools_entrypoints(self):
+ try:
+ from pkg_resources import iter_entry_points, DistributionNotFound
+ except ImportError:
+ return # XXX issue a warning
+ for ep in iter_entry_points('pytest11'):
+ name = ep.name
+ if name.startswith("pytest_"):
+ name = name[7:]
+ if ep.name in self._name2plugin or name in self._name2plugin:
+ continue
+ try:
+ plugin = ep.load()
+ except DistributionNotFound:
+ continue
+ self._plugin_distinfo.append((ep.dist, plugin))
+ self.register(plugin, name=name)
+
+ def consider_preparse(self, args):
+ for opt1,opt2 in zip(args, args[1:]):
+ if opt1 == "-p":
+ if opt2.startswith("no:"):
+ name = opt2[3:]
+ if self.getplugin(name) is not None:
+ self.unregister(None, name=name)
+ self._name2plugin[name] = -1
+ else:
+ if self.getplugin(opt2) is None:
+ self.import_plugin(opt2)
+
+ def consider_conftest(self, conftestmodule):
+ if self.register(conftestmodule, name=conftestmodule.__file__):
+ self.consider_module(conftestmodule)
+
+ def consider_module(self, mod):
+ attr = getattr(mod, "pytest_plugins", ())
+ if attr:
+ if not isinstance(attr, (list, tuple)):
+ attr = (attr,)
+ for spec in attr:
+ self.import_plugin(spec)
+
+ def import_plugin(self, modname):
+ assert isinstance(modname, str)
+ if self.getplugin(modname) is not None:
+ return
+ try:
+ #self.trace("importing", modname)
+ mod = importplugin(modname)
+ except KeyboardInterrupt:
+ raise
+ except ImportError:
+ if modname.startswith("pytest_"):
+ return self.import_plugin(modname[7:])
+ raise
+ except:
+ e = py.std.sys.exc_info()[1]
+ if not hasattr(py.test, 'skip'):
+ raise
+ elif not isinstance(e, py.test.skip.Exception):
+ raise
+ self._hints.append("skipped plugin %r: %s" %((modname, e.msg)))
+ else:
+ self.register(mod, modname)
+ self.consider_module(mod)
+
+ def pytest_plugin_registered(self, plugin):
+ import pytest
+ dic = self.call_plugin(plugin, "pytest_namespace", {}) or {}
+ if dic:
+ self._setns(pytest, dic)
+ if hasattr(self, '_config'):
+ self.call_plugin(plugin, "pytest_addoption",
+ {'parser': self._config._parser})
+ self.call_plugin(plugin, "pytest_configure",
+ {'config': self._config})
+
+ def _setns(self, obj, dic):
+ import pytest
+ for name, value in dic.items():
+ if isinstance(value, dict):
+ mod = getattr(obj, name, None)
+ if mod is None:
+ modname = "pytest.%s" % name
+ mod = py.std.types.ModuleType(modname)
+ sys.modules[modname] = mod
+ mod.__all__ = []
+ setattr(obj, name, mod)
+ obj.__all__.append(name)
+ self._setns(mod, value)
+ else:
+ setattr(obj, name, value)
+ obj.__all__.append(name)
+ #if obj != pytest:
+ # pytest.__all__.append(name)
+ setattr(pytest, name, value)
+
+ def pytest_terminal_summary(self, terminalreporter):
+ tw = terminalreporter._tw
+ if terminalreporter.config.option.traceconfig:
+ for hint in self._hints:
+ tw.line("hint: %s" % hint)
+
+ def do_addoption(self, parser):
+ mname = "pytest_addoption"
+ methods = reversed(self.listattr(mname))
+ MultiCall(methods, {'parser': parser}).execute()
+
+ def do_configure(self, config):
+ assert not hasattr(self, '_config')
+ self._config = config
+ config.hook.pytest_configure(config=self._config)
+
+ def do_unconfigure(self, config):
+ config = self._config
+ del self._config
+ config.hook.pytest_unconfigure(config=config)
+ config.pluginmanager.unregister(self)
+
+ def notify_exception(self, excinfo):
+ excrepr = excinfo.getrepr(funcargs=True, showlocals=True)
+ res = self.hook.pytest_internalerror(excrepr=excrepr)
+ if not py.builtin.any(res):
+ for line in str(excrepr).split("\n"):
+ sys.stderr.write("INTERNALERROR> %s\n" %line)
+ sys.stderr.flush()
+
+ def listattr(self, attrname, plugins=None):
+ if plugins is None:
+ plugins = self._plugins
+ key = (attrname,) + tuple(plugins)
+ try:
+ return list(self._listattrcache[key])
+ except KeyError:
+ pass
+ l = []
+ last = []
+ for plugin in plugins:
+ try:
+ meth = getattr(plugin, attrname)
+ if hasattr(meth, 'tryfirst'):
+ last.append(meth)
+ elif hasattr(meth, 'trylast'):
+ l.insert(0, meth)
+ else:
+ l.append(meth)
+ except AttributeError:
+ continue
+ l.extend(last)
+ self._listattrcache[key] = list(l)
+ return l
+
+ def call_plugin(self, plugin, methname, kwargs):
+ return MultiCall(methods=self.listattr(methname, plugins=[plugin]),
+ kwargs=kwargs, firstresult=True).execute()
+
+
+def importplugin(importspec):
+ name = importspec
+ try:
+ mod = "_pytest." + name
+ return __import__(mod, None, None, '__doc__')
+ except ImportError:
+ #e = py.std.sys.exc_info()[1]
+ #if str(e).find(name) == -1:
+ # raise
+ pass #
+ return __import__(importspec, None, None, '__doc__')
+
+class MultiCall:
+ """ execute a call into multiple python functions/methods. """
+ def __init__(self, methods, kwargs, firstresult=False):
+ self.methods = list(methods)
+ self.kwargs = kwargs
+ self.results = []
+ self.firstresult = firstresult
+
+ def __repr__(self):
+ status = "%d results, %d meths" % (len(self.results), len(self.methods))
+ return "<MultiCall %s, kwargs=%r>" %(status, self.kwargs)
+
+ def execute(self):
+ while self.methods:
+ method = self.methods.pop()
+ kwargs = self.getkwargs(method)
+ res = method(**kwargs)
+ if res is not None:
+ self.results.append(res)
+ if self.firstresult:
+ return res
+ if not self.firstresult:
+ return self.results
+
+ def getkwargs(self, method):
+ kwargs = {}
+ for argname in varnames(method):
+ try:
+ kwargs[argname] = self.kwargs[argname]
+ except KeyError:
+ if argname == "__multicall__":
+ kwargs[argname] = self
+ return kwargs
+
+def varnames(func):
+ try:
+ return func._varnames
+ except AttributeError:
+ pass
+ if not inspect.isfunction(func) and not inspect.ismethod(func):
+ func = getattr(func, '__call__', func)
+ ismethod = inspect.ismethod(func)
+ rawcode = py.code.getrawcode(func)
+ try:
+ x = rawcode.co_varnames[ismethod:rawcode.co_argcount]
+ except AttributeError:
+ x = ()
+ py.builtin._getfuncdict(func)['_varnames'] = x
+ return x
+
+class HookRelay:
+ def __init__(self, hookspecs, pm, prefix="pytest_"):
+ if not isinstance(hookspecs, list):
+ hookspecs = [hookspecs]
+ self._hookspecs = []
+ self._pm = pm
+ self.trace = pm.trace.root.get("hook")
+ for hookspec in hookspecs:
+ self._addhooks(hookspec, prefix)
+
+ def _addhooks(self, hookspecs, prefix):
+ self._hookspecs.append(hookspecs)
+ added = False
+ for name, method in vars(hookspecs).items():
+ if name.startswith(prefix):
+ firstresult = getattr(method, 'firstresult', False)
+ hc = HookCaller(self, name, firstresult=firstresult)
+ setattr(self, name, hc)
+ added = True
+ #print ("setting new hook", name)
+ if not added:
+ raise ValueError("did not find new %r hooks in %r" %(
+ prefix, hookspecs,))
+
+
+class HookCaller:
+ def __init__(self, hookrelay, name, firstresult):
+ self.hookrelay = hookrelay
+ self.name = name
+ self.firstresult = firstresult
+ self.trace = self.hookrelay.trace
+
+ def __repr__(self):
+ return "<HookCaller %r>" %(self.name,)
+
+ def __call__(self, **kwargs):
+ methods = self.hookrelay._pm.listattr(self.name)
+ return self._docall(methods, kwargs)
+
+ def pcall(self, plugins, **kwargs):
+ methods = self.hookrelay._pm.listattr(self.name, plugins=plugins)
+ return self._docall(methods, kwargs)
+
+ def _docall(self, methods, kwargs):
+ self.trace(self.name, kwargs)
+ self.trace.root.indent += 1
+ mc = MultiCall(methods, kwargs, firstresult=self.firstresult)
+ try:
+ res = mc.execute()
+ if res:
+ self.trace("finish", self.name, "-->", res)
+ finally:
+ self.trace.root.indent -= 1
+ return res
+
+_preinit = []
+
+def _preloadplugins():
+ _preinit.append(PluginManager(load=True))
+
+def main(args=None, plugins=None):
+ """ returned exit code integer, after an in-process testing run
+ with the given command line arguments, preloading an optional list
+ of passed in plugin objects. """
+ if args is None:
+ args = sys.argv[1:]
+ elif isinstance(args, py.path.local):
+ args = [str(args)]
+ elif not isinstance(args, (tuple, list)):
+ if not isinstance(args, str):
+ raise ValueError("not a string or argument list: %r" % (args,))
+ args = py.std.shlex.split(args)
+ if _preinit:
+ _pluginmanager = _preinit.pop(0)
+ else: # subsequent calls to main will create a fresh instance
+ _pluginmanager = PluginManager(load=True)
+ hook = _pluginmanager.hook
+ try:
+ if plugins:
+ for plugin in plugins:
+ _pluginmanager.register(plugin)
+ config = hook.pytest_cmdline_parse(
+ pluginmanager=_pluginmanager, args=args)
+ exitstatus = hook.pytest_cmdline_main(config=config)
+ except UsageError:
+ e = sys.exc_info()[1]
+ sys.stderr.write("ERROR: %s\n" %(e.args[0],))
+ exitstatus = 3
+ return exitstatus
+
+class UsageError(Exception):
+ """ error in py.test usage or invocation"""
+
diff --git a/_pytest/doctest.py b/_pytest/doctest.py
new file mode 100644
--- /dev/null
+++ b/_pytest/doctest.py
@@ -0,0 +1,87 @@
+""" discover and run doctests in modules and test files."""
+
+import pytest, py
+from py._code.code import TerminalRepr, ReprFileLocation
+
+def pytest_addoption(parser):
+ group = parser.getgroup("collect")
+ group.addoption("--doctest-modules",
+ action="store_true", default=False,
+ help="run doctests in all .py modules",
+ dest="doctestmodules")
+ group.addoption("--doctest-glob",
+ action="store", default="test*.txt", metavar="pat",
+ help="doctests file matching pattern, default: test*.txt",
+ dest="doctestglob")
+
+def pytest_collect_file(path, parent):
+ config = parent.config
+ if path.ext == ".py":
+ if config.option.doctestmodules:
+ return DoctestModule(path, parent)
+ elif (path.ext in ('.txt', '.rst') and parent.session.isinitpath(path)) or \
+ path.check(fnmatch=config.getvalue("doctestglob")):
+ return DoctestTextfile(path, parent)
+
+class ReprFailDoctest(TerminalRepr):
+ def __init__(self, reprlocation, lines):
+ self.reprlocation = reprlocation
+ self.lines = lines
+ def toterminal(self, tw):
+ for line in self.lines:
+ tw.line(line)
+ self.reprlocation.toterminal(tw)
+
+class DoctestItem(pytest.Item):
+ def repr_failure(self, excinfo):
+ doctest = py.std.doctest
+ if excinfo.errisinstance((doctest.DocTestFailure,
+ doctest.UnexpectedException)):
+ doctestfailure = excinfo.value
+ example = doctestfailure.example
+ test = doctestfailure.test
+ filename = test.filename
+ lineno = test.lineno + example.lineno + 1
+ message = excinfo.type.__name__
+ reprlocation = ReprFileLocation(filename, lineno, message)
+ checker = py.std.doctest.OutputChecker()
+ REPORT_UDIFF = py.std.doctest.REPORT_UDIFF
+ filelines = py.path.local(filename).readlines(cr=0)
+ i = max(test.lineno, max(0, lineno - 10)) # XXX?
+ lines = []
+ for line in filelines[i:lineno]:
+ lines.append("%03d %s" % (i+1, line))
+ i += 1
+ if excinfo.errisinstance(doctest.DocTestFailure):
+ lines += checker.output_difference(example,
+ doctestfailure.got, REPORT_UDIFF).split("\n")
+ else:
+ inner_excinfo = py.code.ExceptionInfo(excinfo.value.exc_info)
+ lines += ["UNEXPECTED EXCEPTION: %s" %
+ repr(inner_excinfo.value)]
+
+ return ReprFailDoctest(reprlocation, lines)
+ else:
+ return super(DoctestItem, self).repr_failure(excinfo)
+
+ def reportinfo(self):
+ return self.fspath, None, "[doctest]"
+
+class DoctestTextfile(DoctestItem, pytest.File):
+ def runtest(self):
+ doctest = py.std.doctest
+ failed, tot = doctest.testfile(
+ str(self.fspath), module_relative=False,
+ optionflags=doctest.ELLIPSIS,
+ raise_on_error=True, verbose=0)
+
+class DoctestModule(DoctestItem, pytest.File):
+ def runtest(self):
+ doctest = py.std.doctest
+ if self.fspath.basename == "conftest.py":
+ module = self.config._conftest.importconftest(self.fspath)
+ else:
+ module = self.fspath.pyimport()
+ failed, tot = doctest.testmod(
+ module, raise_on_error=True, verbose=0,
+ optionflags=doctest.ELLIPSIS)
diff --git a/_pytest/genscript.py b/_pytest/genscript.py
new file mode 100755
--- /dev/null
+++ b/_pytest/genscript.py
@@ -0,0 +1,69 @@
+""" generate a single-file self-contained version of py.test """
+import py
+
+def find_toplevel(name):
+ for syspath in py.std.sys.path:
+ base = py.path.local(syspath)
+ lib = base/name
+ if lib.check(dir=1):
+ return lib
+ mod = base.join("%s.py" % name)
+ if mod.check(file=1):
+ return mod
+ raise LookupError(name)
+
+def pkgname(toplevel, rootpath, path):
+ parts = path.parts()[len(rootpath.parts()):]
+ return '.'.join([toplevel] + [x.purebasename for x in parts])
+
+def pkg_to_mapping(name):
+ toplevel = find_toplevel(name)
+ name2src = {}
+ if toplevel.check(file=1): # module
+ name2src[toplevel.purebasename] = toplevel.read()
+ else: # package
+ for pyfile in toplevel.visit('*.py'):
+ pkg = pkgname(name, toplevel, pyfile)
+ name2src[pkg] = pyfile.read()
+ return name2src
+
+def compress_mapping(mapping):
+ data = py.std.pickle.dumps(mapping, 2)
+ data = py.std.zlib.compress(data, 9)
+ data = py.std.base64.encodestring(data)
+ data = data.decode('ascii')
+ return data
+
+
+def compress_packages(names):
+ mapping = {}
+ for name in names:
+ mapping.update(pkg_to_mapping(name))
+ return compress_mapping(mapping)
+
+def generate_script(entry, packages):
+ data = compress_packages(packages)
+ tmpl = py.path.local(__file__).dirpath().join('standalonetemplate.py')
+ exe = tmpl.read()
+ exe = exe.replace('@SOURCES@', data)
+ exe = exe.replace('@ENTRY@', entry)
+ return exe
+
+
+def pytest_addoption(parser):
+ group = parser.getgroup("debugconfig")
+ group.addoption("--genscript", action="store", default=None,
+ dest="genscript", metavar="path",
+ help="create standalone py.test script at given target path.")
+
+def pytest_cmdline_main(config):
+ genscript = config.getvalue("genscript")
+ if genscript:
+ script = generate_script(
+ 'import py; raise SystemExit(py.test.cmdline.main())',
+ ['py', '_pytest', 'pytest'],
+ )
+
+ genscript = py.path.local(genscript)
+ genscript.write(script)
+ return 0
diff --git a/_pytest/helpconfig.py b/_pytest/helpconfig.py
new file mode 100644
--- /dev/null
+++ b/_pytest/helpconfig.py
@@ -0,0 +1,177 @@
+""" version info, help messages, tracing configuration. """
+import py
+import pytest
+import inspect, sys
+from _pytest.core import varnames
+
+def pytest_addoption(parser):
+ group = parser.getgroup('debugconfig')
+ group.addoption('--version', action="store_true",
+ help="display pytest lib version and import information.")
+ group._addoption("-h", "--help", action="store_true", dest="help",
+ help="show help message and configuration info")
+ group._addoption('-p', action="append", dest="plugins", default = [],
+ metavar="name",
+ help="early-load given plugin (multi-allowed).")
+ group.addoption('--traceconfig',
+ action="store_true", dest="traceconfig", default=False,
+ help="trace considerations of conftest.py files."),
+ group._addoption('--nomagic',
+ action="store_true", dest="nomagic", default=False,
+ help="don't reinterpret asserts, no traceback cutting. ")
+ group.addoption('--debug',
+ action="store_true", dest="debug", default=False,
+ help="generate and show internal debugging information.")
+
+
+def pytest_cmdline_main(config):
+ if config.option.version:
+ p = py.path.local(pytest.__file__)
+ sys.stderr.write("This is py.test version %s, imported from %s\n" %
+ (pytest.__version__, p))
+ plugininfo = getpluginversioninfo(config)
+ if plugininfo:
+ for line in plugininfo:
+ sys.stderr.write(line + "\n")
+ return 0
+ elif config.option.help:
+ config.pluginmanager.do_configure(config)
+ showhelp(config)
+ return 0
+
+def showhelp(config):
+ tw = py.io.TerminalWriter()
+ tw.write(config._parser.optparser.format_help())
+ tw.line()
+ tw.line()
+ #tw.sep( "=", "config file settings")
+ tw.line("[pytest] ini-options in the next "
+ "pytest.ini|tox.ini|setup.cfg file:")
+ tw.line()
+
+ for name in config._parser._ininames:
+ help, type, default = config._parser._inidict[name]
+ if type is None:
+ type = "string"
+ spec = "%s (%s)" % (name, type)
+ line = " %-24s %s" %(spec, help)
+ tw.line(line[:tw.fullwidth])
+
+ tw.line() ; tw.line()
+ #tw.sep("=")
+ return
+
+ tw.line("conftest.py options:")
+ tw.line()
+ conftestitems = sorted(config._parser._conftestdict.items())
+ for name, help in conftest_options + conftestitems:
+ line = " %-15s %s" %(name, help)
+ tw.line(line[:tw.fullwidth])
+ tw.line()
+ #tw.sep( "=")
+
+conftest_options = [
+ ('pytest_plugins', 'list of plugin names to load'),
+]
+
+def getpluginversioninfo(config):
+ lines = []
+ plugininfo = config.pluginmanager._plugin_distinfo
+ if plugininfo:
+ lines.append("setuptools registered plugins:")
+ for dist, plugin in plugininfo:
+ loc = getattr(plugin, '__file__', repr(plugin))
+ content = "%s-%s at %s" % (dist.project_name, dist.version, loc)
+ lines.append(" " + content)
+ return lines
+
+def pytest_report_header(config):
+ lines = []
+ if config.option.debug or config.option.traceconfig:
+ lines.append("using: pytest-%s pylib-%s" %
+ (pytest.__version__,py.__version__))
+
+ verinfo = getpluginversioninfo(config)
+ if verinfo:
+ lines.extend(verinfo)
+
+ if config.option.traceconfig:
+ lines.append("active plugins:")
+ plugins = []
+ items = config.pluginmanager._name2plugin.items()
+ for name, plugin in items:
+ if hasattr(plugin, '__file__'):
+ r = plugin.__file__
+ else:
+ r = repr(plugin)
+ lines.append(" %-20s: %s" %(name, r))
+ return lines
+
+
+# =====================================================
+# validate plugin syntax and hooks
+# =====================================================
+
+def pytest_plugin_registered(manager, plugin):
+ methods = collectattr(plugin)
+ hooks = {}
+ for hookspec in manager.hook._hookspecs:
+ hooks.update(collectattr(hookspec))
+
+ stringio = py.io.TextIO()
+ def Print(*args):
+ if args:
+ stringio.write(" ".join(map(str, args)))
+ stringio.write("\n")
+
+ fail = False
+ while methods:
+ name, method = methods.popitem()
+ #print "checking", name
+ if isgenerichook(name):
+ continue
+ if name not in hooks:
+ if not getattr(method, 'optionalhook', False):
+ Print("found unknown hook:", name)
+ fail = True
+ else:
+ #print "checking", method
+ method_args = list(varnames(method))
+ if '__multicall__' in method_args:
+ method_args.remove('__multicall__')
+ hook = hooks[name]
+ hookargs = varnames(hook)
+ for arg in method_args:
+ if arg not in hookargs:
+ Print("argument %r not available" %(arg, ))
+ Print("actual definition: %s" %(formatdef(method)))
+ Print("available hook arguments: %s" %
+ ", ".join(hookargs))
+ fail = True
+ break
+ #if not fail:
+ # print "matching hook:", formatdef(method)
+ if fail:
+ name = getattr(plugin, '__name__', plugin)
+ raise PluginValidationError("%s:\n%s" % (name, stringio.getvalue()))
+
+class PluginValidationError(Exception):
+ """ plugin failed validation. """
+
+def isgenerichook(name):
+ return name == "pytest_plugins" or \
+ name.startswith("pytest_funcarg__")
+
+def collectattr(obj):
+ methods = {}
+ for apiname in dir(obj):
+ if apiname.startswith("pytest_"):
+ methods[apiname] = getattr(obj, apiname)
+ return methods
+
+def formatdef(func):
+ return "%s%s" % (
+ func.__name__,
+ inspect.formatargspec(*inspect.getargspec(func))
+ )
+
diff --git a/_pytest/hookspec.py b/_pytest/hookspec.py
new file mode 100644
--- /dev/null
+++ b/_pytest/hookspec.py
@@ -0,0 +1,222 @@
+""" hook specifications for pytest plugins, invoked from main.py and builtin plugins. """
+
+# -------------------------------------------------------------------------
+# Initialization
+# -------------------------------------------------------------------------
+
+def pytest_addhooks(pluginmanager):
+ """called at plugin load time to allow adding new hooks via a call to
+ pluginmanager.registerhooks(module)."""
+
+
+def pytest_namespace():
+ """return dict of name->object to be made globally available in
+ the py.test/pytest namespace. This hook is called before command
+ line options are parsed.
+ """
+
+def pytest_cmdline_parse(pluginmanager, args):
+ """return initialized config object, parsing the specified args. """
+pytest_cmdline_parse.firstresult = True
+
+def pytest_cmdline_preparse(config, args):
+ """modify command line arguments before option parsing. """
+
+def pytest_addoption(parser):
+ """add optparse-style options and ini-style config values via calls
+ to ``parser.addoption`` and ``parser.addini(...)``.
+ """
+
+def pytest_cmdline_main(config):
+ """ called for performing the main command line action. The default
+ implementation will invoke the configure hooks and runtest_mainloop. """
+pytest_cmdline_main.firstresult = True
+
+def pytest_configure(config):
+ """ called after command line options have been parsed.
+ and all plugins and initial conftest files been loaded.
+ """
+
+def pytest_unconfigure(config):
+ """ called before test process is exited. """
+
+def pytest_runtestloop(session):
+ """ called for performing the main runtest loop
+ (after collection finished). """
+pytest_runtestloop.firstresult = True
+
+# -------------------------------------------------------------------------
+# collection hooks
+# -------------------------------------------------------------------------
+
+def pytest_collection(session):
+ """ perform the collection protocol for the given session. """
+pytest_collection.firstresult = True
+
+def pytest_collection_modifyitems(session, config, items):
+ """ called after collection has been performed, may filter or re-order
+ the items in-place."""
+
+def pytest_collection_finish(session):
+ """ called after collection has been performed and modified. """
+
+def pytest_ignore_collect(path, config):
+ """ return True to prevent considering this path for collection.
+ This hook is consulted for all files and directories prior to calling
+ more specific hooks.
+ """
+pytest_ignore_collect.firstresult = True
+
+def pytest_collect_directory(path, parent):
+ """ called before traversing a directory for collection files. """
+pytest_collect_directory.firstresult = True
+
+def pytest_collect_file(path, parent):
+ """ return collection Node or None for the given path. Any new node
+ needs to have the specified ``parent`` as a parent."""
+
+# logging hooks for collection
+def pytest_collectstart(collector):
+ """ collector starts collecting. """
+
+def pytest_itemcollected(item):
+ """ we just collected a test item. """
+
+def pytest_collectreport(report):
+ """ collector finished collecting. """
+
+def pytest_deselected(items):
+ """ called for test items deselected by keyword. """
+
+def pytest_make_collect_report(collector):
+ """ perform ``collector.collect()`` and return a CollectReport. """
+pytest_make_collect_report.firstresult = True
+
+# -------------------------------------------------------------------------
+# Python test function related hooks
+# -------------------------------------------------------------------------
+
+def pytest_pycollect_makemodule(path, parent):
+ """ return a Module collector or None for the given path.
+ This hook will be called for each matching test module path.
+ The pytest_collect_file hook needs to be used if you want to
+ create test modules for files that do not match as a test module.
+ """
+pytest_pycollect_makemodule.firstresult = True
+
+def pytest_pycollect_makeitem(collector, name, obj):
+ """ return custom item/collector for a python object in a module, or None. """
+pytest_pycollect_makeitem.firstresult = True
+
+def pytest_pyfunc_call(pyfuncitem):
+ """ call underlying test function. """
+pytest_pyfunc_call.firstresult = True
+
+def pytest_generate_tests(metafunc):
+ """ generate (multiple) parametrized calls to a test function."""
+
+# -------------------------------------------------------------------------
+# generic runtest related hooks
+# -------------------------------------------------------------------------
+def pytest_itemstart(item, node=None):
+ """ (deprecated, use pytest_runtest_logstart). """
+
+def pytest_runtest_protocol(item):
+ """ implements the standard runtest_setup/call/teardown protocol including
+ capturing exceptions and calling reporting hooks on the results accordingly.
+
+ :return boolean: True if no further hook implementations should be invoked.
+ """
+pytest_runtest_protocol.firstresult = True
+
+def pytest_runtest_logstart(nodeid, location):
+ """ signal the start of a test run. """
+
+def pytest_runtest_setup(item):
+ """ called before ``pytest_runtest_call(item)``. """
+
+def pytest_runtest_call(item):
+ """ called to execute the test ``item``. """
+
+def pytest_runtest_teardown(item):
+ """ called after ``pytest_runtest_call``. """
+
+def pytest_runtest_makereport(item, call):
+ """ return a :py:class:`_pytest.runner.TestReport` object
+ for the given :py:class:`pytest.Item` and
+ :py:class:`_pytest.runner.CallInfo`.
+ """
+pytest_runtest_makereport.firstresult = True
+
+def pytest_runtest_logreport(report):
+ """ process item test report. """
+
+# special handling for final teardown - somewhat internal for now
+def pytest__teardown_final(session):
+ """ called before test session finishes. """
+pytest__teardown_final.firstresult = True
+
+def pytest__teardown_final_logerror(report, session):
+ """ called if runtest_teardown_final failed. """
+
+# -------------------------------------------------------------------------
+# test session related hooks
+# -------------------------------------------------------------------------
+
+def pytest_sessionstart(session):
+ """ before session.main() is called. """
+
+def pytest_sessionfinish(session, exitstatus):
+ """ whole test run finishes. """
+
+
+# -------------------------------------------------------------------------
+# hooks for customising the assert methods
+# -------------------------------------------------------------------------
+
+def pytest_assertrepr_compare(config, op, left, right):
+ """return explanation for comparisons in failing assert expressions.
+
+ Return None for no custom explanation, otherwise return a list
+ of strings. The strings will be joined by newlines but any newlines
+ *in* a string will be escaped. Note that all but the first line will
+ be indented sligthly, the intention is for the first line to be a summary.
+ """
+
+# -------------------------------------------------------------------------
+# hooks for influencing reporting (invoked from _pytest_terminal)
+# -------------------------------------------------------------------------
+
+def pytest_report_header(config):
+ """ return a string to be displayed as header info for terminal reporting."""
+
+def pytest_report_teststatus(report):
+ """ return result-category, shortletter and verbose word for reporting."""
+pytest_report_teststatus.firstresult = True
+
+def pytest_terminal_summary(terminalreporter):
+ """ add additional section in terminal summary reporting. """
+
+# -------------------------------------------------------------------------
+# doctest hooks
+# -------------------------------------------------------------------------
+
+def pytest_doctest_prepare_content(content):
+ """ return processed content for a given doctest"""
+pytest_doctest_prepare_content.firstresult = True
+
+# -------------------------------------------------------------------------
+# error handling and internal debugging hooks
+# -------------------------------------------------------------------------
+
+def pytest_plugin_registered(plugin, manager):
+ """ a new py lib plugin got registered. """
+
+def pytest_plugin_unregistered(plugin):
+ """ a py lib plugin got unregistered. """
+
+def pytest_internalerror(excrepr):
+ """ called for internal errors. """
+
+def pytest_keyboard_interrupt(excinfo):
+ """ called for keyboard interrupt. """
diff --git a/_pytest/junitxml.py b/_pytest/junitxml.py
new file mode 100644
--- /dev/null
+++ b/_pytest/junitxml.py
@@ -0,0 +1,173 @@
+""" report test results in JUnit-XML format, for use with Hudson and build integration servers.
+
+Based on initial code from Ross Lawley.
+"""
+
+import py
+import os
+import time
+
+def pytest_addoption(parser):
+ group = parser.getgroup("terminal reporting")
+ group.addoption('--junitxml', action="store", dest="xmlpath",
+ metavar="path", default=None,
+ help="create junit-xml style report file at given path.")
+ group.addoption('--junitprefix', action="store", dest="junitprefix",
+ metavar="str", default=None,
+ help="prepend prefix to classnames in junit-xml output")
+
+def pytest_configure(config):
+ xmlpath = config.option.xmlpath
+ if xmlpath:
+ config._xml = LogXML(xmlpath, config.option.junitprefix)
+ config.pluginmanager.register(config._xml)
+
+def pytest_unconfigure(config):
+ xml = getattr(config, '_xml', None)
+ if xml:
+ del config._xml
+ config.pluginmanager.unregister(xml)
+
+class LogXML(object):
+ def __init__(self, logfile, prefix):
+ self.logfile = logfile
+ self.prefix = prefix
+ self.test_logs = []
+ self.passed = self.skipped = 0
+ self.failed = self.errors = 0
+ self._durations = {}
+
+ def _opentestcase(self, report):
+ names = report.nodeid.split("::")
+ names[0] = names[0].replace("/", '.')
+ names = tuple(names)
+ d = {'time': self._durations.pop(names, "0")}
+ names = [x.replace(".py", "") for x in names if x != "()"]
+ classnames = names[:-1]
+ if self.prefix:
+ classnames.insert(0, self.prefix)
+ d['classname'] = ".".join(classnames)
+ d['name'] = py.xml.escape(names[-1])
+ attrs = ['%s="%s"' % item for item in sorted(d.items())]
+ self.test_logs.append("\n<testcase %s>" % " ".join(attrs))
+
+ def _closetestcase(self):
+ self.test_logs.append("</testcase>")
+
+ def appendlog(self, fmt, *args):
+ args = tuple([py.xml.escape(arg) for arg in args])
+ self.test_logs.append(fmt % args)
+
+ def append_pass(self, report):
+ self.passed += 1
+ self._opentestcase(report)
+ self._closetestcase()
+
+ def append_failure(self, report):
+ self._opentestcase(report)
+ #msg = str(report.longrepr.reprtraceback.extraline)
+ if "xfail" in report.keywords:
+ self.appendlog(
+ '<skipped message="xfail-marked test passes unexpectedly"/>')
+ self.skipped += 1
+ else:
+ self.appendlog('<failure message="test failure">%s</failure>',
+ report.longrepr)
+ self.failed += 1
+ self._closetestcase()
+
+ def append_collect_failure(self, report):
+ self._opentestcase(report)
+ #msg = str(report.longrepr.reprtraceback.extraline)
+ self.appendlog('<failure message="collection failure">%s</failure>',
+ report.longrepr)
+ self._closetestcase()
+ self.errors += 1
+
+ def append_collect_skipped(self, report):
+ self._opentestcase(report)
+ #msg = str(report.longrepr.reprtraceback.extraline)
+ self.appendlog('<skipped message="collection skipped">%s</skipped>',
+ report.longrepr)
+ self._closetestcase()
+ self.skipped += 1
+
+ def append_error(self, report):
+ self._opentestcase(report)
+ self.appendlog('<error message="test setup failure">%s</error>',
+ report.longrepr)
+ self._closetestcase()
+ self.errors += 1
+
+ def append_skipped(self, report):
+ self._opentestcase(report)
+ if "xfail" in report.keywords:
+ self.appendlog(
+ '<skipped message="expected test failure">%s</skipped>',
+ report.keywords['xfail'])
+ else:
+ self.appendlog("<skipped/>")
+ self._closetestcase()
+ self.skipped += 1
+
+ def pytest_runtest_logreport(self, report):
+ if report.passed:
+ self.append_pass(report)
+ elif report.failed:
+ if report.when != "call":
+ self.append_error(report)
+ else:
+ self.append_failure(report)
+ elif report.skipped:
+ self.append_skipped(report)
+
+ def pytest_runtest_call(self, item, __multicall__):
+ names = tuple(item.listnames())
+ start = time.time()
+ try:
+ return __multicall__.execute()
+ finally:
+ self._durations[names] = time.time() - start
+
+ def pytest_collectreport(self, report):
+ if not report.passed:
+ if report.failed:
+ self.append_collect_failure(report)
+ else:
+ self.append_collect_skipped(report)
+
+ def pytest_internalerror(self, excrepr):
+ self.errors += 1
+ data = py.xml.escape(excrepr)
+ self.test_logs.append(
+ '\n<testcase classname="pytest" name="internal">'
+ ' <error message="internal error">'
+ '%s</error></testcase>' % data)
+
+ def pytest_sessionstart(self, session):
+ self.suite_start_time = time.time()
+
+ def pytest_sessionfinish(self, session, exitstatus, __multicall__):
+ if py.std.sys.version_info[0] < 3:
+ logfile = py.std.codecs.open(self.logfile, 'w', encoding='utf-8')
+ else:
+ logfile = open(self.logfile, 'w', encoding='utf-8')
+
+ suite_stop_time = time.time()
+ suite_time_delta = suite_stop_time - self.suite_start_time
+ numtests = self.passed + self.failed
+ logfile.write('<?xml version="1.0" encoding="utf-8"?>')
+ logfile.write('<testsuite ')
+ logfile.write('name="" ')
+ logfile.write('errors="%i" ' % self.errors)
+ logfile.write('failures="%i" ' % self.failed)
+ logfile.write('skips="%i" ' % self.skipped)
+ logfile.write('tests="%i" ' % numtests)
+ logfile.write('time="%.3f"' % suite_time_delta)
+ logfile.write(' >')
+ logfile.writelines(self.test_logs)
+ logfile.write('</testsuite>')
+ logfile.close()
+
+ def pytest_terminal_summary(self, terminalreporter):
+ terminalreporter.write_sep("-", "generated xml file: %s" % (self.logfile))
diff --git a/_pytest/main.py b/_pytest/main.py
new file mode 100644
--- /dev/null
+++ b/_pytest/main.py
@@ -0,0 +1,534 @@
+""" core implementation of testing process: init, session, runtest loop. """
+
+import py
+import pytest, _pytest
+import os, sys
+tracebackcutdir = py.path.local(_pytest.__file__).dirpath()
+
+# exitcodes for the command line
+EXIT_OK = 0
+EXIT_TESTSFAILED = 1
+EXIT_INTERRUPTED = 2
+EXIT_INTERNALERROR = 3
+
+def pytest_addoption(parser):
+ parser.addini("norecursedirs", "directory patterns to avoid for recursion",
+ type="args", default=('.*', 'CVS', '_darcs', '{arch}'))
+ #parser.addini("dirpatterns",
+ # "patterns specifying possible locations of test files",
+ # type="linelist", default=["**/test_*.txt",
+ # "**/test_*.py", "**/*_test.py"]
+ #)
+ group = parser.getgroup("general", "running and selection options")
+ group._addoption('-x', '--exitfirst', action="store_true", default=False,
+ dest="exitfirst",
+ help="exit instantly on first error or failed test."),
+ group._addoption('--maxfail', metavar="num",
+ action="store", type="int", dest="maxfail", default=0,
+ help="exit after first num failures or errors.")
+
+ group = parser.getgroup("collect", "collection")
+ group.addoption('--collectonly',
+ action="store_true", dest="collectonly",
+ help="only collect tests, don't execute them."),
+ group.addoption('--pyargs', action="store_true",
+ help="try to interpret all arguments as python packages.")
+ group.addoption("--ignore", action="append", metavar="path",
+ help="ignore path during collection (multi-allowed).")
+ group.addoption('--confcutdir', dest="confcutdir", default=None,
+ metavar="dir",
+ help="only load conftest.py's relative to specified dir.")
+
+ group = parser.getgroup("debugconfig",
+ "test session debugging and configuration")
+ group.addoption('--basetemp', dest="basetemp", default=None, metavar="dir",
+ help="base temporary directory for this test run.")
+
+
+def pytest_namespace():
+ return dict(collect=dict(Item=Item, Collector=Collector, File=File))
+
+def pytest_configure(config):
+ py.test.config = config # compatibiltiy
+ if config.option.exitfirst:
+ config.option.maxfail = 1
+
+def pytest_cmdline_main(config):
+ """ default command line protocol for initialization, session,
+ running tests and reporting. """
+ session = Session(config)
+ session.exitstatus = EXIT_OK
+ try:
+ config.pluginmanager.do_configure(config)
+ config.hook.pytest_sessionstart(session=session)
+ config.hook.pytest_collection(session=session)
+ config.hook.pytest_runtestloop(session=session)
+ except pytest.UsageError:
+ raise
+ except KeyboardInterrupt:
+ excinfo = py.code.ExceptionInfo()
+ config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
+ session.exitstatus = EXIT_INTERRUPTED
+ except:
+ excinfo = py.code.ExceptionInfo()
+ config.pluginmanager.notify_exception(excinfo)
+ session.exitstatus = EXIT_INTERNALERROR
+ if excinfo.errisinstance(SystemExit):
+ sys.stderr.write("mainloop: caught Spurious SystemExit!\n")
+ if not session.exitstatus and session._testsfailed:
+ session.exitstatus = EXIT_TESTSFAILED
+ config.hook.pytest_sessionfinish(session=session,
+ exitstatus=session.exitstatus)
+ config.pluginmanager.do_unconfigure(config)
+ return session.exitstatus
+
+def pytest_collection(session):
+ session.perform_collect()
+ hook = session.config.hook
+ hook.pytest_collection_modifyitems(session=session,
+ config=session.config, items=session.items)
+ hook.pytest_collection_finish(session=session)
+ return True
+
+def pytest_runtestloop(session):
+ if session.config.option.collectonly:
+ return True
+ for item in session.session.items:
+ item.config.hook.pytest_runtest_protocol(item=item)
+ if session.shouldstop:
+ raise session.Interrupted(session.shouldstop)
+ return True
+
+def pytest_ignore_collect(path, config):
+ p = path.dirpath()
+ ignore_paths = config._getconftest_pathlist("collect_ignore", path=p)
+ ignore_paths = ignore_paths or []
+ excludeopt = config.getvalue("ignore")
+ if excludeopt:
+ ignore_paths.extend([py.path.local(x) for x in excludeopt])
+ return path in ignore_paths
+
+class HookProxy:
+ def __init__(self, fspath, config):
+ self.fspath = fspath
+ self.config = config
+ def __getattr__(self, name):
+ hookmethod = getattr(self.config.hook, name)
+ def call_matching_hooks(**kwargs):
+ plugins = self.config._getmatchingplugins(self.fspath)
+ return hookmethod.pcall(plugins, **kwargs)
+ return call_matching_hooks
+
+def compatproperty(name):
+ def fget(self):
+ return getattr(pytest, name)
+ return property(fget, None, None,
+ "deprecated attribute %r, use pytest.%s" % (name,name))
+
+class Node(object):
+ """ base class for all Nodes in the collection tree.
+ Collector subclasses have children, Items are terminal nodes."""
+
+ def __init__(self, name, parent=None, config=None, session=None):
+ #: a unique name with the scope of the parent
+ self.name = name
+
+ #: the parent collector node.
+ self.parent = parent
+
+ #: the test config object
+ self.config = config or parent.config
+
+ #: the collection this node is part of
+ self.session = session or parent.session
+
+ #: filesystem path where this node was collected from
+ self.fspath = getattr(parent, 'fspath', None)
+ self.ihook = self.session.gethookproxy(self.fspath)
+ self.keywords = {self.name: True}
+
+ Module = compatproperty("Module")
+ Class = compatproperty("Class")
+ Instance = compatproperty("Instance")
+ Function = compatproperty("Function")
+ 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))
+
+ # methods for ordering nodes
+ @property
+ def nodeid(self):
+ try:
+ return self._nodeid
+ except AttributeError:
+ self._nodeid = x = self._makeid()
+ return x
+
+ def _makeid(self):
+ return self.parent.nodeid + "::" + self.name
+
+ def __eq__(self, other):
+ if not isinstance(other, Node):
+ return False
+ return self.__class__ == other.__class__ and \
+ self.name == other.name and self.parent == other.parent
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __hash__(self):
+ return hash((self.name, self.parent))
+
+ def setup(self):
+ pass
+
+ def teardown(self):
+ pass
+
+ def _memoizedcall(self, attrname, function):
+ exattrname = "_ex_" + attrname
+ failure = getattr(self, exattrname, None)
+ if failure is not None:
+ py.builtin._reraise(failure[0], failure[1], failure[2])
+ if hasattr(self, attrname):
+ return getattr(self, attrname)
+ try:
+ res = function()
+ except py.builtin._sysex:
+ raise
+ except:
+ failure = py.std.sys.exc_info()
+ setattr(self, exattrname, failure)
+ raise
+ setattr(self, attrname, res)
+ return res
+
+ def listchain(self):
+ """ return list of all parent collectors up to self,
+ starting from root of collection tree. """
+ l = [self]
+ while 1:
+ x = l[0]
+ if x.parent is not None: # and x.parent.parent is not None:
+ l.insert(0, x.parent)
+ else:
+ return l
+
+ def listnames(self):
+ return [x.name for x in self.listchain()]
+
+ def getplugins(self):
+ return self.config._getmatchingplugins(self.fspath)
+
+ def getparent(self, cls):
+ current = self
+ while current and not isinstance(current, cls):
+ current = current.parent
+ return current
+
+ def _prunetraceback(self, excinfo):
+ pass
+
+ def _repr_failure_py(self, excinfo, style=None):
+ if self.config.option.fulltrace:
+ style="long"
+ else:
+ self._prunetraceback(excinfo)
+ # XXX should excinfo.getrepr record all data and toterminal()
+ # process it?
+ if style is None:
+ if self.config.option.tbstyle == "short":
+ style = "short"
+ else:
+ style = "long"
+ return excinfo.getrepr(funcargs=True,
+ showlocals=self.config.option.showlocals,
+ style=style)
+
+ repr_failure = _repr_failure_py
+
+class Collector(Node):
+ """ Collector instances create children through collect()
+ and thus iteratively build a tree.
+ """
+ class CollectError(Exception):
+ """ an error during collection, contains a custom message. """
+
+ def collect(self):
+ """ returns a list of children (items and collectors)
+ for this collection node.
+ """
+ raise NotImplementedError("abstract")
+
+ def repr_failure(self, excinfo):
+ """ represent a collection failure. """
+ if excinfo.errisinstance(self.CollectError):
+ exc = excinfo.value
+ return str(exc.args[0])
+ return self._repr_failure_py(excinfo, style="short")
+
+ def _memocollect(self):
+ """ internal helper method to cache results of calling collect(). """
+ return self._memoizedcall('_collected', lambda: list(self.collect()))
+
+ def _prunetraceback(self, excinfo):
+ if hasattr(self, 'fspath'):
+ path = self.fspath
+ traceback = excinfo.traceback
+ ntraceback = traceback.cut(path=self.fspath)
+ if ntraceback == traceback:
+ ntraceback = ntraceback.cut(excludepath=tracebackcutdir)
+ excinfo.traceback = ntraceback.filter()
+
+class FSCollector(Collector):
+ def __init__(self, fspath, parent=None, config=None, session=None):
+ fspath = py.path.local(fspath) # xxx only for test_resultlog.py?
+ name = fspath.basename
+ if parent is not None:
+ rel = fspath.relto(parent.fspath)
+ if rel:
+ name = rel
+ name = name.replace(os.sep, "/")
+ super(FSCollector, self).__init__(name, parent, config, session)
+ self.fspath = fspath
+
+ def _makeid(self):
+ if self == self.session:
+ return "."
+ relpath = self.session.fspath.bestrelpath(self.fspath)
+ if os.sep != "/":
+ relpath = relpath.replace(os.sep, "/")
+ return relpath
+
+class File(FSCollector):
+ """ base class for collecting tests from a file. """
+
+class Item(Node):
+ """ a basic test invocation item. Note that for a single function
+ there might be multiple test invocation items.
+ """
+ def reportinfo(self):
+ return self.fspath, None, ""
+
+ @property
+ def location(self):
+ try:
+ return self._location
+ except AttributeError:
+ location = self.reportinfo()
+ # bestrelpath is a quite slow function
+ cache = self.config.__dict__.setdefault("_bestrelpathcache", {})
+ try:
+ fspath = cache[location[0]]
+ except KeyError:
+ fspath = self.session.fspath.bestrelpath(location[0])
+ cache[location[0]] = fspath
+ location = (fspath, location[1], str(location[2]))
+ self._location = location
+ return location
+
+class NoMatch(Exception):
+ """ raised if matching cannot locate a matching names. """
+
+class Session(FSCollector):
+ class Interrupted(KeyboardInterrupt):
+ """ signals an interrupted test run. """
+ __module__ = 'builtins' # for py3
+
+ def __init__(self, config):
+ super(Session, self).__init__(py.path.local(), parent=None,
+ config=config, session=self)
+ assert self.config.pluginmanager.register(self, name="session", prepend=True)
+ self._testsfailed = 0
+ self.shouldstop = False
+ self.trace = config.trace.root.get("collection")
+ self._norecursepatterns = config.getini("norecursedirs")
+
+ def pytest_collectstart(self):
+ if self.shouldstop:
+ raise self.Interrupted(self.shouldstop)
+
+ def pytest_runtest_logreport(self, report):
+ if report.failed and 'xfail' not in getattr(report, 'keywords', []):
+ self._testsfailed += 1
+ maxfail = self.config.getvalue("maxfail")
+ if maxfail and self._testsfailed >= maxfail:
+ self.shouldstop = "stopping after %d failures" % (
+ self._testsfailed)
+ pytest_collectreport = pytest_runtest_logreport
+
+ def isinitpath(self, path):
+ return path in self._initialpaths
+
+ def gethookproxy(self, fspath):
+ return HookProxy(fspath, self.config)
+
+ def perform_collect(self, args=None, genitems=True):
+ if args is None:
+ args = self.config.args
+ self.trace("perform_collect", self, args)
+ self.trace.root.indent += 1
+ self._notfound = []
+ self._initialpaths = set()
+ self._initialparts = []
+ for arg in args:
+ parts = self._parsearg(arg)
+ self._initialparts.append(parts)
+ self._initialpaths.add(parts[0])
+ self.ihook.pytest_collectstart(collector=self)
+ rep = self.ihook.pytest_make_collect_report(collector=self)
+ self.ihook.pytest_collectreport(report=rep)
+ self.trace.root.indent -= 1
+ if self._notfound:
+ for arg, exc in self._notfound:
+ line = "(no name %r in any of %r)" % (arg, exc.args[0])
+ raise pytest.UsageError("not found: %s\n%s" %(arg, line))
+ if not genitems:
+ return rep.result
+ else:
+ self.items = items = []
+ if rep.passed:
+ for node in rep.result:
+ self.items.extend(self.genitems(node))
+ return items
+
+ def collect(self):
+ for parts in self._initialparts:
+ arg = "::".join(map(str, parts))
+ self.trace("processing argument", arg)
+ self.trace.root.indent += 1
+ try:
+ for x in self._collect(arg):
+ yield x
+ except NoMatch:
+ # we are inside a make_report hook so
+ # we cannot directly pass through the exception
+ self._notfound.append((arg, sys.exc_info()[1]))
+ self.trace.root.indent -= 1
+ break
+ self.trace.root.indent -= 1
+
+ def _collect(self, arg):
+ names = self._parsearg(arg)
+ path = names.pop(0)
+ if path.check(dir=1):
+ assert not names, "invalid arg %r" %(arg,)
+ for path in path.visit(fil=lambda x: x.check(file=1),
+ rec=self._recurse, bf=True, sort=True):
+ for x in self._collectfile(path):
+ yield x
+ else:
+ assert path.check(file=1)
+ for x in self.matchnodes(self._collectfile(path), names):
+ yield x
+
+ def _collectfile(self, path):
+ ihook = self.gethookproxy(path)
+ if not self.isinitpath(path):
+ if ihook.pytest_ignore_collect(path=path, config=self.config):
+ return ()
+ return ihook.pytest_collect_file(path=path, parent=self)
+
+ def _recurse(self, path):
+ ihook = self.gethookproxy(path.dirpath())
+ if ihook.pytest_ignore_collect(path=path, config=self.config):
+ return
+ for pat in self._norecursepatterns:
+ if path.check(fnmatch=pat):
+ return False
+ ihook = self.gethookproxy(path)
+ ihook.pytest_collect_directory(path=path, parent=self)
+ return True
+
+ def _tryconvertpyarg(self, x):
+ try:
+ mod = __import__(x, None, None, ['__doc__'])
+ except (ValueError, ImportError):
+ return x
+ p = py.path.local(mod.__file__)
+ if p.purebasename == "__init__":
+ p = p.dirpath()
+ else:
+ p = p.new(basename=p.purebasename+".py")
+ return str(p)
+
+ def _parsearg(self, arg):
+ """ return (fspath, names) tuple after checking the file exists. """
+ arg = str(arg)
+ if self.config.option.pyargs:
+ arg = self._tryconvertpyarg(arg)
+ parts = str(arg).split("::")
+ relpath = parts[0].replace("/", os.sep)
+ path = self.fspath.join(relpath, abs=True)
+ if not path.check():
+ if self.config.option.pyargs:
+ msg = "file or package not found: "
+ else:
+ msg = "file not found: "
+ raise pytest.UsageError(msg + arg)
+ parts[0] = path
+ return parts
+
+ def matchnodes(self, matching, names):
+ self.trace("matchnodes", matching, names)
+ self.trace.root.indent += 1
+ nodes = self._matchnodes(matching, names)
+ num = len(nodes)
+ self.trace("matchnodes finished -> ", num, "nodes")
+ self.trace.root.indent -= 1
+ if num == 0:
+ raise NoMatch(matching, names[:1])
+ return nodes
+
+ def _matchnodes(self, matching, names):
+ if not matching or not names:
+ return matching
+ name = names[0]
+ assert name
+ nextnames = names[1:]
+ resultnodes = []
+ for node in matching:
+ if isinstance(node, pytest.Item):
+ if not names:
+ resultnodes.append(node)
+ continue
+ assert isinstance(node, pytest.Collector)
+ 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
+
+ def genitems(self, node):
+ self.trace("genitems", node)
+ if isinstance(node, pytest.Item):
+ node.ihook.pytest_itemcollected(item=node)
+ yield node
+ else:
+ assert isinstance(node, pytest.Collector)
+ node.ihook.pytest_collectstart(collector=node)
+ rep = node.ihook.pytest_make_collect_report(collector=node)
+ if rep.passed:
+ for subnode in rep.result:
+ for x in self.genitems(subnode):
+ yield x
+ node.ihook.pytest_collectreport(report=rep)
diff --git a/_pytest/mark.py b/_pytest/mark.py
new file mode 100644
--- /dev/null
+++ b/_pytest/mark.py
@@ -0,0 +1,176 @@
+""" generic mechanism for marking and selecting python functions. """
+import pytest, py
+
+def pytest_namespace():
+ return {'mark': MarkGenerator()}
+
+def pytest_addoption(parser):
+ group = parser.getgroup("general")
+ group._addoption('-k',
+ action="store", dest="keyword", default='', metavar="KEYWORDEXPR",
+ help="only run tests which match given keyword expression. "
+ "An expression consists of space-separated terms. "
+ "Each term must match. Precede a term with '-' to negate. "
+ "Terminate expression with ':' to make the first match match "
+ "all subsequent tests (usually file-order). ")
+
+def pytest_collection_modifyitems(items, config):
+ keywordexpr = config.option.keyword
+ if not keywordexpr:
+ return
+ selectuntil = False
+ if keywordexpr[-1] == ":":
+ selectuntil = True
+ keywordexpr = keywordexpr[:-1]
+
+ remaining = []
+ deselected = []
+ for colitem in items:
+ if keywordexpr and skipbykeyword(colitem, keywordexpr):
+ deselected.append(colitem)
+ else:
+ remaining.append(colitem)
+ if selectuntil:
+ keywordexpr = None
+
+ if deselected:
+ config.hook.pytest_deselected(items=deselected)
+ items[:] = remaining
+
+def skipbykeyword(colitem, keywordexpr):
+ """ return True if they given keyword expression means to
+ skip this collector/item.
+ """
+ if not keywordexpr:
+ return
+
+ itemkeywords = getkeywords(colitem)
+ for key in filter(None, keywordexpr.split()):
+ eor = key[:1] == '-'
+ if eor:
+ key = key[1:]
+ if not (eor ^ matchonekeyword(key, itemkeywords)):
+ return True
+
+def getkeywords(node):
+ keywords = {}
+ while node is not None:
+ keywords.update(node.keywords)
+ node = node.parent
+ return keywords
+
+
+def matchonekeyword(key, itemkeywords):
+ for elem in key.split("."):
+ for kw in itemkeywords:
+ if elem in kw:
+ break
+ else:
+ return False
+ return True
+
+class MarkGenerator:
+ """ Factory for :class:`MarkDecorator` objects - exposed as
+ a ``py.test.mark`` singleton instance. Example::
+
+ import py
+ @py.test.mark.slowtest
+ def test_function():
+ pass
+
+ will set a 'slowtest' :class:`MarkInfo` object
+ on the ``test_function`` object. """
+
+ def __getattr__(self, name):
+ if name[0] == "_":
+ raise AttributeError(name)
+ return MarkDecorator(name)
+
+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 <excontrolskip>`.
+ MarkDecorator instances are often created like this::
+
+ mark1 = py.test.mark.NAME # simple MarkDecorator
+ mark2 = py.test.mark.NAME(name1=value) # parametrized MarkDecorator
+
+ and can then be applied as decorators to test functions::
+
+ @mark2
+ def test_function():
+ pass
+ """
+ def __init__(self, name, args=None, kwargs=None):
+ self.markname = name
+ self.args = args or ()
+ self.kwargs = kwargs or {}
+
+ def __repr__(self):
+ d = self.__dict__.copy()
+ name = d.pop('markname')
+ return "<MarkDecorator %r %r>" %(name, d)
+
+ def __call__(self, *args, **kwargs):
+ """ if passed a single callable argument: decorate it with mark info.
+ otherwise add *args/**kwargs in-place to mark information. """
+ if args:
+ func = args[0]
+ if len(args) == 1 and hasattr(func, '__call__') or \
+ hasattr(func, '__bases__'):
+ if hasattr(func, '__bases__'):
+ if hasattr(func, 'pytestmark'):
+ l = func.pytestmark
+ if not isinstance(l, list):
+ func.pytestmark = [l, self]
+ else:
+ l.append(self)
+ else:
+ func.pytestmark = [self]
+ else:
+ holder = getattr(func, self.markname, None)
+ if holder is None:
+ holder = MarkInfo(self.markname, self.args, self.kwargs)
+ setattr(func, self.markname, holder)
+ else:
+ holder.kwargs.update(self.kwargs)
+ holder.args += self.args
+ return func
+ kw = self.kwargs.copy()
+ kw.update(kwargs)
+ args = self.args + args
+ return self.__class__(self.markname, args=args, kwargs=kw)
+
+class MarkInfo:
+ """ Marking object created by :class:`MarkDecorator` instances. """
+ def __init__(self, name, args, kwargs):
+ #: name of attribute
+ self.name = name
+ #: positional argument list, empty if none specified
+ self.args = args
+ #: keyword argument dictionary, empty if nothing specified
+ self.kwargs = kwargs
+
+ def __repr__(self):
+ return "<MarkInfo %r args=%r kwargs=%r>" % (
+ self._name, self.args, self.kwargs)
+
+def pytest_itemcollected(item):
+ if not isinstance(item, pytest.Function):
+ return
+ try:
+ func = item.obj.__func__
+ except AttributeError:
+ func = getattr(item.obj, 'im_func', item.obj)
+ pyclasses = (pytest.Class, pytest.Module)
+ for node in item.listchain():
+ if isinstance(node, pyclasses):
+ marker = getattr(node.obj, 'pytestmark', None)
+ if marker is not None:
+ if isinstance(marker, list):
+ for mark in marker:
+ mark(func)
+ else:
+ marker(func)
+ node = node.parent
+ item.keywords.update(py.builtin._getfuncdict(func))
diff --git a/_pytest/monkeypatch.py b/_pytest/monkeypatch.py
new file mode 100644
--- /dev/null
+++ b/_pytest/monkeypatch.py
@@ -0,0 +1,103 @@
+""" monkeypatching and mocking functionality. """
+
+import os, sys
+
+def pytest_funcarg__monkeypatch(request):
+ """The returned ``monkeypatch`` funcarg provides these
+ helper methods to modify objects, dictionaries or os.environ::
+
+ monkeypatch.setattr(obj, name, value, raising=True)
+ monkeypatch.delattr(obj, name, raising=True)
+ monkeypatch.setitem(mapping, name, value)
+ monkeypatch.delitem(obj, name, raising=True)
+ monkeypatch.setenv(name, value, prepend=False)
+ monkeypatch.delenv(name, value, raising=True)
+ monkeypatch.syspath_prepend(path)
+
+ 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.
+ """
+ mpatch = monkeypatch()
+ request.addfinalizer(mpatch.undo)
+ return mpatch
+
+notset = object()
+
+class monkeypatch:
+ """ object keeping a record of setattr/item/env/syspath changes. """
+ def __init__(self):
+ self._setattr = []
+ self._setitem = []
+
+ def setattr(self, obj, name, value, raising=True):
+ """ set attribute ``name`` on ``obj`` to ``value``, by default
+ raise AttributeEror if the attribute did not exist. """
+ oldval = getattr(obj, name, notset)
+ if raising and oldval is notset:
+ raise AttributeError("%r has no attribute %r" %(obj, name))
+ self._setattr.insert(0, (obj, name, oldval))
+ setattr(obj, name, value)
+
+ def delattr(self, obj, name, raising=True):
+ """ delete attribute ``name`` from ``obj``, by default raise
+ AttributeError it the attribute did not previously exist. """
+ if not hasattr(obj, name):
+ if raising:
+ raise AttributeError(name)
+ else:
+ self._setattr.insert(0, (obj, name, getattr(obj, name, notset)))
+ delattr(obj, name)
+
+ def setitem(self, dic, name, value):
+ """ set dictionary entry ``name`` to value. """
+ self._setitem.insert(0, (dic, name, dic.get(name, notset)))
+ dic[name] = value
+
+ def delitem(self, dic, name, raising=True):
+ """ delete ``name`` from dict, raise KeyError if it doesn't exist."""
+ if name not in dic:
+ if raising:
+ raise KeyError(name)
+ else:
+ self._setitem.insert(0, (dic, name, dic.get(name, notset)))
+ del dic[name]
+
+ def setenv(self, name, value, prepend=None):
+ """ set environment variable ``name`` to ``value``. if ``prepend``
+ is a character, read the current environment variable value
+ and prepend the ``value`` adjoined with the ``prepend`` character."""
+ value = str(value)
+ if prepend and name in os.environ:
+ value = value + prepend + os.environ[name]
+ self.setitem(os.environ, name, value)
+
+ def delenv(self, name, raising=True):
+ """ delete ``name`` from environment, raise KeyError it not exists."""
+ self.delitem(os.environ, name, raising=raising)
+
+ def syspath_prepend(self, path):
+ """ prepend ``path`` to ``sys.path`` list of import locations. """
+ if not hasattr(self, '_savesyspath'):
+ self._savesyspath = sys.path[:]
+ sys.path.insert(0, str(path))
+
+ def undo(self):
+ """ undo previous changes. This call consumes the
+ undo stack. Calling it a second time has no effect unless
+ you do more monkeypatching after the undo call."""
+ for obj, name, value in self._setattr:
+ if value is not notset:
+ setattr(obj, name, value)
+ else:
+ delattr(obj, name)
+ self._setattr[:] = []
+ for dictionary, name, value in self._setitem:
+ if value is notset:
+ del dictionary[name]
+ else:
+ dictionary[name] = value
+ self._setitem[:] = []
+ if hasattr(self, '_savesyspath'):
+ sys.path[:] = self._savesyspath
diff --git a/_pytest/nose.py b/_pytest/nose.py
new file mode 100644
--- /dev/null
+++ b/_pytest/nose.py
@@ -0,0 +1,47 @@
+""" run test suites written for nose. """
+
+import pytest, py
+import inspect
+import sys
+
+def pytest_runtest_makereport(__multicall__, item, call):
+ SkipTest = getattr(sys.modules.get('nose', None), 'SkipTest', None)
+ if SkipTest:
+ if call.excinfo and call.excinfo.errisinstance(SkipTest):
+ # let's substitute the excinfo with a py.test.skip one
+ call2 = call.__class__(lambda: py.test.skip(str(call.excinfo.value)), call.when)
+ call.excinfo = call2.excinfo
+
+
+def pytest_runtest_setup(item):
+ if isinstance(item, (pytest.Function)):
+ if isinstance(item.parent, pytest.Generator):
+ gen = item.parent
+ if not hasattr(gen, '_nosegensetup'):
+ call_optional(gen.obj, 'setup')
+ if isinstance(gen.parent, pytest.Instance):
+ call_optional(gen.parent.obj, 'setup')
+ gen._nosegensetup = True
+ if not call_optional(item.obj, 'setup'):
+ # call module level setup if there is no object level one
+ call_optional(item.parent.obj, 'setup')
+
+def pytest_runtest_teardown(item):
+ if isinstance(item, pytest.Function):
+ if not call_optional(item.obj, 'teardown'):
+ call_optional(item.parent.obj, 'teardown')
+ #if hasattr(item.parent, '_nosegensetup'):
+ # #call_optional(item._nosegensetup, 'teardown')
+ # del item.parent._nosegensetup
+
+def pytest_make_collect_report(collector):
+ if isinstance(collector, pytest.Generator):
+ call_optional(collector.obj, 'setup')
+
+def call_optional(obj, name):
+ method = getattr(obj, name, None)
+ if method:
+ # If there's any problems allow the exception to raise rather than
+ # silently ignoring them
+ method()
+ return True
diff --git a/_pytest/pastebin.py b/_pytest/pastebin.py
new file mode 100644
--- /dev/null
+++ b/_pytest/pastebin.py
@@ -0,0 +1,63 @@
+""" submit failure or test session information to a pastebin service. """
+import py, sys
+
+class url:
+ base = "http://paste.pocoo.org"
+ xmlrpc = base + "/xmlrpc/"
+ show = base + "/show/"
+
+def pytest_addoption(parser):
+ group = parser.getgroup("terminal reporting")
+ group._addoption('--pastebin', metavar="mode",
+ action='store', dest="pastebin", default=None,
+ type="choice", choices=['failed', 'all'],
+ help="send failed|all info to Pocoo pastebin service.")
+
+def pytest_configure(__multicall__, config):
+ import tempfile
+ __multicall__.execute()
+ if config.option.pastebin == "all":
+ config._pastebinfile = tempfile.TemporaryFile('w+')
+ tr = config.pluginmanager.getplugin('terminalreporter')
+ oldwrite = tr._tw.write
+ def tee_write(s, **kwargs):
+ oldwrite(s, **kwargs)
+ config._pastebinfile.write(str(s))
+ tr._tw.write = tee_write
+
+def pytest_unconfigure(config):
+ if hasattr(config, '_pastebinfile'):
+ config._pastebinfile.seek(0)
+ sessionlog = config._pastebinfile.read()
+ config._pastebinfile.close()
+ del config._pastebinfile
+ proxyid = getproxy().newPaste("python", sessionlog)
+ pastebinurl = "%s%s" % (url.show, proxyid)
+ sys.stderr.write("pastebin session-log: %s\n" % pastebinurl)
+ tr = config.pluginmanager.getplugin('terminalreporter')
+ del tr._tw.__dict__['write']
+
+def getproxy():
+ return py.std.xmlrpclib.ServerProxy(url.xmlrpc).pastes
+
+def pytest_terminal_summary(terminalreporter):
+ if terminalreporter.config.option.pastebin != "failed":
+ return
+ tr = terminalreporter
+ if 'failed' in tr.stats:
+ terminalreporter.write_sep("=", "Sending information to Paste Service")
+ if tr.config.option.debug:
+ terminalreporter.write_line("xmlrpcurl: %s" %(url.xmlrpc,))
+ serverproxy = getproxy()
+ for rep in terminalreporter.stats.get('failed'):
+ try:
+ msg = rep.longrepr.reprtraceback.reprentries[-1].reprfileloc
+ except AttributeError:
+ msg = tr._getfailureheadline(rep)
+ tw = py.io.TerminalWriter(stringio=True)
+ rep.toterminal(tw)
+ s = tw.stringio.getvalue()
+ assert len(s)
+ proxyid = serverproxy.newPaste("python", s)
+ pastebinurl = "%s%s" % (url.show, proxyid)
+ tr.write_line("%s --> %s" %(msg, pastebinurl))
diff --git a/_pytest/pdb.py b/_pytest/pdb.py
new file mode 100644
--- /dev/null
+++ b/_pytest/pdb.py
@@ -0,0 +1,79 @@
+""" interactive debugging with PDB, the Python Debugger. """
+
+import pytest, py
+import sys
+
+def pytest_addoption(parser):
+ group = parser.getgroup("general")
+ group._addoption('--pdb',
+ action="store_true", dest="usepdb", default=False,
+ help="start the interactive Python debugger on errors.")
+
+def pytest_namespace():
+ return {'set_trace': pytestPDB().set_trace}
+
+def pytest_configure(config):
+ if config.getvalue("usepdb"):
+ config.pluginmanager.register(PdbInvoke(), 'pdbinvoke')
+
+class pytestPDB:
+ """ Pseudo PDB that defers to the real pdb. """
+ item = None
+
+ def set_trace(self):
+ """ invoke PDB set_trace debugging, dropping any IO capturing. """
+ frame = sys._getframe().f_back
+ item = getattr(self, 'item', None)
+ if item is not None:
+ capman = item.config.pluginmanager.getplugin("capturemanager")
+ out, err = capman.suspendcapture()
+ if hasattr(item, 'outerr'):
+ item.outerr = (item.outerr[0] + out, item.outerr[1] + err)
+ tw = py.io.TerminalWriter()
+ tw.line()
+ tw.sep(">", "PDB set_trace (IO-capturing turned off)")
+ py.std.pdb.Pdb().set_trace(frame)
+
+def pdbitem(item):
+ pytestPDB.item = item
+pytest_runtest_setup = pytest_runtest_call = pytest_runtest_teardown = pdbitem
+
+def pytest_runtest_makereport():
+ pytestPDB.item = None
+
+class PdbInvoke:
+ @pytest.mark.tryfirst
+ def pytest_runtest_makereport(self, item, call, __multicall__):
+ rep = __multicall__.execute()
+ if not call.excinfo or \
+ call.excinfo.errisinstance(pytest.skip.Exception) or \
+ call.excinfo.errisinstance(py.std.bdb.BdbQuit):
+ return rep
+ if "xfail" in rep.keywords:
+ return rep
+ # we assume that the above execute() suspended capturing
+ # XXX we re-use the TerminalReporter's terminalwriter
+ # because this seems to avoid some encoding related troubles
+ # for not completely clear reasons.
+ tw = item.config.pluginmanager.getplugin("terminalreporter")._tw
+ tw.line()
+ tw.sep(">", "traceback")
+ rep.toterminal(tw)
+ tw.sep(">", "entering PDB")
+ post_mortem(call.excinfo._excinfo[2])
+ rep._pdbshown = True
+ return rep
+
+def post_mortem(t):
+ pdb = py.std.pdb
+ class Pdb(pdb.Pdb):
+ def get_stack(self, f, t):
+ stack, i = pdb.Pdb.get_stack(self, f, t)
+ if f is None:
+ i = max(0, len(stack) - 1)
+ while i and stack[i][0].f_locals.get("__tracebackhide__", False):
+ i-=1
+ return stack, i
+ p = Pdb()
+ p.reset()
+ p.interaction(None, t)
diff --git a/_pytest/pytester.py b/_pytest/pytester.py
new file mode 100644
--- /dev/null
+++ b/_pytest/pytester.py
@@ -0,0 +1,674 @@
+""" (disabled by default) support for testing py.test and py.test plugins. """
+
+import py, pytest
+import sys, os
+import re
+import inspect
+import time
+from fnmatch import fnmatch
+from _pytest.main import Session
+from py.builtin import print_
+from _pytest.core import HookRelay
+
+def pytest_addoption(parser):
+ group = parser.getgroup("pylib")
+ group.addoption('--no-tools-on-path',
+ action="store_true", dest="notoolsonpath", default=False,
+ help=("discover tools on PATH instead of going through py.cmdline.")
+ )
+
+def pytest_configure(config):
+ # This might be called multiple times. Only take the first.
+ global _pytest_fullpath
+ import pytest
+ try:
+ _pytest_fullpath
+ except NameError:
+ _pytest_fullpath = os.path.abspath(pytest.__file__.rstrip("oc"))
+
+def pytest_funcarg___pytest(request):
+ return PytestArg(request)
+
+class PytestArg:
+ def __init__(self, request):
+ self.request = request
+
+ def gethookrecorder(self, hook):
+ hookrecorder = HookRecorder(hook._pm)
+ hookrecorder.start_recording(hook._hookspecs)
+ self.request.addfinalizer(hookrecorder.finish_recording)
+ return hookrecorder
+
+class ParsedCall:
+ def __init__(self, name, locals):
+ assert '_name' not in locals
+ self.__dict__.update(locals)
+ self.__dict__.pop('self')
+ self._name = name
+
+ def __repr__(self):
+ d = self.__dict__.copy()
+ del d['_name']
+ return "<ParsedCall %r(**%r)>" %(self._name, d)
+
+class HookRecorder:
+ def __init__(self, pluginmanager):
+ self._pluginmanager = pluginmanager
+ self.calls = []
+ self._recorders = {}
+
+ def start_recording(self, hookspecs):
+ if not isinstance(hookspecs, (list, tuple)):
+ hookspecs = [hookspecs]
+ for hookspec in hookspecs:
+ assert hookspec not in self._recorders
+ class RecordCalls:
+ _recorder = self
+ for name, method in vars(hookspec).items():
+ if name[0] != "_":
+ setattr(RecordCalls, name, self._makecallparser(method))
+ recorder = RecordCalls()
+ self._recorders[hookspec] = recorder
+ self._pluginmanager.register(recorder)
+ self.hook = HookRelay(hookspecs, pm=self._pluginmanager,
+ prefix="pytest_")
+
+ def finish_recording(self):
+ for recorder in self._recorders.values():
+ self._pluginmanager.unregister(recorder)
+ self._recorders.clear()
+
+ def _makecallparser(self, method):
+ name = method.__name__
+ args, varargs, varkw, default = py.std.inspect.getargspec(method)
+ if not args or args[0] != "self":
+ args.insert(0, 'self')
+ fspec = py.std.inspect.formatargspec(args, varargs, varkw, default)
+ # we use exec because we want to have early type
+ # errors on wrong input arguments, using
+ # *args/**kwargs delays this and gives errors
+ # elsewhere
+ exec (py.code.compile("""
+ def %(name)s%(fspec)s:
+ self._recorder.calls.append(
+ ParsedCall(%(name)r, locals()))
+ """ % locals()))
+ return locals()[name]
+
+ def getcalls(self, names):
+ if isinstance(names, str):
+ names = names.split()
+ for name in names:
+ for cls in self._recorders:
+ if name in vars(cls):
+ break
+ else:
+ raise ValueError("callname %r not found in %r" %(
+ name, self._recorders.keys()))
+ l = []
+ for call in self.calls:
+ if call._name in names:
+ l.append(call)
+ return l
+
+ def contains(self, entries):
+ __tracebackhide__ = True
+ from py.builtin import print_
+ i = 0
+ entries = list(entries)
+ backlocals = py.std.sys._getframe(1).f_locals
+ while entries:
+ name, check = entries.pop(0)
+ for ind, call in enumerate(self.calls[i:]):
+ if call._name == name:
+ print_("NAMEMATCH", name, call)
+ if eval(check, backlocals, call.__dict__):
+ print_("CHECKERMATCH", repr(check), "->", call)
+ else:
+ print_("NOCHECKERMATCH", repr(check), "-", call)
+ continue
+ i += ind + 1
+ break
+ print_("NONAMEMATCH", name, "with", call)
+ else:
+ py.test.fail("could not find %r check %r" % (name, check))
+
+ def popcall(self, name):
+ __tracebackhide__ = True
+ for i, call in enumerate(self.calls):
+ if call._name == name:
+ del self.calls[i]
+ return call
+ lines = ["could not find call %r, in:" % (name,)]
+ lines.extend([" %s" % str(x) for x in self.calls])
+ py.test.fail("\n".join(lines))
+
+ def getcall(self, name):
+ l = self.getcalls(name)
+ assert len(l) == 1, (name, l)
+ return l[0]
+
+
+def pytest_funcarg__linecomp(request):
+ return LineComp()
+
+def pytest_funcarg__LineMatcher(request):
+ return LineMatcher
+
+def pytest_funcarg__testdir(request):
+ tmptestdir = TmpTestdir(request)
+ return tmptestdir
+
+rex_outcome = re.compile("(\d+) (\w+)")
+class RunResult:
+ def __init__(self, ret, outlines, errlines, duration):
+ self.ret = ret
+ self.outlines = outlines
+ self.errlines = errlines
+ self.stdout = LineMatcher(outlines)
+ self.stderr = LineMatcher(errlines)
+ self.duration = duration
+
+ def parseoutcomes(self):
+ for line in reversed(self.outlines):
+ if 'seconds' in line:
+ outcomes = rex_outcome.findall(line)
+ if outcomes:
+ d = {}
+ for num, cat in outcomes:
+ d[cat] = int(num)
+ return d
+
+class TmpTestdir:
+ def __init__(self, request):
+ self.request = request
+ self.Config = request.config.__class__
+ self._pytest = request.getfuncargvalue("_pytest")
+ # XXX remove duplication with tmpdir plugin
+ basetmp = request.config._tmpdirhandler.ensuretemp("testdir")
+ name = request.function.__name__
+ for i in range(100):
+ try:
+ tmpdir = basetmp.mkdir(name + str(i))
+ except py.error.EEXIST:
+ continue
+ break
+ # we need to create another subdir
+ # because Directory.collect() currently loads
+ # conftest.py from sibling directories
+ self.tmpdir = tmpdir.mkdir(name)
+ self.plugins = []
+ self._syspathremove = []
+ self.chdir() # always chdir
+ self.request.addfinalizer(self.finalize)
+
+ def __repr__(self):
+ return "<TmpTestdir %r>" % (self.tmpdir,)
+
+ def finalize(self):
+ for p in self._syspathremove:
+ py.std.sys.path.remove(p)
+ if hasattr(self, '_olddir'):
+ self._olddir.chdir()
+ # delete modules that have been loaded from tmpdir
+ for name, mod in list(sys.modules.items()):
+ if mod:
+ fn = getattr(mod, '__file__', None)
+ if fn and fn.startswith(str(self.tmpdir)):
+ del sys.modules[name]
+
+ def getreportrecorder(self, obj):
+ if hasattr(obj, 'config'):
+ obj = obj.config
+ if hasattr(obj, 'hook'):
+ obj = obj.hook
+ assert hasattr(obj, '_hookspecs'), obj
+ reprec = ReportRecorder(obj)
+ reprec.hookrecorder = self._pytest.gethookrecorder(obj)
+ reprec.hook = reprec.hookrecorder.hook
+ return reprec
+
+ def chdir(self):
+ old = self.tmpdir.chdir()
+ if not hasattr(self, '_olddir'):
+ self._olddir = old
+
+ def _makefile(self, ext, args, kwargs):
+ items = list(kwargs.items())
+ if args:
+ source = "\n".join(map(str, args)) + "\n"
+ basename = self.request.function.__name__
+ items.insert(0, (basename, source))
+ ret = None
+ for name, value in items:
+ p = self.tmpdir.join(name).new(ext=ext)
+ source = str(py.code.Source(value)).lstrip()
+ p.write(source.encode("utf-8"), "wb")
+ if ret is None:
+ ret = p
+ return ret
+
+
+ def makefile(self, ext, *args, **kwargs):
+ return self._makefile(ext, args, kwargs)
+
+ def makeini(self, source):
+ return self.makefile('cfg', setup=source)
+
+ def makeconftest(self, source):
+ return self.makepyfile(conftest=source)
+
+ def makeini(self, source):
+ return self.makefile('.ini', tox=source)
+
+ def getinicfg(self, source):
+ p = self.makeini(source)
+ return py.iniconfig.IniConfig(p)['pytest']
+
+ def makepyfile(self, *args, **kwargs):
+ return self._makefile('.py', args, kwargs)
+
+ def maketxtfile(self, *args, **kwargs):
+ return self._makefile('.txt', args, kwargs)
+
+ def syspathinsert(self, path=None):
+ if path is None:
+ path = self.tmpdir
+ py.std.sys.path.insert(0, str(path))
+ self._syspathremove.append(str(path))
+
+ def mkdir(self, name):
+ return self.tmpdir.mkdir(name)
+
+ def mkpydir(self, name):
+ p = self.mkdir(name)
+ p.ensure("__init__.py")
+ return p
+
+ Session = Session
+ def getnode(self, config, arg):
+ session = Session(config)
+ assert '::' not in str(arg)
+ p = py.path.local(arg)
+ x = session.fspath.bestrelpath(p)
+ return session.perform_collect([x], genitems=False)[0]
+
+ def getpathnode(self, path):
+ config = self.parseconfig(path)
+ session = Session(config)
+ x = session.fspath.bestrelpath(path)
+ return session.perform_collect([x], genitems=False)[0]
+
+ def genitems(self, colitems):
+ session = colitems[0].session
+ result = []
+ for colitem in colitems:
+ result.extend(session.genitems(colitem))
+ return result
+
+ def inline_genitems(self, *args):
+ #config = self.parseconfig(*args)
+ config = self.parseconfigure(*args)
+ rec = self.getreportrecorder(config)
+ session = Session(config)
+ session.perform_collect()
+ return session.items, rec
+
+ def runitem(self, source):
+ # used from runner functional tests
+ item = self.getitem(source)
+ # the test class where we are called from wants to provide the runner
+ testclassinstance = py.builtin._getimself(self.request.function)
+ runner = testclassinstance.getrunner()
+ return runner(item)
+
+ def inline_runsource(self, source, *cmdlineargs):
+ p = self.makepyfile(source)
+ l = list(cmdlineargs) + [p]
+ return self.inline_run(*l)
+
+ def inline_runsource1(self, *args):
+ args = list(args)
+ source = args.pop()
+ p = self.makepyfile(source)
+ l = list(args) + [p]
+ reprec = self.inline_run(*l)
+ reports = reprec.getreports("pytest_runtest_logreport")
+ assert len(reports) == 1, reports
+ return reports[0]
+
+ def inline_run(self, *args):
+ args = ("-s", ) + args # otherwise FD leakage
+ config = self.parseconfig(*args)
+ reprec = self.getreportrecorder(config)
+ #config.pluginmanager.do_configure(config)
+ config.hook.pytest_cmdline_main(config=config)
+ #config.pluginmanager.do_unconfigure(config)
+ return reprec
+
+ def config_preparse(self):
+ config = self.Config()
+ for plugin in self.plugins:
+ if isinstance(plugin, str):
+ config.pluginmanager.import_plugin(plugin)
+ else:
+ if isinstance(plugin, dict):
+ plugin = PseudoPlugin(plugin)
+ if not config.pluginmanager.isregistered(plugin):
+ config.pluginmanager.register(plugin)
+ return config
+
+ def parseconfig(self, *args):
+ if not args:
+ args = (self.tmpdir,)
+ config = self.config_preparse()
+ args = list(args)
+ for x in args:
+ if str(x).startswith('--basetemp'):
+ break
+ else:
+ args.append("--basetemp=%s" % self.tmpdir.dirpath('basetemp'))
+ config.parse(args)
+ return config
+
+ def reparseconfig(self, args=None):
+ """ this is used from tests that want to re-invoke parse(). """
+ if not args:
+ args = [self.tmpdir]
+ oldconfig = getattr(py.test, 'config', None)
+ try:
+ c = py.test.config = self.Config()
+ c.basetemp = py.path.local.make_numbered_dir(prefix="reparse",
+ keep=0, rootdir=self.tmpdir, lock_timeout=None)
+ c.parse(args)
+ return c
+ finally:
+ py.test.config = oldconfig
+
+ def parseconfigure(self, *args):
+ config = self.parseconfig(*args)
+ config.pluginmanager.do_configure(config)
+ self.request.addfinalizer(lambda:
+ config.pluginmanager.do_unconfigure(config))
+ return config
+
+ def getitem(self, source, funcname="test_func"):
+ for item in self.getitems(source):
+ if item.name == funcname:
+ return item
+ assert 0, "%r item not found in module:\n%s" %(funcname, source)
+
+ def getitems(self, source):
+ modcol = self.getmodulecol(source)
+ return self.genitems([modcol])
+
+ def getmodulecol(self, source, configargs=(), withinit=False):
+ kw = {self.request.function.__name__: py.code.Source(source).strip()}
+ path = self.makepyfile(**kw)
+ if withinit:
+ self.makepyfile(__init__ = "#")
+ self.config = config = self.parseconfigure(path, *configargs)
+ node = self.getnode(config, path)
+ #config.pluginmanager.do_unconfigure(config)
+ return node
+
+ def collect_by_name(self, modcol, name):
+ for colitem in modcol._memocollect():
+ if colitem.name == name:
+ return colitem
+
+ def popen(self, cmdargs, stdout, stderr, **kw):
+ env = os.environ.copy()
+ env['PYTHONPATH'] = os.pathsep.join(filter(None, [
+ str(os.getcwd()), env.get('PYTHONPATH', '')]))
+ kw['env'] = env
+ #print "env", env
+ return py.std.subprocess.Popen(cmdargs, stdout=stdout, stderr=stderr, **kw)
+
+ def pytestmain(self, *args, **kwargs):
+ ret = pytest.main(*args, **kwargs)
+ if ret == 2:
+ raise KeyboardInterrupt()
+ def run(self, *cmdargs):
+ return self._run(*cmdargs)
+
+ def _run(self, *cmdargs):
+ cmdargs = [str(x) for x in cmdargs]
+ p1 = self.tmpdir.join("stdout")
+ p2 = self.tmpdir.join("stderr")
+ print_("running", cmdargs, "curdir=", py.path.local())
+ f1 = p1.open("wb")
+ f2 = p2.open("wb")
+ now = time.time()
+ popen = self.popen(cmdargs, stdout=f1, stderr=f2,
+ close_fds=(sys.platform != "win32"))
+ ret = popen.wait()
+ f1.close()
+ f2.close()
+ out = p1.read("rb")
+ out = getdecoded(out).splitlines()
+ err = p2.read("rb")
+ err = getdecoded(err).splitlines()
+ def dump_lines(lines, fp):
+ try:
+ for line in lines:
+ py.builtin.print_(line, file=fp)
+ except UnicodeEncodeError:
+ print("couldn't print to %s because of encoding" % (fp,))
+ dump_lines(out, sys.stdout)
+ dump_lines(err, sys.stderr)
+ return RunResult(ret, out, err, time.time()-now)
+
+ def runpybin(self, scriptname, *args):
+ fullargs = self._getpybinargs(scriptname) + args
+ return self.run(*fullargs)
+
+ def _getpybinargs(self, scriptname):
+ if not self.request.config.getvalue("notoolsonpath"):
+ # XXX we rely on script refering to the correct environment
+ # we cannot use "(py.std.sys.executable,script)"
+ # becaue on windows the script is e.g. a py.test.exe
+ return (py.std.sys.executable, _pytest_fullpath,)
+ else:
+ py.test.skip("cannot run %r with --no-tools-on-path" % scriptname)
+
+ def runpython(self, script, prepend=True):
+ if prepend:
+ s = self._getsysprepend()
+ if s:
+ script.write(s + "\n" + script.read())
+ return self.run(sys.executable, script)
+
+ def _getsysprepend(self):
+ if self.request.config.getvalue("notoolsonpath"):
+ s = "import sys;sys.path.insert(0,%r);" % str(py._pydir.dirpath())
+ else:
+ s = ""
+ return s
+
+ def runpython_c(self, command):
+ command = self._getsysprepend() + command
+ return self.run(py.std.sys.executable, "-c", command)
+
+ def runpytest(self, *args):
+ p = py.path.local.make_numbered_dir(prefix="runpytest-",
+ keep=None, rootdir=self.tmpdir)
+ args = ('--basetemp=%s' % p, ) + args
+ #for x in args:
+ # if '--confcutdir' in str(x):
+ # break
+ #else:
+ # pass
+ # args = ('--confcutdir=.',) + args
+ plugins = [x for x in self.plugins if isinstance(x, str)]
+ if plugins:
+ args = ('-p', plugins[0]) + args
+ return self.runpybin("py.test", *args)
+
+ def spawn_pytest(self, string, expect_timeout=10.0):
+ if self.request.config.getvalue("notoolsonpath"):
+ py.test.skip("--no-tools-on-path prevents running pexpect-spawn tests")
+ basetemp = self.tmpdir.mkdir("pexpect")
+ invoke = " ".join(map(str, self._getpybinargs("py.test")))
+ cmd = "%s --basetemp=%s %s" % (invoke, basetemp, string)
+ return self.spawn(cmd, expect_timeout=expect_timeout)
+
+ def spawn(self, cmd, expect_timeout=10.0):
+ pexpect = py.test.importorskip("pexpect", "2.4")
+ if hasattr(sys, 'pypy_version_info') and '64' in py.std.platform.machine():
+ pytest.skip("pypy-64 bit not supported")
+ logfile = self.tmpdir.join("spawn.out")
+ child = pexpect.spawn(cmd, logfile=logfile.open("w"))
+ child.timeout = expect_timeout
+ return child
+
+def getdecoded(out):
+ try:
+ return out.decode("utf-8")
+ except UnicodeDecodeError:
+ return "INTERNAL not-utf8-decodeable, truncated string:\n%s" % (
+ py.io.saferepr(out),)
+
+class PseudoPlugin:
+ def __init__(self, vars):
+ self.__dict__.update(vars)
+
+class ReportRecorder(object):
+ def __init__(self, hook):
+ self.hook = hook
+ self.pluginmanager = hook._pm
+ self.pluginmanager.register(self)
+
+ def getcall(self, name):
+ return self.hookrecorder.getcall(name)
+
+ def popcall(self, name):
+ return self.hookrecorder.popcall(name)
+
+ def getcalls(self, names):
+ """ return list of ParsedCall instances matching the given eventname. """
+ return self.hookrecorder.getcalls(names)
+
+ # functionality for test reports
+
+ def getreports(self, names="pytest_runtest_logreport pytest_collectreport"):
+ return [x.report for x in self.getcalls(names)]
+
+ def matchreport(self, inamepart="", names="pytest_runtest_logreport pytest_collectreport", when=None):
+ """ return a testreport whose dotted import path matches """
+ l = []
+ for rep in self.getreports(names=names):
+ if when and getattr(rep, 'when', None) != when:
+ continue
+ if not inamepart or inamepart in rep.nodeid.split("::"):
+ l.append(rep)
+ if not l:
+ raise ValueError("could not find test report matching %r: no test reports at all!" %
+ (inamepart,))
+ if len(l) > 1:
+ raise ValueError("found more than one testreport matching %r: %s" %(
+ inamepart, l))
+ return l[0]
+
+ def getfailures(self, names='pytest_runtest_logreport pytest_collectreport'):
+ return [rep for rep in self.getreports(names) if rep.failed]
+
+ def getfailedcollections(self):
+ return self.getfailures('pytest_collectreport')
+
+ def listoutcomes(self):
+ passed = []
+ skipped = []
+ failed = []
+ for rep in self.getreports("pytest_runtest_logreport"):
+ if rep.passed:
+ if rep.when == "call":
+ passed.append(rep)
+ elif rep.skipped:
+ skipped.append(rep)
+ elif rep.failed:
+ failed.append(rep)
+ return passed, skipped, failed
+
+ def countoutcomes(self):
+ return [len(x) for x in self.listoutcomes()]
+
+ def assertoutcome(self, passed=0, skipped=0, failed=0):
+ realpassed, realskipped, realfailed = self.listoutcomes()
+ assert passed == len(realpassed)
+ assert skipped == len(realskipped)
+ assert failed == len(realfailed)
+
+ def clear(self):
+ self.hookrecorder.calls[:] = []
+
+ def unregister(self):
+ self.pluginmanager.unregister(self)
+ self.hookrecorder.finish_recording()
+
+class LineComp:
+ def __init__(self):
+ self.stringio = py.io.TextIO()
+
+ def assert_contains_lines(self, lines2):
+ """ assert that lines2 are contained (linearly) in lines1.
+ return a list of extralines found.
+ """
+ __tracebackhide__ = True
+ val = self.stringio.getvalue()
+ self.stringio.truncate(0)
+ self.stringio.seek(0)
+ lines1 = val.split("\n")
+ return LineMatcher(lines1).fnmatch_lines(lines2)
+
+class LineMatcher:
+ def __init__(self, lines):
+ self.lines = lines
+
+ def str(self):
+ return "\n".join(self.lines)
+
+ def _getlines(self, lines2):
+ if isinstance(lines2, str):
+ lines2 = py.code.Source(lines2)
+ if isinstance(lines2, py.code.Source):
+ lines2 = lines2.strip().lines
+ return lines2
+
+ def fnmatch_lines_random(self, lines2):
+ lines2 = self._getlines(lines2)
+ for line in lines2:
+ for x in self.lines:
+ if line == x or fnmatch(x, line):
+ print_("matched: ", repr(line))
+ break
+ else:
+ raise ValueError("line %r not found in output" % line)
+
+ def fnmatch_lines(self, lines2):
+ def show(arg1, arg2):
+ py.builtin.print_(arg1, arg2, file=py.std.sys.stderr)
+ lines2 = self._getlines(lines2)
+ lines1 = self.lines[:]
+ nextline = None
+ extralines = []
+ __tracebackhide__ = True
+ for line in lines2:
+ nomatchprinted = False
+ while lines1:
+ nextline = lines1.pop(0)
+ if line == nextline:
+ show("exact match:", repr(line))
+ break
+ elif fnmatch(nextline, line):
+ show("fnmatch:", repr(line))
+ show(" with:", repr(nextline))
+ break
+ else:
+ if not nomatchprinted:
+ show("nomatch:", repr(line))
+ nomatchprinted = True
+ show(" and:", repr(nextline))
+ extralines.append(nextline)
+ else:
+ py.test.fail("remains unmatched: %r, see stderr" % (line,))
diff --git a/_pytest/python.py b/_pytest/python.py
new file mode 100644
--- /dev/null
+++ b/_pytest/python.py
@@ -0,0 +1,870 @@
+""" Python test discovery, setup and run of test functions. """
+import py
+import inspect
+import sys
+import pytest
+from py._code.code import TerminalRepr
+
+import _pytest
+cutdir = py.path.local(_pytest.__file__).dirpath()
+
+def pytest_addoption(parser):
+ group = parser.getgroup("general")
+ group.addoption('--funcargs',
+ action="store_true", dest="showfuncargs", default=False,
+ help="show available function arguments, sorted by plugin")
+ parser.addini("python_files", type="args",
+ default=('test_*.py', '*_test.py'),
+ help="glob-style file patterns for Python test module discovery")
+ parser.addini("python_classes", type="args", default=("Test",),
+ help="prefixes for Python test class discovery")
+ parser.addini("python_functions", type="args", default=("test",),
+ help="prefixes for Python test function and method discovery")
+
+def pytest_cmdline_main(config):
+ if config.option.showfuncargs:
+ showfuncargs(config)
+ return 0
+
+ at pytest.mark.trylast
+def pytest_namespace():
+ raises.Exception = pytest.fail.Exception
+ return {
+ 'raises' : raises,
+ 'collect': {
+ 'Module': Module, 'Class': Class, 'Instance': Instance,
+ 'Function': Function, 'Generator': Generator,
+ '_fillfuncargs': fillfuncargs}
+ }
+
+def pytest_funcarg__pytestconfig(request):
+ """ the pytest config object with access to command line opts."""
+ return request.config
+
+def pytest_pyfunc_call(__multicall__, pyfuncitem):
+ if not __multicall__.execute():
+ testfunction = pyfuncitem.obj
+ if pyfuncitem._isyieldedfunction():
+ testfunction(*pyfuncitem._args)
+ else:
+ funcargs = pyfuncitem.funcargs
+ testfunction(**funcargs)
+
+def pytest_collect_file(path, parent):
+ ext = path.ext
+ pb = path.purebasename
+ if ext == ".py":
+ if not parent.session.isinitpath(path):
+ for pat in parent.config.getini('python_files'):
+ if path.fnmatch(pat):
+ break
+ else:
+ return
+ return parent.ihook.pytest_pycollect_makemodule(
+ path=path, parent=parent)
+
+def pytest_pycollect_makemodule(path, parent):
+ return Module(path, parent)
+
+def pytest_pycollect_makeitem(__multicall__, collector, name, obj):
+ res = __multicall__.execute()
+ if res is not None:
+ return res
+ if collector._istestclasscandidate(name, obj):
+ #if hasattr(collector.obj, 'unittest'):
+ # return # we assume it's a mixin class for a TestCase derived one
+ 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)
+ else:
+ return collector._genfunctions(name, obj)
+
+def is_generator(func):
+ try:
+ return py.code.getrawcode(func).co_flags & 32 # generator function
+ except AttributeError: # builtin functions have no bytecode
+ # assume them to not be generators
+ return False
+
+class PyobjMixin(object):
+ def obj():
+ def fget(self):
+ try:
+ return self._obj
+ except AttributeError:
+ self._obj = obj = self._getobj()
+ return obj
+ def fset(self, value):
+ self._obj = value
+ return property(fget, fset, None, "underlying python object")
+ obj = obj()
+
+ def _getobj(self):
+ return getattr(self.parent.obj, self.name)
+
+ def getmodpath(self, stopatmodule=True, includemodule=False):
+ """ return python path relative to the containing module. """
+ chain = self.listchain()
+ chain.reverse()
+ parts = []
+ for node in chain:
+ if isinstance(node, Instance):
+ continue
+ name = node.name
+ if isinstance(node, Module):
+ assert name.endswith(".py")
+ name = name[:-3]
+ if stopatmodule:
+ if includemodule:
+ parts.append(name)
+ break
+ parts.append(name)
+ parts.reverse()
+ s = ".".join(parts)
+ return s.replace(".[", "[")
+
+ def _getfslineno(self):
+ try:
+ return self._fslineno
+ except AttributeError:
+ pass
+ obj = self.obj
+ # xxx let decorators etc specify a sane ordering
+ if hasattr(obj, 'place_as'):
+ obj = obj.place_as
+
+ self._fslineno = py.code.getfslineno(obj)
+ return self._fslineno
+
+ def reportinfo(self):
+ # XXX caching?
+ obj = self.obj
+ if hasattr(obj, 'compat_co_firstlineno'):
+ # nose compatibility
+ fspath = sys.modules[obj.__module__].__file__
+ if fspath.endswith(".pyc"):
+ fspath = fspath[:-1]
+ #assert 0
+ #fn = inspect.getsourcefile(obj) or inspect.getfile(obj)
+ lineno = obj.compat_co_firstlineno
+ modpath = obj.__module__
+ else:
+ fspath, lineno = self._getfslineno()
+ modpath = self.getmodpath()
+ return fspath, lineno, modpath
+
+class PyCollectorMixin(PyobjMixin, pytest.Collector):
+
+ def funcnamefilter(self, name):
+ for prefix in self.config.getini("python_functions"):
+ if name.startswith(prefix):
+ return True
+
+ def classnamefilter(self, name):
+ for prefix in self.config.getini("python_classes"):
+ if name.startswith(prefix):
+ return True
+
+ def collect(self):
+ # NB. we avoid random getattrs and peek in the __dict__ instead
+ # (XXX originally introduced from a PyPy need, still true?)
+ dicts = [getattr(self.obj, '__dict__', {})]
+ for basecls in inspect.getmro(self.obj.__class__):
+ dicts.append(basecls.__dict__)
+ seen = {}
+ l = []
+ for dic in dicts:
+ for name, obj in dic.items():
+ if name in seen:
+ continue
+ seen[name] = True
+ if name[0] != "_":
+ res = self.makeitem(name, obj)
+ if res is None:
+ continue
+ if not isinstance(res, list):
+ res = [res]
+ l.extend(res)
+ l.sort(key=lambda item: item.reportinfo()[:2])
+ return l
+
+ def makeitem(self, name, obj):
+ return self.ihook.pytest_pycollect_makeitem(
+ collector=self, name=name, obj=obj)
+
+ def _istestclasscandidate(self, name, obj):
+ if self.classnamefilter(name) and \
+ inspect.isclass(obj):
+ if hasinit(obj):
+ # XXX WARN
+ return False
+ return True
+
+ def _genfunctions(self, name, funcobj):
+ module = self.getparent(Module).obj
+ clscol = self.getparent(Class)
+ cls = clscol and clscol.obj or None
+ metafunc = Metafunc(funcobj, config=self.config,
+ cls=cls, module=module)
+ gentesthook = self.config.hook.pytest_generate_tests
+ extra = [module]
+ if cls is not None:
+ extra.append(cls())
+ plugins = self.getplugins() + extra
+ gentesthook.pcall(plugins, metafunc=metafunc)
+ Function = self._getcustomclass("Function")
+ if not metafunc._calls:
+ return Function(name, parent=self)
+ l = []
+ for callspec in metafunc._calls:
+ subname = "%s[%s]" %(name, callspec.id)
+ 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)
+
+ def _importtestmodule(self):
+ # we assume we are only called once per module
+ try:
+ mod = self.fspath.pyimport(ensuresyspath=True)
+ except SyntaxError:
+ excinfo = py.code.ExceptionInfo()
+ raise self.CollectError(excinfo.getrepr(style="short"))
+ except self.fspath.ImportMismatchError:
+ e = sys.exc_info()[1]
+ raise self.CollectError(
+ "import file mismatch:\n"
+ "imported module %r has this __file__ attribute:\n"
+ " %s\n"
+ "which is not the same as the test file we want to collect:\n"
+ " %s\n"
+ "HINT: use a unique basename for your test file modules"
+ % e.args
+ )
+ #print "imported test module", mod
+ self.config.pluginmanager.consider_module(mod)
+ return mod
+
+ def setup(self):
+ if hasattr(self.obj, 'setup_module'):
+ #XXX: nose compat hack, move to nose plugin
+ # if it takes a positional arg, its probably a pytest style one
+ # so we pass the current module object
+ if inspect.getargspec(self.obj.setup_module)[0]:
+ self.obj.setup_module(self.obj)
+ else:
+ self.obj.setup_module()
+
+ def teardown(self):
+ if hasattr(self.obj, 'teardown_module'):
+ #XXX: nose compat hack, move to nose plugin
+ # if it takes a positional arg, its probably a py.test style one
+ # so we pass the current module object
+ if inspect.getargspec(self.obj.teardown_module)[0]:
+ self.obj.teardown_module(self.obj)
+ else:
+ self.obj.teardown_module()
+
+class Class(PyCollectorMixin, pytest.Collector):
+
+ def collect(self):
+ return [self._getcustomclass("Instance")(name="()", parent=self)]
+
+ def setup(self):
+ setup_class = getattr(self.obj, 'setup_class', None)
+ if setup_class is not None:
+ setup_class = getattr(setup_class, 'im_func', setup_class)
+ setup_class(self.obj)
+
+ def teardown(self):
+ teardown_class = getattr(self.obj, 'teardown_class', None)
+ if teardown_class is not None:
+ teardown_class = getattr(teardown_class, 'im_func', teardown_class)
+ teardown_class(self.obj)
+
+class Instance(PyCollectorMixin, pytest.Collector):
+ def _getobj(self):
+ return self.parent.obj()
+
+ def newinstance(self):
+ self.obj = self._getobj()
+ return self.obj
+
+class FunctionMixin(PyobjMixin):
+ """ mixin for the code common to Function and Generator.
+ """
+ def setup(self):
+ """ perform setup for this test function. """
+ if hasattr(self, '_preservedparent'):
+ obj = self._preservedparent
+ elif isinstance(self.parent, Instance):
+ obj = self.parent.newinstance()
+ 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)
+
+ def teardown(self):
+ """ perform teardown for this test function. """
+ if inspect.ismethod(self.obj):
+ name = 'teardown_method'
+ else:
+ name = 'teardown_function'
+ obj = self.parent.obj
+ teardown_func_or_meth = getattr(obj, name, None)
+ if teardown_func_or_meth is not None:
+ teardown_func_or_meth(self.obj)
+
+ def _prunetraceback(self, excinfo):
+ if hasattr(self, '_obj') and not self.config.option.fulltrace:
+ code = py.code.Code(self.obj)
+ path, firstlineno = code.path, code.firstlineno
+ traceback = excinfo.traceback
+ ntraceback = traceback.cut(path=path, firstlineno=firstlineno)
+ if ntraceback == traceback:
+ ntraceback = ntraceback.cut(path=path)
+ if ntraceback == traceback:
+ ntraceback = ntraceback.cut(excludepath=cutdir)
+ excinfo.traceback = ntraceback.filter()
+
+ def _repr_failure_py(self, excinfo, style="long"):
+ if excinfo.errisinstance(FuncargRequest.LookupError):
+ fspath, lineno, msg = self.reportinfo()
+ lines, _ = inspect.getsourcelines(self.obj)
+ for i, line in enumerate(lines):
+ if line.strip().startswith('def'):
+ return FuncargLookupErrorRepr(fspath, lineno,
+ lines[:i+1], str(excinfo.value))
+ if excinfo.errisinstance(pytest.fail.Exception):
+ if not excinfo.value.pytrace:
+ return str(excinfo.value)
+ return super(FunctionMixin, self)._repr_failure_py(excinfo,
+ style=style)
+
+ def repr_failure(self, excinfo, outerr=None):
+ assert outerr is None, "XXX outerr usage is deprecated"
+ return self._repr_failure_py(excinfo,
+ style=self.config.option.tbstyle)
+
+class FuncargLookupErrorRepr(TerminalRepr):
+ def __init__(self, filename, firstlineno, deflines, errorstring):
+ self.deflines = deflines
+ self.errorstring = errorstring
+ self.filename = filename
+ self.firstlineno = firstlineno
+
+ def toterminal(self, tw):
+ tw.line()
+ for line in self.deflines:
+ tw.line(" " + line.strip())
+ for line in self.errorstring.split("\n"):
+ tw.line(" " + line.strip(), red=True)
+ tw.line()
+ tw.line("%s:%d" % (self.filename, self.firstlineno+1))
+
+class Generator(FunctionMixin, PyCollectorMixin, pytest.Collector):
+ def collect(self):
+ # test generators are seen as collectors but they also
+ # invoke setup/teardown on popular request
+ # (induced by the common "test_*" naming shared with normal tests)
+ self.config._setupstate.prepare(self)
+ # see FunctionMixin.setup and test_setupstate_is_preserved_134
+ self._preservedparent = self.parent.obj
+ l = []
+ seen = {}
+ for i, x in enumerate(self.obj()):
+ name, call, args = self.getcallargs(x)
+ if not py.builtin.callable(call):
+ raise TypeError("%r yielded non callable test %r" %(self.obj, call,))
+ if name is None:
+ name = "[%d]" % i
+ else:
+ name = "['%s']" % name
+ if name in seen:
+ raise ValueError("%r generated tests with non-unique name %r" %(self, name))
+ seen[name] = True
+ l.append(self.Function(name, self, args=args, callobj=call))
+ return l
+
+ def getcallargs(self, obj):
+ if not isinstance(obj, (tuple, list)):
+ obj = (obj,)
+ # explict naming
+ if isinstance(obj[0], py.builtin._basestring):
+ name = obj[0]
+ obj = obj[1:]
+ else:
+ name = None
+ call, args = obj[0], obj[1:]
+ return name, call, args
+
+
+#
+# Test Items
+#
+_dummy = object()
+class Function(FunctionMixin, pytest.Item):
+ """ a Function Item is responsible for setting up
+ and executing a Python callable test object.
+ """
+ _genid = None
+ def __init__(self, name, parent=None, args=None, config=None,
+ callspec=None, callobj=_dummy, keywords=None, session=None):
+ super(Function, self).__init__(name, parent,
+ config=config, session=session)
+ self._args = args
+ if self._isyieldedfunction():
+ assert not callspec, (
+ "yielded functions (deprecated) cannot have funcargs")
+ else:
+ if callspec is not None:
+ self.funcargs = callspec.funcargs or {}
+ self._genid = callspec.id
+ if hasattr(callspec, "param"):
+ self._requestparam = callspec.param
+ else:
+ self.funcargs = {}
+ if callobj is not _dummy:
+ self._obj = callobj
+ self.function = getattr(self.obj, 'im_func', self.obj)
+ self.keywords.update(py.builtin._getfuncdict(self.obj) or {})
+ if keywords:
+ self.keywords.update(keywords)
+
+ def _getobj(self):
+ name = self.name
+ i = name.find("[") # parametrization
+ if i != -1:
+ name = name[:i]
+ return getattr(self.parent.obj, name)
+
+ def _isyieldedfunction(self):
+ return self._args is not None
+
+ def runtest(self):
+ """ execute the underlying test function. """
+ self.ihook.pytest_pyfunc_call(pyfuncitem=self)
+
+ def setup(self):
+ super(Function, self).setup()
+ if hasattr(self, 'funcargs'):
+ fillfuncargs(self)
+
+ def __eq__(self, other):
+ try:
+ return (self.name == other.name and
+ self._args == other._args and
+ self.parent == other.parent and
+ self.obj == other.obj and
+ getattr(self, '_genid', None) ==
+ getattr(other, '_genid', None)
+ )
+ except AttributeError:
+ pass
+ return False
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __hash__(self):
+ return hash((self.parent, self.name))
+
+def hasinit(obj):
+ init = getattr(obj, '__init__', None)
+ if init:
+ if init != object.__init__:
+ return True
+
+
+def getfuncargnames(function, startindex=None):
+ # XXX merge with main.py's varnames
+ argnames = py.std.inspect.getargs(py.code.getrawcode(function))[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)
+ if numdefaults:
+ return argnames[startindex:-numdefaults]
+ return argnames[startindex:]
+
+def fillfuncargs(function):
+ """ fill missing funcargs. """
+ request = FuncargRequest(pyfuncitem=function)
+ request._fillfuncargs()
+
+_notexists = object()
+class CallSpec:
+ def __init__(self, funcargs, id, param):
+ self.funcargs = funcargs
+ self.id = id
+ if param is not _notexists:
+ self.param = param
+ def __repr__(self):
+ return "<CallSpec id=%r param=%r funcargs=%r>" %(
+ self.id, getattr(self, 'param', '?'), self.funcargs)
+
+class Metafunc:
+ def __init__(self, function, config=None, cls=None, module=None):
+ self.config = config
+ self.module = module
+ self.function = function
+ self.funcargnames = getfuncargnames(function,
+ startindex=int(cls is not None))
+ self.cls = cls
+ self.module = module
+ self._calls = []
+ self._ids = py.builtin.set()
+
+ 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. 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.
+
+ :arg id: used for reporting and identification purposes. If you
+ don't supply an `id` the length of the currently
+ 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. 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:
+ id = len(self._calls)
+ id = str(id)
+ if id in self._ids:
+ raise ValueError("duplicate id %r" % id)
+ self._ids.add(id)
+ self._calls.append(CallSpec(funcargs, id, param))
+
+class FuncargRequest:
+ """ 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
+
+ class LookupError(LookupError):
+ """ error on performing funcarg request. """
+
+ def __init__(self, pyfuncitem):
+ self._pyfuncitem = pyfuncitem
+ if hasattr(pyfuncitem, '_requestparam'):
+ self.param = pyfuncitem._requestparam
+ extra = [obj for obj in (self.module, self.instance) if obj]
+ self._plugins = pyfuncitem.getplugins() + extra
+ self._funcargs = self._pyfuncitem.funcargs.copy()
+ self._name2factory = {}
+ self._currentarg = None
+
+ @property
+ def function(self):
+ """ function object of the test invocation. """
+ return self._pyfuncitem.obj
+
+ @property
+ def keywords(self):
+ """ keywords of the test function item.
+
+ .. versionadded:: 2.0
+ """
+ return self._pyfuncitem.keywords
+
+ @property
+ def module(self):
+ """ module where the test function was collected. """
+ return self._pyfuncitem.getparent(pytest.Module).obj
+
+ @property
+ def cls(self):
+ """ class (can be None) where the test function was collected. """
+ clscol = self._pyfuncitem.getparent(pytest.Class)
+ if clscol:
+ return clscol.obj
+ @property
+ def instance(self):
+ """ instance (can be None) on which test function was collected. """
+ return py.builtin._getimself(self.function)
+
+ @property
+ def config(self):
+ """ the pytest config object associated with this request. """
+ return self._pyfuncitem.config
+
+ @property
+ def fspath(self):
+ """ the file system path of the test module which collected this test. """
+ return self._pyfuncitem.fspath
+
+ def _fillfuncargs(self):
+ argnames = getfuncargnames(self.function)
+ if argnames:
+ assert not getattr(self._pyfuncitem, '_args', None), (
+ "yielded functions cannot have funcargs")
+ for argname in argnames:
+ if argname not in self._pyfuncitem.funcargs:
+ self._pyfuncitem.funcargs[argname] = self.getfuncargvalue(argname)
+
+
+ def applymarker(self, marker):
+ """ apply a marker to a single test function invocation.
+ This method is useful if you don't want to have a keyword/marker
+ on all function invocations.
+
+ :arg marker: a :py:class:`_pytest.mark.MarkDecorator` object
+ created by a call to ``py.test.mark.NAME(...)``.
+ """
+ if not isinstance(marker, py.test.mark.XYZ.__class__):
+ raise ValueError("%r is not a py.test.mark.* object")
+ self._pyfuncitem.keywords[marker.markname] = marker
+
+ def cached_setup(self, setup, teardown=None, scope="module", extrakey=None):
+ """ return a testing resource managed by ``setup`` &
+ ``teardown`` calls. ``scope`` and ``extrakey`` determine when the
+ ``teardown`` function will be called so that subsequent calls to
+ ``setup`` would recreate the resource.
+
+ :arg teardown: function receiving a previously setup resource.
+ :arg setup: a no-argument function creating a resource.
+ :arg scope: a string value out of ``function``, ``class``, ``module``
+ or ``session`` indicating the caching lifecycle of the resource.
+ :arg extrakey: added to internal caching key of (funcargname, scope).
+ """
+ if not hasattr(self.config, '_setupcache'):
+ self.config._setupcache = {} # XXX weakref?
+ cachekey = (self._currentarg, self._getscopeitem(scope), extrakey)
+ cache = self.config._setupcache
+ try:
+ val = cache[cachekey]
+ except KeyError:
+ val = setup()
+ cache[cachekey] = val
+ if teardown is not None:
+ def finalizer():
+ del cache[cachekey]
+ teardown(val)
+ self._addfinalizer(finalizer, scope=scope)
+ return val
+
+ def getfuncargvalue(self, argname):
+ """ Retrieve a function argument by name for this test
+ function invocation. This allows one function argument factory
+ to call another function argument factory. If there are two
+ funcarg factories for the same test function argument the first
+ factory may use ``getfuncargvalue`` to call the second one and
+ do something additional with the resource.
+ """
+ try:
+ return self._funcargs[argname]
+ except KeyError:
+ pass
+ if argname not in self._name2factory:
+ self._name2factory[argname] = self.config.pluginmanager.listattr(
+ plugins=self._plugins,
+ attrname=self._argprefix + str(argname)
+ )
+ #else: we are called recursively
+ if not self._name2factory[argname]:
+ self._raiselookupfailed(argname)
+ funcargfactory = self._name2factory[argname].pop()
+ oldarg = self._currentarg
+ self._currentarg = argname
+ try:
+ self._funcargs[argname] = res = funcargfactory(request=self)
+ finally:
+ self._currentarg = oldarg
+ return res
+
+ def _getscopeitem(self, scope):
+ if scope == "function":
+ return self._pyfuncitem
+ elif scope == "session":
+ return None
+ elif scope == "class":
+ x = self._pyfuncitem.getparent(pytest.Class)
+ if x is not None:
+ return x
+ scope = "module"
+ if scope == "module":
+ return self._pyfuncitem.getparent(pytest.Module)
+ raise ValueError("unknown finalization scope %r" %(scope,))
+
+ def addfinalizer(self, finalizer):
+ """add finalizer function to be called after test function
+ finished execution. """
+ self._addfinalizer(finalizer, scope="function")
+
+ def _addfinalizer(self, finalizer, scope):
+ colitem = self._getscopeitem(scope)
+ self.config._setupstate.addfinalizer(
+ finalizer=finalizer, colitem=colitem)
+
+ def __repr__(self):
+ return "<FuncargRequest for %r>" %(self._pyfuncitem)
+
+ def _raiselookupfailed(self, argname):
+ available = []
+ for plugin in self._plugins:
+ for name in vars(plugin):
+ if name.startswith(self._argprefix):
+ name = name[len(self._argprefix):]
+ if name not in available:
+ available.append(name)
+ fspath, lineno, msg = self._pyfuncitem.reportinfo()
+ msg = "LookupError: no factory found for function argument %r" % (argname,)
+ msg += "\n available funcargs: %s" %(", ".join(available),)
+ msg += "\n use 'py.test --funcargs [testpath]' for help on them."
+ raise self.LookupError(msg)
+
+def showfuncargs(config):
+ from _pytest.main import Session
+ session = Session(config)
+ session.perform_collect()
+ if session.items:
+ plugins = session.items[0].getplugins()
+ else:
+ plugins = session.getplugins()
+ curdir = py.path.local()
+ tw = py.io.TerminalWriter()
+ verbose = config.getvalue("verbose")
+ for plugin in plugins:
+ available = []
+ for name, factory in vars(plugin).items():
+ if name.startswith(FuncargRequest._argprefix):
+ name = name[len(FuncargRequest._argprefix):]
+ if name not in available:
+ available.append([name, factory])
+ if available:
+ pluginname = plugin.__name__
+ for name, factory in available:
+ loc = getlocation(factory, curdir)
+ if verbose:
+ funcargspec = "%s -- %s" %(name, loc,)
+ else:
+ funcargspec = name
+ tw.line(funcargspec, green=True)
+ doc = factory.__doc__ or ""
+ if doc:
+ for line in doc.split("\n"):
+ tw.line(" " + line.strip())
+ else:
+ tw.line(" %s: no docstring available" %(loc,),
+ red=True)
+
+def getlocation(function, curdir):
+ import inspect
+ fn = py.path.local(inspect.getfile(function))
+ lineno = py.builtin._getcode(function).co_firstlineno
+ if fn.relto(curdir):
+ fn = fn.relto(curdir)
+ return "%s:%d" %(fn, lineno+1)
+
+# builtin pytest.raises helper
+
+def raises(ExpectedException, *args, **kwargs):
+ """ assert that a code block/function call raises @ExpectedException
+ and raise a failure exception otherwise.
+
+ If using Python 2.5 or above, you may use this function as a
+ context manager::
+
+ >>> with raises(ZeroDivisionError):
+ ... 1/0
+
+ Or you can specify a callable by passing a to-be-called lambda::
+
+ >>> raises(ZeroDivisionError, lambda: 1/0)
+ <ExceptionInfo ...>
+
+ or you can specify an arbitrary callable with arguments::
+
+ >>> def f(x): return 1/x
+ ...
+ >>> raises(ZeroDivisionError, f, 0)
+ <ExceptionInfo ...>
+ >>> raises(ZeroDivisionError, f, x=0)
+ <ExceptionInfo ...>
+
+ A third possibility is to use a string which which will
+ be executed::
+
+ >>> raises(ZeroDivisionError, "f(0)")
+ <ExceptionInfo ...>
+ """
+ __tracebackhide__ = True
+
+ if not args:
+ return RaisesContext(ExpectedException)
+ elif isinstance(args[0], str):
+ code, = args
+ assert isinstance(code, str)
+ frame = sys._getframe(1)
+ loc = frame.f_locals.copy()
+ loc.update(kwargs)
+ #print "raises frame scope: %r" % frame.f_locals
+ try:
+ code = py.code.Source(code).compile()
+ py.builtin.exec_(code, frame.f_globals, loc)
+ # XXX didn'T mean f_globals == f_locals something special?
+ # this is destroyed here ...
+ except ExpectedException:
+ return py.code.ExceptionInfo()
+ else:
+ func = args[0]
+ try:
+ func(*args[1:], **kwargs)
+ except ExpectedException:
+ return py.code.ExceptionInfo()
+ k = ", ".join(["%s=%r" % x for x in kwargs.items()])
+ if k:
+ k = ', ' + k
+ expr = '%s(%r%s)' %(getattr(func, '__name__', func), args, k)
+ pytest.fail("DID NOT RAISE")
+
+class RaisesContext(object):
+ def __init__(self, ExpectedException):
+ self.ExpectedException = ExpectedException
+ self.excinfo = None
+
+ def __enter__(self):
+ self.excinfo = object.__new__(py.code.ExceptionInfo)
+ return self.excinfo
+
+ def __exit__(self, *tp):
+ __tracebackhide__ = True
+ if tp[0] is None:
+ pytest.fail("DID NOT RAISE")
+ self.excinfo.__init__(tp)
+ return issubclass(self.excinfo.type, self.ExpectedException)
+
diff --git a/_pytest/recwarn.py b/_pytest/recwarn.py
new file mode 100644
--- /dev/null
+++ b/_pytest/recwarn.py
@@ -0,0 +1,99 @@
+""" recording warnings during test function execution. """
+
+import py
+import sys, os
+
+def pytest_funcarg__recwarn(request):
+ """Return a WarningsRecorder instance that provides these methods:
+
+ * ``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
+ oldfilters = warnings.filters[:]
+ warnings.simplefilter('default')
+ def reset_filters():
+ warnings.filters[:] = oldfilters
+ request.addfinalizer(reset_filters)
+ wrec = WarningsRecorder()
+ request.addfinalizer(wrec.finalize)
+ return wrec
+
+def pytest_namespace():
+ return {'deprecated_call': deprecated_call}
+
+def deprecated_call(func, *args, **kwargs):
+ """ assert that calling ``func(*args, **kwargs)``
+ triggers a DeprecationWarning.
+ """
+ warningmodule = py.std.warnings
+ l = []
+ oldwarn_explicit = getattr(warningmodule, 'warn_explicit')
+ def warn_explicit(*args, **kwargs):
+ l.append(args)
+ oldwarn_explicit(*args, **kwargs)
+ oldwarn = getattr(warningmodule, 'warn')
+ def warn(*args, **kwargs):
+ l.append(args)
+ oldwarn(*args, **kwargs)
+
+ warningmodule.warn_explicit = warn_explicit
+ warningmodule.warn = warn
+ try:
+ ret = func(*args, **kwargs)
+ finally:
+ warningmodule.warn_explicit = warn_explicit
+ warningmodule.warn = warn
+ if not l:
+ #print warningmodule
+ __tracebackhide__ = True
+ raise AssertionError("%r did not produce DeprecationWarning" %(func,))
+ return ret
+
+
+class RecordedWarning:
+ def __init__(self, message, category, filename, lineno, line):
+ self.message = message
+ self.category = category
+ self.filename = filename
+ self.lineno = lineno
+ self.line = line
+
+class WarningsRecorder:
+ def __init__(self):
+ warningmodule = py.std.warnings
+ self.list = []
+ def showwarning(message, category, filename, lineno, line=0):
+ self.list.append(RecordedWarning(
+ message, category, filename, lineno, line))
+ try:
+ self.old_showwarning(message, category,
+ filename, lineno, line=line)
+ except TypeError:
+ # < python2.6
+ self.old_showwarning(message, category, filename, lineno)
+ self.old_showwarning = warningmodule.showwarning
+ warningmodule.showwarning = showwarning
+
+ def pop(self, cls=Warning):
+ """ pop the first recorded warning, raise exception if not exists."""
+ for i, w in enumerate(self.list):
+ if issubclass(w.category, cls):
+ return self.list.pop(i)
+ __tracebackhide__ = True
+ assert 0, "%r not found in %r" %(cls, self.list)
+
+ #def resetregistry(self):
+ # import warnings
+ # warnings.onceregistry.clear()
+ # warnings.__warningregistry__.clear()
+
+ def clear(self):
+ self.list[:] = []
+
+ def finalize(self):
+ py.std.warnings.showwarning = self.old_showwarning
diff --git a/_pytest/resultlog.py b/_pytest/resultlog.py
new file mode 100644
--- /dev/null
+++ b/_pytest/resultlog.py
@@ -0,0 +1,93 @@
+""" (disabled by default) create result information in a plain text file. """
+
+import py
+
+def pytest_addoption(parser):
+ group = parser.getgroup("terminal reporting", "resultlog plugin options")
+ group.addoption('--resultlog', action="store", dest="resultlog",
+ metavar="path", default=None,
+ help="path for machine-readable result log.")
+
+def pytest_configure(config):
+ resultlog = config.option.resultlog
+ # prevent opening resultlog on slave nodes (xdist)
+ if resultlog and not hasattr(config, 'slaveinput'):
+ logfile = open(resultlog, 'w', 1) # line buffered
+ config._resultlog = ResultLog(config, logfile)
+ config.pluginmanager.register(config._resultlog)
+
+def pytest_unconfigure(config):
+ resultlog = getattr(config, '_resultlog', None)
+ if resultlog:
+ resultlog.logfile.close()
+ del config._resultlog
+ config.pluginmanager.unregister(resultlog)
+
+def generic_path(item):
+ chain = item.listchain()
+ gpath = [chain[0].name]
+ fspath = chain[0].fspath
+ fspart = False
+ for node in chain[1:]:
+ newfspath = node.fspath
+ if newfspath == fspath:
+ if fspart:
+ gpath.append(':')
+ fspart = False
+ else:
+ gpath.append('.')
+ else:
+ gpath.append('/')
+ fspart = True
+ name = node.name
+ if name[0] in '([':
+ gpath.pop()
+ gpath.append(name)
+ fspath = newfspath
+ return ''.join(gpath)
+
+class ResultLog(object):
+ def __init__(self, config, logfile):
+ self.config = config
+ self.logfile = logfile # preferably line buffered
+
+ def write_log_entry(self, testpath, lettercode, longrepr):
+ py.builtin.print_("%s %s" % (lettercode, testpath), file=self.logfile)
+ for line in longrepr.splitlines():
+ py.builtin.print_(" %s" % line, file=self.logfile)
+
+ def log_outcome(self, report, lettercode, longrepr):
+ testpath = getattr(report, 'nodeid', None)
+ if testpath is None:
+ testpath = report.fspath
+ self.write_log_entry(testpath, lettercode, longrepr)
+
+ def pytest_runtest_logreport(self, report):
+ res = self.config.hook.pytest_report_teststatus(report=report)
+ code = res[1]
+ if code == 'x':
+ longrepr = str(report.longrepr)
+ elif code == 'X':
+ longrepr = ''
+ elif report.passed:
+ longrepr = ""
+ elif report.failed:
+ longrepr = str(report.longrepr)
+ elif report.skipped:
+ longrepr = str(report.longrepr[2])
+ self.log_outcome(report, code, longrepr)
+
+ def pytest_collectreport(self, report):
+ if not report.passed:
+ if report.failed:
+ code = "F"
+ longrepr = str(report.longrepr.reprcrash)
+ else:
+ assert report.skipped
+ code = "S"
+ longrepr = "%s:%d: %s" % report.longrepr
+ self.log_outcome(report, code, longrepr)
+
+ def pytest_internalerror(self, excrepr):
+ path = excrepr.reprcrash.path
+ self.write_log_entry(path, '!', str(excrepr))
diff --git a/_pytest/runner.py b/_pytest/runner.py
new file mode 100644
--- /dev/null
+++ b/_pytest/runner.py
@@ -0,0 +1,390 @@
+""" basic collect and runtest protocol implementations """
+
+import py, sys
+from py._code.code import TerminalRepr
+
+def pytest_namespace():
+ return {
+ 'fail' : fail,
+ 'skip' : skip,
+ 'importorskip' : importorskip,
+ 'exit' : exit,
+ }
+
+#
+# pytest plugin hooks
+
+# XXX move to pytest_sessionstart and fix py.test owns tests
+def pytest_configure(config):
+ config._setupstate = SetupState()
+
+def pytest_sessionfinish(session, exitstatus):
+ if hasattr(session.config, '_setupstate'):
+ hook = session.config.hook
+ rep = hook.pytest__teardown_final(session=session)
+ if rep:
+ hook.pytest__teardown_final_logerror(session=session, report=rep)
+ session.exitstatus = 1
+
+class NodeInfo:
+ def __init__(self, location):
+ self.location = location
+
+def pytest_runtest_protocol(item):
+ item.ihook.pytest_runtest_logstart(
+ nodeid=item.nodeid, location=item.location,
+ )
+ runtestprotocol(item)
+ return True
+
+def runtestprotocol(item, log=True):
+ rep = call_and_report(item, "setup", log)
+ reports = [rep]
+ if rep.passed:
+ reports.append(call_and_report(item, "call", log))
+ reports.append(call_and_report(item, "teardown", log))
+ return reports
+
+def pytest_runtest_setup(item):
+ item.config._setupstate.prepare(item)
+
+def pytest_runtest_call(item):
+ item.runtest()
+
+def pytest_runtest_teardown(item):
+ item.config._setupstate.teardown_exact(item)
+
+def pytest__teardown_final(session):
+ call = CallInfo(session.config._setupstate.teardown_all, when="teardown")
+ if call.excinfo:
+ ntraceback = call.excinfo.traceback .cut(excludepath=py._pydir)
+ call.excinfo.traceback = ntraceback.filter()
+ longrepr = call.excinfo.getrepr(funcargs=True)
+ return TeardownErrorReport(longrepr)
+
+def pytest_report_teststatus(report):
+ if report.when in ("setup", "teardown"):
+ if report.failed:
+ # category, shortletter, verbose-word
+ return "error", "E", "ERROR"
+ elif report.skipped:
+ return "skipped", "s", "SKIPPED"
+ else:
+ return "", "", ""
+
+
+#
+# Implementation
+
+def call_and_report(item, when, log=True):
+ call = call_runtest_hook(item, when)
+ hook = item.ihook
+ report = hook.pytest_runtest_makereport(item=item, call=call)
+ if log and (when == "call" or not report.passed):
+ hook.pytest_runtest_logreport(report=report)
+ return report
+
+def call_runtest_hook(item, when):
+ hookname = "pytest_runtest_" + when
+ ihook = getattr(item.ihook, hookname)
+ return CallInfo(lambda: ihook(item=item), when=when)
+
+class CallInfo:
+ """ Result/Exception info a function invocation. """
+ #: None or ExceptionInfo object.
+ excinfo = None
+ def __init__(self, func, when):
+ #: context of invocation: one of "setup", "call",
+ #: "teardown", "memocollect"
+ self.when = when
+ try:
+ self.result = func()
+ except KeyboardInterrupt:
+ raise
+ except:
+ self.excinfo = py.code.ExceptionInfo()
+
+ def __repr__(self):
+ if self.excinfo:
+ status = "exception: %s" % str(self.excinfo.value)
+ else:
+ status = "result: %r" % (self.result,)
+ return "<CallInfo when=%r %s>" % (self.when, status)
+
+def getslaveinfoline(node):
+ try:
+ return node._slaveinfocache
+ except AttributeError:
+ d = node.slaveinfo
+ ver = "%s.%s.%s" % d['version_info'][:3]
+ node._slaveinfocache = s = "[%s] %s -- Python %s %s" % (
+ d['id'], d['sysplatform'], ver, d['executable'])
+ return s
+
+class BaseReport(object):
+ def toterminal(self, out):
+ longrepr = self.longrepr
+ if hasattr(self, 'node'):
+ out.line(getslaveinfoline(self.node))
+ if hasattr(longrepr, 'toterminal'):
+ longrepr.toterminal(out)
+ else:
+ out.line(str(longrepr))
+
+ passed = property(lambda x: x.outcome == "passed")
+ failed = property(lambda x: x.outcome == "failed")
+ skipped = property(lambda x: x.outcome == "skipped")
+
+ @property
+ def fspath(self):
+ return self.nodeid.split("::")[0]
+
+def pytest_runtest_makereport(item, call):
+ when = call.when
+ keywords = dict([(x,1) for x in item.keywords])
+ excinfo = call.excinfo
+ if not call.excinfo:
+ outcome = "passed"
+ longrepr = None
+ else:
+ excinfo = call.excinfo
+ if not isinstance(excinfo, py.code.ExceptionInfo):
+ outcome = "failed"
+ longrepr = excinfo
+ elif excinfo.errisinstance(py.test.skip.Exception):
+ outcome = "skipped"
+ r = excinfo._getreprcrash()
+ longrepr = (str(r.path), r.lineno, r.message)
+ else:
+ outcome = "failed"
+ if call.when == "call":
+ longrepr = item.repr_failure(excinfo)
+ else: # exception in setup or teardown
+ longrepr = item._repr_failure_py(excinfo)
+ return TestReport(item.nodeid, item.location,
+ keywords, outcome, longrepr, when)
+
+class TestReport(BaseReport):
+ """ Basic test report object (also used for setup and teardown calls if
+ they fail).
+ """
+ def __init__(self, nodeid, location,
+ keywords, outcome, longrepr, when):
+ #: normalized collection node id
+ self.nodeid = nodeid
+
+ #: a (filesystempath, lineno, domaininfo) tuple indicating the
+ #: actual location of a test item - it might be different from the
+ #: collected one e.g. if a method is inherited from a different module.
+ self.location = location
+
+ #: a name -> value dictionary containing all keywords and
+ #: markers associated with a test invocation.
+ self.keywords = keywords
+
+ #: test outcome, always one of "passed", "failed", "skipped".
+ self.outcome = outcome
+
+ #: None or a failure representation.
+ self.longrepr = longrepr
+
+ #: one of 'setup', 'call', 'teardown' to indicate runtest phase.
+ self.when = when
+
+ def __repr__(self):
+ return "<TestReport %r when=%r outcome=%r>" % (
+ self.nodeid, self.when, self.outcome)
+
+class TeardownErrorReport(BaseReport):
+ outcome = "failed"
+ when = "teardown"
+ def __init__(self, longrepr):
+ self.longrepr = longrepr
+
+def pytest_make_collect_report(collector):
+ call = CallInfo(collector._memocollect, "memocollect")
+ longrepr = None
+ if not call.excinfo:
+ outcome = "passed"
+ else:
+ if call.excinfo.errisinstance(py.test.skip.Exception):
+ outcome = "skipped"
+ r = collector._repr_failure_py(call.excinfo, "line").reprcrash
+ longrepr = (str(r.path), r.lineno, r.message)
+ else:
+ outcome = "failed"
+ errorinfo = collector.repr_failure(call.excinfo)
+ if not hasattr(errorinfo, "toterminal"):
+ errorinfo = CollectErrorRepr(errorinfo)
+ longrepr = errorinfo
+ return CollectReport(collector.nodeid, outcome, longrepr,
+ getattr(call, 'result', None))
+
+class CollectReport(BaseReport):
+ def __init__(self, nodeid, outcome, longrepr, result):
+ self.nodeid = nodeid
+ self.outcome = outcome
+ self.longrepr = longrepr
+ self.result = result or []
+
+ @property
+ def location(self):
+ return (self.fspath, None, self.fspath)
+
+ def __repr__(self):
+ return "<CollectReport %r lenresult=%s outcome=%r>" % (
+ self.nodeid, len(self.result), self.outcome)
+
+class CollectErrorRepr(TerminalRepr):
+ def __init__(self, msg):
+ self.longrepr = msg
+ def toterminal(self, out):
+ out.line(str(self.longrepr), red=True)
+
+class SetupState(object):
+ """ shared state for setting up/tearing down test items or collectors. """
+ def __init__(self):
+ self.stack = []
+ self._finalizers = {}
+
+ def addfinalizer(self, finalizer, colitem):
+ """ attach a finalizer to the given colitem.
+ if colitem is None, this will add a finalizer that
+ is called at the end of teardown_all().
+ """
+ assert hasattr(finalizer, '__call__')
+ #assert colitem in self.stack
+ self._finalizers.setdefault(colitem, []).append(finalizer)
+
+ def _pop_and_teardown(self):
+ colitem = self.stack.pop()
+ self._teardown_with_finalization(colitem)
+
+ def _callfinalizers(self, colitem):
+ finalizers = self._finalizers.pop(colitem, None)
+ while finalizers:
+ fin = finalizers.pop()
+ fin()
+
+ def _teardown_with_finalization(self, colitem):
+ self._callfinalizers(colitem)
+ if colitem:
+ colitem.teardown()
+ for colitem in self._finalizers:
+ assert colitem is None or colitem in self.stack
+
+ def teardown_all(self):
+ while self.stack:
+ self._pop_and_teardown()
+ self._teardown_with_finalization(None)
+ assert not self._finalizers
+
+ def teardown_exact(self, item):
+ if self.stack and item == self.stack[-1]:
+ self._pop_and_teardown()
+ else:
+ self._callfinalizers(item)
+
+ def prepare(self, colitem):
+ """ setup objects along the collector chain to the test-method
+ and teardown previously setup objects."""
+ needed_collectors = colitem.listchain()
+ while self.stack:
+ if self.stack == needed_collectors[:len(self.stack)]:
+ break
+ self._pop_and_teardown()
+ # check if the last collection node has raised an error
+ for col in self.stack:
+ if hasattr(col, '_prepare_exc'):
+ py.builtin._reraise(*col._prepare_exc)
+ for col in needed_collectors[len(self.stack):]:
+ self.stack.append(col)
+ try:
+ col.setup()
+ except Exception:
+ col._prepare_exc = sys.exc_info()
+ raise
+
+# =============================================================
+# Test OutcomeExceptions and helpers for creating them.
+
+
+class OutcomeException(Exception):
+ """ OutcomeException and its subclass instances indicate and
+ contain info about test and collection outcomes.
+ """
+ def __init__(self, msg=None, pytrace=True):
+ self.msg = msg
+ self.pytrace = pytrace
+
+ def __repr__(self):
+ if self.msg:
+ return str(self.msg)
+ return "<%s instance>" %(self.__class__.__name__,)
+ __str__ = __repr__
+
+class Skipped(OutcomeException):
+ # XXX hackish: on 3k we fake to live in the builtins
+ # in order to have Skipped exception printing shorter/nicer
+ __module__ = 'builtins'
+
+class Failed(OutcomeException):
+ """ raised from an explicit call to py.test.fail() """
+ __module__ = 'builtins'
+
+class Exit(KeyboardInterrupt):
+ """ raised for immediate program exits (no tracebacks/summaries)"""
+ def __init__(self, msg="unknown reason"):
+ self.msg = msg
+ KeyboardInterrupt.__init__(self, msg)
+
+# exposed helper methods
+
+def exit(msg):
+ """ exit testing process as if KeyboardInterrupt was triggered. """
+ __tracebackhide__ = True
+ raise Exit(msg)
+
+exit.Exception = Exit
+
+def skip(msg=""):
+ """ skip an executing test with the given message. Note: it's usually
+ better to use the py.test.mark.skipif marker to declare a test to be
+ skipped under certain conditions like mismatching platforms or
+ dependencies. See the pytest_skipping plugin for details.
+ """
+ __tracebackhide__ = True
+ raise Skipped(msg=msg)
+skip.Exception = Skipped
+
+def fail(msg="", pytrace=True):
+ """ explicitely fail an currently-executing test with the given Message.
+ if @pytrace is not True the msg represents the full failure information.
+ """
+ __tracebackhide__ = True
+ raise Failed(msg=msg, pytrace=pytrace)
+fail.Exception = Failed
+
+
+def importorskip(modname, minversion=None):
+ """ return imported module if it has a higher __version__ than the
+ optionally specified 'minversion' - otherwise call py.test.skip()
+ with a message detailing the mismatch.
+ """
+ __tracebackhide__ = True
+ compile(modname, '', 'eval') # to catch syntaxerrors
+ try:
+ mod = __import__(modname, None, None, ['__doc__'])
+ except ImportError:
+ py.test.skip("could not import %r" %(modname,))
+ if minversion is None:
+ return mod
+ verattr = getattr(mod, '__version__', None)
+ if isinstance(minversion, str):
+ minver = minversion.split(".")
+ else:
+ minver = list(minversion)
+ if verattr is None or verattr.split(".") < minver:
+ py.test.skip("module %r has __version__ %r, required is: %r" %(
+ modname, verattr, minversion))
+ return mod
diff --git a/_pytest/skipping.py b/_pytest/skipping.py
new file mode 100644
--- /dev/null
+++ b/_pytest/skipping.py
@@ -0,0 +1,246 @@
+""" support for skip/xfail functions and markers. """
+
+import py, pytest
+import sys
+
+def pytest_addoption(parser):
+ group = parser.getgroup("general")
+ group.addoption('--runxfail',
+ action="store_true", dest="runxfail", default=False,
+ help="run tests even if they are marked xfail")
+
+def pytest_namespace():
+ return dict(xfail=xfail)
+
+class XFailed(pytest.fail.Exception):
+ """ raised from an explicit call to py.test.xfail() """
+
+def xfail(reason=""):
+ """ xfail an executing test or setup functions with the given reason."""
+ __tracebackhide__ = True
+ raise XFailed(reason)
+xfail.Exception = XFailed
+
+class MarkEvaluator:
+ def __init__(self, item, name):
+ self.item = item
+ self.name = name
+
+ @property
+ def holder(self):
+ return self.item.keywords.get(self.name, None)
+ def __bool__(self):
+ 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 = self._getglobals()
+ if self.holder.args:
+ self.result = False
+ for expr in self.holder.args:
+ self.expr = expr
+ if isinstance(expr, str):
+ result = cached_eval(self.item.config, expr, d)
+ else:
+ pytest.fail("expression is not a string")
+ if result:
+ self.result = True
+ self.expr = expr
+ break
+ else:
+ self.result = True
+ return getattr(self, 'result', False)
+
+ def get(self, attr, default=None):
+ return self.holder.kwargs.get(attr, default)
+
+ def getexplanation(self):
+ expl = self.get('reason', None)
+ if not expl:
+ if not hasattr(self, 'expr'):
+ return ""
+ else:
+ return "condition: " + str(self.expr)
+ return expl
+
+
+def pytest_runtest_setup(item):
+ if not isinstance(item, pytest.Function):
+ return
+ evalskip = MarkEvaluator(item, 'skipif')
+ if evalskip.istrue():
+ py.test.skip(evalskip.getexplanation())
+ item._evalxfail = MarkEvaluator(item, 'xfail')
+ check_xfail_no_run(item)
+
+def pytest_pyfunc_call(pyfuncitem):
+ check_xfail_no_run(pyfuncitem)
+
+def check_xfail_no_run(item):
+ if not item.config.option.runxfail:
+ evalxfail = item._evalxfail
+ if evalxfail.istrue():
+ if not evalxfail.get('run', True):
+ py.test.xfail("[NOTRUN] " + evalxfail.getexplanation())
+
+def pytest_runtest_makereport(__multicall__, item, call):
+ if not isinstance(item, pytest.Function):
+ return
+ if not (call.excinfo and
+ call.excinfo.errisinstance(py.test.xfail.Exception)):
+ evalxfail = getattr(item, '_evalxfail', None)
+ if not evalxfail:
+ return
+ if call.excinfo and call.excinfo.errisinstance(py.test.xfail.Exception):
+ if not item.config.getvalue("runxfail"):
+ rep = __multicall__.execute()
+ rep.keywords['xfail'] = "reason: " + call.excinfo.value.msg
+ rep.outcome = "skipped"
+ return rep
+ rep = __multicall__.execute()
+ evalxfail = item._evalxfail
+ 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
+def pytest_report_teststatus(report):
+ if 'xfail' in report.keywords:
+ if report.skipped:
+ return "xfailed", "x", "xfail"
+ elif report.failed:
+ return "xpassed", "X", "XPASS"
+
+# called by the terminalreporter instance/plugin
+def pytest_terminal_summary(terminalreporter):
+ tr = terminalreporter
+ if not tr.reportchars:
+ #for name in "xfailed skipped failed xpassed":
+ # if not tr.stats.get(name, 0):
+ # tr.write_line("HINT: use '-r' option to see extra "
+ # "summary info about tests")
+ # break
+ return
+
+ lines = []
+ for char in tr.reportchars:
+ if char == "x":
+ show_xfailed(terminalreporter, lines)
+ elif char == "X":
+ show_xpassed(terminalreporter, lines)
+ elif char in "fF":
+ show_failed(terminalreporter, lines)
+ elif char in "sS":
+ show_skipped(terminalreporter, lines)
+ if lines:
+ tr._tw.sep("=", "short test summary info")
+ for line in lines:
+ tr._tw.line(line)
+
+def show_failed(terminalreporter, lines):
+ tw = terminalreporter._tw
+ failed = terminalreporter.stats.get("failed")
+ if failed:
+ for rep in failed:
+ pos = rep.nodeid
+ lines.append("FAIL %s" %(pos, ))
+
+def show_xfailed(terminalreporter, lines):
+ xfailed = terminalreporter.stats.get("xfailed")
+ if xfailed:
+ for rep in xfailed:
+ pos = rep.nodeid
+ reason = rep.keywords['xfail']
+ lines.append("XFAIL %s" % (pos,))
+ if reason:
+ lines.append(" " + str(reason))
+
+def show_xpassed(terminalreporter, lines):
+ xpassed = terminalreporter.stats.get("xpassed")
+ if xpassed:
+ for rep in xpassed:
+ pos = rep.nodeid
+ reason = rep.keywords['xfail']
+ lines.append("XPASS %s %s" %(pos, reason))
+
+def cached_eval(config, expr, d):
+ if not hasattr(config, '_evalcache'):
+ config._evalcache = {}
+ try:
+ return config._evalcache[expr]
+ except KeyError:
+ #import sys
+ #print >>sys.stderr, ("cache-miss: %r" % expr)
+ exprcode = py.code.compile(expr, mode="eval")
+ config._evalcache[expr] = x = eval(exprcode, d)
+ return x
+
+
+def folded_skips(skipped):
+ d = {}
+ for event in skipped:
+ key = event.longrepr
+ assert len(key) == 3, (event, key)
+ d.setdefault(key, []).append(event)
+ l = []
+ for key, events in d.items():
+ l.append((len(events),) + key)
+ return l
+
+def show_skipped(terminalreporter, lines):
+ tr = terminalreporter
+ skipped = tr.stats.get('skipped', [])
+ if skipped:
+ #if not tr.hasopt('skipped'):
+ # tr.write_line(
+ # "%d skipped tests, specify -rs for more info" %
+ # len(skipped))
+ # return
+ fskips = folded_skips(skipped)
+ if fskips:
+ #tr.write_sep("_", "skipped test summary")
+ for num, fspath, lineno, reason in fskips:
+ if reason.startswith("Skipped: "):
+ reason = reason[9:]
+ lines.append("SKIP [%d] %s:%d: %s" %
+ (num, fspath, lineno, reason))
diff --git a/_pytest/standalonetemplate.py b/_pytest/standalonetemplate.py
new file mode 100755
--- /dev/null
+++ b/_pytest/standalonetemplate.py
@@ -0,0 +1,63 @@
+#! /usr/bin/env python
+
+sources = """
+ at SOURCES@"""
+
+import sys
+import base64
+import zlib
+import imp
+
+class DictImporter(object):
+ def __init__(self, sources):
+ self.sources = sources
+
+ def find_module(self, fullname, path=None):
+ if fullname in self.sources:
+ return self
+ if fullname + '.__init__' in self.sources:
+ return self
+ return None
+
+ def load_module(self, fullname):
+ # print "load_module:", fullname
+ from types import ModuleType
+ try:
+ s = self.sources[fullname]
+ is_pkg = False
+ except KeyError:
+ s = self.sources[fullname + '.__init__']
+ is_pkg = True
+
+ co = compile(s, fullname, 'exec')
+ module = sys.modules.setdefault(fullname, ModuleType(fullname))
+ module.__file__ = "%s/%s" % (__file__, fullname)
+ module.__loader__ = self
+ if is_pkg:
+ module.__path__ = [fullname]
+
+ do_exec(co, module.__dict__)
+ return sys.modules[fullname]
+
+ def get_source(self, name):
+ res = self.sources.get(name)
+ if res is None:
+ res = self.sources.get(name + '.__init__')
+ return res
+
+if __name__ == "__main__":
+ if sys.version_info >= (3, 0):
+ exec("def do_exec(co, loc): exec(co, loc)\n")
+ import pickle
+ sources = sources.encode("ascii") # ensure bytes
+ sources = pickle.loads(zlib.decompress(base64.decodebytes(sources)))
+ else:
+ import cPickle as pickle
+ exec("def do_exec(co, loc): exec co in loc\n")
+ sources = pickle.loads(zlib.decompress(base64.decodestring(sources)))
+
+ importer = DictImporter(sources)
+ sys.meta_path.append(importer)
+
+ entry = "@ENTRY@"
+ do_exec(entry, locals())
diff --git a/_pytest/terminal.py b/_pytest/terminal.py
new file mode 100644
--- /dev/null
+++ b/_pytest/terminal.py
@@ -0,0 +1,451 @@
+""" terminal reporting of the full testing process.
+
+This is a good source for looking at the various reporting hooks.
+"""
+import pytest, py
+import sys
+import os
+
+def pytest_addoption(parser):
+ group = parser.getgroup("terminal reporting", "reporting", after="general")
+ group._addoption('-v', '--verbose', action="count",
+ dest="verbose", default=0, help="increase verbosity."),
+ group._addoption('-q', '--quiet', action="count",
+ dest="quiet", default=0, help="decreate verbosity."),
+ group._addoption('-r',
+ action="store", dest="reportchars", default=None, metavar="chars",
+ help="show extra test summary info as specified by chars (f)ailed, "
+ "(s)skipped, (x)failed, (X)passed.")
+ group._addoption('-l', '--showlocals',
+ action="store_true", dest="showlocals", default=False,
+ help="show locals in tracebacks (disabled by default).")
+ group._addoption('--report',
+ action="store", dest="report", default=None, metavar="opts",
+ help="(deprecated, use -r)")
+ 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/native/no).")
+ group._addoption('--fulltrace',
+ action="store_true", dest="fulltrace", default=False,
+ help="don't cut any tracebacks (default is to cut).")
+
+def pytest_configure(config):
+ config.option.verbose -= config.option.quiet
+ # we try hard to make printing resilient against
+ # later changes on FD level.
+ stdout = py.std.sys.stdout
+ if hasattr(os, 'dup') and hasattr(stdout, 'fileno'):
+ try:
+ newfd = os.dup(stdout.fileno())
+ #print "got newfd", newfd
+ except ValueError:
+ pass
+ else:
+ stdout = os.fdopen(newfd, stdout.mode, 1)
+ config._toclose = stdout
+ reporter = TerminalReporter(config, stdout)
+ config.pluginmanager.register(reporter, 'terminalreporter')
+ if config.option.debug or config.option.traceconfig:
+ def mywriter(tags, args):
+ msg = " ".join(map(str, args))
+ reporter.write_line("[traceconfig] " + msg)
+ config.trace.root.setprocessor("pytest:config", mywriter)
+
+def pytest_unconfigure(config):
+ if hasattr(config, '_toclose'):
+ #print "closing", config._toclose, config._toclose.fileno()
+ config._toclose.close()
+
+def getreportopt(config):
+ reportopts = ""
+ optvalue = config.option.report
+ if optvalue:
+ py.builtin.print_("DEPRECATED: use -r instead of --report option.",
+ file=py.std.sys.stderr)
+ if optvalue:
+ for setting in optvalue.split(","):
+ setting = setting.strip()
+ if setting == "skipped":
+ reportopts += "s"
+ elif setting == "xfailed":
+ reportopts += "x"
+ reportchars = config.option.reportchars
+ if reportchars:
+ for char in reportchars:
+ if char not in reportopts:
+ reportopts += char
+ return reportopts
+
+def pytest_report_teststatus(report):
+ if report.passed:
+ letter = "."
+ elif report.skipped:
+ letter = "s"
+ elif report.failed:
+ letter = "F"
+ if report.when != "call":
+ letter = "f"
+ return report.outcome, letter, report.outcome.upper()
+
+class TerminalReporter:
+ def __init__(self, config, file=None):
+ self.config = config
+ self.verbosity = self.config.option.verbose
+ self.showheader = self.verbosity >= 0
+ self.showfspath = self.verbosity >= 0
+ self.showlongtestinfo = self.verbosity > 0
+ self._numcollected = 0
+
+ self.stats = {}
+ self.curdir = py.path.local()
+ if file is None:
+ file = py.std.sys.stdout
+ self._tw = py.io.TerminalWriter(file)
+ self.currentfspath = None
+ self.reportchars = getreportopt(config)
+ self.hasmarkup = self._tw.hasmarkup
+
+ def hasopt(self, char):
+ char = {'xfailed': 'x', 'skipped': 's'}.get(char,char)
+ return char in self.reportchars
+
+ def write_fspath_result(self, fspath, res):
+ if fspath != self.currentfspath:
+ self.currentfspath = fspath
+ #fspath = self.curdir.bestrelpath(fspath)
+ self._tw.line()
+ #relpath = self.curdir.bestrelpath(fspath)
+ self._tw.write(fspath + " ")
+ self._tw.write(res)
+
+ def write_ensure_prefix(self, prefix, extra="", **kwargs):
+ if self.currentfspath != prefix:
+ self._tw.line()
+ self.currentfspath = prefix
+ self._tw.write(prefix)
+ if extra:
+ self._tw.write(extra, **kwargs)
+ self.currentfspath = -2
+
+ def ensure_newline(self):
+ if self.currentfspath:
+ self._tw.line()
+ self.currentfspath = None
+
+ def write(self, content, **markup):
+ self._tw.write(content, **markup)
+
+ def write_line(self, line, **markup):
+ line = str(line)
+ self.ensure_newline()
+ self._tw.line(line, **markup)
+
+ def rewrite(self, line, **markup):
+ line = str(line)
+ self._tw.write("\r" + line, **markup)
+
+ def write_sep(self, sep, title=None, **markup):
+ self.ensure_newline()
+ self._tw.sep(sep, title, **markup)
+
+ def pytest_internalerror(self, excrepr):
+ for line in str(excrepr).split("\n"):
+ self.write_line("INTERNALERROR> " + line)
+ return 1
+
+ def pytest_plugin_registered(self, plugin):
+ if self.config.option.traceconfig:
+ msg = "PLUGIN registered: %s" %(plugin,)
+ # XXX this event may happen during setup/teardown time
+ # which unfortunately captures our output here
+ # which garbles our output if we use self.write_line
+ self.write_line(msg)
+
+ def pytest_deselected(self, items):
+ self.stats.setdefault('deselected', []).extend(items)
+
+ def pytest__teardown_final_logerror(self, report):
+ self.stats.setdefault("error", []).append(report)
+
+ def pytest_runtest_logstart(self, nodeid, location):
+ # ensure that the path is printed before the
+ # 1st test of a module starts running
+ fspath = nodeid.split("::")[0]
+ if self.showlongtestinfo:
+ line = self._locationline(fspath, *location)
+ self.write_ensure_prefix(line, "")
+ elif self.showfspath:
+ self.write_fspath_result(fspath, "")
+
+ def pytest_runtest_logreport(self, report):
+ rep = report
+ res = self.config.hook.pytest_report_teststatus(report=rep)
+ cat, letter, word = res
+ self.stats.setdefault(cat, []).append(rep)
+ if not letter and not word:
+ # probably passed setup/teardown
+ return
+ if self.verbosity <= 0:
+ if not hasattr(rep, 'node') and self.showfspath:
+ self.write_fspath_result(rep.fspath, letter)
+ else:
+ self._tw.write(letter)
+ else:
+ if isinstance(word, tuple):
+ word, markup = word
+ else:
+ if rep.passed:
+ markup = {'green':True}
+ elif rep.failed:
+ markup = {'red':True}
+ elif rep.skipped:
+ markup = {'yellow':True}
+ line = self._locationline(str(rep.fspath), *rep.location)
+ if not hasattr(rep, 'node'):
+ self.write_ensure_prefix(line, word, **markup)
+ #self._tw.write(word, **markup)
+ else:
+ self.ensure_newline()
+ if hasattr(rep, 'node'):
+ self._tw.write("[%s] " % rep.node.gateway.id)
+ self._tw.write(word, **markup)
+ self._tw.write(" " + line)
+ self.currentfspath = -2
+
+ def pytest_collection(self):
+ if not self.hasmarkup:
+ self.write("collecting ... ", bold=True)
+
+ def pytest_collectreport(self, report):
+ if report.failed:
+ self.stats.setdefault("error", []).append(report)
+ elif report.skipped:
+ self.stats.setdefault("skipped", []).append(report)
+ items = [x for x in report.result if isinstance(x, pytest.Item)]
+ self._numcollected += len(items)
+ if self.hasmarkup:
+ #self.write_fspath_result(report.fspath, 'E')
+ self.report_collect()
+
+ def report_collect(self, final=False):
+ errors = len(self.stats.get('error', []))
+ skipped = len(self.stats.get('skipped', []))
+ if final:
+ line = "collected "
+ else:
+ line = "collecting "
+ line += str(self._numcollected) + " items"
+ if errors:
+ line += " / %d errors" % errors
+ if skipped:
+ line += " / %d skipped" % skipped
+ if self.hasmarkup:
+ if final:
+ line += " \n"
+ self.rewrite(line, bold=True)
+ else:
+ self.write_line(line)
+
+ def pytest_collection_modifyitems(self):
+ self.report_collect(True)
+
+ def pytest_sessionstart(self, session):
+ self._sessionstarttime = py.std.time.time()
+ if not self.showheader:
+ return
+ self.write_sep("=", "test session starts", bold=True)
+ verinfo = ".".join(map(str, sys.version_info[:3]))
+ msg = "platform %s -- Python %s" % (sys.platform, verinfo)
+ if hasattr(sys, 'pypy_version_info'):
+ verinfo = ".".join(map(str, sys.pypy_version_info[:3]))
+ msg += "[pypy-%s]" % verinfo
+ msg += " -- pytest-%s" % (py.test.__version__)
+ if self.verbosity > 0 or self.config.option.debug or \
+ getattr(self.config.option, 'pastebin', None):
+ msg += " -- " + str(sys.executable)
+ self.write_line(msg)
+ lines = self.config.hook.pytest_report_header(config=self.config)
+ lines.reverse()
+ for line in flatten(lines):
+ self.write_line(line)
+
+ def pytest_collection_finish(self, session):
+ if self.config.option.collectonly:
+ self._printcollecteditems(session.items)
+ if self.stats.get('failed'):
+ self._tw.sep("!", "collection failures")
+ for rep in self.stats.get('failed'):
+ rep.toterminal(self._tw)
+ return 1
+ return 0
+ if not self.showheader:
+ return
+ #for i, testarg in enumerate(self.config.args):
+ # self.write_line("test path %d: %s" %(i+1, testarg))
+
+ def _printcollecteditems(self, items):
+ # to print out items and their parent collectors
+ # we take care to leave out Instances aka ()
+ # because later versions are going to get rid of them anyway
+ if self.config.option.verbose < 0:
+ for item in items:
+ nodeid = item.nodeid
+ nodeid = nodeid.replace("::()::", "::")
+ self._tw.line(nodeid)
+ return
+ stack = []
+ indent = ""
+ for item in items:
+ needed_collectors = item.listchain()[1:] # strip root node
+ while stack:
+ if stack == needed_collectors[:len(stack)]:
+ break
+ stack.pop()
+ for col in needed_collectors[len(stack):]:
+ stack.append(col)
+ #if col.name == "()":
+ # continue
+ indent = (len(stack)-1) * " "
+ self._tw.line("%s%s" %(indent, col))
+
+ def pytest_sessionfinish(self, exitstatus, __multicall__):
+ __multicall__.execute()
+ self._tw.line("")
+ if exitstatus in (0, 1, 2):
+ self.summary_errors()
+ self.summary_failures()
+ self.config.hook.pytest_terminal_summary(terminalreporter=self)
+ if exitstatus == 2:
+ self._report_keyboardinterrupt()
+ self.summary_deselected()
+ self.summary_stats()
+
+ def pytest_keyboard_interrupt(self, excinfo):
+ self._keyboardinterrupt_memo = excinfo.getrepr(funcargs=True)
+
+ def _report_keyboardinterrupt(self):
+ excrepr = self._keyboardinterrupt_memo
+ msg = excrepr.reprcrash.message
+ self.write_sep("!", msg)
+ if "KeyboardInterrupt" in msg:
+ if self.config.option.fulltrace:
+ excrepr.toterminal(self._tw)
+ else:
+ excrepr.reprcrash.toterminal(self._tw)
+
+ def _locationline(self, collect_fspath, fspath, lineno, domain):
+ # collect_fspath comes from testid which has a "/"-normalized path
+ if fspath and fspath.replace("\\", "/") != collect_fspath:
+ fspath = "%s <- %s" % (collect_fspath, fspath)
+ if fspath:
+ line = str(fspath)
+ if lineno is not None:
+ lineno += 1
+ line += ":" + str(lineno)
+ if domain:
+ line += ": " + str(domain)
+ else:
+ line = "[location]"
+ return line + " "
+
+ def _getfailureheadline(self, rep):
+ if hasattr(rep, 'location'):
+ fspath, lineno, domain = rep.location
+ return domain
+ else:
+ return "test session" # XXX?
+
+ def _getcrashline(self, rep):
+ try:
+ return str(rep.longrepr.reprcrash)
+ except AttributeError:
+ try:
+ return str(rep.longrepr)[:50]
+ except AttributeError:
+ return ""
+
+ #
+ # summaries for sessionfinish
+ #
+ def getreports(self, name):
+ l = []
+ for x in self.stats.get(name, []):
+ if not hasattr(x, '_pdbshown'):
+ l.append(x)
+ return l
+
+ def summary_failures(self):
+ if self.config.option.tbstyle != "no":
+ reports = self.getreports('failed')
+ if not reports:
+ return
+ self.write_sep("=", "FAILURES")
+ for rep in reports:
+ if self.config.option.tbstyle == "line":
+ line = self._getcrashline(rep)
+ self.write_line(line)
+ else:
+ msg = self._getfailureheadline(rep)
+ self.write_sep("_", msg)
+ rep.toterminal(self._tw)
+
+ def summary_errors(self):
+ if self.config.option.tbstyle != "no":
+ reports = self.getreports('error')
+ if not reports:
+ return
+ self.write_sep("=", "ERRORS")
+ for rep in self.stats['error']:
+ msg = self._getfailureheadline(rep)
+ if not hasattr(rep, 'when'):
+ # collect
+ msg = "ERROR collecting " + msg
+ elif rep.when == "setup":
+ msg = "ERROR at setup of " + msg
+ elif rep.when == "teardown":
+ msg = "ERROR at teardown of " + msg
+ self.write_sep("_", msg)
+ rep.toterminal(self._tw)
+
+ def summary_stats(self):
+ session_duration = py.std.time.time() - self._sessionstarttime
+
+ keys = "failed passed skipped deselected".split()
+ for key in self.stats.keys():
+ if key not in keys:
+ keys.append(key)
+ parts = []
+ for key in keys:
+ val = self.stats.get(key, None)
+ if val:
+ parts.append("%d %s" %(len(val), key))
+ line = ", ".join(parts)
+ # XXX coloring
+ msg = "%s in %.2f seconds" %(line, session_duration)
+ if self.verbosity >= 0:
+ self.write_sep("=", msg, bold=True)
+ else:
+ self.write_line(msg, bold=True)
+
+ def summary_deselected(self):
+ if 'deselected' in self.stats:
+ self.write_sep("=", "%d tests deselected by %r" %(
+ len(self.stats['deselected']), self.config.option.keyword), bold=True)
+
+def repr_pythonversion(v=None):
+ if v is None:
+ v = sys.version_info
+ try:
+ return "%s.%s.%s-%s-%s" % v
+ except (TypeError, ValueError):
+ return str(v)
+
+def flatten(l):
+ for x in l:
+ if isinstance(x, (list, tuple)):
+ for y in flatten(x):
+ yield y
+ else:
+ yield x
+
diff --git a/_pytest/tmpdir.py b/_pytest/tmpdir.py
new file mode 100644
--- /dev/null
+++ b/_pytest/tmpdir.py
@@ -0,0 +1,71 @@
+""" support for providing temporary directories to test functions. """
+import pytest, py
+from _pytest.monkeypatch import monkeypatch
+
+class TempdirHandler:
+ def __init__(self, config):
+ self.config = config
+ self.trace = config.trace.get("tmpdir")
+
+ def ensuretemp(self, string, dir=1):
+ """ (deprecated) return temporary directory path with
+ the given string as the trailing part. It is usually
+ better to use the 'tmpdir' function argument which
+ provides an empty unique-per-test-invocation directory
+ and is guaranteed to be empty.
+ """
+ #py.log._apiwarn(">1.1", "use tmpdir function argument")
+ return self.getbasetemp().ensure(string, dir=dir)
+
+ def mktemp(self, basename, numbered=True):
+ basetemp = self.getbasetemp()
+ if not numbered:
+ p = basetemp.mkdir(basename)
+ else:
+ p = py.path.local.make_numbered_dir(prefix=basename,
+ keep=0, rootdir=basetemp, lock_timeout=None)
+ self.trace("mktemp", p)
+ return p
+
+ def getbasetemp(self):
+ """ return base temporary directory. """
+ try:
+ return self._basetemp
+ except AttributeError:
+ basetemp = self.config.option.basetemp
+ if basetemp:
+ basetemp = py.path.local(basetemp)
+ if basetemp.check():
+ basetemp.remove()
+ basetemp.mkdir()
+ else:
+ basetemp = py.path.local.make_numbered_dir(prefix='pytest-')
+ self._basetemp = t = basetemp
+ self.trace("new basetemp", t)
+ return t
+
+ def finish(self):
+ self.trace("finish")
+
+def pytest_configure(config):
+ config._mp = mp = monkeypatch()
+ t = TempdirHandler(config)
+ mp.setattr(config, '_tmpdirhandler', t, raising=False)
+ mp.setattr(pytest, 'ensuretemp', t.ensuretemp, raising=False)
+
+def pytest_unconfigure(config):
+ config._tmpdirhandler.finish()
+ config._mp.undo()
+
+def pytest_funcarg__tmpdir(request):
+ """return a temporary directory path object
+ 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.
+ """
+ name = request._pyfuncitem.name
+ name = py.std.re.sub("[\W]", "_", name)
+ x = request.config._tmpdirhandler.mktemp(name, numbered=True)
+ return x.realpath()
+
diff --git a/_pytest/unittest.py b/_pytest/unittest.py
new file mode 100644
--- /dev/null
+++ b/_pytest/unittest.py
@@ -0,0 +1,143 @@
+""" discovery and running of std-library "unittest" style tests. """
+import pytest, py
+import sys, pdb
+
+def pytest_pycollect_makeitem(collector, name, obj):
+ unittest = sys.modules.get('unittest')
+ if unittest is None:
+ return # nobody can have derived unittest.TestCase
+ try:
+ isunit = issubclass(obj, unittest.TestCase)
+ except KeyboardInterrupt:
+ raise
+ except Exception:
+ pass
+ else:
+ if isunit:
+ return UnitTestCase(name, parent=collector)
+
+class UnitTestCase(pytest.Class):
+ def collect(self):
+ loader = py.std.unittest.TestLoader()
+ for name in loader.getTestCaseNames(self.obj):
+ yield TestCaseFunction(name, parent=self)
+
+ def setup(self):
+ meth = getattr(self.obj, 'setUpClass', None)
+ if meth is not None:
+ meth()
+ super(UnitTestCase, self).setup()
+
+ def teardown(self):
+ meth = getattr(self.obj, 'tearDownClass', None)
+ if meth is not None:
+ meth()
+ super(UnitTestCase, self).teardown()
+
+class TestCaseFunction(pytest.Function):
+ _excinfo = None
+
+ def __init__(self, name, parent):
+ super(TestCaseFunction, self).__init__(name, parent)
+ if hasattr(self._obj, 'todo'):
+ getattr(self._obj, 'im_func', self._obj).xfail = \
+ pytest.mark.xfail(reason=str(self._obj.todo))
+
+ def setup(self):
+ self._testcase = self.parent.obj(self.name)
+ self._obj = getattr(self._testcase, self.name)
+ if hasattr(self._testcase, 'setup_method'):
+ self._testcase.setup_method(self._obj)
+
+ def teardown(self):
+ if hasattr(self._testcase, 'teardown_method'):
+ self._testcase.teardown_method(self._obj)
+
+ def startTest(self, testcase):
+ pass
+
+ def _addexcinfo(self, rawexcinfo):
+ # unwrap potential exception info (see twisted trial support below)
+ rawexcinfo = getattr(rawexcinfo, '_rawexcinfo', rawexcinfo)
+ try:
+ excinfo = py.code.ExceptionInfo(rawexcinfo)
+ except TypeError:
+ try:
+ try:
+ l = py.std.traceback.format_exception(*rawexcinfo)
+ l.insert(0, "NOTE: Incompatible Exception Representation, "
+ "displaying natively:\n\n")
+ pytest.fail("".join(l), pytrace=False)
+ except (pytest.fail.Exception, KeyboardInterrupt):
+ raise
+ except:
+ pytest.fail("ERROR: Unknown Incompatible Exception "
+ "representation:\n%r" %(rawexcinfo,), pytrace=False)
+ except KeyboardInterrupt:
+ raise
+ except pytest.fail.Exception:
+ excinfo = py.code.ExceptionInfo()
+ self.__dict__.setdefault('_excinfo', []).append(excinfo)
+
+ def addError(self, testcase, rawexcinfo):
+ self._addexcinfo(rawexcinfo)
+ def addFailure(self, testcase, rawexcinfo):
+ self._addexcinfo(rawexcinfo)
+ def addSkip(self, testcase, reason):
+ try:
+ pytest.skip(reason)
+ except pytest.skip.Exception:
+ self._addexcinfo(sys.exc_info())
+ def addExpectedFailure(self, testcase, rawexcinfo, reason):
+ try:
+ pytest.xfail(str(reason))
+ except pytest.xfail.Exception:
+ self._addexcinfo(sys.exc_info())
+ def addUnexpectedSuccess(self, testcase, reason):
+ pass
+ def addSuccess(self, testcase):
+ pass
+ def stopTest(self, testcase):
+ pass
+ 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'))
+
+ at pytest.mark.tryfirst
+def pytest_runtest_makereport(item, call):
+ if isinstance(item, TestCaseFunction):
+ if item._excinfo:
+ call.excinfo = item._excinfo.pop(0)
+ del call.result
+
+# twisted trial support
+def pytest_runtest_protocol(item, __multicall__):
+ if isinstance(item, TestCaseFunction):
+ if 'twisted.trial.unittest' in sys.modules:
+ ut = sys.modules['twisted.python.failure']
+ Failure__init__ = ut.Failure.__init__.im_func
+ check_testcase_implements_trial_reporter()
+ def excstore(self, exc_value=None, exc_type=None, exc_tb=None):
+ if exc_value is None:
+ self._rawexcinfo = sys.exc_info()
+ else:
+ if exc_type is None:
+ exc_type = type(exc_value)
+ self._rawexcinfo = (exc_type, exc_value, exc_tb)
+ Failure__init__(self, exc_value, exc_type, exc_tb)
+ ut.Failure.__init__ = excstore
+ try:
+ return __multicall__.execute()
+ finally:
+ ut.Failure.__init__ = Failure__init__
+
+def check_testcase_implements_trial_reporter(done=[]):
+ if done:
+ return
+ from zope.interface import classImplements
+ from twisted.trial.itrial import IReporter
+ classImplements(TestCaseFunction, IReporter)
+ done.append(1)
diff --git a/lib-python/conftest.py b/lib-python/conftest.py
--- a/lib-python/conftest.py
+++ b/lib-python/conftest.py
@@ -30,7 +30,7 @@
def pytest_addoption(parser):
group = parser.getgroup("complicance testing options")
group.addoption('-T', '--timeout', action="store", type="string",
- default="100", dest="timeout",
+ default="1000", dest="timeout",
help="fail a test module after the given timeout. "
"specify in seconds or 'NUMmp' aka Mega-Pystones")
group.addoption('--pypy', action="store", type="string",
@@ -39,11 +39,8 @@
group.addoption('--filter', action="store", type="string", default=None,
dest="unittest_filter", help="Similar to -k, XXX")
-option = py.test.config.option
-
-def gettimeout():
+def gettimeout(timeout):
from test import pystone
- timeout = option.timeout.lower()
if timeout.endswith('mp'):
megapystone = float(timeout[:-2])
t, stone = pystone.Proc0(10000)
@@ -340,7 +337,7 @@
RegrTest('test_peepholer.py'),
RegrTest('test_pep247.py'),
RegrTest('test_pep263.py'),
- RegrTest('test_pep277.py', skip=only_win32),
+ RegrTest('test_pep277.py'),
RegrTest('test_pep292.py'),
RegrTest('test_pickle.py', core=True),
RegrTest('test_pickletools.py', core=False),
@@ -536,43 +533,22 @@
assert not missing, "non-listed tests:\n%s" % ('\n'.join(missing),)
check_testmap_complete()
-class RegrDirectory(py.test.collect.Directory):
- """ The central hub for gathering CPython's compliance tests
- Basically we work off the above 'testmap'
- which describes for all test modules their specific
- type. XXX If you find errors in the classification
- please correct them!
- """
- def get(self, name, cache={}):
- if not cache:
- for x in testmap:
- cache[x.basename] = x
- return cache.get(name, None)
-
- def collect(self):
- we_are_in_modified = self.fspath == modregrtestdir
- l = []
- for x in self.fspath.listdir():
- name = x.basename
- regrtest = self.get(name)
- if regrtest is not None:
- if bool(we_are_in_modified) ^ regrtest.ismodified():
- continue
- #if option.extracttests:
- # l.append(InterceptedRunModule(name, self, regrtest))
- #else:
- l.append(RunFileExternal(name, parent=self, regrtest=regrtest))
- return l
+def pytest_configure(config):
+ config._basename2spec = cache = {}
+ for x in testmap:
+ cache[x.basename] = x
-def pytest_collect_directory(parent, path):
- # use RegrDirectory collector for both modified and unmodified tests
- if path in (modregrtestdir, regrtestdir):
- return RegrDirectory(path, parent)
-
-def pytest_ignore_collect(path):
- # ignore all files - only RegrDirectory generates tests in lib-python
- if path.check(file=1):
- return True
+def pytest_collect_file(path, parent, __multicall__):
+ # don't collect files except through this hook
+ # implemented by clearing the list of to-be-called
+ # remaining hook methods
+ __multicall__.methods[:] = []
+ regrtest = parent.config._basename2spec.get(path.basename, None)
+ if regrtest is None:
+ return
+ if path.dirpath() not in (modregrtestdir, regrtestdir):
+ return
+ return RunFileExternal(path.basename, parent=parent, regrtest=regrtest)
class RunFileExternal(py.test.collect.File):
def __init__(self, name, parent, regrtest):
@@ -589,7 +565,7 @@
#
# testmethod:
-# invoking in a seprate process: py.py TESTFILE
+# invoking in a separate process: py.py TESTFILE
#
import os
import time
@@ -615,8 +591,8 @@
'run-script', 'regrverbose.py')
regrrun = str(regr_script)
-
- TIMEOUT = gettimeout()
+ option = self.config.option
+ TIMEOUT = gettimeout(option.timeout.lower())
if option.pypy:
execpath = py.path.local(option.pypy)
if not execpath.check():
@@ -707,8 +683,11 @@
return status, stdout.read(mode='rU'), stderr.read(mode='rU')
def getresult(self, regrtest):
- cmd = self.getinvocation(regrtest)
- exit_status, test_stdout, test_stderr = self.getstatusouterr(cmd)
+ cmd = self.getinvocation(regrtest)
+ tempdir = py.test.ensuretemp(self.fspath.basename)
+ oldcwd = tempdir.chdir()
+ exit_status, test_stdout, test_stderr = self.getstatusouterr(cmd)
+ oldcwd.chdir()
skipped = False
timedout = test_stderr.rfind(26*"=" + "timedout" + 26*"=") != -1
if not timedout:
diff --git a/lib-python/modified-2.7.0/ctypes/__init__.py b/lib-python/modified-2.7.0/ctypes/__init__.py
--- a/lib-python/modified-2.7.0/ctypes/__init__.py
+++ b/lib-python/modified-2.7.0/ctypes/__init__.py
@@ -355,11 +355,12 @@
self._handle = handle
def __repr__(self):
- return "<%s '%s', handle %x at %x>" % \
+ return "<%s '%s', handle %r at %x>" % \
(self.__class__.__name__, self._name,
- (self._handle & (_sys.maxint*2 + 1)),
+ (self._handle),
id(self) & (_sys.maxint*2 + 1))
+
def __getattr__(self, name):
if name.startswith('__') and name.endswith('__'):
raise AttributeError(name)
diff --git a/lib-python/modified-2.7.0/ctypes/test/test_callbacks.py b/lib-python/modified-2.7.0/ctypes/test/test_callbacks.py
--- a/lib-python/modified-2.7.0/ctypes/test/test_callbacks.py
+++ b/lib-python/modified-2.7.0/ctypes/test/test_callbacks.py
@@ -1,7 +1,6 @@
import unittest
from ctypes import *
import _ctypes_test
-from ctypes.test import xfail
class Callbacks(unittest.TestCase):
functype = CFUNCTYPE
@@ -125,7 +124,6 @@
prototype = self.functype.im_func(object)
self.assertRaises(TypeError, prototype, lambda: None)
- @xfail
def test_issue_7959(self):
proto = self.functype.im_func(None)
diff --git a/lib-python/modified-2.7.0/ctypes/test/test_cast.py b/lib-python/modified-2.7.0/ctypes/test/test_cast.py
--- a/lib-python/modified-2.7.0/ctypes/test/test_cast.py
+++ b/lib-python/modified-2.7.0/ctypes/test/test_cast.py
@@ -2,8 +2,6 @@
import unittest
import sys
-from ctypes.test import xfail
-
class Test(unittest.TestCase):
def test_array2pointer(self):
diff --git a/lib-python/modified-2.7.0/ctypes/test/test_init.py b/lib-python/modified-2.7.0/ctypes/test/test_init.py
--- a/lib-python/modified-2.7.0/ctypes/test/test_init.py
+++ b/lib-python/modified-2.7.0/ctypes/test/test_init.py
@@ -1,6 +1,5 @@
from ctypes import *
import unittest
-from ctypes.test import xfail
class X(Structure):
_fields_ = [("a", c_int),
@@ -21,7 +20,6 @@
class InitTest(unittest.TestCase):
- @xfail
def test_get(self):
# make sure the only accessing a nested structure
# doesn't call the structure's __new__ and __init__
diff --git a/lib-python/modified-2.7.0/ctypes/test/test_macholib.py b/lib-python/modified-2.7.0/ctypes/test/test_macholib.py
--- a/lib-python/modified-2.7.0/ctypes/test/test_macholib.py
+++ b/lib-python/modified-2.7.0/ctypes/test/test_macholib.py
@@ -52,7 +52,6 @@
'/usr/lib/libSystem.B.dylib')
result = find_lib('z')
- self.assertTrue(result.startswith('/usr/lib/libz.1'))
self.assertTrue(result.endswith('.dylib'))
self.assertEqual(find_lib('IOKit'),
diff --git a/lib-python/modified-2.7.0/ctypes/test/test_objects.py b/lib-python/modified-2.7.0/ctypes/test/test_objects.py
--- a/lib-python/modified-2.7.0/ctypes/test/test_objects.py
+++ b/lib-python/modified-2.7.0/ctypes/test/test_objects.py
@@ -22,7 +22,7 @@
>>> array[4] = 'foo bar'
>>> array._objects
-{'4': 'foo bar'}
+{'4': <CArgObject 'foo bar'>}
>>> array[4]
'foo bar'
>>>
@@ -47,9 +47,9 @@
>>> x.array[0] = 'spam spam spam'
>>> x._objects
-{'0:2': 'spam spam spam'}
+{'0:2': <CArgObject 'spam spam spam'>}
>>> x.array._b_base_._objects
-{'0:2': 'spam spam spam'}
+{'0:2': <CArgObject 'spam spam spam'>}
>>>
'''
diff --git a/lib-python/modified-2.7.0/ctypes/test/test_prototypes.py b/lib-python/modified-2.7.0/ctypes/test/test_prototypes.py
--- a/lib-python/modified-2.7.0/ctypes/test/test_prototypes.py
+++ b/lib-python/modified-2.7.0/ctypes/test/test_prototypes.py
@@ -1,6 +1,5 @@
from ctypes import *
import unittest
-from ctypes.test import xfail
# IMPORTANT INFO:
#
@@ -49,7 +48,6 @@
func.restype = c_long
func.argtypes = None
- @xfail
def test_paramflags(self):
# function returns c_void_p result,
# and has a required parameter named 'input'
diff --git a/lib-python/modified-2.7.0/ctypes/test/test_refcounts.py b/lib-python/modified-2.7.0/ctypes/test/test_refcounts.py
--- a/lib-python/modified-2.7.0/ctypes/test/test_refcounts.py
+++ b/lib-python/modified-2.7.0/ctypes/test/test_refcounts.py
@@ -90,6 +90,7 @@
return a * b * 2
f = proto(func)
+ gc.collect()
a = sys.getrefcount(ctypes.c_int)
f(1, 2)
self.assertEqual(sys.getrefcount(ctypes.c_int), a)
diff --git a/lib-python/2.7.0/lib2to3/Grammar.txt b/lib-python/modified-2.7.0/lib2to3/Grammar.txt
copy from lib-python/2.7.0/lib2to3/Grammar.txt
copy to lib-python/modified-2.7.0/lib2to3/Grammar.txt
diff --git a/lib-python/2.7.0/lib2to3/PatternGrammar.txt b/lib-python/modified-2.7.0/lib2to3/PatternGrammar.txt
copy from lib-python/2.7.0/lib2to3/PatternGrammar.txt
copy to lib-python/modified-2.7.0/lib2to3/PatternGrammar.txt
diff --git a/lib-python/2.7.0/lib2to3/__init__.py b/lib-python/modified-2.7.0/lib2to3/__init__.py
copy from lib-python/2.7.0/lib2to3/__init__.py
copy to lib-python/modified-2.7.0/lib2to3/__init__.py
diff --git a/lib-python/2.7.0/lib2to3/btm_matcher.py b/lib-python/modified-2.7.0/lib2to3/btm_matcher.py
copy from lib-python/2.7.0/lib2to3/btm_matcher.py
copy to lib-python/modified-2.7.0/lib2to3/btm_matcher.py
diff --git a/lib-python/2.7.0/lib2to3/btm_utils.py b/lib-python/modified-2.7.0/lib2to3/btm_utils.py
copy from lib-python/2.7.0/lib2to3/btm_utils.py
copy to lib-python/modified-2.7.0/lib2to3/btm_utils.py
diff --git a/lib-python/2.7.0/lib2to3/fixer_base.py b/lib-python/modified-2.7.0/lib2to3/fixer_base.py
copy from lib-python/2.7.0/lib2to3/fixer_base.py
copy to lib-python/modified-2.7.0/lib2to3/fixer_base.py
diff --git a/lib-python/2.7.0/lib2to3/fixer_util.py b/lib-python/modified-2.7.0/lib2to3/fixer_util.py
copy from lib-python/2.7.0/lib2to3/fixer_util.py
copy to lib-python/modified-2.7.0/lib2to3/fixer_util.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/__init__.py b/lib-python/modified-2.7.0/lib2to3/fixes/__init__.py
copy from lib-python/2.7.0/lib2to3/fixes/__init__.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/__init__.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_apply.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_apply.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_apply.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_apply.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_basestring.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_basestring.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_basestring.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_basestring.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_buffer.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_buffer.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_buffer.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_buffer.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_callable.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_callable.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_callable.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_callable.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_dict.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_dict.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_dict.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_dict.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_except.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_except.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_except.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_except.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_exec.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_exec.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_exec.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_exec.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_execfile.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_execfile.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_execfile.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_execfile.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_exitfunc.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_exitfunc.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_exitfunc.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_exitfunc.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_filter.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_filter.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_filter.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_filter.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_funcattrs.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_funcattrs.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_funcattrs.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_funcattrs.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_future.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_future.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_future.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_future.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_getcwdu.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_getcwdu.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_getcwdu.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_getcwdu.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_has_key.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_has_key.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_has_key.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_has_key.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_idioms.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_idioms.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_idioms.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_idioms.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_import.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_import.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_import.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_import.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_imports.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_imports.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_imports.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_imports.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_imports2.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_imports2.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_imports2.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_imports2.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_input.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_input.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_input.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_input.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_intern.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_intern.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_intern.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_intern.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_isinstance.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_isinstance.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_isinstance.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_isinstance.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_itertools.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_itertools.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_itertools.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_itertools.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_itertools_imports.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_itertools_imports.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_itertools_imports.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_itertools_imports.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_long.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_long.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_long.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_long.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_map.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_map.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_map.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_map.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_metaclass.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_metaclass.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_metaclass.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_metaclass.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_methodattrs.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_methodattrs.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_methodattrs.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_methodattrs.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_ne.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_ne.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_ne.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_ne.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_next.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_next.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_next.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_next.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_nonzero.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_nonzero.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_nonzero.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_nonzero.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_numliterals.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_numliterals.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_numliterals.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_numliterals.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_operator.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_operator.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_operator.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_operator.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_paren.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_paren.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_paren.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_paren.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_print.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_print.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_print.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_print.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_raise.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_raise.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_raise.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_raise.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_raw_input.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_raw_input.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_raw_input.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_raw_input.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_reduce.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_reduce.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_reduce.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_reduce.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_renames.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_renames.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_renames.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_renames.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_repr.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_repr.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_repr.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_repr.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_set_literal.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_set_literal.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_set_literal.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_set_literal.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_standarderror.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_standarderror.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_standarderror.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_standarderror.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_sys_exc.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_sys_exc.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_sys_exc.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_sys_exc.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_throw.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_throw.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_throw.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_throw.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_tuple_params.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_tuple_params.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_tuple_params.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_tuple_params.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_types.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_types.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_types.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_types.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_unicode.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_unicode.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_unicode.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_unicode.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_urllib.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_urllib.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_urllib.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_urllib.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_ws_comma.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_ws_comma.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_ws_comma.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_ws_comma.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_xrange.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_xrange.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_xrange.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_xrange.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_xreadlines.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_xreadlines.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_xreadlines.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_xreadlines.py
diff --git a/lib-python/2.7.0/lib2to3/fixes/fix_zip.py b/lib-python/modified-2.7.0/lib2to3/fixes/fix_zip.py
copy from lib-python/2.7.0/lib2to3/fixes/fix_zip.py
copy to lib-python/modified-2.7.0/lib2to3/fixes/fix_zip.py
diff --git a/lib-python/2.7.0/lib2to3/main.py b/lib-python/modified-2.7.0/lib2to3/main.py
copy from lib-python/2.7.0/lib2to3/main.py
copy to lib-python/modified-2.7.0/lib2to3/main.py
diff --git a/lib-python/2.7.0/lib2to3/patcomp.py b/lib-python/modified-2.7.0/lib2to3/patcomp.py
copy from lib-python/2.7.0/lib2to3/patcomp.py
copy to lib-python/modified-2.7.0/lib2to3/patcomp.py
diff --git a/lib-python/2.7.0/lib2to3/pgen2/__init__.py b/lib-python/modified-2.7.0/lib2to3/pgen2/__init__.py
copy from lib-python/2.7.0/lib2to3/pgen2/__init__.py
copy to lib-python/modified-2.7.0/lib2to3/pgen2/__init__.py
diff --git a/lib-python/2.7.0/lib2to3/pgen2/conv.py b/lib-python/modified-2.7.0/lib2to3/pgen2/conv.py
copy from lib-python/2.7.0/lib2to3/pgen2/conv.py
copy to lib-python/modified-2.7.0/lib2to3/pgen2/conv.py
diff --git a/lib-python/2.7.0/lib2to3/pgen2/driver.py b/lib-python/modified-2.7.0/lib2to3/pgen2/driver.py
copy from lib-python/2.7.0/lib2to3/pgen2/driver.py
copy to lib-python/modified-2.7.0/lib2to3/pgen2/driver.py
diff --git a/lib-python/2.7.0/lib2to3/pgen2/grammar.py b/lib-python/modified-2.7.0/lib2to3/pgen2/grammar.py
copy from lib-python/2.7.0/lib2to3/pgen2/grammar.py
copy to lib-python/modified-2.7.0/lib2to3/pgen2/grammar.py
diff --git a/lib-python/2.7.0/lib2to3/pgen2/literals.py b/lib-python/modified-2.7.0/lib2to3/pgen2/literals.py
copy from lib-python/2.7.0/lib2to3/pgen2/literals.py
copy to lib-python/modified-2.7.0/lib2to3/pgen2/literals.py
diff --git a/lib-python/2.7.0/lib2to3/pgen2/parse.py b/lib-python/modified-2.7.0/lib2to3/pgen2/parse.py
copy from lib-python/2.7.0/lib2to3/pgen2/parse.py
copy to lib-python/modified-2.7.0/lib2to3/pgen2/parse.py
diff --git a/lib-python/2.7.0/lib2to3/pgen2/pgen.py b/lib-python/modified-2.7.0/lib2to3/pgen2/pgen.py
copy from lib-python/2.7.0/lib2to3/pgen2/pgen.py
copy to lib-python/modified-2.7.0/lib2to3/pgen2/pgen.py
diff --git a/lib-python/2.7.0/lib2to3/pgen2/token.py b/lib-python/modified-2.7.0/lib2to3/pgen2/token.py
copy from lib-python/2.7.0/lib2to3/pgen2/token.py
copy to lib-python/modified-2.7.0/lib2to3/pgen2/token.py
diff --git a/lib-python/2.7.0/lib2to3/pgen2/tokenize.py b/lib-python/modified-2.7.0/lib2to3/pgen2/tokenize.py
copy from lib-python/2.7.0/lib2to3/pgen2/tokenize.py
copy to lib-python/modified-2.7.0/lib2to3/pgen2/tokenize.py
diff --git a/lib-python/2.7.0/lib2to3/pygram.py b/lib-python/modified-2.7.0/lib2to3/pygram.py
copy from lib-python/2.7.0/lib2to3/pygram.py
copy to lib-python/modified-2.7.0/lib2to3/pygram.py
diff --git a/lib-python/2.7.0/lib2to3/pytree.py b/lib-python/modified-2.7.0/lib2to3/pytree.py
copy from lib-python/2.7.0/lib2to3/pytree.py
copy to lib-python/modified-2.7.0/lib2to3/pytree.py
--- a/lib-python/2.7.0/lib2to3/pytree.py
+++ b/lib-python/modified-2.7.0/lib2to3/pytree.py
@@ -741,11 +741,12 @@
elif self.name == "bare_name":
yield self._bare_name_matches(nodes)
else:
- # The reason for this is that hitting the recursion limit usually
- # results in some ugly messages about how RuntimeErrors are being
- # ignored.
- save_stderr = sys.stderr
- sys.stderr = StringIO()
+ # There used to be some monkey patching of sys.stderr here, to
+ # silence the error message from the RuntimError, PyPy has removed
+ # this because it relied on reference counting. This is because the
+ # caller of this function doesn't consume this generator fully, so
+ # the finally statement that used to be here would only be executed
+ # when the gc happened to run.
try:
for count, r in self._recursive_matches(nodes, 0):
if self.name:
@@ -758,8 +759,6 @@
if self.name:
r[self.name] = nodes[:count]
yield count, r
- finally:
- sys.stderr = save_stderr
def _iterative_matches(self, nodes):
"""Helper to iteratively yield the matches."""
diff --git a/lib-python/2.7.0/lib2to3/refactor.py b/lib-python/modified-2.7.0/lib2to3/refactor.py
copy from lib-python/2.7.0/lib2to3/refactor.py
copy to lib-python/modified-2.7.0/lib2to3/refactor.py
diff --git a/lib-python/2.7.0/lib2to3/tests/__init__.py b/lib-python/modified-2.7.0/lib2to3/tests/__init__.py
copy from lib-python/2.7.0/lib2to3/tests/__init__.py
copy to lib-python/modified-2.7.0/lib2to3/tests/__init__.py
diff --git a/lib-python/2.7.0/lib2to3/tests/data/README b/lib-python/modified-2.7.0/lib2to3/tests/data/README
copy from lib-python/2.7.0/lib2to3/tests/data/README
copy to lib-python/modified-2.7.0/lib2to3/tests/data/README
diff --git a/lib-python/2.7.0/lib2to3/tests/data/bom.py b/lib-python/modified-2.7.0/lib2to3/tests/data/bom.py
copy from lib-python/2.7.0/lib2to3/tests/data/bom.py
copy to lib-python/modified-2.7.0/lib2to3/tests/data/bom.py
diff --git a/lib-python/2.7.0/lib2to3/tests/data/crlf.py b/lib-python/modified-2.7.0/lib2to3/tests/data/crlf.py
copy from lib-python/2.7.0/lib2to3/tests/data/crlf.py
copy to lib-python/modified-2.7.0/lib2to3/tests/data/crlf.py
diff --git a/lib-python/2.7.0/lib2to3/tests/data/different_encoding.py b/lib-python/modified-2.7.0/lib2to3/tests/data/different_encoding.py
copy from lib-python/2.7.0/lib2to3/tests/data/different_encoding.py
copy to lib-python/modified-2.7.0/lib2to3/tests/data/different_encoding.py
diff --git a/lib-python/2.7.0/lib2to3/tests/data/fixers/bad_order.py b/lib-python/modified-2.7.0/lib2to3/tests/data/fixers/bad_order.py
copy from lib-python/2.7.0/lib2to3/tests/data/fixers/bad_order.py
copy to lib-python/modified-2.7.0/lib2to3/tests/data/fixers/bad_order.py
diff --git a/lib-python/2.7.0/lib2to3/tests/data/fixers/myfixes/__init__.py b/lib-python/modified-2.7.0/lib2to3/tests/data/fixers/myfixes/__init__.py
copy from lib-python/2.7.0/lib2to3/tests/data/fixers/myfixes/__init__.py
copy to lib-python/modified-2.7.0/lib2to3/tests/data/fixers/myfixes/__init__.py
diff --git a/lib-python/2.7.0/lib2to3/tests/data/fixers/myfixes/fix_explicit.py b/lib-python/modified-2.7.0/lib2to3/tests/data/fixers/myfixes/fix_explicit.py
copy from lib-python/2.7.0/lib2to3/tests/data/fixers/myfixes/fix_explicit.py
copy to lib-python/modified-2.7.0/lib2to3/tests/data/fixers/myfixes/fix_explicit.py
diff --git a/lib-python/2.7.0/lib2to3/tests/data/fixers/myfixes/fix_first.py b/lib-python/modified-2.7.0/lib2to3/tests/data/fixers/myfixes/fix_first.py
copy from lib-python/2.7.0/lib2to3/tests/data/fixers/myfixes/fix_first.py
copy to lib-python/modified-2.7.0/lib2to3/tests/data/fixers/myfixes/fix_first.py
diff --git a/lib-python/2.7.0/lib2to3/tests/data/fixers/myfixes/fix_last.py b/lib-python/modified-2.7.0/lib2to3/tests/data/fixers/myfixes/fix_last.py
copy from lib-python/2.7.0/lib2to3/tests/data/fixers/myfixes/fix_last.py
copy to lib-python/modified-2.7.0/lib2to3/tests/data/fixers/myfixes/fix_last.py
diff --git a/lib-python/2.7.0/lib2to3/tests/data/fixers/myfixes/fix_parrot.py b/lib-python/modified-2.7.0/lib2to3/tests/data/fixers/myfixes/fix_parrot.py
copy from lib-python/2.7.0/lib2to3/tests/data/fixers/myfixes/fix_parrot.py
copy to lib-python/modified-2.7.0/lib2to3/tests/data/fixers/myfixes/fix_parrot.py
diff --git a/lib-python/2.7.0/lib2to3/tests/data/fixers/myfixes/fix_preorder.py b/lib-python/modified-2.7.0/lib2to3/tests/data/fixers/myfixes/fix_preorder.py
copy from lib-python/2.7.0/lib2to3/tests/data/fixers/myfixes/fix_preorder.py
copy to lib-python/modified-2.7.0/lib2to3/tests/data/fixers/myfixes/fix_preorder.py
diff --git a/lib-python/2.7.0/lib2to3/tests/data/fixers/no_fixer_cls.py b/lib-python/modified-2.7.0/lib2to3/tests/data/fixers/no_fixer_cls.py
copy from lib-python/2.7.0/lib2to3/tests/data/fixers/no_fixer_cls.py
copy to lib-python/modified-2.7.0/lib2to3/tests/data/fixers/no_fixer_cls.py
diff --git a/lib-python/2.7.0/lib2to3/tests/data/fixers/parrot_example.py b/lib-python/modified-2.7.0/lib2to3/tests/data/fixers/parrot_example.py
copy from lib-python/2.7.0/lib2to3/tests/data/fixers/parrot_example.py
copy to lib-python/modified-2.7.0/lib2to3/tests/data/fixers/parrot_example.py
diff --git a/lib-python/2.7.0/lib2to3/tests/data/infinite_recursion.py b/lib-python/modified-2.7.0/lib2to3/tests/data/infinite_recursion.py
copy from lib-python/2.7.0/lib2to3/tests/data/infinite_recursion.py
copy to lib-python/modified-2.7.0/lib2to3/tests/data/infinite_recursion.py
diff --git a/lib-python/2.7.0/lib2to3/tests/data/py2_test_grammar.py b/lib-python/modified-2.7.0/lib2to3/tests/data/py2_test_grammar.py
copy from lib-python/2.7.0/lib2to3/tests/data/py2_test_grammar.py
copy to lib-python/modified-2.7.0/lib2to3/tests/data/py2_test_grammar.py
diff --git a/lib-python/2.7.0/lib2to3/tests/data/py3_test_grammar.py b/lib-python/modified-2.7.0/lib2to3/tests/data/py3_test_grammar.py
copy from lib-python/2.7.0/lib2to3/tests/data/py3_test_grammar.py
copy to lib-python/modified-2.7.0/lib2to3/tests/data/py3_test_grammar.py
diff --git a/lib-python/2.7.0/lib2to3/tests/pytree_idempotency.py b/lib-python/modified-2.7.0/lib2to3/tests/pytree_idempotency.py
copy from lib-python/2.7.0/lib2to3/tests/pytree_idempotency.py
copy to lib-python/modified-2.7.0/lib2to3/tests/pytree_idempotency.py
diff --git a/lib-python/2.7.0/lib2to3/tests/support.py b/lib-python/modified-2.7.0/lib2to3/tests/support.py
copy from lib-python/2.7.0/lib2to3/tests/support.py
copy to lib-python/modified-2.7.0/lib2to3/tests/support.py
diff --git a/lib-python/2.7.0/lib2to3/tests/test_all_fixers.py b/lib-python/modified-2.7.0/lib2to3/tests/test_all_fixers.py
copy from lib-python/2.7.0/lib2to3/tests/test_all_fixers.py
copy to lib-python/modified-2.7.0/lib2to3/tests/test_all_fixers.py
diff --git a/lib-python/2.7.0/lib2to3/tests/test_fixers.py b/lib-python/modified-2.7.0/lib2to3/tests/test_fixers.py
copy from lib-python/2.7.0/lib2to3/tests/test_fixers.py
copy to lib-python/modified-2.7.0/lib2to3/tests/test_fixers.py
diff --git a/lib-python/2.7.0/lib2to3/tests/test_main.py b/lib-python/modified-2.7.0/lib2to3/tests/test_main.py
copy from lib-python/2.7.0/lib2to3/tests/test_main.py
copy to lib-python/modified-2.7.0/lib2to3/tests/test_main.py
diff --git a/lib-python/2.7.0/lib2to3/tests/test_parser.py b/lib-python/modified-2.7.0/lib2to3/tests/test_parser.py
copy from lib-python/2.7.0/lib2to3/tests/test_parser.py
copy to lib-python/modified-2.7.0/lib2to3/tests/test_parser.py
diff --git a/lib-python/2.7.0/lib2to3/tests/test_pytree.py b/lib-python/modified-2.7.0/lib2to3/tests/test_pytree.py
copy from lib-python/2.7.0/lib2to3/tests/test_pytree.py
copy to lib-python/modified-2.7.0/lib2to3/tests/test_pytree.py
diff --git a/lib-python/2.7.0/lib2to3/tests/test_refactor.py b/lib-python/modified-2.7.0/lib2to3/tests/test_refactor.py
copy from lib-python/2.7.0/lib2to3/tests/test_refactor.py
copy to lib-python/modified-2.7.0/lib2to3/tests/test_refactor.py
diff --git a/lib-python/2.7.0/lib2to3/tests/test_util.py b/lib-python/modified-2.7.0/lib2to3/tests/test_util.py
copy from lib-python/2.7.0/lib2to3/tests/test_util.py
copy to lib-python/modified-2.7.0/lib2to3/tests/test_util.py
diff --git a/lib-python/modified-2.7.0/test/list_tests.py b/lib-python/modified-2.7.0/test/list_tests.py
--- a/lib-python/modified-2.7.0/test/list_tests.py
+++ b/lib-python/modified-2.7.0/test/list_tests.py
@@ -476,7 +476,11 @@
u += "eggs"
self.assertEqual(u, self.type2test("spameggs"))
- self.assertRaises(TypeError, u.__iadd__, None)
+ def f_iadd(u, x):
+ u += x
+ return u
+
+ self.assertRaises(TypeError, f_iadd, u, None)
def test_imul(self):
u = self.type2test([0, 1])
diff --git a/lib-python/modified-2.7.0/test/test_ast.py b/lib-python/modified-2.7.0/test/test_ast.py
--- a/lib-python/modified-2.7.0/test/test_ast.py
+++ b/lib-python/modified-2.7.0/test/test_ast.py
@@ -195,22 +195,26 @@
self._assertTrueorder(value, parent_pos)
def test_AST_objects(self):
- x = ast.AST()
- try:
- x.foobar = 21
- except AttributeError, e:
- self.assertEquals(e.args[0],
- "'_ast.AST' object has no attribute 'foobar'")
- else:
- self.assert_(False)
+ if test_support.check_impl_detail():
+ # PyPy also provides a __dict__ to the ast.AST base class.
- try:
- ast.AST(lineno=2)
- except AttributeError, e:
- self.assertEquals(e.args[0],
- "'_ast.AST' object has no attribute 'lineno'")
- else:
- self.assert_(False)
+ x = ast.AST()
+ try:
+ x.foobar = 21
+ except AttributeError, e:
+ self.assertEquals(e.args[0],
+ "'_ast.AST' object has no attribute 'foobar'")
+ else:
+ self.assert_(False)
+
+ try:
+ ast.AST(lineno=2)
+ except AttributeError, e:
+ self.assertEquals(e.args[0],
+ "'_ast.AST' object has no attribute 'lineno'")
+ else:
+ self.assert_(False)
+
try:
ast.AST(2)
except TypeError, e:
diff --git a/lib-python/2.7.0/test/test_cmd_line_script.py b/lib-python/modified-2.7.0/test/test_cmd_line_script.py
copy from lib-python/2.7.0/test/test_cmd_line_script.py
copy to lib-python/modified-2.7.0/test/test_cmd_line_script.py
--- a/lib-python/2.7.0/test/test_cmd_line_script.py
+++ b/lib-python/modified-2.7.0/test/test_cmd_line_script.py
@@ -112,6 +112,8 @@
self._check_script(script_dir, script_name, script_dir, '')
def test_directory_compiled(self):
+ if test.test_support.check_impl_detail(pypy=True):
+ raise unittest.SkipTest("pypy won't load lone .pyc files")
with temp_dir() as script_dir:
script_name = _make_test_script(script_dir, '__main__')
compiled_name = compile_script(script_name)
@@ -173,6 +175,8 @@
script_name, 'test_pkg')
def test_package_compiled(self):
+ if test.test_support.check_impl_detail(pypy=True):
+ raise unittest.SkipTest("pypy won't load lone .pyc files")
with temp_dir() as script_dir:
pkg_dir = os.path.join(script_dir, 'test_pkg')
make_pkg(pkg_dir)
diff --git a/lib-python/2.7.0/test/test_runpy.py b/lib-python/modified-2.7.0/test/test_runpy.py
copy from lib-python/2.7.0/test/test_runpy.py
copy to lib-python/modified-2.7.0/test/test_runpy.py
--- a/lib-python/2.7.0/test/test_runpy.py
+++ b/lib-python/modified-2.7.0/test/test_runpy.py
@@ -5,10 +5,15 @@
import sys
import re
import tempfile
-from test.test_support import verbose, run_unittest, forget
+from test.test_support import verbose, run_unittest, forget, check_impl_detail
from test.script_helper import (temp_dir, make_script, compile_script,
make_pkg, make_zip_script, make_zip_pkg)
+if check_impl_detail(pypy=True):
+ no_lone_pyc_file = True
+else:
+ no_lone_pyc_file = False
+
from runpy import _run_code, _run_module_code, run_module, run_path
# Note: This module can't safely test _run_module_as_main as it
@@ -168,13 +173,14 @@
self.assertIn("x", d1)
self.assertTrue(d1["x"] == 1)
del d1 # Ensure __loader__ entry doesn't keep file open
- __import__(mod_name)
- os.remove(mod_fname)
- if verbose: print "Running from compiled:", mod_name
- d2 = run_module(mod_name) # Read from bytecode
- self.assertIn("x", d2)
- self.assertTrue(d2["x"] == 1)
- del d2 # Ensure __loader__ entry doesn't keep file open
+ if not no_lone_pyc_file:
+ __import__(mod_name)
+ os.remove(mod_fname)
+ if verbose: print "Running from compiled:", mod_name
+ d2 = run_module(mod_name) # Read from bytecode
+ self.assertIn("x", d2)
+ self.assertTrue(d2["x"] == 1)
+ del d2 # Ensure __loader__ entry doesn't keep file open
finally:
self._del_pkg(pkg_dir, depth, mod_name)
if verbose: print "Module executed successfully"
@@ -190,13 +196,14 @@
self.assertIn("x", d1)
self.assertTrue(d1["x"] == 1)
del d1 # Ensure __loader__ entry doesn't keep file open
- __import__(mod_name)
- os.remove(mod_fname)
- if verbose: print "Running from compiled:", pkg_name
- d2 = run_module(pkg_name) # Read from bytecode
- self.assertIn("x", d2)
- self.assertTrue(d2["x"] == 1)
- del d2 # Ensure __loader__ entry doesn't keep file open
+ if not no_lone_pyc_file:
+ __import__(mod_name)
+ os.remove(mod_fname)
+ if verbose: print "Running from compiled:", pkg_name
+ d2 = run_module(pkg_name) # Read from bytecode
+ self.assertIn("x", d2)
+ self.assertTrue(d2["x"] == 1)
+ del d2 # Ensure __loader__ entry doesn't keep file open
finally:
self._del_pkg(pkg_dir, depth, pkg_name)
if verbose: print "Package executed successfully"
@@ -244,15 +251,17 @@
self.assertIn("sibling", d1)
self.assertIn("nephew", d1)
del d1 # Ensure __loader__ entry doesn't keep file open
- __import__(mod_name)
- os.remove(mod_fname)
- if verbose: print "Running from compiled:", mod_name
- d2 = run_module(mod_name, run_name=run_name) # Read from bytecode
- self.assertIn("__package__", d2)
- self.assertTrue(d2["__package__"] == pkg_name)
- self.assertIn("sibling", d2)
- self.assertIn("nephew", d2)
- del d2 # Ensure __loader__ entry doesn't keep file open
+ if not no_lone_pyc_file:
+ __import__(mod_name)
+ os.remove(mod_fname)
+ if verbose: print "Running from compiled:", mod_name
+ # Read from bytecode
+ d2 = run_module(mod_name, run_name=run_name)
+ self.assertIn("__package__", d2)
+ self.assertTrue(d2["__package__"] == pkg_name)
+ self.assertIn("sibling", d2)
+ self.assertIn("nephew", d2)
+ del d2 # Ensure __loader__ entry doesn't keep file open
finally:
self._del_pkg(pkg_dir, depth, mod_name)
if verbose: print "Module executed successfully"
@@ -345,6 +354,8 @@
script_dir, '')
def test_directory_compiled(self):
+ if no_lone_pyc_file:
+ return
with temp_dir() as script_dir:
mod_name = '__main__'
script_name = self._make_test_script(script_dir, mod_name)
diff --git a/lib-python/modified-2.7.0/test/test_threading.py b/lib-python/modified-2.7.0/test/test_threading.py
--- a/lib-python/modified-2.7.0/test/test_threading.py
+++ b/lib-python/modified-2.7.0/test/test_threading.py
@@ -429,6 +429,9 @@
def joiningfunc(mainthread):
mainthread.join()
print 'end of thread'
+ # stdout is fully buffered because not a tty, we have to flush
+ # before exit.
+ sys.stdout.flush()
\n""" + script
import subprocess
diff --git a/lib-python/modified-2.7.0/test/test_weakset.py b/lib-python/modified-2.7.0/test/test_weakset.py
--- a/lib-python/modified-2.7.0/test/test_weakset.py
+++ b/lib-python/modified-2.7.0/test/test_weakset.py
@@ -332,10 +332,11 @@
next(it) # Trigger internal iteration
# Destroy an item
del items[-1]
- gc.collect() # just in case
+ test_support.gc_collect()
# We have removed either the first consumed items, or another one
self.assertIn(len(list(it)), [len(items), len(items) - 1])
del it
+ test_support.gc_collect()
# The removal has been committed
self.assertEqual(len(s), len(items))
diff --git a/lib_pypy/_csv.py b/lib_pypy/_csv.py
--- a/lib_pypy/_csv.py
+++ b/lib_pypy/_csv.py
@@ -256,25 +256,33 @@
while True:
if c in '\n\r':
# end of line - return [fields]
+ if pos2 > pos:
+ self._parse_add_char(line[pos:pos2])
+ pos = pos2
self._parse_save_field()
self.state = self.EAT_CRNL
elif c == self.dialect.escapechar:
# possible escaped character
+ pos2 -= 1
self.state = self.ESCAPED_CHAR
elif c == self.dialect.delimiter:
# save field - wait for new field
+ if pos2 > pos:
+ self._parse_add_char(line[pos:pos2])
+ pos = pos2
self._parse_save_field()
self.state = self.START_FIELD
else:
# normal character - save in field
pos2 += 1
- c = line[pos2]
- continue
+ if pos2 < len(line):
+ c = line[pos2]
+ continue
break
if pos2 > pos:
self._parse_add_char(line[pos:pos2])
- pos = pos2
-
+ pos = pos2 - 1
+
elif self.state == self.START_RECORD:
if c in '\n\r':
self.state = self.EAT_CRNL
diff --git a/lib_pypy/_ctypes/__init__.py b/lib_pypy/_ctypes/__init__.py
--- a/lib_pypy/_ctypes/__init__.py
+++ b/lib_pypy/_ctypes/__init__.py
@@ -4,7 +4,7 @@
from _ctypes.primitive import _SimpleCData
from _ctypes.pointer import _Pointer, _cast_addr
from _ctypes.pointer import POINTER, pointer, _pointer_type_cache
-from _ctypes.function import CFuncPtr
+from _ctypes.function import CFuncPtr, call_function
from _ctypes.dll import dlopen
from _ctypes.structure import Structure
from _ctypes.array import Array
diff --git a/lib_pypy/_ctypes/function.py b/lib_pypy/_ctypes/function.py
--- a/lib_pypy/_ctypes/function.py
+++ b/lib_pypy/_ctypes/function.py
@@ -1,23 +1,40 @@
-
-from _ctypes.basics import _CData, _CDataMeta, cdata_from_address
-from _ctypes.primitive import SimpleType
-from _ctypes.basics import ArgumentError, keepalive_key
-from _ctypes.builtin import set_errno, set_last_error
import _rawffi
import sys
import traceback
+from _ctypes.basics import ArgumentError, keepalive_key
+from _ctypes.basics import _CData, _CDataMeta, cdata_from_address
+from _ctypes.builtin import set_errno, set_last_error
+from _ctypes.primitive import SimpleType
+
# XXX this file needs huge refactoring I fear
PARAMFLAG_FIN = 0x1
PARAMFLAG_FOUT = 0x2
PARAMFLAG_FLCID = 0x4
+PARAMFLAG_COMBINED = PARAMFLAG_FIN | PARAMFLAG_FOUT | PARAMFLAG_FLCID
+
+VALID_PARAMFLAGS = (
+ 0,
+ PARAMFLAG_FIN,
+ PARAMFLAG_FIN | PARAMFLAG_FOUT,
+ PARAMFLAG_FIN | PARAMFLAG_FLCID
+ )
+
+WIN64 = sys.platform == 'win32' and sys.maxint == 2**63 - 1
+
def get_com_error(errcode, riid, pIunk):
"Win32 specific: build a COM Error exception"
# XXX need C support code
from _ctypes import COMError
return COMError(errcode, None, None)
+def call_function(func, args):
+ "Only for debugging so far: So that we can call CFunction instances"
+ funcptr = CFuncPtr(func)
+ funcptr.restype = int
+ return funcptr(*args)
+
class CFuncPtrType(_CDataMeta):
# XXX write down here defaults and such things
@@ -54,10 +71,11 @@
def _getargtypes(self):
return self._argtypes_
+
def _setargtypes(self, argtypes):
self._ptr = None
if argtypes is None:
- self._argtypes_ = None
+ self._argtypes_ = ()
else:
for i, argtype in enumerate(argtypes):
if not hasattr(argtype, 'from_param'):
@@ -65,38 +83,91 @@
"item %d in _argtypes_ has no from_param method" % (
i + 1,))
self._argtypes_ = argtypes
+
argtypes = property(_getargtypes, _setargtypes)
+ def _getparamflags(self):
+ return self._paramflags
+
+ def _setparamflags(self, paramflags):
+ if paramflags is None or not self._argtypes_:
+ self._paramflags = None
+ return
+ if not isinstance(paramflags, tuple):
+ raise TypeError("paramflags must be a tuple or None")
+ if len(paramflags) != len(self._argtypes_):
+ raise ValueError("paramflags must have the same length as argtypes")
+ for idx, paramflag in enumerate(paramflags):
+ paramlen = len(paramflag)
+ name = default = None
+ if paramlen == 1:
+ flag = paramflag[0]
+ elif paramlen == 2:
+ flag, name = paramflag
+ elif paramlen == 3:
+ flag, name, default = paramflag
+ else:
+ raise TypeError(
+ "paramflags must be a sequence of (int [,string [,value]]) "
+ "tuples"
+ )
+ if not isinstance(flag, int):
+ raise TypeError(
+ "paramflags must be a sequence of (int [,string [,value]]) "
+ "tuples"
+ )
+ _flag = flag & PARAMFLAG_COMBINED
+ if _flag == PARAMFLAG_FOUT:
+ typ = self._argtypes_[idx]
+ if getattr(typ, '_ffiargshape', None) not in ('P', 'z', 'Z'):
+ raise TypeError(
+ "'out' parameter %d must be a pointer type, not %s"
+ % (idx+1, type(typ).__name__)
+ )
+ elif _flag not in VALID_PARAMFLAGS:
+ raise TypeError("paramflag value %d not supported" % flag)
+ self._paramflags = paramflags
+
+ paramflags = property(_getparamflags, _setparamflags)
+
def _getrestype(self):
return self._restype_
+
def _setrestype(self, restype):
self._ptr = None
if restype is int:
from ctypes import c_int
restype = c_int
- if not isinstance(restype, _CDataMeta) and not restype is None and \
- not callable(restype):
- raise TypeError("Expected ctypes type, got %s" % (restype,))
+ if not (isinstance(restype, _CDataMeta) or restype is None or
+ callable(restype)):
+ raise TypeError("restype must be a type, a callable, or None")
self._restype_ = restype
+
def _delrestype(self):
self._ptr = None
del self._restype_
+
restype = property(_getrestype, _setrestype, _delrestype)
def _geterrcheck(self):
return getattr(self, '_errcheck_', None)
+
def _seterrcheck(self, errcheck):
if not callable(errcheck):
raise TypeError("The errcheck attribute must be callable")
self._errcheck_ = errcheck
+
def _delerrcheck(self):
try:
del self._errcheck_
except AttributeError:
pass
+
errcheck = property(_geterrcheck, _seterrcheck, _delerrcheck)
def _ffishapes(self, args, restype):
+ if args is None:
+ args = []
argtypes = [arg._ffiargshape for arg in args]
if restype is not None:
if not isinstance(restype, SimpleType):
@@ -110,53 +181,66 @@
self.name = None
self._objects = {keepalive_key(0):self}
self._needs_free = True
- argument = None
- if len(args) == 1:
- argument = args[0]
- if isinstance(argument, (int, long)):
- # direct construction from raw address
+ # Empty function object -- this is needed for casts
+ if not args:
+ self._buffer = _rawffi.Array('P')(1)
+ return
+
+ argsl = list(args)
+ argument = argsl.pop(0)
+
+ # Direct construction from raw address
+ if isinstance(argument, (int, long)) and not argsl:
ffiargs, ffires = self._ffishapes(self._argtypes_, self._restype_)
- self._ptr = _rawffi.FuncPtr(argument, ffiargs, ffires,
- self._flags_)
+ self._ptr = _rawffi.FuncPtr(argument, ffiargs, ffires, self._flags_)
self._buffer = self._ptr.byptr()
- elif callable(argument):
- # A callback into python
+ return
+
+ # A callback into Python
+ if callable(argument) and not argsl:
self.callable = argument
ffiargs, ffires = self._ffishapes(self._argtypes_, self._restype_)
if self._restype_ is None:
ffires = None
- self._ptr = _rawffi.CallbackPtr(self._wrap_callable(argument,
- self.argtypes),
- ffiargs, ffires, self._flags_)
+ self._ptr = _rawffi.CallbackPtr(self._wrap_callable(
+ argument, self.argtypes
+ ), ffiargs, ffires, self._flags_)
self._buffer = self._ptr.byptr()
- elif isinstance(argument, tuple) and len(argument) == 2:
- # function exported from a shared library
+ return
+
+ # Function exported from a shared library
+ if isinstance(argument, tuple) and len(argument) == 2:
import ctypes
- self.name, self.dll = argument
- if isinstance(self.dll, str):
- self.dll = ctypes.CDLL(self.dll)
- # we need to check dll anyway
+ self.name, dll = argument
+ if isinstance(dll, str):
+ self.dll = ctypes.CDLL(dll)
+ else:
+ self.dll = dll
+ if argsl:
+ self.paramflags = argsl.pop(0)
+ if argsl:
+ raise TypeError("Unknown constructor %s" % (args,))
+ # We need to check dll anyway
ptr = self._getfuncptr([], ctypes.c_int)
self._buffer = ptr.byptr()
+ return
- elif (sys.platform == 'win32' and
- len(args) >= 2 and isinstance(args[0], (int, long))):
- # A COM function call, by index
+ # A COM function call, by index
+ if (sys.platform == 'win32' and isinstance(argument, (int, long))
+ and argsl):
ffiargs, ffires = self._ffishapes(self._argtypes_, self._restype_)
- self._com_index = args[0] + 0x1000
- self.name = args[1]
- if len(args) > 2:
- self._paramflags = args[2]
- # XXX ignored iid = args[3]
+ self._com_index = argument + 0x1000
+ self.name = argsl.pop(0)
+ if argsl:
+ self.paramflags = argsl.pop(0)
+ if argsl:
+ self._com_iid = argsl.pop(0)
+ if argsl:
+ raise TypeError("Unknown constructor %s" % (args,))
+ return
- elif len(args) == 0:
- # Empty function object.
- # this is needed for casts
- self._buffer = _rawffi.Array('P')(1)
- return
- else:
- raise TypeError("Unknown constructor %s" % (args,))
+ raise TypeError("Unknown constructor %s" % (args,))
def _wrap_callable(self, to_call, argtypes):
def f(*args):
@@ -166,30 +250,31 @@
return to_call(*args)
return f
- def __call__(self, *args):
+ def __call__(self, *args, **kwargs):
+ argtypes = self._argtypes_
if self.callable is not None:
- if len(args) == len(self._argtypes_):
+ if len(args) == len(argtypes):
pass
elif self._flags_ & _rawffi.FUNCFLAG_CDECL:
- if len(args) < len(self._argtypes_):
- plural = len(self._argtypes_) > 1 and "s" or ""
+ if len(args) < len(argtypes):
+ plural = len(argtypes) > 1 and "s" or ""
raise TypeError(
"This function takes at least %d argument%s (%s given)"
- % (len(self._argtypes_), plural, len(args)))
+ % (len(argtypes), plural, len(args)))
else:
# For cdecl functions, we allow more actual arguments
# than the length of the argtypes tuple.
args = args[:len(self._argtypes_)]
else:
- plural = len(self._argtypes_) > 1 and "s" or ""
+ plural = len(argtypes) > 1 and "s" or ""
raise TypeError(
"This function takes %d argument%s (%s given)"
- % (len(self._argtypes_), plural, len(args)))
+ % (len(argtypes), plural, len(args)))
# check that arguments are convertible
## XXX Not as long as ctypes.cast is a callback function with
## py_object arguments...
- ## self._convert_args(self._argtypes_, args)
+ ## self._convert_args(argtypes, args, {})
try:
res = self.callable(*args)
@@ -201,22 +286,26 @@
if self._restype_ is not None:
return res
return
- argtypes = self._argtypes_
+
+ if argtypes is None:
+ argtypes = []
if self._com_index:
from ctypes import cast, c_void_p, POINTER
+ if not args:
+ raise ValueError(
+ "native COM method call without 'this' parameter"
+ )
thisarg = cast(args[0], POINTER(POINTER(c_void_p))).contents
argtypes = [c_void_p] + list(argtypes)
args = list(args)
args[0] = args[0].value
else:
thisarg = None
-
- if argtypes is None:
- argtypes = []
- args = self._convert_args(argtypes, args)
+
+ args, outargs = self._convert_args(argtypes, args, kwargs)
argtypes = [type(arg) for arg in args]
-
+
restype = self._restype_
funcptr = self._getfuncptr(argtypes, restype, thisarg)
if self._flags_ & _rawffi.FUNCFLAG_USE_ERRNO:
@@ -231,7 +320,27 @@
set_errno(_rawffi.get_errno())
if self._flags_ & _rawffi.FUNCFLAG_USE_LASTERROR:
set_last_error(_rawffi.get_last_error())
- result = self._build_result(restype, resbuffer, argtypes, args)
+
+ result = None
+ if self._com_index:
+ if resbuffer[0] & 0x80000000:
+ raise get_com_error(resbuffer[0],
+ self._com_iid, args[0])
+ else:
+ result = int(resbuffer[0])
+ elif restype is not None:
+ checker = getattr(self.restype, '_check_retval_', None)
+ if checker:
+ val = restype(resbuffer[0])
+ # the original ctypes seems to make the distinction between
+ # classes defining a new type, and their subclasses
+ if '_type_' in restype.__dict__:
+ val = val.value
+ result = checker(val)
+ elif not isinstance(restype, _CDataMeta):
+ result = restype(resbuffer[0])
+ else:
+ result = restype._CData_retval(resbuffer)
# The 'errcheck' protocol
if self._errcheck_:
@@ -244,8 +353,13 @@
if v is not args:
result = v
- return result
+ if not outargs:
+ return result
+ if len(outargs) == 1:
+ return outargs[0]
+
+ return tuple(outargs)
def _getfuncptr(self, argtypes, restype, thisarg=None):
if self._ptr is not None and argtypes is self._argtypes_:
@@ -268,14 +382,17 @@
raise ValueError("COM method call without VTable")
ptr = thisarg[self._com_index - 0x1000]
return _rawffi.FuncPtr(ptr, argshapes, resshape, self._flags_)
-
+
cdll = self.dll._handle
try:
return cdll.ptr(self.name, argshapes, resshape, self._flags_)
except AttributeError:
if self._flags_ & _rawffi.FUNCFLAG_CDECL:
raise
-
+ # Win64 has no stdcall calling conv, so it should also not have the
+ # name mangling of it.
+ if WIN64:
+ raise
# For stdcall, try mangled names:
# funcname -> _funcname@<n>
# where n is 0, 4, 8, 12, ..., 128
@@ -289,13 +406,12 @@
raise
@staticmethod
- def _conv_param(argtype, arg, index):
+ def _conv_param(argtype, arg):
from ctypes import c_char_p, c_wchar_p, c_void_p, c_int
if argtype is not None:
arg = argtype.from_param(arg)
if hasattr(arg, '_as_parameter_'):
arg = arg._as_parameter_
-
if isinstance(arg, _CData):
# The usual case when argtype is defined
cobj = arg
@@ -309,133 +425,91 @@
cobj = c_int(arg)
else:
raise TypeError("Don't know how to handle %s" % (arg,))
-
return cobj
- def _convert_args(self, argtypes, args):
- wrapped_args = []
- consumed = 0
+ def _convert_args(self, argtypes, args, kwargs, marker=object()):
+ callargs = []
+ outargs = []
+ total = len(args)
+ paramflags = self._paramflags
+
+ if self._com_index:
+ inargs_idx = 1
+ else:
+ inargs_idx = 0
+
+ if not paramflags and total < len(argtypes):
+ raise TypeError("not enough arguments")
for i, argtype in enumerate(argtypes):
- defaultvalue = None
- if i > 0 and self._paramflags is not None:
- paramflag = self._paramflags[i-1]
- if len(paramflag) == 2:
- idlflag, name = paramflag
- elif len(paramflag) == 3:
- idlflag, name, defaultvalue = paramflag
+ flag = 0
+ name = None
+ defval = marker
+ if paramflags:
+ paramflag = paramflags[i]
+ paramlen = len(paramflag)
+ name = None
+ if paramlen == 1:
+ flag = paramflag[0]
+ elif paramlen == 2:
+ flag, name = paramflag
+ elif paramlen == 3:
+ flag, name, defval = paramflag
+ flag = flag & PARAMFLAG_COMBINED
+ if flag == PARAMFLAG_FIN | PARAMFLAG_FLCID:
+ val = defval
+ if val is marker:
+ val = 0
+ wrapped = self._conv_param(argtype, val)
+ callargs.append(wrapped)
+ elif flag in (0, PARAMFLAG_FIN):
+ if inargs_idx < total:
+ val = args[inargs_idx]
+ inargs_idx += 1
+ elif kwargs and name in kwargs:
+ val = kwargs[name]
+ inargs_idx += 1
+ elif defval is not marker:
+ val = defval
+ elif name:
+ raise TypeError("required argument '%s' missing" % name)
+ else:
+ raise TypeError("not enough arguments")
+ wrapped = self._conv_param(argtype, val)
+ callargs.append(wrapped)
+ elif flag == PARAMFLAG_FOUT:
+ if defval is not marker:
+ outargs.append(defval)
+ wrapped = self._conv_param(argtype, defval)
+ else:
+ import ctypes
+ val = argtype._type_()
+ outargs.append(val)
+ wrapped = ctypes.byref(val)
+ callargs.append(wrapped)
else:
- idlflag = 0
- idlflag &= (PARAMFLAG_FIN | PARAMFLAG_FOUT | PARAMFLAG_FLCID)
+ raise ValueError("paramflag %d not yet implemented" % flag)
+ else:
+ try:
+ wrapped = self._conv_param(argtype, args[i])
+ except (UnicodeError, TypeError, ValueError), e:
+ raise ArgumentError(str(e))
+ callargs.append(wrapped)
+ inargs_idx += 1
- if idlflag in (0, PARAMFLAG_FIN):
- pass
- elif idlflag == PARAMFLAG_FOUT:
- import ctypes
- val = argtype._type_()
- wrapped = (val, ctypes.byref(val))
- wrapped_args.append(wrapped)
- continue
- elif idlflag == PARAMFLAG_FIN | PARAMFLAG_FLCID:
- # Always taken from defaultvalue if given,
- # else the integer 0.
- val = defaultvalue
- if val is None:
- val = 0
- wrapped = self._conv_param(argtype, val, consumed)
- wrapped_args.append(wrapped)
- continue
- else:
- raise NotImplementedError(
- "paramflags = %s" % (self._paramflags[i-1],))
-
- if consumed < len(args):
- arg = args[consumed]
- elif defaultvalue is not None:
- arg = defaultvalue
- else:
- raise TypeError("Not enough arguments")
-
- try:
- wrapped = self._conv_param(argtype, arg, consumed)
- except (UnicodeError, TypeError, ValueError), e:
- raise ArgumentError(str(e))
- wrapped_args.append(wrapped)
- consumed += 1
-
- if len(wrapped_args) < len(args):
- extra = args[len(wrapped_args):]
- argtypes = list(argtypes)
+ if len(callargs) < total:
+ extra = args[len(callargs):]
for i, arg in enumerate(extra):
try:
- wrapped = self._conv_param(None, arg, i)
+ wrapped = self._conv_param(None, arg)
except (UnicodeError, TypeError, ValueError), e:
raise ArgumentError(str(e))
- wrapped_args.append(wrapped)
- return wrapped_args
+ callargs.append(wrapped)
- def _build_result(self, restype, resbuffer, argtypes, argsandobjs):
- """Build the function result:
- If there is no OUT parameter, return the actual function result
- If there is one OUT parameter, return it
- If there are many OUT parameters, return a tuple"""
-
- retval = None
-
- if self._com_index:
- if resbuffer[0] & 0x80000000:
- raise get_com_error(resbuffer[0],
- self._com_iid, argsandobjs[0])
- else:
- retval = int(resbuffer[0])
- elif restype is not None:
- checker = getattr(self.restype, '_check_retval_', None)
- if checker:
- val = restype(resbuffer[0])
- # the original ctypes seems to make the distinction between
- # classes defining a new type, and their subclasses
- if '_type_' in restype.__dict__:
- val = val.value
- retval = checker(val)
- elif not isinstance(restype, _CDataMeta):
- retval = restype(resbuffer[0])
- else:
- retval = restype._CData_retval(resbuffer)
-
- results = []
- if self._paramflags:
- for argtype, obj, paramflag in zip(argtypes[1:], argsandobjs[1:],
- self._paramflags):
- if len(paramflag) == 2:
- idlflag, name = paramflag
- elif len(paramflag) == 3:
- idlflag, name, defaultvalue = paramflag
- else:
- idlflag = 0
- idlflag &= (PARAMFLAG_FIN | PARAMFLAG_FOUT | PARAMFLAG_FLCID)
-
- if idlflag in (0, PARAMFLAG_FIN):
- pass
- elif idlflag == PARAMFLAG_FOUT:
- val = obj.__ctypes_from_outparam__()
- results.append(val)
- elif idlflag == PARAMFLAG_FIN | PARAMFLAG_FLCID:
- pass
- else:
- raise NotImplementedError(
- "paramflags = %s" % (paramflag,))
-
- if results:
- if len(results) == 1:
- return results[0]
- else:
- return tuple(results)
-
- # No output parameter, return the actual function result.
- return retval
+ return callargs, outargs
def __nonzero__(self):
- return bool(self._buffer[0])
+ return self._com_index is not None or bool(self._buffer[0])
def __del__(self):
if self._needs_free:
diff --git a/lib_pypy/_ctypes/structure.py b/lib_pypy/_ctypes/structure.py
--- a/lib_pypy/_ctypes/structure.py
+++ b/lib_pypy/_ctypes/structure.py
@@ -134,7 +134,7 @@
__setattr__ = struct_setattr
def from_address(self, address):
- instance = self.__new__(self)
+ instance = StructOrUnion.__new__(self)
instance.__dict__['_buffer'] = self._ffistruct.fromaddress(address)
return instance
@@ -156,7 +156,7 @@
return _CDataMeta.from_param(self, value)
def _CData_output(self, resarray, base=None, index=-1):
- res = self.__new__(self)
+ res = StructOrUnion.__new__(self)
ffistruct = self._ffistruct.fromaddress(resarray.buffer)
res.__dict__['_buffer'] = ffistruct
res.__dict__['_base'] = base
@@ -164,7 +164,7 @@
return res
def _CData_retval(self, resbuffer):
- res = self.__new__(self)
+ res = StructOrUnion.__new__(self)
res.__dict__['_buffer'] = resbuffer
res.__dict__['_base'] = None
res.__dict__['_index'] = -1
diff --git a/lib_pypy/_scproxy.py b/lib_pypy/_scproxy.py
new file mode 100644
--- /dev/null
+++ b/lib_pypy/_scproxy.py
@@ -0,0 +1,130 @@
+"""Helper methods for urllib to fetch the proxy configuration settings using
+the SystemConfiguration framework.
+
+"""
+import sys
+if sys.platform != 'darwin':
+ raise ImportError('Requires Mac OS X')
+
+from ctypes import c_int32, c_int64, c_void_p, c_char_p, c_int, cdll
+from ctypes import pointer, create_string_buffer
+from ctypes.util import find_library
+
+kCFNumberSInt32Type = 3
+kCFStringEncodingUTF8 = 134217984
+
+def _CFSetup():
+ sc = cdll.LoadLibrary(find_library("SystemConfiguration"))
+ cf = cdll.LoadLibrary(find_library("CoreFoundation"))
+ sctable = [
+ ('SCDynamicStoreCopyProxies', [c_void_p], c_void_p),
+ ]
+ cftable = [
+ ('CFArrayGetCount', [c_void_p], c_int64),
+ ('CFArrayGetValueAtIndex', [c_void_p, c_int64], c_void_p),
+ ('CFDictionaryGetValue', [c_void_p, c_void_p], c_void_p),
+ ('CFStringCreateWithCString', [c_void_p, c_char_p, c_int32], c_void_p),
+ ('CFStringGetLength', [c_void_p], c_int32),
+ ('CFStringGetCString', [c_void_p, c_char_p, c_int32, c_int32], c_int32),
+ ('CFNumberGetValue', [c_void_p, c_int, c_void_p], c_int32),
+ ('CFRelease', [c_void_p], None),
+ ]
+ scconst = [
+ 'kSCPropNetProxiesExceptionsList',
+ 'kSCPropNetProxiesExcludeSimpleHostnames',
+ 'kSCPropNetProxiesHTTPEnable',
+ 'kSCPropNetProxiesHTTPProxy',
+ 'kSCPropNetProxiesHTTPPort',
+ 'kSCPropNetProxiesHTTPSEnable',
+ 'kSCPropNetProxiesHTTPSProxy',
+ 'kSCPropNetProxiesHTTPSPort',
+ 'kSCPropNetProxiesFTPEnable',
+ 'kSCPropNetProxiesFTPProxy',
+ 'kSCPropNetProxiesFTPPort',
+ 'kSCPropNetProxiesGopherEnable',
+ 'kSCPropNetProxiesGopherProxy',
+ 'kSCPropNetProxiesGopherPort',
+ ]
+ class CFProxy(object):
+ def __init__(self):
+ for mod, table in [(sc, sctable), (cf, cftable)]:
+ for fname, argtypes, restype in table:
+ func = getattr(mod, fname)
+ func.argtypes = argtypes
+ func.restype = restype
+ setattr(self, fname, func)
+ for k in scconst:
+ v = None
+ try:
+ v = c_void_p.in_dll(sc, k)
+ except ValueError:
+ v = None
+ setattr(self, k, v)
+ return CFProxy()
+ffi = _CFSetup()
+
+def cfstring_to_pystring(value):
+ length = (ffi.CFStringGetLength(value) * 4) + 1
+ buff = create_string_buffer(length)
+ ffi.CFStringGetCString(value, buff, length * 4, kCFStringEncodingUTF8)
+ return unicode(buff.value, 'utf8')
+
+def cfnum_to_int32(num):
+ result_ptr = pointer(c_int32(0))
+ ffi.CFNumberGetValue(num, kCFNumberSInt32Type, result_ptr)
+ return result_ptr[0]
+
+def _get_proxy_settings():
+ result = {'exclude_simple': False}
+ cfdct = ffi.SCDynamicStoreCopyProxies(None)
+ if not cfdct:
+ return result
+ try:
+ k = ffi.kSCPropNetProxiesExcludeSimpleHostnames
+ if k:
+ cfnum = ffi.CFDictionaryGetValue(cfdct, k)
+ if cfnum:
+ result['exclude_simple'] = bool(cfnum_to_int32(cfnum))
+ k = ffi.kSCPropNetProxiesExceptionsList
+ if k:
+ cfarr = ffi.CFDictionaryGetValue(cfdct, k)
+ if cfarr:
+ lst = []
+ for i in range(ffi.CFArrayGetCount(cfarr)):
+ cfstr = ffi.CFArrayGetValueAtIndex(cfarr, i)
+ if cfstr:
+ v = cfstring_to_pystring(cfstr)
+ else:
+ v = None
+ lst.append(v)
+ result['exceptions'] = lst
+ return result
+ finally:
+ ffi.CFRelease(cfdct)
+
+def _get_proxies():
+ result = {}
+ cfdct = ffi.SCDynamicStoreCopyProxies(None)
+ if not cfdct:
+ return result
+ try:
+ for proto in 'HTTP', 'HTTPS', 'FTP', 'Gopher':
+ enabled_key = getattr(ffi, 'kSCPropNetProxies' + proto + 'Enable')
+ proxy_key = getattr(ffi, 'kSCPropNetProxies' + proto + 'Proxy')
+ port_key = getattr(ffi, 'kSCPropNetProxies' + proto + 'Port')
+ cfnum = ffi.CFDictionaryGetValue(cfdct, enabled_key)
+ if cfnum and cfnum_to_int32(cfnum):
+ cfhoststr = ffi.CFDictionaryGetValue(cfdct, proxy_key)
+ cfportnum = ffi.CFDictionaryGetValue(cfdct, port_key)
+ if cfhoststr:
+ host = cfstring_to_pystring(cfhoststr)
+ if host:
+ if cfportnum:
+ port = cfnum_to_int32(cfportnum)
+ v = u'http://%s:%d' % (host, port)
+ else:
+ v = u'http://%s' % (host,)
+ result[proto.lower()] = v
+ return result
+ finally:
+ ffi.CFRelease(cfdct)
diff --git a/lib_pypy/_sqlite3.py b/lib_pypy/_sqlite3.py
--- a/lib_pypy/_sqlite3.py
+++ b/lib_pypy/_sqlite3.py
@@ -228,6 +228,9 @@
factory = kwargs.get("factory", Connection)
return factory(database, **kwargs)
+def unicode_text_factory(x):
+ return unicode(x, 'utf-8')
+
class Connection(object):
def __init__(self, database, isolation_level="", detect_types=0, timeout=None, *args, **kwargs):
self.db = c_void_p()
@@ -237,7 +240,7 @@
timeout = int(timeout * 1000) # pysqlite2 uses timeout in seconds
sqlite.sqlite3_busy_timeout(self.db, timeout)
- self.text_factory = lambda x: unicode(x, "utf-8")
+ self.text_factory = unicode_text_factory
self.closed = False
self.statements = []
self.statement_counter = 0
@@ -245,6 +248,8 @@
self._isolation_level = isolation_level
self.detect_types = detect_types
+ self.cursors = []
+
self.Error = Error
self.Warning = Warning
self.InterfaceError = InterfaceError
@@ -307,6 +312,12 @@
"The object was created in thread id %d and this is thread id %d",
self.thread_ident, thread_get_ident())
+ def _reset_cursors(self):
+ for cursor_ref in self.cursors:
+ cursor = cursor_ref()
+ if cursor:
+ cursor.reset = True
+
def cursor(self, factory=None):
self._check_thread()
self._check_closed()
@@ -421,6 +432,7 @@
raise self._get_exception(ret)
finally:
sqlite.sqlite3_finalize(statement)
+ self._reset_cursors()
def _check_closed(self):
if getattr(self, 'closed', True):
@@ -450,6 +462,7 @@
self.closed = True
ret = sqlite.sqlite3_close(self.db)
+ self._reset_cursors()
if ret != SQLITE_OK:
raise self._get_exception(ret)
@@ -536,7 +549,7 @@
self._check_closed()
try:
c_closure, _ = self.func_cache[callback]
- except KeyError:
+ except KeyError:
def closure(context, nargs, c_params):
function_callback(callback, context, nargs, c_params)
c_closure = FUNC(closure)
@@ -547,7 +560,7 @@
cast(None, STEP),
cast(None, FINAL))
if ret != SQLITE_OK:
- raise self._get_exception(ret)
+ raise self.OperationalError("Error creating function")
def create_aggregate(self, name, num_args, cls):
self._check_thread()
@@ -629,13 +642,14 @@
raise TypeError
con._check_thread()
con._check_closed()
+ con.cursors.append(weakref.ref(self))
self.connection = con
self._description = None
self.arraysize = 1
- self.text_factory = con.text_factory
self.row_factory = None
self.rowcount = -1
self.statement = None
+ self.reset = False
def _check_closed(self):
if not getattr(self, 'connection', None):
@@ -645,6 +659,7 @@
def execute(self, sql, params=None):
self._description = None
+ self.reset = False
if type(sql) is unicode:
sql = sql.encode("utf-8")
self._check_closed()
@@ -665,8 +680,12 @@
raise self.connection._get_exception(ret)
if self.statement.kind == "DQL":
- self.statement._readahead()
- self.statement._build_row_cast_map()
+ if ret == SQLITE_ROW:
+ self.statement._build_row_cast_map()
+ self.statement._readahead()
+ else:
+ self.statement.item = None
+ self.statement.exhausted = True
if self.statement.kind in ("DML", "DDL"):
self.statement.reset()
@@ -679,6 +698,7 @@
def executemany(self, sql, many_params):
self._description = None
+ self.reset = False
if type(sql) is unicode:
sql = sql.encode("utf-8")
self._check_closed()
@@ -700,6 +720,7 @@
def executescript(self, sql):
self._description = None
+ self.reset = False
if type(sql) is unicode:
sql = sql.encode("utf-8")
self._check_closed()
@@ -736,8 +757,17 @@
def __iter__(self):
return self.statement
+ def _check_reset(self):
+ if self.reset:
+ raise self.connection.InterfaceError("Cursor needed to be reset because "
+ "of commit/rollback and can "
+ "no longer be fetched from.")
+
+ # do all statements
def fetchone(self):
self._check_closed()
+ self._check_reset()
+
if self.statement is None:
return None
@@ -750,6 +780,7 @@
def fetchmany(self, size=None):
self._check_closed()
+ self._check_reset()
if self.statement is None:
return []
if size is None:
@@ -763,6 +794,7 @@
def fetchall(self):
self._check_closed()
+ self._check_reset()
if self.statement is None:
return []
return list(self.statement)
@@ -782,6 +814,7 @@
if self.statement:
self.statement.reset()
self.statement = None
+ self.connection.cursors.remove(weakref.ref(self))
self.connection = None
def setinputsizes(self, *args):
@@ -829,7 +862,7 @@
def _build_row_cast_map(self):
self.row_cast_map = []
- for i in range(sqlite.sqlite3_column_count(self.statement)):
+ for i in xrange(sqlite.sqlite3_column_count(self.statement)):
converter = None
if self.con.detect_types & PARSE_COLNAMES:
@@ -854,14 +887,23 @@
self.row_cast_map.append(converter)
+ def _check_decodable(self, param):
+ if self.con.text_factory in (unicode, OptimizedUnicode, unicode_text_factory):
+ for c in param:
+ if ord(c) & 0x80 != 0:
+ raise self.con.ProgrammingError(
+ "You must not use 8-bit bytestrings unless "
+ "you use a text_factory that can interpret "
+ "8-bit bytestrings (like text_factory = str). "
+ "It is highly recommended that you instead "
+ "just switch your application to Unicode strings.")
+
def set_param(self, idx, param):
cvt = converters.get(type(param))
if cvt is not None:
cvt = param = cvt(param)
- adapter = adapters.get((type(param), PrepareProtocol), None)
- if adapter is not None:
- param = adapter(param)
+ param = adapt(param)
if param is None:
sqlite.sqlite3_bind_null(self.statement, idx)
@@ -873,6 +915,7 @@
elif type(param) is float:
sqlite.sqlite3_bind_double(self.statement, idx, param)
elif isinstance(param, str):
+ self._check_decodable(param)
sqlite.sqlite3_bind_text(self.statement, idx, param, -1, SQLITE_TRANSIENT)
elif isinstance(param, unicode):
param = param.encode("utf-8")
@@ -902,8 +945,8 @@
if len(params) != sqlite.sqlite3_bind_parameter_count(self.statement):
raise ProgrammingError("wrong number of arguments")
- for idx, param in enumerate(params):
- self.set_param(idx+1, param)
+ for i in range(len(params)):
+ self.set_param(i+1, params[i])
else:
for idx in range(1, sqlite.sqlite3_bind_parameter_count(self.statement) + 1):
param_name = sqlite.sqlite3_bind_parameter_name(self.statement, idx)
@@ -942,7 +985,8 @@
self.column_count = sqlite.sqlite3_column_count(self.statement)
row = []
for i in xrange(self.column_count):
- typ = sqlite.sqlite3_column_type(self.statement, i)
+ typ = sqlite.sqlite3_column_type(self.statement, i)
+
converter = self.row_cast_map[i]
if converter is None:
if typ == SQLITE_INTEGER:
@@ -959,7 +1003,7 @@
val = None
elif typ == SQLITE_TEXT:
val = sqlite.sqlite3_column_text(self.statement, i)
- val = self.cur().text_factory(val)
+ val = self.con.text_factory(val)
else:
blob = sqlite.sqlite3_column_blob(self.statement, i)
if not blob:
@@ -1069,12 +1113,6 @@
return 1
return 0
-def register_adapter(typ, callable):
- adapters[typ, PrepareProtocol] = callable
-
-def register_converter(name, callable):
- converters[name.upper()] = callable
-
def _convert_params(con, nargs, params):
_params = []
for i in range(nargs):
@@ -1155,6 +1193,12 @@
class PrepareProtocol(object):
pass
+def register_adapter(typ, callable):
+ adapters[typ, PrepareProtocol] = callable
+
+def register_converter(name, callable):
+ converters[name.upper()] = callable
+
def register_adapters_and_converters():
def adapt_date(val):
return val.isoformat()
@@ -1184,11 +1228,39 @@
register_converter("date", convert_date)
register_converter("timestamp", convert_timestamp)
+def adapt(val, proto=PrepareProtocol):
+ # look for an adapter in the registry
+ adapter = adapters.get((type(val), proto), None)
+ if adapter is not None:
+ return adapter(val)
+
+ # try to have the protocol adapt this object
+ if hasattr(proto, '__adapt__'):
+ try:
+ adapted = proto.__adapt__(val)
+ except TypeError:
+ pass
+ else:
+ if adapted is not None:
+ return adapted
+
+ # and finally try to have the object adapt itself
+ if hasattr(val, '__conform__'):
+ try:
+ adapted = val.__conform__(proto)
+ except TypeError:
+ pass
+ else:
+ if adapted is not None:
+ return adapted
+
+ return val
+
+register_adapters_and_converters()
+
def OptimizedUnicode(s):
try:
val = unicode(s, "ascii").encode("ascii")
except UnicodeDecodeError:
val = unicode(s, "utf-8")
return val
-
-register_adapters_and_converters()
diff --git a/lib_pypy/conftest.py b/lib_pypy/conftest.py
new file mode 100644
--- /dev/null
+++ b/lib_pypy/conftest.py
@@ -0,0 +1,2 @@
+
+from pypy.conftest import *
diff --git a/lib_pypy/ctypes_config_cache/test/test_cache.py b/lib_pypy/ctypes_config_cache/test/test_cache.py
--- a/lib_pypy/ctypes_config_cache/test/test_cache.py
+++ b/lib_pypy/ctypes_config_cache/test/test_cache.py
@@ -11,6 +11,7 @@
dir=True)
tmpdir.join('dumpcache.py').write(dirpath.join('dumpcache.py').read())
path = sys.path[:]
+ sys.modules.pop('dumpcache', None)
try:
sys.path.insert(0, str(tmpdir))
execfile(str(filepath), {})
diff --git a/lib_pypy/distributed/test/test_distributed.py b/lib_pypy/distributed/test/test_distributed.py
--- a/lib_pypy/distributed/test/test_distributed.py
+++ b/lib_pypy/distributed/test/test_distributed.py
@@ -4,11 +4,7 @@
from pypy.conftest import gettestobjspace
import sys
-
-class AppTestNoProxy(object):
- disabled = True
- def test_init(self):
- raises(ImportError, "import distributed")
+import pytest
class AppTestDistributed(object):
def setup_class(cls):
diff --git a/py/__init__.py b/py/__init__.py
--- a/py/__init__.py
+++ b/py/__init__.py
@@ -4,68 +4,52 @@
this module uses apipkg.py for lazy-loading sub modules
and classes. The initpkg-dictionary below specifies
name->value mappings where value can be another namespace
-dictionary or an import path.
+dictionary or an import path.
(c) Holger Krekel and others, 2004-2010
"""
-__version__ = version = "1.3.1"
+__version__ = '1.4.3.dev0'
-import py.apipkg
+from py import _apipkg
-py.apipkg.initpkg(__name__, dict(
+# so that py.error.* instances are picklable
+import sys
+sys.modules['py.error'] = _apipkg.AliasModule("py.error", "py._error", 'error')
+
+_apipkg.initpkg(__name__, attr={'_apipkg': _apipkg}, exportdefs={
# access to all standard lib modules
- std = '._std:std',
+ 'std': '._std:std',
# access to all posix errno's as classes
- error = '._error:error',
+ 'error': '._error:error',
- _pydir = '.__metainfo:pydir',
- version = 'py:__version__', # backward compatibility
+ '_pydir' : '.__metainfo:pydir',
+ 'version': 'py:__version__', # backward compatibility
- cmdline = {
- 'pytest': '._cmdline.pytest:main',
- 'pylookup': '._cmdline.pylookup:main',
- 'pycountloc': '._cmdline.pycountlog:main',
- 'pylookup': '._cmdline.pylookup:main',
- 'pycountloc': '._cmdline.pycountloc:main',
- 'pycleanup': '._cmdline.pycleanup:main',
- 'pywhich' : '._cmdline.pywhich:main',
- 'pysvnwcrevert' : '._cmdline.pysvnwcrevert:main',
- 'pyconvert_unittest' : '._cmdline.pyconvert_unittest:main',
- },
-
- test = {
- # helpers for use from test functions or collectors
- '__onfirstaccess__' : '._test.config:onpytestaccess',
- '__doc__' : '._test:__doc__',
- # configuration/initialization related test api
- 'config' : '._test.config:config_per_process',
- 'ensuretemp' : '._test.config:ensuretemp',
- 'collect': {
- 'Collector' : '._test.collect:Collector',
- 'Directory' : '._test.collect:Directory',
- 'File' : '._test.collect:File',
- 'Item' : '._test.collect:Item',
- 'Module' : '._test.pycollect:Module',
- 'Class' : '._test.pycollect:Class',
- 'Instance' : '._test.pycollect:Instance',
- 'Generator' : '._test.pycollect:Generator',
- 'Function' : '._test.pycollect:Function',
- '_fillfuncargs' : '._test.funcargs:fillfuncargs',
- },
- 'cmdline': {
- 'main' : '._test.cmdline:main', # backward compat
- },
- },
+ # pytest-2.0 has a flat namespace, we use alias modules
+ # to keep old references compatible
+ 'test' : 'pytest',
+ 'test.collect' : 'pytest',
+ 'test.cmdline' : 'pytest',
# hook into the top-level standard library
- process = {
+ 'process' : {
'__doc__' : '._process:__doc__',
'cmdexec' : '._process.cmdexec:cmdexec',
'kill' : '._process.killproc:kill',
'ForkedFunc' : '._process.forkedfunc:ForkedFunc',
},
- path = {
+ 'apipkg' : {
+ 'initpkg' : '._apipkg:initpkg',
+ 'ApiModule' : '._apipkg:ApiModule',
+ },
+
+ 'iniconfig' : {
+ 'IniConfig' : '._iniconfig:IniConfig',
+ 'ParseError' : '._iniconfig:ParseError',
+ },
+
+ 'path' : {
'__doc__' : '._path:__doc__',
'svnwc' : '._path.svnwc:SvnWCCommandPath',
'svnurl' : '._path.svnurl:SvnCommandPath',
@@ -73,18 +57,8 @@
'SvnAuth' : '._path.svnwc:SvnAuth',
},
- # some nice slightly magic APIs
- magic = {
- 'invoke' : '._code.oldmagic:invoke',
- 'revoke' : '._code.oldmagic:revoke',
- 'patch' : '._code.oldmagic:patch',
- 'revert' : '._code.oldmagic:revert',
- 'autopath' : '._path.local:autopath',
- 'AssertionError' : '._code.oldmagic2:AssertionError',
- },
-
# python inspection/code-generation API
- code = {
+ 'code' : {
'__doc__' : '._code:__doc__',
'compile' : '._code.source:compile_',
'Source' : '._code.source:Source',
@@ -99,18 +73,22 @@
'_AssertionError' : '._code.assertion:AssertionError',
'_reinterpret_old' : '._code.assertion:reinterpret_old',
'_reinterpret' : '._code.assertion:reinterpret',
+ '_reprcompare' : '._code.assertion:_reprcompare',
},
# backports and additions of builtins
- builtin = {
+ 'builtin' : {
'__doc__' : '._builtin:__doc__',
'enumerate' : '._builtin:enumerate',
'reversed' : '._builtin:reversed',
'sorted' : '._builtin:sorted',
+ 'any' : '._builtin:any',
+ 'all' : '._builtin:all',
'set' : '._builtin:set',
'frozenset' : '._builtin:frozenset',
'BaseException' : '._builtin:BaseException',
'GeneratorExit' : '._builtin:GeneratorExit',
+ '_sysex' : '._builtin:_sysex',
'print_' : '._builtin:print_',
'_reraise' : '._builtin:_reraise',
'_tryimport' : '._builtin:_tryimport',
@@ -128,7 +106,7 @@
},
# input-output helping
- io = {
+ 'io' : {
'__doc__' : '._io:__doc__',
'dupfile' : '._io.capture:dupfile',
'TextIO' : '._io.capture:TextIO',
@@ -137,13 +115,13 @@
'StdCapture' : '._io.capture:StdCapture',
'StdCaptureFD' : '._io.capture:StdCaptureFD',
'TerminalWriter' : '._io.terminalwriter:TerminalWriter',
- 'ansi_print' : '._io.terminalwriter:ansi_print',
+ 'ansi_print' : '._io.terminalwriter:ansi_print',
'get_terminal_width' : '._io.terminalwriter:get_terminal_width',
'saferepr' : '._io.saferepr:saferepr',
},
# small and mean xml/html generation
- xml = {
+ 'xml' : {
'__doc__' : '._xmlgen:__doc__',
'html' : '._xmlgen:html',
'Tag' : '._xmlgen:Tag',
@@ -152,7 +130,7 @@
'escape' : '._xmlgen:escape',
},
- log = {
+ 'log' : {
# logging API ('producers' and 'consumers' connected via keywords)
'__doc__' : '._log:__doc__',
'_apiwarn' : '._log.warning:_apiwarn',
@@ -166,12 +144,4 @@
'Syslog' : '._log.log:Syslog',
},
- # compatibility modules (deprecated)
- compat = {
- '__doc__' : '._compat:__doc__',
- 'doctest' : '._compat.dep_doctest:doctest',
- 'optparse' : '._compat.dep_optparse:optparse',
- 'textwrap' : '._compat.dep_textwrap:textwrap',
- 'subprocess' : '._compat.dep_subprocess:subprocess',
- },
-))
+})
diff --git a/py/_apipkg.py b/py/_apipkg.py
new file mode 100644
--- /dev/null
+++ b/py/_apipkg.py
@@ -0,0 +1,167 @@
+"""
+apipkg: control the exported namespace of a python package.
+
+see http://pypi.python.org/pypi/apipkg
+
+(c) holger krekel, 2009 - MIT license
+"""
+import os
+import sys
+from types import ModuleType
+
+__version__ = '1.2.dev6'
+
+def initpkg(pkgname, exportdefs, attr=dict()):
+ """ initialize given package from the export definitions. """
+ oldmod = sys.modules.get(pkgname)
+ d = {}
+ f = getattr(oldmod, '__file__', None)
+ if f:
+ f = os.path.abspath(f)
+ d['__file__'] = f
+ if hasattr(oldmod, '__version__'):
+ d['__version__'] = oldmod.__version__
+ if hasattr(oldmod, '__loader__'):
+ d['__loader__'] = oldmod.__loader__
+ if hasattr(oldmod, '__path__'):
+ d['__path__'] = [os.path.abspath(p) for p in oldmod.__path__]
+ if '__doc__' not in exportdefs and getattr(oldmod, '__doc__', None):
+ d['__doc__'] = oldmod.__doc__
+ d.update(attr)
+ if hasattr(oldmod, "__dict__"):
+ oldmod.__dict__.update(d)
+ mod = ApiModule(pkgname, exportdefs, implprefix=pkgname, attr=d)
+ sys.modules[pkgname] = mod
+
+def importobj(modpath, attrname):
+ module = __import__(modpath, None, None, ['__doc__'])
+ if not attrname:
+ return module
+
+ retval = module
+ names = attrname.split(".")
+ for x in names:
+ retval = getattr(retval, x)
+ return retval
+
+class ApiModule(ModuleType):
+ def __docget(self):
+ try:
+ return self.__doc
+ except AttributeError:
+ if '__doc__' in self.__map__:
+ return self.__makeattr('__doc__')
+ def __docset(self, value):
+ self.__doc = value
+ __doc__ = property(__docget, __docset)
+
+ def __init__(self, name, importspec, implprefix=None, attr=None):
+ self.__name__ = name
+ self.__all__ = [x for x in importspec if x != '__onfirstaccess__']
+ self.__map__ = {}
+ self.__implprefix__ = implprefix or name
+ if attr:
+ for name, val in attr.items():
+ #print "setting", self.__name__, name, val
+ setattr(self, name, val)
+ for name, importspec in importspec.items():
+ if isinstance(importspec, dict):
+ subname = '%s.%s'%(self.__name__, name)
+ apimod = ApiModule(subname, importspec, implprefix)
+ sys.modules[subname] = apimod
+ setattr(self, name, apimod)
+ else:
+ parts = importspec.split(':')
+ modpath = parts.pop(0)
+ attrname = parts and parts[0] or ""
+ if modpath[0] == '.':
+ modpath = implprefix + modpath
+
+ if not attrname:
+ subname = '%s.%s'%(self.__name__, name)
+ apimod = AliasModule(subname, modpath)
+ sys.modules[subname] = apimod
+ if '.' not in name:
+ setattr(self, name, apimod)
+ else:
+ self.__map__[name] = (modpath, attrname)
+
+ def __repr__(self):
+ l = []
+ if hasattr(self, '__version__'):
+ l.append("version=" + repr(self.__version__))
+ if hasattr(self, '__file__'):
+ l.append('from ' + repr(self.__file__))
+ if l:
+ return '<ApiModule %r %s>' % (self.__name__, " ".join(l))
+ return '<ApiModule %r>' % (self.__name__,)
+
+ def __makeattr(self, name):
+ """lazily compute value for name or raise AttributeError if unknown."""
+ #print "makeattr", self.__name__, name
+ target = None
+ if '__onfirstaccess__' in self.__map__:
+ target = self.__map__.pop('__onfirstaccess__')
+ importobj(*target)()
+ try:
+ modpath, attrname = self.__map__[name]
+ except KeyError:
+ if target is not None and name != '__onfirstaccess__':
+ # retry, onfirstaccess might have set attrs
+ return getattr(self, name)
+ raise AttributeError(name)
+ else:
+ result = importobj(modpath, attrname)
+ setattr(self, name, result)
+ try:
+ del self.__map__[name]
+ except KeyError:
+ pass # in a recursive-import situation a double-del can happen
+ return result
+
+ __getattr__ = __makeattr
+
+ def __dict__(self):
+ # force all the content of the module to be loaded when __dict__ is read
+ dictdescr = ModuleType.__dict__['__dict__']
+ dict = dictdescr.__get__(self)
+ if dict is not None:
+ hasattr(self, 'some')
+ for name in self.__all__:
+ try:
+ self.__makeattr(name)
+ except AttributeError:
+ pass
+ return dict
+ __dict__ = property(__dict__)
+
+
+def AliasModule(modname, modpath, attrname=None):
+ mod = []
+
+ def getmod():
+ if not mod:
+ x = importobj(modpath, None)
+ if attrname is not None:
+ x = getattr(x, attrname)
+ mod.append(x)
+ return mod[0]
+
+ class AliasModule(ModuleType):
+
+ def __repr__(self):
+ x = modpath
+ if attrname:
+ x += "." + attrname
+ return '<AliasModule %r for %r>' % (modname, x)
+
+ def __getattribute__(self, name):
+ return getattr(getmod(), name)
+
+ def __setattr__(self, name, value):
+ setattr(getmod(), name, value)
+
+ def __delattr__(self, name):
+ delattr(getmod(), name)
+
+ return AliasModule(modname)
diff --git a/py/_builtin.py b/py/_builtin.py
--- a/py/_builtin.py
+++ b/py/_builtin.py
@@ -36,6 +36,24 @@
return self.remaining
try:
+ any = any
+except NameError:
+ def any(iterable):
+ for x in iterable:
+ if x:
+ return True
+ return False
+
+try:
+ all = all
+except NameError:
+ def all(iterable):
+ for x in iterable:
+ if not x:
+ return False
+ return True
+
+try:
sorted = sorted
except NameError:
builtin_cmp = cmp # need to use cmp as keyword arg
@@ -67,10 +85,10 @@
try:
set, frozenset = set, frozenset
except NameError:
- from sets import set, frozenset
+ from sets import set, frozenset
# pass through
-enumerate = enumerate
+enumerate = enumerate
try:
BaseException = BaseException
@@ -87,12 +105,14 @@
pass
GeneratorExit.__module__ = 'exceptions'
+_sysex = (KeyboardInterrupt, SystemExit, MemoryError, GeneratorExit)
+
if sys.version_info >= (3, 0):
exec ("print_ = print ; exec_=exec")
import builtins
- # some backward compatibility helpers
- _basestring = str
+ # some backward compatibility helpers
+ _basestring = str
def _totext(obj, encoding=None):
if isinstance(obj, bytes):
obj = obj.decode(encoding)
@@ -100,9 +120,9 @@
obj = str(obj)
return obj
- def _isbytes(x):
+ def _isbytes(x):
return isinstance(x, bytes)
- def _istext(x):
+ def _istext(x):
return isinstance(x, str)
def _getimself(function):
@@ -135,13 +155,13 @@
else:
import __builtin__ as builtins
- _totext = unicode
+ _totext = unicode
_basestring = basestring
execfile = execfile
callable = callable
- def _isbytes(x):
+ def _isbytes(x):
return isinstance(x, str)
- def _istext(x):
+ def _istext(x):
return isinstance(x, unicode)
def _getimself(function):
@@ -157,7 +177,7 @@
return getattr(function, "func_code", None)
def print_(*args, **kwargs):
- """ minimal backport of py3k print statement. """
+ """ minimal backport of py3k print statement. """
sep = ' '
if 'sep' in kwargs:
sep = kwargs.pop('sep')
@@ -177,24 +197,22 @@
file.write(end)
def exec_(obj, globals=None, locals=None):
- """ minimal backport of py3k exec statement. """
+ """ minimal backport of py3k exec statement. """
__tracebackhide__ = True
- if globals is None:
+ if globals is None:
frame = sys._getframe(1)
- globals = frame.f_globals
+ globals = frame.f_globals
if locals is None:
locals = frame.f_locals
elif locals is None:
locals = globals
- exec2(obj, globals, locals)
+ exec2(obj, globals, locals)
if sys.version_info >= (3,0):
- exec ("""
-def _reraise(cls, val, tb):
- __tracebackhide__ = True
- assert hasattr(val, '__traceback__')
- raise val
-""")
+ def _reraise(cls, val, tb):
+ __tracebackhide__ = True
+ assert hasattr(val, '__traceback__')
+ raise val
else:
exec ("""
def _reraise(cls, val, tb):
@@ -202,11 +220,11 @@
raise cls, val, tb
def exec2(obj, globals, locals):
__tracebackhide__ = True
- exec obj in globals, locals
+ exec obj in globals, locals
""")
def _tryimport(*names):
- """ return the first successfully imported module. """
+ """ return the first successfully imported module. """
assert names
for name in names:
try:
diff --git a/py/_cmdline/__init__.py b/py/_cmdline/__init__.py
deleted file mode 100644
--- a/py/_cmdline/__init__.py
+++ /dev/null
@@ -1,1 +0,0 @@
-#
diff --git a/py/_cmdline/pycleanup.py b/py/_cmdline/pycleanup.py
deleted file mode 100755
--- a/py/_cmdline/pycleanup.py
+++ /dev/null
@@ -1,86 +0,0 @@
-#!/usr/bin/env python
-
-"""\
-py.cleanup [PATH] ...
-
-Delete typical python development related files recursively under the specified PATH (which defaults to the current working directory). Don't follow links and don't recurse into directories with a dot. Optionally remove setup.py related files and empty
-directories.
-
-"""
-import py
-import sys, subprocess
-
-def main():
- parser = py.std.optparse.OptionParser(usage=__doc__)
- parser.add_option("-e", metavar="ENDING",
- dest="endings", default=[".pyc", "$py.class"], action="append",
- help=("(multi) recursively remove files with the given ending."
- " '.pyc' and '$py.class' are in the default list."))
- parser.add_option("-d", action="store_true", dest="removedir",
- help="remove empty directories.")
- parser.add_option("-s", action="store_true", dest="setup",
- help="remove 'build' and 'dist' directories next to setup.py files")
- parser.add_option("-a", action="store_true", dest="all",
- help="synonym for '-S -d -e pip-log.txt'")
- parser.add_option("-n", "--dryrun", dest="dryrun", default=False,
- action="store_true",
- help="don't actually delete but display would-be-removed filenames.")
- (options, args) = parser.parse_args()
-
- Cleanup(options, args).main()
-
-class Cleanup:
- def __init__(self, options, args):
- if not args:
- args = ["."]
- self.options = options
- self.args = [py.path.local(x) for x in args]
- if options.all:
- options.setup = True
- options.removedir = True
- options.endings.append("pip-log.txt")
-
- def main(self):
- if self.options.setup:
- for arg in self.args:
- self.setupclean(arg)
-
- for path in self.args:
- py.builtin.print_("cleaning path", path,
- "of extensions", self.options.endings)
- for x in path.visit(self.shouldremove, self.recursedir):
- self.remove(x)
- if self.options.removedir:
- for x in path.visit(lambda x: x.check(dir=1), self.recursedir):
- if not x.listdir():
- self.remove(x)
-
- def shouldremove(self, p):
- for ending in self.options.endings:
- if p.basename.endswith(ending):
- return True
-
- def recursedir(self, path):
- return path.check(dotfile=0, link=0)
-
- def remove(self, path):
- if not path.check():
- return
- if self.options.dryrun:
- py.builtin.print_("would remove", path)
- else:
- py.builtin.print_("removing", path)
- path.remove()
-
- def XXXcallsetup(self, setup, *args):
- old = setup.dirpath().chdir()
- try:
- subprocess.call([sys.executable, str(setup)] + list(args))
- finally:
- old.chdir()
-
- def setupclean(self, path):
- for x in path.visit("setup.py", self.recursedir):
- basepath = x.dirpath()
- self.remove(basepath / "build")
- self.remove(basepath / "dist")
diff --git a/py/_cmdline/pyconvert_unittest.py b/py/_cmdline/pyconvert_unittest.py
deleted file mode 100644
--- a/py/_cmdline/pyconvert_unittest.py
+++ /dev/null
@@ -1,253 +0,0 @@
-import re
-import sys
-
-try:
- import parser
-except ImportError:
- parser = None
-
-d={}
-# d is the dictionary of unittest changes, keyed to the old name
-# used by unittest.
-# d[old][0] is the new replacement function.
-# d[old][1] is the operator you will substitute, or '' if there is none.
-# d[old][2] is the possible number of arguments to the unittest
-# function.
-
-# Old Unittest Name new name operator # of args
-d['assertRaises'] = ('raises', '', ['Any'])
-d['fail'] = ('raise AssertionError', '', [0,1])
-d['assert_'] = ('assert', '', [1,2])
-d['failIf'] = ('assert not', '', [1,2])
-d['assertEqual'] = ('assert', ' ==', [2,3])
-d['failIfEqual'] = ('assert not', ' ==', [2,3])
-d['assertIn'] = ('assert', ' in', [2,3])
-d['assertNotIn'] = ('assert', ' not in', [2,3])
-d['assertNotEqual'] = ('assert', ' !=', [2,3])
-d['failUnlessEqual'] = ('assert', ' ==', [2,3])
-d['assertAlmostEqual'] = ('assert round', ' ==', [2,3,4])
-d['failIfAlmostEqual'] = ('assert not round', ' ==', [2,3,4])
-d['assertNotAlmostEqual'] = ('assert round', ' !=', [2,3,4])
-d['failUnlessAlmostEquals'] = ('assert round', ' ==', [2,3,4])
-
-# the list of synonyms
-d['failUnlessRaises'] = d['assertRaises']
-d['failUnless'] = d['assert_']
-d['assertEquals'] = d['assertEqual']
-d['assertNotEquals'] = d['assertNotEqual']
-d['assertAlmostEquals'] = d['assertAlmostEqual']
-d['assertNotAlmostEquals'] = d['assertNotAlmostEqual']
-
-# set up the regular expressions we will need
-leading_spaces = re.compile(r'^(\s*)') # this never fails
-
-pat = ''
-for k in d.keys(): # this complicated pattern to match all unittests
- pat += '|' + r'^(\s*)' + 'self.' + k + r'\(' # \tself.whatever(
-
-old_names = re.compile(pat[1:])
-linesep='\n' # nobody will really try to convert files not read
- # in text mode, will they?
-
-
-def blocksplitter(fp):
- '''split a file into blocks that are headed by functions to rename'''
-
- blocklist = []
- blockstring = ''
-
- for line in fp:
- interesting = old_names.match(line)
- if interesting :
- if blockstring:
- blocklist.append(blockstring)
- blockstring = line # reset the block
- else:
- blockstring += line
-
- blocklist.append(blockstring)
- return blocklist
-
-def rewrite_utest(block):
- '''rewrite every block to use the new utest functions'''
-
- '''returns the rewritten unittest, unless it ran into problems,
- in which case it just returns the block unchanged.
- '''
- utest = old_names.match(block)
-
- if not utest:
- return block
-
- old = utest.group(0).lstrip()[5:-1] # the name we want to replace
- new = d[old][0] # the name of the replacement function
- op = d[old][1] # the operator you will use , or '' if there is none.
- possible_args = d[old][2] # a list of the number of arguments the
- # unittest function could possibly take.
-
- if possible_args == ['Any']: # just rename assertRaises & friends
- return re.sub('self.'+old, new, block)
-
- message_pos = possible_args[-1]
- # the remaining unittests can have an optional message to print
- # when they fail. It is always the last argument to the function.
-
- try:
- indent, argl, trailer = decompose_unittest(old, block)
-
- except SyntaxError: # but we couldn't parse it!
- return block
-
- argnum = len(argl)
- if argnum not in possible_args:
- # sanity check - this one isn't real either
- return block
-
- elif argnum == message_pos:
- message = argl[-1]
- argl = argl[:-1]
- else:
- message = None
-
- if argnum is 0 or (argnum is 1 and argnum is message_pos): #unittest fail()
- string = ''
- if message:
- message = ' ' + message
-
- elif message_pos is 4: # assertAlmostEqual & friends
- try:
- pos = argl[2].lstrip()
- except IndexError:
- pos = '7' # default if none is specified
- string = '(%s -%s, %s)%s 0' % (argl[0], argl[1], pos, op )
-
- else: # assert_, assertEquals and all the rest
- string = ' ' + op.join(argl)
-
- if message:
- string = string + ',' + message
-
- return indent + new + string + trailer
-
-def decompose_unittest(old, block):
- '''decompose the block into its component parts'''
-
- ''' returns indent, arglist, trailer
- indent -- the indentation
- arglist -- the arguments to the unittest function
- trailer -- any extra junk after the closing paren, such as #commment
- '''
-
- indent = re.match(r'(\s*)', block).group()
- pat = re.search('self.' + old + r'\(', block)
-
- args, trailer = get_expr(block[pat.end():], ')')
- arglist = break_args(args, [])
-
- if arglist == ['']: # there weren't any
- return indent, [], trailer
-
- for i in range(len(arglist)):
- try:
- parser.expr(arglist[i].lstrip('\t '))
- except SyntaxError:
- if i == 0:
- arglist[i] = '(' + arglist[i] + ')'
- else:
- arglist[i] = ' (' + arglist[i] + ')'
-
- return indent, arglist, trailer
-
-def break_args(args, arglist):
- '''recursively break a string into a list of arguments'''
- try:
- first, rest = get_expr(args, ',')
- if not rest:
- return arglist + [first]
- else:
- return [first] + break_args(rest, arglist)
- except SyntaxError:
- return arglist + [args]
-
-def get_expr(s, char):
- '''split a string into an expression, and the rest of the string'''
-
- pos=[]
- for i in range(len(s)):
- if s[i] == char:
- pos.append(i)
- if pos == []:
- raise SyntaxError # we didn't find the expected char. Ick.
-
- for p in pos:
- # make the python parser do the hard work of deciding which comma
- # splits the string into two expressions
- try:
- parser.expr('(' + s[:p] + ')')
- return s[:p], s[p+1:]
- except SyntaxError: # It's not an expression yet
- pass
- raise SyntaxError # We never found anything that worked.
-
-
-def main():
- import sys
- import py
-
- usage = "usage: %prog [-s [filename ...] | [-i | -c filename ...]]"
- optparser = py.std.optparse.OptionParser(usage)
-
- def select_output (option, opt, value, optparser, **kw):
- if hasattr(optparser, 'output'):
- optparser.error(
- 'Cannot combine -s -i and -c options. Use one only.')
- else:
- optparser.output = kw['output']
-
- optparser.add_option("-s", "--stdout", action="callback",
- callback=select_output,
- callback_kwargs={'output':'stdout'},
- help="send your output to stdout")
-
- optparser.add_option("-i", "--inplace", action="callback",
- callback=select_output,
- callback_kwargs={'output':'inplace'},
- help="overwrite files in place")
-
- optparser.add_option("-c", "--copy", action="callback",
- callback=select_output,
- callback_kwargs={'output':'copy'},
- help="copy files ... fn.py --> fn_cp.py")
-
- options, args = optparser.parse_args()
-
- output = getattr(optparser, 'output', 'stdout')
-
- if output in ['inplace', 'copy'] and not args:
- optparser.error(
- '-i and -c option require at least one filename')
-
- if not args:
- s = ''
- for block in blocksplitter(sys.stdin):
- s += rewrite_utest(block)
- sys.stdout.write(s)
-
- else:
- for infilename in args: # no error checking to see if we can open, etc.
- infile = file(infilename)
- s = ''
- for block in blocksplitter(infile):
- s += rewrite_utest(block)
- if output == 'inplace':
- outfile = file(infilename, 'w+')
- elif output == 'copy': # yes, just go clobber any existing .cp
- outfile = file (infilename[:-3]+ '_cp.py', 'w+')
- else:
- outfile = sys.stdout
-
- outfile.write(s)
-
-
-if __name__ == '__main__':
- main()
diff --git a/py/_cmdline/pycountloc.py b/py/_cmdline/pycountloc.py
deleted file mode 100755
--- a/py/_cmdline/pycountloc.py
+++ /dev/null
@@ -1,94 +0,0 @@
-#!/usr/bin/env python
-
-# hands on script to compute the non-empty Lines of Code
-# for tests and non-test code
-
-"""\
-py.countloc [PATHS]
-
-Count (non-empty) lines of python code and number of python files recursively
-starting from a list of paths given on the command line (starting from the
-current working directory). Distinguish between test files and normal ones and
-report them separately.
-"""
-import py
-
-def main():
- parser = py.std.optparse.OptionParser(usage=__doc__)
- (options, args) = parser.parse_args()
- countloc(args)
-
-def nodot(p):
- return p.check(dotfile=0)
-
-class FileCounter(object):
- def __init__(self):
- self.file2numlines = {}
- self.numlines = 0
- self.numfiles = 0
-
- def addrecursive(self, directory, fil="*.py", rec=nodot):
- for x in directory.visit(fil, rec):
- self.addfile(x)
-
- def addfile(self, fn, emptylines=False):
- if emptylines:
- s = len(p.readlines())
- else:
- s = 0
- for i in fn.readlines():
- if i.strip():
- s += 1
- self.file2numlines[fn] = s
- self.numfiles += 1
- self.numlines += s
-
- def getnumlines(self, fil):
- numlines = 0
- for path, value in self.file2numlines.items():
- if fil(path):
- numlines += value
- return numlines
-
- def getnumfiles(self, fil):
- numfiles = 0
- for path in self.file2numlines:
- if fil(path):
- numfiles += 1
- return numfiles
-
-def get_loccount(locations=None):
- if locations is None:
- localtions = [py.path.local()]
- counter = FileCounter()
- for loc in locations:
- counter.addrecursive(loc, '*.py', rec=nodot)
-
- def istestfile(p):
- return p.check(fnmatch='test_*.py')
- isnottestfile = lambda x: not istestfile(x)
-
- numfiles = counter.getnumfiles(isnottestfile)
- numlines = counter.getnumlines(isnottestfile)
- numtestfiles = counter.getnumfiles(istestfile)
- numtestlines = counter.getnumlines(istestfile)
-
- return counter, numfiles, numlines, numtestfiles, numtestlines
-
-def countloc(paths=None):
- if not paths:
- paths = ['.']
- locations = [py.path.local(x) for x in paths]
- (counter, numfiles, numlines, numtestfiles,
- numtestlines) = get_loccount(locations)
-
- items = counter.file2numlines.items()
- items.sort(lambda x,y: cmp(x[1], y[1]))
- for x, y in items:
- print("%3d %30s" % (y,x))
-
- print("%30s %3d" %("number of testfiles", numtestfiles))
- print("%30s %3d" %("number of non-empty testlines", numtestlines))
- print("%30s %3d" %("number of files", numfiles))
- print("%30s %3d" %("number of non-empty lines", numlines))
-
diff --git a/py/_cmdline/pylookup.py b/py/_cmdline/pylookup.py
deleted file mode 100755
--- a/py/_cmdline/pylookup.py
+++ /dev/null
@@ -1,85 +0,0 @@
-#!/usr/bin/env python
-
-"""\
-py.lookup [search_directory] SEARCH_STRING [options]
-
-Looks recursively at Python files for a SEARCH_STRING, starting from the
-present working directory. Prints the line, with the filename and line-number
-prepended."""
-
-import sys, os
-import py
-from py.io import ansi_print, get_terminal_width
-import re
-
-def rec(p):
- return p.check(dotfile=0)
-
-parser = py.std.optparse.OptionParser(usage=__doc__)
-parser.add_option("-i", "--ignore-case", action="store_true", dest="ignorecase",
- help="ignore case distinctions")
-parser.add_option("-C", "--context", action="store", type="int", dest="context",
- default=0, help="How many lines of output to show")
-
-terminal_width = get_terminal_width()
-
-def find_indexes(search_line, string):
- indexes = []
- before = 0
- while 1:
- i = search_line.find(string, before)
- if i == -1:
- break
- indexes.append(i)
- before = i + len(string)
- return indexes
-
-def main():
- (options, args) = parser.parse_args()
- if len(args) == 2:
- search_dir, string = args
- search_dir = py.path.local(search_dir)
- else:
- search_dir = py.path.local()
- string = args[0]
- if options.ignorecase:
- string = string.lower()
- for x in search_dir.visit('*.py', rec):
- # match filename directly
- s = x.relto(search_dir)
- if options.ignorecase:
- s = s.lower()
- if s.find(string) != -1:
- sys.stdout.write("%s: filename matches %r" %(x, string) + "\n")
-
- try:
- s = x.read()
- except py.error.ENOENT:
- pass # whatever, probably broken link (ie emacs lock)
- searchs = s
- if options.ignorecase:
- searchs = s.lower()
- if s.find(string) != -1:
- lines = s.splitlines()
- if options.ignorecase:
- searchlines = s.lower().splitlines()
- else:
- searchlines = lines
- for i, (line, searchline) in enumerate(zip(lines, searchlines)):
- indexes = find_indexes(searchline, string)
- if not indexes:
- continue
- if not options.context:
- sys.stdout.write("%s:%d: " %(x.relto(search_dir), i+1))
- last_index = 0
- for index in indexes:
- sys.stdout.write(line[last_index: index])
- ansi_print(line[index: index+len(string)],
- file=sys.stdout, esc=31, newline=False)
- last_index = index + len(string)
- sys.stdout.write(line[last_index:] + "\n")
- else:
- context = (options.context)/2
- for count in range(max(0, i-context), min(len(lines) - 1, i+context+1)):
- print("%s:%d: %s" %(x.relto(search_dir), count+1, lines[count].rstrip()))
- print("-" * terminal_width)
diff --git a/py/_cmdline/pysvnwcrevert.py b/py/_cmdline/pysvnwcrevert.py
deleted file mode 100755
--- a/py/_cmdline/pysvnwcrevert.py
+++ /dev/null
@@ -1,55 +0,0 @@
-#! /usr/bin/env python
-"""\
-py.svnwcrevert [options] WCPATH
-
-Running this script and then 'svn up' puts the working copy WCPATH in a state
-as clean as a fresh check-out.
-
-WARNING: you'll loose all local changes, obviously!
-
-This script deletes all files that have been modified
-or that svn doesn't explicitly know about, including svn:ignored files
-(like .pyc files, hint hint).
-
-The goal of this script is to leave the working copy with some files and
-directories possibly missing, but - most importantly - in a state where
-the following 'svn up' won't just crash.
-"""
-
-import sys, py
-
-def kill(p, root):
- print('< %s' % (p.relto(root),))
- p.remove(rec=1)
-
-def svnwcrevert(path, root=None, precious=[]):
- if root is None:
- root = path
- wcpath = py.path.svnwc(path)
- try:
- st = wcpath.status()
- except ValueError: # typically, "bad char in wcpath"
- kill(path, root)
- return
- for p in path.listdir():
- if p.basename == '.svn' or p.basename in precious:
- continue
- wcp = py.path.svnwc(p)
- if wcp not in st.unchanged and wcp not in st.external:
- kill(p, root)
- elif p.check(dir=1):
- svnwcrevert(p, root)
-
-# XXX add a functional test
-
-parser = py.std.optparse.OptionParser(usage=__doc__)
-parser.add_option("-p", "--precious",
- action="append", dest="precious", default=[],
- help="preserve files with this name")
-
-def main():
- opts, args = parser.parse_args()
- if len(args) != 1:
- parser.print_help()
- sys.exit(2)
- svnwcrevert(py.path.local(args[0]), precious=opts.precious)
diff --git a/py/_cmdline/pytest.py b/py/_cmdline/pytest.py
deleted file mode 100755
--- a/py/_cmdline/pytest.py
+++ /dev/null
@@ -1,5 +0,0 @@
-#!/usr/bin/env python
-import py
-
-def main(args=None):
- raise SystemExit(py.test.cmdline.main(args))
diff --git a/py/_cmdline/pywhich.py b/py/_cmdline/pywhich.py
deleted file mode 100755
--- a/py/_cmdline/pywhich.py
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/usr/bin/env python
-
-"""\
-py.which [name]
-
-print the location of the given python module or package name
-"""
-
-import sys
-
-def main():
- name = sys.argv[1]
- try:
- mod = __import__(name)
- except ImportError:
- sys.stderr.write("could not import: " + name + "\n")
- else:
- try:
- location = mod.__file__
- except AttributeError:
- sys.stderr.write("module (has no __file__): " + str(mod))
- else:
- print(location)
diff --git a/py/_code/_assertionnew.py b/py/_code/_assertionnew.py
--- a/py/_code/_assertionnew.py
+++ b/py/_code/_assertionnew.py
@@ -1,6 +1,6 @@
"""
-Like _assertion.py but using builtin AST. It should replace _assertionold.py
-eventually.
+Find intermediate evalutation results in assert statements through builtin AST.
+This should replace _assertionold.py eventually.
"""
import sys
@@ -108,7 +108,7 @@
class DebugInterpreter(ast.NodeVisitor):
- """Interpret AST nodes to gleam useful debugging information."""
+ """Interpret AST nodes to gleam useful debugging information. """
def __init__(self, frame):
self.frame = frame
@@ -162,10 +162,7 @@
def visit_Compare(self, comp):
left = comp.left
left_explanation, left_result = self.visit(left)
- got_result = False
for op, next_op in zip(comp.ops, comp.comparators):
- if got_result and not result:
- break
next_explanation, next_result = self.visit(next_op)
op_symbol = operator_map[op.__class__]
explanation = "%s %s %s" % (left_explanation, op_symbol,
@@ -177,9 +174,20 @@
__exprinfo_right=next_result)
except Exception:
raise Failure(explanation)
- else:
- got_result = True
+ try:
+ if not result:
+ break
+ except KeyboardInterrupt:
+ raise
+ except:
+ break
left_explanation, left_result = next_explanation, next_result
+
+ rcomp = py.code._reprcompare
+ if rcomp:
+ res = rcomp(op_symbol, left_result, next_result)
+ if res:
+ explanation = res
return explanation, result
def visit_BoolOp(self, boolop):
@@ -259,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):
@@ -295,6 +292,9 @@
result = self.frame.eval(co, __exprinfo_expr=source_result)
except Exception:
raise Failure(explanation)
+ explanation = "%s\n{%s = %s.%s\n}" % (self.frame.repr(result),
+ self.frame.repr(result),
+ source_explanation, attr.attr)
# Check if the attr is from an instance.
source = "%r in getattr(__exprinfo_expr, '__dict__', {})"
source = source % (attr.attr,)
@@ -325,10 +325,11 @@
def visit_Assign(self, assign):
value_explanation, value_result = self.visit(assign.value)
explanation = "... = %s" % (value_explanation,)
- name = ast.Name("__exprinfo_expr", ast.Load(), assign.value.lineno,
- assign.value.col_offset)
- new_assign = ast.Assign(assign.targets, name, assign.lineno,
- assign.col_offset)
+ name = ast.Name("__exprinfo_expr", ast.Load(),
+ lineno=assign.value.lineno,
+ col_offset=assign.value.col_offset)
+ new_assign = ast.Assign(assign.targets, name, lineno=assign.lineno,
+ col_offset=assign.col_offset)
mod = ast.Module([new_assign])
co = self._compile(mod, "exec")
try:
diff --git a/py/_code/_assertionold.py b/py/_code/_assertionold.py
--- a/py/_code/_assertionold.py
+++ b/py/_code/_assertionold.py
@@ -3,7 +3,7 @@
from compiler import parse, ast, pycodegen
from py._code.assertion import BuiltinAssertionError, _format_explanation
-passthroughex = (KeyboardInterrupt, SystemExit, MemoryError)
+passthroughex = py.builtin._sysex
class Failure:
def __init__(self, node):
@@ -496,7 +496,7 @@
#frame = py.code.Frame(frame)
#return interpret(line, frame)
- tb = excinfo.traceback[-1]
+ tb = excinfo.traceback[-1]
source = str(tb.statement).strip()
x = interpret(source, tb.frame, should_fail=True)
if not isinstance(x, str):
diff --git a/py/_code/assertion.py b/py/_code/assertion.py
--- a/py/_code/assertion.py
+++ b/py/_code/assertion.py
@@ -3,14 +3,23 @@
BuiltinAssertionError = py.builtin.builtins.AssertionError
+_reprcompare = None # if set, will be called by assert reinterp for comparison ops
def _format_explanation(explanation):
- # uck! See CallFunc for where \n{ and \n} escape sequences are used
+ """This formats an explanation
+
+ Normally all embedded newlines are escaped, however there are
+ three exceptions: \n{, \n} and \n~. The first two are intended
+ cover nested explanations, see function and attribute explanations
+ for examples (.visit_Call(), visit_Attribute()). The last one is
+ for when one explanation needs to span multiple lines, e.g. when
+ displaying diffs.
+ """
raw_lines = (explanation or '').split('\n')
- # escape newlines not followed by { and }
+ # escape newlines not followed by {, } and ~
lines = [raw_lines[0]]
for l in raw_lines[1:]:
- if l.startswith('{') or l.startswith('}'):
+ if l.startswith('{') or l.startswith('}') or l.startswith('~'):
lines.append(l)
else:
lines[-1] += '\\n' + l
@@ -28,23 +37,25 @@
stackcnt[-1] += 1
stackcnt.append(0)
result.append(' +' + ' '*(len(stack)-1) + s + line[1:])
- else:
+ elif line.startswith('}'):
assert line.startswith('}')
stack.pop()
stackcnt.pop()
result[stack[-1]] += line[1:]
+ else:
+ assert line.startswith('~')
+ result.append(' '*len(stack) + line[1:])
assert len(stack) == 1
return '\n'.join(result)
class AssertionError(BuiltinAssertionError):
-
def __init__(self, *args):
BuiltinAssertionError.__init__(self, *args)
if args:
try:
self.msg = str(args[0])
- except (KeyboardInterrupt, SystemExit):
+ except py.builtin._sysex:
raise
except:
self.msg = "<[broken __repr__] %s at %0xd>" %(
@@ -52,18 +63,24 @@
else:
f = py.code.Frame(sys._getframe(1))
try:
- source = f.statement
- source = str(source.deindent()).strip()
+ source = f.code.fullsource
+ if source is not None:
+ try:
+ source = source.getstatement(f.lineno, assertion=True)
+ except IndexError:
+ source = None
+ else:
+ source = str(source.deindent()).strip()
except py.error.ENOENT:
source = None
# this can also occur during reinterpretation, when the
# co_filename is set to "<run>".
if source:
self.msg = reinterpret(source, f, should_fail=True)
- if not self.args:
- self.args = (self.msg,)
else:
- self.msg = None
+ self.msg = "<could not determine information>"
+ if not self.args:
+ self.args = (self.msg,)
if sys.version_info > (3, 0):
AssertionError.__module__ = "builtins"
@@ -74,4 +91,4 @@
from py._code._assertionnew import interpret as reinterpret
else:
reinterpret = reinterpret_old
-
+
diff --git a/py/_code/code.py b/py/_code/code.py
--- a/py/_code/code.py
+++ b/py/_code/code.py
@@ -9,15 +9,15 @@
""" wrapper around Python code objects """
def __init__(self, rawcode):
rawcode = py.code.getrawcode(rawcode)
- self.raw = rawcode
+ self.raw = rawcode
try:
self.filename = rawcode.co_filename
self.firstlineno = rawcode.co_firstlineno - 1
self.name = rawcode.co_name
- except AttributeError:
+ except AttributeError:
raise TypeError("not a code object: %r" %(rawcode,))
-
- def __eq__(self, other):
+
+ def __eq__(self, other):
return self.raw == other.raw
def __ne__(self, other):
@@ -27,11 +27,11 @@
""" return a path object pointing to source code"""
p = py.path.local(self.raw.co_filename)
if not p.check():
- # XXX maybe try harder like the weird logic
- # in the standard lib [linecache.updatecache] does?
+ # XXX maybe try harder like the weird logic
+ # in the standard lib [linecache.updatecache] does?
p = self.raw.co_filename
return p
-
+
path = property(path, None, None, "path of this code object")
def fullsource(self):
@@ -42,7 +42,7 @@
return full
fullsource = property(fullsource, None, None,
"full source containing this code object")
-
+
def source(self):
""" return a py.code.Source object for the code object's source only
"""
@@ -81,7 +81,7 @@
returns the result of the evaluation
"""
- f_locals = self.f_locals.copy()
+ f_locals = self.f_locals.copy()
f_locals.update(vars)
return eval(code, self.f_globals, f_locals)
@@ -90,7 +90,7 @@
'vars' are optiona; additional local variables
"""
- f_locals = self.f_locals.copy()
+ f_locals = self.f_locals.copy()
f_locals.update(vars)
py.builtin.exec_(code, self.f_globals, f_locals )
@@ -115,8 +115,8 @@
class TracebackEntry(object):
""" a single entry in a traceback """
-
- exprinfo = None
+
+ exprinfo = None
def __init__(self, rawentry):
self._rawentry = rawentry
@@ -153,13 +153,14 @@
x = py.code._reinterpret(source, self.frame, should_fail=True)
if not isinstance(x, str):
raise TypeError("interpret returned non-string %r" % (x,))
- self.exprinfo = x
+ self.exprinfo = x
return self.exprinfo
def getfirstlinesource(self):
- return self.frame.code.firstlineno
+ # on Jython this firstlineno can be -1 apparently
+ return max(self.frame.code.firstlineno, 0)
- def getsource(self):
+ def getsource(self):
""" return failing source code. """
source = self.frame.code.fullsource
if source is None:
@@ -167,64 +168,64 @@
start = self.getfirstlinesource()
end = self.lineno
try:
- _, end = source.getstatementrange(end)
- except IndexError:
- end = self.lineno + 1
- # heuristic to stop displaying source on e.g.
+ _, end = source.getstatementrange(end)
+ except IndexError:
+ end = self.lineno + 1
+ # heuristic to stop displaying source on e.g.
# if something: # assume this causes a NameError
- # # _this_ lines and the one
- # below we don't want from entry.getsource()
- for i in range(self.lineno, end):
- if source[i].rstrip().endswith(':'):
+ # # _this_ lines and the one
+ # below we don't want from entry.getsource()
+ for i in range(self.lineno, end):
+ if source[i].rstrip().endswith(':'):
end = i + 1
- break
+ break
return source[start:end]
source = property(getsource)
def ishidden(self):
- """ return True if the current frame has a var __tracebackhide__
+ """ return True if the current frame has a var __tracebackhide__
resolving to True
-
+
mostly for internal use
"""
- try:
- return self.frame.eval("__tracebackhide__")
- except (SystemExit, KeyboardInterrupt):
+ try:
+ return self.frame.eval("__tracebackhide__")
+ except py.builtin._sysex:
raise
except:
- return False
+ return False
- def __str__(self):
- try:
- fn = str(self.path)
- except py.error.Error:
+ def __str__(self):
+ try:
+ fn = str(self.path)
+ except py.error.Error:
fn = '???'
- name = self.frame.code.name
- try:
+ name = self.frame.code.name
+ try:
line = str(self.statement).lstrip()
except KeyboardInterrupt:
raise
except:
line = "???"
- return " File %r:%d in %s\n %s\n" %(fn, self.lineno+1, name, line)
+ return " File %r:%d in %s\n %s\n" %(fn, self.lineno+1, name, line)
def name(self):
return self.frame.code.raw.co_name
name = property(name, None, None, "co_name of underlaying code")
class Traceback(list):
- """ Traceback objects encapsulate and offer higher level
- access to Traceback entries.
+ """ Traceback objects encapsulate and offer higher level
+ access to Traceback entries.
"""
- Entry = TracebackEntry
+ Entry = TracebackEntry
def __init__(self, tb):
""" initialize from given python traceback object. """
if hasattr(tb, 'tb_next'):
- def f(cur):
- while cur is not None:
+ def f(cur):
+ while cur is not None:
yield self.Entry(cur)
- cur = cur.tb_next
- list.__init__(self, f(tb))
+ cur = cur.tb_next
+ list.__init__(self, f(tb))
else:
list.__init__(self, tb)
@@ -243,7 +244,7 @@
codepath = code.path
if ((path is None or codepath == path) and
(excludepath is None or not hasattr(codepath, 'relto') or
- not codepath.relto(excludepath)) and
+ not codepath.relto(excludepath)) and
(lineno is None or x.lineno == lineno) and
(firstlineno is None or x.frame.code.firstlineno == firstlineno)):
return Traceback(x._rawentry)
@@ -269,7 +270,7 @@
def getcrashentry(self):
""" return last non-hidden traceback entry that lead
- to the exception of a traceback.
+ to the exception of a traceback.
"""
tb = self.filter()
if not tb:
@@ -282,17 +283,17 @@
"""
cache = {}
for i, entry in enumerate(self):
- key = entry.frame.code.path, entry.lineno
+ key = entry.frame.code.path, entry.lineno
#print "checking for recursion at", key
l = cache.setdefault(key, [])
- if l:
+ if l:
f = entry.frame
loc = f.f_locals
- for otherloc in l:
- if f.is_true(f.eval(co_equal,
+ for otherloc in l:
+ if f.is_true(f.eval(co_equal,
__recursioncache_locals_1=loc,
__recursioncache_locals_2=otherloc)):
- return i
+ return i
l.append(entry.frame.f_locals)
return None
@@ -303,7 +304,7 @@
""" wraps sys.exc_info() objects and offers
help for navigating the traceback.
"""
- _striptext = ''
+ _striptext = ''
def __init__(self, tup=None, exprinfo=None):
# NB. all attributes are private! Subclasses or other
# ExceptionInfo-like classes may have different attributes.
@@ -318,14 +319,14 @@
self._excinfo = tup
self.type, self.value, tb = self._excinfo
self.typename = self.type.__name__
- self.traceback = py.code.Traceback(tb)
+ self.traceback = py.code.Traceback(tb)
def __repr__(self):
return "<ExceptionInfo %s tblen=%d>" % (self.typename, len(self.traceback))
- def exconly(self, tryshort=False):
+ def exconly(self, tryshort=False):
""" return the exception as a string
-
+
when 'tryshort' resolves to True, and the exception is a
py.code._AssertionError, only the actual exception part of
the exception representation is returned (so 'AssertionError: ' is
@@ -334,14 +335,14 @@
lines = py.std.traceback.format_exception_only(self.type, self.value)
text = ''.join(lines)
text = text.rstrip()
- if tryshort:
- if text.startswith(self._striptext):
+ if tryshort:
+ if text.startswith(self._striptext):
text = text[len(self._striptext):]
return text
- def errisinstance(self, exc):
+ def errisinstance(self, exc):
""" return True if the exception is an instance of exc """
- return isinstance(self.value, exc)
+ return isinstance(self.value, exc)
def _getreprcrash(self):
exconly = self.exconly(tryshort=True)
@@ -350,14 +351,22 @@
reprcrash = ReprFileLocation(path, lineno+1, exconly)
return reprcrash
- def getrepr(self, showlocals=False, style="long",
+ def getrepr(self, showlocals=False, style="long",
abspath=False, tbfilter=True, funcargs=False):
""" return str()able representation of this exception info.
- showlocals: show locals per traceback entry
- style: long|short|no traceback style
+ showlocals: show locals per traceback entry
+ style: long|short|no|native traceback style
tbfilter: hide entries (where __tracebackhide__ is true)
"""
- fmt = FormattedExcinfo(showlocals=showlocals, style=style,
+ if style == 'native':
+ import traceback
+ return ''.join(traceback.format_exception(
+ self.type,
+ self.value,
+ self.traceback[0]._rawentry,
+ ))
+
+ fmt = FormattedExcinfo(showlocals=showlocals, style=style,
abspath=abspath, tbfilter=tbfilter, funcargs=funcargs)
return fmt.repr_excinfo(self)
@@ -370,27 +379,27 @@
entry = self.traceback[-1]
loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
return unicode(loc)
-
+
class FormattedExcinfo(object):
- """ presenting information about failing Functions and Generators. """
More information about the pypy-commit
mailing list