rewrote the initpkg mechanism and moved py lib implementation files to
_py/... with py/__init__.py containing pointers into them The new apipkg is only around 70 lines of code and allows us to get rid of the infamous "py.__." by regular non-magical "_py." imports. It is also available as a separately installable package, see http://bitbucket.org/hpk42/apipkg --HG-- branch : trunk
This commit is contained in:
320
py/__init__.py
320
py/__init__.py
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
advanced testing and development support library:
|
||||
py.test and pylib: rapid testing and development utils
|
||||
|
||||
- `py.test`_: cross-project testing tool with many advanced features
|
||||
- `py.path`_: path abstractions over local and subversion files
|
||||
@@ -11,191 +11,177 @@ For questions please check out http://pylib.org/contact.html
|
||||
|
||||
.. _`py.test`: http://pylib.org/test.html
|
||||
.. _`py.path`: http://pylib.org/path.html
|
||||
.. _`py.code`: http://pylib.org/code.html
|
||||
.. _`py.code`: http://pylib.org/html
|
||||
|
||||
(c) Holger Krekel and others, 2009
|
||||
"""
|
||||
from py.initpkg import initpkg
|
||||
trunk = "1.1.0b1"
|
||||
version = "1.1.0b1"
|
||||
|
||||
version = trunk or "1.0.x"
|
||||
__version__ = version = version or "1.1.x"
|
||||
import _py.apipkg
|
||||
|
||||
del trunk
|
||||
_py.apipkg.initpkg(__name__, dict(
|
||||
# access to all standard lib modules
|
||||
std = '_py.std:std',
|
||||
# access to all posix errno's as classes
|
||||
error = '_py.error:error',
|
||||
|
||||
initpkg(__name__,
|
||||
description = "py.test and pylib: rapid testing and high-level path/code objects.",
|
||||
version = version,
|
||||
url = "http://pylib.org",
|
||||
license = "MIT license",
|
||||
platforms = ['unix', 'linux', 'osx', 'cygwin', 'win32'],
|
||||
author = "holger krekel, Guido Wesdorp, Carl Friedrich Bolz, Armin Rigo, Maciej Fijalkowski & others",
|
||||
author_email = "holger at merlinux.eu, py-dev at codespeak.net",
|
||||
long_description = globals()['__doc__'],
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: POSIX",
|
||||
"Operating System :: Microsoft :: Windows",
|
||||
"Operating System :: MacOS :: MacOS X",
|
||||
"Topic :: Software Development :: Testing",
|
||||
"Topic :: Software Development :: Libraries",
|
||||
"Topic :: System :: Distributed Computing",
|
||||
"Topic :: Utilities",
|
||||
"Programming Language :: Python",
|
||||
],
|
||||
_com = {
|
||||
'Registry': '_py._com:Registry',
|
||||
'MultiCall': '_py._com:MultiCall',
|
||||
'comregistry': '_py._com:comregistry',
|
||||
'HookRelay': '_py._com:HookRelay',
|
||||
},
|
||||
cmdline = {
|
||||
'pytest': '_py.cmdline.pytest:main',
|
||||
'pyrest': '_py.cmdline.pyrest:main',
|
||||
'pylookup': '_py.cmdline.pylookup:main',
|
||||
'pycountloc': '_py.cmdline.pycountlog:main',
|
||||
'pytest': '_py.test.cmdline:main',
|
||||
'pyrest': '_py.cmdline.pyrest:main',
|
||||
'pylookup': '_py.cmdline.pylookup:main',
|
||||
'pycountloc': '_py.cmdline.pycountloc:main',
|
||||
'pycleanup': '_py.cmdline.pycleanup:main',
|
||||
'pywhich' : '_py.cmdline.pywhich:main',
|
||||
'pysvnwcrevert' : '_py.cmdline.pysvnwcrevert:main',
|
||||
'pyconvert_unittest' : '_py.cmdline.pyconvert_unittest:main',
|
||||
},
|
||||
|
||||
# EXPORTED API
|
||||
exportdefs = {
|
||||
|
||||
# py lib events and plugins
|
||||
'_com.Registry' : ('./_com.py', 'Registry'),
|
||||
'_com.MultiCall' : ('./_com.py', 'MultiCall'),
|
||||
'_com.comregistry' : ('./_com.py', 'comregistry'),
|
||||
'_com.HookRelay' : ('./_com.py', 'HookRelay'),
|
||||
|
||||
# py lib cmdline tools
|
||||
'cmdline.pytest' : ('./cmdline/pytest.py', 'main',),
|
||||
'cmdline.pyrest' : ('./cmdline/pyrest.py', 'main',),
|
||||
'cmdline.pylookup' : ('./cmdline/pylookup.py', 'main',),
|
||||
'cmdline.pycountloc' : ('./cmdline/pycountloc.py', 'main',),
|
||||
'cmdline.pycleanup' : ('./cmdline/pycleanup.py', 'main',),
|
||||
'cmdline.pywhich' : ('./cmdline/pywhich.py', 'main',),
|
||||
'cmdline.pysvnwcrevert' : ('./cmdline/pysvnwcrevert.py', 'main',),
|
||||
'cmdline.pyconvert_unittest' : ('./cmdline/pyconvert_unittest.py', 'main',),
|
||||
|
||||
# helpers for use from test functions or collectors
|
||||
'test.__doc__' : ('./test/__init__.py', '__doc__'),
|
||||
'test._PluginManager' : ('./test/pluginmanager.py', 'PluginManager'),
|
||||
'test.raises' : ('./test/outcome.py', 'raises'),
|
||||
'test.skip' : ('./test/outcome.py', 'skip'),
|
||||
'test.importorskip' : ('./test/outcome.py', 'importorskip'),
|
||||
'test.fail' : ('./test/outcome.py', 'fail'),
|
||||
'test.exit' : ('./test/outcome.py', 'exit'),
|
||||
|
||||
# configuration/initialization related test api
|
||||
'test.config' : ('./test/config.py', 'config_per_process'),
|
||||
'test.ensuretemp' : ('./test/config.py', 'ensuretemp'),
|
||||
'test.cmdline.main' : ('./test/cmdline.py', 'main'),
|
||||
|
||||
# for customization of collecting/running tests
|
||||
'test.collect.Collector' : ('./test/collect.py', 'Collector'),
|
||||
'test.collect.Directory' : ('./test/collect.py', 'Directory'),
|
||||
'test.collect.File' : ('./test/collect.py', 'File'),
|
||||
'test.collect.Item' : ('./test/collect.py', 'Item'),
|
||||
'test.collect.Module' : ('./test/pycollect.py', 'Module'),
|
||||
'test.collect.Class' : ('./test/pycollect.py', 'Class'),
|
||||
'test.collect.Instance' : ('./test/pycollect.py', 'Instance'),
|
||||
'test.collect.Generator' : ('./test/pycollect.py', 'Generator'),
|
||||
'test.collect.Function' : ('./test/pycollect.py', 'Function'),
|
||||
'test.collect._fillfuncargs' : ('./test/funcargs.py', 'fillfuncargs'),
|
||||
|
||||
# thread related API (still in early design phase)
|
||||
'_thread.WorkerPool' : ('./thread/pool.py', 'WorkerPool'),
|
||||
'_thread.NamedThreadPool' : ('./thread/pool.py', 'NamedThreadPool'),
|
||||
'_thread.ThreadOut' : ('./thread/io.py', 'ThreadOut'),
|
||||
test = {
|
||||
# helpers for use from test functions or collectors
|
||||
'__doc__' : '_py.test:__doc__',
|
||||
'_PluginManager' : '_py.test.pluginmanager:PluginManager',
|
||||
'raises' : '_py.test.outcome:raises',
|
||||
'skip' : '_py.test.outcome:skip',
|
||||
'importorskip' : '_py.test.outcome:importorskip',
|
||||
'fail' : '_py.test.outcome:fail',
|
||||
'exit' : '_py.test.outcome:exit',
|
||||
# configuration/initialization related test api
|
||||
'config' : '_py.test.config:config_per_process',
|
||||
'ensuretemp' : '_py.test.config:ensuretemp',
|
||||
'collect': {
|
||||
'Collector' : '_py.test.collect:Collector',
|
||||
'Directory' : '_py.test.collect:Directory',
|
||||
'File' : '_py.test.collect:File',
|
||||
'Item' : '_py.test.collect:Item',
|
||||
'Module' : '_py.test.pycollect:Module',
|
||||
'Class' : '_py.test.pycollect:Class',
|
||||
'Instance' : '_py.test.pycollect:Instance',
|
||||
'Generator' : '_py.test.pycollect:Generator',
|
||||
'Function' : '_py.test.pycollect:Function',
|
||||
'_fillfuncargs' : '_py.test.funcargs:fillfuncargs',
|
||||
},
|
||||
},
|
||||
|
||||
# hook into the top-level standard library
|
||||
'std' : ('./std.py', 'std'),
|
||||
process = {
|
||||
'__doc__' : '_py.process:__doc__',
|
||||
'cmdexec' : '_py.process.cmdexec:cmdexec',
|
||||
'kill' : '_py.process.killproc:kill',
|
||||
'ForkedFunc' : '_py.process.forkedfunc:ForkedFunc',
|
||||
},
|
||||
|
||||
'process.__doc__' : ('./process/__init__.py', '__doc__'),
|
||||
'process.cmdexec' : ('./process/cmdexec.py', 'cmdexec'),
|
||||
'process.kill' : ('./process/killproc.py', 'kill'),
|
||||
'process.ForkedFunc' : ('./process/forkedfunc.py', 'ForkedFunc'),
|
||||
|
||||
# path implementation
|
||||
'path.__doc__' : ('./path/__init__.py', '__doc__'),
|
||||
'path.svnwc' : ('./path/svnwc.py', 'SvnWCCommandPath'),
|
||||
'path.svnurl' : ('./path/svnurl.py', 'SvnCommandPath'),
|
||||
'path.local' : ('./path/local.py', 'LocalPath'),
|
||||
'path.SvnAuth' : ('./path/svnwc.py', 'SvnAuth'),
|
||||
path = {
|
||||
'__doc__' : '_py.path:__doc__',
|
||||
'svnwc' : '_py.path.svnwc:SvnWCCommandPath',
|
||||
'svnurl' : '_py.path.svnurl:SvnCommandPath',
|
||||
'local' : '_py.path.local:LocalPath',
|
||||
'SvnAuth' : '_py.path.svnwc:SvnAuth',
|
||||
},
|
||||
|
||||
# some nice slightly magic APIs
|
||||
#'magic.__doc__' : ('./magic/__init__.py', '__doc__'),
|
||||
'magic.invoke' : ('./code/oldmagic.py', 'invoke'),
|
||||
'magic.revoke' : ('./code/oldmagic.py', 'revoke'),
|
||||
'magic.patch' : ('./code/oldmagic.py', 'patch'),
|
||||
'magic.revert' : ('./code/oldmagic.py', 'revert'),
|
||||
'magic.autopath' : ('./path/local.py', 'autopath'),
|
||||
'magic.AssertionError' : ('./code/oldmagic2.py', 'AssertionError'),
|
||||
magic = {
|
||||
'invoke' : '_py.code.oldmagic:invoke',
|
||||
'revoke' : '_py.code.oldmagic:revoke',
|
||||
'patch' : '_py.code.oldmagic:patch',
|
||||
'revert' : '_py.code.oldmagic:revert',
|
||||
'autopath' : '_py.path.local:autopath',
|
||||
'AssertionError' : '_py.code.oldmagic2:AssertionError',
|
||||
},
|
||||
|
||||
# python inspection/code-generation API
|
||||
'code.__doc__' : ('./code/__init__.py', '__doc__'),
|
||||
'code.compile' : ('./code/source.py', 'compile_'),
|
||||
'code.Source' : ('./code/source.py', 'Source'),
|
||||
'code.Code' : ('./code/code.py', 'Code'),
|
||||
'code.Frame' : ('./code/code.py', 'Frame'),
|
||||
'code.ExceptionInfo' : ('./code/code.py', 'ExceptionInfo'),
|
||||
'code.Traceback' : ('./code/code.py', 'Traceback'),
|
||||
'code.getfslineno' : ('./code/source.py', 'getfslineno'),
|
||||
'code.getrawcode' : ('./code/code.py', 'getrawcode'),
|
||||
'code.patch_builtins' : ('./code/code.py', 'patch_builtins'),
|
||||
'code.unpatch_builtins' : ('./code/code.py', 'unpatch_builtins'),
|
||||
'code._AssertionError' : ('./code/assertion.py', 'AssertionError'),
|
||||
code = {
|
||||
'__doc__' : '_py.code:__doc__',
|
||||
'compile' : '_py.code.source:compile_',
|
||||
'Source' : '_py.code.source:Source',
|
||||
'Code' : '_py.code.code:Code',
|
||||
'Frame' : '_py.code.code:Frame',
|
||||
'ExceptionInfo' : '_py.code.code:ExceptionInfo',
|
||||
'Traceback' : '_py.code.code:Traceback',
|
||||
'getfslineno' : '_py.code.source:getfslineno',
|
||||
'getrawcode' : '_py.code.code:getrawcode',
|
||||
'patch_builtins' : '_py.code.code:patch_builtins',
|
||||
'unpatch_builtins' : '_py.code.code:unpatch_builtins',
|
||||
'_AssertionError' : '_py.code.assertion:AssertionError',
|
||||
},
|
||||
|
||||
# backports and additions of builtins
|
||||
'builtin.__doc__' : ('./builtin/__init__.py', '__doc__'),
|
||||
'builtin.enumerate' : ('./builtin/builtin24.py', 'enumerate'),
|
||||
'builtin.reversed' : ('./builtin/builtin24.py', 'reversed'),
|
||||
'builtin.sorted' : ('./builtin/builtin24.py', 'sorted'),
|
||||
'builtin.set' : ('./builtin/builtin24.py', 'set'),
|
||||
'builtin.frozenset' : ('./builtin/builtin24.py', 'frozenset'),
|
||||
'builtin.BaseException' : ('./builtin/builtin25.py', 'BaseException'),
|
||||
'builtin.GeneratorExit' : ('./builtin/builtin25.py', 'GeneratorExit'),
|
||||
'builtin.print_' : ('./builtin/builtin31.py', 'print_'),
|
||||
'builtin._reraise' : ('./builtin/builtin31.py', '_reraise'),
|
||||
'builtin._tryimport' : ('./builtin/builtin31.py', '_tryimport'),
|
||||
'builtin.exec_' : ('./builtin/builtin31.py', 'exec_'),
|
||||
'builtin._basestring' : ('./builtin/builtin31.py', '_basestring'),
|
||||
'builtin._totext' : ('./builtin/builtin31.py', '_totext'),
|
||||
'builtin._isbytes' : ('./builtin/builtin31.py', '_isbytes'),
|
||||
'builtin._istext' : ('./builtin/builtin31.py', '_istext'),
|
||||
'builtin._getimself' : ('./builtin/builtin31.py', '_getimself'),
|
||||
'builtin._getfuncdict' : ('./builtin/builtin31.py', '_getfuncdict'),
|
||||
'builtin.builtins' : ('./builtin/builtin31.py', 'builtins'),
|
||||
'builtin.execfile' : ('./builtin/builtin31.py', 'execfile'),
|
||||
'builtin.callable' : ('./builtin/builtin31.py', 'callable'),
|
||||
builtin = {
|
||||
'__doc__' : '_py.builtin:__doc__',
|
||||
'enumerate' : '_py.builtin.builtin24:enumerate',
|
||||
'reversed' : '_py.builtin.builtin24:reversed',
|
||||
'sorted' : '_py.builtin.builtin24:sorted',
|
||||
'set' : '_py.builtin.builtin24:set',
|
||||
'frozenset' : '_py.builtin.builtin24:frozenset',
|
||||
'BaseException' : '_py.builtin.builtin25:BaseException',
|
||||
'GeneratorExit' : '_py.builtin.builtin25:GeneratorExit',
|
||||
'print_' : '_py.builtin.builtin31:print_',
|
||||
'_reraise' : '_py.builtin.builtin31:_reraise',
|
||||
'_tryimport' : '_py.builtin.builtin31:_tryimport',
|
||||
'exec_' : '_py.builtin.builtin31:exec_',
|
||||
'_basestring' : '_py.builtin.builtin31:_basestring',
|
||||
'_totext' : '_py.builtin.builtin31:_totext',
|
||||
'_isbytes' : '_py.builtin.builtin31:_isbytes',
|
||||
'_istext' : '_py.builtin.builtin31:_istext',
|
||||
'_getimself' : '_py.builtin.builtin31:_getimself',
|
||||
'_getfuncdict' : '_py.builtin.builtin31:_getfuncdict',
|
||||
'builtins' : '_py.builtin.builtin31:builtins',
|
||||
'execfile' : '_py.builtin.builtin31:execfile',
|
||||
'callable' : '_py.builtin.builtin31:callable',
|
||||
},
|
||||
|
||||
# input-output helping
|
||||
'io.__doc__' : ('./io/__init__.py', '__doc__'),
|
||||
'io.dupfile' : ('./io/capture.py', 'dupfile'),
|
||||
'io.TextIO' : ('./io/capture.py', 'TextIO'),
|
||||
'io.BytesIO' : ('./io/capture.py', 'BytesIO'),
|
||||
'io.FDCapture' : ('./io/capture.py', 'FDCapture'),
|
||||
'io.StdCapture' : ('./io/capture.py', 'StdCapture'),
|
||||
'io.StdCaptureFD' : ('./io/capture.py', 'StdCaptureFD'),
|
||||
'io.TerminalWriter' : ('./io/terminalwriter.py', 'TerminalWriter'),
|
||||
|
||||
# error module, defining all errno's as Classes
|
||||
'error' : ('./error.py', 'error'),
|
||||
# input-output helping
|
||||
io = {
|
||||
'__doc__' : '_py.io:__doc__',
|
||||
'dupfile' : '_py.io.capture:dupfile',
|
||||
'TextIO' : '_py.io.capture:TextIO',
|
||||
'BytesIO' : '_py.io.capture:BytesIO',
|
||||
'FDCapture' : '_py.io.capture:FDCapture',
|
||||
'StdCapture' : '_py.io.capture:StdCapture',
|
||||
'StdCaptureFD' : '_py.io.capture:StdCaptureFD',
|
||||
'TerminalWriter' : '_py.io.terminalwriter:TerminalWriter',
|
||||
},
|
||||
|
||||
# small and mean xml/html generation
|
||||
'xml.__doc__' : ('./xmlgen.py', '__doc__'),
|
||||
'xml.html' : ('./xmlgen.py', 'html'),
|
||||
'xml.Tag' : ('./xmlgen.py', 'Tag'),
|
||||
'xml.raw' : ('./xmlgen.py', 'raw'),
|
||||
'xml.Namespace' : ('./xmlgen.py', 'Namespace'),
|
||||
'xml.escape' : ('./xmlgen.py', 'escape'),
|
||||
xml = {
|
||||
'__doc__' : '_py.xmlgen:__doc__',
|
||||
'html' : '_py.xmlgen:html',
|
||||
'Tag' : '_py.xmlgen:Tag',
|
||||
'raw' : '_py.xmlgen:raw',
|
||||
'Namespace' : '_py.xmlgen:Namespace',
|
||||
'escape' : '_py.xmlgen:escape',
|
||||
},
|
||||
|
||||
# logging API ('producers' and 'consumers' connected via keywords)
|
||||
'log.__doc__' : ('./log/__init__.py', '__doc__'),
|
||||
'log._apiwarn' : ('./log/warning.py', '_apiwarn'),
|
||||
'log.Producer' : ('./log/log.py', 'Producer'),
|
||||
'log.setconsumer' : ('./log/log.py', 'setconsumer'),
|
||||
'log._setstate' : ('./log/log.py', 'setstate'),
|
||||
'log._getstate' : ('./log/log.py', 'getstate'),
|
||||
'log.Path' : ('./log/log.py', 'Path'),
|
||||
'log.STDOUT' : ('./log/log.py', 'STDOUT'),
|
||||
'log.STDERR' : ('./log/log.py', 'STDERR'),
|
||||
'log.Syslog' : ('./log/log.py', 'Syslog'),
|
||||
log = {
|
||||
# logging API ('producers' and 'consumers' connected via keywords)
|
||||
'__doc__' : '_py.log:__doc__',
|
||||
'_apiwarn' : '_py.log.warning:_apiwarn',
|
||||
'Producer' : '_py.log.log:Producer',
|
||||
'setconsumer' : '_py.log.log:setconsumer',
|
||||
'_setstate' : '_py.log.log:setstate',
|
||||
'_getstate' : '_py.log.log:getstate',
|
||||
'Path' : '_py.log.log:Path',
|
||||
'STDOUT' : '_py.log.log:STDOUT',
|
||||
'STDERR' : '_py.log.log:STDERR',
|
||||
'Syslog' : '_py.log.log:Syslog',
|
||||
},
|
||||
|
||||
# compatibility modules (deprecated)
|
||||
'compat.__doc__' : ('./compat/__init__.py', '__doc__'),
|
||||
'compat.doctest' : ('./compat/dep_doctest.py', 'doctest'),
|
||||
'compat.optparse' : ('./compat/dep_optparse.py', 'optparse'),
|
||||
'compat.textwrap' : ('./compat/dep_textwrap.py', 'textwrap'),
|
||||
'compat.subprocess' : ('./compat/dep_subprocess.py', 'subprocess'),
|
||||
})
|
||||
|
||||
|
||||
|
||||
compat = {
|
||||
'__doc__' : '_py.compat:__doc__',
|
||||
'doctest' : '_py.compat.dep_doctest:doctest',
|
||||
'optparse' : '_py.compat.dep_optparse:optparse',
|
||||
'textwrap' : '_py.compat.dep_textwrap:textwrap',
|
||||
'subprocess' : '_py.compat.dep_subprocess:subprocess',
|
||||
},
|
||||
))
|
||||
|
||||
123
py/_com.py
123
py/_com.py
@@ -1,123 +0,0 @@
|
||||
"""
|
||||
py lib plugins and plugin call management
|
||||
"""
|
||||
|
||||
import py
|
||||
import inspect
|
||||
|
||||
class MultiCall:
|
||||
""" execute a call into multiple python functions/methods. """
|
||||
|
||||
def __init__(self, methods, kwargs, firstresult=False):
|
||||
self.methods = methods[:]
|
||||
self.kwargs = kwargs.copy()
|
||||
self.kwargs['__multicall__'] = self
|
||||
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:
|
||||
pass # might be optional param
|
||||
return kwargs
|
||||
|
||||
def varnames(func):
|
||||
ismethod = inspect.ismethod(func)
|
||||
rawcode = py.code.getrawcode(func)
|
||||
try:
|
||||
return rawcode.co_varnames[ismethod:]
|
||||
except AttributeError:
|
||||
return ()
|
||||
|
||||
class Registry:
|
||||
"""
|
||||
Manage Plugins: register/unregister call calls to plugins.
|
||||
"""
|
||||
def __init__(self, plugins=None):
|
||||
if plugins is None:
|
||||
plugins = []
|
||||
self._plugins = plugins
|
||||
|
||||
def register(self, plugin):
|
||||
assert not isinstance(plugin, str)
|
||||
assert not plugin in self._plugins
|
||||
self._plugins.append(plugin)
|
||||
|
||||
def unregister(self, plugin):
|
||||
self._plugins.remove(plugin)
|
||||
|
||||
def isregistered(self, plugin):
|
||||
return plugin in self._plugins
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._plugins)
|
||||
|
||||
def listattr(self, attrname, plugins=None, extra=(), reverse=False):
|
||||
l = []
|
||||
if plugins is None:
|
||||
plugins = self._plugins
|
||||
candidates = list(plugins) + list(extra)
|
||||
for plugin in candidates:
|
||||
try:
|
||||
l.append(getattr(plugin, attrname))
|
||||
except AttributeError:
|
||||
continue
|
||||
if reverse:
|
||||
l.reverse()
|
||||
return l
|
||||
|
||||
class HookRelay:
|
||||
def __init__(self, hookspecs, registry):
|
||||
self._hookspecs = hookspecs
|
||||
self._registry = registry
|
||||
for name, method in vars(hookspecs).items():
|
||||
if name[:1] != "_":
|
||||
setattr(self, name, self._makecall(name))
|
||||
|
||||
def _makecall(self, name, extralookup=None):
|
||||
hookspecmethod = getattr(self._hookspecs, name)
|
||||
firstresult = getattr(hookspecmethod, 'firstresult', False)
|
||||
return HookCaller(self, name, firstresult=firstresult,
|
||||
extralookup=extralookup)
|
||||
|
||||
def _getmethods(self, name, extralookup=()):
|
||||
return self._registry.listattr(name, extra=extralookup)
|
||||
|
||||
def _performcall(self, name, multicall):
|
||||
return multicall.execute()
|
||||
|
||||
class HookCaller:
|
||||
def __init__(self, hookrelay, name, firstresult, extralookup=None):
|
||||
self.hookrelay = hookrelay
|
||||
self.name = name
|
||||
self.firstresult = firstresult
|
||||
self.extralookup = extralookup and [extralookup] or ()
|
||||
|
||||
def __repr__(self):
|
||||
return "<HookCaller %r>" %(self.name,)
|
||||
|
||||
def __call__(self, **kwargs):
|
||||
methods = self.hookrelay._getmethods(self.name, self.extralookup)
|
||||
mc = MultiCall(methods, kwargs, firstresult=self.firstresult)
|
||||
return self.hookrelay._performcall(self.name, mc)
|
||||
|
||||
comregistry = Registry([])
|
||||
@@ -1,2 +0,0 @@
|
||||
""" backports and additions of builtins """
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
try:
|
||||
reversed = reversed
|
||||
except NameError:
|
||||
def reversed(sequence):
|
||||
"""reversed(sequence) -> reverse iterator over values of the sequence
|
||||
|
||||
Return a reverse iterator
|
||||
"""
|
||||
if hasattr(sequence, '__reversed__'):
|
||||
return sequence.__reversed__()
|
||||
if not hasattr(sequence, '__getitem__'):
|
||||
raise TypeError("argument to reversed() must be a sequence")
|
||||
return reversed_iterator(sequence)
|
||||
|
||||
class reversed_iterator(object):
|
||||
|
||||
def __init__(self, seq):
|
||||
self.seq = seq
|
||||
self.remaining = len(seq)
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def next(self):
|
||||
i = self.remaining
|
||||
if i > 0:
|
||||
i -= 1
|
||||
item = self.seq[i]
|
||||
self.remaining = i
|
||||
return item
|
||||
raise StopIteration
|
||||
|
||||
def __length_hint__(self):
|
||||
return self.remaining
|
||||
|
||||
try:
|
||||
sorted = sorted
|
||||
except NameError:
|
||||
builtin_cmp = cmp # need to use cmp as keyword arg
|
||||
|
||||
def sorted(iterable, cmp=None, key=None, reverse=0):
|
||||
use_cmp = None
|
||||
if key is not None:
|
||||
if cmp is None:
|
||||
def use_cmp(x, y):
|
||||
return builtin_cmp(x[0], y[0])
|
||||
else:
|
||||
def use_cmp(x, y):
|
||||
return cmp(x[0], y[0])
|
||||
l = [(key(element), element) for element in iterable]
|
||||
else:
|
||||
if cmp is not None:
|
||||
use_cmp = cmp
|
||||
l = list(iterable)
|
||||
if use_cmp is not None:
|
||||
l.sort(use_cmp)
|
||||
else:
|
||||
l.sort()
|
||||
if reverse:
|
||||
l.reverse()
|
||||
if key is not None:
|
||||
return [element for (_, element) in l]
|
||||
return l
|
||||
|
||||
try:
|
||||
set, frozenset = set, frozenset
|
||||
except NameError:
|
||||
from sets import set, frozenset
|
||||
|
||||
# pass through
|
||||
enumerate = enumerate
|
||||
@@ -1,15 +0,0 @@
|
||||
|
||||
try:
|
||||
BaseException = BaseException
|
||||
except NameError:
|
||||
BaseException = Exception
|
||||
|
||||
try:
|
||||
GeneratorExit = GeneratorExit
|
||||
except NameError:
|
||||
class GeneratorExit(Exception):
|
||||
""" This exception is never raised, it is there to make it possible to
|
||||
write code compatible with CPython 2.5 even in lower CPython
|
||||
versions."""
|
||||
pass
|
||||
GeneratorExit.__module__ = 'exceptions'
|
||||
@@ -1,117 +0,0 @@
|
||||
import py
|
||||
import sys
|
||||
|
||||
if sys.version_info >= (3, 0):
|
||||
exec ("print_ = print ; exec_=exec")
|
||||
import builtins
|
||||
|
||||
# some backward compatibility helpers
|
||||
_basestring = str
|
||||
def _totext(obj, encoding):
|
||||
if isinstance(obj, bytes):
|
||||
obj = obj.decode(encoding)
|
||||
elif not isinstance(obj, str):
|
||||
obj = str(obj)
|
||||
return obj
|
||||
|
||||
def _isbytes(x):
|
||||
return isinstance(x, bytes)
|
||||
def _istext(x):
|
||||
return isinstance(x, str)
|
||||
|
||||
def _getimself(function):
|
||||
return getattr(function, '__self__', None)
|
||||
|
||||
def _getfuncdict(function):
|
||||
return getattr(function, "__dict__", None)
|
||||
|
||||
def execfile(fn, globs=None, locs=None):
|
||||
if globs is None:
|
||||
back = sys._getframe(1)
|
||||
globs = back.f_globals
|
||||
locs = back.f_locals
|
||||
del back
|
||||
elif locs is None:
|
||||
locs = globs
|
||||
fp = open(fn, "rb")
|
||||
try:
|
||||
source = fp.read()
|
||||
finally:
|
||||
fp.close()
|
||||
co = compile(source, fn, "exec", dont_inherit=True)
|
||||
exec_(co, globs, locs)
|
||||
|
||||
def callable(obj):
|
||||
return hasattr(obj, "__call__")
|
||||
|
||||
else:
|
||||
import __builtin__ as builtins
|
||||
_totext = unicode
|
||||
_basestring = basestring
|
||||
execfile = execfile
|
||||
callable = callable
|
||||
def _isbytes(x):
|
||||
return isinstance(x, str)
|
||||
def _istext(x):
|
||||
return isinstance(x, unicode)
|
||||
|
||||
def _getimself(function):
|
||||
return getattr(function, 'im_self', None)
|
||||
|
||||
def _getfuncdict(function):
|
||||
return getattr(function, "__dict__", None)
|
||||
|
||||
def print_(*args, **kwargs):
|
||||
""" minimal backport of py3k print statement. """
|
||||
sep = ' '
|
||||
if 'sep' in kwargs:
|
||||
sep = kwargs.pop('sep')
|
||||
end = '\n'
|
||||
if 'end' in kwargs:
|
||||
end = kwargs.pop('end')
|
||||
file = 'file' in kwargs and kwargs.pop('file') or sys.stdout
|
||||
if kwargs:
|
||||
args = ", ".join([str(x) for x in kwargs])
|
||||
raise TypeError("invalid keyword arguments: %s" % args)
|
||||
at_start = True
|
||||
for x in args:
|
||||
if not at_start:
|
||||
file.write(sep)
|
||||
file.write(str(x))
|
||||
at_start = False
|
||||
file.write(end)
|
||||
|
||||
def exec_(obj, globals=None, locals=None):
|
||||
""" minimal backport of py3k exec statement. """
|
||||
if globals is None:
|
||||
frame = sys._getframe(1)
|
||||
globals = frame.f_globals
|
||||
if locals is None:
|
||||
locals = frame.f_locals
|
||||
elif locals is None:
|
||||
locals = globals
|
||||
exec2(obj, globals, locals)
|
||||
|
||||
if sys.version_info >= (3,0):
|
||||
exec ("""
|
||||
def _reraise(cls, val, tb):
|
||||
assert hasattr(val, '__traceback__')
|
||||
raise val
|
||||
""")
|
||||
else:
|
||||
exec ("""
|
||||
def _reraise(cls, val, tb):
|
||||
raise cls, val, tb
|
||||
def exec2(obj, globals, locals):
|
||||
exec obj in globals, locals
|
||||
""")
|
||||
|
||||
def _tryimport(*names):
|
||||
""" return the first successfully imported module. """
|
||||
assert names
|
||||
for name in names:
|
||||
try:
|
||||
return __import__(name, None, None, '__doc__')
|
||||
except ImportError:
|
||||
excinfo = sys.exc_info()
|
||||
py.builtin._reraise(*excinfo)
|
||||
@@ -1 +0,0 @@
|
||||
#
|
||||
@@ -1,36 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""\
|
||||
py.cleanup [PATH]
|
||||
|
||||
Delete pyc file recursively, starting from PATH (which defaults to the current
|
||||
working directory). Don't follow links and don't recurse into directories with
|
||||
a ".".
|
||||
"""
|
||||
import py
|
||||
|
||||
def main():
|
||||
parser = py.std.optparse.OptionParser(usage=__doc__)
|
||||
parser.add_option("-e", "--remove", dest="ext", default=".pyc", action="store",
|
||||
help="remove files with the given comma-separated list of extensions"
|
||||
)
|
||||
parser.add_option("-n", "--dryrun", dest="dryrun", default=False,
|
||||
action="store_true",
|
||||
help="display would-be-removed filenames"
|
||||
)
|
||||
(options, args) = parser.parse_args()
|
||||
if not args:
|
||||
args = ["."]
|
||||
ext = options.ext.split(",")
|
||||
def shouldremove(p):
|
||||
return p.ext in ext
|
||||
|
||||
for arg in args:
|
||||
path = py.path.local(arg)
|
||||
py.builtin.print_("cleaning path", path, "of extensions", ext)
|
||||
for x in path.visit(shouldremove, lambda x: x.check(dotfile=0, link=0)):
|
||||
if options.dryrun:
|
||||
py.builtin.print_("would remove", x)
|
||||
else:
|
||||
py.builtin.print_("removing", x)
|
||||
x.remove()
|
||||
@@ -1,249 +0,0 @@
|
||||
import re
|
||||
import sys
|
||||
import parser
|
||||
|
||||
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()
|
||||
@@ -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))
|
||||
|
||||
@@ -1,83 +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.terminalwriter import ansi_print, 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")
|
||||
|
||||
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)
|
||||
@@ -1,82 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
invoke
|
||||
|
||||
py.rest filename1.txt directory
|
||||
|
||||
to generate html files from ReST.
|
||||
|
||||
It is also possible to generate pdf files using the --topdf option.
|
||||
|
||||
http://docutils.sourceforge.net/docs/user/rst/quickref.html
|
||||
|
||||
"""
|
||||
|
||||
import os, sys
|
||||
import py
|
||||
|
||||
if hasattr(sys.stdout, 'fileno') and os.isatty(sys.stdout.fileno()):
|
||||
def log(msg):
|
||||
print(msg)
|
||||
else:
|
||||
def log(msg):
|
||||
pass
|
||||
|
||||
|
||||
parser = py.std.optparse.OptionParser(usage=__doc__)
|
||||
parser.add_option("--topdf", action="store_true", dest="topdf", default=False,
|
||||
help="generate pdf files")
|
||||
parser.add_option("--stylesheet", dest="stylesheet", default=None,
|
||||
help="use specified latex style sheet")
|
||||
parser.add_option("--debug", action="store_true", dest="debug",
|
||||
default=False,
|
||||
help="print debug output and don't delete files")
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
from py.__.rest import directive, resthtml
|
||||
from py.__.rest.latex import process_rest_file, process_configfile
|
||||
except ImportError:
|
||||
e = sys.exc_info()[1]
|
||||
print(str(e))
|
||||
sys.exit(1)
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
if len(args) == 0:
|
||||
filenames = [py.path.svnwc()]
|
||||
else:
|
||||
filenames = [py.path.svnwc(x) for x in args]
|
||||
|
||||
if options.topdf:
|
||||
directive.set_backend_and_register_directives("latex")
|
||||
|
||||
for p in filenames:
|
||||
if not p.check():
|
||||
log("path %s not found, ignoring" % p)
|
||||
continue
|
||||
def fil(p):
|
||||
return p.check(fnmatch='*.txt', versioned=True)
|
||||
def rec(p):
|
||||
return p.check(dotfile=0)
|
||||
if p.check(dir=1):
|
||||
for x in p.visit(fil, rec):
|
||||
resthtml.process(x)
|
||||
elif p.check(file=1):
|
||||
if p.ext == ".rst2pdfconfig":
|
||||
directive.set_backend_and_register_directives("latex")
|
||||
process_configfile(p, options.debug)
|
||||
else:
|
||||
if options.topdf:
|
||||
cfg = p.new(ext=".rst2pdfconfig")
|
||||
if cfg.check():
|
||||
print("using config file %s" % (cfg, ))
|
||||
process_configfile(cfg, options.debug)
|
||||
else:
|
||||
process_rest_file(p.localpath,
|
||||
options.stylesheet,
|
||||
options.debug)
|
||||
else:
|
||||
resthtml.process(p)
|
||||
|
||||
@@ -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)
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
import py
|
||||
|
||||
def main():
|
||||
py.test.cmdline.main()
|
||||
@@ -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)
|
||||
@@ -1 +0,0 @@
|
||||
""" python inspection/code generation API """
|
||||
@@ -1,314 +0,0 @@
|
||||
"""
|
||||
Like _assertion.py but using builtin AST. It should replace _assertion.py
|
||||
eventually.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import ast
|
||||
|
||||
import py
|
||||
from py.__.code.assertion import _format_explanation, BuiltinAssertionError
|
||||
|
||||
|
||||
class Failure(Exception):
|
||||
"""Error found while interpreting AST."""
|
||||
|
||||
def __init__(self, explanation=""):
|
||||
self.cause = sys.exc_info()
|
||||
self.explanation = explanation
|
||||
|
||||
|
||||
def interpret(source, frame, should_fail=False):
|
||||
mod = ast.parse(source)
|
||||
visitor = DebugInterpreter(frame)
|
||||
try:
|
||||
visitor.visit(mod)
|
||||
except Failure:
|
||||
failure = sys.exc_info()[1]
|
||||
return getfailure(failure)
|
||||
if should_fail:
|
||||
return ("(assertion failed, but when it was re-run for "
|
||||
"printing intermediate values, it did not fail. Suggestions: "
|
||||
"compute assert expression before the assert or use --nomagic)")
|
||||
|
||||
def run(offending_line, frame=None):
|
||||
if frame is None:
|
||||
frame = py.code.Frame(sys._getframe(1))
|
||||
return interpret(offending_line, frame)
|
||||
|
||||
def getfailure(failure):
|
||||
explanation = _format_explanation(failure.explanation)
|
||||
value = failure.cause[1]
|
||||
if str(value):
|
||||
lines = explanation.splitlines()
|
||||
if not lines:
|
||||
lines.append("")
|
||||
lines[0] += " << {0}".format(value)
|
||||
explanation = "\n".join(lines)
|
||||
text = "{0}: {1}".format(failure.cause[0].__name__, explanation)
|
||||
if text.startswith("AssertionError: assert "):
|
||||
text = text[16:]
|
||||
return text
|
||||
|
||||
|
||||
operator_map = {
|
||||
ast.BitOr : "|",
|
||||
ast.BitXor : "^",
|
||||
ast.BitAnd : "&",
|
||||
ast.LShift : "<<",
|
||||
ast.RShift : ">>",
|
||||
ast.Add : "+",
|
||||
ast.Sub : "-",
|
||||
ast.Mult : "*",
|
||||
ast.Div : "/",
|
||||
ast.FloorDiv : "//",
|
||||
ast.Mod : "%",
|
||||
ast.Eq : "==",
|
||||
ast.NotEq : "!=",
|
||||
ast.Lt : "<",
|
||||
ast.LtE : "<=",
|
||||
ast.Gt : ">",
|
||||
ast.GtE : ">=",
|
||||
ast.Is : "is",
|
||||
ast.IsNot : "is not",
|
||||
ast.In : "in",
|
||||
ast.NotIn : "not in"
|
||||
}
|
||||
|
||||
unary_map = {
|
||||
ast.Not : "not {0}",
|
||||
ast.Invert : "~{0}",
|
||||
ast.USub : "-{0}",
|
||||
ast.UAdd : "+{0}"
|
||||
}
|
||||
|
||||
|
||||
class DebugInterpreter(ast.NodeVisitor):
|
||||
"""Interpret AST nodes to gleam useful debugging information."""
|
||||
|
||||
def __init__(self, frame):
|
||||
self.frame = frame
|
||||
|
||||
def generic_visit(self, node):
|
||||
# Fallback when we don't have a special implementation.
|
||||
if isinstance(node, ast.expr):
|
||||
mod = ast.Expression(node)
|
||||
co = self._compile(mod)
|
||||
try:
|
||||
result = self.frame.eval(co)
|
||||
except Exception:
|
||||
raise Failure()
|
||||
explanation = self.frame.repr(result)
|
||||
return explanation, result
|
||||
elif isinstance(node, ast.stmt):
|
||||
mod = ast.Module([node])
|
||||
co = self._compile(mod, "exec")
|
||||
try:
|
||||
self.frame.exec_(co)
|
||||
except Exception:
|
||||
raise Failure()
|
||||
return None, None
|
||||
else:
|
||||
raise AssertionError("can't handle {0}".format(node))
|
||||
|
||||
def _compile(self, source, mode="eval"):
|
||||
return compile(source, "<assertion interpretation>", mode)
|
||||
|
||||
def visit_Expr(self, expr):
|
||||
return self.visit(expr.value)
|
||||
|
||||
def visit_Module(self, mod):
|
||||
for stmt in mod.body:
|
||||
self.visit(stmt)
|
||||
|
||||
def visit_Name(self, name):
|
||||
explanation, result = self.generic_visit(name)
|
||||
# See if the name is local.
|
||||
source = "{0!r} in locals() is not globals()".format(name.id)
|
||||
co = self._compile(source)
|
||||
try:
|
||||
local = self.frame.eval(co)
|
||||
except Exception:
|
||||
# have to assume it isn't
|
||||
local = False
|
||||
if not local:
|
||||
return name.id, result
|
||||
return explanation, result
|
||||
|
||||
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 = "{0} {1} {2}".format(left_explanation, op_symbol,
|
||||
next_explanation)
|
||||
source = "__exprinfo_left {0} __exprinfo_right".format(op_symbol)
|
||||
co = self._compile(source)
|
||||
try:
|
||||
result = self.frame.eval(co, __exprinfo_left=left_result,
|
||||
__exprinfo_right=next_result)
|
||||
except Exception:
|
||||
raise Failure(explanation)
|
||||
else:
|
||||
got_result = True
|
||||
left_explanation, left_result = next_explanation, next_result
|
||||
return explanation, result
|
||||
|
||||
def visit_BoolOp(self, boolop):
|
||||
is_or = isinstance(boolop.op, ast.Or)
|
||||
explanations = []
|
||||
for operand in boolop.values:
|
||||
explanation, result = self.visit(operand)
|
||||
explanations.append(explanation)
|
||||
if result == is_or:
|
||||
break
|
||||
name = is_or and " or " or " and "
|
||||
explanation = "(" + name.join(explanations) + ")"
|
||||
return explanation, result
|
||||
|
||||
def visit_UnaryOp(self, unary):
|
||||
pattern = unary_map[unary.op.__class__]
|
||||
operand_explanation, operand_result = self.visit(unary.operand)
|
||||
explanation = pattern.format(operand_explanation)
|
||||
co = self._compile(pattern.format("__exprinfo_expr"))
|
||||
try:
|
||||
result = self.frame.eval(co, __exprinfo_expr=operand_result)
|
||||
except Exception:
|
||||
raise Failure(explanation)
|
||||
return explanation, result
|
||||
|
||||
def visit_BinOp(self, binop):
|
||||
left_explanation, left_result = self.visit(binop.left)
|
||||
right_explanation, right_result = self.visit(binop.right)
|
||||
symbol = operator_map[binop.op.__class__]
|
||||
explanation = "({0} {1} {2})".format(left_explanation, symbol,
|
||||
right_explanation)
|
||||
source = "__exprinfo_left {0} __exprinfo_right".format(symbol)
|
||||
co = self._compile(source)
|
||||
try:
|
||||
result = self.frame.eval(co, __exprinfo_left=left_result,
|
||||
__exprinfo_right=right_result)
|
||||
except Exception:
|
||||
raise Failure(explanation)
|
||||
return explanation, result
|
||||
|
||||
def visit_Call(self, call):
|
||||
func_explanation, func = self.visit(call.func)
|
||||
arg_explanations = []
|
||||
ns = {"__exprinfo_func" : func}
|
||||
arguments = []
|
||||
for arg in call.args:
|
||||
arg_explanation, arg_result = self.visit(arg)
|
||||
arg_name = "__exprinfo_{0}".format(len(ns))
|
||||
ns[arg_name] = arg_result
|
||||
arguments.append(arg_name)
|
||||
arg_explanations.append(arg_explanation)
|
||||
for keyword in call.keywords:
|
||||
arg_explanation, arg_result = self.visit(keyword.value)
|
||||
arg_name = "__exprinfo_{0}".format(len(ns))
|
||||
ns[arg_name] = arg_result
|
||||
keyword_source = "{0}={{0}}".format(keyword.id)
|
||||
arguments.append(keyword_source.format(arg_name))
|
||||
arg_explanations.append(keyword_source.format(arg_explanation))
|
||||
if call.starargs:
|
||||
arg_explanation, arg_result = self.visit(call.starargs)
|
||||
arg_name = "__exprinfo_star"
|
||||
ns[arg_name] = arg_result
|
||||
arguments.append("*{0}".format(arg_name))
|
||||
arg_explanations.append("*{0}".format(arg_explanation))
|
||||
if call.kwargs:
|
||||
arg_explanation, arg_result = self.visit(call.kwargs)
|
||||
arg_name = "__exprinfo_kwds"
|
||||
ns[arg_name] = arg_result
|
||||
arguments.append("**{0}".format(arg_name))
|
||||
arg_explanations.append("**{0}".format(arg_explanation))
|
||||
args_explained = ", ".join(arg_explanations)
|
||||
explanation = "{0}({1})".format(func_explanation, args_explained)
|
||||
args = ", ".join(arguments)
|
||||
source = "__exprinfo_func({0})".format(args)
|
||||
co = self._compile(source)
|
||||
try:
|
||||
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 = "{0}\n{{{0} = {1}\n}}"
|
||||
rep = self.frame.repr(result)
|
||||
explanation = pattern.format(rep, explanation)
|
||||
return explanation, result
|
||||
|
||||
def _is_builtin_name(self, name):
|
||||
pattern = "{0!r} not in globals() and {0!r} not in locals()"
|
||||
source = pattern.format(name.id)
|
||||
co = self._compile(source)
|
||||
try:
|
||||
return self.frame.eval(co)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def visit_Attribute(self, attr):
|
||||
if not isinstance(attr.ctx, ast.Load):
|
||||
return self.generic_visit(attr)
|
||||
source_explanation, source_result = self.visit(attr.value)
|
||||
explanation = "{0}.{1}".format(source_explanation, attr.attr)
|
||||
source = "__exprinfo_expr.{0}".format(attr.attr)
|
||||
co = self._compile(source)
|
||||
try:
|
||||
result = self.frame.eval(co, __exprinfo_expr=source_result)
|
||||
except Exception:
|
||||
raise Failure(explanation)
|
||||
# Check if the attr is from an instance.
|
||||
source = "{0!r} in getattr(__exprinfo_expr, '__dict__', {{}})"
|
||||
source = source.format(attr.attr)
|
||||
co = self._compile(source)
|
||||
try:
|
||||
from_instance = self.frame.eval(co, __exprinfo_expr=source_result)
|
||||
except Exception:
|
||||
from_instance = True
|
||||
if from_instance:
|
||||
rep = self.frame.repr(result)
|
||||
pattern = "{0}\n{{{0} = {1}\n}}"
|
||||
explanation = pattern.format(rep, explanation)
|
||||
return explanation, result
|
||||
|
||||
def visit_Assert(self, assrt):
|
||||
test_explanation, test_result = self.visit(assrt.test)
|
||||
if test_explanation.startswith("False\n{False =") and \
|
||||
test_explanation.endswith("\n"):
|
||||
test_explanation = test_explanation[15:-2]
|
||||
explanation = "assert {0}".format(test_explanation)
|
||||
if not test_result:
|
||||
try:
|
||||
raise BuiltinAssertionError
|
||||
except Exception:
|
||||
raise Failure(explanation)
|
||||
return explanation, test_result
|
||||
|
||||
def visit_Assign(self, assign):
|
||||
value_explanation, value_result = self.visit(assign.value)
|
||||
explanation = "... = {0}".format(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)
|
||||
mod = ast.Module([new_assign])
|
||||
co = self._compile(mod, "exec")
|
||||
try:
|
||||
self.frame.exec_(co, __exprinfo_expr=value_result)
|
||||
except Exception:
|
||||
raise Failure(explanation)
|
||||
return explanation, value_result
|
||||
@@ -1,558 +0,0 @@
|
||||
import py
|
||||
import sys, inspect
|
||||
from compiler import parse, ast, pycodegen
|
||||
from py.__.code.assertion import BuiltinAssertionError, _format_explanation
|
||||
|
||||
passthroughex = (KeyboardInterrupt, SystemExit, MemoryError)
|
||||
|
||||
class Failure:
|
||||
def __init__(self, node):
|
||||
self.exc, self.value, self.tb = sys.exc_info()
|
||||
self.node = node
|
||||
|
||||
class View(object):
|
||||
"""View base class.
|
||||
|
||||
If C is a subclass of View, then C(x) creates a proxy object around
|
||||
the object x. The actual class of the proxy is not C in general,
|
||||
but a *subclass* of C determined by the rules below. To avoid confusion
|
||||
we call view class the class of the proxy (a subclass of C, so of View)
|
||||
and object class the class of x.
|
||||
|
||||
Attributes and methods not found in the proxy are automatically read on x.
|
||||
Other operations like setting attributes are performed on the proxy, as
|
||||
determined by its view class. The object x is available from the proxy
|
||||
as its __obj__ attribute.
|
||||
|
||||
The view class selection is determined by the __view__ tuples and the
|
||||
optional __viewkey__ method. By default, the selected view class is the
|
||||
most specific subclass of C whose __view__ mentions the class of x.
|
||||
If no such subclass is found, the search proceeds with the parent
|
||||
object classes. For example, C(True) will first look for a subclass
|
||||
of C with __view__ = (..., bool, ...) and only if it doesn't find any
|
||||
look for one with __view__ = (..., int, ...), and then ..., object,...
|
||||
If everything fails the class C itself is considered to be the default.
|
||||
|
||||
Alternatively, the view class selection can be driven by another aspect
|
||||
of the object x, instead of the class of x, by overriding __viewkey__.
|
||||
See last example at the end of this module.
|
||||
"""
|
||||
|
||||
_viewcache = {}
|
||||
__view__ = ()
|
||||
|
||||
def __new__(rootclass, obj, *args, **kwds):
|
||||
self = object.__new__(rootclass)
|
||||
self.__obj__ = obj
|
||||
self.__rootclass__ = rootclass
|
||||
key = self.__viewkey__()
|
||||
try:
|
||||
self.__class__ = self._viewcache[key]
|
||||
except KeyError:
|
||||
self.__class__ = self._selectsubclass(key)
|
||||
return self
|
||||
|
||||
def __getattr__(self, attr):
|
||||
# attributes not found in the normal hierarchy rooted on View
|
||||
# are looked up in the object's real class
|
||||
return getattr(self.__obj__, attr)
|
||||
|
||||
def __viewkey__(self):
|
||||
return self.__obj__.__class__
|
||||
|
||||
def __matchkey__(self, key, subclasses):
|
||||
if inspect.isclass(key):
|
||||
keys = inspect.getmro(key)
|
||||
else:
|
||||
keys = [key]
|
||||
for key in keys:
|
||||
result = [C for C in subclasses if key in C.__view__]
|
||||
if result:
|
||||
return result
|
||||
return []
|
||||
|
||||
def _selectsubclass(self, key):
|
||||
subclasses = list(enumsubclasses(self.__rootclass__))
|
||||
for C in subclasses:
|
||||
if not isinstance(C.__view__, tuple):
|
||||
C.__view__ = (C.__view__,)
|
||||
choices = self.__matchkey__(key, subclasses)
|
||||
if not choices:
|
||||
return self.__rootclass__
|
||||
elif len(choices) == 1:
|
||||
return choices[0]
|
||||
else:
|
||||
# combine the multiple choices
|
||||
return type('?', tuple(choices), {})
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%r)' % (self.__rootclass__.__name__, self.__obj__)
|
||||
|
||||
|
||||
def enumsubclasses(cls):
|
||||
for subcls in cls.__subclasses__():
|
||||
for subsubclass in enumsubclasses(subcls):
|
||||
yield subsubclass
|
||||
yield cls
|
||||
|
||||
|
||||
class Interpretable(View):
|
||||
"""A parse tree node with a few extra methods."""
|
||||
explanation = None
|
||||
|
||||
def is_builtin(self, frame):
|
||||
return False
|
||||
|
||||
def eval(self, frame):
|
||||
# fall-back for unknown expression nodes
|
||||
try:
|
||||
expr = ast.Expression(self.__obj__)
|
||||
expr.filename = '<eval>'
|
||||
self.__obj__.filename = '<eval>'
|
||||
co = pycodegen.ExpressionCodeGenerator(expr).getCode()
|
||||
result = frame.eval(co)
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
raise Failure(self)
|
||||
self.result = result
|
||||
self.explanation = self.explanation or frame.repr(self.result)
|
||||
|
||||
def run(self, frame):
|
||||
# fall-back for unknown statement nodes
|
||||
try:
|
||||
expr = ast.Module(None, ast.Stmt([self.__obj__]))
|
||||
expr.filename = '<run>'
|
||||
co = pycodegen.ModuleCodeGenerator(expr).getCode()
|
||||
frame.exec_(co)
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
raise Failure(self)
|
||||
|
||||
def nice_explanation(self):
|
||||
return _format_explanation(self.explanation)
|
||||
|
||||
|
||||
class Name(Interpretable):
|
||||
__view__ = ast.Name
|
||||
|
||||
def is_local(self, frame):
|
||||
co = compile('%r in locals() is not globals()' % self.name, '?', 'eval')
|
||||
try:
|
||||
return frame.is_true(frame.eval(co))
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
return False
|
||||
|
||||
def is_global(self, frame):
|
||||
co = compile('%r in globals()' % self.name, '?', 'eval')
|
||||
try:
|
||||
return frame.is_true(frame.eval(co))
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
return False
|
||||
|
||||
def is_builtin(self, frame):
|
||||
co = compile('%r not in locals() and %r not in globals()' % (
|
||||
self.name, self.name), '?', 'eval')
|
||||
try:
|
||||
return frame.is_true(frame.eval(co))
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
return False
|
||||
|
||||
def eval(self, frame):
|
||||
super(Name, self).eval(frame)
|
||||
if not self.is_local(frame):
|
||||
self.explanation = self.name
|
||||
|
||||
class Compare(Interpretable):
|
||||
__view__ = ast.Compare
|
||||
|
||||
def eval(self, frame):
|
||||
expr = Interpretable(self.expr)
|
||||
expr.eval(frame)
|
||||
for operation, expr2 in self.ops:
|
||||
if hasattr(self, 'result'):
|
||||
# shortcutting in chained expressions
|
||||
if not frame.is_true(self.result):
|
||||
break
|
||||
expr2 = Interpretable(expr2)
|
||||
expr2.eval(frame)
|
||||
self.explanation = "%s %s %s" % (
|
||||
expr.explanation, operation, expr2.explanation)
|
||||
co = compile("__exprinfo_left %s __exprinfo_right" % operation,
|
||||
'?', 'eval')
|
||||
try:
|
||||
self.result = frame.eval(co, __exprinfo_left=expr.result,
|
||||
__exprinfo_right=expr2.result)
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
raise Failure(self)
|
||||
expr = expr2
|
||||
|
||||
class And(Interpretable):
|
||||
__view__ = ast.And
|
||||
|
||||
def eval(self, frame):
|
||||
explanations = []
|
||||
for expr in self.nodes:
|
||||
expr = Interpretable(expr)
|
||||
expr.eval(frame)
|
||||
explanations.append(expr.explanation)
|
||||
self.result = expr.result
|
||||
if not frame.is_true(expr.result):
|
||||
break
|
||||
self.explanation = '(' + ' and '.join(explanations) + ')'
|
||||
|
||||
class Or(Interpretable):
|
||||
__view__ = ast.Or
|
||||
|
||||
def eval(self, frame):
|
||||
explanations = []
|
||||
for expr in self.nodes:
|
||||
expr = Interpretable(expr)
|
||||
expr.eval(frame)
|
||||
explanations.append(expr.explanation)
|
||||
self.result = expr.result
|
||||
if frame.is_true(expr.result):
|
||||
break
|
||||
self.explanation = '(' + ' or '.join(explanations) + ')'
|
||||
|
||||
|
||||
# == Unary operations ==
|
||||
keepalive = []
|
||||
for astclass, astpattern in {
|
||||
ast.Not : 'not __exprinfo_expr',
|
||||
ast.Invert : '(~__exprinfo_expr)',
|
||||
}.items():
|
||||
|
||||
class UnaryArith(Interpretable):
|
||||
__view__ = astclass
|
||||
|
||||
def eval(self, frame, astpattern=astpattern,
|
||||
co=compile(astpattern, '?', 'eval')):
|
||||
expr = Interpretable(self.expr)
|
||||
expr.eval(frame)
|
||||
self.explanation = astpattern.replace('__exprinfo_expr',
|
||||
expr.explanation)
|
||||
try:
|
||||
self.result = frame.eval(co, __exprinfo_expr=expr.result)
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
raise Failure(self)
|
||||
|
||||
keepalive.append(UnaryArith)
|
||||
|
||||
# == Binary operations ==
|
||||
for astclass, astpattern in {
|
||||
ast.Add : '(__exprinfo_left + __exprinfo_right)',
|
||||
ast.Sub : '(__exprinfo_left - __exprinfo_right)',
|
||||
ast.Mul : '(__exprinfo_left * __exprinfo_right)',
|
||||
ast.Div : '(__exprinfo_left / __exprinfo_right)',
|
||||
ast.Mod : '(__exprinfo_left % __exprinfo_right)',
|
||||
ast.Power : '(__exprinfo_left ** __exprinfo_right)',
|
||||
}.items():
|
||||
|
||||
class BinaryArith(Interpretable):
|
||||
__view__ = astclass
|
||||
|
||||
def eval(self, frame, astpattern=astpattern,
|
||||
co=compile(astpattern, '?', 'eval')):
|
||||
left = Interpretable(self.left)
|
||||
left.eval(frame)
|
||||
right = Interpretable(self.right)
|
||||
right.eval(frame)
|
||||
self.explanation = (astpattern
|
||||
.replace('__exprinfo_left', left .explanation)
|
||||
.replace('__exprinfo_right', right.explanation))
|
||||
try:
|
||||
self.result = frame.eval(co, __exprinfo_left=left.result,
|
||||
__exprinfo_right=right.result)
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
raise Failure(self)
|
||||
|
||||
keepalive.append(BinaryArith)
|
||||
|
||||
|
||||
class CallFunc(Interpretable):
|
||||
__view__ = ast.CallFunc
|
||||
|
||||
def is_bool(self, frame):
|
||||
co = compile('isinstance(__exprinfo_value, bool)', '?', 'eval')
|
||||
try:
|
||||
return frame.is_true(frame.eval(co, __exprinfo_value=self.result))
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
return False
|
||||
|
||||
def eval(self, frame):
|
||||
node = Interpretable(self.node)
|
||||
node.eval(frame)
|
||||
explanations = []
|
||||
vars = {'__exprinfo_fn': node.result}
|
||||
source = '__exprinfo_fn('
|
||||
for a in self.args:
|
||||
if isinstance(a, ast.Keyword):
|
||||
keyword = a.name
|
||||
a = a.expr
|
||||
else:
|
||||
keyword = None
|
||||
a = Interpretable(a)
|
||||
a.eval(frame)
|
||||
argname = '__exprinfo_%d' % len(vars)
|
||||
vars[argname] = a.result
|
||||
if keyword is None:
|
||||
source += argname + ','
|
||||
explanations.append(a.explanation)
|
||||
else:
|
||||
source += '%s=%s,' % (keyword, argname)
|
||||
explanations.append('%s=%s' % (keyword, a.explanation))
|
||||
if self.star_args:
|
||||
star_args = Interpretable(self.star_args)
|
||||
star_args.eval(frame)
|
||||
argname = '__exprinfo_star'
|
||||
vars[argname] = star_args.result
|
||||
source += '*' + argname + ','
|
||||
explanations.append('*' + star_args.explanation)
|
||||
if self.dstar_args:
|
||||
dstar_args = Interpretable(self.dstar_args)
|
||||
dstar_args.eval(frame)
|
||||
argname = '__exprinfo_kwds'
|
||||
vars[argname] = dstar_args.result
|
||||
source += '**' + argname + ','
|
||||
explanations.append('**' + dstar_args.explanation)
|
||||
self.explanation = "%s(%s)" % (
|
||||
node.explanation, ', '.join(explanations))
|
||||
if source.endswith(','):
|
||||
source = source[:-1]
|
||||
source += ')'
|
||||
co = compile(source, '?', 'eval')
|
||||
try:
|
||||
self.result = frame.eval(co, **vars)
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
raise Failure(self)
|
||||
if not node.is_builtin(frame) or not self.is_bool(frame):
|
||||
r = frame.repr(self.result)
|
||||
self.explanation = '%s\n{%s = %s\n}' % (r, r, self.explanation)
|
||||
|
||||
class Getattr(Interpretable):
|
||||
__view__ = ast.Getattr
|
||||
|
||||
def eval(self, frame):
|
||||
expr = Interpretable(self.expr)
|
||||
expr.eval(frame)
|
||||
co = compile('__exprinfo_expr.%s' % self.attrname, '?', 'eval')
|
||||
try:
|
||||
self.result = frame.eval(co, __exprinfo_expr=expr.result)
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
raise Failure(self)
|
||||
self.explanation = '%s.%s' % (expr.explanation, self.attrname)
|
||||
# if the attribute comes from the instance, its value is interesting
|
||||
co = compile('hasattr(__exprinfo_expr, "__dict__") and '
|
||||
'%r in __exprinfo_expr.__dict__' % self.attrname,
|
||||
'?', 'eval')
|
||||
try:
|
||||
from_instance = frame.is_true(
|
||||
frame.eval(co, __exprinfo_expr=expr.result))
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
from_instance = True
|
||||
if from_instance:
|
||||
r = frame.repr(self.result)
|
||||
self.explanation = '%s\n{%s = %s\n}' % (r, r, self.explanation)
|
||||
|
||||
# == Re-interpretation of full statements ==
|
||||
|
||||
class Assert(Interpretable):
|
||||
__view__ = ast.Assert
|
||||
|
||||
def run(self, frame):
|
||||
test = Interpretable(self.test)
|
||||
test.eval(frame)
|
||||
# simplify 'assert False where False = ...'
|
||||
if (test.explanation.startswith('False\n{False = ') and
|
||||
test.explanation.endswith('\n}')):
|
||||
test.explanation = test.explanation[15:-2]
|
||||
# print the result as 'assert <explanation>'
|
||||
self.result = test.result
|
||||
self.explanation = 'assert ' + test.explanation
|
||||
if not frame.is_true(test.result):
|
||||
try:
|
||||
raise BuiltinAssertionError
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
raise Failure(self)
|
||||
|
||||
class Assign(Interpretable):
|
||||
__view__ = ast.Assign
|
||||
|
||||
def run(self, frame):
|
||||
expr = Interpretable(self.expr)
|
||||
expr.eval(frame)
|
||||
self.result = expr.result
|
||||
self.explanation = '... = ' + expr.explanation
|
||||
# fall-back-run the rest of the assignment
|
||||
ass = ast.Assign(self.nodes, ast.Name('__exprinfo_expr'))
|
||||
mod = ast.Module(None, ast.Stmt([ass]))
|
||||
mod.filename = '<run>'
|
||||
co = pycodegen.ModuleCodeGenerator(mod).getCode()
|
||||
try:
|
||||
frame.exec_(co, __exprinfo_expr=expr.result)
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
raise Failure(self)
|
||||
|
||||
class Discard(Interpretable):
|
||||
__view__ = ast.Discard
|
||||
|
||||
def run(self, frame):
|
||||
expr = Interpretable(self.expr)
|
||||
expr.eval(frame)
|
||||
self.result = expr.result
|
||||
self.explanation = expr.explanation
|
||||
|
||||
class Stmt(Interpretable):
|
||||
__view__ = ast.Stmt
|
||||
|
||||
def run(self, frame):
|
||||
for stmt in self.nodes:
|
||||
stmt = Interpretable(stmt)
|
||||
stmt.run(frame)
|
||||
|
||||
|
||||
def report_failure(e):
|
||||
explanation = e.node.nice_explanation()
|
||||
if explanation:
|
||||
explanation = ", in: " + explanation
|
||||
else:
|
||||
explanation = ""
|
||||
sys.stdout.write("%s: %s%s\n" % (e.exc.__name__, e.value, explanation))
|
||||
|
||||
def check(s, frame=None):
|
||||
if frame is None:
|
||||
import sys
|
||||
frame = sys._getframe(1)
|
||||
frame = py.code.Frame(frame)
|
||||
expr = parse(s, 'eval')
|
||||
assert isinstance(expr, ast.Expression)
|
||||
node = Interpretable(expr.node)
|
||||
try:
|
||||
node.eval(frame)
|
||||
except passthroughex:
|
||||
raise
|
||||
except Failure:
|
||||
e = sys.exc_info()[1]
|
||||
report_failure(e)
|
||||
else:
|
||||
if not frame.is_true(node.result):
|
||||
sys.stderr.write("assertion failed: %s\n" % node.nice_explanation())
|
||||
|
||||
|
||||
###########################################################
|
||||
# API / Entry points
|
||||
# #########################################################
|
||||
|
||||
def interpret(source, frame, should_fail=False):
|
||||
module = Interpretable(parse(source, 'exec').node)
|
||||
#print "got module", module
|
||||
if isinstance(frame, py.std.types.FrameType):
|
||||
frame = py.code.Frame(frame)
|
||||
try:
|
||||
module.run(frame)
|
||||
except Failure:
|
||||
e = sys.exc_info()[1]
|
||||
return getfailure(e)
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
if should_fail:
|
||||
return ("(assertion failed, but when it was re-run for "
|
||||
"printing intermediate values, it did not fail. Suggestions: "
|
||||
"compute assert expression before the assert or use --nomagic)")
|
||||
else:
|
||||
return None
|
||||
|
||||
def getmsg(excinfo):
|
||||
if isinstance(excinfo, tuple):
|
||||
excinfo = py.code.ExceptionInfo(excinfo)
|
||||
#frame, line = gettbline(tb)
|
||||
#frame = py.code.Frame(frame)
|
||||
#return interpret(line, frame)
|
||||
|
||||
tb = excinfo.traceback[-1]
|
||||
source = str(tb.statement).strip()
|
||||
x = interpret(source, tb.frame, should_fail=True)
|
||||
if not isinstance(x, str):
|
||||
raise TypeError("interpret returned non-string %r" % (x,))
|
||||
return x
|
||||
|
||||
def getfailure(e):
|
||||
explanation = e.node.nice_explanation()
|
||||
if str(e.value):
|
||||
lines = explanation.split('\n')
|
||||
lines[0] += " << %s" % (e.value,)
|
||||
explanation = '\n'.join(lines)
|
||||
text = "%s: %s" % (e.exc.__name__, explanation)
|
||||
if text.startswith('AssertionError: assert '):
|
||||
text = text[16:]
|
||||
return text
|
||||
|
||||
def run(s, frame=None):
|
||||
if frame is None:
|
||||
import sys
|
||||
frame = sys._getframe(1)
|
||||
frame = py.code.Frame(frame)
|
||||
module = Interpretable(parse(s, 'exec').node)
|
||||
try:
|
||||
module.run(frame)
|
||||
except Failure:
|
||||
e = sys.exc_info()[1]
|
||||
report_failure(e)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# example:
|
||||
def f():
|
||||
return 5
|
||||
def g():
|
||||
return 3
|
||||
def h(x):
|
||||
return 'never'
|
||||
check("f() * g() == 5")
|
||||
check("not f()")
|
||||
check("not (f() and g() or 0)")
|
||||
check("f() == g()")
|
||||
i = 4
|
||||
check("i == f()")
|
||||
check("len(f()) == 0")
|
||||
check("isinstance(2+3+4, float)")
|
||||
|
||||
run("x = i")
|
||||
check("x == 5")
|
||||
|
||||
run("assert not f(), 'oops'")
|
||||
run("a, b, c = 1, 2")
|
||||
run("a, b, c = f()")
|
||||
|
||||
check("max([f(),g()]) == 4")
|
||||
check("'hello'[g()] == 'h'")
|
||||
run("'guk%d' % h(f())")
|
||||
@@ -1,75 +0,0 @@
|
||||
import sys
|
||||
import py
|
||||
|
||||
BuiltinAssertionError = py.builtin.builtins.AssertionError
|
||||
|
||||
|
||||
def _format_explanation(explanation):
|
||||
# uck! See CallFunc for where \n{ and \n} escape sequences are used
|
||||
raw_lines = (explanation or '').split('\n')
|
||||
# escape newlines not followed by { and }
|
||||
lines = [raw_lines[0]]
|
||||
for l in raw_lines[1:]:
|
||||
if l.startswith('{') or l.startswith('}'):
|
||||
lines.append(l)
|
||||
else:
|
||||
lines[-1] += '\\n' + l
|
||||
|
||||
result = lines[:1]
|
||||
stack = [0]
|
||||
stackcnt = [0]
|
||||
for line in lines[1:]:
|
||||
if line.startswith('{'):
|
||||
if stackcnt[-1]:
|
||||
s = 'and '
|
||||
else:
|
||||
s = 'where '
|
||||
stack.append(len(result))
|
||||
stackcnt[-1] += 1
|
||||
stackcnt.append(0)
|
||||
result.append(' +' + ' '*(len(stack)-1) + s + line[1:])
|
||||
else:
|
||||
assert line.startswith('}')
|
||||
stack.pop()
|
||||
stackcnt.pop()
|
||||
result[stack[-1]] += line[1:]
|
||||
assert len(stack) == 1
|
||||
return '\n'.join(result)
|
||||
|
||||
|
||||
if sys.version_info >= (2, 6):
|
||||
from py.__.code._assertionnew import interpret
|
||||
else:
|
||||
from py.__.code._assertionold import interpret
|
||||
|
||||
|
||||
class AssertionError(BuiltinAssertionError):
|
||||
|
||||
def __init__(self, *args):
|
||||
BuiltinAssertionError.__init__(self, *args)
|
||||
if args:
|
||||
try:
|
||||
self.msg = str(args[0])
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
self.msg = "<[broken __repr__] %s at %0xd>" %(
|
||||
args[0].__class__, id(args[0]))
|
||||
else:
|
||||
f = py.code.Frame(sys._getframe(1))
|
||||
try:
|
||||
source = f.statement
|
||||
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 = interpret(source, f, should_fail=True)
|
||||
if not self.args:
|
||||
self.args = (self.msg,)
|
||||
else:
|
||||
self.msg = None
|
||||
|
||||
if sys.version_info > (3, 0):
|
||||
AssertionError.__module__ = "builtins"
|
||||
764
py/code/code.py
764
py/code/code.py
@@ -1,764 +0,0 @@
|
||||
import py
|
||||
import sys
|
||||
|
||||
builtin_repr = repr
|
||||
|
||||
repr = py.builtin._tryimport('repr', 'reprlib')
|
||||
|
||||
class Code(object):
|
||||
""" wrapper around Python code objects """
|
||||
def __init__(self, rawcode):
|
||||
rawcode = py.code.getrawcode(rawcode)
|
||||
self.raw = rawcode
|
||||
try:
|
||||
self.filename = rawcode.co_filename
|
||||
self.firstlineno = rawcode.co_firstlineno - 1
|
||||
self.name = rawcode.co_name
|
||||
except AttributeError:
|
||||
raise TypeError("not a code object: %r" %(rawcode,))
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.raw == other.raw
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
def new(self, rec=False, **kwargs):
|
||||
""" return new code object with modified attributes.
|
||||
if rec-cursive is true then dive into code
|
||||
objects contained in co_consts.
|
||||
"""
|
||||
names = [x for x in dir(self.raw) if x[:3] == 'co_']
|
||||
for name in kwargs:
|
||||
if name not in names:
|
||||
raise TypeError("unknown code attribute: %r" %(name, ))
|
||||
if rec and hasattr(self.raw, 'co_consts'): # jython
|
||||
newconstlist = []
|
||||
co = self.raw
|
||||
cotype = type(co)
|
||||
for c in co.co_consts:
|
||||
if isinstance(c, cotype):
|
||||
c = self.__class__(c).new(rec=True, **kwargs)
|
||||
newconstlist.append(c)
|
||||
return self.new(rec=False, co_consts=tuple(newconstlist), **kwargs)
|
||||
for name in names:
|
||||
if name not in kwargs:
|
||||
kwargs[name] = getattr(self.raw, name)
|
||||
arglist = [
|
||||
kwargs['co_argcount'],
|
||||
kwargs['co_nlocals'],
|
||||
kwargs.get('co_stacksize', 0), # jython
|
||||
kwargs.get('co_flags', 0), # jython
|
||||
kwargs.get('co_code', ''), # jython
|
||||
kwargs.get('co_consts', ()), # jython
|
||||
kwargs.get('co_names', []), #
|
||||
kwargs['co_varnames'],
|
||||
kwargs['co_filename'],
|
||||
kwargs['co_name'],
|
||||
kwargs['co_firstlineno'],
|
||||
kwargs.get('co_lnotab', ''), #jython
|
||||
kwargs.get('co_freevars', None), #jython
|
||||
kwargs.get('co_cellvars', None), # jython
|
||||
]
|
||||
if sys.version_info >= (3,0):
|
||||
arglist.insert(1, kwargs['co_kwonlyargcount'])
|
||||
return self.raw.__class__(*arglist)
|
||||
else:
|
||||
return py.std.new.code(*arglist)
|
||||
|
||||
def path(self):
|
||||
""" return a py.path.local object pointing to the source code """
|
||||
fn = self.raw.co_filename
|
||||
try:
|
||||
return fn.__path__
|
||||
except AttributeError:
|
||||
p = py.path.local(self.raw.co_filename)
|
||||
if not p.check(file=1):
|
||||
# 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):
|
||||
""" return a py.code.Source object for the full source file of the code
|
||||
"""
|
||||
from py.__.code import source
|
||||
full, _ = source.findsource(self.raw)
|
||||
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
|
||||
"""
|
||||
# return source only for that part of code
|
||||
return py.code.Source(self.raw)
|
||||
|
||||
def getargs(self):
|
||||
""" return a tuple with the argument names for the code object
|
||||
"""
|
||||
# handfull shortcut for getting args
|
||||
raw = self.raw
|
||||
return raw.co_varnames[:raw.co_argcount]
|
||||
|
||||
class Frame(object):
|
||||
"""Wrapper around a Python frame holding f_locals and f_globals
|
||||
in which expressions can be evaluated."""
|
||||
|
||||
def __init__(self, frame):
|
||||
self.code = py.code.Code(frame.f_code)
|
||||
self.lineno = frame.f_lineno - 1
|
||||
self.f_globals = frame.f_globals
|
||||
self.f_locals = frame.f_locals
|
||||
self.raw = frame
|
||||
|
||||
def statement(self):
|
||||
if self.code.fullsource is None:
|
||||
return py.code.Source("")
|
||||
return self.code.fullsource.getstatement(self.lineno)
|
||||
statement = property(statement, None, None,
|
||||
"statement this frame is at")
|
||||
|
||||
def eval(self, code, **vars):
|
||||
""" evaluate 'code' in the frame
|
||||
|
||||
'vars' are optional additional local variables
|
||||
|
||||
returns the result of the evaluation
|
||||
"""
|
||||
f_locals = self.f_locals.copy()
|
||||
f_locals.update(vars)
|
||||
return eval(code, self.f_globals, f_locals)
|
||||
|
||||
def exec_(self, code, **vars):
|
||||
""" exec 'code' in the frame
|
||||
|
||||
'vars' are optiona; additional local variables
|
||||
"""
|
||||
f_locals = self.f_locals.copy()
|
||||
f_locals.update(vars)
|
||||
py.builtin.exec_(code, self.f_globals, f_locals )
|
||||
|
||||
def repr(self, object):
|
||||
""" return a 'safe' (non-recursive, one-line) string repr for 'object'
|
||||
"""
|
||||
return safe_repr(object)
|
||||
|
||||
def is_true(self, object):
|
||||
return object
|
||||
|
||||
def getargs(self):
|
||||
""" return a list of tuples (name, value) for all arguments
|
||||
"""
|
||||
retval = []
|
||||
for arg in self.code.getargs():
|
||||
try:
|
||||
retval.append((arg, self.f_locals[arg]))
|
||||
except KeyError:
|
||||
pass # this can occur when using Psyco
|
||||
return retval
|
||||
|
||||
class TracebackEntry(object):
|
||||
""" a single entry in a traceback """
|
||||
|
||||
exprinfo = None
|
||||
|
||||
def __init__(self, rawentry):
|
||||
self._rawentry = rawentry
|
||||
self.frame = py.code.Frame(rawentry.tb_frame)
|
||||
# Ugh. 2.4 and 2.5 differs here when encountering
|
||||
# multi-line statements. Not sure about the solution, but
|
||||
# should be portable
|
||||
self.lineno = rawentry.tb_lineno - 1
|
||||
self.relline = self.lineno - self.frame.code.firstlineno
|
||||
|
||||
def __repr__(self):
|
||||
return "<TracebackEntry %s:%d>" %(self.frame.code.path, self.lineno+1)
|
||||
|
||||
def statement(self):
|
||||
""" return a py.code.Source object for the current statement """
|
||||
source = self.frame.code.fullsource
|
||||
return source.getstatement(self.lineno)
|
||||
statement = property(statement, None, None,
|
||||
"statement of this traceback entry.")
|
||||
|
||||
def path(self):
|
||||
return self.frame.code.path
|
||||
path = property(path, None, None, "path to the full source code")
|
||||
|
||||
def getlocals(self):
|
||||
return self.frame.f_locals
|
||||
locals = property(getlocals, None, None, "locals of underlaying frame")
|
||||
|
||||
def reinterpret(self):
|
||||
"""Reinterpret the failing statement and returns a detailed information
|
||||
about what operations are performed."""
|
||||
if self.exprinfo is None:
|
||||
from py.__.code import assertion
|
||||
source = str(self.statement).strip()
|
||||
x = assertion.interpret(source, self.frame, should_fail=True)
|
||||
if not isinstance(x, str):
|
||||
raise TypeError("interpret returned non-string %r" % (x,))
|
||||
self.exprinfo = x
|
||||
return self.exprinfo
|
||||
|
||||
def getfirstlinesource(self):
|
||||
return self.frame.code.firstlineno
|
||||
|
||||
def getsource(self):
|
||||
""" return failing source code. """
|
||||
source = self.frame.code.fullsource
|
||||
if source is None:
|
||||
return None
|
||||
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.
|
||||
# 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(':'):
|
||||
end = i + 1
|
||||
break
|
||||
return source[start:end]
|
||||
source = property(getsource)
|
||||
|
||||
def ishidden(self):
|
||||
""" 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):
|
||||
raise
|
||||
except:
|
||||
return False
|
||||
|
||||
def __str__(self):
|
||||
try:
|
||||
fn = str(self.path)
|
||||
except py.error.Error:
|
||||
fn = '???'
|
||||
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)
|
||||
|
||||
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.
|
||||
"""
|
||||
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:
|
||||
yield self.Entry(cur)
|
||||
cur = cur.tb_next
|
||||
list.__init__(self, f(tb))
|
||||
else:
|
||||
list.__init__(self, tb)
|
||||
|
||||
def cut(self, path=None, lineno=None, firstlineno=None, excludepath=None):
|
||||
""" return a Traceback instance wrapping part of this Traceback
|
||||
|
||||
by provding any combination of path, lineno and firstlineno, the
|
||||
first frame to start the to-be-returned traceback is determined
|
||||
|
||||
this allows cutting the first part of a Traceback instance e.g.
|
||||
for formatting reasons (removing some uninteresting bits that deal
|
||||
with handling of the exception/traceback)
|
||||
"""
|
||||
for x in self:
|
||||
code = x.frame.code
|
||||
codepath = code.path
|
||||
if ((path is None or codepath == path) and
|
||||
(excludepath is None or (hasattr(codepath, 'relto') 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)
|
||||
return self
|
||||
|
||||
def __getitem__(self, key):
|
||||
val = super(Traceback, self).__getitem__(key)
|
||||
if isinstance(key, type(slice(0))):
|
||||
val = self.__class__(val)
|
||||
return val
|
||||
|
||||
def filter(self, fn=lambda x: not x.ishidden()):
|
||||
""" return a Traceback instance with certain items removed
|
||||
|
||||
fn is a function that gets a single argument, a TracebackItem
|
||||
instance, and should return True when the item should be added
|
||||
to the Traceback, False when not
|
||||
|
||||
by default this removes all the TracebackItems which are hidden
|
||||
(see ishidden() above)
|
||||
"""
|
||||
return Traceback(filter(fn, self))
|
||||
|
||||
def getcrashentry(self):
|
||||
""" return last non-hidden traceback entry that lead
|
||||
to the exception of a traceback.
|
||||
"""
|
||||
tb = self.filter()
|
||||
if not tb:
|
||||
tb = self
|
||||
return tb[-1]
|
||||
|
||||
def recursionindex(self):
|
||||
""" return the index of the frame/TracebackItem where recursion
|
||||
originates if appropriate, None if no recursion occurred
|
||||
"""
|
||||
cache = {}
|
||||
for i, entry in enumerate(self):
|
||||
key = entry.frame.code.path, entry.lineno
|
||||
#print "checking for recursion at", key
|
||||
l = cache.setdefault(key, [])
|
||||
if l:
|
||||
f = entry.frame
|
||||
loc = f.f_locals
|
||||
for otherloc in l:
|
||||
if f.is_true(f.eval(co_equal,
|
||||
__recursioncache_locals_1=loc,
|
||||
__recursioncache_locals_2=otherloc)):
|
||||
return i
|
||||
l.append(entry.frame.f_locals)
|
||||
return None
|
||||
|
||||
co_equal = compile('__recursioncache_locals_1 == __recursioncache_locals_2',
|
||||
'?', 'eval')
|
||||
|
||||
class ExceptionInfo(object):
|
||||
""" wraps sys.exc_info() objects and offers
|
||||
help for navigating the traceback.
|
||||
"""
|
||||
_striptext = ''
|
||||
def __init__(self, tup=None, exprinfo=None):
|
||||
# NB. all attributes are private! Subclasses or other
|
||||
# ExceptionInfo-like classes may have different attributes.
|
||||
if tup is None:
|
||||
tup = sys.exc_info()
|
||||
if exprinfo is None and isinstance(tup[1], py.code._AssertionError):
|
||||
exprinfo = getattr(tup[1], 'msg', None)
|
||||
if exprinfo is None:
|
||||
exprinfo = str(tup[1])
|
||||
if exprinfo and exprinfo.startswith('assert '):
|
||||
self._striptext = 'AssertionError: '
|
||||
self._excinfo = tup
|
||||
self.type, self.value, tb = self._excinfo
|
||||
self.typename = self.type.__name__
|
||||
self.traceback = py.code.Traceback(tb)
|
||||
|
||||
def __repr__(self):
|
||||
return "<ExceptionInfo %s tblen=%d>" % (self.typename, len(self.traceback))
|
||||
|
||||
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
|
||||
removed from the beginning)
|
||||
"""
|
||||
lines = py.std.traceback.format_exception_only(self.type, self.value)
|
||||
text = ''.join(lines)
|
||||
text = text.rstrip()
|
||||
if tryshort:
|
||||
if text.startswith(self._striptext):
|
||||
text = text[len(self._striptext):]
|
||||
return text
|
||||
|
||||
def errisinstance(self, exc):
|
||||
""" return True if the exception is an instance of exc """
|
||||
return isinstance(self.value, exc)
|
||||
|
||||
def _getreprcrash(self):
|
||||
exconly = self.exconly(tryshort=True)
|
||||
entry = self.traceback.getcrashentry()
|
||||
path, lineno = entry.path, entry.lineno
|
||||
reprcrash = ReprFileLocation(path, lineno+1, exconly)
|
||||
return reprcrash
|
||||
|
||||
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
|
||||
tbfilter: hide entries (where __tracebackhide__ is true)
|
||||
"""
|
||||
fmt = FormattedExcinfo(showlocals=showlocals, style=style,
|
||||
abspath=abspath, tbfilter=tbfilter, funcargs=funcargs)
|
||||
return fmt.repr_excinfo(self)
|
||||
|
||||
def __str__(self):
|
||||
entry = self.traceback[-1]
|
||||
loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
|
||||
return str(loc)
|
||||
|
||||
class FormattedExcinfo(object):
|
||||
""" presenting information about failing Functions and Generators. """
|
||||
# for traceback entries
|
||||
flow_marker = ">"
|
||||
fail_marker = "E"
|
||||
|
||||
def __init__(self, showlocals=False, style="long", abspath=True, tbfilter=True, funcargs=False):
|
||||
self.showlocals = showlocals
|
||||
self.style = style
|
||||
self.tbfilter = tbfilter
|
||||
self.funcargs = funcargs
|
||||
self.abspath = abspath
|
||||
|
||||
def _getindent(self, source):
|
||||
# figure out indent for given source
|
||||
try:
|
||||
s = str(source.getstatement(len(source)-1))
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
try:
|
||||
s = str(source[-1])
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
return 0
|
||||
return 4 + (len(s) - len(s.lstrip()))
|
||||
|
||||
def _getentrysource(self, entry):
|
||||
source = entry.getsource()
|
||||
if source is not None:
|
||||
source = source.deindent()
|
||||
return source
|
||||
|
||||
def _saferepr(self, obj):
|
||||
return safe_repr(obj)
|
||||
|
||||
def repr_args(self, entry):
|
||||
if self.funcargs:
|
||||
args = []
|
||||
for argname, argvalue in entry.frame.getargs():
|
||||
args.append((argname, self._saferepr(argvalue)))
|
||||
return ReprFuncArgs(args)
|
||||
|
||||
def get_source(self, source, line_index=-1, excinfo=None):
|
||||
""" return formatted and marked up source lines. """
|
||||
lines = []
|
||||
if source is None:
|
||||
source = py.code.Source("???")
|
||||
line_index = 0
|
||||
if line_index < 0:
|
||||
line_index += len(source)
|
||||
for i in range(len(source)):
|
||||
if i == line_index:
|
||||
prefix = self.flow_marker + " "
|
||||
else:
|
||||
prefix = " "
|
||||
line = prefix + source[i]
|
||||
lines.append(line)
|
||||
if excinfo is not None:
|
||||
indent = self._getindent(source)
|
||||
lines.extend(self.get_exconly(excinfo, indent=indent, markall=True))
|
||||
return lines
|
||||
|
||||
def get_exconly(self, excinfo, indent=4, markall=False):
|
||||
lines = []
|
||||
indent = " " * indent
|
||||
# get the real exception information out
|
||||
exlines = excinfo.exconly(tryshort=True).split('\n')
|
||||
failindent = self.fail_marker + indent[1:]
|
||||
for line in exlines:
|
||||
lines.append(failindent + line)
|
||||
if not markall:
|
||||
failindent = indent
|
||||
return lines
|
||||
|
||||
def repr_locals(self, locals):
|
||||
if self.showlocals:
|
||||
lines = []
|
||||
keys = list(locals)
|
||||
keys.sort()
|
||||
for name in keys:
|
||||
value = locals[name]
|
||||
if name == '__builtins__':
|
||||
lines.append("__builtins__ = <builtins>")
|
||||
else:
|
||||
# This formatting could all be handled by the
|
||||
# _repr() function, which is only repr.Repr in
|
||||
# disguise, so is very configurable.
|
||||
str_repr = self._saferepr(value)
|
||||
#if len(str_repr) < 70 or not isinstance(value,
|
||||
# (list, tuple, dict)):
|
||||
lines.append("%-10s = %s" %(name, str_repr))
|
||||
#else:
|
||||
# self._line("%-10s =\\" % (name,))
|
||||
# # XXX
|
||||
# py.std.pprint.pprint(value, stream=self.excinfowriter)
|
||||
return ReprLocals(lines)
|
||||
|
||||
def repr_traceback_entry(self, entry, excinfo=None):
|
||||
# excinfo is not None if this is the last tb entry
|
||||
source = self._getentrysource(entry)
|
||||
if source is None:
|
||||
source = py.code.Source("???")
|
||||
line_index = 0
|
||||
else:
|
||||
line_index = entry.lineno - entry.getfirstlinesource()
|
||||
|
||||
lines = []
|
||||
if self.style == "long":
|
||||
reprargs = self.repr_args(entry)
|
||||
lines.extend(self.get_source(source, line_index, excinfo))
|
||||
message = excinfo and excinfo.typename or ""
|
||||
path = self._makepath(entry.path)
|
||||
filelocrepr = ReprFileLocation(path, entry.lineno+1, message)
|
||||
localsrepr = self.repr_locals(entry.locals)
|
||||
return ReprEntry(lines, reprargs, localsrepr, filelocrepr)
|
||||
else:
|
||||
if self.style == "short":
|
||||
line = source[line_index].lstrip()
|
||||
lines.append(' File "%s", line %d, in %s' % (
|
||||
entry.path.basename, entry.lineno+1, entry.name))
|
||||
lines.append(" " + line)
|
||||
if excinfo:
|
||||
lines.extend(self.get_exconly(excinfo, indent=4))
|
||||
return ReprEntry(lines, None, None, None)
|
||||
|
||||
def _makepath(self, path):
|
||||
if not self.abspath:
|
||||
np = py.path.local().bestrelpath(path)
|
||||
if len(np) < len(str(path)):
|
||||
path = np
|
||||
return path
|
||||
|
||||
def repr_traceback(self, excinfo):
|
||||
traceback = excinfo.traceback
|
||||
if self.tbfilter:
|
||||
traceback = traceback.filter()
|
||||
recursionindex = None
|
||||
if excinfo.errisinstance(RuntimeError):
|
||||
recursionindex = traceback.recursionindex()
|
||||
last = traceback[-1]
|
||||
entries = []
|
||||
extraline = None
|
||||
for index, entry in enumerate(traceback):
|
||||
einfo = (last == entry) and excinfo or None
|
||||
reprentry = self.repr_traceback_entry(entry, einfo)
|
||||
entries.append(reprentry)
|
||||
if index == recursionindex:
|
||||
extraline = "!!! Recursion detected (same locals & position)"
|
||||
break
|
||||
return ReprTraceback(entries, extraline, style=self.style)
|
||||
|
||||
def repr_excinfo(self, excinfo):
|
||||
reprtraceback = self.repr_traceback(excinfo)
|
||||
reprcrash = excinfo._getreprcrash()
|
||||
return ReprExceptionInfo(reprtraceback, reprcrash)
|
||||
|
||||
class TerminalRepr:
|
||||
def __str__(self):
|
||||
tw = py.io.TerminalWriter(stringio=True)
|
||||
self.toterminal(tw)
|
||||
return tw.stringio.getvalue().strip()
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s instance at %0x>" %(self.__class__, id(self))
|
||||
|
||||
class ReprExceptionInfo(TerminalRepr):
|
||||
def __init__(self, reprtraceback, reprcrash):
|
||||
self.reprtraceback = reprtraceback
|
||||
self.reprcrash = reprcrash
|
||||
self.sections = []
|
||||
|
||||
def addsection(self, name, content, sep="-"):
|
||||
self.sections.append((name, content, sep))
|
||||
|
||||
def toterminal(self, tw):
|
||||
self.reprtraceback.toterminal(tw)
|
||||
for name, content, sep in self.sections:
|
||||
tw.sep(sep, name)
|
||||
tw.line(content)
|
||||
|
||||
class ReprTraceback(TerminalRepr):
|
||||
entrysep = "_ "
|
||||
|
||||
def __init__(self, reprentries, extraline, style):
|
||||
self.reprentries = reprentries
|
||||
self.extraline = extraline
|
||||
self.style = style
|
||||
|
||||
def toterminal(self, tw):
|
||||
sepok = False
|
||||
for entry in self.reprentries:
|
||||
if self.style == "long":
|
||||
if sepok:
|
||||
tw.sep(self.entrysep)
|
||||
tw.line("")
|
||||
sepok = True
|
||||
entry.toterminal(tw)
|
||||
if self.extraline:
|
||||
tw.line(self.extraline)
|
||||
|
||||
class ReprEntry(TerminalRepr):
|
||||
localssep = "_ "
|
||||
|
||||
def __init__(self, lines, reprfuncargs, reprlocals, filelocrepr):
|
||||
self.lines = lines
|
||||
self.reprfuncargs = reprfuncargs
|
||||
self.reprlocals = reprlocals
|
||||
self.reprfileloc = filelocrepr
|
||||
|
||||
def toterminal(self, tw):
|
||||
if self.reprfuncargs:
|
||||
self.reprfuncargs.toterminal(tw)
|
||||
for line in self.lines:
|
||||
red = line.startswith("E ")
|
||||
tw.line(line, bold=True, red=red)
|
||||
if self.reprlocals:
|
||||
#tw.sep(self.localssep, "Locals")
|
||||
tw.line("")
|
||||
self.reprlocals.toterminal(tw)
|
||||
if self.reprfileloc:
|
||||
tw.line("")
|
||||
self.reprfileloc.toterminal(tw)
|
||||
|
||||
def __str__(self):
|
||||
return "%s\n%s\n%s" % ("\n".join(self.lines),
|
||||
self.reprlocals,
|
||||
self.reprfileloc)
|
||||
|
||||
class ReprFileLocation(TerminalRepr):
|
||||
def __init__(self, path, lineno, message):
|
||||
self.path = str(path)
|
||||
self.lineno = lineno
|
||||
self.message = message
|
||||
|
||||
def toterminal(self, tw):
|
||||
# filename and lineno output for each entry,
|
||||
# using an output format that most editors unterstand
|
||||
msg = self.message
|
||||
i = msg.find("\n")
|
||||
if i != -1:
|
||||
msg = msg[:i]
|
||||
tw.line("%s:%s: %s" %(self.path, self.lineno, msg))
|
||||
|
||||
class ReprLocals(TerminalRepr):
|
||||
def __init__(self, lines):
|
||||
self.lines = lines
|
||||
|
||||
def toterminal(self, tw):
|
||||
for line in self.lines:
|
||||
tw.line(line)
|
||||
|
||||
class ReprFuncArgs(TerminalRepr):
|
||||
def __init__(self, args):
|
||||
self.args = args
|
||||
|
||||
def toterminal(self, tw):
|
||||
if self.args:
|
||||
linesofar = ""
|
||||
for name, value in self.args:
|
||||
ns = "%s = %s" %(name, value)
|
||||
if len(ns) + len(linesofar) + 2 > tw.fullwidth:
|
||||
if linesofar:
|
||||
tw.line(linesofar)
|
||||
linesofar = ns
|
||||
else:
|
||||
if linesofar:
|
||||
linesofar += ", " + ns
|
||||
else:
|
||||
linesofar = ns
|
||||
if linesofar:
|
||||
tw.line(linesofar)
|
||||
tw.line("")
|
||||
|
||||
|
||||
|
||||
class SafeRepr(repr.Repr):
|
||||
""" subclass of repr.Repr that limits the resulting size of repr()
|
||||
and includes information on exceptions raised during the call.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
repr.Repr.__init__(self, *args, **kwargs)
|
||||
self.maxstring = 240 # 3 * 80 chars
|
||||
self.maxother = 160 # 2 * 80 chars
|
||||
|
||||
def repr(self, x):
|
||||
return self._callhelper(repr.Repr.repr, self, x)
|
||||
|
||||
def repr_instance(self, x, level):
|
||||
return self._callhelper(builtin_repr, x)
|
||||
|
||||
def _callhelper(self, call, x, *args):
|
||||
try:
|
||||
# Try the vanilla repr and make sure that the result is a string
|
||||
s = call(x, *args)
|
||||
except (KeyboardInterrupt, MemoryError, SystemExit):
|
||||
raise
|
||||
except:
|
||||
cls, e, tb = sys.exc_info()
|
||||
try:
|
||||
exc_name = cls.__name__
|
||||
except:
|
||||
exc_name = 'unknown'
|
||||
try:
|
||||
exc_info = str(e)
|
||||
except:
|
||||
exc_info = 'unknown'
|
||||
return '<[%s("%s") raised in repr()] %s object at 0x%x>' % (
|
||||
exc_name, exc_info, x.__class__.__name__, id(x))
|
||||
else:
|
||||
if len(s) > self.maxstring:
|
||||
i = max(0, (self.maxstring-3)//2)
|
||||
j = max(0, self.maxstring-3-i)
|
||||
s = s[:i] + '...' + s[len(s)-j:]
|
||||
return s
|
||||
|
||||
safe_repr = SafeRepr().repr
|
||||
|
||||
oldbuiltins = {}
|
||||
|
||||
def patch_builtins(assertion=True, compile=True):
|
||||
""" put compile and AssertionError builtins to Python's builtins. """
|
||||
if assertion:
|
||||
from py.__.code import assertion
|
||||
l = oldbuiltins.setdefault('AssertionError', [])
|
||||
l.append(py.builtin.builtins.AssertionError)
|
||||
py.builtin.builtins.AssertionError = assertion.AssertionError
|
||||
if compile:
|
||||
l = oldbuiltins.setdefault('compile', [])
|
||||
l.append(py.builtin.builtins.compile)
|
||||
py.builtin.builtins.compile = py.code.compile
|
||||
|
||||
def unpatch_builtins(assertion=True, compile=True):
|
||||
""" remove compile and AssertionError builtins from Python builtins. """
|
||||
if assertion:
|
||||
py.builtin.builtins.AssertionError = oldbuiltins['AssertionError'].pop()
|
||||
if compile:
|
||||
py.builtin.builtins.compile = oldbuiltins['compile'].pop()
|
||||
|
||||
def getrawcode(obj):
|
||||
""" return code object for given function. """
|
||||
obj = getattr(obj, 'im_func', obj)
|
||||
obj = getattr(obj, 'func_code', obj)
|
||||
obj = getattr(obj, 'f_code', obj)
|
||||
obj = getattr(obj, '__code__', obj)
|
||||
return obj
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
""" deprecated module for turning on/off some features. """
|
||||
|
||||
import py
|
||||
|
||||
from py.builtin import builtins as cpy_builtin
|
||||
|
||||
def invoke(assertion=False, compile=False):
|
||||
""" (deprecated) invoke magic, currently you can specify:
|
||||
|
||||
assertion patches the builtin AssertionError to try to give
|
||||
more meaningful AssertionErrors, which by means
|
||||
of deploying a mini-interpreter constructs
|
||||
a useful error message.
|
||||
"""
|
||||
py.log._apiwarn("1.1",
|
||||
"py.magic.invoke() is deprecated, use py.code.patch_builtins()",
|
||||
stacklevel=2,
|
||||
)
|
||||
py.code.patch_builtins(assertion=assertion, compile=compile)
|
||||
|
||||
def revoke(assertion=False, compile=False):
|
||||
""" (deprecated) revoke previously invoked magic (see invoke())."""
|
||||
py.log._apiwarn("1.1",
|
||||
"py.magic.revoke() is deprecated, use py.code.unpatch_builtins()",
|
||||
stacklevel=2,
|
||||
)
|
||||
py.code.unpatch_builtins(assertion=assertion, compile=compile)
|
||||
|
||||
patched = {}
|
||||
|
||||
def patch(namespace, name, value):
|
||||
""" (deprecated) rebind the 'name' on the 'namespace' to the 'value',
|
||||
possibly and remember the original value. Multiple
|
||||
invocations to the same namespace/name pair will
|
||||
remember a list of old values.
|
||||
"""
|
||||
py.log._apiwarn("1.1",
|
||||
"py.magic.patch() is deprecated, in tests use monkeypatch funcarg.",
|
||||
stacklevel=2,
|
||||
)
|
||||
nref = (namespace, name)
|
||||
orig = getattr(namespace, name)
|
||||
patched.setdefault(nref, []).append(orig)
|
||||
setattr(namespace, name, value)
|
||||
return orig
|
||||
|
||||
def revert(namespace, name):
|
||||
""" (deprecated) revert to the orginal value the last patch modified.
|
||||
Raise ValueError if no such original value exists.
|
||||
"""
|
||||
py.log._apiwarn("1.1",
|
||||
"py.magic.revert() is deprecated, in tests use monkeypatch funcarg.",
|
||||
stacklevel=2,
|
||||
)
|
||||
nref = (namespace, name)
|
||||
if nref not in patched or not patched[nref]:
|
||||
raise ValueError("No original value stored for %s.%s" % nref)
|
||||
current = getattr(namespace, name)
|
||||
orig = patched[nref].pop()
|
||||
setattr(namespace, name, orig)
|
||||
return current
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
|
||||
import py
|
||||
|
||||
py.log._apiwarn("1.1", "py.magic.AssertionError is deprecated, use py.code._AssertionError", stacklevel=2)
|
||||
|
||||
from py.code import _AssertionError as AssertionError
|
||||
@@ -1,347 +0,0 @@
|
||||
from __future__ import generators
|
||||
import sys
|
||||
import inspect, tokenize
|
||||
import py
|
||||
cpy_compile = compile
|
||||
|
||||
try:
|
||||
import _ast
|
||||
from _ast import PyCF_ONLY_AST as _AST_FLAG
|
||||
except ImportError:
|
||||
_AST_FLAG = 0
|
||||
_ast = None
|
||||
|
||||
|
||||
class Source(object):
|
||||
""" a immutable object holding a source code fragment,
|
||||
possibly deindenting it.
|
||||
"""
|
||||
def __init__(self, *parts, **kwargs):
|
||||
self.lines = lines = []
|
||||
de = kwargs.get('deindent', True)
|
||||
rstrip = kwargs.get('rstrip', True)
|
||||
for part in parts:
|
||||
if not part:
|
||||
partlines = []
|
||||
if isinstance(part, Source):
|
||||
partlines = part.lines
|
||||
elif isinstance(part, py.builtin._basestring):
|
||||
partlines = part.split('\n')
|
||||
if rstrip:
|
||||
while partlines:
|
||||
if partlines[-1].strip():
|
||||
break
|
||||
partlines.pop()
|
||||
else:
|
||||
partlines = getsource(part, deindent=de).lines
|
||||
if de:
|
||||
partlines = deindent(partlines)
|
||||
lines.extend(partlines)
|
||||
|
||||
def __eq__(self, other):
|
||||
try:
|
||||
return self.lines == other.lines
|
||||
except AttributeError:
|
||||
if isinstance(other, str):
|
||||
return str(self) == other
|
||||
return False
|
||||
|
||||
def __getitem__(self, key):
|
||||
if isinstance(key, int):
|
||||
return self.lines[key]
|
||||
else:
|
||||
if key.step not in (None, 1):
|
||||
raise IndexError("cannot slice a Source with a step")
|
||||
return self.__getslice__(key.start, key.stop)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.lines)
|
||||
|
||||
def __getslice__(self, start, end):
|
||||
newsource = Source()
|
||||
newsource.lines = self.lines[start:end]
|
||||
return newsource
|
||||
|
||||
def strip(self):
|
||||
""" return new source object with trailing
|
||||
and leading blank lines removed.
|
||||
"""
|
||||
start, end = 0, len(self)
|
||||
while start < end and not self.lines[start].strip():
|
||||
start += 1
|
||||
while end > start and not self.lines[end-1].strip():
|
||||
end -= 1
|
||||
source = Source()
|
||||
source.lines[:] = self.lines[start:end]
|
||||
return source
|
||||
|
||||
def putaround(self, before='', after='', indent=' ' * 4):
|
||||
""" return a copy of the source object with
|
||||
'before' and 'after' wrapped around it.
|
||||
"""
|
||||
before = Source(before)
|
||||
after = Source(after)
|
||||
newsource = Source()
|
||||
lines = [ (indent + line) for line in self.lines]
|
||||
newsource.lines = before.lines + lines + after.lines
|
||||
return newsource
|
||||
|
||||
def indent(self, indent=' ' * 4):
|
||||
""" return a copy of the source object with
|
||||
all lines indented by the given indent-string.
|
||||
"""
|
||||
newsource = Source()
|
||||
newsource.lines = [(indent+line) for line in self.lines]
|
||||
return newsource
|
||||
|
||||
def getstatement(self, lineno):
|
||||
""" return Source statement which contains the
|
||||
given linenumber (counted from 0).
|
||||
"""
|
||||
start, end = self.getstatementrange(lineno)
|
||||
return self[start:end]
|
||||
|
||||
def getstatementrange(self, lineno):
|
||||
""" return (start, end) tuple which spans the minimal
|
||||
statement region which containing the given lineno.
|
||||
"""
|
||||
# XXX there must be a better than these heuristic ways ...
|
||||
# XXX there may even be better heuristics :-)
|
||||
if not (0 <= lineno < len(self)):
|
||||
raise IndexError("lineno out of range")
|
||||
|
||||
# 1. find the start of the statement
|
||||
from codeop import compile_command
|
||||
for start in range(lineno, -1, -1):
|
||||
trylines = self.lines[start:lineno+1]
|
||||
# quick hack to indent the source and get it as a string in one go
|
||||
trylines.insert(0, 'def xxx():')
|
||||
trysource = '\n '.join(trylines)
|
||||
# ^ space here
|
||||
try:
|
||||
compile_command(trysource)
|
||||
except (SyntaxError, OverflowError, ValueError):
|
||||
pass
|
||||
else:
|
||||
break # got a valid or incomplete statement
|
||||
|
||||
# 2. find the end of the statement
|
||||
for end in range(lineno+1, len(self)+1):
|
||||
trysource = self[start:end]
|
||||
if trysource.isparseable():
|
||||
break
|
||||
|
||||
return start, end
|
||||
|
||||
def getblockend(self, lineno):
|
||||
# XXX
|
||||
lines = [x + '\n' for x in self.lines[lineno:]]
|
||||
blocklines = inspect.getblock(lines)
|
||||
#print blocklines
|
||||
return lineno + len(blocklines) - 1
|
||||
|
||||
def deindent(self, offset=None):
|
||||
""" return a new source object deindented by offset.
|
||||
If offset is None then guess an indentation offset from
|
||||
the first non-blank line. Subsequent lines which have a
|
||||
lower indentation offset will be copied verbatim as
|
||||
they are assumed to be part of multilines.
|
||||
"""
|
||||
# XXX maybe use the tokenizer to properly handle multiline
|
||||
# strings etc.pp?
|
||||
newsource = Source()
|
||||
newsource.lines[:] = deindent(self.lines, offset)
|
||||
return newsource
|
||||
|
||||
def isparseable(self, deindent=True):
|
||||
""" return True if source is parseable, heuristically
|
||||
deindenting it by default.
|
||||
"""
|
||||
try:
|
||||
import parser
|
||||
except ImportError:
|
||||
syntax_checker = lambda x: compile(x, 'asd', 'exec')
|
||||
else:
|
||||
syntax_checker = parser.suite
|
||||
|
||||
if deindent:
|
||||
source = str(self.deindent())
|
||||
else:
|
||||
source = str(self)
|
||||
try:
|
||||
#compile(source+'\n', "x", "exec")
|
||||
syntax_checker(source+'\n')
|
||||
except SyntaxError:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def __str__(self):
|
||||
return "\n".join(self.lines)
|
||||
|
||||
def compile(self, filename=None, mode='exec',
|
||||
flag=generators.compiler_flag,
|
||||
dont_inherit=0, _genframe=None):
|
||||
""" return compiled code object. if filename is None
|
||||
invent an artificial filename which displays
|
||||
the source/line position of the caller frame.
|
||||
"""
|
||||
if not filename or py.path.local(filename).check(file=0):
|
||||
if _genframe is None:
|
||||
_genframe = sys._getframe(1) # the caller
|
||||
fn,lineno = _genframe.f_code.co_filename, _genframe.f_lineno
|
||||
if not filename:
|
||||
filename = '<codegen %s:%d>' % (fn, lineno)
|
||||
else:
|
||||
filename = '<codegen %r %s:%d>' % (filename, fn, lineno)
|
||||
source = "\n".join(self.lines) + '\n'
|
||||
try:
|
||||
co = cpy_compile(source, filename, mode, flag)
|
||||
except SyntaxError:
|
||||
ex = sys.exc_info()[1]
|
||||
# re-represent syntax errors from parsing python strings
|
||||
msglines = self.lines[:ex.lineno]
|
||||
if ex.offset:
|
||||
msglines.append(" "*ex.offset + '^')
|
||||
msglines.append("syntax error probably generated here: %s" % filename)
|
||||
newex = SyntaxError('\n'.join(msglines))
|
||||
newex.offset = ex.offset
|
||||
newex.lineno = ex.lineno
|
||||
newex.text = ex.text
|
||||
raise newex
|
||||
else:
|
||||
if flag & _AST_FLAG:
|
||||
return co
|
||||
co_filename = MyStr(filename)
|
||||
co_filename.__source__ = self
|
||||
return py.code.Code(co).new(rec=1, co_filename=co_filename)
|
||||
#return newcode_withfilename(co, co_filename)
|
||||
|
||||
#
|
||||
# public API shortcut functions
|
||||
#
|
||||
|
||||
def compile_(source, filename=None, mode='exec', flags=
|
||||
generators.compiler_flag, dont_inherit=0):
|
||||
""" compile the given source to a raw code object,
|
||||
which points back to the source code through
|
||||
"co_filename.__source__". All code objects
|
||||
contained in the code object will recursively
|
||||
also have this special subclass-of-string
|
||||
filename.
|
||||
"""
|
||||
if _ast is not None and isinstance(source, _ast.AST):
|
||||
# XXX should Source support having AST?
|
||||
return cpy_compile(source, filename, mode, flags, dont_inherit)
|
||||
_genframe = sys._getframe(1) # the caller
|
||||
s = Source(source)
|
||||
co = s.compile(filename, mode, flags, _genframe=_genframe)
|
||||
return co
|
||||
|
||||
|
||||
def getfslineno(obj):
|
||||
try:
|
||||
code = py.code.Code(obj)
|
||||
except TypeError:
|
||||
# fallback to
|
||||
fn = (py.std.inspect.getsourcefile(obj) or
|
||||
py.std.inspect.getfile(obj))
|
||||
fspath = fn and py.path.local(fn) or None
|
||||
if fspath:
|
||||
try:
|
||||
_, lineno = findsource(obj)
|
||||
except IOError:
|
||||
lineno = None
|
||||
else:
|
||||
lineno = None
|
||||
else:
|
||||
fspath = code.path
|
||||
lineno = code.firstlineno
|
||||
return fspath, lineno
|
||||
|
||||
#
|
||||
# helper functions
|
||||
#
|
||||
class MyStr(str):
|
||||
""" custom string which allows to add attributes. """
|
||||
|
||||
def findsource(obj):
|
||||
obj = py.code.getrawcode(obj)
|
||||
try:
|
||||
fullsource = obj.co_filename.__source__
|
||||
except AttributeError:
|
||||
try:
|
||||
sourcelines, lineno = py.std.inspect.findsource(obj)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
return None, None
|
||||
source = Source()
|
||||
source.lines = [line.rstrip() for line in sourcelines]
|
||||
return source, lineno
|
||||
else:
|
||||
lineno = obj.co_firstlineno - 1
|
||||
return fullsource, lineno
|
||||
|
||||
|
||||
def getsource(obj, **kwargs):
|
||||
obj = py.code.getrawcode(obj)
|
||||
try:
|
||||
fullsource = obj.co_filename.__source__
|
||||
except AttributeError:
|
||||
try:
|
||||
strsrc = inspect.getsource(obj)
|
||||
except IndentationError:
|
||||
strsrc = "\"Buggy python version consider upgrading, cannot get source\""
|
||||
assert isinstance(strsrc, str)
|
||||
return Source(strsrc, **kwargs)
|
||||
else:
|
||||
lineno = obj.co_firstlineno - 1
|
||||
end = fullsource.getblockend(lineno)
|
||||
return Source(fullsource[lineno:end+1], deident=True)
|
||||
|
||||
|
||||
def deindent(lines, offset=None):
|
||||
if offset is None:
|
||||
for line in lines:
|
||||
line = line.expandtabs()
|
||||
s = line.lstrip()
|
||||
if s:
|
||||
offset = len(line)-len(s)
|
||||
break
|
||||
else:
|
||||
offset = 0
|
||||
if offset == 0:
|
||||
return list(lines)
|
||||
newlines = []
|
||||
def readline_generator(lines):
|
||||
for line in lines:
|
||||
yield line + '\n'
|
||||
while True:
|
||||
yield ''
|
||||
|
||||
r = readline_generator(lines)
|
||||
try:
|
||||
readline = r.next
|
||||
except AttributeError:
|
||||
readline = r.__next__
|
||||
|
||||
try:
|
||||
for _, _, (sline, _), (eline, _), _ in tokenize.generate_tokens(readline):
|
||||
if sline > len(lines):
|
||||
break # End of input reached
|
||||
if sline > len(newlines):
|
||||
line = lines[sline - 1].expandtabs()
|
||||
if line.lstrip() and line[:offset].isspace():
|
||||
line = line[offset:] # Deindent
|
||||
newlines.append(line)
|
||||
|
||||
for i in range(sline, eline):
|
||||
# Don't deindent continuing lines of
|
||||
# multiline tokens (i.e. multiline strings)
|
||||
newlines.append(lines[i])
|
||||
except (IndentationError, tokenize.TokenError):
|
||||
pass
|
||||
# Add any lines we didn't see. E.g. if an exception was raised.
|
||||
newlines.extend(lines[len(newlines):])
|
||||
return newlines
|
||||
@@ -1,2 +0,0 @@
|
||||
""" compatibility modules (taken from 2.4.4) """
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
import py
|
||||
|
||||
py.log._apiwarn("1.1", "py.compat.doctest deprecated, use standard library version.", stacklevel="initpkg")
|
||||
doctest = py.std.doctest
|
||||
@@ -1,4 +0,0 @@
|
||||
import py
|
||||
py.log._apiwarn("1.1", "py.compat.optparse deprecated, use standard library version.", stacklevel="initpkg")
|
||||
|
||||
optparse = py.std.optparse
|
||||
@@ -1,4 +0,0 @@
|
||||
|
||||
import py
|
||||
py.log._apiwarn("1.1", "py.compat.subprocess deprecated, use standard library version.", stacklevel="initpkg")
|
||||
subprocess = py.std.subprocess
|
||||
@@ -1,4 +0,0 @@
|
||||
import py
|
||||
|
||||
py.log._apiwarn("1.1", "py.compat.textwrap deprecated, use standard library version.", stacklevel="initpkg")
|
||||
textwrap = py.std.textwrap
|
||||
83
py/error.py
83
py/error.py
@@ -1,83 +0,0 @@
|
||||
"""
|
||||
create errno-specific classes for IO or os calls.
|
||||
|
||||
"""
|
||||
import sys, os, errno
|
||||
|
||||
class Error(EnvironmentError):
|
||||
def __repr__(self):
|
||||
return "%s.%s %r: %s " %(self.__class__.__module__,
|
||||
self.__class__.__name__,
|
||||
self.__class__.__doc__,
|
||||
" ".join(map(str, self.args)),
|
||||
#repr(self.args)
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
s = "[%s]: %s" %(self.__class__.__doc__,
|
||||
" ".join(map(str, self.args)),
|
||||
)
|
||||
return s
|
||||
|
||||
_winerrnomap = {
|
||||
2: errno.ENOENT,
|
||||
3: errno.ENOENT,
|
||||
17: errno.EEXIST,
|
||||
22: errno.ENOTDIR,
|
||||
267: errno.ENOTDIR,
|
||||
5: errno.EACCES, # anything better?
|
||||
}
|
||||
|
||||
class ErrorMaker(object):
|
||||
""" lazily provides Exception classes for each possible POSIX errno
|
||||
(as defined per the 'errno' module). All such instances
|
||||
subclass EnvironmentError.
|
||||
"""
|
||||
Error = Error
|
||||
_errno2class = {}
|
||||
|
||||
def __getattr__(self, name):
|
||||
eno = getattr(errno, name)
|
||||
cls = self._geterrnoclass(eno)
|
||||
setattr(self, name, cls)
|
||||
return cls
|
||||
|
||||
def _geterrnoclass(self, eno):
|
||||
try:
|
||||
return self._errno2class[eno]
|
||||
except KeyError:
|
||||
clsname = errno.errorcode.get(eno, "UnknownErrno%d" %(eno,))
|
||||
errorcls = type(Error)(clsname, (Error,),
|
||||
{'__module__':'py.error',
|
||||
'__doc__': os.strerror(eno)})
|
||||
self._errno2class[eno] = errorcls
|
||||
return errorcls
|
||||
|
||||
def checked_call(self, func, *args):
|
||||
""" call a function and raise an errno-exception if applicable. """
|
||||
__tracebackhide__ = True
|
||||
try:
|
||||
return func(*args)
|
||||
except self.Error:
|
||||
raise
|
||||
except EnvironmentError:
|
||||
cls, value, tb = sys.exc_info()
|
||||
if not hasattr(value, 'errno'):
|
||||
raise
|
||||
__tracebackhide__ = False
|
||||
errno = value.errno
|
||||
try:
|
||||
if not isinstance(value, WindowsError):
|
||||
raise NameError
|
||||
except NameError:
|
||||
# we are not on Windows, or we got a proper OSError
|
||||
cls = self._geterrnoclass(errno)
|
||||
else:
|
||||
try:
|
||||
cls = self._geterrnoclass(_winerrnomap[errno])
|
||||
except KeyError:
|
||||
raise value
|
||||
raise cls("%s%r" % (func.__name__, args))
|
||||
__tracebackhide__ = True
|
||||
|
||||
error = ErrorMaker()
|
||||
235
py/initpkg.py
235
py/initpkg.py
@@ -1,235 +0,0 @@
|
||||
"""
|
||||
package initialization.
|
||||
|
||||
You use the functionality of this package by putting
|
||||
|
||||
from py.initpkg import initpkg
|
||||
initpkg(__name__, exportdefs={
|
||||
'name1.name2' : ('./path/to/file.py', 'name')
|
||||
...
|
||||
})
|
||||
|
||||
into your package's __init__.py file. This will
|
||||
lead your package to only expose the names of all
|
||||
your implementation files that you explicitely
|
||||
specify. In the above example 'name1' will
|
||||
become a Module instance where 'name2' is
|
||||
bound in its namespace to the 'name' object
|
||||
in the relative './path/to/file.py' python module.
|
||||
Note that you can also use a '.c' file in which
|
||||
case it will be compiled via distutils-facilities
|
||||
on the fly.
|
||||
|
||||
"""
|
||||
from __future__ import generators
|
||||
import sys
|
||||
import os
|
||||
assert sys.version_info >= (2,2,0), "py lib requires python 2.2 or higher"
|
||||
from types import ModuleType
|
||||
|
||||
# ---------------------------------------------------
|
||||
# Package Object
|
||||
# ---------------------------------------------------
|
||||
|
||||
class Package(object):
|
||||
def __init__(self, name, exportdefs, metainfo):
|
||||
pkgmodule = sys.modules[name]
|
||||
assert pkgmodule.__name__ == name
|
||||
self.name = name
|
||||
self.exportdefs = exportdefs
|
||||
self.module = pkgmodule
|
||||
assert not hasattr(pkgmodule, '__pkg__'), \
|
||||
"unsupported reinitialization of %r" % pkgmodule
|
||||
pkgmodule.__pkg__ = self
|
||||
|
||||
# make available pkgname.__
|
||||
implname = name + '.' + '__'
|
||||
self.implmodule = ModuleType(implname)
|
||||
self.implmodule.__name__ = implname
|
||||
self.implmodule.__file__ = os.path.abspath(pkgmodule.__file__)
|
||||
self.implmodule.__path__ = [os.path.abspath(p)
|
||||
for p in pkgmodule.__path__]
|
||||
pkgmodule.__ = self.implmodule
|
||||
setmodule(implname, self.implmodule)
|
||||
# inhibit further direct filesystem imports through the package module
|
||||
del pkgmodule.__path__
|
||||
|
||||
# set metainfo
|
||||
for name, value in metainfo.items():
|
||||
setattr(self, name, value)
|
||||
version = metainfo.get('version', None)
|
||||
if version:
|
||||
pkgmodule.__version__ = version
|
||||
|
||||
def _resolve(self, extpyish):
|
||||
""" resolve a combined filesystem/python extpy-ish path. """
|
||||
fspath, modpath = extpyish
|
||||
assert fspath.startswith('./'), \
|
||||
"%r is not an implementation path (XXX)" % (extpyish,)
|
||||
implmodule = self._loadimpl(fspath[:-3])
|
||||
if not isinstance(modpath, str): # export the entire module
|
||||
return implmodule
|
||||
|
||||
current = implmodule
|
||||
for x in modpath.split('.'):
|
||||
try:
|
||||
current = getattr(current, x)
|
||||
except AttributeError:
|
||||
raise AttributeError("resolving %r failed: %s" %(
|
||||
extpyish, x))
|
||||
|
||||
return current
|
||||
|
||||
def getimportname(self, path):
|
||||
if not path.ext.startswith('.py'):
|
||||
return None
|
||||
import py
|
||||
base = py.path.local(self.implmodule.__file__).dirpath()
|
||||
if not path.relto(base):
|
||||
return None
|
||||
names = path.new(ext='').relto(base).split(path.sep)
|
||||
dottedname = ".".join([self.implmodule.__name__] + names)
|
||||
return dottedname
|
||||
|
||||
def _loadimpl(self, relfile):
|
||||
""" load implementation for the given relfile. """
|
||||
parts = [x.strip() for x in relfile.split('/') if x and x!= '.']
|
||||
modpath = ".".join([self.implmodule.__name__] + parts)
|
||||
#print "trying import", modpath
|
||||
return __import__(modpath, None, None, ['__doc__'])
|
||||
|
||||
def exportitems(self):
|
||||
return self.exportdefs.items()
|
||||
|
||||
def getpath(self):
|
||||
from py.path import local
|
||||
base = local(self.implmodule.__file__).dirpath()
|
||||
assert base.check()
|
||||
return base
|
||||
|
||||
def setmodule(modpath, module):
|
||||
#print "sys.modules[%r] = %r" % (modpath, module)
|
||||
sys.modules[modpath] = module
|
||||
|
||||
# ---------------------------------------------------
|
||||
# API Module Object
|
||||
# ---------------------------------------------------
|
||||
|
||||
class ApiModule(ModuleType):
|
||||
def __init__(self, pkg, name):
|
||||
self.__map__ = {}
|
||||
self.__pkg__ = pkg
|
||||
self.__name__ = name
|
||||
|
||||
def __repr__(self):
|
||||
return '<ApiModule %r>' % (self.__name__,)
|
||||
|
||||
def __getattr__(self, name):
|
||||
if '*' in self.__map__:
|
||||
extpy = self.__map__['*'][0], name
|
||||
result = self.__pkg__._resolve(extpy)
|
||||
else:
|
||||
try:
|
||||
extpy = self.__map__.pop(name)
|
||||
except KeyError:
|
||||
__tracebackhide__ = True
|
||||
raise AttributeError(name)
|
||||
else:
|
||||
result = self.__pkg__._resolve(extpy)
|
||||
|
||||
setattr(self, name, result)
|
||||
#self._fixinspection(result, name)
|
||||
return result
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
super(ApiModule, self).__setattr__(name, value)
|
||||
try:
|
||||
del self.__map__[name]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def _deprecated_fixinspection(self, result, name):
|
||||
# modify some attrs to make a class appear at export level
|
||||
if hasattr(result, '__module__'):
|
||||
if not result.__module__.startswith('py.__'):
|
||||
return # don't change __module__ nor __name__ for classes
|
||||
# that the py lib re-exports from somewhere else,
|
||||
# e.g. py.builtin.BaseException
|
||||
try:
|
||||
setattr(result, '__module__', self.__name__)
|
||||
except (AttributeError, TypeError):
|
||||
pass
|
||||
if hasattr(result, '__bases__'):
|
||||
try:
|
||||
setattr(result, '__name__', name)
|
||||
except (AttributeError, TypeError):
|
||||
pass
|
||||
|
||||
def getdict(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:
|
||||
if '*' not in self.__map__:
|
||||
for name in list(self.__map__):
|
||||
hasattr(self, name) # force attribute load, ignore errors
|
||||
assert not self.__map__, "%r not empty" % self.__map__
|
||||
else:
|
||||
fsname = self.__map__['*'][0]
|
||||
dict.update(self.__pkg__._loadimpl(fsname[:-3]).__dict__)
|
||||
return dict
|
||||
|
||||
__dict__ = property(getdict)
|
||||
del getdict
|
||||
|
||||
# ---------------------------------------------------
|
||||
# Bootstrap Virtual Module Hierarchy
|
||||
# ---------------------------------------------------
|
||||
|
||||
def initpkg(pkgname, exportdefs, **kw):
|
||||
#print "initializing package", pkgname
|
||||
# bootstrap Package object
|
||||
pkg = Package(pkgname, exportdefs, kw)
|
||||
seen = { pkgname : pkg.module }
|
||||
deferred_imports = []
|
||||
|
||||
for pypath, extpy in pkg.exportitems():
|
||||
pyparts = pypath.split('.')
|
||||
modparts = pyparts[:]
|
||||
if extpy[1] != '*':
|
||||
lastmodpart = modparts.pop()
|
||||
else:
|
||||
lastmodpart = '*'
|
||||
current = pkgname
|
||||
|
||||
# ensure modules
|
||||
for name in modparts:
|
||||
previous = current
|
||||
current += '.' + name
|
||||
if current not in seen:
|
||||
seen[current] = mod = ApiModule(pkg, current)
|
||||
setattr(seen[previous], name, mod)
|
||||
setmodule(current, mod)
|
||||
|
||||
mod = seen[current]
|
||||
if not hasattr(mod, '__map__'):
|
||||
assert mod is pkg.module, \
|
||||
"only root modules are allowed to be non-lazy. "
|
||||
deferred_imports.append((mod, pyparts[-1], extpy))
|
||||
else:
|
||||
if extpy[1] == '__doc__':
|
||||
mod.__doc__ = pkg._resolve(extpy)
|
||||
else:
|
||||
mod.__map__[lastmodpart] = extpy
|
||||
|
||||
for mod, pypart, extpy in deferred_imports:
|
||||
setattr(mod, pypart, pkg._resolve(extpy))
|
||||
|
||||
autoimport(pkgname)
|
||||
|
||||
def autoimport(pkgname):
|
||||
import py
|
||||
ENVKEY = pkgname.upper() + "_AUTOIMPORT"
|
||||
if ENVKEY in os.environ:
|
||||
for impname in os.environ[ENVKEY].split(","):
|
||||
__import__(impname)
|
||||
@@ -1 +0,0 @@
|
||||
""" input/output helping """
|
||||
344
py/io/capture.py
344
py/io/capture.py
@@ -1,344 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
import py
|
||||
import tempfile
|
||||
|
||||
try:
|
||||
from io import StringIO
|
||||
except ImportError:
|
||||
from StringIO import StringIO
|
||||
|
||||
if sys.version_info < (3,0):
|
||||
class TextIO(StringIO):
|
||||
def write(self, data):
|
||||
if not isinstance(data, unicode):
|
||||
data = unicode(data, getattr(self, '_encoding', 'UTF-8'))
|
||||
StringIO.write(self, data)
|
||||
else:
|
||||
TextIO = StringIO
|
||||
|
||||
try:
|
||||
from io import BytesIO
|
||||
except ImportError:
|
||||
class BytesIO(StringIO):
|
||||
def write(self, data):
|
||||
if isinstance(data, unicode):
|
||||
raise TypeError("not a byte value: %r" %(data,))
|
||||
StringIO.write(self, data)
|
||||
|
||||
class FDCapture:
|
||||
""" Capture IO to/from a given os-level filedescriptor. """
|
||||
|
||||
def __init__(self, targetfd, tmpfile=None):
|
||||
""" save targetfd descriptor, and open a new
|
||||
temporary file there. If no tmpfile is
|
||||
specified a tempfile.Tempfile() will be opened
|
||||
in text mode.
|
||||
"""
|
||||
self.targetfd = targetfd
|
||||
if tmpfile is None:
|
||||
f = tempfile.TemporaryFile('wb+')
|
||||
tmpfile = dupfile(f, encoding="UTF-8")
|
||||
f.close()
|
||||
self.tmpfile = tmpfile
|
||||
self._savefd = os.dup(targetfd)
|
||||
os.dup2(self.tmpfile.fileno(), targetfd)
|
||||
self._patched = []
|
||||
|
||||
def setasfile(self, name, module=sys):
|
||||
""" patch <module>.<name> to self.tmpfile
|
||||
"""
|
||||
key = (module, name)
|
||||
self._patched.append((key, getattr(module, name)))
|
||||
setattr(module, name, self.tmpfile)
|
||||
|
||||
def unsetfiles(self):
|
||||
""" unpatch all patched items
|
||||
"""
|
||||
while self._patched:
|
||||
(module, name), value = self._patched.pop()
|
||||
setattr(module, name, value)
|
||||
|
||||
def done(self):
|
||||
""" unpatch and clean up, returns the self.tmpfile (file object)
|
||||
"""
|
||||
os.dup2(self._savefd, self.targetfd)
|
||||
self.unsetfiles()
|
||||
os.close(self._savefd)
|
||||
self.tmpfile.seek(0)
|
||||
return self.tmpfile
|
||||
|
||||
def writeorg(self, data):
|
||||
""" write a string to the original file descriptor
|
||||
"""
|
||||
tempfp = tempfile.TemporaryFile()
|
||||
try:
|
||||
os.dup2(self._savefd, tempfp.fileno())
|
||||
tempfp.write(data)
|
||||
finally:
|
||||
tempfp.close()
|
||||
|
||||
|
||||
def dupfile(f, mode=None, buffering=0, raising=False, encoding=None):
|
||||
""" return a new open file object that's a duplicate of f
|
||||
|
||||
mode is duplicated if not given, 'buffering' controls
|
||||
buffer size (defaulting to no buffering) and 'raising'
|
||||
defines whether an exception is raised when an incompatible
|
||||
file object is passed in (if raising is False, the file
|
||||
object itself will be returned)
|
||||
"""
|
||||
try:
|
||||
fd = f.fileno()
|
||||
except AttributeError:
|
||||
if raising:
|
||||
raise
|
||||
return f
|
||||
newfd = os.dup(fd)
|
||||
mode = mode and mode or f.mode
|
||||
if sys.version_info >= (3,0):
|
||||
if encoding is not None:
|
||||
mode = mode.replace("b", "")
|
||||
buffering = True
|
||||
return os.fdopen(newfd, mode, buffering, encoding, closefd=False)
|
||||
else:
|
||||
f = os.fdopen(newfd, mode, buffering)
|
||||
if encoding is not None:
|
||||
return EncodedFile(f, encoding)
|
||||
return f
|
||||
|
||||
class EncodedFile(object):
|
||||
def __init__(self, _stream, encoding):
|
||||
self._stream = _stream
|
||||
self.encoding = encoding
|
||||
|
||||
def write(self, obj):
|
||||
if isinstance(obj, unicode):
|
||||
obj = obj.encode(self.encoding)
|
||||
elif isinstance(obj, str):
|
||||
pass
|
||||
else:
|
||||
obj = str(obj)
|
||||
self._stream.write(obj)
|
||||
|
||||
def writelines(self, linelist):
|
||||
data = ''.join(linelist)
|
||||
self.write(data)
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._stream, name)
|
||||
|
||||
class Capture(object):
|
||||
def call(cls, func, *args, **kwargs):
|
||||
""" return a (res, out, err) tuple where
|
||||
out and err represent the output/error output
|
||||
during function execution.
|
||||
call the given function with args/kwargs
|
||||
and capture output/error during its execution.
|
||||
"""
|
||||
so = cls()
|
||||
try:
|
||||
res = func(*args, **kwargs)
|
||||
finally:
|
||||
out, err = so.reset()
|
||||
return res, out, err
|
||||
call = classmethod(call)
|
||||
|
||||
def reset(self):
|
||||
""" reset sys.stdout/stderr and return captured output as strings. """
|
||||
if hasattr(self, '_suspended'):
|
||||
outfile = self._kwargs['out']
|
||||
errfile = self._kwargs['err']
|
||||
del self._kwargs
|
||||
else:
|
||||
outfile, errfile = self.done()
|
||||
out, err = "", ""
|
||||
if outfile:
|
||||
out = outfile.read()
|
||||
outfile.close()
|
||||
if errfile and errfile != outfile:
|
||||
err = errfile.read()
|
||||
errfile.close()
|
||||
return out, err
|
||||
|
||||
def suspend(self):
|
||||
""" return current snapshot captures, memorize tempfiles. """
|
||||
assert not hasattr(self, '_suspended')
|
||||
self._suspended = True
|
||||
outerr = self.readouterr()
|
||||
outfile, errfile = self.done()
|
||||
self._kwargs['out'] = outfile
|
||||
self._kwargs['err'] = errfile
|
||||
return outerr
|
||||
|
||||
def resume(self):
|
||||
""" resume capturing with original temp files. """
|
||||
assert self._suspended
|
||||
self._initialize(**self._kwargs)
|
||||
del self._suspended
|
||||
|
||||
|
||||
class StdCaptureFD(Capture):
|
||||
""" This class allows to capture writes to FD1 and FD2
|
||||
and may connect a NULL file to FD0 (and prevent
|
||||
reads from sys.stdin)
|
||||
"""
|
||||
def __init__(self, out=True, err=True,
|
||||
mixed=False, in_=True, patchsys=True):
|
||||
self._kwargs = locals().copy()
|
||||
del self._kwargs['self']
|
||||
self._initialize(**self._kwargs)
|
||||
|
||||
def _initialize(self, out=True, err=True,
|
||||
mixed=False, in_=True, patchsys=True):
|
||||
if in_:
|
||||
self._oldin = (sys.stdin, os.dup(0))
|
||||
sys.stdin = DontReadFromInput()
|
||||
fd = os.open(devnullpath, os.O_RDONLY)
|
||||
os.dup2(fd, 0)
|
||||
os.close(fd)
|
||||
if out:
|
||||
tmpfile = None
|
||||
if hasattr(out, 'write'):
|
||||
tmpfile = out
|
||||
self.out = py.io.FDCapture(1, tmpfile=tmpfile)
|
||||
if patchsys:
|
||||
self.out.setasfile('stdout')
|
||||
if err:
|
||||
if mixed and out:
|
||||
tmpfile = self.out.tmpfile
|
||||
elif hasattr(err, 'write'):
|
||||
tmpfile = err
|
||||
else:
|
||||
tmpfile = None
|
||||
self.err = py.io.FDCapture(2, tmpfile=tmpfile)
|
||||
if patchsys:
|
||||
self.err.setasfile('stderr')
|
||||
|
||||
def done(self):
|
||||
""" return (outfile, errfile) and stop capturing. """
|
||||
if hasattr(self, 'out'):
|
||||
outfile = self.out.done()
|
||||
else:
|
||||
outfile = None
|
||||
if hasattr(self, 'err'):
|
||||
errfile = self.err.done()
|
||||
else:
|
||||
errfile = None
|
||||
if hasattr(self, '_oldin'):
|
||||
oldsys, oldfd = self._oldin
|
||||
os.dup2(oldfd, 0)
|
||||
os.close(oldfd)
|
||||
sys.stdin = oldsys
|
||||
return outfile, errfile
|
||||
|
||||
def readouterr(self):
|
||||
""" return snapshot value of stdout/stderr capturings. """
|
||||
l = []
|
||||
for name in ('out', 'err'):
|
||||
res = ""
|
||||
if hasattr(self, name):
|
||||
f = getattr(self, name).tmpfile
|
||||
f.seek(0)
|
||||
res = f.read()
|
||||
f.truncate(0)
|
||||
f.seek(0)
|
||||
l.append(res)
|
||||
return l
|
||||
|
||||
class StdCapture(Capture):
|
||||
""" This class allows to capture writes to sys.stdout|stderr "in-memory"
|
||||
and will raise errors on tries to read from sys.stdin. It only
|
||||
modifies sys.stdout|stderr|stdin attributes and does not
|
||||
touch underlying File Descriptors (use StdCaptureFD for that).
|
||||
"""
|
||||
def __init__(self, out=True, err=True, in_=True, mixed=False):
|
||||
self._kwargs = locals().copy()
|
||||
del self._kwargs['self']
|
||||
self._initialize(**self._kwargs)
|
||||
|
||||
def _initialize(self, out, err, in_, mixed):
|
||||
self._out = out
|
||||
self._err = err
|
||||
self._in = in_
|
||||
if out:
|
||||
self._oldout = sys.stdout
|
||||
if not hasattr(out, 'write'):
|
||||
out = TextIO()
|
||||
sys.stdout = self.out = out
|
||||
if err:
|
||||
self._olderr = sys.stderr
|
||||
if out and mixed:
|
||||
err = self.out
|
||||
elif not hasattr(err, 'write'):
|
||||
err = TextIO()
|
||||
sys.stderr = self.err = err
|
||||
if in_:
|
||||
self._oldin = sys.stdin
|
||||
sys.stdin = self.newin = DontReadFromInput()
|
||||
|
||||
def done(self):
|
||||
""" return (outfile, errfile) and stop capturing. """
|
||||
o,e = sys.stdout, sys.stderr
|
||||
if self._out:
|
||||
try:
|
||||
sys.stdout = self._oldout
|
||||
except AttributeError:
|
||||
raise IOError("stdout capturing already reset")
|
||||
del self._oldout
|
||||
outfile = self.out
|
||||
outfile.seek(0)
|
||||
else:
|
||||
outfile = None
|
||||
if self._err:
|
||||
try:
|
||||
sys.stderr = self._olderr
|
||||
except AttributeError:
|
||||
raise IOError("stderr capturing already reset")
|
||||
del self._olderr
|
||||
errfile = self.err
|
||||
errfile.seek(0)
|
||||
else:
|
||||
errfile = None
|
||||
if self._in:
|
||||
sys.stdin = self._oldin
|
||||
return outfile, errfile
|
||||
|
||||
def readouterr(self):
|
||||
""" return snapshot value of stdout/stderr capturings. """
|
||||
out = err = ""
|
||||
if self._out:
|
||||
out = sys.stdout.getvalue()
|
||||
sys.stdout.truncate(0)
|
||||
if self._err:
|
||||
err = sys.stderr.getvalue()
|
||||
sys.stderr.truncate(0)
|
||||
return out, err
|
||||
|
||||
class DontReadFromInput:
|
||||
"""Temporary stub class. Ideally when stdin is accessed, the
|
||||
capturing should be turned off, with possibly all data captured
|
||||
so far sent to the screen. This should be configurable, though,
|
||||
because in automated test runs it is better to crash than
|
||||
hang indefinitely.
|
||||
"""
|
||||
def read(self, *args):
|
||||
raise IOError("reading from stdin while output is captured")
|
||||
readline = read
|
||||
readlines = read
|
||||
__iter__ = read
|
||||
|
||||
def fileno(self):
|
||||
raise ValueError("redirected Stdin is pseudofile, has no fileno()")
|
||||
def isatty(self):
|
||||
return False
|
||||
|
||||
try:
|
||||
devnullpath = os.devnull
|
||||
except AttributeError:
|
||||
if os.name == 'nt':
|
||||
devnullpath = 'NUL'
|
||||
else:
|
||||
devnullpath = '/dev/null'
|
||||
|
||||
|
||||
@@ -1,264 +0,0 @@
|
||||
"""
|
||||
|
||||
Helper functions for writing to terminals and files.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
import sys, os
|
||||
import py
|
||||
|
||||
def _getdimensions():
|
||||
import termios,fcntl,struct
|
||||
call = fcntl.ioctl(0,termios.TIOCGWINSZ,"\000"*8)
|
||||
height,width = struct.unpack( "hhhh", call ) [:2]
|
||||
return height, width
|
||||
|
||||
if sys.platform == 'win32':
|
||||
# ctypes access to the Windows console
|
||||
|
||||
STD_OUTPUT_HANDLE = -11
|
||||
STD_ERROR_HANDLE = -12
|
||||
FOREGROUND_BLUE = 0x0001 # text color contains blue.
|
||||
FOREGROUND_GREEN = 0x0002 # text color contains green.
|
||||
FOREGROUND_RED = 0x0004 # text color contains red.
|
||||
FOREGROUND_WHITE = 0x0007
|
||||
FOREGROUND_INTENSITY = 0x0008 # text color is intensified.
|
||||
BACKGROUND_BLUE = 0x0010 # background color contains blue.
|
||||
BACKGROUND_GREEN = 0x0020 # background color contains green.
|
||||
BACKGROUND_RED = 0x0040 # background color contains red.
|
||||
BACKGROUND_WHITE = 0x0070
|
||||
BACKGROUND_INTENSITY = 0x0080 # background color is intensified.
|
||||
|
||||
def GetStdHandle(kind):
|
||||
import ctypes
|
||||
return ctypes.windll.kernel32.GetStdHandle(kind)
|
||||
|
||||
def SetConsoleTextAttribute(handle, attr):
|
||||
import ctypes
|
||||
ctypes.windll.kernel32.SetConsoleTextAttribute(
|
||||
handle, attr)
|
||||
|
||||
def _getdimensions():
|
||||
import ctypes
|
||||
from ctypes import wintypes
|
||||
|
||||
SHORT = ctypes.c_short
|
||||
class COORD(ctypes.Structure):
|
||||
_fields_ = [('X', SHORT),
|
||||
('Y', SHORT)]
|
||||
class SMALL_RECT(ctypes.Structure):
|
||||
_fields_ = [('Left', SHORT),
|
||||
('Top', SHORT),
|
||||
('Right', SHORT),
|
||||
('Bottom', SHORT)]
|
||||
class CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
|
||||
_fields_ = [('dwSize', COORD),
|
||||
('dwCursorPosition', COORD),
|
||||
('wAttributes', wintypes.WORD),
|
||||
('srWindow', SMALL_RECT),
|
||||
('dwMaximumWindowSize', COORD)]
|
||||
STD_OUTPUT_HANDLE = -11
|
||||
handle = GetStdHandle(STD_OUTPUT_HANDLE)
|
||||
info = CONSOLE_SCREEN_BUFFER_INFO()
|
||||
ctypes.windll.kernel32.GetConsoleScreenBufferInfo(
|
||||
handle, ctypes.byref(info))
|
||||
# Substract one from the width, otherwise the cursor wraps
|
||||
# and the ending \n causes an empty line to display.
|
||||
return info.dwSize.Y, info.dwSize.X - 1
|
||||
|
||||
def get_terminal_width():
|
||||
try:
|
||||
height, width = _getdimensions()
|
||||
except (SystemExit, KeyboardInterrupt):
|
||||
raise
|
||||
except:
|
||||
# FALLBACK
|
||||
width = int(os.environ.get('COLUMNS', 80))-1
|
||||
# XXX the windows getdimensions may be bogus, let's sanify a bit
|
||||
width = max(width, 40) # we alaways need 40 chars
|
||||
return width
|
||||
|
||||
terminal_width = get_terminal_width()
|
||||
|
||||
# XXX unify with _escaped func below
|
||||
def ansi_print(text, esc, file=None, newline=True, flush=False):
|
||||
if file is None:
|
||||
file = sys.stderr
|
||||
text = text.rstrip()
|
||||
if esc and not isinstance(esc, tuple):
|
||||
esc = (esc,)
|
||||
if esc and sys.platform != "win32" and file.isatty():
|
||||
text = (''.join(['\x1b[%sm' % cod for cod in esc]) +
|
||||
text +
|
||||
'\x1b[0m') # ANSI color code "reset"
|
||||
if newline:
|
||||
text += '\n'
|
||||
|
||||
if esc and sys.platform == "win32" and file.isatty():
|
||||
if 1 in esc:
|
||||
bold = True
|
||||
esc = tuple([x for x in esc if x != 1])
|
||||
else:
|
||||
bold = False
|
||||
esctable = {() : FOREGROUND_WHITE, # normal
|
||||
(31,): FOREGROUND_RED, # red
|
||||
(32,): FOREGROUND_GREEN, # green
|
||||
(33,): FOREGROUND_GREEN|FOREGROUND_RED, # yellow
|
||||
(34,): FOREGROUND_BLUE, # blue
|
||||
(35,): FOREGROUND_BLUE|FOREGROUND_RED, # purple
|
||||
(36,): FOREGROUND_BLUE|FOREGROUND_GREEN, # cyan
|
||||
(37,): FOREGROUND_WHITE, # white
|
||||
(39,): FOREGROUND_WHITE, # reset
|
||||
}
|
||||
attr = esctable.get(esc, FOREGROUND_WHITE)
|
||||
if bold:
|
||||
attr |= FOREGROUND_INTENSITY
|
||||
STD_OUTPUT_HANDLE = -11
|
||||
STD_ERROR_HANDLE = -12
|
||||
if file is sys.stderr:
|
||||
handle = GetStdHandle(STD_ERROR_HANDLE)
|
||||
else:
|
||||
handle = GetStdHandle(STD_OUTPUT_HANDLE)
|
||||
SetConsoleTextAttribute(handle, attr)
|
||||
file.write(text)
|
||||
SetConsoleTextAttribute(handle, FOREGROUND_WHITE)
|
||||
else:
|
||||
file.write(text)
|
||||
|
||||
if flush:
|
||||
file.flush()
|
||||
|
||||
def should_do_markup(file):
|
||||
return hasattr(file, 'isatty') and file.isatty() \
|
||||
and os.environ.get('TERM') != 'dumb'
|
||||
|
||||
class TerminalWriter(object):
|
||||
_esctable = dict(black=30, red=31, green=32, yellow=33,
|
||||
blue=34, purple=35, cyan=36, white=37,
|
||||
Black=40, Red=41, Green=42, Yellow=43,
|
||||
Blue=44, Purple=45, Cyan=46, White=47,
|
||||
bold=1, light=2, blink=5, invert=7)
|
||||
|
||||
# XXX deprecate stringio argument
|
||||
def __init__(self, file=None, stringio=False, encoding=None):
|
||||
self.encoding = encoding
|
||||
|
||||
if file is None:
|
||||
if stringio:
|
||||
self.stringio = file = py.io.TextIO()
|
||||
else:
|
||||
file = py.std.sys.stdout
|
||||
elif hasattr(file, '__call__'):
|
||||
file = WriteFile(file, encoding=encoding)
|
||||
self._file = file
|
||||
self.fullwidth = get_terminal_width()
|
||||
self.hasmarkup = should_do_markup(file)
|
||||
|
||||
def _escaped(self, text, esc):
|
||||
if esc and self.hasmarkup:
|
||||
text = (''.join(['\x1b[%sm' % cod for cod in esc]) +
|
||||
text +'\x1b[0m')
|
||||
return text
|
||||
|
||||
def markup(self, text, **kw):
|
||||
esc = []
|
||||
for name in kw:
|
||||
if name not in self._esctable:
|
||||
raise ValueError("unknown markup: %r" %(name,))
|
||||
if kw[name]:
|
||||
esc.append(self._esctable[name])
|
||||
return self._escaped(text, tuple(esc))
|
||||
|
||||
def sep(self, sepchar, title=None, fullwidth=None, **kw):
|
||||
if fullwidth is None:
|
||||
fullwidth = self.fullwidth
|
||||
# the goal is to have the line be as long as possible
|
||||
# under the condition that len(line) <= fullwidth
|
||||
if title is not None:
|
||||
# we want 2 + 2*len(fill) + len(title) <= fullwidth
|
||||
# i.e. 2 + 2*len(sepchar)*N + len(title) <= fullwidth
|
||||
# 2*len(sepchar)*N <= fullwidth - len(title) - 2
|
||||
# N <= (fullwidth - len(title) - 2) // (2*len(sepchar))
|
||||
N = (fullwidth - len(title) - 2) // (2*len(sepchar))
|
||||
fill = sepchar * N
|
||||
line = "%s %s %s" % (fill, title, fill)
|
||||
else:
|
||||
# we want len(sepchar)*N <= fullwidth
|
||||
# i.e. N <= fullwidth // len(sepchar)
|
||||
line = sepchar * (fullwidth // len(sepchar))
|
||||
# in some situations there is room for an extra sepchar at the right,
|
||||
# in particular if we consider that with a sepchar like "_ " the
|
||||
# trailing space is not important at the end of the line
|
||||
if len(line) + len(sepchar.rstrip()) <= fullwidth:
|
||||
line += sepchar.rstrip()
|
||||
|
||||
self.line(line, **kw)
|
||||
|
||||
def write(self, s, **kw):
|
||||
if s:
|
||||
s = self._getbytestring(s)
|
||||
if self.hasmarkup and kw:
|
||||
s = self.markup(s, **kw)
|
||||
self._file.write(s)
|
||||
self._file.flush()
|
||||
|
||||
def _getbytestring(self, s):
|
||||
# XXX review this and the whole logic
|
||||
if self.encoding and sys.version_info < (3,0) and isinstance(s, unicode):
|
||||
return s.encode(self.encoding)
|
||||
elif not isinstance(s, str):
|
||||
return str(s)
|
||||
return s
|
||||
|
||||
def line(self, s='', **kw):
|
||||
self.write(s, **kw)
|
||||
self.write('\n')
|
||||
|
||||
class Win32ConsoleWriter(TerminalWriter):
|
||||
def write(self, s, **kw):
|
||||
if s:
|
||||
s = self._getbytestring(s)
|
||||
if self.hasmarkup:
|
||||
handle = GetStdHandle(STD_OUTPUT_HANDLE)
|
||||
|
||||
if self.hasmarkup and kw:
|
||||
attr = 0
|
||||
if kw.pop('bold', False):
|
||||
attr |= FOREGROUND_INTENSITY
|
||||
|
||||
if kw.pop('red', False):
|
||||
attr |= FOREGROUND_RED
|
||||
elif kw.pop('blue', False):
|
||||
attr |= FOREGROUND_BLUE
|
||||
elif kw.pop('green', False):
|
||||
attr |= FOREGROUND_GREEN
|
||||
else:
|
||||
attr |= FOREGROUND_WHITE
|
||||
|
||||
SetConsoleTextAttribute(handle, attr)
|
||||
self._file.write(s)
|
||||
self._file.flush()
|
||||
if self.hasmarkup:
|
||||
SetConsoleTextAttribute(handle, FOREGROUND_WHITE)
|
||||
|
||||
def line(self, s="", **kw):
|
||||
self.write(s+"\n", **kw)
|
||||
|
||||
if sys.platform == 'win32':
|
||||
TerminalWriter = Win32ConsoleWriter
|
||||
|
||||
class WriteFile(object):
|
||||
def __init__(self, writemethod, encoding=None):
|
||||
self.encoding = encoding
|
||||
self._writemethod = writemethod
|
||||
|
||||
def write(self, data):
|
||||
if self.encoding:
|
||||
data = data.encode(self.encoding)
|
||||
self._writemethod(data)
|
||||
|
||||
def flush(self):
|
||||
return
|
||||
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
""" logging API ('producers' and 'consumers' connected via keywords) """
|
||||
|
||||
184
py/log/log.py
184
py/log/log.py
@@ -1,184 +0,0 @@
|
||||
"""
|
||||
basic logging functionality based on a producer/consumer scheme.
|
||||
|
||||
XXX implement this API: (maybe put it into slogger.py?)
|
||||
|
||||
log = Logger(
|
||||
info=py.log.STDOUT,
|
||||
debug=py.log.STDOUT,
|
||||
command=None)
|
||||
log.info("hello", "world")
|
||||
log.command("hello", "world")
|
||||
|
||||
log = Logger(info=Logger(something=...),
|
||||
debug=py.log.STDOUT,
|
||||
command=None)
|
||||
"""
|
||||
import py, sys
|
||||
|
||||
class Message(object):
|
||||
def __init__(self, keywords, args):
|
||||
self.keywords = keywords
|
||||
self.args = args
|
||||
|
||||
def content(self):
|
||||
return " ".join(map(str, self.args))
|
||||
|
||||
def prefix(self):
|
||||
return "[%s] " % (":".join(self.keywords))
|
||||
|
||||
def __str__(self):
|
||||
return self.prefix() + self.content()
|
||||
|
||||
|
||||
class Producer(object):
|
||||
""" (deprecated) Log producer API which sends messages to be logged
|
||||
to a 'consumer' object, which then prints them to stdout,
|
||||
stderr, files, etc. Used extensively by PyPy-1.1.
|
||||
"""
|
||||
|
||||
Message = Message # to allow later customization
|
||||
keywords2consumer = {}
|
||||
|
||||
def __init__(self, keywords, keywordmapper=None, **kw):
|
||||
if hasattr(keywords, 'split'):
|
||||
keywords = tuple(keywords.split())
|
||||
self._keywords = keywords
|
||||
if keywordmapper is None:
|
||||
keywordmapper = default_keywordmapper
|
||||
self._keywordmapper = keywordmapper
|
||||
|
||||
def __repr__(self):
|
||||
return "<py.log.Producer %s>" % ":".join(self._keywords)
|
||||
|
||||
def __getattr__(self, name):
|
||||
if '_' in name:
|
||||
raise AttributeError(name)
|
||||
producer = self.__class__(self._keywords + (name,))
|
||||
setattr(self, name, producer)
|
||||
return producer
|
||||
|
||||
def __call__(self, *args):
|
||||
""" write a message to the appropriate consumer(s) """
|
||||
func = self._keywordmapper.getconsumer(self._keywords)
|
||||
if func is not None:
|
||||
func(self.Message(self._keywords, args))
|
||||
|
||||
class KeywordMapper:
|
||||
def __init__(self):
|
||||
self.keywords2consumer = {}
|
||||
|
||||
def getstate(self):
|
||||
return self.keywords2consumer.copy()
|
||||
def setstate(self, state):
|
||||
self.keywords2consumer.clear()
|
||||
self.keywords2consumer.update(state)
|
||||
|
||||
def getconsumer(self, keywords):
|
||||
""" return a consumer matching the given keywords.
|
||||
|
||||
tries to find the most suitable consumer by walking, starting from
|
||||
the back, the list of keywords, the first consumer matching a
|
||||
keyword is returned (falling back to py.log.default)
|
||||
"""
|
||||
for i in range(len(keywords), 0, -1):
|
||||
try:
|
||||
return self.keywords2consumer[keywords[:i]]
|
||||
except KeyError:
|
||||
continue
|
||||
return self.keywords2consumer.get('default', default_consumer)
|
||||
|
||||
def setconsumer(self, keywords, consumer):
|
||||
""" set a consumer for a set of keywords. """
|
||||
# normalize to tuples
|
||||
if isinstance(keywords, str):
|
||||
keywords = tuple(filter(None, keywords.split()))
|
||||
elif hasattr(keywords, '_keywords'):
|
||||
keywords = keywords._keywords
|
||||
elif not isinstance(keywords, tuple):
|
||||
raise TypeError("key %r is not a string or tuple" % (keywords,))
|
||||
if consumer is not None and not py.builtin.callable(consumer):
|
||||
if not hasattr(consumer, 'write'):
|
||||
raise TypeError(
|
||||
"%r should be None, callable or file-like" % (consumer,))
|
||||
consumer = File(consumer)
|
||||
self.keywords2consumer[keywords] = consumer
|
||||
|
||||
def default_consumer(msg):
|
||||
""" the default consumer, prints the message to stdout (using 'print') """
|
||||
sys.stderr.write(str(msg)+"\n")
|
||||
|
||||
default_keywordmapper = KeywordMapper()
|
||||
|
||||
def setconsumer(keywords, consumer):
|
||||
default_keywordmapper.setconsumer(keywords, consumer)
|
||||
|
||||
def setstate(state):
|
||||
default_keywordmapper.setstate(state)
|
||||
def getstate():
|
||||
return default_keywordmapper.getstate()
|
||||
|
||||
#
|
||||
# Consumers
|
||||
#
|
||||
|
||||
class File(object):
|
||||
""" log consumer wrapping a file(-like) object """
|
||||
def __init__(self, f):
|
||||
assert hasattr(f, 'write')
|
||||
#assert isinstance(f, file) or not hasattr(f, 'open')
|
||||
self._file = f
|
||||
|
||||
def __call__(self, msg):
|
||||
""" write a message to the log """
|
||||
self._file.write(str(msg) + "\n")
|
||||
|
||||
class Path(object):
|
||||
""" log consumer that opens and writes to a Path """
|
||||
def __init__(self, filename, append=False,
|
||||
delayed_create=False, buffering=False):
|
||||
self._append = append
|
||||
self._filename = str(filename)
|
||||
self._buffering = buffering
|
||||
if not delayed_create:
|
||||
self._openfile()
|
||||
|
||||
def _openfile(self):
|
||||
mode = self._append and 'a' or 'w'
|
||||
f = open(self._filename, mode)
|
||||
self._file = f
|
||||
|
||||
def __call__(self, msg):
|
||||
""" write a message to the log """
|
||||
if not hasattr(self, "_file"):
|
||||
self._openfile()
|
||||
self._file.write(str(msg) + "\n")
|
||||
if not self._buffering:
|
||||
self._file.flush()
|
||||
|
||||
def STDOUT(msg):
|
||||
""" consumer that writes to sys.stdout """
|
||||
sys.stdout.write(str(msg)+"\n")
|
||||
|
||||
def STDERR(msg):
|
||||
""" consumer that writes to sys.stderr """
|
||||
sys.stderr.write(str(msg)+"\n")
|
||||
|
||||
class Syslog:
|
||||
""" consumer that writes to the syslog daemon """
|
||||
|
||||
def __init__(self, priority = None):
|
||||
if priority is None:
|
||||
priority = self.LOG_INFO
|
||||
self.priority = priority
|
||||
|
||||
def __call__(self, msg):
|
||||
""" write a message to the log """
|
||||
py.std.syslog.syslog(self.priority, str(msg))
|
||||
|
||||
for _prio in "EMERG ALERT CRIT ERR WARNING NOTICE INFO DEBUG".split():
|
||||
_prio = "LOG_" + _prio
|
||||
try:
|
||||
setattr(Syslog, _prio, getattr(py.std.syslog, _prio))
|
||||
except AttributeError:
|
||||
pass
|
||||
@@ -1,70 +0,0 @@
|
||||
import py, sys
|
||||
|
||||
class Warning(DeprecationWarning):
|
||||
def __init__(self, msg, path, lineno):
|
||||
self.msg = msg
|
||||
self.path = path
|
||||
self.lineno = lineno
|
||||
def __repr__(self):
|
||||
return "%s:%d: %s" %(self.path, self.lineno+1, self.msg)
|
||||
def __str__(self):
|
||||
return self.msg
|
||||
|
||||
def _apiwarn(startversion, msg, stacklevel=2, function=None):
|
||||
# below is mostly COPIED from python2.4/warnings.py's def warn()
|
||||
# Get context information
|
||||
if stacklevel == "initpkg":
|
||||
frame = sys._getframe(stacklevel == "initpkg" and 1 or stacklevel)
|
||||
level = 2
|
||||
while frame:
|
||||
co = frame.f_code
|
||||
if co.co_name == "__getattr__" and co.co_filename.find("initpkg") !=-1:
|
||||
stacklevel = level
|
||||
break
|
||||
level += 1
|
||||
frame = frame.f_back
|
||||
else:
|
||||
stacklevel = 1
|
||||
msg = "%s (since version %s)" %(msg, startversion)
|
||||
warn(msg, stacklevel=stacklevel+1, function=function)
|
||||
|
||||
def warn(msg, stacklevel=1, function=None):
|
||||
if function is not None:
|
||||
filename = py.std.inspect.getfile(function)
|
||||
lineno = py.code.getrawcode(function).co_firstlineno
|
||||
else:
|
||||
try:
|
||||
caller = sys._getframe(stacklevel)
|
||||
except ValueError:
|
||||
globals = sys.__dict__
|
||||
lineno = 1
|
||||
else:
|
||||
globals = caller.f_globals
|
||||
lineno = caller.f_lineno
|
||||
if '__name__' in globals:
|
||||
module = globals['__name__']
|
||||
else:
|
||||
module = "<string>"
|
||||
filename = globals.get('__file__')
|
||||
if filename:
|
||||
fnl = filename.lower()
|
||||
if fnl.endswith(".pyc") or fnl.endswith(".pyo"):
|
||||
filename = filename[:-1]
|
||||
else:
|
||||
if module == "__main__":
|
||||
try:
|
||||
filename = sys.argv[0]
|
||||
except AttributeError:
|
||||
# embedded interpreters don't have sys.argv, see bug #839151
|
||||
filename = '__main__'
|
||||
if not filename:
|
||||
filename = module
|
||||
path = py.path.local(filename)
|
||||
warning = Warning(msg, path, lineno)
|
||||
py.std.warnings.warn_explicit(warning, category=Warning,
|
||||
filename=str(warning.path),
|
||||
lineno=warning.lineno,
|
||||
registry=py.std.warnings.__dict__.setdefault(
|
||||
"__warningsregistry__", {})
|
||||
)
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
""" unified file system api """
|
||||
@@ -1,111 +0,0 @@
|
||||
"""
|
||||
This module contains multithread-safe cache implementations.
|
||||
|
||||
All Caches have
|
||||
|
||||
getorbuild(key, builder)
|
||||
delentry(key)
|
||||
|
||||
methods and allow configuration when instantiating the cache class.
|
||||
"""
|
||||
from time import time as gettime
|
||||
|
||||
class BasicCache(object):
|
||||
def __init__(self, maxentries=128):
|
||||
self.maxentries = maxentries
|
||||
self.prunenum = int(maxentries - maxentries/8)
|
||||
self._dict = {}
|
||||
|
||||
def _getentry(self, key):
|
||||
return self._dict[key]
|
||||
|
||||
def _putentry(self, key, entry):
|
||||
self._prunelowestweight()
|
||||
self._dict[key] = entry
|
||||
|
||||
def delentry(self, key, raising=False):
|
||||
try:
|
||||
del self._dict[key]
|
||||
except KeyError:
|
||||
if raising:
|
||||
raise
|
||||
|
||||
def getorbuild(self, key, builder):
|
||||
try:
|
||||
entry = self._getentry(key)
|
||||
except KeyError:
|
||||
entry = self._build(key, builder)
|
||||
self._putentry(key, entry)
|
||||
return entry.value
|
||||
|
||||
def _prunelowestweight(self):
|
||||
""" prune out entries with lowest weight. """
|
||||
numentries = len(self._dict)
|
||||
if numentries >= self.maxentries:
|
||||
# evict according to entry's weight
|
||||
items = [(entry.weight, key)
|
||||
for key, entry in self._dict.items()]
|
||||
items.sort()
|
||||
index = numentries - self.prunenum
|
||||
if index > 0:
|
||||
for weight, key in items[:index]:
|
||||
# in MT situations the element might be gone
|
||||
self.delentry(key, raising=False)
|
||||
|
||||
class BuildcostAccessCache(BasicCache):
|
||||
""" A BuildTime/Access-counting cache implementation.
|
||||
the weight of a value is computed as the product of
|
||||
|
||||
num-accesses-of-a-value * time-to-build-the-value
|
||||
|
||||
The values with the least such weights are evicted
|
||||
if the cache maxentries threshold is superceded.
|
||||
For implementation flexibility more than one object
|
||||
might be evicted at a time.
|
||||
"""
|
||||
# time function to use for measuring build-times
|
||||
|
||||
def _build(self, key, builder):
|
||||
start = gettime()
|
||||
val = builder()
|
||||
end = gettime()
|
||||
return WeightedCountingEntry(val, end-start)
|
||||
|
||||
|
||||
class WeightedCountingEntry(object):
|
||||
def __init__(self, value, oneweight):
|
||||
self._value = value
|
||||
self.weight = self._oneweight = oneweight
|
||||
|
||||
def value(self):
|
||||
self.weight += self._oneweight
|
||||
return self._value
|
||||
value = property(value)
|
||||
|
||||
class AgingCache(BasicCache):
|
||||
""" This cache prunes out cache entries that are too old.
|
||||
"""
|
||||
def __init__(self, maxentries=128, maxseconds=10.0):
|
||||
super(AgingCache, self).__init__(maxentries)
|
||||
self.maxseconds = maxseconds
|
||||
|
||||
def _getentry(self, key):
|
||||
entry = self._dict[key]
|
||||
if entry.isexpired():
|
||||
self.delentry(key)
|
||||
raise KeyError(key)
|
||||
return entry
|
||||
|
||||
def _build(self, key, builder):
|
||||
val = builder()
|
||||
entry = AgingEntry(val, gettime() + self.maxseconds)
|
||||
return entry
|
||||
|
||||
class AgingEntry(object):
|
||||
def __init__(self, value, expirationtime):
|
||||
self.value = value
|
||||
self.weight = expirationtime
|
||||
|
||||
def isexpired(self):
|
||||
t = gettime()
|
||||
return t >= self.weight
|
||||
@@ -1,329 +0,0 @@
|
||||
"""
|
||||
"""
|
||||
import os, sys
|
||||
import py
|
||||
|
||||
class Checkers:
|
||||
_depend_on_existence = 'exists', 'link', 'dir', 'file'
|
||||
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
|
||||
def dir(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def file(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def dotfile(self):
|
||||
return self.path.basename.startswith('.')
|
||||
|
||||
def ext(self, arg):
|
||||
if not arg.startswith('.'):
|
||||
arg = '.' + arg
|
||||
return self.path.ext == arg
|
||||
|
||||
def exists(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def basename(self, arg):
|
||||
return self.path.basename == arg
|
||||
|
||||
def basestarts(self, arg):
|
||||
return self.path.basename.startswith(arg)
|
||||
|
||||
def relto(self, arg):
|
||||
return self.path.relto(arg)
|
||||
|
||||
def fnmatch(self, arg):
|
||||
return FNMatcher(arg)(self.path)
|
||||
|
||||
def endswith(self, arg):
|
||||
return str(self.path).endswith(arg)
|
||||
|
||||
def _evaluate(self, kw):
|
||||
for name, value in kw.items():
|
||||
invert = False
|
||||
meth = None
|
||||
try:
|
||||
meth = getattr(self, name)
|
||||
except AttributeError:
|
||||
if name[:3] == 'not':
|
||||
invert = True
|
||||
try:
|
||||
meth = getattr(self, name[3:])
|
||||
except AttributeError:
|
||||
pass
|
||||
if meth is None:
|
||||
raise TypeError(
|
||||
"no %r checker available for %r" % (name, self.path))
|
||||
try:
|
||||
if py.code.getrawcode(meth).co_argcount > 1:
|
||||
if (not meth(value)) ^ invert:
|
||||
return False
|
||||
else:
|
||||
if bool(value) ^ bool(meth()) ^ invert:
|
||||
return False
|
||||
except (py.error.ENOENT, py.error.ENOTDIR):
|
||||
for name in self._depend_on_existence:
|
||||
if name in kw:
|
||||
if kw.get(name):
|
||||
return False
|
||||
name = 'not' + name
|
||||
if name in kw:
|
||||
if not kw.get(name):
|
||||
return False
|
||||
return True
|
||||
|
||||
class NeverRaised(Exception):
|
||||
pass
|
||||
|
||||
class PathBase(object):
|
||||
""" shared implementation for filesystem path objects."""
|
||||
Checkers = Checkers
|
||||
|
||||
def __div__(self, other):
|
||||
return self.join(str(other))
|
||||
__truediv__ = __div__ # py3k
|
||||
|
||||
def basename(self):
|
||||
""" basename part of path. """
|
||||
return self._getbyspec('basename')[0]
|
||||
basename = property(basename, None, None, basename.__doc__)
|
||||
|
||||
def purebasename(self):
|
||||
""" pure base name of the path."""
|
||||
return self._getbyspec('purebasename')[0]
|
||||
purebasename = property(purebasename, None, None, purebasename.__doc__)
|
||||
|
||||
def ext(self):
|
||||
""" extension of the path (including the '.')."""
|
||||
return self._getbyspec('ext')[0]
|
||||
ext = property(ext, None, None, ext.__doc__)
|
||||
|
||||
def dirpath(self, *args, **kwargs):
|
||||
""" return the directory Path of the current Path joined
|
||||
with any given path arguments.
|
||||
"""
|
||||
return self.new(basename='').join(*args, **kwargs)
|
||||
|
||||
def read(self, mode='r'):
|
||||
""" read and return a bytestring from reading the path. """
|
||||
if sys.version_info < (2,3):
|
||||
for x in 'u', 'U':
|
||||
if x in mode:
|
||||
mode = mode.replace(x, '')
|
||||
f = self.open(mode)
|
||||
try:
|
||||
return f.read()
|
||||
finally:
|
||||
f.close()
|
||||
|
||||
def readlines(self, cr=1):
|
||||
""" read and return a list of lines from the path. if cr is False, the
|
||||
newline will be removed from the end of each line. """
|
||||
if not cr:
|
||||
content = self.read('rU')
|
||||
return content.split('\n')
|
||||
else:
|
||||
f = self.open('rU')
|
||||
try:
|
||||
return f.readlines()
|
||||
finally:
|
||||
f.close()
|
||||
|
||||
def load(self):
|
||||
""" (deprecated) return object unpickled from self.read() """
|
||||
f = self.open('rb')
|
||||
try:
|
||||
return py.error.checked_call(py.std.pickle.load, f)
|
||||
finally:
|
||||
f.close()
|
||||
|
||||
def move(self, target):
|
||||
""" move this path to target. """
|
||||
if target.relto(self):
|
||||
raise py.error.EINVAL(target,
|
||||
"cannot move path into a subdirectory of itself")
|
||||
try:
|
||||
self.rename(target)
|
||||
except py.error.EXDEV: # invalid cross-device link
|
||||
self.copy(target)
|
||||
self.remove()
|
||||
|
||||
def __repr__(self):
|
||||
""" return a string representation of this path. """
|
||||
return repr(str(self))
|
||||
|
||||
def check(self, **kw):
|
||||
""" check a path for existence, or query its properties
|
||||
|
||||
without arguments, this returns True if the path exists (on the
|
||||
filesystem), False if not
|
||||
|
||||
with (keyword only) arguments, the object compares the value
|
||||
of the argument with the value of a property with the same name
|
||||
(if it has one, else it raises a TypeError)
|
||||
|
||||
when for example the keyword argument 'ext' is '.py', this will
|
||||
return True if self.ext == '.py', False otherwise
|
||||
"""
|
||||
if not kw:
|
||||
kw = {'exists' : 1}
|
||||
return self.Checkers(self)._evaluate(kw)
|
||||
|
||||
def relto(self, relpath):
|
||||
""" return a string which is the relative part of the path
|
||||
to the given 'relpath'.
|
||||
"""
|
||||
if not isinstance(relpath, (str, PathBase)):
|
||||
raise TypeError("%r: not a string or path object" %(relpath,))
|
||||
strrelpath = str(relpath)
|
||||
if strrelpath and strrelpath[-1] != self.sep:
|
||||
strrelpath += self.sep
|
||||
#assert strrelpath[-1] == self.sep
|
||||
#assert strrelpath[-2] != self.sep
|
||||
strself = str(self)
|
||||
if sys.platform == "win32":
|
||||
if os.path.normcase(strself).startswith(
|
||||
os.path.normcase(strrelpath)):
|
||||
return strself[len(strrelpath):]
|
||||
elif strself.startswith(strrelpath):
|
||||
return strself[len(strrelpath):]
|
||||
return ""
|
||||
|
||||
def bestrelpath(self, dest):
|
||||
""" return a string which is a relative path from self
|
||||
to dest such that self.join(bestrelpath) == dest and
|
||||
if not such path can be determined return dest.
|
||||
"""
|
||||
try:
|
||||
base = self.common(dest)
|
||||
if not base: # can be the case on windows
|
||||
return str(dest)
|
||||
self2base = self.relto(base)
|
||||
reldest = dest.relto(base)
|
||||
if self2base:
|
||||
n = self2base.count(self.sep) + 1
|
||||
else:
|
||||
n = 0
|
||||
l = ['..'] * n
|
||||
if reldest:
|
||||
l.append(reldest)
|
||||
target = dest.sep.join(l)
|
||||
return target
|
||||
except AttributeError:
|
||||
return str(dest)
|
||||
|
||||
|
||||
def parts(self, reverse=False):
|
||||
""" return a root-first list of all ancestor directories
|
||||
plus the path itself.
|
||||
"""
|
||||
current = self
|
||||
l = [self]
|
||||
while 1:
|
||||
last = current
|
||||
current = current.dirpath()
|
||||
if last == current:
|
||||
break
|
||||
l.insert(0, current)
|
||||
if reverse:
|
||||
l.reverse()
|
||||
return l
|
||||
|
||||
def common(self, other):
|
||||
""" return the common part shared with the other path
|
||||
or None if there is no common part.
|
||||
"""
|
||||
last = None
|
||||
for x, y in zip(self.parts(), other.parts()):
|
||||
if x != y:
|
||||
return last
|
||||
last = x
|
||||
return last
|
||||
|
||||
def __add__(self, other):
|
||||
""" return new path object with 'other' added to the basename"""
|
||||
return self.new(basename=self.basename+str(other))
|
||||
|
||||
def __cmp__(self, other):
|
||||
""" return sort value (-1, 0, +1). """
|
||||
try:
|
||||
return cmp(self.strpath, other.strpath)
|
||||
except AttributeError:
|
||||
return cmp(str(self), str(other)) # self.path, other.path)
|
||||
|
||||
def __lt__(self, other):
|
||||
try:
|
||||
return self.strpath < other.strpath
|
||||
except AttributeError:
|
||||
return str(self) < str(other)
|
||||
|
||||
def visit(self, fil=None, rec=None, ignore=NeverRaised):
|
||||
""" yields all paths below the current one
|
||||
|
||||
fil is a filter (glob pattern or callable), if not matching the
|
||||
path will not be yielded, defaulting to None (everything is
|
||||
returned)
|
||||
|
||||
rec is a filter (glob pattern or callable) that controls whether
|
||||
a node is descended, defaulting to None
|
||||
|
||||
ignore is an Exception class that is ignoredwhen calling dirlist()
|
||||
on any of the paths (by default, all exceptions are reported)
|
||||
"""
|
||||
if isinstance(fil, str):
|
||||
fil = FNMatcher(fil)
|
||||
if rec:
|
||||
if isinstance(rec, str):
|
||||
rec = fnmatch(fil)
|
||||
elif not hasattr(rec, '__call__'):
|
||||
rec = None
|
||||
try:
|
||||
entries = self.listdir()
|
||||
except ignore:
|
||||
return
|
||||
dirs = [p for p in entries
|
||||
if p.check(dir=1) and (rec is None or rec(p))]
|
||||
for subdir in dirs:
|
||||
for p in subdir.visit(fil=fil, rec=rec, ignore=ignore):
|
||||
yield p
|
||||
for p in entries:
|
||||
if fil is None or fil(p):
|
||||
yield p
|
||||
|
||||
def _sortlist(self, res, sort):
|
||||
if sort:
|
||||
if hasattr(sort, '__call__'):
|
||||
res.sort(sort)
|
||||
else:
|
||||
res.sort()
|
||||
|
||||
class FNMatcher:
|
||||
def __init__(self, pattern):
|
||||
self.pattern = pattern
|
||||
def __call__(self, path):
|
||||
"""return true if the basename/fullname matches the glob-'pattern'.
|
||||
|
||||
* matches everything
|
||||
? matches any single character
|
||||
[seq] matches any character in seq
|
||||
[!seq] matches any char not in seq
|
||||
|
||||
if the pattern contains a path-separator then the full path
|
||||
is used for pattern matching and a '*' is prepended to the
|
||||
pattern.
|
||||
|
||||
if the pattern doesn't contain a path-separator the pattern
|
||||
is only matched against the basename.
|
||||
"""
|
||||
pattern = self.pattern
|
||||
if pattern.find(path.sep) == -1:
|
||||
name = path.basename
|
||||
else:
|
||||
name = str(path) # path.strpath # XXX svn?
|
||||
pattern = '*' + path.sep + pattern
|
||||
from fnmatch import fnmatch
|
||||
return fnmatch(name, pattern)
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
#
|
||||
@@ -1,65 +0,0 @@
|
||||
import threading
|
||||
|
||||
|
||||
class PathServer:
|
||||
|
||||
def __init__(self, channel):
|
||||
self.channel = channel
|
||||
self.C2P = {}
|
||||
self.next_id = 0
|
||||
threading.Thread(target=self.serve).start()
|
||||
|
||||
def p2c(self, path):
|
||||
id = self.next_id
|
||||
self.next_id += 1
|
||||
self.C2P[id] = path
|
||||
return id
|
||||
|
||||
def command_LIST(self, id, *args):
|
||||
path = self.C2P[id]
|
||||
answer = [(self.p2c(p), p.basename) for p in path.listdir(*args)]
|
||||
self.channel.send(answer)
|
||||
|
||||
def command_DEL(self, id):
|
||||
del self.C2P[id]
|
||||
|
||||
def command_GET(self, id, spec):
|
||||
path = self.C2P[id]
|
||||
self.channel.send(path._getbyspec(spec))
|
||||
|
||||
def command_READ(self, id):
|
||||
path = self.C2P[id]
|
||||
self.channel.send(path.read())
|
||||
|
||||
def command_JOIN(self, id, resultid, *args):
|
||||
path = self.C2P[id]
|
||||
assert resultid not in self.C2P
|
||||
self.C2P[resultid] = path.join(*args)
|
||||
|
||||
def command_DIRPATH(self, id, resultid):
|
||||
path = self.C2P[id]
|
||||
assert resultid not in self.C2P
|
||||
self.C2P[resultid] = path.dirpath()
|
||||
|
||||
def serve(self):
|
||||
try:
|
||||
while 1:
|
||||
msg = self.channel.receive()
|
||||
meth = getattr(self, 'command_' + msg[0])
|
||||
meth(*msg[1:])
|
||||
except EOFError:
|
||||
pass
|
||||
|
||||
if __name__ == '__main__':
|
||||
import py
|
||||
gw = execnet.PopenGateway()
|
||||
channel = gw._channelfactory.new()
|
||||
srv = PathServer(channel)
|
||||
c = gw.remote_exec("""
|
||||
import remotepath
|
||||
p = remotepath.RemotePath(channel.receive(), channel.receive())
|
||||
channel.send(len(p.listdir()))
|
||||
""")
|
||||
c.send(channel)
|
||||
c.send(srv.p2c(py.path.local('/tmp')))
|
||||
print(c.receive())
|
||||
@@ -1,21 +0,0 @@
|
||||
import py
|
||||
from remotepath import RemotePath
|
||||
|
||||
|
||||
SRC = open('channeltest.py', 'r').read()
|
||||
|
||||
SRC += '''
|
||||
import py
|
||||
srv = PathServer(channel.receive())
|
||||
channel.send(srv.p2c(py.path.local("/tmp")))
|
||||
'''
|
||||
|
||||
|
||||
#gw = execnet.SshGateway('codespeak.net')
|
||||
gw = execnet.PopenGateway()
|
||||
gw.remote_init_threads(5)
|
||||
c = gw.remote_exec(SRC, stdout=py.std.sys.stdout, stderr=py.std.sys.stderr)
|
||||
subchannel = gw._channelfactory.new()
|
||||
c.send(subchannel)
|
||||
|
||||
p = RemotePath(subchannel, c.receive())
|
||||
@@ -1,47 +0,0 @@
|
||||
import py, itertools
|
||||
from py.__.path import common
|
||||
|
||||
COUNTER = itertools.count()
|
||||
|
||||
class RemotePath(common.PathBase):
|
||||
sep = '/'
|
||||
|
||||
def __init__(self, channel, id, basename=None):
|
||||
self._channel = channel
|
||||
self._id = id
|
||||
self._basename = basename
|
||||
self._specs = {}
|
||||
|
||||
def __del__(self):
|
||||
self._channel.send(('DEL', self._id))
|
||||
|
||||
def __repr__(self):
|
||||
return 'RemotePath(%s)' % self.basename
|
||||
|
||||
def listdir(self, *args):
|
||||
self._channel.send(('LIST', self._id) + args)
|
||||
return [RemotePath(self._channel, id, basename)
|
||||
for (id, basename) in self._channel.receive()]
|
||||
|
||||
def dirpath(self):
|
||||
id = ~COUNTER.next()
|
||||
self._channel.send(('DIRPATH', self._id, id))
|
||||
return RemotePath(self._channel, id)
|
||||
|
||||
def join(self, *args):
|
||||
id = ~COUNTER.next()
|
||||
self._channel.send(('JOIN', self._id, id) + args)
|
||||
return RemotePath(self._channel, id)
|
||||
|
||||
def _getbyspec(self, spec):
|
||||
parts = spec.split(',')
|
||||
ask = [x for x in parts if x not in self._specs]
|
||||
if ask:
|
||||
self._channel.send(('GET', self._id, ",".join(ask)))
|
||||
for part, value in zip(ask, self._channel.receive()):
|
||||
self._specs[part] = value
|
||||
return [self._specs[x] for x in parts]
|
||||
|
||||
def read(self):
|
||||
self._channel.send(('READ', self._id))
|
||||
return self._channel.receive()
|
||||
797
py/path/local.py
797
py/path/local.py
@@ -1,797 +0,0 @@
|
||||
"""
|
||||
local path implementation.
|
||||
"""
|
||||
from __future__ import generators
|
||||
import sys, os, stat, re, atexit
|
||||
import py
|
||||
from py.__.path import common
|
||||
|
||||
iswin32 = sys.platform == "win32"
|
||||
|
||||
class Stat(object):
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._osstatresult, "st_" + name)
|
||||
|
||||
def __init__(self, path, osstatresult):
|
||||
self.path = path
|
||||
self._osstatresult = osstatresult
|
||||
|
||||
def owner(self):
|
||||
if iswin32:
|
||||
raise NotImplementedError("XXX win32")
|
||||
import pwd
|
||||
entry = py.error.checked_call(pwd.getpwuid, self.uid)
|
||||
return entry[0]
|
||||
owner = property(owner, None, None, "owner of path")
|
||||
|
||||
def group(self):
|
||||
""" return group name of file. """
|
||||
if iswin32:
|
||||
raise NotImplementedError("XXX win32")
|
||||
import grp
|
||||
entry = py.error.checked_call(grp.getgrgid, self.gid)
|
||||
return entry[0]
|
||||
group = property(group)
|
||||
|
||||
class PosixPath(common.PathBase):
|
||||
def chown(self, user, group, rec=0):
|
||||
""" change ownership to the given user and group.
|
||||
user and group may be specified by a number or
|
||||
by a name. if rec is True change ownership
|
||||
recursively.
|
||||
"""
|
||||
uid = getuserid(user)
|
||||
gid = getgroupid(group)
|
||||
if rec:
|
||||
for x in self.visit(rec=lambda x: x.check(link=0)):
|
||||
if x.check(link=0):
|
||||
py.error.checked_call(os.chown, str(x), uid, gid)
|
||||
py.error.checked_call(os.chown, str(self), uid, gid)
|
||||
|
||||
def readlink(self):
|
||||
""" return value of a symbolic link. """
|
||||
return py.error.checked_call(os.readlink, self.strpath)
|
||||
|
||||
def mklinkto(self, oldname):
|
||||
""" posix style hard link to another name. """
|
||||
py.error.checked_call(os.link, str(oldname), str(self))
|
||||
|
||||
def mksymlinkto(self, value, absolute=1):
|
||||
""" create a symbolic link with the given value (pointing to another name). """
|
||||
if absolute:
|
||||
py.error.checked_call(os.symlink, str(value), self.strpath)
|
||||
else:
|
||||
base = self.common(value)
|
||||
# with posix local paths '/' is always a common base
|
||||
relsource = self.__class__(value).relto(base)
|
||||
reldest = self.relto(base)
|
||||
n = reldest.count(self.sep)
|
||||
target = self.sep.join(('..', )*n + (relsource, ))
|
||||
py.error.checked_call(os.symlink, target, self.strpath)
|
||||
|
||||
def samefile(self, other):
|
||||
""" return True if other refers to the same stat object as self. """
|
||||
return py.std.os.path.samefile(str(self), str(other))
|
||||
|
||||
def getuserid(user):
|
||||
import pwd
|
||||
if not isinstance(user, int):
|
||||
user = pwd.getpwnam(user)[2]
|
||||
return user
|
||||
|
||||
def getgroupid(group):
|
||||
import grp
|
||||
if not isinstance(group, int):
|
||||
group = grp.getgrnam(group)[2]
|
||||
return group
|
||||
|
||||
FSBase = not iswin32 and PosixPath or common.PathBase
|
||||
|
||||
class LocalPath(FSBase):
|
||||
""" object oriented interface to os.path and other local filesystem
|
||||
related information.
|
||||
"""
|
||||
sep = os.sep
|
||||
class Checkers(common.Checkers):
|
||||
def _stat(self):
|
||||
try:
|
||||
return self._statcache
|
||||
except AttributeError:
|
||||
try:
|
||||
self._statcache = self.path.stat()
|
||||
except py.error.ELOOP:
|
||||
self._statcache = self.path.lstat()
|
||||
return self._statcache
|
||||
|
||||
def dir(self):
|
||||
return stat.S_ISDIR(self._stat().mode)
|
||||
|
||||
def file(self):
|
||||
return stat.S_ISREG(self._stat().mode)
|
||||
|
||||
def exists(self):
|
||||
return self._stat()
|
||||
|
||||
def link(self):
|
||||
st = self.path.lstat()
|
||||
return stat.S_ISLNK(st.mode)
|
||||
|
||||
def __new__(cls, path=None):
|
||||
""" Initialize and return a local Path instance.
|
||||
|
||||
Path can be relative to the current directory.
|
||||
If it is None then the current working directory is taken.
|
||||
Note that Path instances always carry an absolute path.
|
||||
Note also that passing in a local path object will simply return
|
||||
the exact same path object. Use new() to get a new copy.
|
||||
"""
|
||||
if isinstance(path, common.PathBase):
|
||||
if path.__class__ == cls:
|
||||
return path
|
||||
path = path.strpath
|
||||
# initialize the path
|
||||
self = object.__new__(cls)
|
||||
if not path:
|
||||
self.strpath = os.getcwd()
|
||||
elif isinstance(path, py.builtin._basestring):
|
||||
self.strpath = os.path.abspath(os.path.normpath(str(path)))
|
||||
else:
|
||||
raise ValueError("can only pass None, Path instances "
|
||||
"or non-empty strings to LocalPath")
|
||||
assert isinstance(self.strpath, str)
|
||||
return self
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.strpath)
|
||||
|
||||
def __eq__(self, other):
|
||||
s1 = str(self)
|
||||
s2 = str(other)
|
||||
if iswin32:
|
||||
s1 = s1.lower()
|
||||
s2 = s2.lower()
|
||||
return s1 == s2
|
||||
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
|
||||
def __lt__(self, other):
|
||||
return str(self) < str(other)
|
||||
|
||||
def remove(self, rec=1):
|
||||
""" remove a file or directory (or a directory tree if rec=1). """
|
||||
if self.check(dir=1, link=0):
|
||||
if rec:
|
||||
# force remove of readonly files on windows
|
||||
if iswin32:
|
||||
self.chmod(448, rec=1) # octcal 0700
|
||||
py.error.checked_call(py.std.shutil.rmtree, self.strpath)
|
||||
else:
|
||||
py.error.checked_call(os.rmdir, self.strpath)
|
||||
else:
|
||||
if iswin32:
|
||||
self.chmod(448) # octcal 0700
|
||||
py.error.checked_call(os.remove, self.strpath)
|
||||
|
||||
def computehash(self, hashtype="md5", chunksize=524288):
|
||||
""" return hexdigest of hashvalue for this file. """
|
||||
try:
|
||||
try:
|
||||
import hashlib as mod
|
||||
except ImportError:
|
||||
if hashtype == "sha1":
|
||||
hashtype = "sha"
|
||||
mod = __import__(hashtype)
|
||||
hash = getattr(mod, hashtype)()
|
||||
except (AttributeError, ImportError):
|
||||
raise ValueError("Don't know how to compute %r hash" %(hashtype,))
|
||||
f = self.open('rb')
|
||||
try:
|
||||
while 1:
|
||||
buf = f.read(chunksize)
|
||||
if not buf:
|
||||
return hash.hexdigest()
|
||||
hash.update(buf)
|
||||
finally:
|
||||
f.close()
|
||||
|
||||
def new(self, **kw):
|
||||
""" create a modified version of this path.
|
||||
the following keyword arguments modify various path parts:
|
||||
|
||||
a:/some/path/to/a/file.ext
|
||||
|| drive
|
||||
|-------------| dirname
|
||||
|------| basename
|
||||
|--| purebasename
|
||||
|--| ext
|
||||
"""
|
||||
obj = object.__new__(self.__class__)
|
||||
drive, dirname, basename, purebasename,ext = self._getbyspec(
|
||||
"drive,dirname,basename,purebasename,ext")
|
||||
if 'basename' in kw:
|
||||
if 'purebasename' in kw or 'ext' in kw:
|
||||
raise ValueError("invalid specification %r" % kw)
|
||||
else:
|
||||
pb = kw.setdefault('purebasename', purebasename)
|
||||
try:
|
||||
ext = kw['ext']
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
if ext and not ext.startswith('.'):
|
||||
ext = '.' + ext
|
||||
kw['basename'] = pb + ext
|
||||
|
||||
kw.setdefault('drive', drive)
|
||||
kw.setdefault('dirname', dirname)
|
||||
kw.setdefault('sep', self.sep)
|
||||
obj.strpath = os.path.normpath(
|
||||
"%(drive)s%(dirname)s%(sep)s%(basename)s" % kw)
|
||||
return obj
|
||||
|
||||
def _getbyspec(self, spec):
|
||||
""" return a sequence of specified path parts. 'spec' is
|
||||
a comma separated string containing path part names.
|
||||
according to the following convention:
|
||||
a:/some/path/to/a/file.ext
|
||||
|| drive
|
||||
|-------------| dirname
|
||||
|------| basename
|
||||
|--| purebasename
|
||||
|--| ext
|
||||
"""
|
||||
res = []
|
||||
parts = self.strpath.split(self.sep)
|
||||
|
||||
args = filter(None, spec.split(',') )
|
||||
append = res.append
|
||||
for name in args:
|
||||
if name == 'drive':
|
||||
append(parts[0])
|
||||
elif name == 'dirname':
|
||||
append(self.sep.join(['']+parts[1:-1]))
|
||||
else:
|
||||
basename = parts[-1]
|
||||
if name == 'basename':
|
||||
append(basename)
|
||||
else:
|
||||
i = basename.rfind('.')
|
||||
if i == -1:
|
||||
purebasename, ext = basename, ''
|
||||
else:
|
||||
purebasename, ext = basename[:i], basename[i:]
|
||||
if name == 'purebasename':
|
||||
append(purebasename)
|
||||
elif name == 'ext':
|
||||
append(ext)
|
||||
else:
|
||||
raise ValueError("invalid part specification %r" % name)
|
||||
return res
|
||||
|
||||
def join(self, *args, **kwargs):
|
||||
""" return a new path by appending all 'args' as path
|
||||
components. if abs=1 is used restart from root if any
|
||||
of the args is an absolute path.
|
||||
"""
|
||||
if not args:
|
||||
return self
|
||||
strpath = self.strpath
|
||||
sep = self.sep
|
||||
strargs = [str(x) for x in args]
|
||||
if kwargs.get('abs', 0):
|
||||
for i in range(len(strargs)-1, -1, -1):
|
||||
if os.path.isabs(strargs[i]):
|
||||
strpath = strargs[i]
|
||||
strargs = strargs[i+1:]
|
||||
break
|
||||
for arg in strargs:
|
||||
arg = arg.strip(sep)
|
||||
if iswin32:
|
||||
# allow unix style paths even on windows.
|
||||
arg = arg.strip('/')
|
||||
arg = arg.replace('/', sep)
|
||||
if arg:
|
||||
if not strpath.endswith(sep):
|
||||
strpath += sep
|
||||
strpath += arg
|
||||
obj = self.new()
|
||||
obj.strpath = os.path.normpath(strpath)
|
||||
return obj
|
||||
|
||||
def open(self, mode='r'):
|
||||
""" return an opened file with the given mode. """
|
||||
return py.error.checked_call(open, self.strpath, mode)
|
||||
|
||||
def listdir(self, fil=None, sort=None):
|
||||
""" list directory contents, possibly filter by the given fil func
|
||||
and possibly sorted.
|
||||
"""
|
||||
if isinstance(fil, str):
|
||||
fil = common.FNMatcher(fil)
|
||||
res = []
|
||||
for name in py.error.checked_call(os.listdir, self.strpath):
|
||||
childurl = self.join(name)
|
||||
if fil is None or fil(childurl):
|
||||
res.append(childurl)
|
||||
self._sortlist(res, sort)
|
||||
return res
|
||||
|
||||
def size(self):
|
||||
""" return size of the underlying file object """
|
||||
return self.stat().size
|
||||
|
||||
def mtime(self):
|
||||
""" return last modification time of the path. """
|
||||
return self.stat().mtime
|
||||
|
||||
def copy(self, target, archive=False):
|
||||
""" copy path to target."""
|
||||
assert not archive, "XXX archive-mode not supported"
|
||||
if self.check(file=1):
|
||||
if target.check(dir=1):
|
||||
target = target.join(self.basename)
|
||||
assert self!=target
|
||||
copychunked(self, target)
|
||||
else:
|
||||
def rec(p):
|
||||
return p.check(link=0)
|
||||
for x in self.visit(rec=rec):
|
||||
relpath = x.relto(self)
|
||||
newx = target.join(relpath)
|
||||
newx.dirpath().ensure(dir=1)
|
||||
if x.check(link=1):
|
||||
newx.mksymlinkto(x.readlink())
|
||||
elif x.check(file=1):
|
||||
copychunked(x, newx)
|
||||
elif x.check(dir=1):
|
||||
newx.ensure(dir=1)
|
||||
|
||||
def rename(self, target):
|
||||
""" rename this path to target. """
|
||||
return py.error.checked_call(os.rename, str(self), str(target))
|
||||
|
||||
def dump(self, obj, bin=1):
|
||||
""" pickle object into path location"""
|
||||
f = self.open('wb')
|
||||
try:
|
||||
py.error.checked_call(py.std.pickle.dump, obj, f, bin)
|
||||
finally:
|
||||
f.close()
|
||||
|
||||
def mkdir(self, *args):
|
||||
""" create & return the directory joined with args. """
|
||||
p = self.join(*args)
|
||||
py.error.checked_call(os.mkdir, str(p))
|
||||
return p
|
||||
|
||||
def write(self, data, mode='w'):
|
||||
""" write data into path. """
|
||||
if 'b' in mode:
|
||||
if not py.builtin._isbytes(data):
|
||||
raise ValueError("can only process bytes")
|
||||
else:
|
||||
if not py.builtin._istext(data):
|
||||
if not py.builtin._isbytes(data):
|
||||
data = str(data)
|
||||
else:
|
||||
data = py.builtin._totext(data, sys.getdefaultencoding())
|
||||
f = self.open(mode)
|
||||
try:
|
||||
f.write(data)
|
||||
finally:
|
||||
f.close()
|
||||
|
||||
def _ensuredirs(self):
|
||||
parent = self.dirpath()
|
||||
if parent == self:
|
||||
return self
|
||||
if parent.check(dir=0):
|
||||
parent._ensuredirs()
|
||||
if self.check(dir=0):
|
||||
try:
|
||||
self.mkdir()
|
||||
except py.error.EEXIST:
|
||||
# race condition: file/dir created by another thread/process.
|
||||
# complain if it is not a dir
|
||||
if self.check(dir=0):
|
||||
raise
|
||||
return self
|
||||
|
||||
def ensure(self, *args, **kwargs):
|
||||
""" ensure that an args-joined path exists (by default as
|
||||
a file). if you specify a keyword argument 'dir=True'
|
||||
then the path is forced to be a directory path.
|
||||
"""
|
||||
p = self.join(*args)
|
||||
if kwargs.get('dir', 0):
|
||||
return p._ensuredirs()
|
||||
else:
|
||||
p.dirpath()._ensuredirs()
|
||||
if not p.check(file=1):
|
||||
p.open('w').close()
|
||||
return p
|
||||
|
||||
def stat(self):
|
||||
""" Return an os.stat() tuple. """
|
||||
return Stat(self, py.error.checked_call(os.stat, self.strpath))
|
||||
|
||||
def lstat(self):
|
||||
""" Return an os.lstat() tuple. """
|
||||
return Stat(self, py.error.checked_call(os.lstat, self.strpath))
|
||||
|
||||
def setmtime(self, mtime=None):
|
||||
""" set modification time for the given path. if 'mtime' is None
|
||||
(the default) then the file's mtime is set to current time.
|
||||
|
||||
Note that the resolution for 'mtime' is platform dependent.
|
||||
"""
|
||||
if mtime is None:
|
||||
return py.error.checked_call(os.utime, self.strpath, mtime)
|
||||
try:
|
||||
return py.error.checked_call(os.utime, self.strpath, (-1, mtime))
|
||||
except py.error.EINVAL:
|
||||
return py.error.checked_call(os.utime, self.strpath, (self.atime(), mtime))
|
||||
|
||||
def chdir(self):
|
||||
""" change directory to self and return old current directory """
|
||||
old = self.__class__()
|
||||
py.error.checked_call(os.chdir, self.strpath)
|
||||
return old
|
||||
|
||||
def realpath(self):
|
||||
""" return a new path which contains no symbolic links."""
|
||||
return self.__class__(os.path.realpath(self.strpath))
|
||||
|
||||
def atime(self):
|
||||
""" return last access time of the path. """
|
||||
return self.stat().atime
|
||||
|
||||
def __repr__(self):
|
||||
return 'local(%r)' % self.strpath
|
||||
|
||||
def __str__(self):
|
||||
""" return string representation of the Path. """
|
||||
return self.strpath
|
||||
|
||||
def pypkgpath(self, pkgname=None):
|
||||
""" return the path's package path by looking for the given
|
||||
pkgname. If pkgname is None then look for the last
|
||||
directory upwards which still contains an __init__.py.
|
||||
Return None if a pkgpath can not be determined.
|
||||
"""
|
||||
pkgpath = None
|
||||
for parent in self.parts(reverse=True):
|
||||
if pkgname is None:
|
||||
if parent.check(file=1):
|
||||
continue
|
||||
if parent.join('__init__.py').check():
|
||||
pkgpath = parent
|
||||
continue
|
||||
return pkgpath
|
||||
else:
|
||||
if parent.basename == pkgname:
|
||||
return parent
|
||||
return pkgpath
|
||||
|
||||
def _prependsyspath(self, path):
|
||||
s = str(path)
|
||||
if s != sys.path[0]:
|
||||
#print "prepending to sys.path", s
|
||||
sys.path.insert(0, s)
|
||||
|
||||
def chmod(self, mode, rec=0):
|
||||
""" change permissions to the given mode. If mode is an
|
||||
integer it directly encodes the os-specific modes.
|
||||
if rec is True perform recursively.
|
||||
"""
|
||||
if not isinstance(mode, int):
|
||||
raise TypeError("mode %r must be an integer" % (mode,))
|
||||
if rec:
|
||||
for x in self.visit(rec=rec):
|
||||
py.error.checked_call(os.chmod, str(x), mode)
|
||||
py.error.checked_call(os.chmod, str(self), mode)
|
||||
|
||||
def pyimport(self, modname=None, ensuresyspath=True):
|
||||
""" return path as an imported python module.
|
||||
if modname is None, look for the containing package
|
||||
and construct an according module name.
|
||||
The module will be put/looked up in sys.modules.
|
||||
"""
|
||||
if not self.check():
|
||||
raise py.error.ENOENT(self)
|
||||
#print "trying to import", self
|
||||
pkgpath = None
|
||||
if modname is None:
|
||||
pkgpath = self.pypkgpath()
|
||||
if pkgpath is not None:
|
||||
if ensuresyspath:
|
||||
self._prependsyspath(pkgpath.dirpath())
|
||||
pkg = __import__(pkgpath.basename, None, None, [])
|
||||
|
||||
if hasattr(pkg, '__pkg__'):
|
||||
modname = pkg.__pkg__.getimportname(self)
|
||||
assert modname is not None, "package %s doesn't know %s" % (
|
||||
pkg.__name__, self)
|
||||
|
||||
else:
|
||||
names = self.new(ext='').relto(pkgpath.dirpath())
|
||||
names = names.split(self.sep)
|
||||
modname = ".".join(names)
|
||||
else:
|
||||
# no package scope, still make it possible
|
||||
if ensuresyspath:
|
||||
self._prependsyspath(self.dirpath())
|
||||
modname = self.purebasename
|
||||
mod = __import__(modname, None, None, ['__doc__'])
|
||||
#self._module = mod
|
||||
return mod
|
||||
else:
|
||||
try:
|
||||
return sys.modules[modname]
|
||||
except KeyError:
|
||||
# we have a custom modname, do a pseudo-import
|
||||
mod = py.std.types.ModuleType(modname)
|
||||
mod.__file__ = str(self)
|
||||
sys.modules[modname] = mod
|
||||
try:
|
||||
py.builtin.execfile(str(self), mod.__dict__)
|
||||
except:
|
||||
del sys.modules[modname]
|
||||
raise
|
||||
return mod
|
||||
|
||||
def sysexec(self, *argv):
|
||||
""" return stdout text from executing a system child process,
|
||||
where the 'self' path points to executable.
|
||||
The process is directly invoked and not through a system shell.
|
||||
"""
|
||||
from subprocess import Popen, PIPE
|
||||
argv = map(str, argv)
|
||||
proc = Popen([str(self)] + list(argv), stdout=PIPE, stderr=PIPE)
|
||||
stdout, stderr = proc.communicate()
|
||||
ret = proc.wait()
|
||||
if py.builtin._isbytes(stdout):
|
||||
stdout = py.builtin._totext(stdout, sys.getdefaultencoding())
|
||||
if ret != 0:
|
||||
if py.builtin._isbytes(stderr):
|
||||
stderr = py.builtin._totext(stderr, sys.getdefaultencoding())
|
||||
raise py.process.cmdexec.Error(ret, ret, str(self),
|
||||
stdout, stderr,)
|
||||
return stdout
|
||||
|
||||
def sysfind(cls, name, checker=None):
|
||||
""" return a path object found by looking at the systems
|
||||
underlying PATH specification. If the checker is not None
|
||||
it will be invoked to filter matching paths. If a binary
|
||||
cannot be found, None is returned
|
||||
Note: This is probably not working on plain win32 systems
|
||||
but may work on cygwin.
|
||||
"""
|
||||
if os.path.isabs(name):
|
||||
p = py.path.local(name)
|
||||
if p.check(file=1):
|
||||
return p
|
||||
else:
|
||||
if iswin32:
|
||||
paths = py.std.os.environ['Path'].split(';')
|
||||
if '' not in paths and '.' not in paths:
|
||||
paths.append('.')
|
||||
try:
|
||||
systemroot = os.environ['SYSTEMROOT']
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
paths = [re.sub('%SystemRoot%', systemroot, path)
|
||||
for path in paths]
|
||||
tryadd = '', '.exe', '.com', '.bat' # XXX add more?
|
||||
else:
|
||||
paths = py.std.os.environ['PATH'].split(':')
|
||||
tryadd = ('',)
|
||||
|
||||
for x in paths:
|
||||
for addext in tryadd:
|
||||
p = py.path.local(x).join(name, abs=True) + addext
|
||||
try:
|
||||
if p.check(file=1):
|
||||
if checker:
|
||||
if not checker(p):
|
||||
continue
|
||||
return p
|
||||
except py.error.EACCES:
|
||||
pass
|
||||
return None
|
||||
sysfind = classmethod(sysfind)
|
||||
|
||||
def _gethomedir(cls):
|
||||
try:
|
||||
x = os.environ['HOME']
|
||||
except KeyError:
|
||||
x = os.environ['HOMEPATH']
|
||||
return cls(x)
|
||||
_gethomedir = classmethod(_gethomedir)
|
||||
|
||||
#"""
|
||||
#special class constructors for local filesystem paths
|
||||
#"""
|
||||
def get_temproot(cls):
|
||||
""" return the system's temporary directory
|
||||
(where tempfiles are usually created in)
|
||||
"""
|
||||
return py.path.local(py.std.tempfile.gettempdir())
|
||||
get_temproot = classmethod(get_temproot)
|
||||
|
||||
def mkdtemp(cls):
|
||||
""" return a Path object pointing to a fresh new temporary directory
|
||||
(which we created ourself).
|
||||
"""
|
||||
import tempfile
|
||||
tries = 10
|
||||
for i in range(tries):
|
||||
dname = tempfile.mktemp()
|
||||
dpath = cls(tempfile.mktemp())
|
||||
try:
|
||||
dpath.mkdir()
|
||||
except (py.error.EEXIST, py.error.EPERM, py.error.EACCES):
|
||||
continue
|
||||
return dpath
|
||||
raise py.error.ENOENT(dpath, "could not create tempdir, %d tries" % tries)
|
||||
mkdtemp = classmethod(mkdtemp)
|
||||
|
||||
def make_numbered_dir(cls, prefix='session-', rootdir=None, keep=3,
|
||||
lock_timeout = 172800): # two days
|
||||
""" return unique directory with a number greater than the current
|
||||
maximum one. The number is assumed to start directly after prefix.
|
||||
if keep is true directories with a number less than (maxnum-keep)
|
||||
will be removed.
|
||||
"""
|
||||
if rootdir is None:
|
||||
rootdir = cls.get_temproot()
|
||||
|
||||
def parse_num(path):
|
||||
""" parse the number out of a path (if it matches the prefix) """
|
||||
bn = path.basename
|
||||
if bn.startswith(prefix):
|
||||
try:
|
||||
return int(bn[len(prefix):])
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# compute the maximum number currently in use with the
|
||||
# prefix
|
||||
lastmax = None
|
||||
while True:
|
||||
maxnum = -1
|
||||
for path in rootdir.listdir():
|
||||
num = parse_num(path)
|
||||
if num is not None:
|
||||
maxnum = max(maxnum, num)
|
||||
|
||||
# make the new directory
|
||||
try:
|
||||
udir = rootdir.mkdir(prefix + str(maxnum+1))
|
||||
except py.error.EEXIST:
|
||||
# race condition: another thread/process created the dir
|
||||
# in the meantime. Try counting again
|
||||
if lastmax == maxnum:
|
||||
raise
|
||||
lastmax = maxnum
|
||||
continue
|
||||
break
|
||||
|
||||
# put a .lock file in the new directory that will be removed at
|
||||
# process exit
|
||||
if lock_timeout:
|
||||
lockfile = udir.join('.lock')
|
||||
mypid = os.getpid()
|
||||
if hasattr(lockfile, 'mksymlinkto'):
|
||||
lockfile.mksymlinkto(str(mypid))
|
||||
else:
|
||||
lockfile.write(str(mypid))
|
||||
def try_remove_lockfile():
|
||||
# in a fork() situation, only the last process should
|
||||
# remove the .lock, otherwise the other processes run the
|
||||
# risk of seeing their temporary dir disappear. For now
|
||||
# we remove the .lock in the parent only (i.e. we assume
|
||||
# that the children finish before the parent).
|
||||
if os.getpid() != mypid:
|
||||
return
|
||||
try:
|
||||
lockfile.remove()
|
||||
except py.error.Error:
|
||||
pass
|
||||
atexit.register(try_remove_lockfile)
|
||||
|
||||
# prune old directories
|
||||
if keep:
|
||||
for path in rootdir.listdir():
|
||||
num = parse_num(path)
|
||||
if num is not None and num <= (maxnum - keep):
|
||||
lf = path.join('.lock')
|
||||
try:
|
||||
t1 = lf.lstat().mtime
|
||||
t2 = lockfile.lstat().mtime
|
||||
if not lock_timeout or abs(t2-t1) < lock_timeout:
|
||||
continue # skip directories still locked
|
||||
except py.error.Error:
|
||||
pass # assume that it means that there is no 'lf'
|
||||
try:
|
||||
path.remove(rec=1)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except: # this might be py.error.Error, WindowsError ...
|
||||
pass
|
||||
|
||||
# make link...
|
||||
try:
|
||||
username = os.environ['USER'] #linux, et al
|
||||
except KeyError:
|
||||
try:
|
||||
username = os.environ['USERNAME'] #windows
|
||||
except KeyError:
|
||||
username = 'current'
|
||||
|
||||
src = str(udir)
|
||||
dest = src[:src.rfind('-')] + '-' + username
|
||||
try:
|
||||
os.unlink(dest)
|
||||
except OSError:
|
||||
pass
|
||||
try:
|
||||
os.symlink(src, dest)
|
||||
except (OSError, AttributeError): # AttributeError on win32
|
||||
pass
|
||||
|
||||
return udir
|
||||
make_numbered_dir = classmethod(make_numbered_dir)
|
||||
|
||||
def copychunked(src, dest):
|
||||
chunksize = 524288 # half a meg of bytes
|
||||
fsrc = src.open('rb')
|
||||
try:
|
||||
fdest = dest.open('wb')
|
||||
try:
|
||||
while 1:
|
||||
buf = fsrc.read(chunksize)
|
||||
if not buf:
|
||||
break
|
||||
fdest.write(buf)
|
||||
finally:
|
||||
fdest.close()
|
||||
finally:
|
||||
fsrc.close()
|
||||
|
||||
def autopath(globs=None):
|
||||
""" (deprecated) return the (local) path of the "current" file pointed to by globals or - if it is none - alternatively the callers frame globals.
|
||||
|
||||
the path will always point to a .py file or to None.
|
||||
the path will have the following payload:
|
||||
pkgdir is the last parent directory path containing __init__.py
|
||||
"""
|
||||
py.log._apiwarn("1.1", "py.magic.autopath deprecated, "
|
||||
"use py.path.local(__file__) and maybe pypkgpath/pyimport().")
|
||||
if globs is None:
|
||||
globs = sys._getframe(1).f_globals
|
||||
try:
|
||||
__file__ = globs['__file__']
|
||||
except KeyError:
|
||||
if not sys.argv[0]:
|
||||
raise ValueError("cannot compute autopath in interactive mode")
|
||||
__file__ = os.path.abspath(sys.argv[0])
|
||||
|
||||
ret = py.path.local(__file__)
|
||||
if ret.ext in ('.pyc', '.pyo'):
|
||||
ret = ret.new(ext='.py')
|
||||
current = pkgdir = ret.dirpath()
|
||||
while 1:
|
||||
if current.join('__init__.py').check():
|
||||
pkgdir = current
|
||||
current = current.dirpath()
|
||||
if pkgdir != current:
|
||||
continue
|
||||
elif str(current) not in sys.path:
|
||||
sys.path.insert(0, str(current))
|
||||
break
|
||||
ret.pkgdir = pkgdir
|
||||
return ret
|
||||
|
||||
@@ -1,365 +0,0 @@
|
||||
"""
|
||||
module defining a subversion path object based on the external
|
||||
command 'svn'. This modules aims to work with svn 1.3 and higher
|
||||
but might also interact well with earlier versions.
|
||||
"""
|
||||
|
||||
import os, sys, time, re
|
||||
import py
|
||||
from py import path, process
|
||||
from py.__.path import common
|
||||
from py.__.path import svnwc as svncommon
|
||||
from py.__.path.cacheutil import BuildcostAccessCache, AgingCache
|
||||
|
||||
DEBUG=False
|
||||
|
||||
class SvnCommandPath(svncommon.SvnPathBase):
|
||||
""" path implementation that offers access to (possibly remote) subversion
|
||||
repositories. """
|
||||
|
||||
_lsrevcache = BuildcostAccessCache(maxentries=128)
|
||||
_lsnorevcache = AgingCache(maxentries=1000, maxseconds=60.0)
|
||||
|
||||
def __new__(cls, path, rev=None, auth=None):
|
||||
self = object.__new__(cls)
|
||||
if isinstance(path, cls):
|
||||
rev = path.rev
|
||||
auth = path.auth
|
||||
path = path.strpath
|
||||
svncommon.checkbadchars(path)
|
||||
path = path.rstrip('/')
|
||||
self.strpath = path
|
||||
self.rev = rev
|
||||
self.auth = auth
|
||||
return self
|
||||
|
||||
def __repr__(self):
|
||||
if self.rev == -1:
|
||||
return 'svnurl(%r)' % self.strpath
|
||||
else:
|
||||
return 'svnurl(%r, %r)' % (self.strpath, self.rev)
|
||||
|
||||
def _svnwithrev(self, cmd, *args):
|
||||
""" execute an svn command, append our own url and revision """
|
||||
if self.rev is None:
|
||||
return self._svnwrite(cmd, *args)
|
||||
else:
|
||||
args = ['-r', self.rev] + list(args)
|
||||
return self._svnwrite(cmd, *args)
|
||||
|
||||
def _svnwrite(self, cmd, *args):
|
||||
""" execute an svn command, append our own url """
|
||||
l = ['svn %s' % cmd]
|
||||
args = ['"%s"' % self._escape(item) for item in args]
|
||||
l.extend(args)
|
||||
l.append('"%s"' % self._encodedurl())
|
||||
# fixing the locale because we can't otherwise parse
|
||||
string = " ".join(l)
|
||||
if DEBUG:
|
||||
print("execing %s" % string)
|
||||
out = self._svncmdexecauth(string)
|
||||
return out
|
||||
|
||||
def _svncmdexecauth(self, cmd):
|
||||
""" execute an svn command 'as is' """
|
||||
cmd = svncommon.fixlocale() + cmd
|
||||
if self.auth is not None:
|
||||
cmd += ' ' + self.auth.makecmdoptions()
|
||||
return self._cmdexec(cmd)
|
||||
|
||||
def _cmdexec(self, cmd):
|
||||
try:
|
||||
out = process.cmdexec(cmd)
|
||||
except py.process.cmdexec.Error:
|
||||
e = sys.exc_info()[1]
|
||||
if (e.err.find('File Exists') != -1 or
|
||||
e.err.find('File already exists') != -1):
|
||||
raise py.error.EEXIST(self)
|
||||
raise
|
||||
return out
|
||||
|
||||
def _svnpopenauth(self, cmd):
|
||||
""" execute an svn command, return a pipe for reading stdin """
|
||||
cmd = svncommon.fixlocale() + cmd
|
||||
if self.auth is not None:
|
||||
cmd += ' ' + self.auth.makecmdoptions()
|
||||
return self._popen(cmd)
|
||||
|
||||
def _popen(self, cmd):
|
||||
return os.popen(cmd)
|
||||
|
||||
def _encodedurl(self):
|
||||
return self._escape(self.strpath)
|
||||
|
||||
def _norev_delentry(self, path):
|
||||
auth = self.auth and self.auth.makecmdoptions() or None
|
||||
self._lsnorevcache.delentry((str(path), auth))
|
||||
|
||||
def open(self, mode='r'):
|
||||
""" return an opened file with the given mode. """
|
||||
if mode not in ("r", "rU",):
|
||||
raise ValueError("mode %r not supported" % (mode,))
|
||||
assert self.check(file=1) # svn cat returns an empty file otherwise
|
||||
if self.rev is None:
|
||||
return self._svnpopenauth('svn cat "%s"' % (
|
||||
self._escape(self.strpath), ))
|
||||
else:
|
||||
return self._svnpopenauth('svn cat -r %s "%s"' % (
|
||||
self.rev, self._escape(self.strpath)))
|
||||
|
||||
def dirpath(self, *args, **kwargs):
|
||||
""" return the directory path of the current path joined
|
||||
with any given path arguments.
|
||||
"""
|
||||
l = self.strpath.split(self.sep)
|
||||
if len(l) < 4:
|
||||
raise py.error.EINVAL(self, "base is not valid")
|
||||
elif len(l) == 4:
|
||||
return self.join(*args, **kwargs)
|
||||
else:
|
||||
return self.new(basename='').join(*args, **kwargs)
|
||||
|
||||
# modifying methods (cache must be invalidated)
|
||||
def mkdir(self, *args, **kwargs):
|
||||
""" create & return the directory joined with args.
|
||||
pass a 'msg' keyword argument to set the commit message.
|
||||
"""
|
||||
commit_msg = kwargs.get('msg', "mkdir by py lib invocation")
|
||||
createpath = self.join(*args)
|
||||
createpath._svnwrite('mkdir', '-m', commit_msg)
|
||||
self._norev_delentry(createpath.dirpath())
|
||||
return createpath
|
||||
|
||||
def copy(self, target, msg='copied by py lib invocation'):
|
||||
""" copy path to target with checkin message msg."""
|
||||
if getattr(target, 'rev', None) is not None:
|
||||
raise py.error.EINVAL(target, "revisions are immutable")
|
||||
self._svncmdexecauth('svn copy -m "%s" "%s" "%s"' %(msg,
|
||||
self._escape(self), self._escape(target)))
|
||||
self._norev_delentry(target.dirpath())
|
||||
|
||||
def rename(self, target, msg="renamed by py lib invocation"):
|
||||
""" rename this path to target with checkin message msg. """
|
||||
if getattr(self, 'rev', None) is not None:
|
||||
raise py.error.EINVAL(self, "revisions are immutable")
|
||||
self._svncmdexecauth('svn move -m "%s" --force "%s" "%s"' %(
|
||||
msg, self._escape(self), self._escape(target)))
|
||||
self._norev_delentry(self.dirpath())
|
||||
self._norev_delentry(self)
|
||||
|
||||
def remove(self, rec=1, msg='removed by py lib invocation'):
|
||||
""" remove a file or directory (or a directory tree if rec=1) with
|
||||
checkin message msg."""
|
||||
if self.rev is not None:
|
||||
raise py.error.EINVAL(self, "revisions are immutable")
|
||||
self._svncmdexecauth('svn rm -m "%s" "%s"' %(msg, self._escape(self)))
|
||||
self._norev_delentry(self.dirpath())
|
||||
|
||||
def export(self, topath):
|
||||
""" export to a local path
|
||||
|
||||
topath should not exist prior to calling this, returns a
|
||||
py.path.local instance
|
||||
"""
|
||||
topath = py.path.local(topath)
|
||||
args = ['"%s"' % (self._escape(self),),
|
||||
'"%s"' % (self._escape(topath),)]
|
||||
if self.rev is not None:
|
||||
args = ['-r', str(self.rev)] + args
|
||||
self._svncmdexecauth('svn export %s' % (' '.join(args),))
|
||||
return topath
|
||||
|
||||
def ensure(self, *args, **kwargs):
|
||||
""" ensure that an args-joined path exists (by default as
|
||||
a file). If you specify a keyword argument 'dir=True'
|
||||
then the path is forced to be a directory path.
|
||||
"""
|
||||
if getattr(self, 'rev', None) is not None:
|
||||
raise py.error.EINVAL(self, "revisions are immutable")
|
||||
target = self.join(*args)
|
||||
dir = kwargs.get('dir', 0)
|
||||
for x in target.parts(reverse=True):
|
||||
if x.check():
|
||||
break
|
||||
else:
|
||||
raise py.error.ENOENT(target, "has not any valid base!")
|
||||
if x == target:
|
||||
if not x.check(dir=dir):
|
||||
raise dir and py.error.ENOTDIR(x) or py.error.EISDIR(x)
|
||||
return x
|
||||
tocreate = target.relto(x)
|
||||
basename = tocreate.split(self.sep, 1)[0]
|
||||
tempdir = py.path.local.mkdtemp()
|
||||
try:
|
||||
tempdir.ensure(tocreate, dir=dir)
|
||||
cmd = 'svn import -m "%s" "%s" "%s"' % (
|
||||
"ensure %s" % self._escape(tocreate),
|
||||
self._escape(tempdir.join(basename)),
|
||||
x.join(basename)._encodedurl())
|
||||
self._svncmdexecauth(cmd)
|
||||
self._norev_delentry(x)
|
||||
finally:
|
||||
tempdir.remove()
|
||||
return target
|
||||
|
||||
# end of modifying methods
|
||||
def _propget(self, name):
|
||||
res = self._svnwithrev('propget', name)
|
||||
return res[:-1] # strip trailing newline
|
||||
|
||||
def _proplist(self):
|
||||
res = self._svnwithrev('proplist')
|
||||
lines = res.split('\n')
|
||||
lines = [x.strip() for x in lines[1:]]
|
||||
return svncommon.PropListDict(self, lines)
|
||||
|
||||
def _listdir_nameinfo(self):
|
||||
""" return sequence of name-info directory entries of self """
|
||||
def builder():
|
||||
try:
|
||||
res = self._svnwithrev('ls', '-v')
|
||||
except process.cmdexec.Error:
|
||||
e = sys.exc_info()[1]
|
||||
if e.err.find('non-existent in that revision') != -1:
|
||||
raise py.error.ENOENT(self, e.err)
|
||||
elif e.err.find('File not found') != -1:
|
||||
raise py.error.ENOENT(self, e.err)
|
||||
elif e.err.find('not part of a repository')!=-1:
|
||||
raise py.error.ENOENT(self, e.err)
|
||||
elif e.err.find('Unable to open')!=-1:
|
||||
raise py.error.ENOENT(self, e.err)
|
||||
elif e.err.lower().find('method not allowed')!=-1:
|
||||
raise py.error.EACCES(self, e.err)
|
||||
raise py.error.Error(e.err)
|
||||
lines = res.split('\n')
|
||||
nameinfo_seq = []
|
||||
for lsline in lines:
|
||||
if lsline:
|
||||
info = InfoSvnCommand(lsline)
|
||||
if info._name != '.': # svn 1.5 produces '.' dirs,
|
||||
nameinfo_seq.append((info._name, info))
|
||||
nameinfo_seq.sort()
|
||||
return nameinfo_seq
|
||||
auth = self.auth and self.auth.makecmdoptions() or None
|
||||
if self.rev is not None:
|
||||
return self._lsrevcache.getorbuild((self.strpath, self.rev, auth),
|
||||
builder)
|
||||
else:
|
||||
return self._lsnorevcache.getorbuild((self.strpath, auth),
|
||||
builder)
|
||||
|
||||
def listdir(self, fil=None, sort=None):
|
||||
""" list directory contents, possibly filter by the given fil func
|
||||
and possibly sorted.
|
||||
"""
|
||||
if isinstance(fil, str):
|
||||
fil = common.FNMatcher(fil)
|
||||
nameinfo_seq = self._listdir_nameinfo()
|
||||
if len(nameinfo_seq) == 1:
|
||||
name, info = nameinfo_seq[0]
|
||||
if name == self.basename and info.kind == 'file':
|
||||
#if not self.check(dir=1):
|
||||
raise py.error.ENOTDIR(self)
|
||||
paths = [self.join(name) for (name, info) in nameinfo_seq]
|
||||
if fil:
|
||||
paths = [x for x in paths if fil(x)]
|
||||
self._sortlist(paths, sort)
|
||||
return paths
|
||||
|
||||
|
||||
def log(self, rev_start=None, rev_end=1, verbose=False):
|
||||
""" return a list of LogEntry instances for this path.
|
||||
rev_start is the starting revision (defaulting to the first one).
|
||||
rev_end is the last revision (defaulting to HEAD).
|
||||
if verbose is True, then the LogEntry instances also know which files changed.
|
||||
"""
|
||||
assert self.check() #make it simpler for the pipe
|
||||
rev_start = rev_start is None and "HEAD" or rev_start
|
||||
rev_end = rev_end is None and "HEAD" or rev_end
|
||||
|
||||
if rev_start == "HEAD" and rev_end == 1:
|
||||
rev_opt = ""
|
||||
else:
|
||||
rev_opt = "-r %s:%s" % (rev_start, rev_end)
|
||||
verbose_opt = verbose and "-v" or ""
|
||||
xmlpipe = self._svnpopenauth('svn log --xml %s %s "%s"' %
|
||||
(rev_opt, verbose_opt, self.strpath))
|
||||
from xml.dom import minidom
|
||||
tree = minidom.parse(xmlpipe)
|
||||
result = []
|
||||
for logentry in filter(None, tree.firstChild.childNodes):
|
||||
if logentry.nodeType == logentry.ELEMENT_NODE:
|
||||
result.append(svncommon.LogEntry(logentry))
|
||||
return result
|
||||
|
||||
#01234567890123456789012345678901234567890123467
|
||||
# 2256 hpk 165 Nov 24 17:55 __init__.py
|
||||
# XXX spotted by Guido, SVN 1.3.0 has different aligning, breaks the code!!!
|
||||
# 1312 johnny 1627 May 05 14:32 test_decorators.py
|
||||
#
|
||||
class InfoSvnCommand:
|
||||
# the '0?' part in the middle is an indication of whether the resource is
|
||||
# locked, see 'svn help ls'
|
||||
lspattern = re.compile(
|
||||
r'^ *(?P<rev>\d+) +(?P<author>.+?) +(0? *(?P<size>\d+))? '
|
||||
'*(?P<date>\w+ +\d{2} +[\d:]+) +(?P<file>.*)$')
|
||||
def __init__(self, line):
|
||||
# this is a typical line from 'svn ls http://...'
|
||||
#_ 1127 jum 0 Jul 13 15:28 branch/
|
||||
match = self.lspattern.match(line)
|
||||
data = match.groupdict()
|
||||
self._name = data['file']
|
||||
if self._name[-1] == '/':
|
||||
self._name = self._name[:-1]
|
||||
self.kind = 'dir'
|
||||
else:
|
||||
self.kind = 'file'
|
||||
#self.has_props = l.pop(0) == 'P'
|
||||
self.created_rev = int(data['rev'])
|
||||
self.last_author = data['author']
|
||||
self.size = data['size'] and int(data['size']) or 0
|
||||
self.mtime = parse_time_with_missing_year(data['date'])
|
||||
self.time = self.mtime * 1000000
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.__dict__ == other.__dict__
|
||||
|
||||
|
||||
#____________________________________________________
|
||||
#
|
||||
# helper functions
|
||||
#____________________________________________________
|
||||
def parse_time_with_missing_year(timestr):
|
||||
""" analyze the time part from a single line of "svn ls -v"
|
||||
the svn output doesn't show the year makes the 'timestr'
|
||||
ambigous.
|
||||
"""
|
||||
import calendar
|
||||
t_now = time.gmtime()
|
||||
|
||||
tparts = timestr.split()
|
||||
month = time.strptime(tparts.pop(0), '%b')[1]
|
||||
day = time.strptime(tparts.pop(0), '%d')[2]
|
||||
last = tparts.pop(0) # year or hour:minute
|
||||
try:
|
||||
year = time.strptime(last, '%Y')[0]
|
||||
hour = minute = 0
|
||||
except ValueError:
|
||||
hour, minute = time.strptime(last, '%H:%M')[3:5]
|
||||
year = t_now[0]
|
||||
|
||||
t_result = (year, month, day, hour, minute, 0,0,0,0)
|
||||
if t_result > t_now:
|
||||
year -= 1
|
||||
t_result = (year, month, day, hour, minute, 0,0,0,0)
|
||||
return calendar.timegm(t_result)
|
||||
|
||||
class PathEntry:
|
||||
def __init__(self, ppart):
|
||||
self.strpath = ppart.firstChild.nodeValue.encode('UTF-8')
|
||||
self.action = ppart.getAttribute('action').encode('UTF-8')
|
||||
if self.action == 'A':
|
||||
self.copyfrom_path = ppart.getAttribute('copyfrom-path').encode('UTF-8')
|
||||
if self.copyfrom_path:
|
||||
self.copyfrom_rev = int(ppart.getAttribute('copyfrom-rev'))
|
||||
|
||||
1236
py/path/svnwc.py
1236
py/path/svnwc.py
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
||||
""" high-level sub-process handling """
|
||||
@@ -1,177 +0,0 @@
|
||||
"""
|
||||
|
||||
module defining basic hook for executing commands
|
||||
in a - as much as possible - platform independent way.
|
||||
|
||||
Current list:
|
||||
|
||||
exec_cmd(cmd) executes the given command and returns output
|
||||
or ExecutionFailed exception (if exit status!=0)
|
||||
|
||||
"""
|
||||
|
||||
import os, sys
|
||||
import py
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
#-----------------------------------------------------------
|
||||
# posix external command execution
|
||||
#-----------------------------------------------------------
|
||||
def posix_exec_cmd(cmd):
|
||||
""" return output of executing 'cmd'.
|
||||
|
||||
raise ExecutionFailed exeception if the command failed.
|
||||
the exception will provide an 'err' attribute containing
|
||||
the error-output from the command.
|
||||
"""
|
||||
#__tracebackhide__ = True
|
||||
|
||||
import errno
|
||||
|
||||
child = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE, close_fds=True)
|
||||
stdin, stdout, stderr = child.stdin, child.stdout, child.stderr
|
||||
|
||||
# XXX sometimes we get a blocked r.read() call (see below)
|
||||
# although select told us there is something to read.
|
||||
# only the next three lines appear to prevent
|
||||
# the read call from blocking infinitely.
|
||||
import fcntl
|
||||
def set_non_block(fd):
|
||||
flags = fcntl.fcntl(fd, fcntl.F_GETFL)
|
||||
flags = flags | os.O_NONBLOCK
|
||||
fcntl.fcntl(fd, fcntl.F_SETFL, flags)
|
||||
set_non_block(stdout.fileno())
|
||||
set_non_block(stderr.fileno())
|
||||
#fcntl.fcntl(stdout, fcntl.F_SETFL, os.O_NONBLOCK)
|
||||
#fcntl.fcntl(stderr, fcntl.F_SETFL, os.O_NONBLOCK)
|
||||
|
||||
import select
|
||||
out, err = [], []
|
||||
while 1:
|
||||
r_list = [x for x in [stdout, stderr] if x and not x.closed]
|
||||
if not r_list:
|
||||
break
|
||||
try:
|
||||
r_list = select.select(r_list, [], [])[0]
|
||||
except (select.error, IOError):
|
||||
se = sys.exc_info()[1]
|
||||
if se.args[0] == errno.EINTR:
|
||||
continue
|
||||
else:
|
||||
raise
|
||||
for r in r_list:
|
||||
try:
|
||||
data = r.read() # XXX see XXX above
|
||||
except IOError:
|
||||
io = sys.exc_info()[1]
|
||||
if io.args[0] == errno.EAGAIN:
|
||||
continue
|
||||
# Connection Lost
|
||||
raise
|
||||
except OSError:
|
||||
ose = sys.exc_info()[1]
|
||||
if ose.errno == errno.EPIPE:
|
||||
# Connection Lost
|
||||
raise
|
||||
if ose.errno == errno.EAGAIN: # MacOS-X does this
|
||||
continue
|
||||
raise
|
||||
|
||||
if not data:
|
||||
r.close()
|
||||
continue
|
||||
if r is stdout:
|
||||
out.append(data)
|
||||
else:
|
||||
err.append(data)
|
||||
pid, systemstatus = os.waitpid(child.pid, 0)
|
||||
if pid != child.pid:
|
||||
raise ExecutionFailed("child process disappeared during: "+ cmd)
|
||||
if systemstatus:
|
||||
if os.WIFSIGNALED(systemstatus):
|
||||
status = os.WTERMSIG(systemstatus) + 128
|
||||
else:
|
||||
status = os.WEXITSTATUS(systemstatus)
|
||||
raise ExecutionFailed(status, systemstatus, cmd,
|
||||
joiner(out), joiner(err))
|
||||
return joiner(out)
|
||||
|
||||
def joiner(out):
|
||||
encoding = sys.getdefaultencoding()
|
||||
return "".join([py.builtin._totext(x, encoding) for x in out])
|
||||
|
||||
#-----------------------------------------------------------
|
||||
# simple win32 external command execution
|
||||
#-----------------------------------------------------------
|
||||
def win32_exec_cmd(cmd):
|
||||
""" return output of executing 'cmd'.
|
||||
|
||||
raise ExecutionFailed exeception if the command failed.
|
||||
the exception will provide an 'err' attribute containing
|
||||
the error-output from the command.
|
||||
|
||||
Note that this method can currently deadlock because
|
||||
we don't have WaitForMultipleObjects in the std-python api.
|
||||
|
||||
Further note that the rules for quoting are very special
|
||||
under Windows. Do a HELP CMD in a shell, and tell me if
|
||||
you understand this. For now, I try to do a fix.
|
||||
"""
|
||||
#print "*****", cmd
|
||||
|
||||
# the following quoting is only valid for CMD.EXE, not COMMAND.COM
|
||||
cmd_quoting = True
|
||||
try:
|
||||
if os.environ['COMSPEC'].upper().endswith('COMMAND.COM'):
|
||||
cmd_quoting = False
|
||||
except KeyError:
|
||||
pass
|
||||
if cmd_quoting:
|
||||
if '"' in cmd and not cmd.startswith('""'):
|
||||
cmd = '"%s"' % cmd
|
||||
|
||||
return popen3_exec_cmd(cmd)
|
||||
|
||||
def popen3_exec_cmd(cmd):
|
||||
stdin, stdout, stderr = os.popen3(cmd)
|
||||
out = stdout.read()
|
||||
err = stderr.read()
|
||||
stdout.close()
|
||||
stderr.close()
|
||||
status = stdin.close()
|
||||
if status:
|
||||
raise ExecutionFailed(status, status, cmd, out, err)
|
||||
return out
|
||||
|
||||
def pypy_exec_cmd(cmd):
|
||||
return popen3_exec_cmd(cmd)
|
||||
|
||||
class ExecutionFailed(py.error.Error):
|
||||
def __init__(self, status, systemstatus, cmd, out, err):
|
||||
Exception.__init__(self)
|
||||
self.status = status
|
||||
self.systemstatus = systemstatus
|
||||
self.cmd = cmd
|
||||
self.err = err
|
||||
self.out = out
|
||||
|
||||
def __str__(self):
|
||||
return "ExecutionFailed: %d %s\n%s" %(self.status, self.cmd, self.err)
|
||||
#
|
||||
# choose correct platform-version
|
||||
#
|
||||
|
||||
if sys.platform == 'win32':
|
||||
cmdexec = win32_exec_cmd
|
||||
elif hasattr(sys, 'pypy') or hasattr(sys, 'pypy_objspaceclass'):
|
||||
cmdexec = popen3_exec_cmd
|
||||
else:
|
||||
cmdexec = posix_exec_cmd
|
||||
|
||||
# export the exception under the name 'py.process.cmdexec.Error'
|
||||
cmdexec.Error = ExecutionFailed
|
||||
try:
|
||||
ExecutionFailed.__module__ = 'py.process.cmdexec'
|
||||
ExecutionFailed.__name__ = 'Error'
|
||||
except (AttributeError, TypeError):
|
||||
pass
|
||||
@@ -1,108 +0,0 @@
|
||||
|
||||
"""
|
||||
ForkedFunc provides a way to run a function in a forked process
|
||||
and get at its return value, stdout and stderr output as well
|
||||
as signals and exitstatusus.
|
||||
|
||||
XXX see if tempdir handling is sane
|
||||
"""
|
||||
|
||||
import py
|
||||
import os
|
||||
import sys
|
||||
import marshal
|
||||
|
||||
class ForkedFunc(object):
|
||||
EXITSTATUS_EXCEPTION = 3
|
||||
def __init__(self, fun, args=None, kwargs=None, nice_level=0):
|
||||
if args is None:
|
||||
args = []
|
||||
if kwargs is None:
|
||||
kwargs = {}
|
||||
self.fun = fun
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
self.tempdir = tempdir = py.path.local.mkdtemp()
|
||||
self.RETVAL = tempdir.ensure('retval')
|
||||
self.STDOUT = tempdir.ensure('stdout')
|
||||
self.STDERR = tempdir.ensure('stderr')
|
||||
|
||||
pid = os.fork()
|
||||
if pid: # in parent process
|
||||
self.pid = pid
|
||||
else: # in child process
|
||||
self._child(nice_level)
|
||||
|
||||
def _child(self, nice_level):
|
||||
# right now we need to call a function, but first we need to
|
||||
# map all IO that might happen
|
||||
# make sure sys.stdout points to file descriptor one
|
||||
sys.stdout = stdout = self.STDOUT.open('w')
|
||||
sys.stdout.flush()
|
||||
fdstdout = stdout.fileno()
|
||||
if fdstdout != 1:
|
||||
os.dup2(fdstdout, 1)
|
||||
sys.stderr = stderr = self.STDERR.open('w')
|
||||
fdstderr = stderr.fileno()
|
||||
if fdstderr != 2:
|
||||
os.dup2(fdstderr, 2)
|
||||
retvalf = self.RETVAL.open("wb")
|
||||
EXITSTATUS = 0
|
||||
try:
|
||||
if nice_level:
|
||||
os.nice(nice_level)
|
||||
try:
|
||||
retval = self.fun(*self.args, **self.kwargs)
|
||||
retvalf.write(marshal.dumps(retval))
|
||||
except:
|
||||
excinfo = py.code.ExceptionInfo()
|
||||
stderr.write(excinfo.exconly())
|
||||
EXITSTATUS = self.EXITSTATUS_EXCEPTION
|
||||
finally:
|
||||
stdout.close()
|
||||
stderr.close()
|
||||
retvalf.close()
|
||||
os.close(1)
|
||||
os.close(2)
|
||||
os._exit(EXITSTATUS)
|
||||
|
||||
def waitfinish(self, waiter=os.waitpid):
|
||||
pid, systemstatus = waiter(self.pid, 0)
|
||||
if systemstatus:
|
||||
if os.WIFSIGNALED(systemstatus):
|
||||
exitstatus = os.WTERMSIG(systemstatus) + 128
|
||||
else:
|
||||
exitstatus = os.WEXITSTATUS(systemstatus)
|
||||
#raise ExecutionFailed(status, systemstatus, cmd,
|
||||
# ''.join(out), ''.join(err))
|
||||
else:
|
||||
exitstatus = 0
|
||||
signal = systemstatus & 0x7f
|
||||
if not exitstatus and not signal:
|
||||
retval = self.RETVAL.open('rb')
|
||||
try:
|
||||
retval_data = retval.read()
|
||||
finally:
|
||||
retval.close()
|
||||
retval = marshal.loads(retval_data)
|
||||
else:
|
||||
retval = None
|
||||
stdout = self.STDOUT.read()
|
||||
stderr = self.STDERR.read()
|
||||
self._removetemp()
|
||||
return Result(exitstatus, signal, retval, stdout, stderr)
|
||||
|
||||
def _removetemp(self):
|
||||
if self.tempdir.check():
|
||||
self.tempdir.remove()
|
||||
|
||||
def __del__(self):
|
||||
self._removetemp()
|
||||
|
||||
class Result(object):
|
||||
def __init__(self, exitstatus, signal, retval, stdout, stderr):
|
||||
self.exitstatus = exitstatus
|
||||
self.signal = signal
|
||||
self.retval = retval
|
||||
self.out = stdout
|
||||
self.err = stderr
|
||||
@@ -1,23 +0,0 @@
|
||||
import py
|
||||
import os, sys
|
||||
|
||||
if sys.platform == "win32":
|
||||
try:
|
||||
import ctypes
|
||||
except ImportError:
|
||||
def dokill(pid):
|
||||
py.process.cmdexec("taskkill /F /PID %d" %(pid,))
|
||||
else:
|
||||
def dokill(pid):
|
||||
PROCESS_TERMINATE = 1
|
||||
handle = ctypes.windll.kernel32.OpenProcess(
|
||||
PROCESS_TERMINATE, False, pid)
|
||||
ctypes.windll.kernel32.TerminateProcess(handle, -1)
|
||||
ctypes.windll.kernel32.CloseHandle(handle)
|
||||
else:
|
||||
def dokill(pid):
|
||||
os.kill(pid, 15)
|
||||
|
||||
def kill(pid):
|
||||
""" kill process by id. """
|
||||
dokill(pid)
|
||||
@@ -1,163 +0,0 @@
|
||||
import py
|
||||
|
||||
from py.__.process.cmdexec import ExecutionFailed
|
||||
# utility functions to convert between various formats
|
||||
|
||||
format_to_dotargument = {"png": "png",
|
||||
"eps": "ps",
|
||||
"ps": "ps",
|
||||
"pdf": "ps",
|
||||
}
|
||||
|
||||
def ps2eps(ps):
|
||||
# XXX write a pure python version
|
||||
if not py.path.local.sysfind("ps2epsi") and \
|
||||
not py.path.local.sysfind("ps2eps"):
|
||||
raise SystemExit("neither ps2eps nor ps2epsi found")
|
||||
try:
|
||||
eps = ps.new(ext=".eps")
|
||||
py.process.cmdexec('ps2epsi "%s" "%s"' % (ps, eps))
|
||||
except ExecutionFailed:
|
||||
py.process.cmdexec('ps2eps -l -f "%s"' % ps)
|
||||
|
||||
def ps2pdf(ps, compat_level="1.2"):
|
||||
if not py.path.local.sysfind("gs"):
|
||||
raise SystemExit("ERROR: gs not found")
|
||||
pdf = ps.new(ext=".pdf")
|
||||
options = dict(OPTIONS="-dSAFER -dCompatibilityLevel=%s" % compat_level,
|
||||
infile=ps, outfile=pdf)
|
||||
cmd = ('gs %(OPTIONS)s -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite '
|
||||
'"-sOutputFile=%(outfile)s" %(OPTIONS)s -c .setpdfwrite '
|
||||
'-f "%(infile)s"') % options
|
||||
py.process.cmdexec(cmd)
|
||||
return pdf
|
||||
|
||||
def eps2pdf(eps):
|
||||
# XXX write a pure python version
|
||||
if not py.path.local.sysfind("epstopdf"):
|
||||
raise SystemExit("ERROR: epstopdf not found")
|
||||
py.process.cmdexec('epstopdf "%s"' % eps)
|
||||
|
||||
def dvi2eps(dvi, dest=None):
|
||||
if dest is None:
|
||||
dest = eps.new(ext=".eps")
|
||||
command = 'dvips -q -E -n 1 -D 600 -p 1 -o "%s" "%s"' % (dest, dvi)
|
||||
if not py.path.local.sysfind("dvips"):
|
||||
raise SystemExit("ERROR: dvips not found")
|
||||
py.process.cmdexec(command)
|
||||
|
||||
def convert_dot(fn, new_extension):
|
||||
if not py.path.local.sysfind("dot"):
|
||||
raise SystemExit("ERROR: dot not found")
|
||||
result = fn.new(ext=new_extension)
|
||||
print(result)
|
||||
arg = "-T%s" % (format_to_dotargument[new_extension], )
|
||||
py.std.os.system('dot "%s" "%s" > "%s"' % (arg, fn, result))
|
||||
if new_extension == "eps":
|
||||
ps = result.new(ext="ps")
|
||||
result.move(ps)
|
||||
ps2eps(ps)
|
||||
ps.remove()
|
||||
elif new_extension == "pdf":
|
||||
# convert to eps file first, to get the bounding box right
|
||||
eps = result.new(ext="eps")
|
||||
ps = result.new(ext="ps")
|
||||
result.move(ps)
|
||||
ps2eps(ps)
|
||||
eps2pdf(eps)
|
||||
ps.remove()
|
||||
eps.remove()
|
||||
return result
|
||||
|
||||
|
||||
class latexformula2png(object):
|
||||
def __init__(self, formula, dest, temp=None):
|
||||
self.formula = formula
|
||||
try:
|
||||
import Image
|
||||
self.Image = Image
|
||||
self.scale = 2 # create a larger image
|
||||
self.upscale = 5 # create the image upscale times larger, then scale it down
|
||||
except ImportError:
|
||||
self.scale = 2
|
||||
self.upscale = 1
|
||||
self.Image = None
|
||||
self.output_format = ('pngmono', 'pnggray', 'pngalpha')[2]
|
||||
if temp is None:
|
||||
temp = py.test.ensuretemp("latexformula")
|
||||
self.temp = temp
|
||||
self.latex = self.temp.join('formula.tex')
|
||||
self.dvi = self.temp.join('formula.dvi')
|
||||
self.eps = self.temp.join('formula.eps')
|
||||
self.png = self.temp.join('formula.png')
|
||||
self.saveas(dest)
|
||||
|
||||
def saveas(self, dest):
|
||||
self.gen_latex()
|
||||
self.gen_dvi()
|
||||
dvi2eps(self.dvi, self.eps)
|
||||
self.gen_png()
|
||||
self.scale_image()
|
||||
self.png.copy(dest)
|
||||
|
||||
def gen_latex(self):
|
||||
self.latex.write ("""
|
||||
\\documentclass{article}
|
||||
\\pagestyle{empty}
|
||||
\\begin{document}
|
||||
|
||||
%s
|
||||
\\pagebreak
|
||||
|
||||
\\end{document}
|
||||
""" % (self.formula))
|
||||
|
||||
def gen_dvi(self):
|
||||
origdir = py.path.local()
|
||||
self.temp.chdir()
|
||||
py.process.cmdexec('latex "%s"' % (self.latex))
|
||||
origdir.chdir()
|
||||
|
||||
def gen_png(self):
|
||||
tempdir = py.path.local.mkdtemp()
|
||||
|
||||
re_bbox = py.std.re.compile('%%BoundingBox:\s*(\d+) (\d+) (\d+) (\d+)')
|
||||
eps = self.eps.read()
|
||||
x1, y1, x2, y2 = [int(i) for i in re_bbox.search(eps).groups()]
|
||||
X = x2 - x1 + 2
|
||||
Y = y2 - y1 + 2
|
||||
mx = -x1
|
||||
my = -y1
|
||||
ps = self.temp.join('temp.ps')
|
||||
source = self.eps
|
||||
ps.write("""
|
||||
1 1 1 setrgbcolor
|
||||
newpath
|
||||
-1 -1 moveto
|
||||
%(X)d -1 lineto
|
||||
%(X)d %(Y)d lineto
|
||||
-1 %(Y)d lineto
|
||||
closepath
|
||||
fill
|
||||
%(mx)d %(my)d translate
|
||||
0 0 0 setrgbcolor
|
||||
(%(source)s) run
|
||||
|
||||
""" % locals())
|
||||
|
||||
sx = int((x2 - x1) * self.scale * self.upscale)
|
||||
sy = int((y2 - y1) * self.scale * self.upscale)
|
||||
res = 72 * self.scale * self.upscale
|
||||
command = ('gs -q -g%dx%d -r%dx%d -sDEVICE=%s -sOutputFile="%s" '
|
||||
'-dNOPAUSE -dBATCH "%s"') % (
|
||||
sx, sy, res, res, self.output_format, self.png, ps)
|
||||
py.process.cmdexec(command)
|
||||
|
||||
def scale_image(self):
|
||||
if self.Image is None:
|
||||
return
|
||||
image = self.Image.open(str(self.png))
|
||||
image.resize((image.size[0] / self.upscale,
|
||||
image.size[1] / self.upscale),
|
||||
self.Image.ANTIALIAS).save(str(self.png))
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
# XXX this file is messy since it tries to deal with several docutils versions
|
||||
import py
|
||||
|
||||
from py.__.rest.convert import convert_dot, latexformula2png
|
||||
|
||||
import sys
|
||||
import docutils
|
||||
from docutils import nodes
|
||||
from docutils.parsers.rst import directives, states, roles
|
||||
from docutils.parsers.rst.directives import images
|
||||
|
||||
if hasattr(images, "image"):
|
||||
directives_are_functions = True
|
||||
else:
|
||||
directives_are_functions = False
|
||||
|
||||
try:
|
||||
from docutils.utils import unescape # docutils version > 0.3.5
|
||||
except ImportError:
|
||||
from docutils.parsers.rst.states import unescape # docutils 0.3.5
|
||||
|
||||
if not directives_are_functions:
|
||||
ImageClass = images.Image
|
||||
|
||||
else:
|
||||
class ImageClass(object):
|
||||
option_spec = images.image.options
|
||||
def run(self):
|
||||
return images.image('image',
|
||||
self.arguments,
|
||||
self.options,
|
||||
self.content,
|
||||
self.lineno,
|
||||
self.content_offset,
|
||||
self.block_text,
|
||||
self.state,
|
||||
self.state_machine)
|
||||
|
||||
|
||||
backend_to_image_format = {"html": "png", "latex": "pdf"}
|
||||
|
||||
class GraphvizDirective(ImageClass):
|
||||
def convert(self, fn, path):
|
||||
path = py.path.local(path).dirpath()
|
||||
dot = path.join(fn)
|
||||
result = convert_dot(dot, backend_to_image_format[_backend])
|
||||
return result.relto(path)
|
||||
|
||||
def run(self):
|
||||
newname = self.convert(self.arguments[0],
|
||||
self.state.document.settings._source)
|
||||
text = self.block_text.replace("graphviz", "image", 1)
|
||||
self.block_text = text.replace(self.arguments[0], newname, 1)
|
||||
self.name = 'image'
|
||||
self.arguments = [newname]
|
||||
return ImageClass.run(self)
|
||||
|
||||
def old_interface(self):
|
||||
def f(name, arguments, options, content, lineno,
|
||||
content_offset, block_text, state, state_machine):
|
||||
for arg in "name arguments options content lineno " \
|
||||
"content_offset block_text state state_machine".split():
|
||||
setattr(self, arg, locals()[arg])
|
||||
return self.run()
|
||||
f.arguments = (1, 0, 1)
|
||||
f.options = self.option_spec
|
||||
return f
|
||||
|
||||
|
||||
_backend = None
|
||||
def set_backend_and_register_directives(backend):
|
||||
#XXX this is only used to work around the inflexibility of docutils:
|
||||
# a directive does not know the target format
|
||||
global _backend
|
||||
_backend = backend
|
||||
if not directives_are_functions:
|
||||
directives.register_directive("graphviz", GraphvizDirective)
|
||||
else:
|
||||
directives.register_directive("graphviz",
|
||||
GraphvizDirective().old_interface())
|
||||
roles.register_canonical_role("latexformula", latexformula_role)
|
||||
|
||||
def latexformula_role(name, rawtext, text, lineno, inliner,
|
||||
options={}, content=[]):
|
||||
if _backend == 'latex':
|
||||
options['format'] = 'latex'
|
||||
return roles.raw_role(name, rawtext, text, lineno, inliner,
|
||||
options, content)
|
||||
else:
|
||||
# XXX: make the place of the image directory configurable
|
||||
sourcedir = py.path.local(inliner.document.settings._source).dirpath()
|
||||
imagedir = sourcedir.join("img")
|
||||
if not imagedir.check():
|
||||
imagedir.mkdir()
|
||||
# create halfway senseful imagename:
|
||||
# use hash of formula + alphanumeric characters of it
|
||||
# could
|
||||
imagename = "%s_%s.png" % (
|
||||
hash(text), "".join([c for c in text if c.isalnum()]))
|
||||
image = imagedir.join(imagename)
|
||||
latexformula2png(unescape(text, True), image)
|
||||
imagenode = nodes.image(image.relto(sourcedir), uri=image.relto(sourcedir))
|
||||
return [imagenode], []
|
||||
latexformula_role.content = True
|
||||
latexformula_role.options = {}
|
||||
|
||||
def register_linkrole(role_name, callback):
|
||||
def source_role(name, rawtext, text, lineno, inliner, options={},
|
||||
content=[]):
|
||||
text, target = callback(name, text)
|
||||
reference_node = nodes.reference(rawtext, text, name=text, refuri=target)
|
||||
return [reference_node], []
|
||||
source_role.content = True
|
||||
source_role.options = {}
|
||||
roles.register_canonical_role(role_name, source_role)
|
||||
154
py/rest/latex.py
154
py/rest/latex.py
@@ -1,154 +0,0 @@
|
||||
import py
|
||||
|
||||
from py.__.process.cmdexec import ExecutionFailed
|
||||
|
||||
font_to_package = {"times": "times", "helvetica": "times",
|
||||
"new century schoolbock": "newcent", "avant garde": "newcent",
|
||||
"palatino": "palatino",
|
||||
}
|
||||
sans_serif_fonts = {"helvetica": True,
|
||||
"avant garde": True,
|
||||
}
|
||||
|
||||
|
||||
def merge_files(pathlist, pagebreak=False):
|
||||
if len(pathlist) == 1:
|
||||
return pathlist[0].read()
|
||||
sectnum = False
|
||||
toc = False
|
||||
result = []
|
||||
includes = {}
|
||||
for path in pathlist:
|
||||
lines = path.readlines()
|
||||
for line in lines:
|
||||
# prevent several table of contents
|
||||
# and especially sectnum several times
|
||||
if ".. contents::" in line:
|
||||
if not toc:
|
||||
toc = True
|
||||
result.append(line)
|
||||
elif ".. sectnum::" in line:
|
||||
if not sectnum:
|
||||
sectnum = True
|
||||
result.append(line)
|
||||
elif line.strip().startswith(".. include:: "):
|
||||
#XXX slightly unsafe
|
||||
inc = line.strip()[13:]
|
||||
if inc not in includes:
|
||||
includes[inc] = True
|
||||
result.append(line)
|
||||
else:
|
||||
result.append(line)
|
||||
if pagebreak:
|
||||
result.append(".. raw:: latex \n\n \\newpage\n\n")
|
||||
if pagebreak:
|
||||
result.pop() #remove the last pagebreak again
|
||||
return "".join(result)
|
||||
|
||||
def create_stylesheet(options, path):
|
||||
fill_in = {}
|
||||
if "logo" in options:
|
||||
fill_in["have_logo"] = ""
|
||||
fill_in["logo"] = options["logo"]
|
||||
else:
|
||||
fill_in["have_logo"] = "%"
|
||||
fill_in["logo"] = ""
|
||||
if "font" in options:
|
||||
font = options["font"].lower()
|
||||
fill_in["font_package"] = font_to_package[font]
|
||||
fill_in["specified_font"] = ""
|
||||
fill_in["sans_serif"] = font not in sans_serif_fonts and "%" or ""
|
||||
else:
|
||||
fill_in["specified_font"] = "%"
|
||||
fill_in["sans_serif"] = "%"
|
||||
fill_in["font_package"] = ""
|
||||
if 'toc_depth' in options:
|
||||
fill_in["have_tocdepth"] = ""
|
||||
fill_in["toc_depth"] = options["toc_depth"]
|
||||
else:
|
||||
fill_in["have_tocdepth"] = "%"
|
||||
fill_in["toc_depth"] = ""
|
||||
fill_in["heading"] = options.get("heading", "")
|
||||
template_file = path.join("rest.sty.template")
|
||||
if not template_file.check():
|
||||
template_file = py.path.local(__file__).dirpath("rest.sty.template")
|
||||
return template_file.read() % fill_in
|
||||
|
||||
def process_configfile(configfile, debug=False):
|
||||
old = py.path.local()
|
||||
py.path.local(configfile).dirpath().chdir()
|
||||
configfile = py.path.local(configfile)
|
||||
path = configfile.dirpath()
|
||||
configfile_dic = {}
|
||||
py.std.sys.path.insert(0, str(path))
|
||||
py.builtin.execfile(str(configfile), configfile_dic)
|
||||
pagebreak = configfile_dic.get("pagebreak", False)
|
||||
rest_sources = [py.path.local(p)
|
||||
for p in configfile_dic['rest_sources']]
|
||||
rest = configfile.new(ext='txt')
|
||||
if len(rest_sources) > 1:
|
||||
assert rest not in rest_sources
|
||||
content = merge_files(rest_sources, pagebreak)
|
||||
if len(rest_sources) > 1:
|
||||
rest.write(content)
|
||||
sty = configfile.new(ext='sty')
|
||||
content = create_stylesheet(configfile_dic, path)
|
||||
sty.write(content)
|
||||
rest_options = None
|
||||
if 'rest_options' in configfile_dic:
|
||||
rest_options = configfile_dic['rest_options']
|
||||
process_rest_file(rest, sty.basename, debug, rest_options)
|
||||
#cleanup:
|
||||
if not debug:
|
||||
sty.remove()
|
||||
if rest not in rest_sources:
|
||||
rest.remove()
|
||||
old.chdir()
|
||||
|
||||
def process_rest_file(restfile, stylesheet=None, debug=False, rest_options=None):
|
||||
from docutils.core import publish_cmdline
|
||||
if not py.path.local.sysfind("pdflatex"):
|
||||
raise SystemExit("ERROR: pdflatex not found")
|
||||
old = py.path.local()
|
||||
f = py.path.local(restfile)
|
||||
path = f.dirpath()
|
||||
path.chdir()
|
||||
pdf = f.new(ext="pdf")
|
||||
if pdf.check():
|
||||
pdf.remove()
|
||||
tex = f.new(ext="tex").basename
|
||||
options = [f, "--input-encoding=latin-1", "--graphicx-option=auto",
|
||||
"--traceback"]
|
||||
if stylesheet is not None:
|
||||
sty = path.join(stylesheet)
|
||||
if sty.check():
|
||||
options.append('--stylesheet=%s' % (sty.relto(f.dirpath()), ))
|
||||
options.append(f.new(basename=tex))
|
||||
options = map(str, options)
|
||||
if rest_options is not None:
|
||||
options.extend(rest_options)
|
||||
publish_cmdline(writer_name='latex', argv=options)
|
||||
i = 0
|
||||
while i < 10: # there should never be as many as five reruns, but to be sure
|
||||
try:
|
||||
latexoutput = py.process.cmdexec('pdflatex "%s"' % (tex, ))
|
||||
except ExecutionFailed:
|
||||
e = py.std.sys.exc_info()[1]
|
||||
print("ERROR: pdflatex execution failed")
|
||||
print("pdflatex stdout:")
|
||||
print(e.out)
|
||||
print("pdflatex stderr:")
|
||||
print(e.err)
|
||||
raise SystemExit
|
||||
if debug:
|
||||
print(latexoutput)
|
||||
if py.std.re.search("LaTeX Warning:.*Rerun", latexoutput) is None:
|
||||
break
|
||||
i += 1
|
||||
|
||||
old.chdir()
|
||||
#cleanup:
|
||||
if not debug:
|
||||
for ext in "log aux tex out".split():
|
||||
p = pdf.new(ext=ext)
|
||||
p.remove()
|
||||
@@ -1,26 +0,0 @@
|
||||
\usepackage{fancyhdr}
|
||||
\usepackage{lastpage}
|
||||
\pagestyle{fancy}
|
||||
\usepackage[pdftex]{graphicx}
|
||||
|
||||
%(have_tocdepth)s\setcounter{tocdepth}{%(toc_depth)s}
|
||||
|
||||
%(sans_serif)s\renewcommand{\familydefault}{\sfdefault}
|
||||
%(specified_font)s\usepackage{%(font_package)s}
|
||||
\lhead{
|
||||
\begin{tabular}{l}
|
||||
\textbf{\Large %(heading)s}\tabularnewline
|
||||
\thepage\ of \pageref{LastPage}, \today
|
||||
\tabularnewline
|
||||
\tabularnewline
|
||||
\end{tabular}
|
||||
}
|
||||
\rhead{
|
||||
%(have_logo)s\includegraphics[height=4\baselineskip]{%(logo)s}
|
||||
}
|
||||
\cfoot{}
|
||||
\addtolength{\headheight}{3\baselineskip}
|
||||
\addtolength{\headheight}{0.61pt}
|
||||
\setlength\parskip{\medskipamount}
|
||||
\setlength\parindent{0pt}
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
import py
|
||||
import sys, os, traceback
|
||||
import re
|
||||
|
||||
if hasattr(sys.stdout, 'fileno') and os.isatty(sys.stdout.fileno()):
|
||||
def log(msg):
|
||||
print(msg)
|
||||
else:
|
||||
def log(msg):
|
||||
pass
|
||||
|
||||
def convert_rest_html(source, source_path, stylesheet=None, encoding='latin1'):
|
||||
from py.__.rest import directive
|
||||
""" return html latin1-encoded document for the given input.
|
||||
source a ReST-string
|
||||
sourcepath where to look for includes (basically)
|
||||
stylesheet path (to be used if any)
|
||||
"""
|
||||
from docutils.core import publish_string
|
||||
directive.set_backend_and_register_directives("html")
|
||||
kwargs = {
|
||||
'stylesheet' : stylesheet,
|
||||
'stylesheet_path': None,
|
||||
'traceback' : 1,
|
||||
'embed_stylesheet': 0,
|
||||
'output_encoding' : encoding,
|
||||
#'halt' : 0, # 'info',
|
||||
'halt_level' : 2,
|
||||
}
|
||||
# docutils uses os.getcwd() :-(
|
||||
source_path = os.path.abspath(str(source_path))
|
||||
prevdir = os.getcwd()
|
||||
try:
|
||||
#os.chdir(os.path.dirname(source_path))
|
||||
return publish_string(source, source_path, writer_name='html',
|
||||
settings_overrides=kwargs)
|
||||
finally:
|
||||
os.chdir(prevdir)
|
||||
|
||||
def process(txtpath, encoding='latin1'):
|
||||
""" process a textfile """
|
||||
log("processing %s" % txtpath)
|
||||
assert txtpath.check(ext='.txt')
|
||||
if isinstance(txtpath, py.path.svnwc):
|
||||
txtpath = txtpath.localpath
|
||||
htmlpath = txtpath.new(ext='.html')
|
||||
#svninfopath = txtpath.localpath.new(ext='.svninfo')
|
||||
|
||||
style = txtpath.dirpath('style.css')
|
||||
if style.check():
|
||||
stylesheet = style.basename
|
||||
else:
|
||||
stylesheet = None
|
||||
content = unicode(txtpath.read(), encoding)
|
||||
doc = convert_rest_html(content, txtpath, stylesheet=stylesheet, encoding=encoding)
|
||||
htmlpath.open('wb').write(doc)
|
||||
#log("wrote %r" % htmlpath)
|
||||
#if txtpath.check(svnwc=1, versioned=1):
|
||||
# info = txtpath.info()
|
||||
# svninfopath.dump(info)
|
||||
|
||||
if sys.version_info > (3, 0):
|
||||
def _uni(s): return s
|
||||
else:
|
||||
def _uni(s):
|
||||
return unicode(s)
|
||||
|
||||
rex1 = re.compile(r'.*<body>(.*)</body>.*', re.MULTILINE | re.DOTALL)
|
||||
rex2 = re.compile(r'.*<div class="document">(.*)</div>.*', re.MULTILINE | re.DOTALL)
|
||||
|
||||
def strip_html_header(string, encoding='utf8'):
|
||||
""" return the content of the body-tag """
|
||||
uni = unicode(string, encoding)
|
||||
for rex in rex1,rex2:
|
||||
match = rex.search(uni)
|
||||
if not match:
|
||||
break
|
||||
uni = match.group(1)
|
||||
return uni
|
||||
|
||||
class Project: # used for confrest.py files
|
||||
def __init__(self, sourcepath):
|
||||
self.sourcepath = sourcepath
|
||||
def process(self, path):
|
||||
return process(path)
|
||||
def get_htmloutputpath(self, path):
|
||||
return path.new(ext='html')
|
||||
18
py/std.py
18
py/std.py
@@ -1,18 +0,0 @@
|
||||
import sys
|
||||
|
||||
class Std(object):
|
||||
""" makes top-level python modules available as an attribute,
|
||||
importing them on first access.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.__dict__ = sys.modules
|
||||
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
m = __import__(name)
|
||||
except ImportError:
|
||||
raise AttributeError("py.std: could not import %s" % name)
|
||||
return m
|
||||
|
||||
std = Std()
|
||||
@@ -1 +0,0 @@
|
||||
""" versatile unit-testing tool + libraries """
|
||||
@@ -1,23 +0,0 @@
|
||||
import py
|
||||
import sys
|
||||
|
||||
#
|
||||
# main entry point
|
||||
#
|
||||
|
||||
def main(args=None):
|
||||
if args is None:
|
||||
args = sys.argv[1:]
|
||||
config = py.test.config
|
||||
try:
|
||||
config.parse(args)
|
||||
config.pluginmanager.do_configure(config)
|
||||
session = config.initsession()
|
||||
exitstatus = session.main()
|
||||
config.pluginmanager.do_unconfigure(config)
|
||||
raise SystemExit(exitstatus)
|
||||
except config.Error:
|
||||
e = sys.exc_info()[1]
|
||||
sys.stderr.write("ERROR: %s\n" %(e.args[0],))
|
||||
raise SystemExit(3)
|
||||
|
||||
@@ -1,467 +0,0 @@
|
||||
"""
|
||||
base test collection objects.
|
||||
Collectors and test Items form a tree
|
||||
that is usually built iteratively.
|
||||
"""
|
||||
import py
|
||||
pydir = py.path.local(py.__file__).dirpath()
|
||||
|
||||
def configproperty(name):
|
||||
def fget(self):
|
||||
#print "retrieving %r property from %s" %(name, self.fspath)
|
||||
return self.config.getvalue(name, self.fspath)
|
||||
return property(fget)
|
||||
|
||||
class Node(object):
|
||||
""" base class for Nodes in the collection tree.
|
||||
Collector nodes have children and
|
||||
Item nodes are terminal.
|
||||
|
||||
All nodes of the collection tree carry a _config
|
||||
attribute for these reasons:
|
||||
- to access custom Collection Nodes from a project
|
||||
(defined in conftest's)
|
||||
- to pickle themselves relatively to the "topdir"
|
||||
- configuration/options for setup/teardown
|
||||
stdout/stderr capturing and execution of test items
|
||||
"""
|
||||
def __init__(self, name, parent=None):
|
||||
self.name = name
|
||||
self.parent = parent
|
||||
self.config = getattr(parent, 'config', None)
|
||||
self.fspath = getattr(parent, 'fspath', None)
|
||||
|
||||
def _checkcollectable(self):
|
||||
if not hasattr(self, 'fspath'):
|
||||
self.parent._memocollect() # to reraise exception
|
||||
|
||||
#
|
||||
# note to myself: Pickling is uh.
|
||||
#
|
||||
def __getstate__(self):
|
||||
return (self.name, self.parent)
|
||||
def __setstate__(self, nameparent):
|
||||
name, parent = nameparent
|
||||
try:
|
||||
colitems = parent._memocollect()
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except Exception:
|
||||
# seems our parent can't collect us
|
||||
# so let's be somewhat operable
|
||||
# _checkcollectable() is to tell outsiders about the fact
|
||||
self.name = name
|
||||
self.parent = parent
|
||||
self.config = parent.config
|
||||
#self._obj = "could not unpickle"
|
||||
else:
|
||||
for colitem in colitems:
|
||||
if colitem.name == name:
|
||||
# we are a copy that will not be returned
|
||||
# by our parent
|
||||
self.__dict__ = colitem.__dict__
|
||||
break
|
||||
|
||||
def __repr__(self):
|
||||
if getattr(self.config.option, 'debug', False):
|
||||
return "<%s %r %0x>" %(self.__class__.__name__,
|
||||
getattr(self, 'name', None), id(self))
|
||||
else:
|
||||
return "<%s %r>" %(self.__class__.__name__,
|
||||
getattr(self, 'name', None))
|
||||
|
||||
# methods for ordering nodes
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, Node):
|
||||
return False
|
||||
return 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 (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
failure = py.std.sys.exc_info()
|
||||
setattr(self, exattrname, failure)
|
||||
raise
|
||||
setattr(self, attrname, res)
|
||||
return res
|
||||
|
||||
def listchain(self, rootfirst=False):
|
||||
""" return list of all parent collectors up to self,
|
||||
starting form root of collection tree. """
|
||||
l = [self]
|
||||
while 1:
|
||||
x = l[-1]
|
||||
if x.parent is not None:
|
||||
l.append(x.parent)
|
||||
else:
|
||||
if not rootfirst:
|
||||
l.reverse()
|
||||
return l
|
||||
|
||||
def listnames(self):
|
||||
return [x.name for x in self.listchain()]
|
||||
|
||||
def getparent(self, cls):
|
||||
current = self
|
||||
while current and not isinstance(current, cls):
|
||||
current = current.parent
|
||||
return current
|
||||
|
||||
def _getitembynames(self, namelist):
|
||||
cur = self
|
||||
for name in namelist:
|
||||
if name:
|
||||
next = cur.collect_by_name(name)
|
||||
if next is None:
|
||||
existingnames = [x.name for x in self._memocollect()]
|
||||
msg = ("Collector %r does not have name %r "
|
||||
"existing names are: %s" %
|
||||
(cur, name, existingnames))
|
||||
raise AssertionError(msg)
|
||||
cur = next
|
||||
return cur
|
||||
|
||||
|
||||
def _getfsnode(self, path):
|
||||
# this method is usually called from
|
||||
# config.getfsnode() which returns a colitem
|
||||
# from filename arguments
|
||||
#
|
||||
# pytest's collector tree does not neccessarily
|
||||
# follow the filesystem and we thus need to do
|
||||
# some special matching code here because
|
||||
# _getitembynames() works by colitem names, not
|
||||
# basenames.
|
||||
if path == self.fspath:
|
||||
return self
|
||||
basenames = path.relto(self.fspath).split(path.sep)
|
||||
cur = self
|
||||
while basenames:
|
||||
basename = basenames.pop(0)
|
||||
assert basename
|
||||
fspath = cur.fspath.join(basename)
|
||||
colitems = cur._memocollect()
|
||||
l = []
|
||||
for colitem in colitems:
|
||||
if colitem.fspath == fspath or colitem.name == basename:
|
||||
l.append(colitem)
|
||||
if not l:
|
||||
raise self.config.Error("can't collect: %s" %(fspath,))
|
||||
if basenames:
|
||||
if len(l) > 1:
|
||||
msg = ("Collector %r has more than one %r colitem "
|
||||
"existing colitems are: %s" %
|
||||
(cur, fspath, colitems))
|
||||
raise self.config.Error("xxx-too many test types for: %s" % (fspath, ))
|
||||
cur = l[0]
|
||||
else:
|
||||
if len(l) > 1:
|
||||
cur = l
|
||||
else:
|
||||
cur = l[0]
|
||||
break
|
||||
return cur
|
||||
|
||||
def readkeywords(self):
|
||||
return dict([(x, True) for x in self._keywords()])
|
||||
|
||||
def _keywords(self):
|
||||
return [self.name]
|
||||
|
||||
def _skipbykeyword(self, keywordexpr):
|
||||
""" return True if they given keyword expression means to
|
||||
skip this collector/item.
|
||||
"""
|
||||
if not keywordexpr:
|
||||
return
|
||||
chain = self.listchain()
|
||||
for key in filter(None, keywordexpr.split()):
|
||||
eor = key[:1] == '-'
|
||||
if eor:
|
||||
key = key[1:]
|
||||
if not (eor ^ self._matchonekeyword(key, chain)):
|
||||
return True
|
||||
|
||||
def _matchonekeyword(self, key, chain):
|
||||
elems = key.split(".")
|
||||
# XXX O(n^2), anyone cares?
|
||||
chain = [item.readkeywords() for item in chain if item._keywords()]
|
||||
for start, _ in enumerate(chain):
|
||||
if start + len(elems) > len(chain):
|
||||
return False
|
||||
for num, elem in enumerate(elems):
|
||||
for keyword in chain[num + start]:
|
||||
ok = False
|
||||
if elem in keyword:
|
||||
ok = True
|
||||
break
|
||||
if not ok:
|
||||
break
|
||||
if num == len(elems) - 1 and ok:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _prunetraceback(self, traceback):
|
||||
return traceback
|
||||
|
||||
def _totrail(self):
|
||||
""" provide a trail relative to the topdir,
|
||||
which can be used to reconstruct the
|
||||
collector (possibly on a different host
|
||||
starting from a different topdir).
|
||||
"""
|
||||
chain = self.listchain()
|
||||
topdir = self.config.topdir
|
||||
relpath = chain[0].fspath.relto(topdir)
|
||||
if not relpath:
|
||||
if chain[0].fspath == topdir:
|
||||
relpath = "."
|
||||
else:
|
||||
raise ValueError("%r not relative to topdir %s"
|
||||
%(chain[0].fspath, topdir))
|
||||
return relpath, tuple([x.name for x in chain[1:]])
|
||||
|
||||
def _fromtrail(trail, config):
|
||||
relpath, names = trail
|
||||
fspath = config.topdir.join(relpath)
|
||||
col = config.getfsnode(fspath)
|
||||
return col._getitembynames(names)
|
||||
_fromtrail = staticmethod(_fromtrail)
|
||||
|
||||
def _repr_failure_py(self, excinfo, outerr=None):
|
||||
assert outerr is None, "XXX deprecated"
|
||||
excinfo.traceback = self._prunetraceback(excinfo.traceback)
|
||||
# XXX temporary hack: getrepr() should not take a 'style' argument
|
||||
# at all; it should record all data in all cases, and the style
|
||||
# should be parametrized in toterminal().
|
||||
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
|
||||
shortfailurerepr = "F"
|
||||
|
||||
class Collector(Node):
|
||||
"""
|
||||
Collector instances create children through collect()
|
||||
and thus iteratively build a tree. attributes::
|
||||
|
||||
parent: attribute pointing to the parent collector
|
||||
(or None if this is the root collector)
|
||||
name: basename of this collector object
|
||||
"""
|
||||
Directory = configproperty('Directory')
|
||||
Module = configproperty('Module')
|
||||
|
||||
def collect(self):
|
||||
""" returns a list of children (items and collectors)
|
||||
for this collection node.
|
||||
"""
|
||||
raise NotImplementedError("abstract")
|
||||
|
||||
def collect_by_name(self, name):
|
||||
""" return a child matching the given name, else None. """
|
||||
for colitem in self._memocollect():
|
||||
if colitem.name == name:
|
||||
return colitem
|
||||
|
||||
def repr_failure(self, excinfo, outerr=None):
|
||||
""" represent a failure. """
|
||||
assert outerr is None, "XXX deprecated"
|
||||
return self._repr_failure_py(excinfo)
|
||||
|
||||
def _memocollect(self):
|
||||
""" internal helper method to cache results of calling collect(). """
|
||||
return self._memoizedcall('_collected', self.collect)
|
||||
|
||||
# **********************************************************************
|
||||
# DEPRECATED METHODS
|
||||
# **********************************************************************
|
||||
|
||||
def _deprecated_collect(self):
|
||||
# avoid recursion:
|
||||
# collect -> _deprecated_collect -> custom run() ->
|
||||
# super().run() -> collect
|
||||
attrname = '_depcollectentered'
|
||||
if hasattr(self, attrname):
|
||||
return
|
||||
setattr(self, attrname, True)
|
||||
method = getattr(self.__class__, 'run', None)
|
||||
if method is not None and method != Collector.run:
|
||||
warnoldcollect(function=method)
|
||||
names = self.run()
|
||||
return [x for x in [self.join(name) for name in names] if x]
|
||||
|
||||
def run(self):
|
||||
""" DEPRECATED: returns a list of names available from this collector.
|
||||
You can return an empty list. Callers of this method
|
||||
must take care to catch exceptions properly.
|
||||
"""
|
||||
return [colitem.name for colitem in self._memocollect()]
|
||||
|
||||
def join(self, name):
|
||||
""" DEPRECATED: return a child collector or item for the given name.
|
||||
If the return value is None there is no such child.
|
||||
"""
|
||||
return self.collect_by_name(name)
|
||||
|
||||
def _prunetraceback(self, traceback):
|
||||
if hasattr(self, 'fspath'):
|
||||
path = self.fspath
|
||||
ntraceback = traceback.cut(path=self.fspath)
|
||||
if ntraceback == traceback:
|
||||
ntraceback = ntraceback.cut(excludepath=pydir)
|
||||
traceback = ntraceback.filter()
|
||||
return traceback
|
||||
|
||||
class FSCollector(Collector):
|
||||
def __init__(self, fspath, parent=None):
|
||||
fspath = py.path.local(fspath)
|
||||
super(FSCollector, self).__init__(fspath.basename, parent)
|
||||
self.fspath = fspath
|
||||
|
||||
def __getstate__(self):
|
||||
if self.parent is None:
|
||||
# the root node needs to pickle more context info
|
||||
topdir = self.config.topdir
|
||||
relpath = self.fspath.relto(topdir)
|
||||
if not relpath:
|
||||
if self.fspath == topdir:
|
||||
relpath = "."
|
||||
else:
|
||||
raise ValueError("%r not relative to topdir %s"
|
||||
%(self.fspath, topdir))
|
||||
return (self.name, self.config, relpath)
|
||||
else:
|
||||
return (self.name, self.parent)
|
||||
|
||||
def __setstate__(self, picklestate):
|
||||
if len(picklestate) == 3:
|
||||
# root node
|
||||
name, config, relpath = picklestate
|
||||
fspath = config.topdir.join(relpath)
|
||||
fsnode = config.getfsnode(fspath)
|
||||
self.__dict__.update(fsnode.__dict__)
|
||||
else:
|
||||
name, parent = picklestate
|
||||
self.__init__(parent.fspath.join(name), parent=parent)
|
||||
|
||||
class File(FSCollector):
|
||||
""" base class for collecting tests from a file. """
|
||||
|
||||
class Directory(FSCollector):
|
||||
def recfilter(self, path):
|
||||
if path.check(dir=1, dotfile=0):
|
||||
return path.basename not in ('CVS', '_darcs', '{arch}')
|
||||
|
||||
def collect(self):
|
||||
l = self._deprecated_collect()
|
||||
if l is not None:
|
||||
return l
|
||||
l = []
|
||||
for path in self.fspath.listdir(sort=True):
|
||||
res = self.consider(path)
|
||||
if res is not None:
|
||||
if isinstance(res, (list, tuple)):
|
||||
l.extend(res)
|
||||
else:
|
||||
l.append(res)
|
||||
return l
|
||||
|
||||
def _ignore(self, path):
|
||||
ignore_paths = self.config.getconftest_pathlist("collect_ignore", path=path)
|
||||
return ignore_paths and path in ignore_paths
|
||||
# XXX more refined would be:
|
||||
if ignore_paths:
|
||||
for p in ignore_paths:
|
||||
if path == p or path.relto(p):
|
||||
return True
|
||||
|
||||
def consider(self, path):
|
||||
if self._ignore(path):
|
||||
return
|
||||
if path.check(file=1):
|
||||
res = self.consider_file(path)
|
||||
elif path.check(dir=1):
|
||||
res = self.consider_dir(path)
|
||||
else:
|
||||
res = None
|
||||
if isinstance(res, list):
|
||||
# throw out identical results
|
||||
l = []
|
||||
for x in res:
|
||||
if x not in l:
|
||||
assert x.parent == self, "wrong collection tree construction"
|
||||
l.append(x)
|
||||
res = l
|
||||
return res
|
||||
|
||||
def consider_file(self, path):
|
||||
return self.config.hook.pytest_collect_file(path=path, parent=self)
|
||||
|
||||
def consider_dir(self, path, usefilters=None):
|
||||
if usefilters is not None:
|
||||
py.log._apiwarn("0.99", "usefilters argument not needed")
|
||||
return self.config.hook.pytest_collect_directory(
|
||||
path=path, parent=self)
|
||||
|
||||
class Item(Node):
|
||||
""" a basic test item. """
|
||||
def _deprecated_testexecution(self):
|
||||
if self.__class__.run != Item.run:
|
||||
warnoldtestrun(function=self.run)
|
||||
elif self.__class__.execute != Item.execute:
|
||||
warnoldtestrun(function=self.execute)
|
||||
else:
|
||||
return False
|
||||
self.run()
|
||||
return True
|
||||
|
||||
def run(self):
|
||||
""" deprecated, here because subclasses might call it. """
|
||||
return self.execute(self.obj)
|
||||
|
||||
def execute(self, obj):
|
||||
""" deprecated, here because subclasses might call it. """
|
||||
return obj()
|
||||
|
||||
def reportinfo(self):
|
||||
return self.fspath, None, ""
|
||||
|
||||
def warnoldcollect(function=None):
|
||||
py.log._apiwarn("1.0",
|
||||
"implement collector.collect() instead of "
|
||||
"collector.run() and collector.join()",
|
||||
stacklevel=2, function=function)
|
||||
|
||||
def warnoldtestrun(function=None):
|
||||
py.log._apiwarn("1.0",
|
||||
"implement item.runtest() instead of "
|
||||
"item.run() and item.execute()",
|
||||
stacklevel=2, function=function)
|
||||
@@ -1,58 +0,0 @@
|
||||
import py
|
||||
|
||||
from py.test.collect import Function
|
||||
|
||||
class TestCaseUnit(Function):
|
||||
""" compatibility Unit executor for TestCase methods
|
||||
honouring setUp and tearDown semantics.
|
||||
"""
|
||||
def runtest(self, _deprecated=None):
|
||||
boundmethod = self.obj
|
||||
instance = py.builtin._getimself(boundmethod)
|
||||
instance.setUp()
|
||||
try:
|
||||
boundmethod()
|
||||
finally:
|
||||
instance.tearDown()
|
||||
|
||||
class TestCase(object):
|
||||
"""compatibility class of unittest's TestCase. """
|
||||
Function = TestCaseUnit
|
||||
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
def fail(self, msg=None):
|
||||
""" fail immediate with given message. """
|
||||
py.test.fail(msg)
|
||||
|
||||
def assertRaises(self, excclass, func, *args, **kwargs):
|
||||
py.test.raises(excclass, func, *args, **kwargs)
|
||||
failUnlessRaises = assertRaises
|
||||
|
||||
# dynamically construct (redundant) methods
|
||||
aliasmap = [
|
||||
('x', 'not x', 'assert_, failUnless'),
|
||||
('x', 'x', 'failIf'),
|
||||
('x,y', 'x!=y', 'failUnlessEqual,assertEqual, assertEquals'),
|
||||
('x,y', 'x==y', 'failIfEqual,assertNotEqual, assertNotEquals'),
|
||||
]
|
||||
items = []
|
||||
for sig, expr, names in aliasmap:
|
||||
names = map(str.strip, names.split(','))
|
||||
sigsubst = expr.replace('y', '%s').replace('x', '%s')
|
||||
for name in names:
|
||||
items.append("""
|
||||
def %(name)s(self, %(sig)s, msg=""):
|
||||
__tracebackhide__ = True
|
||||
if %(expr)s:
|
||||
py.test.fail(msg=msg + (%(sigsubst)r %% (%(sig)s)))
|
||||
""" % locals() )
|
||||
|
||||
source = "".join(items)
|
||||
exec(py.code.Source(source).compile())
|
||||
|
||||
__all__ = ['TestCase']
|
||||
@@ -1,310 +0,0 @@
|
||||
import py, os
|
||||
from py.__.test.conftesthandle import Conftest
|
||||
|
||||
from py.__.test import parseopt
|
||||
|
||||
def ensuretemp(string, dir=1):
|
||||
""" return temporary directory path with
|
||||
the given string as the trailing part.
|
||||
"""
|
||||
return py.test.config.ensuretemp(string, dir=dir)
|
||||
|
||||
class CmdOptions(object):
|
||||
""" pure container instance for holding cmdline options
|
||||
as attributes.
|
||||
"""
|
||||
def __repr__(self):
|
||||
return "<CmdOptions %r>" %(self.__dict__,)
|
||||
|
||||
class Error(Exception):
|
||||
""" Test Configuration Error. """
|
||||
|
||||
class Config(object):
|
||||
""" test configuration object, provides access to config valueso,
|
||||
the pluginmanager and plugin api.
|
||||
"""
|
||||
Option = py.std.optparse.Option
|
||||
Error = Error
|
||||
basetemp = None
|
||||
_sessionclass = None
|
||||
|
||||
def __init__(self, pluginmanager=None, topdir=None):
|
||||
self.option = CmdOptions()
|
||||
self.topdir = topdir
|
||||
self._parser = parseopt.Parser(
|
||||
usage="usage: %prog [options] [file_or_dir] [file_or_dir] [...]",
|
||||
processopt=self._processopt,
|
||||
)
|
||||
if pluginmanager is None:
|
||||
pluginmanager = py.test._PluginManager()
|
||||
assert isinstance(pluginmanager, py.test._PluginManager)
|
||||
self.pluginmanager = pluginmanager
|
||||
self._conftest = Conftest(onimport=self._onimportconftest)
|
||||
self.hook = pluginmanager.hook
|
||||
|
||||
def _onimportconftest(self, conftestmodule):
|
||||
self.trace("loaded conftestmodule %r" %(conftestmodule,))
|
||||
self.pluginmanager.consider_conftest(conftestmodule)
|
||||
|
||||
def trace(self, msg):
|
||||
if getattr(self.option, 'traceconfig', None):
|
||||
self.hook.pytest_trace(category="config", msg=msg)
|
||||
|
||||
def _processopt(self, opt):
|
||||
if hasattr(opt, 'default') and opt.dest:
|
||||
val = os.environ.get("PYTEST_OPTION_" + opt.dest.upper(), None)
|
||||
if val is not None:
|
||||
if opt.type == "int":
|
||||
val = int(val)
|
||||
elif opt.type == "long":
|
||||
val = long(val)
|
||||
elif opt.type == "float":
|
||||
val = float(val)
|
||||
elif not opt.type and opt.action in ("store_true", "store_false"):
|
||||
val = eval(val)
|
||||
opt.default = val
|
||||
else:
|
||||
name = "option_" + opt.dest
|
||||
try:
|
||||
opt.default = self._conftest.rget(name)
|
||||
except (ValueError, KeyError):
|
||||
pass
|
||||
if not hasattr(self.option, opt.dest):
|
||||
setattr(self.option, opt.dest, opt.default)
|
||||
|
||||
def _preparse(self, args):
|
||||
self._conftest.setinitial(args)
|
||||
self.pluginmanager.consider_preparse(args)
|
||||
self.pluginmanager.consider_env()
|
||||
self.pluginmanager.do_addoption(self._parser)
|
||||
|
||||
def parse(self, args):
|
||||
""" parse 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)
|
||||
args = self._parser.parse_setoption(args, self.option)
|
||||
if not args:
|
||||
args.append(py.std.os.getcwd())
|
||||
self.topdir = gettopdir(args)
|
||||
self.args = [py.path.local(x) for x in args]
|
||||
|
||||
# config objects are usually pickled across system
|
||||
# barriers but they contain filesystem paths.
|
||||
# upon getstate/setstate we take care to do everything
|
||||
# relative to "topdir".
|
||||
def __getstate__(self):
|
||||
l = []
|
||||
for path in self.args:
|
||||
path = py.path.local(path)
|
||||
l.append(path.relto(self.topdir))
|
||||
return l, self.option
|
||||
|
||||
def __setstate__(self, repr):
|
||||
# warning global side effects:
|
||||
# * registering to py lib plugins
|
||||
# * setting py.test.config
|
||||
self.__init__(
|
||||
pluginmanager=py.test._PluginManager(py._com.comregistry),
|
||||
topdir=py.path.local(),
|
||||
)
|
||||
# we have to set py.test.config because preparse()
|
||||
# might load conftest files which have
|
||||
# py.test.config.addoptions() lines in them
|
||||
py.test.config = self
|
||||
args, cmdlineopts = repr
|
||||
args = [self.topdir.join(x) for x in args]
|
||||
self.option = cmdlineopts
|
||||
self._preparse(args)
|
||||
self.args = args
|
||||
|
||||
def ensuretemp(self, string, dir=True):
|
||||
return self.getbasetemp().ensure(string, dir=dir)
|
||||
|
||||
def getbasetemp(self):
|
||||
if self.basetemp is None:
|
||||
basetemp = self.option.basetemp
|
||||
if basetemp:
|
||||
basetemp = py.path.local(basetemp)
|
||||
if not basetemp.check(dir=1):
|
||||
basetemp.mkdir()
|
||||
else:
|
||||
basetemp = py.path.local.make_numbered_dir(prefix='pytest-')
|
||||
self.basetemp = basetemp
|
||||
return self.basetemp
|
||||
|
||||
def mktemp(self, basename, numbered=False):
|
||||
basetemp = self.getbasetemp()
|
||||
if not numbered:
|
||||
return basetemp.mkdir(basename)
|
||||
else:
|
||||
return py.path.local.make_numbered_dir(prefix=basename + "-",
|
||||
keep=0, rootdir=basetemp, lock_timeout=None)
|
||||
|
||||
def getcolitems(self):
|
||||
return [self.getfsnode(arg) for arg in self.args]
|
||||
|
||||
def getfsnode(self, path):
|
||||
path = py.path.local(path)
|
||||
if not path.check():
|
||||
raise self.Error("file not found: %s" %(path,))
|
||||
# we want our possibly custom collection tree to start at pkgroot
|
||||
pkgpath = path.pypkgpath()
|
||||
if pkgpath is None:
|
||||
pkgpath = path.check(file=1) and path.dirpath() or path
|
||||
Dir = self._conftest.rget("Directory", pkgpath)
|
||||
col = Dir(pkgpath)
|
||||
col.config = self
|
||||
return col._getfsnode(path)
|
||||
|
||||
def getconftest_pathlist(self, name, path=None):
|
||||
""" return a matching value, which needs to be sequence
|
||||
of filenames that will be returned as a list of Path
|
||||
objects (they can be relative to the location
|
||||
where they were found).
|
||||
"""
|
||||
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:
|
||||
relroot = relroot.replace("/", py.path.local.sep)
|
||||
l.append(modpath.join(relroot, abs=True))
|
||||
return l
|
||||
|
||||
def addoptions(self, groupname, *specs):
|
||||
""" add a named group of options to the current testing session.
|
||||
This function gets invoked during testing session initialization.
|
||||
"""
|
||||
py.log._apiwarn("1.0", "define plugins to add options", stacklevel=2)
|
||||
group = self._parser.addgroup(groupname)
|
||||
for opt in specs:
|
||||
group._addoption_instance(opt)
|
||||
return self.option
|
||||
|
||||
def addoption(self, *optnames, **attrs):
|
||||
return self._parser.addoption(*optnames, **attrs)
|
||||
|
||||
def getvalueorskip(self, name, path=None):
|
||||
""" return getvalue() or call py.test.skip if no value exists. """
|
||||
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 getvalue(self, name, path=None):
|
||||
""" return 'name' value looked up from the 'options'
|
||||
and then from the first conftest file found up
|
||||
the path (including the path itself).
|
||||
if path is None, lookup the value in the initial
|
||||
conftest modules found during command line parsing.
|
||||
"""
|
||||
try:
|
||||
return getattr(self.option, name)
|
||||
except AttributeError:
|
||||
return self._conftest.rget(name, path)
|
||||
|
||||
def setsessionclass(self, cls):
|
||||
if self._sessionclass is not None:
|
||||
raise ValueError("sessionclass already set to: %r" %(
|
||||
self._sessionclass))
|
||||
self._sessionclass = cls
|
||||
|
||||
def initsession(self):
|
||||
""" return an initialized session object. """
|
||||
cls = self._sessionclass
|
||||
if cls is None:
|
||||
from py.__.test.session import Session
|
||||
cls = Session
|
||||
session = cls(self)
|
||||
self.trace("instantiated session %r" % session)
|
||||
return session
|
||||
|
||||
def _reparse(self, args):
|
||||
""" this is used from tests that want to re-invoke parse(). """
|
||||
#assert args # XXX should not be empty
|
||||
global config_per_process
|
||||
oldconfig = py.test.config
|
||||
try:
|
||||
config_per_process = py.test.config = Config()
|
||||
config_per_process.basetemp = self.mktemp("reparse", numbered=True)
|
||||
config_per_process.parse(args)
|
||||
return config_per_process
|
||||
finally:
|
||||
config_per_process = py.test.config = oldconfig
|
||||
|
||||
def getxspecs(self):
|
||||
xspeclist = []
|
||||
for xspec in self.getvalue("tx"):
|
||||
i = xspec.find("*")
|
||||
try:
|
||||
num = int(xspec[:i])
|
||||
except ValueError:
|
||||
xspeclist.append(xspec)
|
||||
else:
|
||||
xspeclist.extend([xspec[i+1:]] * num)
|
||||
if not xspeclist:
|
||||
raise self.Error("MISSING test execution (tx) nodes: please specify --tx")
|
||||
import execnet
|
||||
return [execnet.XSpec(x) for x in xspeclist]
|
||||
|
||||
def getrsyncdirs(self):
|
||||
config = self
|
||||
roots = config.option.rsyncdir
|
||||
conftestroots = config.getconftest_pathlist("rsyncdirs")
|
||||
if conftestroots:
|
||||
roots.extend(conftestroots)
|
||||
pydir = py.path.local(py.__file__).dirpath()
|
||||
roots = [py.path.local(root) for root in roots]
|
||||
for root in roots:
|
||||
if not root.check():
|
||||
raise config.Error("rsyncdir doesn't exist: %r" %(root,))
|
||||
if pydir is not None and root.basename == "py":
|
||||
if root != pydir:
|
||||
raise config.Error("root %r conflicts with imported %r" %(root, pydir))
|
||||
pydir = None
|
||||
if pydir is not None:
|
||||
roots.append(pydir)
|
||||
return roots
|
||||
|
||||
#
|
||||
# helpers
|
||||
#
|
||||
|
||||
def checkmarshal(name, value):
|
||||
try:
|
||||
py.std.marshal.dumps(value)
|
||||
except ValueError:
|
||||
raise ValueError("%s=%r is not marshallable" %(name, value))
|
||||
|
||||
def gettopdir(args):
|
||||
""" return the top directory for the given paths.
|
||||
if the common base dir resides in a python package
|
||||
parent directory of the root package is returned.
|
||||
"""
|
||||
args = [py.path.local(arg) for arg in args]
|
||||
p = args and args[0] or None
|
||||
for x in args[1:]:
|
||||
p = p.common(x)
|
||||
assert p, "cannot determine common basedir of %s" %(args,)
|
||||
pkgdir = p.pypkgpath()
|
||||
if pkgdir is None:
|
||||
if p.check(file=1):
|
||||
p = p.dirpath()
|
||||
return p
|
||||
else:
|
||||
return pkgdir.dirpath()
|
||||
|
||||
|
||||
# this is the one per-process instance of py.test configuration
|
||||
config_per_process = Config(
|
||||
pluginmanager=py.test._PluginManager(py._com.comregistry)
|
||||
)
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
import py
|
||||
defaultconftestpath = py.path.local(__file__).dirpath("defaultconftest.py")
|
||||
|
||||
class Conftest(object):
|
||||
""" the single place for accessing values and interacting
|
||||
towards conftest modules from py.test objects.
|
||||
|
||||
Note that triggering Conftest instances to import
|
||||
conftest.py files may result in added cmdline options.
|
||||
XXX
|
||||
"""
|
||||
def __init__(self, path=None, onimport=None):
|
||||
self._path2confmods = {}
|
||||
self._onimport = onimport
|
||||
if path is not None:
|
||||
self.setinitial([path])
|
||||
|
||||
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()
|
||||
for arg in args + [current]:
|
||||
anchor = current.join(arg, abs=1)
|
||||
if anchor.check(): # we found some file object
|
||||
self._path2confmods[None] = self.getconftestmodules(anchor)
|
||||
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 conftest.")
|
||||
dp = path.dirpath()
|
||||
if dp == path:
|
||||
return [self.importconftest(defaultconftestpath)]
|
||||
clist = self.getconftestmodules(dp)
|
||||
conftestpath = path.join("conftest.py")
|
||||
if conftestpath.check(file=1):
|
||||
clist.append(self.importconftest(conftestpath))
|
||||
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):
|
||||
# Using caching here looks redundant since ultimately
|
||||
# sys.modules caches already
|
||||
assert conftestpath.check(), conftestpath
|
||||
if not conftestpath.dirpath('__init__.py').check(file=1):
|
||||
# HACK: we don't want any "globally" imported conftest.py,
|
||||
# prone to conflicts and subtle problems
|
||||
modname = str(conftestpath).replace('.', conftestpath.sep)
|
||||
mod = conftestpath.pyimport(modname=modname)
|
||||
else:
|
||||
mod = conftestpath.pyimport()
|
||||
if self._onimport:
|
||||
self._onimport(mod)
|
||||
return mod
|
||||
@@ -1,14 +0,0 @@
|
||||
import py
|
||||
|
||||
Module = py.test.collect.Module
|
||||
Directory = py.test.collect.Directory
|
||||
File = py.test.collect.File
|
||||
|
||||
# python collectors
|
||||
Class = py.test.collect.Class
|
||||
Generator = py.test.collect.Generator
|
||||
Function = py.test.collect.Function
|
||||
Instance = py.test.collect.Instance
|
||||
|
||||
pytest_plugins = "default runner capture terminal keyword xfail tmpdir monkeypatch recwarn pdb pastebin unittest helpconfig nose assertion".split()
|
||||
|
||||
1
py/test/dist/__init__.py
vendored
1
py/test/dist/__init__.py
vendored
@@ -1 +0,0 @@
|
||||
#
|
||||
280
py/test/dist/dsession.py
vendored
280
py/test/dist/dsession.py
vendored
@@ -1,280 +0,0 @@
|
||||
"""
|
||||
|
||||
EXPERIMENTAL dsession session (for dist/non-dist unification)
|
||||
|
||||
"""
|
||||
|
||||
import py
|
||||
from py.__.test.session import Session
|
||||
from py.__.test import outcome
|
||||
from py.__.test.dist.nodemanage import NodeManager
|
||||
queue = py.builtin._tryimport('queue', 'Queue')
|
||||
|
||||
debug_file = None # open('/tmp/loop.log', 'w')
|
||||
def debug(*args):
|
||||
if debug_file is not None:
|
||||
s = " ".join(map(str, args))
|
||||
debug_file.write(s+"\n")
|
||||
debug_file.flush()
|
||||
|
||||
class LoopState(object):
|
||||
def __init__(self, dsession, colitems):
|
||||
self.dsession = dsession
|
||||
self.colitems = colitems
|
||||
self.exitstatus = None
|
||||
# loopstate.dowork is False after reschedule events
|
||||
# because otherwise we might very busily loop
|
||||
# waiting for a host to become ready.
|
||||
self.dowork = True
|
||||
self.shuttingdown = False
|
||||
self.testsfailed = False
|
||||
|
||||
def __repr__(self):
|
||||
return "<LoopState exitstatus=%r shuttingdown=%r len(colitems)=%d>" % (
|
||||
self.exitstatus, self.shuttingdown, len(self.colitems))
|
||||
|
||||
def pytest_runtest_logreport(self, report):
|
||||
if report.item in self.dsession.item2nodes:
|
||||
if report.when != "teardown": # otherwise we already managed it
|
||||
self.dsession.removeitem(report.item, report.node)
|
||||
if report.failed:
|
||||
self.testsfailed = True
|
||||
|
||||
def pytest_collectreport(self, report):
|
||||
if report.passed:
|
||||
self.colitems.extend(report.result)
|
||||
|
||||
def pytest_testnodeready(self, node):
|
||||
self.dsession.addnode(node)
|
||||
|
||||
def pytest_testnodedown(self, node, error=None):
|
||||
pending = self.dsession.removenode(node)
|
||||
if pending:
|
||||
if error:
|
||||
crashitem = pending[0]
|
||||
debug("determined crashitem", crashitem)
|
||||
self.dsession.handle_crashitem(crashitem, node)
|
||||
# XXX recovery handling for "each"?
|
||||
# currently pending items are not retried
|
||||
if self.dsession.config.option.dist == "load":
|
||||
self.colitems.extend(pending[1:])
|
||||
|
||||
def pytest_rescheduleitems(self, items):
|
||||
self.colitems.extend(items)
|
||||
self.dowork = False # avoid busywait
|
||||
|
||||
class DSession(Session):
|
||||
"""
|
||||
Session drives the collection and running of tests
|
||||
and generates test events for reporters.
|
||||
"""
|
||||
MAXITEMSPERHOST = 15
|
||||
|
||||
def __init__(self, config):
|
||||
self.queue = queue.Queue()
|
||||
self.node2pending = {}
|
||||
self.item2nodes = {}
|
||||
super(DSession, self).__init__(config=config)
|
||||
|
||||
#def pytest_configure(self, __multicall__, config):
|
||||
# __multicall__.execute()
|
||||
# try:
|
||||
# config.getxspecs()
|
||||
# except config.Error:
|
||||
# print
|
||||
# raise config.Error("dist mode %r needs test execution environments, "
|
||||
# "none found." %(config.option.dist))
|
||||
|
||||
def main(self, colitems=None):
|
||||
colitems = self.getinitialitems(colitems)
|
||||
self.sessionstarts()
|
||||
self.setup()
|
||||
exitstatus = self.loop(colitems)
|
||||
self.teardown()
|
||||
self.sessionfinishes(exitstatus=exitstatus)
|
||||
return exitstatus
|
||||
|
||||
def loop_once(self, loopstate):
|
||||
if loopstate.shuttingdown:
|
||||
return self.loop_once_shutdown(loopstate)
|
||||
colitems = loopstate.colitems
|
||||
if loopstate.dowork and colitems:
|
||||
self.triggertesting(loopstate.colitems)
|
||||
colitems[:] = []
|
||||
# we use a timeout here so that control-C gets through
|
||||
while 1:
|
||||
try:
|
||||
eventcall = self.queue.get(timeout=2.0)
|
||||
break
|
||||
except queue.Empty:
|
||||
continue
|
||||
loopstate.dowork = True
|
||||
|
||||
callname, args, kwargs = eventcall
|
||||
if callname is not None:
|
||||
call = getattr(self.config.hook, callname)
|
||||
assert not args
|
||||
call(**kwargs)
|
||||
|
||||
# termination conditions
|
||||
if ((loopstate.testsfailed and self.config.option.exitfirst) or
|
||||
(not self.item2nodes and not colitems and not self.queue.qsize())):
|
||||
self.triggershutdown()
|
||||
loopstate.shuttingdown = True
|
||||
elif not self.node2pending:
|
||||
loopstate.exitstatus = outcome.EXIT_NOHOSTS
|
||||
|
||||
def loop_once_shutdown(self, loopstate):
|
||||
# once we are in shutdown mode we dont send
|
||||
# events other than HostDown upstream
|
||||
eventname, args, kwargs = self.queue.get()
|
||||
if eventname == "pytest_testnodedown":
|
||||
self.config.hook.pytest_testnodedown(**kwargs)
|
||||
self.removenode(kwargs['node'])
|
||||
elif eventname == "pytest_runtest_logreport":
|
||||
# might be some teardown report
|
||||
self.config.hook.pytest_runtest_logreport(**kwargs)
|
||||
if not self.node2pending:
|
||||
# finished
|
||||
if loopstate.testsfailed:
|
||||
loopstate.exitstatus = outcome.EXIT_TESTSFAILED
|
||||
else:
|
||||
loopstate.exitstatus = outcome.EXIT_OK
|
||||
#self.config.pluginmanager.unregister(loopstate)
|
||||
|
||||
def _initloopstate(self, colitems):
|
||||
loopstate = LoopState(self, colitems)
|
||||
self.config.pluginmanager.register(loopstate)
|
||||
return loopstate
|
||||
|
||||
def loop(self, colitems):
|
||||
try:
|
||||
loopstate = self._initloopstate(colitems)
|
||||
loopstate.dowork = False # first receive at least one HostUp events
|
||||
while 1:
|
||||
self.loop_once(loopstate)
|
||||
if loopstate.exitstatus is not None:
|
||||
exitstatus = loopstate.exitstatus
|
||||
break
|
||||
except KeyboardInterrupt:
|
||||
excinfo = py.code.ExceptionInfo()
|
||||
self.config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
|
||||
exitstatus = outcome.EXIT_INTERRUPTED
|
||||
except:
|
||||
self.config.pluginmanager.notify_exception()
|
||||
exitstatus = outcome.EXIT_INTERNALERROR
|
||||
self.config.pluginmanager.unregister(loopstate)
|
||||
if exitstatus == 0 and self._testsfailed:
|
||||
exitstatus = outcome.EXIT_TESTSFAILED
|
||||
return exitstatus
|
||||
|
||||
def triggershutdown(self):
|
||||
for node in self.node2pending:
|
||||
node.shutdown()
|
||||
|
||||
def addnode(self, node):
|
||||
assert node not in self.node2pending
|
||||
self.node2pending[node] = []
|
||||
|
||||
def removenode(self, node):
|
||||
try:
|
||||
pending = self.node2pending.pop(node)
|
||||
except KeyError:
|
||||
# this happens if we didn't receive a testnodeready event yet
|
||||
return []
|
||||
for item in pending:
|
||||
l = self.item2nodes[item]
|
||||
l.remove(node)
|
||||
if not l:
|
||||
del self.item2nodes[item]
|
||||
return pending
|
||||
|
||||
def triggertesting(self, colitems):
|
||||
colitems = self.filteritems(colitems)
|
||||
senditems = []
|
||||
for next in colitems:
|
||||
if isinstance(next, py.test.collect.Item):
|
||||
senditems.append(next)
|
||||
else:
|
||||
self.config.hook.pytest_collectstart(collector=next)
|
||||
colrep = self.config.hook.pytest_make_collect_report(collector=next)
|
||||
self.queueevent("pytest_collectreport", report=colrep)
|
||||
if self.config.option.dist == "each":
|
||||
self.senditems_each(senditems)
|
||||
else:
|
||||
# XXX assert self.config.option.dist == "load"
|
||||
self.senditems_load(senditems)
|
||||
|
||||
def queueevent(self, eventname, **kwargs):
|
||||
self.queue.put((eventname, (), kwargs))
|
||||
|
||||
def senditems_each(self, tosend):
|
||||
if not tosend:
|
||||
return
|
||||
room = self.MAXITEMSPERHOST
|
||||
for node, pending in self.node2pending.items():
|
||||
room = min(self.MAXITEMSPERHOST - len(pending), room)
|
||||
sending = tosend[:room]
|
||||
for node, pending in self.node2pending.items():
|
||||
node.sendlist(sending)
|
||||
pending.extend(sending)
|
||||
for item in sending:
|
||||
nodes = self.item2nodes.setdefault(item, [])
|
||||
assert node not in nodes
|
||||
nodes.append(node)
|
||||
self.config.hook.pytest_itemstart(item=item, node=node)
|
||||
tosend[:] = tosend[room:] # update inplace
|
||||
if tosend:
|
||||
# we have some left, give it to the main loop
|
||||
self.queueevent("pytest_rescheduleitems", items=tosend)
|
||||
|
||||
def senditems_load(self, tosend):
|
||||
if not tosend:
|
||||
return
|
||||
for node, pending in self.node2pending.items():
|
||||
room = self.MAXITEMSPERHOST - len(pending)
|
||||
if room > 0:
|
||||
sending = tosend[:room]
|
||||
node.sendlist(sending)
|
||||
for item in sending:
|
||||
#assert item not in self.item2node, (
|
||||
# "sending same item %r to multiple "
|
||||
# "not implemented" %(item,))
|
||||
self.item2nodes.setdefault(item, []).append(node)
|
||||
self.config.hook.pytest_itemstart(item=item, node=node)
|
||||
pending.extend(sending)
|
||||
tosend[:] = tosend[room:] # update inplace
|
||||
if not tosend:
|
||||
break
|
||||
if tosend:
|
||||
# we have some left, give it to the main loop
|
||||
self.queueevent("pytest_rescheduleitems", items=tosend)
|
||||
|
||||
def removeitem(self, item, node):
|
||||
if item not in self.item2nodes:
|
||||
raise AssertionError(item, self.item2nodes)
|
||||
nodes = self.item2nodes[item]
|
||||
if node in nodes: # the node might have gone down already
|
||||
nodes.remove(node)
|
||||
if not nodes:
|
||||
del self.item2nodes[item]
|
||||
pending = self.node2pending[node]
|
||||
pending.remove(item)
|
||||
|
||||
def handle_crashitem(self, item, node):
|
||||
runner = item.config.pluginmanager.getplugin("runner")
|
||||
info = "!!! Node %r crashed during running of test %r" %(node, item)
|
||||
rep = runner.ItemTestReport(item=item, excinfo=info, when="???")
|
||||
rep.node = node
|
||||
self.config.hook.pytest_runtest_logreport(report=rep)
|
||||
|
||||
def setup(self):
|
||||
""" setup any neccessary resources ahead of the test run. """
|
||||
self.nodemanager = NodeManager(self.config)
|
||||
self.nodemanager.setup_nodes(putevent=self.queue.put)
|
||||
if self.config.option.dist == "each":
|
||||
self.nodemanager.wait_nodesready(5.0)
|
||||
|
||||
def teardown(self):
|
||||
""" teardown any resources after a test run. """
|
||||
self.nodemanager.teardown_nodes()
|
||||
124
py/test/dist/gwmanage.py
vendored
124
py/test/dist/gwmanage.py
vendored
@@ -1,124 +0,0 @@
|
||||
"""
|
||||
instantiating, managing and rsyncing to test hosts
|
||||
"""
|
||||
|
||||
import py
|
||||
import sys, os
|
||||
import execnet
|
||||
from execnet.gateway_base import RemoteError
|
||||
|
||||
class GatewayManager:
|
||||
RemoteError = RemoteError
|
||||
def __init__(self, specs, hook, defaultchdir="pyexecnetcache"):
|
||||
self.gateways = []
|
||||
self.specs = []
|
||||
self.hook = hook
|
||||
for spec in specs:
|
||||
if not isinstance(spec, execnet.XSpec):
|
||||
spec = execnet.XSpec(spec)
|
||||
if not spec.chdir and not spec.popen:
|
||||
spec.chdir = defaultchdir
|
||||
self.specs.append(spec)
|
||||
|
||||
def makegateways(self):
|
||||
assert not self.gateways
|
||||
for spec in self.specs:
|
||||
gw = execnet.makegateway(spec)
|
||||
self.gateways.append(gw)
|
||||
gw.id = "[%s]" % len(self.gateways)
|
||||
self.hook.pytest_gwmanage_newgateway(
|
||||
gateway=gw, platinfo=gw._rinfo())
|
||||
|
||||
def getgateways(self, remote=True, inplacelocal=True):
|
||||
if not self.gateways and self.specs:
|
||||
self.makegateways()
|
||||
l = []
|
||||
for gw in self.gateways:
|
||||
if gw.spec._samefilesystem():
|
||||
if inplacelocal:
|
||||
l.append(gw)
|
||||
else:
|
||||
if remote:
|
||||
l.append(gw)
|
||||
return execnet.MultiGateway(gateways=l)
|
||||
|
||||
def multi_exec(self, source, inplacelocal=True):
|
||||
""" remote execute code on all gateways.
|
||||
@param inplacelocal=False: don't send code to inplacelocal hosts.
|
||||
"""
|
||||
multigw = self.getgateways(inplacelocal=inplacelocal)
|
||||
return multigw.remote_exec(source)
|
||||
|
||||
def multi_chdir(self, basename, inplacelocal=True):
|
||||
""" perform a remote chdir to the given path, may be relative.
|
||||
@param inplacelocal=False: don't send code to inplacelocal hosts.
|
||||
"""
|
||||
self.multi_exec("import os ; os.chdir(%r)" % basename,
|
||||
inplacelocal=inplacelocal).waitclose()
|
||||
|
||||
def rsync(self, source, notify=None, verbose=False, ignores=None):
|
||||
""" perform rsync to all remote hosts.
|
||||
"""
|
||||
rsync = HostRSync(source, verbose=verbose, ignores=ignores)
|
||||
seen = py.builtin.set()
|
||||
gateways = []
|
||||
for gateway in self.gateways:
|
||||
spec = gateway.spec
|
||||
if not spec._samefilesystem():
|
||||
if spec not in seen:
|
||||
def finished():
|
||||
if notify:
|
||||
notify("rsyncrootready", spec, source)
|
||||
rsync.add_target_host(gateway, finished=finished)
|
||||
seen.add(spec)
|
||||
gateways.append(gateway)
|
||||
if seen:
|
||||
self.hook.pytest_gwmanage_rsyncstart(
|
||||
source=source,
|
||||
gateways=gateways,
|
||||
)
|
||||
rsync.send()
|
||||
self.hook.pytest_gwmanage_rsyncfinish(
|
||||
source=source,
|
||||
gateways=gateways,
|
||||
)
|
||||
|
||||
def exit(self):
|
||||
while self.gateways:
|
||||
gw = self.gateways.pop()
|
||||
gw.exit()
|
||||
|
||||
class HostRSync(execnet.RSync):
|
||||
""" RSyncer that filters out common files
|
||||
"""
|
||||
def __init__(self, sourcedir, *args, **kwargs):
|
||||
self._synced = {}
|
||||
ignores= None
|
||||
if 'ignores' in kwargs:
|
||||
ignores = kwargs.pop('ignores')
|
||||
self._ignores = ignores or []
|
||||
super(HostRSync, self).__init__(sourcedir=sourcedir, **kwargs)
|
||||
|
||||
def filter(self, path):
|
||||
path = py.path.local(path)
|
||||
if not path.ext in ('.pyc', '.pyo'):
|
||||
if not path.basename.endswith('~'):
|
||||
if path.check(dotfile=0):
|
||||
for x in self._ignores:
|
||||
if path == x:
|
||||
break
|
||||
else:
|
||||
return True
|
||||
|
||||
def add_target_host(self, gateway, finished=None):
|
||||
remotepath = os.path.basename(self._sourcedir)
|
||||
super(HostRSync, self).add_target(gateway, remotepath,
|
||||
finishedcallback=finished,
|
||||
delete=True,)
|
||||
|
||||
def _report_send_file(self, gateway, modified_rel_path):
|
||||
if self._verbose:
|
||||
path = os.path.basename(self._sourcedir) + "/" + modified_rel_path
|
||||
remotepath = gateway.spec.chdir
|
||||
py.builtin.print_('%s:%s <= %s' %
|
||||
(gateway.spec, remotepath, path))
|
||||
191
py/test/dist/mypickle.py
vendored
191
py/test/dist/mypickle.py
vendored
@@ -1,191 +0,0 @@
|
||||
"""
|
||||
|
||||
Pickling support for two processes that want to exchange
|
||||
*immutable* object instances. Immutable in the sense
|
||||
that the receiving side of an object can modify its
|
||||
copy but when it sends it back the original sending
|
||||
side will continue to see its unmodified version
|
||||
(and no actual state will go over the wire).
|
||||
|
||||
This module also implements an experimental
|
||||
execnet pickling channel using this idea.
|
||||
|
||||
"""
|
||||
|
||||
import py
|
||||
import sys, os, struct
|
||||
#debug = open("log-mypickle-%d" % os.getpid(), 'w')
|
||||
|
||||
if sys.version_info >= (3,0):
|
||||
makekey = lambda x: x
|
||||
fromkey = lambda x: x
|
||||
from pickle import _Pickler as Pickler
|
||||
from pickle import _Unpickler as Unpickler
|
||||
else:
|
||||
makekey = str
|
||||
fromkey = int
|
||||
from pickle import Pickler, Unpickler
|
||||
|
||||
|
||||
class MyPickler(Pickler):
|
||||
""" Pickler with a custom memoize()
|
||||
to take care of unique ID creation.
|
||||
See the usage in ImmutablePickler
|
||||
XXX we could probably extend Pickler
|
||||
and Unpickler classes to directly
|
||||
update the other'S memos.
|
||||
"""
|
||||
def __init__(self, file, protocol, uneven):
|
||||
Pickler.__init__(self, file, protocol)
|
||||
self.uneven = uneven
|
||||
|
||||
def memoize(self, obj):
|
||||
if self.fast:
|
||||
return
|
||||
assert id(obj) not in self.memo
|
||||
memo_len = len(self.memo)
|
||||
key = memo_len * 2 + self.uneven
|
||||
self.write(self.put(key))
|
||||
self.memo[id(obj)] = key, obj
|
||||
|
||||
#if sys.version_info < (3,0):
|
||||
# def save_string(self, obj, pack=struct.pack):
|
||||
# obj = unicode(obj)
|
||||
# self.save_unicode(obj, pack=pack)
|
||||
# Pickler.dispatch[str] = save_string
|
||||
|
||||
class ImmutablePickler:
|
||||
def __init__(self, uneven, protocol=0):
|
||||
""" ImmutablePicklers are instantiated in Pairs.
|
||||
The two sides need to create unique IDs
|
||||
while pickling their objects. This is
|
||||
done by using either even or uneven
|
||||
numbers, depending on the instantiation
|
||||
parameter.
|
||||
"""
|
||||
self._picklememo = {}
|
||||
self._unpicklememo = {}
|
||||
self._protocol = protocol
|
||||
self.uneven = uneven and 1 or 0
|
||||
|
||||
def selfmemoize(self, obj):
|
||||
# this is for feeding objects to ourselfes
|
||||
# which be the case e.g. if you want to pickle
|
||||
# from a forked process back to the original
|
||||
f = py.io.BytesIO()
|
||||
pickler = MyPickler(f, self._protocol, uneven=self.uneven)
|
||||
pickler.memo = self._picklememo
|
||||
pickler.memoize(obj)
|
||||
self._updateunpicklememo()
|
||||
|
||||
def dumps(self, obj):
|
||||
f = py.io.BytesIO()
|
||||
pickler = MyPickler(f, self._protocol, uneven=self.uneven)
|
||||
pickler.memo = self._picklememo
|
||||
pickler.dump(obj)
|
||||
if obj is not None:
|
||||
self._updateunpicklememo()
|
||||
#print >>debug, "dumped", obj
|
||||
#print >>debug, "picklememo", self._picklememo
|
||||
return f.getvalue()
|
||||
|
||||
def loads(self, string):
|
||||
f = py.io.BytesIO(string)
|
||||
unpickler = Unpickler(f)
|
||||
unpickler.memo = self._unpicklememo
|
||||
res = unpickler.load()
|
||||
self._updatepicklememo()
|
||||
#print >>debug, "loaded", res
|
||||
#print >>debug, "unpicklememo", self._unpicklememo
|
||||
return res
|
||||
|
||||
def _updatepicklememo(self):
|
||||
for x, obj in self._unpicklememo.items():
|
||||
self._picklememo[id(obj)] = (fromkey(x), obj)
|
||||
|
||||
def _updateunpicklememo(self):
|
||||
for key,obj in self._picklememo.values():
|
||||
key = makekey(key)
|
||||
if key in self._unpicklememo:
|
||||
assert self._unpicklememo[key] is obj
|
||||
self._unpicklememo[key] = obj
|
||||
|
||||
NO_ENDMARKER_WANTED = object()
|
||||
|
||||
class UnpickleError(Exception):
|
||||
""" Problems while unpickling. """
|
||||
def __init__(self, formatted):
|
||||
self.formatted = formatted
|
||||
Exception.__init__(self, formatted)
|
||||
def __str__(self):
|
||||
return self.formatted
|
||||
|
||||
class PickleChannel(object):
|
||||
""" PickleChannels wrap execnet channels
|
||||
and allow to send/receive by using
|
||||
"immutable pickling".
|
||||
"""
|
||||
_unpicklingerror = None
|
||||
def __init__(self, channel):
|
||||
self._channel = channel
|
||||
# we use the fact that each side of a
|
||||
# gateway connection counts with uneven
|
||||
# or even numbers depending on which
|
||||
# side it is (for the purpose of creating
|
||||
# unique ids - which is what we need it here for)
|
||||
uneven = channel.gateway._channelfactory.count % 2
|
||||
self._ipickle = ImmutablePickler(uneven=uneven)
|
||||
self.RemoteError = channel.RemoteError
|
||||
|
||||
def send(self, obj):
|
||||
from execnet.gateway_base import Channel
|
||||
if not isinstance(obj, Channel):
|
||||
pickled_obj = self._ipickle.dumps(obj)
|
||||
self._channel.send(pickled_obj)
|
||||
else:
|
||||
self._channel.send(obj)
|
||||
|
||||
def receive(self):
|
||||
pickled_obj = self._channel.receive()
|
||||
return self._unpickle(pickled_obj)
|
||||
|
||||
def _unpickle(self, pickled_obj):
|
||||
if isinstance(pickled_obj, self._channel.__class__):
|
||||
return pickled_obj
|
||||
return self._ipickle.loads(pickled_obj)
|
||||
|
||||
def _getremoteerror(self):
|
||||
return self._unpicklingerror or self._channel._getremoteerror()
|
||||
|
||||
def close(self):
|
||||
return self._channel.close()
|
||||
|
||||
def isclosed(self):
|
||||
return self._channel.isclosed()
|
||||
|
||||
def waitclose(self, timeout=None):
|
||||
return self._channel.waitclose(timeout=timeout)
|
||||
|
||||
def setcallback(self, callback, endmarker=NO_ENDMARKER_WANTED):
|
||||
if endmarker is NO_ENDMARKER_WANTED:
|
||||
def unpickle_callback(pickled_obj):
|
||||
obj = self._unpickle(pickled_obj)
|
||||
callback(obj)
|
||||
self._channel.setcallback(unpickle_callback)
|
||||
return
|
||||
uniqueendmarker = object()
|
||||
def unpickle_callback(pickled_obj):
|
||||
if pickled_obj is uniqueendmarker:
|
||||
return callback(endmarker)
|
||||
try:
|
||||
obj = self._unpickle(pickled_obj)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
excinfo = py.code.ExceptionInfo()
|
||||
formatted = str(excinfo.getrepr(showlocals=True,funcargs=True))
|
||||
self._unpicklingerror = UnpickleError(formatted)
|
||||
callback(endmarker)
|
||||
else:
|
||||
callback(obj)
|
||||
self._channel.setcallback(unpickle_callback, uniqueendmarker)
|
||||
81
py/test/dist/nodemanage.py
vendored
81
py/test/dist/nodemanage.py
vendored
@@ -1,81 +0,0 @@
|
||||
import py
|
||||
import sys, os
|
||||
from py.__.test.dist.txnode import TXNode
|
||||
from py.__.test.dist.gwmanage import GatewayManager
|
||||
|
||||
|
||||
class NodeManager(object):
|
||||
def __init__(self, config, specs=None):
|
||||
self.config = config
|
||||
if specs is None:
|
||||
specs = self.config.getxspecs()
|
||||
self.roots = self.config.getrsyncdirs()
|
||||
self.gwmanager = GatewayManager(specs, config.hook)
|
||||
self.nodes = []
|
||||
self._nodesready = py.std.threading.Event()
|
||||
|
||||
def trace(self, msg):
|
||||
self.config.hook.pytest_trace(category="nodemanage", msg=msg)
|
||||
|
||||
def config_getignores(self):
|
||||
return self.config.getconftest_pathlist("rsyncignore")
|
||||
|
||||
def rsync_roots(self):
|
||||
""" make sure that all remote gateways
|
||||
have the same set of roots in their
|
||||
current directory.
|
||||
"""
|
||||
self.makegateways()
|
||||
options = {
|
||||
'ignores': self.config_getignores(),
|
||||
'verbose': self.config.option.verbose,
|
||||
}
|
||||
if self.roots:
|
||||
# send each rsync root
|
||||
for root in self.roots:
|
||||
self.gwmanager.rsync(root, **options)
|
||||
else:
|
||||
XXX # do we want to care for situations without explicit rsyncdirs?
|
||||
# we transfer our topdir as the root
|
||||
self.gwmanager.rsync(self.config.topdir, **options)
|
||||
# and cd into it
|
||||
self.gwmanager.multi_chdir(self.config.topdir.basename, inplacelocal=False)
|
||||
|
||||
def makegateways(self):
|
||||
# we change to the topdir sot that
|
||||
# PopenGateways will have their cwd
|
||||
# such that unpickling configs will
|
||||
# pick it up as the right topdir
|
||||
# (for other gateways this chdir is irrelevant)
|
||||
self.trace("making gateways")
|
||||
old = self.config.topdir.chdir()
|
||||
try:
|
||||
self.gwmanager.makegateways()
|
||||
finally:
|
||||
old.chdir()
|
||||
|
||||
def setup_nodes(self, putevent):
|
||||
self.rsync_roots()
|
||||
self.trace("setting up nodes")
|
||||
for gateway in self.gwmanager.gateways:
|
||||
node = TXNode(gateway, self.config, putevent, slaveready=self._slaveready)
|
||||
gateway.node = node # to keep node alive
|
||||
self.trace("started node %r" % node)
|
||||
|
||||
def _slaveready(self, node):
|
||||
#assert node.gateway == node.gateway
|
||||
#assert node.gateway.node == node
|
||||
self.nodes.append(node)
|
||||
self.trace("%s slave node ready %r" % (node.gateway.id, node))
|
||||
if len(self.nodes) == len(self.gwmanager.gateways):
|
||||
self._nodesready.set()
|
||||
|
||||
def wait_nodesready(self, timeout=None):
|
||||
self._nodesready.wait(timeout)
|
||||
if not self._nodesready.isSet():
|
||||
raise IOError("nodes did not get ready for %r secs" % timeout)
|
||||
|
||||
def teardown_nodes(self):
|
||||
# XXX do teardown nodes?
|
||||
self.gwmanager.exit()
|
||||
|
||||
153
py/test/dist/txnode.py
vendored
153
py/test/dist/txnode.py
vendored
@@ -1,153 +0,0 @@
|
||||
"""
|
||||
Manage setup, running and local representation of remote nodes/processes.
|
||||
"""
|
||||
import py
|
||||
from py.__.test.dist.mypickle import PickleChannel
|
||||
|
||||
class TXNode(object):
|
||||
""" Represents a Test Execution environment in the controlling process.
|
||||
- sets up a slave node through an execnet gateway
|
||||
- manages sending of test-items and receival of results and events
|
||||
- creates events when the remote side crashes
|
||||
"""
|
||||
ENDMARK = -1
|
||||
|
||||
def __init__(self, gateway, config, putevent, slaveready=None):
|
||||
self.config = config
|
||||
self.putevent = putevent
|
||||
self.gateway = gateway
|
||||
self.channel = install_slave(gateway, config)
|
||||
self._sendslaveready = slaveready
|
||||
self.channel.setcallback(self.callback, endmarker=self.ENDMARK)
|
||||
self._down = False
|
||||
|
||||
def __repr__(self):
|
||||
id = self.gateway.id
|
||||
status = self._down and 'true' or 'false'
|
||||
return "<TXNode %r down=%s>" %(id, status)
|
||||
|
||||
def notify(self, eventname, *args, **kwargs):
|
||||
assert not args
|
||||
self.putevent((eventname, args, kwargs))
|
||||
|
||||
def callback(self, eventcall):
|
||||
""" this gets called for each object we receive from
|
||||
the other side and if the channel closes.
|
||||
|
||||
Note that channel callbacks run in the receiver
|
||||
thread of execnet gateways - we need to
|
||||
avoid raising exceptions or doing heavy work.
|
||||
"""
|
||||
try:
|
||||
if eventcall == self.ENDMARK:
|
||||
err = self.channel._getremoteerror()
|
||||
if not self._down:
|
||||
if not err:
|
||||
err = "Not properly terminated"
|
||||
self.notify("pytest_testnodedown", node=self, error=err)
|
||||
self._down = True
|
||||
return
|
||||
eventname, args, kwargs = eventcall
|
||||
if eventname == "slaveready":
|
||||
if self._sendslaveready:
|
||||
self._sendslaveready(self)
|
||||
self.notify("pytest_testnodeready", node=self)
|
||||
elif eventname == "slavefinished":
|
||||
self._down = True
|
||||
self.notify("pytest_testnodedown", error=None, node=self)
|
||||
elif eventname == "pytest_runtest_logreport":
|
||||
rep = kwargs['report']
|
||||
rep.node = self
|
||||
self.notify("pytest_runtest_logreport", report=rep)
|
||||
else:
|
||||
self.notify(eventname, *args, **kwargs)
|
||||
except KeyboardInterrupt:
|
||||
# should not land in receiver-thread
|
||||
raise
|
||||
except:
|
||||
excinfo = py.code.ExceptionInfo()
|
||||
py.builtin.print_("!" * 20, excinfo)
|
||||
self.config.pluginmanager.notify_exception(excinfo)
|
||||
|
||||
def send(self, item):
|
||||
assert item is not None
|
||||
self.channel.send(item)
|
||||
|
||||
def sendlist(self, itemlist):
|
||||
self.channel.send(itemlist)
|
||||
|
||||
def shutdown(self):
|
||||
self.channel.send(None)
|
||||
|
||||
# setting up slave code
|
||||
def install_slave(gateway, config):
|
||||
channel = gateway.remote_exec(source="""
|
||||
import os, sys
|
||||
sys.path.insert(0, os.getcwd())
|
||||
from py.__.test.dist.mypickle import PickleChannel
|
||||
from py.__.test.dist.txnode import SlaveNode
|
||||
channel = PickleChannel(channel)
|
||||
slavenode = SlaveNode(channel)
|
||||
slavenode.run()
|
||||
""")
|
||||
channel = PickleChannel(channel)
|
||||
basetemp = None
|
||||
if gateway.spec.popen:
|
||||
popenbase = config.ensuretemp("popen")
|
||||
basetemp = py.path.local.make_numbered_dir(prefix="slave-",
|
||||
keep=0, rootdir=popenbase)
|
||||
basetemp = str(basetemp)
|
||||
channel.send((config, basetemp))
|
||||
return channel
|
||||
|
||||
class SlaveNode(object):
|
||||
def __init__(self, channel):
|
||||
self.channel = channel
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s channel=%s>" %(self.__class__.__name__, self.channel)
|
||||
|
||||
def sendevent(self, eventname, *args, **kwargs):
|
||||
self.channel.send((eventname, args, kwargs))
|
||||
|
||||
def pytest_runtest_logreport(self, report):
|
||||
self.sendevent("pytest_runtest_logreport", report=report)
|
||||
|
||||
def run(self):
|
||||
channel = self.channel
|
||||
self.config, basetemp = channel.receive()
|
||||
if basetemp:
|
||||
self.config.basetemp = py.path.local(basetemp)
|
||||
self.config.pluginmanager.do_configure(self.config)
|
||||
self.config.pluginmanager.register(self)
|
||||
self.runner = self.config.pluginmanager.getplugin("pytest_runner")
|
||||
self.sendevent("slaveready")
|
||||
try:
|
||||
while 1:
|
||||
task = channel.receive()
|
||||
if task is None:
|
||||
self.sendevent("slavefinished")
|
||||
break
|
||||
if isinstance(task, list):
|
||||
for item in task:
|
||||
self.run_single(item=item)
|
||||
else:
|
||||
self.run_single(item=task)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
er = py.code.ExceptionInfo().getrepr(funcargs=True, showlocals=True)
|
||||
self.sendevent("pytest_internalerror", excrepr=er)
|
||||
raise
|
||||
|
||||
def run_single(self, item):
|
||||
call = self.runner.CallInfo(item._checkcollectable, when='setup')
|
||||
if call.excinfo:
|
||||
# likely it is not collectable here because of
|
||||
# platform/import-dependency induced skips
|
||||
# we fake a setup-error report with the obtained exception
|
||||
# and do not care about capturing or non-runner hooks
|
||||
rep = self.runner.pytest_runtest_makereport(item=item, call=call)
|
||||
self.pytest_runtest_logreport(rep)
|
||||
return
|
||||
item.config.hook.pytest_runtest_protocol(item=item)
|
||||
@@ -1,194 +0,0 @@
|
||||
import py
|
||||
|
||||
def getfuncargnames(function):
|
||||
argnames = py.std.inspect.getargs(py.code.getrawcode(function))[0]
|
||||
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)
|
||||
self.cls = cls
|
||||
self.module = module
|
||||
self._calls = []
|
||||
self._ids = py.builtin.set()
|
||||
|
||||
def addcall(self, funcargs=None, id=_notexists, param=_notexists):
|
||||
assert funcargs is None or isinstance(funcargs, dict)
|
||||
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 FunctionCollector(py.test.collect.Collector):
|
||||
def __init__(self, name, parent, calls):
|
||||
super(FunctionCollector, self).__init__(name, parent)
|
||||
self.calls = calls
|
||||
self.obj = getattr(self.parent.obj, name)
|
||||
|
||||
def collect(self):
|
||||
l = []
|
||||
for callspec in self.calls:
|
||||
name = "%s[%s]" %(self.name, callspec.id)
|
||||
function = self.parent.Function(name=name, parent=self,
|
||||
callspec=callspec, callobj=self.obj)
|
||||
l.append(function)
|
||||
return l
|
||||
|
||||
def reportinfo(self):
|
||||
try:
|
||||
return self._fslineno, self.name
|
||||
except AttributeError:
|
||||
pass
|
||||
fspath, lineno = py.code.getfslineno(self.obj)
|
||||
self._fslineno = fspath, lineno
|
||||
return fspath, lineno, self.name
|
||||
|
||||
|
||||
class FuncargRequest:
|
||||
_argprefix = "pytest_funcarg__"
|
||||
_argname = None
|
||||
|
||||
class Error(LookupError):
|
||||
""" error on performing funcarg request. """
|
||||
|
||||
def __init__(self, pyfuncitem):
|
||||
self._pyfuncitem = pyfuncitem
|
||||
self.function = pyfuncitem.obj
|
||||
self.module = pyfuncitem.getparent(py.test.collect.Module).obj
|
||||
clscol = pyfuncitem.getparent(py.test.collect.Class)
|
||||
self.cls = clscol and clscol.obj or None
|
||||
self.instance = py.builtin._getimself(self.function)
|
||||
self.config = pyfuncitem.config
|
||||
self.fspath = pyfuncitem.fspath
|
||||
if hasattr(pyfuncitem, '_requestparam'):
|
||||
self.param = pyfuncitem._requestparam
|
||||
self._plugins = self.config.pluginmanager.getplugins()
|
||||
self._plugins.append(self.module)
|
||||
if self.instance is not None:
|
||||
self._plugins.append(self.instance)
|
||||
self._funcargs = self._pyfuncitem.funcargs.copy()
|
||||
self._name2factory = {}
|
||||
self._currentarg = None
|
||||
|
||||
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 cached_setup(self, setup, teardown=None, scope="module", extrakey=None):
|
||||
""" cache and return result of calling setup().
|
||||
|
||||
The requested argument name, the scope and the ``extrakey``
|
||||
determine the cache key. The scope also determines when
|
||||
teardown(result) will be called. valid scopes are:
|
||||
scope == 'function': when the single test function run finishes.
|
||||
scope == 'module': when tests in a different module are run
|
||||
scope == 'session': when tests of the session have run.
|
||||
"""
|
||||
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):
|
||||
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 == "module":
|
||||
return self._pyfuncitem.getparent(py.test.collect.Module)
|
||||
elif scope == "session":
|
||||
return None
|
||||
raise ValueError("unknown finalization scope %r" %(scope,))
|
||||
|
||||
def _addfinalizer(self, finalizer, scope):
|
||||
colitem = self._getscopeitem(scope)
|
||||
self.config._setupstate.addfinalizer(
|
||||
finalizer=finalizer, colitem=colitem)
|
||||
|
||||
def addfinalizer(self, finalizer):
|
||||
""" call the given finalizer after test function finished execution. """
|
||||
self._addfinalizer(finalizer, scope="function")
|
||||
|
||||
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()
|
||||
line = "%s:%s" %(fspath, lineno)
|
||||
msg = "funcargument %r not found for: %s" %(argname, line)
|
||||
msg += "\n available funcargs: %s" %(", ".join(available),)
|
||||
raise self.Error(msg)
|
||||
@@ -1 +0,0 @@
|
||||
#
|
||||
@@ -1,159 +0,0 @@
|
||||
"""
|
||||
LooponfailingSession and Helpers.
|
||||
|
||||
NOTE that one really has to avoid loading and depending on
|
||||
application modules within the controlling process
|
||||
(the one that starts repeatedly test processes)
|
||||
otherwise changes to source code can crash
|
||||
the controlling process which should never happen.
|
||||
"""
|
||||
import py
|
||||
import sys
|
||||
import execnet
|
||||
from py.__.test.session import Session
|
||||
from py.__.test.dist.mypickle import PickleChannel
|
||||
from py.__.test.looponfail import util
|
||||
|
||||
class LooponfailingSession(Session):
|
||||
def __init__(self, config):
|
||||
super(LooponfailingSession, self).__init__(config=config)
|
||||
self.rootdirs = [self.config.topdir] # xxx dist_rsync_roots?
|
||||
self.statrecorder = util.StatRecorder(self.rootdirs)
|
||||
self.remotecontrol = RemoteControl(self.config)
|
||||
self.out = py.io.TerminalWriter()
|
||||
|
||||
def main(self, initialitems=None):
|
||||
try:
|
||||
self.loopstate = loopstate = LoopState(initialitems)
|
||||
self.remotecontrol.setup()
|
||||
while 1:
|
||||
self.loop_once(loopstate)
|
||||
if not loopstate.colitems and loopstate.wasfailing:
|
||||
continue # the last failures passed, let's rerun all
|
||||
self.statrecorder.waitonchange(checkinterval=2.0)
|
||||
except KeyboardInterrupt:
|
||||
print
|
||||
|
||||
def loop_once(self, loopstate):
|
||||
colitems = loopstate.colitems
|
||||
loopstate.wasfailing = colitems and len(colitems)
|
||||
loopstate.colitems = self.remotecontrol.runsession(colitems or ())
|
||||
self.remotecontrol.setup()
|
||||
|
||||
class LoopState:
|
||||
def __init__(self, colitems=None):
|
||||
self.colitems = colitems
|
||||
|
||||
class RemoteControl(object):
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
|
||||
def trace(self, *args):
|
||||
if self.config.option.debug:
|
||||
msg = " ".join([str(x) for x in args])
|
||||
py.builtin.print_("RemoteControl:", msg)
|
||||
|
||||
def initgateway(self):
|
||||
return execnet.PopenGateway()
|
||||
|
||||
def setup(self, out=None):
|
||||
if out is None:
|
||||
out = py.io.TerminalWriter()
|
||||
if hasattr(self, 'gateway'):
|
||||
raise ValueError("already have gateway %r" % self.gateway)
|
||||
self.trace("setting up slave session")
|
||||
old = self.config.topdir.chdir()
|
||||
try:
|
||||
self.gateway = self.initgateway()
|
||||
finally:
|
||||
old.chdir()
|
||||
channel = self.gateway.remote_exec(source="""
|
||||
from py.__.test.dist.mypickle import PickleChannel
|
||||
from py.__.test.looponfail.remote import slave_runsession
|
||||
outchannel = channel.gateway.newchannel()
|
||||
channel.send(outchannel)
|
||||
channel = PickleChannel(channel)
|
||||
config, fullwidth, hasmarkup = channel.receive()
|
||||
import sys
|
||||
sys.stdout = sys.stderr = outchannel.makefile('w')
|
||||
slave_runsession(channel, config, fullwidth, hasmarkup)
|
||||
""")
|
||||
remote_outchannel = channel.receive()
|
||||
remote_outchannel.setcallback(out._file.write)
|
||||
channel = self.channel = PickleChannel(channel)
|
||||
channel.send((self.config, out.fullwidth, out.hasmarkup))
|
||||
self.trace("set up of slave session complete")
|
||||
|
||||
def ensure_teardown(self):
|
||||
if hasattr(self, 'channel'):
|
||||
if not self.channel.isclosed():
|
||||
self.trace("closing", self.channel)
|
||||
self.channel.close()
|
||||
del self.channel
|
||||
if hasattr(self, 'gateway'):
|
||||
self.trace("exiting", self.gateway)
|
||||
self.gateway.exit()
|
||||
del self.gateway
|
||||
|
||||
def runsession(self, colitems=()):
|
||||
try:
|
||||
self.trace("sending", colitems)
|
||||
trails = colitems
|
||||
self.channel.send(trails)
|
||||
try:
|
||||
return self.channel.receive()
|
||||
except self.channel.RemoteError:
|
||||
e = sys.exc_info()[1]
|
||||
self.trace("ERROR", e)
|
||||
raise
|
||||
finally:
|
||||
self.ensure_teardown()
|
||||
|
||||
def slave_runsession(channel, config, fullwidth, hasmarkup):
|
||||
""" we run this on the other side. """
|
||||
if config.option.debug:
|
||||
def DEBUG(*args):
|
||||
print(" ".join(map(str, args)))
|
||||
else:
|
||||
def DEBUG(*args): pass
|
||||
|
||||
DEBUG("SLAVE: received configuration, using topdir:", config.topdir)
|
||||
#config.option.session = None
|
||||
config.option.looponfail = False
|
||||
config.option.usepdb = False
|
||||
trails = channel.receive()
|
||||
config.pluginmanager.do_configure(config)
|
||||
DEBUG("SLAVE: initsession()")
|
||||
session = config.initsession()
|
||||
# XXX configure the reporter object's terminal writer more directly
|
||||
# XXX and write a test for this remote-terminal setting logic
|
||||
config.pytest_terminal_hasmarkup = hasmarkup
|
||||
config.pytest_terminal_fullwidth = fullwidth
|
||||
if trails:
|
||||
colitems = []
|
||||
for trail in trails:
|
||||
try:
|
||||
colitem = py.test.collect.Collector._fromtrail(trail, config)
|
||||
except AssertionError:
|
||||
#XXX send info for "test disappeared" or so
|
||||
continue
|
||||
colitems.append(colitem)
|
||||
else:
|
||||
colitems = None
|
||||
session.shouldclose = channel.isclosed
|
||||
|
||||
class Failures(list):
|
||||
def pytest_runtest_logreport(self, report):
|
||||
if report.failed:
|
||||
self.append(report)
|
||||
pytest_collectreport = pytest_runtest_logreport
|
||||
|
||||
failreports = Failures()
|
||||
session.pluginmanager.register(failreports)
|
||||
|
||||
DEBUG("SLAVE: starting session.main()")
|
||||
session.main(colitems)
|
||||
session.config.hook.pytest_looponfailinfo(
|
||||
failreports=list(failreports),
|
||||
rootdirs=[config.topdir])
|
||||
channel.send([rep.getnode()._totrail() for rep in failreports])
|
||||
@@ -1,53 +0,0 @@
|
||||
import py
|
||||
|
||||
class StatRecorder:
|
||||
def __init__(self, rootdirlist):
|
||||
self.rootdirlist = rootdirlist
|
||||
self.statcache = {}
|
||||
self.check() # snapshot state
|
||||
|
||||
def fil(self, p):
|
||||
return p.ext in ('.py', '.txt', '.c', '.h')
|
||||
def rec(self, p):
|
||||
return p.check(dotfile=0)
|
||||
|
||||
def waitonchange(self, checkinterval=1.0):
|
||||
while 1:
|
||||
changed = self.check()
|
||||
if changed:
|
||||
return
|
||||
py.std.time.sleep(checkinterval)
|
||||
|
||||
def check(self, removepycfiles=True):
|
||||
changed = False
|
||||
statcache = self.statcache
|
||||
newstat = {}
|
||||
for rootdir in self.rootdirlist:
|
||||
for path in rootdir.visit(self.fil, self.rec):
|
||||
oldstat = statcache.get(path, None)
|
||||
if oldstat is not None:
|
||||
del statcache[path]
|
||||
try:
|
||||
newstat[path] = curstat = path.stat()
|
||||
except py.error.ENOENT:
|
||||
if oldstat:
|
||||
del statcache[path]
|
||||
changed = True
|
||||
else:
|
||||
if oldstat:
|
||||
if oldstat.mtime != curstat.mtime or \
|
||||
oldstat.size != curstat.size:
|
||||
changed = True
|
||||
py.builtin.print_("# MODIFIED", path)
|
||||
if removepycfiles and path.ext == ".py":
|
||||
pycfile = path + "c"
|
||||
if pycfile.check():
|
||||
pycfile.remove()
|
||||
|
||||
else:
|
||||
changed = True
|
||||
if statcache:
|
||||
changed = True
|
||||
self.statcache = newstat
|
||||
return changed
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
"""
|
||||
Test OutcomeExceptions and helpers for creating them.
|
||||
py.test.skip|fail|raises helper implementations
|
||||
|
||||
"""
|
||||
|
||||
import py
|
||||
import sys
|
||||
|
||||
class OutcomeException(Exception):
|
||||
""" OutcomeException and its subclass instances indicate and
|
||||
contain info about test and collection outcomes.
|
||||
"""
|
||||
def __init__(self, msg=None, excinfo=None):
|
||||
self.msg = msg
|
||||
self.excinfo = excinfo
|
||||
|
||||
def __repr__(self):
|
||||
if self.msg:
|
||||
return repr(self.msg)
|
||||
return "<%s instance>" %(self.__class__.__name__,)
|
||||
__str__ = __repr__
|
||||
|
||||
class Passed(OutcomeException):
|
||||
pass
|
||||
|
||||
class Skipped(OutcomeException):
|
||||
# XXX slighly hackish: on 3k we fake to live in the builtins
|
||||
# in order to have Skipped exception printing shorter/nicer
|
||||
__module__ = 'builtins'
|
||||
|
||||
class Failed(OutcomeException):
|
||||
pass
|
||||
|
||||
class ExceptionFailure(Failed):
|
||||
def __init__(self, expr, expected, msg=None, excinfo=None):
|
||||
Failed.__init__(self, msg=msg, excinfo=excinfo)
|
||||
self.expr = expr
|
||||
self.expected = expected
|
||||
|
||||
class Exit(KeyboardInterrupt):
|
||||
""" for immediate program exits without tracebacks and reporter/summary. """
|
||||
def __init__(self, msg="unknown reason"):
|
||||
self.msg = msg
|
||||
KeyboardInterrupt.__init__(self, msg)
|
||||
|
||||
# exposed helper methods
|
||||
|
||||
def exit(msg):
|
||||
""" exit testing process immediately. """
|
||||
__tracebackhide__ = True
|
||||
raise Exit(msg)
|
||||
|
||||
def skip(msg=""):
|
||||
""" skip with the given message. """
|
||||
__tracebackhide__ = True
|
||||
raise Skipped(msg=msg)
|
||||
|
||||
def importorskip(modname, minversion=None):
|
||||
""" return imported module or skip() """
|
||||
compile(modname, '', 'eval') # to catch syntaxerrors
|
||||
try:
|
||||
mod = __import__(modname)
|
||||
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
|
||||
|
||||
def fail(msg="unknown failure"):
|
||||
""" fail with the given Message. """
|
||||
__tracebackhide__ = True
|
||||
raise Failed(msg=msg)
|
||||
|
||||
def raises(ExpectedException, *args, **kwargs):
|
||||
""" raise AssertionError, if target code does not raise the expected
|
||||
exception.
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
assert args
|
||||
if 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)' %(func.__name__, args, k)
|
||||
raise ExceptionFailure(msg="DID NOT RAISE",
|
||||
expr=args, expected=ExpectedException)
|
||||
|
||||
|
||||
# exitcodes for the command line
|
||||
EXIT_OK = 0
|
||||
EXIT_TESTSFAILED = 1
|
||||
EXIT_INTERRUPTED = 2
|
||||
EXIT_INTERNALERROR = 3
|
||||
EXIT_NOHOSTS = 4
|
||||
@@ -1,100 +0,0 @@
|
||||
"""
|
||||
thin wrapper around Python's optparse.py
|
||||
adding some extra checks and ways to systematically
|
||||
have Environment variables provide default values
|
||||
for options. basic usage:
|
||||
|
||||
>>> parser = Parser()
|
||||
>>> parser.addoption("--hello", action="store_true", dest="hello")
|
||||
>>> option, args = parser.parse(['--hello'])
|
||||
>>> option.hello
|
||||
True
|
||||
>>> args
|
||||
[]
|
||||
|
||||
"""
|
||||
import py
|
||||
import optparse
|
||||
|
||||
class Parser:
|
||||
""" Parser for command line arguments. """
|
||||
|
||||
def __init__(self, usage=None, processopt=None):
|
||||
self._anonymous = OptionGroup("custom options", parser=self)
|
||||
self._groups = [self._anonymous]
|
||||
self._processopt = processopt
|
||||
self._usage = usage
|
||||
self.epilog = ""
|
||||
|
||||
def processoption(self, option):
|
||||
if self._processopt:
|
||||
if option.dest:
|
||||
self._processopt(option)
|
||||
|
||||
def addnote(self, note):
|
||||
self._notes.append(note)
|
||||
|
||||
def addgroup(self, name, description=""):
|
||||
for group in self._groups:
|
||||
if group.name == name:
|
||||
raise ValueError("group %r already exists" % name)
|
||||
group = OptionGroup(name, description, parser=self)
|
||||
self._groups.append(group)
|
||||
return group
|
||||
|
||||
def getgroup(self, name, description=""):
|
||||
for group in self._groups:
|
||||
if group.name == name:
|
||||
return group
|
||||
return self.addgroup(name, description)
|
||||
|
||||
def addoption(self, *opts, **attrs):
|
||||
""" add an optparse-style option. """
|
||||
self._anonymous.addoption(*opts, **attrs)
|
||||
|
||||
def parse(self, args):
|
||||
optparser = optparse.OptionParser(usage=self._usage)
|
||||
# make sure anaonymous group is at the end
|
||||
optparser.epilog = self.epilog
|
||||
groups = self._groups[1:] + [self._groups[0]]
|
||||
for group in groups:
|
||||
if group.options:
|
||||
desc = group.description or group.name
|
||||
optgroup = optparse.OptionGroup(optparser, desc)
|
||||
optgroup.add_options(group.options)
|
||||
optparser.add_option_group(optgroup)
|
||||
return 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
|
||||
|
||||
|
||||
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 = optparse.Option(*optnames, **attrs)
|
||||
self._addoption_instance(option, shortupper=False)
|
||||
|
||||
def _addoption(self, *optnames, **attrs):
|
||||
option = 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)
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
#
|
||||
@@ -1,175 +0,0 @@
|
||||
"""
|
||||
hook specifications for py.test plugins
|
||||
"""
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Command line and configuration
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
def pytest_addoption(parser):
|
||||
""" called before commandline parsing. """
|
||||
|
||||
def pytest_namespace():
|
||||
""" return dict of name->object which will get stored at py.test. namespace"""
|
||||
|
||||
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. """
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# collection hooks
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
def pytest_collect_directory(path, parent):
|
||||
""" return Collection node or None for the given path. """
|
||||
|
||||
def pytest_collect_file(path, parent):
|
||||
""" return Collection node or None for the given path. """
|
||||
|
||||
def pytest_collectstart(collector):
|
||||
""" collector starts collecting. """
|
||||
|
||||
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 a collection and return a collection. """
|
||||
pytest_make_collect_report.firstresult = True
|
||||
|
||||
# XXX rename to item_collected()? meaning in distribution context?
|
||||
def pytest_itemstart(item, node=None):
|
||||
""" test item gets collected. """
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Python test function related hooks
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
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):
|
||||
""" perform function call to the with the given function arguments. """
|
||||
pytest_pyfunc_call.firstresult = True
|
||||
|
||||
def pytest_generate_tests(metafunc):
|
||||
""" generate (multiple) parametrized calls to a test function."""
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# generic runtest related hooks
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
def pytest_runtest_protocol(item):
|
||||
""" implement fixture, run and report protocol. """
|
||||
pytest_runtest_protocol.firstresult = True
|
||||
|
||||
def pytest_runtest_setup(item):
|
||||
""" called before pytest_runtest_call(). """
|
||||
|
||||
def pytest_runtest_call(item):
|
||||
""" execute test item. """
|
||||
|
||||
def pytest_runtest_teardown(item):
|
||||
""" called after pytest_runtest_call(). """
|
||||
|
||||
def pytest_runtest_makereport(item, call):
|
||||
""" make ItemTestReport for the given item and call outcome. """
|
||||
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):
|
||||
""" 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 influencing reporting (invoked from pytest_terminal)
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
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. """
|
||||
|
||||
def pytest_report_iteminfo(item):
|
||||
""" return (fspath, lineno, name) for the item.
|
||||
the information is used for result display and to sort tests
|
||||
"""
|
||||
pytest_report_iteminfo.firstresult = True
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# doctest hooks
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
def pytest_doctest_prepare_content(content):
|
||||
""" return processed content for a given doctest"""
|
||||
pytest_doctest_prepare_content.firstresult = True
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# distributed testing
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
def pytest_gwmanage_newgateway(gateway, platinfo):
|
||||
""" called on new raw gateway creation. """
|
||||
|
||||
def pytest_gwmanage_rsyncstart(source, gateways):
|
||||
""" called before rsyncing a directory to remote gateways takes place. """
|
||||
|
||||
def pytest_gwmanage_rsyncfinish(source, gateways):
|
||||
""" called after rsyncing a directory to remote gateways takes place. """
|
||||
|
||||
def pytest_testnodeready(node):
|
||||
""" Test Node is ready to operate. """
|
||||
|
||||
def pytest_testnodedown(node, error):
|
||||
""" Test Node is down. """
|
||||
|
||||
def pytest_rescheduleitems(items):
|
||||
""" reschedule Items from a node that went down. """
|
||||
|
||||
def pytest_looponfailinfo(failreports, rootdirs):
|
||||
""" info for repeating failing tests. """
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# error handling and internal debugging hooks
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
def pytest_plugin_registered(plugin):
|
||||
""" 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. """
|
||||
|
||||
def pytest_trace(category, msg):
|
||||
""" called for debug info. """
|
||||
@@ -1,101 +0,0 @@
|
||||
import py
|
||||
|
||||
def pytest_funcarg___pytest(request):
|
||||
return PytestArg(request)
|
||||
|
||||
class PytestArg:
|
||||
def __init__(self, request):
|
||||
self.request = request
|
||||
self.monkeypatch = self.request.getfuncargvalue("monkeypatch")
|
||||
self.comregistry = py._com.Registry()
|
||||
self.monkeypatch.setattr(py._com, 'comregistry', self.comregistry)
|
||||
|
||||
def gethookrecorder(self, hookspecs, registry=None):
|
||||
if registry is not None:
|
||||
self.monkeypatch.setattr(py._com, 'comregistry', registry)
|
||||
self.comregistry = registry
|
||||
hookrecorder = HookRecorder(self.comregistry)
|
||||
hookrecorder.start_recording(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, comregistry):
|
||||
self._comregistry = comregistry
|
||||
self.calls = []
|
||||
self._recorders = {}
|
||||
|
||||
def start_recording(self, hookspecs):
|
||||
assert hookspecs not in self._recorders
|
||||
class RecordCalls:
|
||||
_recorder = self
|
||||
for name, method in vars(hookspecs).items():
|
||||
if name[0] != "_":
|
||||
setattr(RecordCalls, name, self._makecallparser(method))
|
||||
recorder = RecordCalls()
|
||||
self._recorders[hookspecs] = recorder
|
||||
self._comregistry.register(recorder)
|
||||
self.hook = py._com.HookRelay(hookspecs, registry=self._comregistry)
|
||||
|
||||
def finish_recording(self):
|
||||
for recorder in self._recorders.values():
|
||||
self._comregistry.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 popcall(self, name):
|
||||
for i, call in enumerate(self.calls):
|
||||
if call._name == name:
|
||||
del self.calls[i]
|
||||
return call
|
||||
raise ValueError("could not find call %r" %(name, ))
|
||||
|
||||
def getcall(self, name):
|
||||
l = self.getcalls(name)
|
||||
assert len(l) == 1, (name, l)
|
||||
return l[0]
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import py
|
||||
import sys
|
||||
|
||||
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):
|
||||
if not config.getvalue("noassert") and not config.getvalue("nomagic"):
|
||||
warn_about_missing_assertion()
|
||||
config._oldassertion = py.builtin.builtins.AssertionError
|
||||
py.builtin.builtins.AssertionError = py.code._AssertionError
|
||||
|
||||
def pytest_unconfigure(config):
|
||||
if hasattr(config, '_oldassertion'):
|
||||
py.builtin.builtins.AssertionError = config._oldassertion
|
||||
del config._oldassertion
|
||||
|
||||
def warn_about_missing_assertion():
|
||||
try:
|
||||
assert False
|
||||
except AssertionError:
|
||||
pass
|
||||
else:
|
||||
py.std.warnings.warn("Assertions are turned off!"
|
||||
" (are you using python -O?)")
|
||||
@@ -1,277 +0,0 @@
|
||||
"""
|
||||
configurable per-test stdout/stderr capturing mechanisms.
|
||||
|
||||
This plugin captures stdout/stderr output for each test separately.
|
||||
In case of test failures this captured output is shown grouped
|
||||
togtther with the test.
|
||||
|
||||
The plugin also provides test function arguments that help to
|
||||
assert stdout/stderr output from within your tests, see the
|
||||
`funcarg example`_.
|
||||
|
||||
|
||||
Capturing of input/output streams during tests
|
||||
---------------------------------------------------
|
||||
|
||||
By default ``sys.stdout`` and ``sys.stderr`` are substituted with
|
||||
temporary streams during the execution of tests and setup/teardown code.
|
||||
During the whole testing process it will re-use the same temporary
|
||||
streams allowing to play well with the logging module which easily
|
||||
takes ownership on these streams.
|
||||
|
||||
Also, 'sys.stdin' is substituted with a file-like "null" object that
|
||||
does not return any values. This is to immediately error out
|
||||
on tests that wait on reading something from stdin.
|
||||
|
||||
You can influence output capturing mechanisms from the command line::
|
||||
|
||||
py.test -s # disable all capturing
|
||||
py.test --capture=sys # replace sys.stdout/stderr with in-mem files
|
||||
py.test --capture=fd # point filedescriptors 1 and 2 to temp file
|
||||
|
||||
If you set capturing values in a conftest file like this::
|
||||
|
||||
# conftest.py
|
||||
option_capture = 'fd'
|
||||
|
||||
then all tests in that directory will execute with "fd" style capturing.
|
||||
|
||||
sys-level capturing
|
||||
------------------------------------------
|
||||
|
||||
Capturing on 'sys' level means that ``sys.stdout`` and ``sys.stderr``
|
||||
will be replaced with in-memory files (``py.io.TextIO`` to be precise)
|
||||
that capture writes and decode non-unicode strings to a unicode object
|
||||
(using a default, usually, UTF-8, encoding).
|
||||
|
||||
FD-level capturing and subprocesses
|
||||
------------------------------------------
|
||||
|
||||
The ``fd`` based method means that writes going to system level files
|
||||
based on the standard file descriptors will be captured, for example
|
||||
writes such as ``os.write(1, 'hello')`` will be captured properly.
|
||||
Capturing on fd-level will include output generated from
|
||||
any subprocesses created during a test.
|
||||
|
||||
.. _`funcarg example`:
|
||||
|
||||
Example Usage of the capturing Function arguments
|
||||
---------------------------------------------------
|
||||
|
||||
You can use the `capsys funcarg`_ and `capfd funcarg`_ to
|
||||
capture writes to stdout and stderr streams. Using the
|
||||
funcargs frees your test from having to care about setting/resetting
|
||||
the old streams and also interacts well with py.test's own
|
||||
per-test capturing. Here is an example test function:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
def test_myoutput(capsys):
|
||||
print ("hello")
|
||||
sys.stderr.write("world\\n")
|
||||
out, err = capsys.readouterr()
|
||||
assert out == "hello\\n"
|
||||
assert err == "world\\n"
|
||||
print "next"
|
||||
out, err = capsys.readouterr()
|
||||
assert out == "next\\n"
|
||||
|
||||
The ``readouterr()`` call snapshots the output so far -
|
||||
and capturing will be continued. After the test
|
||||
function finishes the original streams will
|
||||
be restored. If you want to capture on
|
||||
the filedescriptor level you can use the ``capfd`` function
|
||||
argument which offers the same interface.
|
||||
"""
|
||||
|
||||
import py
|
||||
import os
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("general")
|
||||
group._addoption('-s', action="store_const", const="no", dest="capture",
|
||||
help="shortcut for --capture=no.")
|
||||
group._addoption('--capture', action="store", default=None,
|
||||
metavar="method", type="choice", choices=['fd', 'sys', 'no'],
|
||||
help="set capturing method during tests: fd (default)|sys|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_configure(config):
|
||||
config.pluginmanager.register(CaptureManager(), 'capturemanager')
|
||||
|
||||
class CaptureManager:
|
||||
def __init__(self):
|
||||
self._method2capture = {}
|
||||
|
||||
def _maketempfile(self):
|
||||
f = py.std.tempfile.TemporaryFile()
|
||||
newf = py.io.dupfile(f, encoding="UTF-8")
|
||||
return newf
|
||||
|
||||
def _makestringio(self):
|
||||
return py.io.TextIO()
|
||||
|
||||
def _startcapture(self, method):
|
||||
if method == "fd":
|
||||
return py.io.StdCaptureFD(
|
||||
out=self._maketempfile(), err=self._maketempfile()
|
||||
)
|
||||
elif method == "sys":
|
||||
return py.io.StdCapture(
|
||||
out=self._makestringio(), err=self._makestringio()
|
||||
)
|
||||
else:
|
||||
raise ValueError("unknown capturing method: %r" % method)
|
||||
|
||||
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,))
|
||||
if method != "no":
|
||||
cap = self._method2capture.get(method)
|
||||
if cap is None:
|
||||
cap = self._startcapture(method)
|
||||
self._method2capture[method] = cap
|
||||
else:
|
||||
cap.resume()
|
||||
self._capturing = method
|
||||
|
||||
def suspendcapture(self):
|
||||
self.deactivate_funcargs()
|
||||
method = self._capturing
|
||||
if method != "no":
|
||||
cap = self._method2capture[method]
|
||||
outerr = cap.suspend()
|
||||
else:
|
||||
outerr = "", ""
|
||||
del self._capturing
|
||||
return outerr
|
||||
|
||||
def activate_funcargs(self, pyfuncitem):
|
||||
if not hasattr(pyfuncitem, 'funcargs'):
|
||||
return
|
||||
assert not hasattr(self, '_capturing_funcargs')
|
||||
l = []
|
||||
for name, obj in pyfuncitem.funcargs.items():
|
||||
if name in ('capsys', 'capfd'):
|
||||
obj._start()
|
||||
l.append(obj)
|
||||
if l:
|
||||
self._capturing_funcargs = l
|
||||
|
||||
def deactivate_funcargs(self):
|
||||
if hasattr(self, '_capturing_funcargs'):
|
||||
for capfuncarg in self._capturing_funcargs:
|
||||
capfuncarg._finalize()
|
||||
del self._capturing_funcargs
|
||||
|
||||
def pytest_make_collect_report(self, __multicall__, collector):
|
||||
method = self._getmethod(collector.config, collector.fspath)
|
||||
self.resumecapture(method)
|
||||
try:
|
||||
rep = __multicall__.execute()
|
||||
finally:
|
||||
outerr = self.suspendcapture()
|
||||
addouterr(rep, outerr)
|
||||
return rep
|
||||
|
||||
def pytest_runtest_setup(self, item):
|
||||
self.resumecapture_item(item)
|
||||
|
||||
def pytest_runtest_call(self, item):
|
||||
self.resumecapture_item(item)
|
||||
self.activate_funcargs(item)
|
||||
|
||||
def pytest_runtest_teardown(self, item):
|
||||
self.resumecapture_item(item)
|
||||
|
||||
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()
|
||||
|
||||
def pytest_runtest_makereport(self, __multicall__, item, call):
|
||||
self.deactivate_funcargs()
|
||||
rep = __multicall__.execute()
|
||||
outerr = self.suspendcapture()
|
||||
outerr = (item.outerr[0] + outerr[0], item.outerr[1] + outerr[1])
|
||||
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):
|
||||
"""captures writes to sys.stdout/sys.stderr and makes
|
||||
them available successively via a ``capsys.readouterr()`` method
|
||||
which returns a ``(out, err)`` tuple of captured snapshot strings.
|
||||
"""
|
||||
return CaptureFuncarg(request, py.io.StdCapture)
|
||||
|
||||
def pytest_funcarg__capfd(request):
|
||||
"""captures writes to file descriptors 1 and 2 and makes
|
||||
snapshotted ``(out, err)`` string tuples available
|
||||
via the ``capsys.readouterr()`` method.
|
||||
"""
|
||||
return CaptureFuncarg(request, py.io.StdCaptureFD)
|
||||
|
||||
|
||||
class CaptureFuncarg:
|
||||
def __init__(self, request, captureclass):
|
||||
self._cclass = captureclass
|
||||
#request.addfinalizer(self._finalize)
|
||||
|
||||
def _start(self):
|
||||
self.capture = self._cclass()
|
||||
|
||||
def _finalize(self):
|
||||
if hasattr(self, 'capture'):
|
||||
self.capture.reset()
|
||||
del self.capture
|
||||
|
||||
def readouterr(self):
|
||||
return self.capture.readouterr()
|
||||
|
||||
def close(self):
|
||||
self.capture.reset()
|
||||
del self.capture
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
""" default hooks and general py.test options. """
|
||||
|
||||
import sys
|
||||
import py
|
||||
|
||||
try:
|
||||
import execnet
|
||||
except ImportError:
|
||||
execnet = None
|
||||
|
||||
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 pb.startswith("test_") or pb.endswith("_test") or \
|
||||
path in parent.config.args:
|
||||
if ext == ".py":
|
||||
return parent.Module(path, parent=parent)
|
||||
|
||||
def pytest_collect_directory(path, parent):
|
||||
# XXX reconsider the following comment
|
||||
# not use parent.Directory here as we generally
|
||||
# want dir/conftest.py to be able to
|
||||
# define Directory(dir) already
|
||||
if not parent.recfilter(path): # by default special ".cvs", ...
|
||||
# check if cmdline specified this dir or a subdir directly
|
||||
for arg in parent.config.args:
|
||||
if path == arg or arg.relto(path):
|
||||
break
|
||||
else:
|
||||
return
|
||||
Directory = parent.config.getvalue('Directory', path)
|
||||
return Directory(path, parent=parent)
|
||||
|
||||
def pytest_report_iteminfo(item):
|
||||
return item.reportinfo()
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("general", "general testing options")
|
||||
group._addoption('-v', '--verbose', action="count",
|
||||
dest="verbose", default=0, help="increase verbosity."),
|
||||
group._addoption('-x', '--exitfirst',
|
||||
action="store_true", dest="exitfirst", default=False,
|
||||
help="exit instantly on first error or failed test."),
|
||||
group._addoption('-k',
|
||||
action="store", dest="keyword", default='',
|
||||
help="only run test items matching the given "
|
||||
"space separated keywords. precede a keyword with '-' to negate. "
|
||||
"Terminate the expression with ':' to treat a match as a signal "
|
||||
"to run all subsequent tests. ")
|
||||
group._addoption('-l', '--showlocals',
|
||||
action="store_true", dest="showlocals", default=False,
|
||||
help="show locals in tracebacks (disabled by default).")
|
||||
#group._addoption('--showskipsummary',
|
||||
# action="store_true", dest="showskipsummary", default=False,
|
||||
# help="always show summary of skipped tests")
|
||||
group._addoption('--tb', metavar="style",
|
||||
action="store", dest="tbstyle", default='long',
|
||||
type="choice", choices=['long', 'short', 'no'],
|
||||
help="traceback verboseness (long/short/no).")
|
||||
group._addoption('-p', action="append", dest="plugins", default = [],
|
||||
help=("load the specified plugin after command line parsing. "))
|
||||
if execnet:
|
||||
group._addoption('-f', '--looponfail',
|
||||
action="store_true", dest="looponfail", default=False,
|
||||
help="run tests, re-run failing test set until all pass.")
|
||||
|
||||
group = parser.addgroup("debugconfig", "test process debugging and configuration")
|
||||
group.addoption('--basetemp', dest="basetemp", default=None, metavar="dir",
|
||||
help="base temporary directory for this test run.")
|
||||
|
||||
if execnet:
|
||||
add_dist_options(parser)
|
||||
else:
|
||||
parser.epilog = (
|
||||
"execnet missing: --looponfailing and distributed testing not available.")
|
||||
|
||||
def add_dist_options(parser):
|
||||
group = parser.addgroup("dist", "distributed testing") # see http://pytest.org/help/dist")
|
||||
group._addoption('--dist', metavar="distmode",
|
||||
action="store", choices=['load', 'each', 'no'],
|
||||
type="choice", dest="dist", default="no",
|
||||
help=("set mode for distributing tests to exec environments.\n\n"
|
||||
"each: send each test to each available environment.\n\n"
|
||||
"load: send each test to available environment.\n\n"
|
||||
"(default) no: run tests inprocess, don't distribute."))
|
||||
group._addoption('--tx', dest="tx", action="append", default=[], metavar="xspec",
|
||||
help=("add a test execution environment. some examples: "
|
||||
"--tx popen//python=python2.5 --tx socket=192.168.1.102:8888 "
|
||||
"--tx ssh=user@codespeak.net//chdir=testcache"))
|
||||
group._addoption('-d',
|
||||
action="store_true", dest="distload", default=False,
|
||||
help="load-balance tests. shortcut for '--dist=load'")
|
||||
group._addoption('-n', dest="numprocesses", metavar="numprocesses",
|
||||
action="store", type="int",
|
||||
help="shortcut for '--dist=load --tx=NUM*popen'")
|
||||
group.addoption('--rsyncdir', action="append", default=[], metavar="dir1",
|
||||
help="add directory for rsyncing to remote tx nodes.")
|
||||
|
||||
def pytest_configure(config):
|
||||
fixoptions(config)
|
||||
setsession(config)
|
||||
|
||||
def fixoptions(config):
|
||||
if execnet:
|
||||
if config.option.numprocesses:
|
||||
config.option.dist = "load"
|
||||
config.option.tx = ['popen'] * int(config.option.numprocesses)
|
||||
if config.option.distload:
|
||||
config.option.dist = "load"
|
||||
|
||||
def setsession(config):
|
||||
val = config.getvalue
|
||||
if val("collectonly"):
|
||||
from py.__.test.session import Session
|
||||
config.setsessionclass(Session)
|
||||
elif execnet:
|
||||
if val("looponfail"):
|
||||
from py.__.test.looponfail.remote import LooponfailingSession
|
||||
config.setsessionclass(LooponfailingSession)
|
||||
elif val("dist") != "no":
|
||||
from py.__.test.dist.dsession import DSession
|
||||
config.setsessionclass(DSession)
|
||||
@@ -1,86 +0,0 @@
|
||||
"""
|
||||
collect and execute doctests from modules and test files.
|
||||
|
||||
Usage
|
||||
-------------
|
||||
|
||||
By default all files matching the ``test_*.txt`` pattern will
|
||||
be run with the ``doctest`` module. If you issue::
|
||||
|
||||
py.test --doctest-modules
|
||||
|
||||
all python files in your projects will be doctest-run
|
||||
as well.
|
||||
"""
|
||||
|
||||
import py
|
||||
from py.__.code.code import TerminalRepr, ReprFileLocation
|
||||
import doctest
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.addgroup("doctest options")
|
||||
group.addoption("--doctest-modules",
|
||||
action="store_true", default=False,
|
||||
help="search all python files for doctests",
|
||||
dest="doctestmodules")
|
||||
|
||||
def pytest_collect_file(path, parent):
|
||||
if path.ext == ".py":
|
||||
if parent.config.getvalue("doctestmodules"):
|
||||
return DoctestModule(path, parent)
|
||||
if path.check(fnmatch="test_*.txt"):
|
||||
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(py.test.collect.Item):
|
||||
def __init__(self, path, parent):
|
||||
name = self.__class__.__name__ + ":" + path.basename
|
||||
super(DoctestItem, self).__init__(name=name, parent=parent)
|
||||
self.fspath = path
|
||||
|
||||
def repr_failure(self, excinfo):
|
||||
if excinfo.errisinstance(doctest.DocTestFailure):
|
||||
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 = doctest.OutputChecker()
|
||||
REPORT_UDIFF = 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
|
||||
lines += checker.output_difference(example,
|
||||
doctestfailure.got, REPORT_UDIFF).split("\n")
|
||||
return ReprFailDoctest(reprlocation, lines)
|
||||
elif excinfo.errisinstance(doctest.UnexpectedException):
|
||||
excinfo = py.code.ExceptionInfo(excinfo.value.exc_info)
|
||||
return super(DoctestItem, self).repr_failure(excinfo)
|
||||
else:
|
||||
return super(DoctestItem, self).repr_failure(excinfo)
|
||||
|
||||
class DoctestTextfile(DoctestItem):
|
||||
def runtest(self):
|
||||
if not self._deprecated_testexecution():
|
||||
failed, tot = doctest.testfile(
|
||||
str(self.fspath), module_relative=False,
|
||||
raise_on_error=True, verbose=0)
|
||||
|
||||
class DoctestModule(DoctestItem):
|
||||
def runtest(self):
|
||||
module = self.fspath.pyimport()
|
||||
failed, tot = doctest.testmod(
|
||||
module, raise_on_error=True, verbose=0)
|
||||
@@ -1,50 +0,0 @@
|
||||
"""
|
||||
write and report coverage data with 'figleaf'.
|
||||
|
||||
"""
|
||||
import py
|
||||
|
||||
figleaf = py.test.importorskip("figleaf.annotate_html")
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.addgroup('figleaf options')
|
||||
group.addoption('-F', action='store_true', default=False,
|
||||
dest = 'figleaf',
|
||||
help=('trace python coverage with figleaf and write HTML '
|
||||
'for files below the current working dir'))
|
||||
group.addoption('--figleaf-data', action='store', default='.figleaf',
|
||||
dest='figleafdata',
|
||||
help='path to coverage tracing file.')
|
||||
group.addoption('--figleaf-html', action='store', default='html',
|
||||
dest='figleafhtml',
|
||||
help='path to the coverage html dir.')
|
||||
|
||||
def pytest_configure(config):
|
||||
figleaf.start()
|
||||
|
||||
def pytest_terminal_summary(terminalreporter):
|
||||
config = terminalreporter.config
|
||||
datafile = py.path.local(config.getvalue('figleafdata'))
|
||||
tw = terminalreporter._tw
|
||||
tw.sep('-', 'figleaf')
|
||||
tw.line('Writing figleaf data to %s' % (datafile))
|
||||
figleaf.stop()
|
||||
figleaf.write_coverage(str(datafile))
|
||||
coverage = get_coverage(datafile, config)
|
||||
reportdir = py.path.local(config.getvalue('figleafhtml'))
|
||||
tw.line('Writing figleaf html to file://%s' % (reportdir))
|
||||
figleaf.annotate_html.prepare_reportdir(str(reportdir))
|
||||
exclude = []
|
||||
figleaf.annotate_html.report_as_html(coverage,
|
||||
str(reportdir), exclude, {})
|
||||
|
||||
def get_coverage(datafile, config):
|
||||
# basepath = config.topdir
|
||||
basepath = py.path.local()
|
||||
data = figleaf.read_coverage(str(datafile))
|
||||
d = {}
|
||||
coverage = figleaf.combine_coverage(d, data)
|
||||
for path in coverage.keys():
|
||||
if not py.path.local(path).relto(basepath):
|
||||
del coverage[path]
|
||||
return coverage
|
||||
@@ -1,63 +0,0 @@
|
||||
""" provide version info, conftest/environment config names.
|
||||
"""
|
||||
import py
|
||||
import sys
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup('debugconfig')
|
||||
group.addoption("--help-config", action="store_true", dest="helpconfig",
|
||||
help="show available conftest.py and ENV-variable names.")
|
||||
group.addoption('--version', action="store_true",
|
||||
help="display py lib version and import information.")
|
||||
|
||||
def pytest_configure(__multicall__, config):
|
||||
if config.option.version:
|
||||
p = py.path.local(py.__file__).dirpath()
|
||||
sys.stderr.write("This is py.test version %s, imported from %s\n" %
|
||||
(py.__version__, p))
|
||||
sys.exit(0)
|
||||
if not config.option.helpconfig:
|
||||
return
|
||||
__multicall__.execute()
|
||||
options = []
|
||||
for group in config._parser._groups:
|
||||
options.extend(group.options)
|
||||
widths = [0] * 10
|
||||
tw = py.io.TerminalWriter()
|
||||
tw.sep("-")
|
||||
tw.line("%-13s | %-18s | %-25s | %s" %(
|
||||
"cmdline name", "conftest.py name", "ENV-variable name", "help"))
|
||||
tw.sep("-")
|
||||
|
||||
options = [opt for opt in options if opt._long_opts]
|
||||
options.sort(key=lambda x: x._long_opts)
|
||||
for opt in options:
|
||||
if not opt._long_opts:
|
||||
continue
|
||||
optstrings = list(opt._long_opts) # + list(opt._short_opts)
|
||||
optstrings = filter(None, optstrings)
|
||||
optstring = "|".join(optstrings)
|
||||
line = "%-13s | %-18s | %-25s | %s" %(
|
||||
optstring,
|
||||
"option_%s" % opt.dest,
|
||||
"PYTEST_OPTION_%s" % opt.dest.upper(),
|
||||
opt.help and opt.help or "",
|
||||
)
|
||||
tw.line(line[:tw.fullwidth])
|
||||
for name, help in conftest_options:
|
||||
line = "%-13s | %-18s | %-25s | %s" %(
|
||||
"",
|
||||
name,
|
||||
"",
|
||||
help,
|
||||
)
|
||||
tw.line(line[:tw.fullwidth])
|
||||
|
||||
tw.sep("-")
|
||||
sys.exit(0)
|
||||
|
||||
conftest_options = (
|
||||
('pytest_plugins', 'list of plugin names to load'),
|
||||
('collect_ignore', '(relative) paths ignored during collection'),
|
||||
('rsyncdirs', 'to-be-rsynced directories for dist-testing'),
|
||||
)
|
||||
@@ -1,31 +0,0 @@
|
||||
""" log invocations of extension hooks to a file. """
|
||||
import py
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption("--hooklog", dest="hooklog", default=None,
|
||||
help="write hook calls to the given file.")
|
||||
|
||||
def pytest_configure(config):
|
||||
hooklog = config.getvalue("hooklog")
|
||||
if hooklog:
|
||||
config._hooklogfile = open(hooklog, 'w')
|
||||
config._hooklog_oldperformcall = config.hook._performcall
|
||||
config.hook._performcall = (lambda name, multicall:
|
||||
logged_call(name=name, multicall=multicall, config=config))
|
||||
|
||||
def logged_call(name, multicall, config):
|
||||
f = config._hooklogfile
|
||||
f.write("%s(**%s)\n" % (name, multicall.kwargs))
|
||||
try:
|
||||
res = config._hooklog_oldperformcall(name=name, multicall=multicall)
|
||||
except:
|
||||
f.write("-> exception")
|
||||
raise
|
||||
f.write("-> %r" % (res,))
|
||||
return res
|
||||
|
||||
def pytest_unconfigure(config):
|
||||
try:
|
||||
del config.hook.__dict__['_performcall']
|
||||
except KeyError:
|
||||
pass
|
||||
@@ -1,68 +0,0 @@
|
||||
"""
|
||||
mark test functions with keywords that may hold values.
|
||||
|
||||
Marking functions and setting rich attributes
|
||||
----------------------------------------------------
|
||||
|
||||
By default, all filename parts and class/function names of a test
|
||||
function are put into the set of keywords for a given test. You can
|
||||
specify additional kewords like this::
|
||||
|
||||
@py.test.mark.webtest
|
||||
def test_send_http():
|
||||
...
|
||||
|
||||
This will set an attribute 'webtest' on the given test function
|
||||
and by default all such attributes signal keywords. You can
|
||||
also set values in this attribute which you could read from
|
||||
a hook in order to do something special with respect to
|
||||
the test function::
|
||||
|
||||
@py.test.mark.timeout(seconds=5)
|
||||
def test_receive():
|
||||
...
|
||||
|
||||
This will set the "timeout" attribute with a Marker object
|
||||
that has a 'seconds' attribute.
|
||||
|
||||
"""
|
||||
import py
|
||||
|
||||
def pytest_namespace():
|
||||
return {'mark': Mark()}
|
||||
|
||||
|
||||
class Mark(object):
|
||||
def __getattr__(self, name):
|
||||
if name[0] == "_":
|
||||
raise AttributeError(name)
|
||||
return MarkerDecorator(name)
|
||||
|
||||
class MarkerDecorator:
|
||||
""" decorator for setting function attributes. """
|
||||
def __init__(self, name):
|
||||
self.markname = name
|
||||
|
||||
def __repr__(self):
|
||||
d = self.__dict__.copy()
|
||||
name = d.pop('markname')
|
||||
return "<MarkerDecorator %r %r>" %(name, d)
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
if not args:
|
||||
if hasattr(self, 'kwargs'):
|
||||
raise TypeError("double mark-keywords?")
|
||||
self.kwargs = kwargs.copy()
|
||||
return self
|
||||
else:
|
||||
if not len(args) == 1 or not hasattr(args[0], '__dict__'):
|
||||
raise TypeError("need exactly one function to decorate, "
|
||||
"got %r" %(args,))
|
||||
func = args[0]
|
||||
mh = MarkHolder(getattr(self, 'kwargs', {}))
|
||||
setattr(func, self.markname, mh)
|
||||
return func
|
||||
|
||||
class MarkHolder:
|
||||
def __init__(self, kwargs):
|
||||
self.__dict__.update(kwargs)
|
||||
@@ -1,142 +0,0 @@
|
||||
"""
|
||||
safely patch object attributes, dicts and environment variables.
|
||||
|
||||
Usage
|
||||
----------------
|
||||
|
||||
Use the `monkeypatch funcarg`_ to tweak your global test environment
|
||||
for running a particular test. You can safely set/del an attribute,
|
||||
dictionary item or environment variable by respective methods
|
||||
on the monkeypatch funcarg. If you want e.g. to set an ENV1 variable
|
||||
and have os.path.expanduser return a particular directory, you can
|
||||
write it down like this:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
def test_mytest(monkeypatch):
|
||||
monkeypatch.setenv('ENV1', 'myval')
|
||||
monkeypatch.setattr(os.path, 'expanduser', lambda x: '/tmp/xyz')
|
||||
... # your test code that uses those patched values implicitely
|
||||
|
||||
After the test function finished all modifications will be undone,
|
||||
because the ``monkeypatch.undo()`` method is registered as a finalizer.
|
||||
|
||||
``monkeypatch.setattr/delattr/delitem/delenv()`` all
|
||||
by default raise an Exception if the target does not exist.
|
||||
Pass ``raising=False`` if you want to skip this check.
|
||||
|
||||
prepending to PATH or other environment variables
|
||||
---------------------------------------------------------
|
||||
|
||||
To prepend a value to an already existing environment parameter:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
def test_mypath_finding(monkeypatch):
|
||||
monkeypatch.setenv('PATH', 'x/y', prepend=":")
|
||||
# in bash language: export PATH=x/y:$PATH
|
||||
|
||||
calling "undo" finalization explicitely
|
||||
-----------------------------------------
|
||||
|
||||
At the end of function execution py.test invokes
|
||||
a teardown hook which undoes all monkeypatch changes.
|
||||
If you do not want to wait that long you can call
|
||||
finalization explicitely::
|
||||
|
||||
monkeypatch.undo()
|
||||
|
||||
This will undo previous changes. This call consumes the
|
||||
undo stack. Calling it a second time has no effect unless
|
||||
you start monkeypatching after the undo call.
|
||||
|
||||
.. _`monkeypatch blog post`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/
|
||||
"""
|
||||
|
||||
import py, 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 when the requesting
|
||||
test function finished its execution. For the ``del``
|
||||
methods the ``raising`` parameter determines if a
|
||||
KeyError or AttributeError will be raised if the
|
||||
deletion has no target.
|
||||
"""
|
||||
monkeypatch = MonkeyPatch()
|
||||
request.addfinalizer(monkeypatch.undo)
|
||||
return monkeypatch
|
||||
|
||||
notset = object()
|
||||
|
||||
class MonkeyPatch:
|
||||
def __init__(self):
|
||||
self._setattr = []
|
||||
self._setitem = []
|
||||
|
||||
def setattr(self, obj, name, value, raising=True):
|
||||
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):
|
||||
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):
|
||||
self._setitem.insert(0, (dic, name, dic.get(name, notset)))
|
||||
dic[name] = value
|
||||
|
||||
def delitem(self, dic, name, raising=True):
|
||||
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):
|
||||
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):
|
||||
self.delitem(os.environ, name, raising=raising)
|
||||
|
||||
def syspath_prepend(self, path):
|
||||
if not hasattr(self, '_savesyspath'):
|
||||
self._savesyspath = sys.path[:]
|
||||
sys.path.insert(0, str(path))
|
||||
|
||||
def undo(self):
|
||||
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
|
||||
@@ -1,85 +0,0 @@
|
||||
"""nose-compatibility plugin: allow to run nose test suites natively.
|
||||
|
||||
This is an experimental plugin for allowing to run tests written
|
||||
in 'nosetests' style with py.test.
|
||||
|
||||
Usage
|
||||
-------------
|
||||
|
||||
type::
|
||||
|
||||
py.test # instead of 'nosetests'
|
||||
|
||||
and you should be able to run nose style tests. You will of course
|
||||
get py.test style reporting and its feature set.
|
||||
|
||||
Issues?
|
||||
----------------
|
||||
|
||||
If you find issues or have suggestions please run::
|
||||
|
||||
py.test --pastebin=all
|
||||
|
||||
and send the resulting URL to a some contact channel.
|
||||
|
||||
Known issues
|
||||
------------------
|
||||
|
||||
- nose-style doctests are not collected and executed correctly,
|
||||
also fixtures don't work.
|
||||
|
||||
- no nose-configuration is recognized
|
||||
|
||||
"""
|
||||
import 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_report_iteminfo(item):
|
||||
# nose 0.11.1 uses decorators for "raises" and other helpers.
|
||||
# for reporting progress by filename we fish for the filename
|
||||
if isinstance(item, py.test.collect.Function):
|
||||
obj = item.obj
|
||||
if hasattr(obj, 'compat_co_firstlineno'):
|
||||
fn = sys.modules[obj.__module__].__file__
|
||||
if fn.endswith(".pyc"):
|
||||
fn = fn[:-1]
|
||||
#assert 0
|
||||
#fn = inspect.getsourcefile(obj) or inspect.getfile(obj)
|
||||
lineno = obj.compat_co_firstlineno
|
||||
return py.path.local(fn), lineno, obj.__module__
|
||||
|
||||
def pytest_runtest_setup(item):
|
||||
if isinstance(item, (py.test.collect.Function)):
|
||||
if isinstance(item.parent, py.test.collect.Generator):
|
||||
gen = item.parent
|
||||
if not hasattr(gen, '_nosegensetup'):
|
||||
call_optional(gen.obj, 'setup')
|
||||
if isinstance(gen.parent, py.test.collect.Instance):
|
||||
call_optional(gen.parent.obj, 'setup')
|
||||
gen._nosegensetup = True
|
||||
call_optional(item.obj, 'setup')
|
||||
|
||||
def pytest_runtest_teardown(item):
|
||||
if isinstance(item, py.test.collect.Function):
|
||||
call_optional(item.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, py.test.collect.Generator):
|
||||
call_optional(collector.obj, 'setup')
|
||||
|
||||
def call_optional(obj, name):
|
||||
method = getattr(obj, name, None)
|
||||
if method:
|
||||
method()
|
||||
@@ -1,83 +0,0 @@
|
||||
"""
|
||||
submit failure or test session information to a pastebin service.
|
||||
|
||||
Usage
|
||||
----------
|
||||
|
||||
**Creating a URL for each test failure**::
|
||||
|
||||
py.test --pastebin=failed
|
||||
|
||||
This will submit test run information to a remote Paste service and
|
||||
provide a URL for each failure. You may select tests as usual or add
|
||||
for example ``-x`` if you only want to send one particular failure.
|
||||
|
||||
**Creating a URL for a whole test session log**::
|
||||
|
||||
py.test --pastebin=all
|
||||
|
||||
Currently only pasting to the http://paste.pocoo.org service is implemented.
|
||||
|
||||
"""
|
||||
import py, sys
|
||||
|
||||
class url:
|
||||
base = "http://paste.pocoo.org"
|
||||
xmlrpc = base + "/xmlrpc/"
|
||||
show = base + "/show/"
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("general")
|
||||
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.impname2plugin['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("session-log: %s" % pastebinurl)
|
||||
tr = config.pluginmanager.impname2plugin['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))
|
||||
@@ -1,115 +0,0 @@
|
||||
"""
|
||||
interactive debugging with the Python Debugger.
|
||||
"""
|
||||
import py
|
||||
import pdb, sys, linecache
|
||||
from py.__.test.outcome import Skipped
|
||||
try:
|
||||
import execnet
|
||||
except ImportError:
|
||||
execnet = None
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("general")
|
||||
group._addoption('--pdb',
|
||||
action="store_true", dest="usepdb", default=False,
|
||||
help="start pdb (the Python debugger) on errors.")
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
if config.option.usepdb:
|
||||
if execnet:
|
||||
if config.getvalue("looponfail"):
|
||||
raise config.Error("--pdb incompatible with --looponfail.")
|
||||
if config.option.dist != "no":
|
||||
raise config.Error("--pdb incompatible with distributing tests.")
|
||||
config.pluginmanager.register(PdbInvoke())
|
||||
|
||||
class PdbInvoke:
|
||||
def pytest_runtest_makereport(self, item, call):
|
||||
if call.excinfo and not call.excinfo.errisinstance(Skipped):
|
||||
# XXX hack hack hack to play well with capturing
|
||||
capman = item.config.pluginmanager.impname2plugin['capturemanager']
|
||||
capman.suspendcapture()
|
||||
|
||||
tw = py.io.TerminalWriter()
|
||||
repr = call.excinfo.getrepr()
|
||||
repr.toterminal(tw)
|
||||
post_mortem(call.excinfo._excinfo[2])
|
||||
|
||||
# XXX hack end
|
||||
capman.resumecapture_item(item)
|
||||
|
||||
class Pdb(py.std.pdb.Pdb):
|
||||
def do_list(self, arg):
|
||||
self.lastcmd = 'list'
|
||||
last = None
|
||||
if arg:
|
||||
try:
|
||||
x = eval(arg, {}, {})
|
||||
if type(x) == type(()):
|
||||
first, last = x
|
||||
first = int(first)
|
||||
last = int(last)
|
||||
if last < first:
|
||||
# Assume it's a count
|
||||
last = first + last
|
||||
else:
|
||||
first = max(1, int(x) - 5)
|
||||
except:
|
||||
print ('*** Error in argument: %s' % repr(arg))
|
||||
return
|
||||
elif self.lineno is None:
|
||||
first = max(1, self.curframe.f_lineno - 5)
|
||||
else:
|
||||
first = self.lineno + 1
|
||||
if last is None:
|
||||
last = first + 10
|
||||
filename = self.curframe.f_code.co_filename
|
||||
breaklist = self.get_file_breaks(filename)
|
||||
try:
|
||||
for lineno in range(first, last+1):
|
||||
# start difference from normal do_line
|
||||
line = self._getline(filename, lineno)
|
||||
# end difference from normal do_line
|
||||
if not line:
|
||||
print ('[EOF]')
|
||||
break
|
||||
else:
|
||||
s = repr(lineno).rjust(3)
|
||||
if len(s) < 4: s = s + ' '
|
||||
if lineno in breaklist: s = s + 'B'
|
||||
else: s = s + ' '
|
||||
if lineno == self.curframe.f_lineno:
|
||||
s = s + '->'
|
||||
sys.stdout.write(s + '\t' + line)
|
||||
self.lineno = lineno
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
do_l = do_list
|
||||
|
||||
def _getline(self, filename, lineno):
|
||||
if hasattr(filename, "__source__"):
|
||||
try:
|
||||
return filename.__source__.lines[lineno - 1] + "\n"
|
||||
except IndexError:
|
||||
return None
|
||||
return linecache.getline(filename, lineno)
|
||||
|
||||
def get_stack(self, f, t):
|
||||
# Modified from bdb.py to be able to walk the stack beyond generators,
|
||||
# which does not work in the normal pdb :-(
|
||||
stack, i = pdb.Pdb.get_stack(self, f, t)
|
||||
if f is None:
|
||||
i = max(0, len(stack) - 1)
|
||||
return stack, i
|
||||
|
||||
def post_mortem(t):
|
||||
# modified from pdb.py for the new get_stack() implementation
|
||||
p = Pdb()
|
||||
p.reset()
|
||||
p.interaction(None, t)
|
||||
|
||||
def set_trace():
|
||||
# again, a copy of the version in pdb.py
|
||||
Pdb().set_trace(sys._getframe().f_back)
|
||||
@@ -1,36 +0,0 @@
|
||||
"""pylint plugin
|
||||
|
||||
XXX: Currently in progress, NOT IN WORKING STATE.
|
||||
"""
|
||||
import py
|
||||
|
||||
pylint = py.test.importorskip("pylint.lint")
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.addgroup('pylint options')
|
||||
group.addoption('--pylint', action='store_true',
|
||||
default=False, dest='pylint',
|
||||
help='run pylint on python files.')
|
||||
|
||||
def pytest_collect_file(path, parent):
|
||||
if path.ext == ".py":
|
||||
if parent.config.getvalue('pylint'):
|
||||
return PylintItem(path, parent)
|
||||
|
||||
#def pytest_terminal_summary(terminalreporter):
|
||||
# print 'placeholder for pylint output'
|
||||
|
||||
class PylintItem(py.test.collect.Item):
|
||||
def runtest(self):
|
||||
capture = py.io.StdCaptureFD()
|
||||
try:
|
||||
linter = pylint.lint.PyLinter()
|
||||
linter.check(str(self.fspath))
|
||||
finally:
|
||||
out, err = capture.reset()
|
||||
rating = out.strip().split('\n')[-1]
|
||||
sys.stdout.write(">>>")
|
||||
print(rating)
|
||||
assert 0
|
||||
|
||||
|
||||
@@ -1,451 +0,0 @@
|
||||
"""
|
||||
funcargs and support code for testing py.test's own functionality.
|
||||
"""
|
||||
|
||||
import py
|
||||
import sys, os
|
||||
import inspect
|
||||
from py.__.test.config import Config as pytestConfig
|
||||
from py.__.test.plugin import hookspec
|
||||
from py.builtin import print_
|
||||
|
||||
pytest_plugins = '_pytest'
|
||||
|
||||
def pytest_funcarg__linecomp(request):
|
||||
return LineComp()
|
||||
|
||||
def pytest_funcarg__LineMatcher(request):
|
||||
return LineMatcher
|
||||
|
||||
def pytest_funcarg__testdir(request):
|
||||
tmptestdir = TmpTestdir(request)
|
||||
return tmptestdir
|
||||
|
||||
def pytest_funcarg__reportrecorder(request):
|
||||
reprec = ReportRecorder(py._com.comregistry)
|
||||
request.addfinalizer(lambda: reprec.comregistry.unregister(reprec))
|
||||
return reprec
|
||||
|
||||
class RunResult:
|
||||
def __init__(self, ret, outlines, errlines):
|
||||
self.ret = ret
|
||||
self.outlines = outlines
|
||||
self.errlines = errlines
|
||||
self.stdout = LineMatcher(outlines)
|
||||
self.stderr = LineMatcher(errlines)
|
||||
|
||||
class TmpTestdir:
|
||||
def __init__(self, request):
|
||||
self.request = request
|
||||
self._pytest = request.getfuncargvalue("_pytest")
|
||||
# XXX remove duplication with tmpdir plugin
|
||||
basetmp = request.config.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
|
||||
assert hasattr(self, '_olddir')
|
||||
self.request.addfinalizer(self.finalize)
|
||||
|
||||
def __repr__(self):
|
||||
return "<TmpTestdir %r>" % (self.tmpdir,)
|
||||
|
||||
def Config(self, comregistry=None, topdir=None):
|
||||
if topdir is None:
|
||||
topdir = self.tmpdir.dirpath()
|
||||
return pytestConfig(comregistry, topdir=topdir)
|
||||
|
||||
def finalize(self):
|
||||
for p in self._syspathremove:
|
||||
py.std.sys.path.remove(p)
|
||||
if hasattr(self, '_olddir'):
|
||||
self._olddir.chdir()
|
||||
|
||||
def getreportrecorder(self, obj):
|
||||
if isinstance(obj, py._com.Registry):
|
||||
registry = obj
|
||||
elif hasattr(obj, 'comregistry'):
|
||||
registry = obj.comregistry
|
||||
elif hasattr(obj, 'pluginmanager'):
|
||||
registry = obj.pluginmanager.comregistry
|
||||
elif hasattr(obj, 'config'):
|
||||
registry = obj.config.pluginmanager.comregistry
|
||||
else:
|
||||
raise ValueError("obj %r provides no comregistry" %(obj,))
|
||||
assert isinstance(registry, py._com.Registry)
|
||||
reprec = ReportRecorder(registry)
|
||||
reprec.hookrecorder = self._pytest.gethookrecorder(hookspec, registry)
|
||||
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))
|
||||
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 = py.code.Source(value)
|
||||
p.write(str(py.code.Source(value)).lstrip())
|
||||
if ret is None:
|
||||
ret = p
|
||||
return ret
|
||||
|
||||
|
||||
def makefile(self, ext, *args, **kwargs):
|
||||
return self._makefile(ext, args, kwargs)
|
||||
|
||||
def makeconftest(self, source):
|
||||
return self.makepyfile(conftest=source)
|
||||
|
||||
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
|
||||
|
||||
def genitems(self, colitems):
|
||||
return list(self.session.genitems(colitems))
|
||||
|
||||
def inline_genitems(self, *args):
|
||||
#config = self.parseconfig(*args)
|
||||
config = self.parseconfig(*args)
|
||||
session = config.initsession()
|
||||
rec = self.getreportrecorder(config)
|
||||
colitems = [config.getfsnode(arg) for arg in config.args]
|
||||
items = list(session.genitems(colitems))
|
||||
return 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):
|
||||
config = self.parseconfig(*args)
|
||||
config.pluginmanager.do_configure(config)
|
||||
session = config.initsession()
|
||||
reprec = self.getreportrecorder(config)
|
||||
session.main()
|
||||
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)
|
||||
#print "config.pluginmanager.impname2plugin", config.pluginmanager.impname2plugin
|
||||
return config
|
||||
|
||||
def parseconfig(self, *args):
|
||||
if not args:
|
||||
args = (self.tmpdir,)
|
||||
config = self.config_preparse()
|
||||
args = list(args) + ["--basetemp=%s" % self.tmpdir.dirpath('basetemp')]
|
||||
config.parse(args)
|
||||
return config
|
||||
|
||||
def parseconfigure(self, *args):
|
||||
config = self.parseconfig(*args)
|
||||
config.pluginmanager.do_configure(config)
|
||||
return config
|
||||
|
||||
def getitem(self, source, funcname="test_func"):
|
||||
modcol = self.getmodulecol(source)
|
||||
moditems = modcol.collect()
|
||||
for item in modcol.collect():
|
||||
if item.name == funcname:
|
||||
return item
|
||||
else:
|
||||
assert 0, "%r item not found in module:\n%s" %(funcname, source)
|
||||
|
||||
def getitems(self, source):
|
||||
modcol = self.getmodulecol(source)
|
||||
return list(modcol.config.initsession().genitems([modcol]))
|
||||
#assert item is not None, "%r item not found in module:\n%s" %(funcname, source)
|
||||
#return item
|
||||
|
||||
def getfscol(self, path, configargs=()):
|
||||
self.config = self.parseconfig(path, *configargs)
|
||||
self.session = self.config.initsession()
|
||||
return self.config.getfsnode(path)
|
||||
|
||||
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 = self.parseconfig(path, *configargs)
|
||||
self.session = self.config.initsession()
|
||||
#self.config.pluginmanager.do_configure(config=self.config)
|
||||
# XXX
|
||||
self.config.pluginmanager.import_plugin("runner")
|
||||
plugin = self.config.pluginmanager.getplugin("runner")
|
||||
plugin.pytest_configure(config=self.config)
|
||||
|
||||
return self.config.getfsnode(path)
|
||||
|
||||
def prepare(self):
|
||||
p = self.tmpdir.join("conftest.py")
|
||||
if not p.check():
|
||||
plugins = [x for x in self.plugins if isinstance(x, str)]
|
||||
if not plugins:
|
||||
return
|
||||
p.write("import py ; pytest_plugins = %r" % plugins)
|
||||
else:
|
||||
if self.plugins:
|
||||
print ("warning, ignoring reusing existing %s" % p)
|
||||
|
||||
def popen(self, cmdargs, stdout, stderr, **kw):
|
||||
if not hasattr(py.std, 'subprocess'):
|
||||
py.test.skip("no subprocess module")
|
||||
env = os.environ.copy()
|
||||
env['PYTHONPATH'] = ":".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 run(self, *cmdargs):
|
||||
self.prepare()
|
||||
old = self.tmpdir.chdir()
|
||||
#print "chdir", self.tmpdir
|
||||
try:
|
||||
return self._run(*cmdargs)
|
||||
finally:
|
||||
old.chdir()
|
||||
|
||||
def _run(self, *cmdargs):
|
||||
cmdargs = [str(x) for x in cmdargs]
|
||||
p1 = py.path.local("stdout")
|
||||
p2 = py.path.local("stderr")
|
||||
print_("running", cmdargs, "curdir=", py.path.local())
|
||||
f1 = p1.open("w")
|
||||
f2 = p2.open("w")
|
||||
popen = self.popen(cmdargs, stdout=f1, stderr=f2,
|
||||
close_fds=(sys.platform != "win32"))
|
||||
ret = popen.wait()
|
||||
f1.close()
|
||||
f2.close()
|
||||
out, err = p1.readlines(cr=0), p2.readlines(cr=0)
|
||||
if err:
|
||||
for line in err:
|
||||
py.builtin.print_(line, file=sys.stderr)
|
||||
if out:
|
||||
for line in out:
|
||||
py.builtin.print_(line, file=sys.stdout)
|
||||
return RunResult(ret, out, err)
|
||||
|
||||
def runpybin(self, scriptname, *args):
|
||||
fullargs = self._getpybinargs(scriptname) + args
|
||||
return self.run(*fullargs)
|
||||
|
||||
def _getpybinargs(self, scriptname):
|
||||
bindir = py.path.local(py.__file__).dirpath("bin")
|
||||
script = bindir.join(scriptname)
|
||||
assert script.check()
|
||||
return py.std.sys.executable, script
|
||||
|
||||
def runpython(self, script):
|
||||
return self.run(py.std.sys.executable, script)
|
||||
|
||||
def runpytest(self, *args):
|
||||
p = py.path.local.make_numbered_dir(prefix="runpytest-",
|
||||
keep=None, rootdir=self.tmpdir)
|
||||
args = ('--basetemp=%s' % p, ) + args
|
||||
return self.runpybin("py.test", *args)
|
||||
|
||||
def spawn_pytest(self, string, expect_timeout=10.0):
|
||||
pexpect = py.test.importorskip("pexpect", "2.3")
|
||||
basetemp = self.tmpdir.mkdir("pexpect")
|
||||
invoke = "%s %s" % self._getpybinargs("py.test")
|
||||
cmd = "%s --basetemp=%s %s" % (invoke, basetemp, string)
|
||||
child = pexpect.spawn(cmd, logfile=basetemp.join("spawn.out").open("w"))
|
||||
child.timeout = expect_timeout
|
||||
return child
|
||||
|
||||
class PseudoPlugin:
|
||||
def __init__(self, vars):
|
||||
self.__dict__.update(vars)
|
||||
|
||||
class ReportRecorder(object):
|
||||
def __init__(self, comregistry):
|
||||
self.comregistry = comregistry
|
||||
comregistry.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"):
|
||||
""" return a testreport whose dotted import path matches """
|
||||
l = []
|
||||
for rep in self.getreports(names=names):
|
||||
colitem = rep.getnode()
|
||||
if not inamepart or inamepart in colitem.listnames():
|
||||
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.comregistry.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) # remove what we got
|
||||
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 fnmatch_lines(self, lines2):
|
||||
if isinstance(lines2, str):
|
||||
lines2 = py.code.Source(lines2)
|
||||
if isinstance(lines2, py.code.Source):
|
||||
lines2 = lines2.strip().lines
|
||||
|
||||
from fnmatch import fnmatch
|
||||
__tracebackhide__ = True
|
||||
lines1 = self.lines[:]
|
||||
nextline = None
|
||||
extralines = []
|
||||
for line in lines2:
|
||||
nomatchprinted = False
|
||||
while lines1:
|
||||
nextline = lines1.pop(0)
|
||||
if line == nextline:
|
||||
print_("exact match:", repr(line))
|
||||
break
|
||||
elif fnmatch(nextline, line):
|
||||
print_("fnmatch:", repr(line))
|
||||
print_(" with:", repr(nextline))
|
||||
break
|
||||
else:
|
||||
if not nomatchprinted:
|
||||
print_("nomatch:", repr(line))
|
||||
nomatchprinted = True
|
||||
print_(" and:", repr(nextline))
|
||||
extralines.append(nextline)
|
||||
else:
|
||||
if line != nextline:
|
||||
#__tracebackhide__ = True
|
||||
raise AssertionError("expected line not found: %r" % line)
|
||||
extralines.extend(lines1)
|
||||
return extralines
|
||||
@@ -1,120 +0,0 @@
|
||||
"""
|
||||
helpers for asserting deprecation and other warnings.
|
||||
|
||||
Example usage
|
||||
---------------------
|
||||
|
||||
You can use the ``recwarn`` funcarg to track
|
||||
warnings within a test function:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
def test_hello(recwarn):
|
||||
from warnings import warn
|
||||
warn("hello", DeprecationWarning)
|
||||
w = recwarn.pop(DeprecationWarning)
|
||||
assert issubclass(w.category, DeprecationWarning)
|
||||
assert 'hello' in str(w.message)
|
||||
assert w.filename
|
||||
assert w.lineno
|
||||
|
||||
You can also call a global helper for checking
|
||||
taht a certain function call yields a Deprecation
|
||||
warning:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
import py
|
||||
|
||||
def test_global():
|
||||
py.test.deprecated_call(myfunction, 17)
|
||||
|
||||
|
||||
"""
|
||||
|
||||
import py
|
||||
import 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
|
||||
"""
|
||||
warnings = WarningsRecorder()
|
||||
request.addfinalizer(warnings.finalize)
|
||||
return warnings
|
||||
|
||||
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
|
||||
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
|
||||
@@ -1,351 +0,0 @@
|
||||
"""
|
||||
perform ReST syntax, local and remote reference tests on .rst/.txt files.
|
||||
"""
|
||||
import py
|
||||
import sys
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.addgroup("ReST", "ReST documentation check options")
|
||||
group.addoption('-R', '--urlcheck',
|
||||
action="store_true", dest="urlcheck", default=False,
|
||||
help="urlopen() remote links found in ReST text files.")
|
||||
group.addoption('--urltimeout', action="store", metavar="secs",
|
||||
type="int", dest="urlcheck_timeout", default=5,
|
||||
help="timeout in seconds for remote urlchecks")
|
||||
group.addoption('--forcegen',
|
||||
action="store_true", dest="forcegen", default=False,
|
||||
help="force generation of html files.")
|
||||
|
||||
def pytest_collect_file(path, parent):
|
||||
if path.ext in (".txt", ".rst"):
|
||||
project = getproject(path)
|
||||
if project is not None:
|
||||
return ReSTFile(path, parent=parent, project=project)
|
||||
|
||||
def getproject(path):
|
||||
for parent in path.parts(reverse=True):
|
||||
confrest = parent.join("confrest.py")
|
||||
if confrest.check():
|
||||
Project = confrest.pyimport().Project
|
||||
return Project(parent)
|
||||
|
||||
class ReSTFile(py.test.collect.File):
|
||||
def __init__(self, fspath, parent, project=None):
|
||||
super(ReSTFile, self).__init__(fspath=fspath, parent=parent)
|
||||
if project is None:
|
||||
project = getproject(fspath)
|
||||
assert project is not None
|
||||
self.project = project
|
||||
|
||||
def collect(self):
|
||||
return [
|
||||
ReSTSyntaxTest(self.project, "ReSTSyntax", parent=self),
|
||||
LinkCheckerMaker("checklinks", parent=self),
|
||||
DoctestText("doctest", parent=self),
|
||||
]
|
||||
|
||||
def deindent(s, sep='\n'):
|
||||
leastspaces = -1
|
||||
lines = s.split(sep)
|
||||
for line in lines:
|
||||
if not line.strip():
|
||||
continue
|
||||
spaces = len(line) - len(line.lstrip())
|
||||
if leastspaces == -1 or spaces < leastspaces:
|
||||
leastspaces = spaces
|
||||
if leastspaces == -1:
|
||||
return s
|
||||
for i, line in enumerate(lines):
|
||||
if not line.strip():
|
||||
lines[i] = ''
|
||||
else:
|
||||
lines[i] = line[leastspaces:]
|
||||
return sep.join(lines)
|
||||
|
||||
class ReSTSyntaxTest(py.test.collect.Item):
|
||||
def __init__(self, project, *args, **kwargs):
|
||||
super(ReSTSyntaxTest, self).__init__(*args, **kwargs)
|
||||
self.project = project
|
||||
|
||||
def reportinfo(self):
|
||||
return self.fspath, None, "syntax check"
|
||||
|
||||
def runtest(self):
|
||||
self.restcheck(py.path.svnwc(self.fspath))
|
||||
|
||||
def restcheck(self, path):
|
||||
py.test.importorskip("docutils")
|
||||
self.register_linkrole()
|
||||
from docutils.utils import SystemMessage
|
||||
try:
|
||||
self._checkskip(path, self.project.get_htmloutputpath(path))
|
||||
self.project.process(path)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except SystemMessage:
|
||||
# we assume docutils printed info on stdout
|
||||
py.test.fail("docutils processing failed, see captured stderr")
|
||||
|
||||
def register_linkrole(self):
|
||||
from py.__.rest import directive
|
||||
directive.register_linkrole('api', self.resolve_linkrole)
|
||||
directive.register_linkrole('source', self.resolve_linkrole)
|
||||
|
||||
# XXX fake sphinx' "toctree" and refs
|
||||
directive.register_linkrole('ref', self.resolve_linkrole)
|
||||
|
||||
from docutils.parsers.rst import directives
|
||||
def toctree_directive(name, arguments, options, content, lineno,
|
||||
content_offset, block_text, state, state_machine):
|
||||
return []
|
||||
toctree_directive.content = 1
|
||||
toctree_directive.options = {'maxdepth': int, 'glob': directives.flag,
|
||||
'hidden': directives.flag}
|
||||
directives.register_directive('toctree', toctree_directive)
|
||||
self.register_pygments()
|
||||
|
||||
def register_pygments(self):
|
||||
# taken from pygments-main/external/rst-directive.py
|
||||
from docutils.parsers.rst import directives
|
||||
try:
|
||||
from pygments.formatters import HtmlFormatter
|
||||
except ImportError:
|
||||
def pygments_directive(name, arguments, options, content, lineno,
|
||||
content_offset, block_text, state, state_machine):
|
||||
return []
|
||||
pygments_directive.options = {}
|
||||
else:
|
||||
# The default formatter
|
||||
DEFAULT = HtmlFormatter(noclasses=True)
|
||||
# Add name -> formatter pairs for every variant you want to use
|
||||
VARIANTS = {
|
||||
# 'linenos': HtmlFormatter(noclasses=INLINESTYLES, linenos=True),
|
||||
}
|
||||
|
||||
from docutils import nodes
|
||||
|
||||
from pygments import highlight
|
||||
from pygments.lexers import get_lexer_by_name, TextLexer
|
||||
|
||||
def pygments_directive(name, arguments, options, content, lineno,
|
||||
content_offset, block_text, state, state_machine):
|
||||
try:
|
||||
lexer = get_lexer_by_name(arguments[0])
|
||||
except ValueError:
|
||||
# no lexer found - use the text one instead of an exception
|
||||
lexer = TextLexer()
|
||||
# take an arbitrary option if more than one is given
|
||||
formatter = options and VARIANTS[options.keys()[0]] or DEFAULT
|
||||
parsed = highlight('\n'.join(content), lexer, formatter)
|
||||
return [nodes.raw('', parsed, format='html')]
|
||||
|
||||
pygments_directive.options = dict([(key, directives.flag) for key in VARIANTS])
|
||||
|
||||
pygments_directive.arguments = (1, 0, 1)
|
||||
pygments_directive.content = 1
|
||||
directives.register_directive('sourcecode', pygments_directive)
|
||||
|
||||
def resolve_linkrole(self, name, text, check=True):
|
||||
apigen_relpath = self.project.apigen_relpath
|
||||
|
||||
if name == 'api':
|
||||
if text == 'py':
|
||||
return ('py', apigen_relpath + 'api/index.html')
|
||||
else:
|
||||
assert text.startswith('py.'), (
|
||||
'api link "%s" does not point to the py package') % (text,)
|
||||
dotted_name = text
|
||||
if dotted_name.find('(') > -1:
|
||||
dotted_name = dotted_name[:text.find('(')]
|
||||
# remove pkg root
|
||||
path = dotted_name.split('.')[1:]
|
||||
dotted_name = '.'.join(path)
|
||||
obj = py
|
||||
if check:
|
||||
for chunk in path:
|
||||
try:
|
||||
obj = getattr(obj, chunk)
|
||||
except AttributeError:
|
||||
raise AssertionError(
|
||||
'problem with linkrole :api:`%s`: can not resolve '
|
||||
'dotted name %s' % (text, dotted_name,))
|
||||
return (text, apigen_relpath + 'api/%s.html' % (dotted_name,))
|
||||
elif name == 'source':
|
||||
assert text.startswith('py/'), ('source link "%s" does not point '
|
||||
'to the py package') % (text,)
|
||||
relpath = '/'.join(text.split('/')[1:])
|
||||
if check:
|
||||
pkgroot = py.__pkg__.getpath()
|
||||
abspath = pkgroot.join(relpath)
|
||||
assert pkgroot.join(relpath).check(), (
|
||||
'problem with linkrole :source:`%s`: '
|
||||
'path %s does not exist' % (text, relpath))
|
||||
if relpath.endswith('/') or not relpath:
|
||||
relpath += 'index.html'
|
||||
else:
|
||||
relpath += '.html'
|
||||
return (text, apigen_relpath + 'source/%s' % (relpath,))
|
||||
elif name == 'ref':
|
||||
return ("", "")
|
||||
|
||||
def _checkskip(self, lpath, htmlpath=None):
|
||||
if not self.config.getvalue("forcegen"):
|
||||
lpath = py.path.local(lpath)
|
||||
if htmlpath is not None:
|
||||
htmlpath = py.path.local(htmlpath)
|
||||
if lpath.ext == '.txt':
|
||||
htmlpath = htmlpath or lpath.new(ext='.html')
|
||||
if htmlpath.check(file=1) and htmlpath.mtime() >= lpath.mtime():
|
||||
py.test.skip("html file is up to date, use --forcegen to regenerate")
|
||||
#return [] # no need to rebuild
|
||||
|
||||
class DoctestText(py.test.collect.Item):
|
||||
def reportinfo(self):
|
||||
return self.fspath, None, "doctest"
|
||||
|
||||
def runtest(self):
|
||||
content = self._normalize_linesep()
|
||||
newcontent = self.config.hook.pytest_doctest_prepare_content(content=content)
|
||||
if newcontent is not None:
|
||||
content = newcontent
|
||||
s = content
|
||||
l = []
|
||||
prefix = '.. >>> '
|
||||
mod = py.std.types.ModuleType(self.fspath.purebasename)
|
||||
skipchunk = False
|
||||
for line in deindent(s).split('\n'):
|
||||
stripped = line.strip()
|
||||
if skipchunk and line.startswith(skipchunk):
|
||||
py.builtin.print_("skipping", line)
|
||||
continue
|
||||
skipchunk = False
|
||||
if stripped.startswith(prefix):
|
||||
try:
|
||||
py.builtin.exec_(py.code.Source(
|
||||
stripped[len(prefix):]).compile(), mod.__dict__)
|
||||
except ValueError:
|
||||
e = sys.exc_info()[1]
|
||||
if e.args and e.args[0] == "skipchunk":
|
||||
skipchunk = " " * (len(line) - len(line.lstrip()))
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
l.append(line)
|
||||
docstring = "\n".join(l)
|
||||
mod.__doc__ = docstring
|
||||
failed, tot = py.std.doctest.testmod(mod, verbose=1)
|
||||
if failed:
|
||||
py.test.fail("doctest %s: %s failed out of %s" %(
|
||||
self.fspath, failed, tot))
|
||||
|
||||
def _normalize_linesep(self):
|
||||
# XXX quite nasty... but it works (fixes win32 issues)
|
||||
s = self.fspath.read()
|
||||
linesep = '\n'
|
||||
if '\r' in s:
|
||||
if '\n' not in s:
|
||||
linesep = '\r'
|
||||
else:
|
||||
linesep = '\r\n'
|
||||
s = s.replace(linesep, '\n')
|
||||
return s
|
||||
|
||||
class LinkCheckerMaker(py.test.collect.Collector):
|
||||
def collect(self):
|
||||
return list(self.genlinkchecks())
|
||||
|
||||
def genlinkchecks(self):
|
||||
path = self.fspath
|
||||
# generating functions + args as single tests
|
||||
timeout = self.config.getvalue("urlcheck_timeout")
|
||||
for lineno, line in enumerate(path.readlines()):
|
||||
line = line.strip()
|
||||
if line.startswith('.. _'):
|
||||
if line.startswith('.. _`'):
|
||||
delim = '`:'
|
||||
else:
|
||||
delim = ':'
|
||||
l = line.split(delim, 1)
|
||||
if len(l) != 2:
|
||||
continue
|
||||
tryfn = l[1].strip()
|
||||
name = "%s:%d" %(tryfn, lineno)
|
||||
if tryfn.startswith('http:') or tryfn.startswith('https'):
|
||||
if self.config.getvalue("urlcheck"):
|
||||
yield CheckLink(name, parent=self,
|
||||
args=(tryfn, path, lineno, timeout), checkfunc=urlcheck)
|
||||
elif tryfn.startswith('webcal:'):
|
||||
continue
|
||||
else:
|
||||
i = tryfn.find('#')
|
||||
if i != -1:
|
||||
checkfn = tryfn[:i]
|
||||
else:
|
||||
checkfn = tryfn
|
||||
if checkfn.strip() and (1 or checkfn.endswith('.html')):
|
||||
yield CheckLink(name, parent=self,
|
||||
args=(tryfn, path, lineno), checkfunc=localrefcheck)
|
||||
|
||||
class CheckLink(py.test.collect.Item):
|
||||
def __init__(self, name, parent, args, checkfunc):
|
||||
super(CheckLink, self).__init__(name, parent)
|
||||
self.args = args
|
||||
self.checkfunc = checkfunc
|
||||
|
||||
def runtest(self):
|
||||
return self.checkfunc(*self.args)
|
||||
|
||||
def reportinfo(self, basedir=None):
|
||||
return (self.fspath, self.args[2], "checklink: %s" % self.args[0])
|
||||
|
||||
def urlcheck(tryfn, path, lineno, TIMEOUT_URLOPEN):
|
||||
old = py.std.socket.getdefaulttimeout()
|
||||
py.std.socket.setdefaulttimeout(TIMEOUT_URLOPEN)
|
||||
try:
|
||||
try:
|
||||
py.builtin.print_("trying remote", tryfn)
|
||||
py.std.urllib2.urlopen(tryfn)
|
||||
finally:
|
||||
py.std.socket.setdefaulttimeout(old)
|
||||
except (py.std.urllib2.URLError, py.std.urllib2.HTTPError):
|
||||
e = sys.exc_info()[1]
|
||||
if getattr(e, 'code', None) in (401, 403): # authorization required, forbidden
|
||||
py.test.skip("%s: %s" %(tryfn, str(e)))
|
||||
else:
|
||||
py.test.fail("remote reference error %r in %s:%d\n%s" %(
|
||||
tryfn, path.basename, lineno+1, e))
|
||||
|
||||
def localrefcheck(tryfn, path, lineno):
|
||||
# assume it should be a file
|
||||
i = tryfn.find('#')
|
||||
if tryfn.startswith('javascript:'):
|
||||
return # don't check JS refs
|
||||
if i != -1:
|
||||
anchor = tryfn[i+1:]
|
||||
tryfn = tryfn[:i]
|
||||
else:
|
||||
anchor = ''
|
||||
fn = path.dirpath(tryfn)
|
||||
ishtml = fn.ext == '.html'
|
||||
fn = ishtml and fn.new(ext='.txt') or fn
|
||||
py.builtin.print_("filename is", fn)
|
||||
if not fn.check(): # not ishtml or not fn.check():
|
||||
if not py.path.local(tryfn).check(): # the html could be there
|
||||
py.test.fail("reference error %r in %s:%d" %(
|
||||
tryfn, path.basename, lineno+1))
|
||||
if anchor:
|
||||
source = unicode(fn.read(), 'latin1')
|
||||
source = source.lower().replace('-', ' ') # aehem
|
||||
|
||||
anchor = anchor.replace('-', ' ')
|
||||
match2 = ".. _`%s`:" % anchor
|
||||
match3 = ".. _%s:" % anchor
|
||||
candidates = (anchor, match2, match3)
|
||||
py.builtin.print_("candidates", repr(candidates))
|
||||
for line in source.split('\n'):
|
||||
line = line.strip()
|
||||
if line in candidates:
|
||||
break
|
||||
else:
|
||||
py.test.fail("anchor reference error %s#%s in %s:%d" %(
|
||||
tryfn, anchor, path.basename, lineno+1))
|
||||
@@ -1,94 +0,0 @@
|
||||
"""resultlog plugin for machine-readable logging of test results.
|
||||
Useful for buildbot integration code.
|
||||
"""
|
||||
|
||||
import py
|
||||
from py.builtin import print_
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.addgroup("resultlog", "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
|
||||
if resultlog:
|
||||
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, shortrepr, longrepr):
|
||||
print_("%s %s" % (shortrepr, testpath), file=self.logfile)
|
||||
for line in longrepr.splitlines():
|
||||
print_(" %s" % line, file=self.logfile)
|
||||
|
||||
def log_outcome(self, node, shortrepr, longrepr):
|
||||
testpath = generic_path(node)
|
||||
self.write_log_entry(testpath, shortrepr, longrepr)
|
||||
|
||||
def pytest_runtest_logreport(self, report):
|
||||
res = self.config.hook.pytest_report_teststatus(report=report)
|
||||
if res is not None:
|
||||
code = res[1]
|
||||
else:
|
||||
code = report.shortrepr
|
||||
if code == 'x':
|
||||
longrepr = str(report.longrepr)
|
||||
elif code == 'P':
|
||||
longrepr = ''
|
||||
elif report.passed:
|
||||
longrepr = ""
|
||||
elif report.failed:
|
||||
longrepr = str(report.longrepr)
|
||||
elif report.skipped:
|
||||
longrepr = str(report.longrepr.reprcrash.message)
|
||||
self.log_outcome(report.item, code, longrepr)
|
||||
|
||||
def pytest_collectreport(self, report):
|
||||
if not report.passed:
|
||||
if report.failed:
|
||||
code = "F"
|
||||
else:
|
||||
assert report.skipped
|
||||
code = "S"
|
||||
longrepr = str(report.longrepr.reprcrash)
|
||||
self.log_outcome(report.collector, code, longrepr)
|
||||
|
||||
def pytest_internalerror(self, excrepr):
|
||||
path = excrepr.reprcrash.path
|
||||
self.write_log_entry(path, '!', str(excrepr))
|
||||
@@ -1,294 +0,0 @@
|
||||
"""
|
||||
collect and run test items and create reports.
|
||||
"""
|
||||
|
||||
import py
|
||||
from py.__.test.outcome import Skipped
|
||||
|
||||
#
|
||||
# pytest plugin hooks
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("general")
|
||||
group.addoption('--boxed',
|
||||
action="store_true", dest="boxed", default=False,
|
||||
help="box each test run in a separate process")
|
||||
|
||||
# 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(report=rep)
|
||||
|
||||
def pytest_make_collect_report(collector):
|
||||
result = excinfo = None
|
||||
try:
|
||||
result = collector._memocollect()
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
excinfo = py.code.ExceptionInfo()
|
||||
return CollectReport(collector, result, excinfo)
|
||||
|
||||
def pytest_runtest_protocol(item):
|
||||
if item.config.getvalue("boxed"):
|
||||
reports = forked_run_report(item)
|
||||
for rep in reports:
|
||||
item.config.hook.pytest_runtest_logreport(report=rep)
|
||||
else:
|
||||
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):
|
||||
if not item._deprecated_testexecution():
|
||||
item.runtest()
|
||||
|
||||
def pytest_runtest_makereport(item, call):
|
||||
return ItemTestReport(item, call.excinfo, call.when)
|
||||
|
||||
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:
|
||||
rep = TeardownErrorReport(call.excinfo)
|
||||
return rep
|
||||
|
||||
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.config.hook
|
||||
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
|
||||
hook = getattr(item.config.hook, hookname)
|
||||
return CallInfo(lambda: hook(item=item), when=when)
|
||||
|
||||
class CallInfo:
|
||||
excinfo = None
|
||||
def __init__(self, func, when):
|
||||
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 forked_run_report(item):
|
||||
# for now, we run setup/teardown in the subprocess
|
||||
# XXX optionally allow sharing of setup/teardown
|
||||
EXITSTATUS_TESTEXIT = 4
|
||||
from py.__.test.dist.mypickle import ImmutablePickler
|
||||
ipickle = ImmutablePickler(uneven=0)
|
||||
ipickle.selfmemoize(item.config)
|
||||
# XXX workaround the issue that 2.6 cannot pickle
|
||||
# instances of classes defined in global conftest.py files
|
||||
ipickle.selfmemoize(item)
|
||||
def runforked():
|
||||
try:
|
||||
reports = runtestprotocol(item, log=False)
|
||||
except KeyboardInterrupt:
|
||||
py.std.os._exit(EXITSTATUS_TESTEXIT)
|
||||
return ipickle.dumps(reports)
|
||||
|
||||
ff = py.process.ForkedFunc(runforked)
|
||||
result = ff.waitfinish()
|
||||
if result.retval is not None:
|
||||
return ipickle.loads(result.retval)
|
||||
else:
|
||||
if result.exitstatus == EXITSTATUS_TESTEXIT:
|
||||
py.test.exit("forked test item %s raised Exit" %(item,))
|
||||
return [report_process_crash(item, result)]
|
||||
|
||||
def report_process_crash(item, result):
|
||||
path, lineno = item._getfslineno()
|
||||
info = "%s:%s: running the test CRASHED with signal %d" %(
|
||||
path, lineno, result.signal)
|
||||
return ItemTestReport(item, excinfo=info, when="???")
|
||||
|
||||
class BaseReport(object):
|
||||
def __repr__(self):
|
||||
l = ["%s=%s" %(key, value)
|
||||
for key, value in self.__dict__.items()]
|
||||
return "<%s %s>" %(self.__class__.__name__, " ".join(l),)
|
||||
|
||||
def toterminal(self, out):
|
||||
longrepr = self.longrepr
|
||||
if hasattr(longrepr, 'toterminal'):
|
||||
longrepr.toterminal(out)
|
||||
else:
|
||||
out.line(str(longrepr))
|
||||
|
||||
class ItemTestReport(BaseReport):
|
||||
failed = passed = skipped = False
|
||||
|
||||
def __init__(self, item, excinfo=None, when=None):
|
||||
self.item = item
|
||||
self.when = when
|
||||
if item and when != "setup":
|
||||
self.keywords = item.readkeywords()
|
||||
else:
|
||||
# if we fail during setup it might mean
|
||||
# we are not able to access the underlying object
|
||||
# this might e.g. happen if we are unpickled
|
||||
# and our parent collector did not collect us
|
||||
# (because it e.g. skipped for platform reasons)
|
||||
self.keywords = {}
|
||||
if not excinfo:
|
||||
self.passed = True
|
||||
self.shortrepr = "."
|
||||
else:
|
||||
if not isinstance(excinfo, py.code.ExceptionInfo):
|
||||
self.failed = True
|
||||
shortrepr = "?"
|
||||
longrepr = excinfo
|
||||
elif excinfo.errisinstance(Skipped):
|
||||
self.skipped = True
|
||||
shortrepr = "s"
|
||||
longrepr = self.item._repr_failure_py(excinfo)
|
||||
else:
|
||||
self.failed = True
|
||||
shortrepr = self.item.shortfailurerepr
|
||||
if self.when == "call":
|
||||
longrepr = self.item.repr_failure(excinfo)
|
||||
else: # exception in setup or teardown
|
||||
longrepr = self.item._repr_failure_py(excinfo)
|
||||
shortrepr = shortrepr.lower()
|
||||
self.shortrepr = shortrepr
|
||||
self.longrepr = longrepr
|
||||
|
||||
def __repr__(self):
|
||||
status = (self.passed and "passed" or
|
||||
self.skipped and "skipped" or
|
||||
self.failed and "failed" or
|
||||
"CORRUPT")
|
||||
l = [repr(self.item.name), "when=%r" % self.when, "outcome %r" % status,]
|
||||
if hasattr(self, 'node'):
|
||||
l.append("txnode=%s" % self.node.gateway.id)
|
||||
info = " " .join(map(str, l))
|
||||
return "<ItemTestReport %s>" % info
|
||||
|
||||
def getnode(self):
|
||||
return self.item
|
||||
|
||||
class CollectReport(BaseReport):
|
||||
skipped = failed = passed = False
|
||||
|
||||
def __init__(self, collector, result, excinfo=None):
|
||||
self.collector = collector
|
||||
if not excinfo:
|
||||
self.passed = True
|
||||
self.result = result
|
||||
else:
|
||||
self.longrepr = self.collector._repr_failure_py(excinfo)
|
||||
if excinfo.errisinstance(Skipped):
|
||||
self.skipped = True
|
||||
self.reason = str(excinfo.value)
|
||||
else:
|
||||
self.failed = True
|
||||
|
||||
def getnode(self):
|
||||
return self.collector
|
||||
|
||||
class TeardownErrorReport(BaseReport):
|
||||
skipped = passed = False
|
||||
failed = True
|
||||
when = "teardown"
|
||||
def __init__(self, excinfo):
|
||||
self.longrepr = excinfo.getrepr(funcargs=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 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()
|
||||
for col in needed_collectors[len(self.stack):]:
|
||||
col.setup()
|
||||
self.stack.append(col)
|
||||
@@ -1,455 +0,0 @@
|
||||
"""
|
||||
Implements terminal reporting of the full testing process.
|
||||
|
||||
This is a good source for looking at the various reporting hooks.
|
||||
"""
|
||||
import py
|
||||
import sys
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("debugconfig")
|
||||
group.addoption('--collectonly',
|
||||
action="store_true", dest="collectonly",
|
||||
help="only collect tests, don't execute them."),
|
||||
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('--fulltrace',
|
||||
action="store_true", dest="fulltrace", default=False,
|
||||
help="don't cut any tracebacks (default is to cut).")
|
||||
group.addoption('--debug',
|
||||
action="store_true", dest="debug", default=False,
|
||||
help="generate and show debugging information.")
|
||||
|
||||
def pytest_configure(config):
|
||||
if config.option.collectonly:
|
||||
reporter = CollectonlyReporter(config)
|
||||
else:
|
||||
reporter = TerminalReporter(config)
|
||||
# XXX see remote.py's XXX
|
||||
for attr in 'pytest_terminal_hasmarkup', 'pytest_terminal_fullwidth':
|
||||
if hasattr(config, attr):
|
||||
#print "SETTING TERMINAL OPTIONS", attr, getattr(config, attr)
|
||||
name = attr.split("_")[-1]
|
||||
assert hasattr(self.reporter._tw, name), name
|
||||
setattr(reporter._tw, name, getattr(config, attr))
|
||||
config.pluginmanager.register(reporter, 'terminalreporter')
|
||||
|
||||
class TerminalReporter:
|
||||
def __init__(self, config, file=None):
|
||||
self.config = config
|
||||
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.gateway2info = {}
|
||||
|
||||
def write_fspath_result(self, fspath, res):
|
||||
fspath = self.curdir.bestrelpath(fspath)
|
||||
if fspath != self.currentfspath:
|
||||
self._tw.line()
|
||||
relpath = self.curdir.bestrelpath(fspath)
|
||||
self._tw.write(relpath + " ")
|
||||
self.currentfspath = 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_line(self, line, **markup):
|
||||
line = str(line)
|
||||
self.ensure_newline()
|
||||
self._tw.line(line, **markup)
|
||||
|
||||
def write_sep(self, sep, title=None, **markup):
|
||||
self.ensure_newline()
|
||||
self._tw.sep(sep, title, **markup)
|
||||
|
||||
def getcategoryletterword(self, rep):
|
||||
res = self.config.hook.pytest_report_teststatus(report=rep)
|
||||
if res:
|
||||
return res
|
||||
for cat in 'skipped failed passed ???'.split():
|
||||
if getattr(rep, cat, None):
|
||||
break
|
||||
return cat, self.getoutcomeletter(rep), self.getoutcomeword(rep)
|
||||
|
||||
def getoutcomeletter(self, rep):
|
||||
return rep.shortrepr
|
||||
|
||||
def getoutcomeword(self, rep):
|
||||
if rep.passed:
|
||||
return "PASS", dict(green=True)
|
||||
elif rep.failed:
|
||||
return "FAIL", dict(red=True)
|
||||
elif rep.skipped:
|
||||
return "SKIP"
|
||||
else:
|
||||
return "???", dict(red=True)
|
||||
|
||||
def pytest_internalerror(self, excrepr):
|
||||
for line in str(excrepr).split("\n"):
|
||||
self.write_line("INTERNALERROR> " + line)
|
||||
|
||||
def pytest_gwmanage_newgateway(self, gateway, platinfo):
|
||||
#self.write_line("%s instantiated gateway from spec %r" %(gateway.id, gateway.spec._spec))
|
||||
d = {}
|
||||
d['version'] = repr_pythonversion(platinfo.version_info)
|
||||
d['id'] = gateway.id
|
||||
d['spec'] = gateway.spec._spec
|
||||
d['platform'] = platinfo.platform
|
||||
if self.config.option.verbose:
|
||||
d['extra'] = "- " + platinfo.executable
|
||||
else:
|
||||
d['extra'] = ""
|
||||
d['cwd'] = platinfo.cwd
|
||||
infoline = ("%(id)s %(spec)s -- platform %(platform)s, "
|
||||
"Python %(version)s "
|
||||
"cwd: %(cwd)s"
|
||||
"%(extra)s" % d)
|
||||
self.write_line(infoline)
|
||||
self.gateway2info[gateway] = infoline
|
||||
|
||||
def pytest_gwmanage_rsyncstart(self, source, gateways):
|
||||
targets = ", ".join([gw.id for gw in gateways])
|
||||
msg = "rsyncstart: %s -> %s" %(source, targets)
|
||||
if not self.config.option.verbose:
|
||||
msg += " # use --verbose to see rsync progress"
|
||||
self.write_line(msg)
|
||||
|
||||
def pytest_gwmanage_rsyncfinish(self, source, gateways):
|
||||
targets = ", ".join([gw.id for gw in gateways])
|
||||
self.write_line("rsyncfinish: %s -> %s" %(source, targets))
|
||||
|
||||
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_testnodeready(self, node):
|
||||
self.write_line("%s txnode ready to receive tests" %(node.gateway.id,))
|
||||
|
||||
def pytest_testnodedown(self, node, error):
|
||||
if error:
|
||||
self.write_line("%s node down, error: %s" %(node.gateway.id, error))
|
||||
|
||||
def pytest_trace(self, category, msg):
|
||||
if self.config.option.debug or \
|
||||
self.config.option.traceconfig and category.find("config") != -1:
|
||||
self.write_line("[%s] %s" %(category, msg))
|
||||
|
||||
def pytest_rescheduleitems(self, items):
|
||||
if self.config.option.debug:
|
||||
self.write_sep("!", "RESCHEDULING %s " %(items,))
|
||||
|
||||
def pytest_deselected(self, items):
|
||||
self.stats.setdefault('deselected', []).append(items)
|
||||
|
||||
def pytest_itemstart(self, item, node=None):
|
||||
if getattr(self.config.option, 'dist', 'no') != "no":
|
||||
# for dist-testing situations itemstart means we
|
||||
# queued the item for sending, not interesting (unless debugging)
|
||||
if self.config.option.debug:
|
||||
line = self._reportinfoline(item)
|
||||
extra = ""
|
||||
if node:
|
||||
extra = "-> " + str(node.gateway.id)
|
||||
self.write_ensure_prefix(line, extra)
|
||||
else:
|
||||
if self.config.option.verbose:
|
||||
line = self._reportinfoline(item)
|
||||
self.write_ensure_prefix(line, "")
|
||||
else:
|
||||
# ensure that the path is printed before the
|
||||
# 1st test of a module starts running
|
||||
|
||||
self.write_fspath_result(self._getfspath(item), "")
|
||||
|
||||
def pytest__teardown_final_logerror(self, report):
|
||||
self.stats.setdefault("error", []).append(report)
|
||||
|
||||
def pytest_runtest_logreport(self, report):
|
||||
rep = report
|
||||
cat, letter, word = self.getcategoryletterword(rep)
|
||||
if not letter and not word:
|
||||
# probably passed setup/teardown
|
||||
return
|
||||
if isinstance(word, tuple):
|
||||
word, markup = word
|
||||
else:
|
||||
markup = {}
|
||||
self.stats.setdefault(cat, []).append(rep)
|
||||
if not self.config.option.verbose:
|
||||
self.write_fspath_result(self._getfspath(rep.item), letter)
|
||||
else:
|
||||
line = self._reportinfoline(rep.item)
|
||||
if not hasattr(rep, 'node'):
|
||||
self.write_ensure_prefix(line, 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_collectreport(self, report):
|
||||
if not report.passed:
|
||||
if report.failed:
|
||||
self.stats.setdefault("error", []).append(report)
|
||||
msg = report.longrepr.reprcrash.message
|
||||
self.write_fspath_result(report.collector.fspath, "E")
|
||||
elif report.skipped:
|
||||
self.stats.setdefault("skipped", []).append(report)
|
||||
self.write_fspath_result(report.collector.fspath, "S")
|
||||
|
||||
def pytest_sessionstart(self, session):
|
||||
self.write_sep("=", "test session starts", bold=True)
|
||||
self._sessionstarttime = py.std.time.time()
|
||||
|
||||
verinfo = ".".join(map(str, sys.version_info[:3]))
|
||||
msg = "python: platform %s -- Python %s" % (sys.platform, verinfo)
|
||||
if self.config.option.verbose or self.config.option.debug or getattr(self.config.option, 'pastebin', None):
|
||||
msg += " -- pytest-%s" % (py.__version__)
|
||||
msg += " -- " + str(sys.executable)
|
||||
self.write_line(msg)
|
||||
|
||||
if self.config.option.debug or self.config.option.traceconfig:
|
||||
self.write_line("using py lib: %s" % (py.path.local(py.__file__).dirpath()))
|
||||
if self.config.option.traceconfig:
|
||||
plugins = []
|
||||
for plugin in self.config.pluginmanager.comregistry:
|
||||
name = getattr(plugin, '__name__', None)
|
||||
if name is None:
|
||||
name = plugin.__class__.__name__
|
||||
plugins.append(name)
|
||||
plugins = ", ".join(plugins)
|
||||
self.write_line("active plugins: %s" %(plugins,))
|
||||
for i, testarg in enumerate(self.config.args):
|
||||
self.write_line("test object %d: %s" %(i+1, testarg))
|
||||
|
||||
def pytest_sessionfinish(self, exitstatus, __multicall__):
|
||||
__multicall__.execute()
|
||||
self._tw.line("")
|
||||
if exitstatus in (0, 1, 2):
|
||||
self.summary_errors()
|
||||
self.summary_failures()
|
||||
self.summary_skips()
|
||||
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()
|
||||
|
||||
def _report_keyboardinterrupt(self):
|
||||
self.write_sep("!", "KEYBOARD INTERRUPT")
|
||||
excrepr = self._keyboardinterrupt_memo
|
||||
if self.config.option.verbose:
|
||||
excrepr.toterminal(self._tw)
|
||||
else:
|
||||
excrepr.reprcrash.toterminal(self._tw)
|
||||
|
||||
def pytest_looponfailinfo(self, failreports, rootdirs):
|
||||
if failreports:
|
||||
self.write_sep("#", "LOOPONFAILING", red=True)
|
||||
for report in failreports:
|
||||
try:
|
||||
loc = report.longrepr.reprcrash
|
||||
except AttributeError:
|
||||
loc = str(report.longrepr)[:50]
|
||||
self.write_line(loc, red=True)
|
||||
self.write_sep("#", "waiting for changes")
|
||||
for rootdir in rootdirs:
|
||||
self.write_line("### Watching: %s" %(rootdir,), bold=True)
|
||||
|
||||
def _reportinfoline(self, item):
|
||||
collect_fspath = self._getfspath(item)
|
||||
fspath, lineno, msg = self._getreportinfo(item)
|
||||
if fspath and fspath != collect_fspath:
|
||||
fspath = "%s <- %s" % (
|
||||
self.curdir.bestrelpath(collect_fspath),
|
||||
self.curdir.bestrelpath(fspath))
|
||||
elif fspath:
|
||||
fspath = self.curdir.bestrelpath(fspath)
|
||||
if lineno is not None:
|
||||
lineno += 1
|
||||
if fspath and lineno and msg:
|
||||
line = "%(fspath)s:%(lineno)s: %(msg)s"
|
||||
elif fspath and msg:
|
||||
line = "%(fspath)s: %(msg)s"
|
||||
elif fspath and lineno:
|
||||
line = "%(fspath)s:%(lineno)s %(extrapath)s"
|
||||
else:
|
||||
line = "[noreportinfo]"
|
||||
return line % locals() + " "
|
||||
|
||||
def _getfailureheadline(self, rep):
|
||||
if hasattr(rep, "collector"):
|
||||
return str(rep.collector.fspath)
|
||||
elif hasattr(rep, 'item'):
|
||||
fspath, lineno, msg = self._getreportinfo(rep.item)
|
||||
return msg
|
||||
else:
|
||||
return "test session"
|
||||
|
||||
def _getreportinfo(self, item):
|
||||
try:
|
||||
return item.__reportinfo
|
||||
except AttributeError:
|
||||
pass
|
||||
reportinfo = item.config.hook.pytest_report_iteminfo(item=item)
|
||||
# cache on item
|
||||
item.__reportinfo = reportinfo
|
||||
return reportinfo
|
||||
|
||||
def _getfspath(self, item):
|
||||
try:
|
||||
return item.fspath
|
||||
except AttributeError:
|
||||
fspath, lineno, msg = self._getreportinfo(item)
|
||||
return fspath
|
||||
|
||||
#
|
||||
# summaries for sessionfinish
|
||||
#
|
||||
|
||||
def summary_failures(self):
|
||||
if 'failed' in self.stats and self.config.option.tbstyle != "no":
|
||||
self.write_sep("=", "FAILURES")
|
||||
for rep in self.stats['failed']:
|
||||
msg = self._getfailureheadline(rep)
|
||||
self.write_sep("_", msg)
|
||||
self.write_platinfo(rep)
|
||||
rep.toterminal(self._tw)
|
||||
|
||||
def summary_errors(self):
|
||||
if 'error' in self.stats and self.config.option.tbstyle != "no":
|
||||
self.write_sep("=", "ERRORS")
|
||||
for rep in self.stats['error']:
|
||||
msg = self._getfailureheadline(rep)
|
||||
if not hasattr(rep, 'when'):
|
||||
# collect
|
||||
msg = "ERROR during collection " + 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)
|
||||
self.write_platinfo(rep)
|
||||
rep.toterminal(self._tw)
|
||||
|
||||
def write_platinfo(self, rep):
|
||||
if hasattr(rep, 'node'):
|
||||
self.write_line(self.gateway2info.get(
|
||||
rep.node.gateway,
|
||||
"node %r (platinfo not found? strange)")
|
||||
[:self._tw.fullwidth-1])
|
||||
|
||||
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
|
||||
self.write_sep("=", "%s in %.2f seconds" %(line, session_duration))
|
||||
|
||||
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 summary_skips(self):
|
||||
if 'skipped' in self.stats:
|
||||
if 'failed' not in self.stats: # or self.config.option.showskipsummary:
|
||||
fskips = folded_skips(self.stats['skipped'])
|
||||
if fskips:
|
||||
self.write_sep("_", "skipped test summary")
|
||||
for num, fspath, lineno, reason in fskips:
|
||||
self._tw.line("%s:%d: [%d] %s" %(fspath, lineno, num, reason))
|
||||
|
||||
class CollectonlyReporter:
|
||||
INDENT = " "
|
||||
|
||||
def __init__(self, config, out=None):
|
||||
self.config = config
|
||||
if out is None:
|
||||
out = py.std.sys.stdout
|
||||
self.out = py.io.TerminalWriter(out)
|
||||
self.indent = ""
|
||||
self._failed = []
|
||||
|
||||
def outindent(self, line):
|
||||
self.out.line(self.indent + str(line))
|
||||
|
||||
def pytest_internalerror(self, excrepr):
|
||||
for line in str(excrepr).split("\n"):
|
||||
self.out.line("INTERNALERROR> " + line)
|
||||
|
||||
def pytest_collectstart(self, collector):
|
||||
self.outindent(collector)
|
||||
self.indent += self.INDENT
|
||||
|
||||
def pytest_itemstart(self, item, node=None):
|
||||
self.outindent(item)
|
||||
|
||||
def pytest_collectreport(self, report):
|
||||
if not report.passed:
|
||||
self.outindent("!!! %s !!!" % report.longrepr.reprcrash.message)
|
||||
self._failed.append(report)
|
||||
self.indent = self.indent[:-len(self.INDENT)]
|
||||
|
||||
def pytest_sessionfinish(self, session, exitstatus):
|
||||
if self._failed:
|
||||
self.out.sep("!", "collection failures")
|
||||
for rep in self._failed:
|
||||
rep.toterminal(self.out)
|
||||
|
||||
def folded_skips(skipped):
|
||||
d = {}
|
||||
for event in skipped:
|
||||
entry = event.longrepr.reprcrash
|
||||
key = entry.path, entry.lineno, entry.message
|
||||
d.setdefault(key, []).append(event)
|
||||
l = []
|
||||
for key, events in d.items():
|
||||
l.append((len(events),) + key)
|
||||
return l
|
||||
|
||||
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)
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
"""
|
||||
provide temporary directories to test functions and methods.
|
||||
|
||||
example:
|
||||
|
||||
pytest_plugins = "pytest_tmpdir"
|
||||
|
||||
def test_plugin(tmpdir):
|
||||
tmpdir.join("hello").write("hello")
|
||||
|
||||
"""
|
||||
import py
|
||||
|
||||
def pytest_funcarg__tmpdir(request):
|
||||
name = request.function.__name__
|
||||
return request.config.mktemp(name, numbered=True)
|
||||
@@ -1,74 +0,0 @@
|
||||
"""
|
||||
automatically discover and run traditional "unittest.py" style tests.
|
||||
|
||||
Usage
|
||||
----------------
|
||||
|
||||
This plugin collects and runs Python `unittest.py style`_ tests.
|
||||
It will automatically collect ``unittest.TestCase`` subclasses
|
||||
and their ``test`` methods from the test modules of a project
|
||||
(usually following the ``test_*.py`` pattern).
|
||||
|
||||
This plugin is enabled by default.
|
||||
|
||||
.. _`unittest.py style`: http://docs.python.org/library/unittest.html
|
||||
"""
|
||||
import py
|
||||
import sys
|
||||
|
||||
def pytest_pycollect_makeitem(collector, name, obj):
|
||||
if 'unittest' not in sys.modules:
|
||||
return # nobody could have possibly derived a subclass
|
||||
if py.std.inspect.isclass(obj) and issubclass(obj, py.std.unittest.TestCase):
|
||||
return UnitTestCase(name, parent=collector)
|
||||
|
||||
class UnitTestCase(py.test.collect.Class):
|
||||
def collect(self):
|
||||
return [UnitTestCaseInstance("()", self)]
|
||||
|
||||
def setup(self):
|
||||
pass
|
||||
|
||||
def teardown(self):
|
||||
pass
|
||||
|
||||
_dummy = object()
|
||||
class UnitTestCaseInstance(py.test.collect.Instance):
|
||||
def collect(self):
|
||||
loader = py.std.unittest.TestLoader()
|
||||
names = loader.getTestCaseNames(self.obj.__class__)
|
||||
l = []
|
||||
for name in names:
|
||||
callobj = getattr(self.obj, name)
|
||||
if py.builtin.callable(callobj):
|
||||
l.append(UnitTestFunction(name, parent=self))
|
||||
return l
|
||||
|
||||
def _getobj(self):
|
||||
x = self.parent.obj
|
||||
return self.parent.obj(methodName='run')
|
||||
|
||||
class UnitTestFunction(py.test.collect.Function):
|
||||
def __init__(self, name, parent, args=(), obj=_dummy, sort_value=None):
|
||||
super(UnitTestFunction, self).__init__(name, parent)
|
||||
self._args = args
|
||||
if obj is not _dummy:
|
||||
self._obj = obj
|
||||
self._sort_value = sort_value
|
||||
if hasattr(self.parent, 'newinstance'):
|
||||
self.parent.newinstance()
|
||||
self.obj = self._getobj()
|
||||
|
||||
def runtest(self):
|
||||
target = self.obj
|
||||
args = self._args
|
||||
target(*args)
|
||||
|
||||
def setup(self):
|
||||
instance = py.builtin._getimself(self.obj)
|
||||
instance.setUp()
|
||||
|
||||
def teardown(self):
|
||||
instance = py.builtin._getimself(self.obj)
|
||||
instance.tearDown()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user