* deprecate py.magic.invoke/revoke in favour of

the new py.code.patch_builtins, py.code.unpatch_builtins

* deprecate py.magic.patch/revert

* deprecate py.magic.AssertionError in favour of py.code._AssertionError

* introduced pytest_assertion plugin.

--HG--
branch : trunk
This commit is contained in:
holger krekel 2009-08-27 17:26:02 +02:00
parent e391662cff
commit 13932b7f4b
26 changed files with 564 additions and 696 deletions

View File

@ -114,12 +114,12 @@ initpkg(__name__,
# some nice slightly magic APIs
'magic.__doc__' : ('./magic/__init__.py', '__doc__'),
'magic.invoke' : ('./magic/invoke.py', 'invoke'),
'magic.revoke' : ('./magic/invoke.py', 'revoke'),
'magic.patch' : ('./magic/patch.py', 'patch'),
'magic.revert' : ('./magic/patch.py', 'revert'),
'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' : ('./magic/autopath.py', 'autopath'),
'magic.AssertionError' : ('./magic/assertion.py', 'AssertionError'),
'magic.AssertionError' : ('./code/oldmagic2.py', 'AssertionError'),
# python inspection/code-generation API
'code.__doc__' : ('./code/__init__.py', '__doc__'),
@ -130,6 +130,9 @@ initpkg(__name__,
'code.ExceptionInfo' : ('./code/code.py', 'ExceptionInfo'),
'code.Traceback' : ('./code/code.py', 'Traceback'),
'code.getfslineno' : ('./code/source.py', 'getfslineno'),
'code.patch_builtins' : ('./code/code.py', 'patch_builtins'),
'code.unpatch_builtins' : ('./code/code.py', 'unpatch_builtins'),
'code._AssertionError' : ('./code/assertion.py', 'AssertionError'),
# backports and additions of builtins
'builtin.__doc__' : ('./builtin/__init__.py', '__doc__'),

View File

@ -4,52 +4,3 @@ import py
import sys
import inspect
def test_all_resolves():
seen = py.builtin.set([py])
lastlength = None
while len(seen) != lastlength:
lastlength = len(seen)
for item in py.builtin.frozenset(seen):
for value in item.__dict__.values():
if isinstance(value, type(py.test)):
seen.add(value)
class TestAPI_V0_namespace_consistence:
def test_path_entrypoints(self):
assert inspect.ismodule(py.path)
assert_class('py.path', 'local')
assert_class('py.path', 'svnwc')
assert_class('py.path', 'svnurl')
def test_magic_entrypoints(self):
assert_function('py.magic', 'invoke')
assert_function('py.magic', 'revoke')
assert_function('py.magic', 'patch')
assert_function('py.magic', 'revoke')
def test_process_entrypoints(self):
assert_function('py.process', 'cmdexec')
def XXXtest_utest_entrypoints(self):
# XXX TOBECOMPLETED
assert_function('py.test', 'main')
#assert_module('std.utest', 'collect')
def assert_class(modpath, name):
mod = __import__(modpath, None, None, [name])
obj = getattr(mod, name)
assert inspect.isclass(obj)
# we don't test anymore that the exported classes have
# the exported module path and name on them.
#fullpath = modpath + '.' + name
#assert obj.__module__ == modpath
#if sys.version_info >= (2,3):
# assert obj.__name__ == name
def assert_function(modpath, name):
mod = __import__(modpath, None, None, [name])
obj = getattr(mod, name)
assert hasattr(obj, 'func_doc')
#assert obj.func_name == name

View File

@ -174,3 +174,14 @@ def test_autoimport():
from py.initpkg import autoimport
py.std.os.environ['AUTOTEST_AUTOIMPORT'] = "nonexistmodule"
py.test.raises(ImportError, "autoimport('autotest')")
def test_all_resolves():
seen = py.builtin.set([py])
lastlength = None
while len(seen) != lastlength:
lastlength = len(seen)
for item in py.builtin.frozenset(seen):
for value in item.__dict__.values():
if isinstance(value, type(py.test)):
seen.add(value)

View File

@ -1,6 +1,8 @@
from compiler import parse, ast, pycodegen
import py
import __builtin__, sys
import sys, inspect
from compiler import parse, ast, pycodegen
import __builtin__ as cpy_builtin
BuiltinAssertionError = cpy_builtin.AssertionError
passthroughex = (KeyboardInterrupt, SystemExit, MemoryError)
@ -8,10 +10,92 @@ class Failure:
def __init__(self, node):
self.exc, self.value, self.tb = sys.exc_info()
self.node = node
#import traceback
#traceback.print_exc()
from py.__.magic.viewtype import View
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."""
@ -322,8 +406,6 @@ class Getattr(Interpretable):
self.explanation = '%s\n{%s = %s\n}' % (r, r, self.explanation)
# == Re-interpretation of full statements ==
import __builtin__
BuiltinAssertionError = __builtin__.AssertionError
class Assert(Interpretable):
__view__ = ast.Assert
@ -390,7 +472,7 @@ def report_failure(e):
explanation = ", in: " + explanation
else:
explanation = ""
print "%s: %s%s" % (e.exc.__name__, e.value, explanation)
sys.stdout.write("%s: %s%s\n" % (e.exc.__name__, e.value, explanation))
def check(s, frame=None):
if frame is None:
@ -404,11 +486,12 @@ def check(s, frame=None):
node.eval(frame)
except passthroughex:
raise
except Failure, e:
except Failure:
e = sys.exc_info()[1]
report_failure(e)
else:
if not frame.is_true(node.result):
print "assertion failed:", node.nice_explanation()
sys.stderr.write("assertion failed: %s\n" % node.nice_explanation())
###########################################################
@ -422,7 +505,8 @@ def interpret(source, frame, should_fail=False):
frame = py.code.Frame(frame)
try:
module.run(frame)
except Failure, e:
except Failure:
e = sys.exc_info()[1]
return getfailure(e)
except passthroughex:
raise
@ -447,7 +531,7 @@ def getmsg(excinfo):
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,)
raise TypeError("interpret returned non-string %r" % (x,))
return x
def getfailure(e):
@ -469,10 +553,40 @@ def run(s, frame=None):
module = Interpretable(parse(s, 'exec').node)
try:
module.run(frame)
except Failure, e:
except Failure:
e = sys.exc_info()[1]
report_failure(e)
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 = sys._getframe(1)
try:
source = py.code.Frame(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 __name__ == '__main__':
# example:
def f():

View File

@ -5,7 +5,9 @@ try:
except ImportError:
import reprlib as repr
from __builtin__ import repr as builtin_repr
import __builtin__ as cpy_builtin
builtin_repr = cpy_builtin.repr
class Code(object):
""" wrapper around Python code objects """
@ -194,9 +196,9 @@ class TracebackEntry(object):
"""Reinterpret the failing statement and returns a detailed information
about what operations are performed."""
if self.exprinfo is None:
from py.__.magic import exprinfo
from py.__.code import assertion
source = str(self.statement).strip()
x = exprinfo.interpret(source, self.frame, should_fail=True)
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
@ -355,7 +357,7 @@ class ExceptionInfo(object):
# ExceptionInfo-like classes may have different attributes.
if tup is None:
tup = sys.exc_info()
if exprinfo is None and isinstance(tup[1], py.magic.AssertionError):
if exprinfo is None and isinstance(tup[1], py.code._AssertionError):
exprinfo = tup[1].msg
if exprinfo and exprinfo.startswith('assert '):
self._striptext = 'AssertionError: '
@ -371,7 +373,7 @@ class ExceptionInfo(object):
""" return the exception as a string
when 'tryshort' resolves to True, and the exception is a
py.magic.AssertionError, only the actual exception part of
py.code._AssertionError, only the actual exception part of
the exception representation is returned (so 'AssertionError: ' is
removed from the beginning)
"""
@ -727,3 +729,24 @@ class SafeRepr(repr.Repr):
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(cpy_builtin.AssertionError)
cpy_builtin.AssertionError = assertion.AssertionError
if compile:
l = oldbuiltins.setdefault('compile', [])
l.append(cpy_builtin.compile)
cpy_builtin.compile = py.code.compile
def unpatch_builtins(assertion=True, compile=True):
""" remove compile and AssertionError builtins from Python builtins. """
if assertion:
cpy_builtin.AssertionError = oldbuiltins['AssertionError'].pop()
if compile:
cpy_builtin.compile = oldbuiltins['compile'].pop()

61
py/code/oldmagic.py Normal file
View File

@ -0,0 +1,61 @@
""" deprecated module for turning on/off some features. """
import py
import __builtin__ 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

6
py/code/oldmagic2.py Normal file
View File

@ -0,0 +1,6 @@
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

View File

@ -0,0 +1,173 @@
import py
from py.__.code.assertion import View
def setup_module(mod):
py.code.patch_builtins(assertion=True, compile=False)
def teardown_module(mod):
py.code.unpatch_builtins(assertion=True, compile=False)
def f():
return 2
def test_assert():
try:
assert f() == 3
except AssertionError, e:
s = str(e)
assert s.startswith('assert 2 == 3\n')
def test_assert_with_explicit_message():
try:
assert f() == 3, "hello"
except AssertionError, e:
assert e.msg == 'hello'
def test_assert_within_finally():
class A:
def f():
pass
excinfo = py.test.raises(TypeError, """
try:
A().f()
finally:
i = 42
""")
s = excinfo.exconly()
assert s.find("takes no argument") != -1
#def g():
# A.f()
#excinfo = getexcinfo(TypeError, g)
#msg = getmsg(excinfo)
#assert msg.find("must be called with A") != -1
def test_assert_multiline_1():
try:
assert (f() ==
3)
except AssertionError, e:
s = str(e)
assert s.startswith('assert 2 == 3\n')
def test_assert_multiline_2():
try:
assert (f() == (4,
3)[-1])
except AssertionError, e:
s = str(e)
assert s.startswith('assert 2 ==')
def test_assert_non_string_message():
class A:
def __str__(self):
return "hello"
try:
assert 0 == 1, A()
except AssertionError, e:
assert e.msg == "hello"
# These tests should both fail, but should fail nicely...
class WeirdRepr:
def __repr__(self):
return '<WeirdRepr\nsecond line>'
def bug_test_assert_repr():
v = WeirdRepr()
try:
assert v == 1
except AssertionError, e:
assert e.msg.find('WeirdRepr') != -1
assert e.msg.find('second line') != -1
assert 0
def test_assert_non_string():
try:
assert 0, ['list']
except AssertionError, e:
assert e.msg.find("list") != -1
def test_assert_implicit_multiline():
try:
x = [1,2,3]
assert x != [1,
2, 3]
except AssertionError, e:
assert e.msg.find('assert [1, 2, 3] !=') != -1
def test_assert_with_brokenrepr_arg():
class BrokenRepr:
def __repr__(self): 0 / 0
e = AssertionError(BrokenRepr())
if e.msg.find("broken __repr__") == -1:
py.test.fail("broken __repr__ not handle correctly")
class TestView:
def test_class_dispatch(self):
### Use a custom class hierarchy with existing instances
class Picklable(View):
pass
class Simple(Picklable):
__view__ = object
def pickle(self):
return repr(self.__obj__)
class Seq(Picklable):
__view__ = list, tuple, dict
def pickle(self):
return ';'.join(
[Picklable(item).pickle() for item in self.__obj__])
class Dict(Seq):
__view__ = dict
def pickle(self):
return Seq.pickle(self) + '!' + Seq(self.values()).pickle()
assert Picklable(123).pickle() == '123'
assert Picklable([1,[2,3],4]).pickle() == '1;2;3;4'
assert Picklable({1:2}).pickle() == '1!2'
def test_viewtype_class_hierarchy(self):
# Use a custom class hierarchy based on attributes of existing instances
class Operation:
"Existing class that I don't want to change."
def __init__(self, opname, *args):
self.opname = opname
self.args = args
existing = [Operation('+', 4, 5),
Operation('getitem', '', 'join'),
Operation('setattr', 'x', 'y', 3),
Operation('-', 12, 1)]
class PyOp(View):
def __viewkey__(self):
return self.opname
def generate(self):
return '%s(%s)' % (self.opname, ', '.join(map(repr, self.args)))
class PyBinaryOp(PyOp):
__view__ = ('+', '-', '*', '/')
def generate(self):
return '%s %s %s' % (self.args[0], self.opname, self.args[1])
codelines = [PyOp(op).generate() for op in existing]
assert codelines == ["4 + 5", "getitem('', 'join')",
"setattr('x', 'y', 3)", "12 - 1"]
def test_AssertionError(testdir):
testdir.makepyfile("""
import py
def test_hello(recwarn):
err = py.magic.AssertionError
recwarn.pop(DeprecationWarning)
assert err is py.code._AssertionError
""")
result = testdir.runpytest()
assert "1 passed" in result.stdout.str()

View File

@ -164,5 +164,18 @@ class TestSafeRepr:
s = safe_repr(Function())
except Exception, e:
py.test.fail("saferepr failed for newstyle class")
def test_builtin_patch_unpatch(monkeypatch):
import __builtin__ as cpy_builtin
comp = cpy_builtin.compile
def mycompile(*args, **kwargs):
return comp(*args, **kwargs)
monkeypatch.setattr(cpy_builtin, 'AssertionError', None)
monkeypatch.setattr(cpy_builtin, 'compile', mycompile)
py.code.patch_builtins()
assert cpy_builtin.AssertionError
assert cpy_builtin.compile != mycompile
py.code.unpatch_builtins()
assert cpy_builtin.AssertionError is None
assert cpy_builtin.compile == mycompile

View File

@ -577,11 +577,11 @@ raise ValueError()
x = 1
assert x == 2
""")
py.magic.invoke(assertion=True)
py.code.patch_builtins(assertion=True)
try:
excinfo = py.test.raises(AssertionError, mod.somefunc)
finally:
py.magic.revoke(assertion=True)
py.code.unpatch_builtins(assertion=True)
p = FormattedExcinfo()
reprentry = p.repr_traceback_entry(excinfo.traceback[-1], excinfo)

View File

@ -0,0 +1,64 @@
import py
def check_assertion():
excinfo = py.test.raises(AssertionError, "assert 1 == 2")
s = excinfo.exconly(tryshort=True)
if not s == "assert 1 == 2":
raise ValueError("assertion not enabled: got %s" % s)
def test_invoke_assertion(recwarn, monkeypatch):
monkeypatch.setattr(py.std.__builtin__, 'AssertionError', None)
py.magic.invoke(assertion=True)
try:
check_assertion()
finally:
py.magic.revoke(assertion=True)
recwarn.pop(DeprecationWarning)
def test_invoke_compile(recwarn, monkeypatch):
monkeypatch.setattr(py.std.__builtin__, 'compile', None)
py.magic.invoke(compile=True)
try:
co = compile("""if 1:
def f():
return 1
\n""", '', 'exec')
d = {}
exec co in d
assert py.code.Source(d['f'])
finally:
py.magic.revoke(compile=True)
recwarn.pop(DeprecationWarning)
def test_patch_revert(recwarn):
class a:
pass
py.test.raises(AttributeError, "py.magic.patch(a, 'i', 42)")
a.i = 42
py.magic.patch(a, 'i', 23)
assert a.i == 23
recwarn.pop(DeprecationWarning)
py.magic.revert(a, 'i')
assert a.i == 42
recwarn.pop(DeprecationWarning)
def test_double_patch(recwarn):
class a:
i = 42
assert py.magic.patch(a, 'i', 2) == 42
recwarn.pop(DeprecationWarning)
assert py.magic.patch(a, 'i', 3) == 2
assert a.i == 3
assert py.magic.revert(a, 'i') == 3
recwarn.pop(DeprecationWarning)
assert a.i == 2
assert py.magic.revert(a, 'i') == 2
assert a.i == 42
def test_valueerror(recwarn):
class a:
i = 2
pass
py.test.raises(ValueError, "py.magic.revert(a, 'i')")
recwarn.pop(DeprecationWarning)

View File

@ -1,38 +0,0 @@
import __builtin__, sys
import py
from py.__.magic import exprinfo
BuiltinAssertionError = __builtin__.AssertionError
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 = sys._getframe(1)
try:
source = py.code.Frame(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 = exprinfo.interpret(source, f, should_fail=True)
if not self.args:
self.args = (self.msg,)
else:
self.msg = None
def invoke():
py.magic.patch(__builtin__, 'AssertionError', AssertionError)
def revoke():
py.magic.revert(__builtin__, 'AssertionError')

View File

@ -1,24 +0,0 @@
import py
import __builtin__ as cpy_builtin
def invoke(assertion=False, compile=False):
""" 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.
"""
if assertion:
from py.__.magic import assertion
assertion.invoke()
if compile:
py.magic.patch(cpy_builtin, 'compile', py.code.compile )
def revoke(assertion=False, compile=False):
""" revoke previously invoked magic (see invoke())."""
if assertion:
from py.__.magic import assertion
assertion.revoke()
if compile:
py.magic.revert(cpy_builtin, 'compile')

View File

@ -1,26 +0,0 @@
patched = {}
def patch(namespace, name, value):
""" 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.
"""
nref = (namespace, name)
orig = getattr(namespace, name)
patched.setdefault(nref, []).append(orig)
setattr(namespace, name, value)
return orig
def revert(namespace, name):
""" revert to the orginal value the last patch modified.
Raise ValueError if no such original value exists.
"""
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

View File

@ -1,106 +0,0 @@
import py
def setup_module(mod):
py.magic.invoke(assertion=1)
def teardown_module(mod):
py.magic.revoke(assertion=1)
def f():
return 2
def test_assert():
try:
assert f() == 3
except AssertionError, e:
s = str(e)
assert s.startswith('assert 2 == 3\n')
def test_assert_with_explicit_message():
try:
assert f() == 3, "hello"
except AssertionError, e:
assert e.msg == 'hello'
def test_assert_within_finally():
class A:
def f():
pass
excinfo = py.test.raises(TypeError, """
try:
A().f()
finally:
i = 42
""")
s = excinfo.exconly()
assert s.find("takes no argument") != -1
#def g():
# A.f()
#excinfo = getexcinfo(TypeError, g)
#msg = getmsg(excinfo)
#assert msg.find("must be called with A") != -1
def test_assert_multiline_1():
try:
assert (f() ==
3)
except AssertionError, e:
s = str(e)
assert s.startswith('assert 2 == 3\n')
def test_assert_multiline_2():
try:
assert (f() == (4,
3)[-1])
except AssertionError, e:
s = str(e)
assert s.startswith('assert 2 ==')
def test_assert_non_string_message():
class A:
def __str__(self):
return "hello"
try:
assert 0 == 1, A()
except AssertionError, e:
assert e.msg == "hello"
# These tests should both fail, but should fail nicely...
class WeirdRepr:
def __repr__(self):
return '<WeirdRepr\nsecond line>'
def bug_test_assert_repr():
v = WeirdRepr()
try:
assert v == 1
except AssertionError, e:
assert e.msg.find('WeirdRepr') != -1
assert e.msg.find('second line') != -1
assert 0
def test_assert_non_string():
try:
assert 0, ['list']
except AssertionError, e:
assert e.msg.find("list") != -1
def test_assert_implicit_multiline():
try:
x = [1,2,3]
assert x != [1,
2, 3]
except AssertionError, e:
assert e.msg.find('assert [1, 2, 3] !=') != -1
def test_assert_with_brokenrepr_arg():
class BrokenRepr:
def __repr__(self): 0 / 0
e = AssertionError(BrokenRepr())
if e.msg.find("broken __repr__") == -1:
py.test.fail("broken __repr__ not handle correctly")

View File

@ -1,156 +0,0 @@
import sys
import py
from py.__.magic.exprinfo import getmsg, interpret
def getexcinfo(exc, obj, *args, **kwargs):
try:
obj(*args, **kwargs)
except KeyboardInterrupt:
raise
except exc:
return sys.exc_info()
else:
raise AssertionError, "%r(*%r, **%r) did not raise" %(
obj, args, kwargs)
def test_assert_exprinfo():
def g():
a = 1
b = 2
assert a == b
excinfo = getexcinfo(AssertionError, g)
msg = getmsg(excinfo)
assert msg == 'assert 1 == 2'
def test_nested_scopes():
def g():
a = 1
def h():
return a
b = 2
assert h() == b
excinfo = getexcinfo(AssertionError, g)
msg = getmsg(excinfo)
assert msg.startswith('assert 1 == 2\n + where 1 = ')
def test_nested_scopes_2():
a = 1
def g():
b = 2
assert a == b
excinfo = getexcinfo(AssertionError, g)
msg = getmsg(excinfo)
assert msg == 'assert 1 == 2'
def test_assert_func_argument_type_error():
def f ():
pass
def g():
f(1)
excinfo = getexcinfo(TypeError, g)
msg = getmsg(excinfo)
assert msg.find("takes no argument") != -1
class A:
def f():
pass
def g():
A().f()
excinfo = getexcinfo(TypeError, g)
msg = getmsg(excinfo)
assert msg.find("takes no argument") != -1
def g():
A.f()
excinfo = getexcinfo(TypeError, g)
msg = getmsg(excinfo)
assert msg.find("must be called with A") != -1
def global_f(u=6, v=7):
return u*v
def test_exprinfo_funccall():
def g():
assert global_f() == 43
excinfo = getexcinfo(AssertionError, g)
msg = getmsg(excinfo)
assert msg == 'assert 42 == 43\n + where 42 = global_f()'
def test_exprinfo_funccall_keywords():
def g():
assert global_f(v=11) == 67
excinfo = getexcinfo(AssertionError, g)
msg = getmsg(excinfo)
assert msg == 'assert 66 == 67\n + where 66 = global_f(v=11)'
def test_interpretable_escapes_newlines():
class X(object):
def __repr__(self):
return '1\n2'
def g():
assert X() == 'XXX'
excinfo = getexcinfo(AssertionError, g)
msg = getmsg(excinfo)
assert msg == "assert 1\\n2 == 'XXX'\n + where 1\\n2 = <class 'py.__.magic.testing.test_exprinfo.X'>()"
def test_keyboard_interrupt():
# XXX this test is slightly strange because it is not
# clear that "interpret" should execute "raise" statements
# ... but it apparently currently does and it's nice to
# exercise the code because the exprinfo-machinery is
# not much executed when all tests pass ...
class DummyCode:
co_filename = 'dummy'
co_firstlineno = 0
co_name = 'dummy'
class DummyFrame:
f_globals = f_locals = {}
f_code = DummyCode
f_lineno = 0
for exstr in "SystemExit", "KeyboardInterrupt", "MemoryError":
ex = eval(exstr)
try:
interpret("raise %s" % exstr, py.code.Frame(DummyFrame))
except ex:
pass
else:
raise AssertionError, "ex %s didn't pass through" %(exstr, )
def test_inconsistent_assert_result(testdir):
p = testdir.makepyfile("""
def test_func():
def f(l=[1,0]):
return l.pop()
assert f()
""")
result = testdir.runpytest(p)
s = result.stdout.str()
assert s.find("re-run") != -1
def test_twoarg_comparison_does_not_call_nonzero():
# this arises e.g. in numpy array comparisons
class X(object):
def __eq__(self, other):
return self
def __nonzero__(self):
raise ValueError
def all(self):
return False
def f():
a = X()
b = X()
assert (a == b).all()
excinfo = getexcinfo(AssertionError, f)
msg = getmsg(excinfo)
print msg
assert "re-run" not in msg
assert "ValueError" not in msg

View File

@ -1,29 +0,0 @@
import __builtin__ as bltin
import py
import inspect
def check_assertion():
excinfo = py.test.raises(AssertionError, "assert 1 == 2")
assert excinfo.exconly(tryshort=True) == "assert 1 == 2"
def test_invoke_assertion():
py.magic.invoke(assertion=True)
try:
check_assertion()
finally:
py.magic.revoke(assertion=True)
def test_invoke_compile():
py.magic.invoke(compile=True)
try:
co = compile("""if 1:
def f():
return 1
\n""", '', 'exec')
d = {}
exec co in d
assert py.code.Source(d['f'])
finally:
py.magic.revoke(compile=True)

View File

@ -1,31 +0,0 @@
from py.test import raises
from py.magic import patch, revert
def test_patch_revert():
class a:
pass
raises(AttributeError, "patch(a, 'i', 42)")
a.i = 42
patch(a, 'i', 23)
assert a.i == 23
revert(a, 'i')
assert a.i == 42
def test_double_patch():
class a:
i = 42
assert patch(a, 'i', 2) == 42
assert patch(a, 'i', 3) == 2
assert a.i == 3
assert revert(a, 'i') == 3
assert a.i == 2
assert revert(a, 'i') == 2
assert a.i == 42
def test_valueerror():
class a:
i = 2
pass
raises(ValueError, "revert(a, 'i')")

View File

@ -1,55 +0,0 @@
from py.__.magic.viewtype import View
def test_class_dispatch():
### Use a custom class hierarchy with existing instances
class Picklable(View):
pass
class Simple(Picklable):
__view__ = object
def pickle(self):
return repr(self.__obj__)
class Seq(Picklable):
__view__ = list, tuple, dict
def pickle(self):
return ';'.join([Picklable(item).pickle() for item in self.__obj__])
class Dict(Seq):
__view__ = dict
def pickle(self):
return Seq.pickle(self) + '!' + Seq(self.values()).pickle()
assert Picklable(123).pickle() == '123'
assert Picklable([1,[2,3],4]).pickle() == '1;2;3;4'
assert Picklable({1:2}).pickle() == '1!2'
def test_custom_class_hierarchy():
### Use a custom class hierarchy based on attributes of existing instances
class Operation:
"Existing class that I don't want to change."
def __init__(self, opname, *args):
self.opname = opname
self.args = args
existing = [Operation('+', 4, 5),
Operation('getitem', '', 'join'),
Operation('setattr', 'x', 'y', 3),
Operation('-', 12, 1)]
class PyOp(View):
def __viewkey__(self):
return self.opname
def generate(self):
return '%s(%s)' % (self.opname, ', '.join(map(repr, self.args)))
class PyBinaryOp(PyOp):
__view__ = ('+', '-', '*', '/')
def generate(self):
return '%s %s %s' % (self.args[0], self.opname, self.args[1])
codelines = [PyOp(op).generate() for op in existing]
assert codelines == ["4 + 5", "getitem('', 'join')", "setattr('x', 'y', 3)", "12 - 1"]

View File

@ -1,94 +0,0 @@
"""
The View base class for view-based programming.
A view of an object is an extension of this existing object.
This is useful to *locally* add methods or even attributes to objects
that you have obtained from elsewhere.
"""
from __future__ import generators
import inspect
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

View File

@ -5,7 +5,6 @@ import py
#
def main(args=None):
warn_about_missing_assertion()
if args is None:
args = py.std.sys.argv[1:]
config = py.test.config
@ -20,11 +19,3 @@ def main(args=None):
py.std.sys.stderr.write("ERROR: %s\n" %(e.args[0],))
raise SystemExit(3)
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?)")

View File

@ -10,5 +10,5 @@ Generator = py.test.collect.Generator
Function = py.test.collect.Function
Instance = py.test.collect.Instance
pytest_plugins = "default runner capture terminal keyword xfail tmpdir execnetcleanup monkeypatch recwarn pdb pastebin unittest helpconfig nose".split()
pytest_plugins = "default runner capture terminal keyword xfail tmpdir execnetcleanup monkeypatch recwarn pdb pastebin unittest helpconfig nose assertion".split()

View File

@ -0,0 +1,68 @@
import py
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"):
warn_about_missing_assertion()
config._oldassertion = py.std.__builtin__.AssertionError
py.std.__builtin__.AssertionError = py.code._AssertionError
def pytest_unconfigure(config):
if hasattr(config, '_oldassertion'):
py.std.__builtin__.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?)")
def test_functional(testdir):
testdir.makepyfile("""
def test_hello():
x = 3
assert x == 4
""")
result = testdir.runpytest()
assert "3 == 4" in result.stdout.str()
result = testdir.runpytest("--no-assert")
assert "3 == 4" not in result.stdout.str()
def test_traceback_failure(testdir):
p1 = testdir.makepyfile("""
def g():
return 2
def f(x):
assert x == g()
def test_onefails():
f(3)
""")
result = testdir.runpytest(p1)
result.stdout.fnmatch_lines([
"*test_traceback_failure.py F",
"====* FAILURES *====",
"____*____",
"",
" def test_onefails():",
"> f(3)",
"",
"*test_*.py:6: ",
"_ _ _ *",
#"",
" def f(x):",
"> assert x == g()",
"E assert 3 == 2",
"E + where 2 = g()",
"",
"*test_traceback_failure.py:4: AssertionError"
])

