diff --git a/CHANGELOG b/CHANGELOG index 74e2ebae3..52bf9e503 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,8 @@ NEXT (2.6) ----------------------------------- +- fix issue514: teach assertion reinterpretation about private class attributes + - change -v output to include full node IDs of tests. Users can copy a node ID from a test run, including line number, and use it as a positional argument in order to run only a single test. diff --git a/_pytest/assertion/newinterpret.py b/_pytest/assertion/newinterpret.py index e7e9658d7..d8b27784d 100644 --- a/_pytest/assertion/newinterpret.py +++ b/_pytest/assertion/newinterpret.py @@ -286,7 +286,19 @@ class DebugInterpreter(ast.NodeVisitor): source = "__exprinfo_expr.%s" % (attr.attr,) co = self._compile(source) try: - result = self.frame.eval(co, __exprinfo_expr=source_result) + try: + result = self.frame.eval(co, __exprinfo_expr=source_result) + except AttributeError: + # Maybe the attribute name needs to be mangled? + if not attr.attr.startswith("__") or attr.attr.endswith("__"): + raise + source = "getattr(__exprinfo_expr.__class__, '__name__', '')" + co = self._compile(source) + class_name = self.frame.eval(co, __exprinfo_expr=source_result) + mangled_attr = "_" + class_name + attr.attr + source = "__exprinfo_expr.%s" % (mangled_attr,) + co = self._compile(source) + result = self.frame.eval(co, __exprinfo_expr=source_result) except Exception: raise Failure(explanation) explanation = "%s\n{%s = %s.%s\n}" % (self.frame.repr(result), diff --git a/_pytest/assertion/oldinterpret.py b/_pytest/assertion/oldinterpret.py index 27cfff03d..df737b7d5 100644 --- a/_pytest/assertion/oldinterpret.py +++ b/_pytest/assertion/oldinterpret.py @@ -355,7 +355,18 @@ class Getattr(Interpretable): expr.eval(frame) source = '__exprinfo_expr.%s' % self.attrname try: - self.result = frame.eval(source, __exprinfo_expr=expr.result) + try: + self.result = frame.eval(source, __exprinfo_expr=expr.result) + except AttributeError: + # Maybe the attribute name needs to be mangled? + if (not self.attrname.startswith("__") or + self.attrname.endswith("__")): + raise + source = "getattr(__exprinfo_expr.__class__, '__name__', '')" + class_name = frame.eval(source, __exprinfo_expr=expr.result) + mangled_attr = "_" + class_name + self.attrname + source = "__exprinfo_expr.%s" % (mangled_attr,) + self.result = frame.eval(source, __exprinfo_expr=expr.result) except passthroughex: raise except: diff --git a/testing/test_assertinterpret.py b/testing/test_assertinterpret.py index eb9cc57dc..209bdfd49 100644 --- a/testing/test_assertinterpret.py +++ b/testing/test_assertinterpret.py @@ -131,6 +131,18 @@ def test_assert_keyword_arg(): e = exvalue() assert "x=5" in e.msg +def test_private_class_variable(): + class X: + def __init__(self): + self.__v = 41 + def m(self): + assert self.__v == 42 + try: + X().m() + except AssertionError: + e = exvalue() + assert "== 42" in e.msg + # These tests should both fail, but should fail nicely... class WeirdRepr: def __repr__(self):