View File

@ -564,36 +564,6 @@ class TestTerminalFunctional:
"=* 1 passed in *.[0-9][0-9] seconds *=",
])
def test_traceback_failure(self, testdir):
p1 = testdir.makepyfile("""
def g():
return 2
def f(x):
assert x == g()
def test_onefails():
f(3)
""")
result = testdir.runpytest(p1)
result.stdout.fnmatch_lines([
"*test_traceback_failure.py F",
"====* FAILURES *====",
"____*____",
"",
" def test_onefails():",
"> f(3)",
"",
"*test_*.py:6: ",
"_ _ _ *",
#"",
" def f(x):",
"> assert x == g()",
"E assert 3 == 2",
"E + where 2 = g()",
"",
"*test_traceback_failure.py:4: AssertionError"
])
def test_showlocals(self, testdir):
p1 = testdir.makepyfile("""
def test_showlocals():

View File

@ -183,21 +183,13 @@ class Module(py.test.collect.File, PyCollectorMixin):
def setup(self):
if getattr(self.obj, 'disabled', 0):
py.test.skip("%r is disabled" %(self.obj,))
if not self.config.option.nomagic:
#print "*" * 20, "INVOKE assertion", self
py.magic.invoke(assertion=1)
mod = self.obj
#self.config.pluginmanager.register(mod)
if hasattr(mod, 'setup_module'):
self.obj.setup_module(mod)
def teardown(self):
if hasattr(self.obj, 'teardown_module'):
self.obj.teardown_module(self.obj)
if not self.config.option.nomagic:
#print "*" * 20, "revoke assertion", self
py.magic.revoke(assertion=1)
#self.config.pluginmanager.unregister(self.obj)
class Class(PyCollectorMixin, py.test.collect.Collector):

View File

@ -22,19 +22,6 @@ class TestModule:
py.test.raises(SyntaxError, modcol.collect)
py.test.raises(SyntaxError, modcol.run)
def test_module_assertion_setup(self, testdir, monkeypatch):
modcol = testdir.getmodulecol("pass")
from py.__.magic import assertion
l = []
monkeypatch.setattr(assertion, "invoke", lambda: l.append(None))
modcol.setup()
x = l.pop()
assert x is None
monkeypatch.setattr(assertion, "revoke", lambda: l.append(None))
modcol.teardown()
x = l.pop()
assert x is None
def test_module_considers_pluginmanager_at_import(self, testdir):
modcol = testdir.getmodulecol("pytest_plugins='xasdlkj',")
py.test.raises(ImportError, "modcol.obj")