Fixtures to help testing functionality#

Utilities which modify or replace code to help with doctesting functionality. Wrappers, proxies and mockups are typical examples of fixtures.

AUTHORS:

  • Martin von Gagern (2014-12-15): AttributeAccessTracerProxy and trace_method

  • Martin von Gagern (2015-01-02): Factor out TracerHelper and reproducible_repr

EXAMPLES:

You can use trace_method() to see how a method communicates with its surroundings:

sage: class Foo():
....:     def f(self):
....:         self.y = self.g(self.x)
....:     def g(self, arg):
....:         return arg + 1
....:
sage: foo = Foo()
sage: foo.x = 3
sage: from sage.doctest.fixtures import trace_method
sage: trace_method(foo, "f")
sage: foo.f()
enter f()
  read x = 3
  call g(3) -> 4
  write y = 4
exit f -> None
>>> from sage.all import *
>>> class Foo():
...     def f(self):
...         self.y = self.g(self.x)
...     def g(self, arg):
...         return arg + Integer(1)
....:
>>> foo = Foo()
>>> foo.x = Integer(3)
>>> from sage.doctest.fixtures import trace_method
>>> trace_method(foo, "f")
>>> foo.f()
enter f()
  read x = 3
  call g(3) -> 4
  write y = 4
exit f -> None
class sage.doctest.fixtures.AttributeAccessTracerHelper(delegate, prefix='  ', reads=True)[source]#

Bases: object

Helper to print proxied access to attributes.

This class does the actual printing of access traces for objects proxied by AttributeAccessTracerProxy. The fact that it’s not a proxy at the same time helps avoiding complicated attribute access syntax.

INPUT:

  • delegate – the actual object to be proxied.

  • prefix – (default: "  ") string to prepend to each printed output.

  • reads – (default: True) whether to trace read access as well.

EXAMPLES:

sage: class Foo():
....:     def f(self, *args):
....:         return self.x*self.x
....:
sage: foo = Foo()
sage: from sage.doctest.fixtures import AttributeAccessTracerHelper
sage: pat = AttributeAccessTracerHelper(foo)
sage: pat.set("x", 2)
  write x = 2
sage: pat.get("x")
  read x = 2
2
sage: pat.get("f")(3)
  call f(3) -> 4
4
>>> from sage.all import *
>>> class Foo():
...     def f(self, *args):
...         return self.x*self.x
....:
>>> foo = Foo()
>>> from sage.doctest.fixtures import AttributeAccessTracerHelper
>>> pat = AttributeAccessTracerHelper(foo)
>>> pat.set("x", Integer(2))
  write x = 2
>>> pat.get("x")
  read x = 2
2
>>> pat.get("f")(Integer(3))
  call f(3) -> 4
4
get(name)[source]#

Read an attribute from the wrapped delegate object.

If that value is a method (i.e. a callable object which is not contained in the dictionary of the object itself but instead inherited from some class) then it is replaced by a wrapper function to report arguments and return value. Otherwise an attribute read access is reported.

EXAMPLES:

sage: class Foo():
....:     def f(self, *args):
....:         return self.x*self.x
....:
sage: foo = Foo()
sage: foo.x = 2
sage: from sage.doctest.fixtures import AttributeAccessTracerHelper
sage: pat = AttributeAccessTracerHelper(foo)
sage: pat.get("x")
  read x = 2
2
sage: pat.get("f")(3)
  call f(3) -> 4
4
>>> from sage.all import *
>>> class Foo():
...     def f(self, *args):
...         return self.x*self.x
....:
>>> foo = Foo()
>>> foo.x = Integer(2)
>>> from sage.doctest.fixtures import AttributeAccessTracerHelper
>>> pat = AttributeAccessTracerHelper(foo)
>>> pat.get("x")
  read x = 2
2
>>> pat.get("f")(Integer(3))
  call f(3) -> 4
4
set(name, val)[source]#

Write an attribute to the wrapped delegate object.

The name and new value are also reported in the output.

EXAMPLES:

sage: class Foo():
....:     pass
....:
sage: foo = Foo()
sage: from sage.doctest.fixtures import AttributeAccessTracerHelper
sage: pat = AttributeAccessTracerHelper(foo)
sage: pat.set("x", 2)
  write x = 2
sage: foo.x
2
>>> from sage.all import *
>>> class Foo():
...     pass
....:
>>> foo = Foo()
>>> from sage.doctest.fixtures import AttributeAccessTracerHelper
>>> pat = AttributeAccessTracerHelper(foo)
>>> pat.set("x", Integer(2))
  write x = 2
>>> foo.x
2
class sage.doctest.fixtures.AttributeAccessTracerProxy(delegate, **kwds)[source]#

Bases: object

Proxy object which prints all attribute and method access to an object.

The implementation is kept lean since all access to attributes of the proxy itself requires complicated syntax. For this reason, the actual handling of attribute access is delegated to a AttributeAccessTracerHelper.

INPUT:

  • delegate – the actual object to be proxied.

  • prefix – (default: "  ") string to prepend to each printed output.

  • reads – (default: True) whether to trace read access as well.

EXAMPLES:

sage: class Foo():
....:     def f(self, *args):
....:         return self.x*self.x
....:
sage: foo = Foo()
sage: from sage.doctest.fixtures import AttributeAccessTracerProxy
sage: pat = AttributeAccessTracerProxy(foo)
sage: pat.x = 2
  write x = 2
sage: pat.x
  read x = 2
2
sage: pat.f(3)
  call f(3) -> 4
4
>>> from sage.all import *
>>> class Foo():
...     def f(self, *args):
...         return self.x*self.x
....:
>>> foo = Foo()
>>> from sage.doctest.fixtures import AttributeAccessTracerProxy
>>> pat = AttributeAccessTracerProxy(foo)
>>> pat.x = Integer(2)
  write x = 2
>>> pat.x
  read x = 2
2
>>> pat.f(Integer(3))
  call f(3) -> 4
4
__getattribute__(name)[source]#

Read an attribute from the wrapped delegate object.

If that value is a method (i.e. a callable object which is not contained in the dictionary of the object itself but instead inherited from some class) then it is replaced by a wrapper function to report arguments and return value. Otherwise an attribute read access is reported.

EXAMPLES:

sage: class Foo():
....:     def f(self, *args):
....:         return self.x*self.x
....:
sage: foo = Foo()
sage: foo.x = 2
sage: from sage.doctest.fixtures import AttributeAccessTracerProxy
sage: pat = AttributeAccessTracerProxy(foo)
sage: pat.x
  read x = 2
2
sage: pat.f(3)
  call f(3) -> 4
4
>>> from sage.all import *
>>> class Foo():
...     def f(self, *args):
...         return self.x*self.x
....:
>>> foo = Foo()
>>> foo.x = Integer(2)
>>> from sage.doctest.fixtures import AttributeAccessTracerProxy
>>> pat = AttributeAccessTracerProxy(foo)
>>> pat.x
  read x = 2
2
>>> pat.f(Integer(3))
  call f(3) -> 4
4
__setattr__(name, val)[source]#

Write an attribute to the wrapped delegate object.

The name and new value are also reported in the output.

EXAMPLES:

sage: class Foo():
....:     pass
....:
sage: foo = Foo()
sage: from sage.doctest.fixtures import AttributeAccessTracerProxy
sage: pat = AttributeAccessTracerProxy(foo)
sage: pat.x = 2
  write x = 2
sage: foo.x
2
>>> from sage.all import *
>>> class Foo():
...     pass
....:
>>> foo = Foo()
>>> from sage.doctest.fixtures import AttributeAccessTracerProxy
>>> pat = AttributeAccessTracerProxy(foo)
>>> pat.x = Integer(2)
  write x = 2
>>> foo.x
2
sage.doctest.fixtures.reproducible_repr(val)[source]#

String representation of an object in a reproducible way.

This tries to ensure that the returned string does not depend on factors outside the control of the doctest. One example is the order of elements in a hash-based structure. For most objects, this is simply the repr of the object.

All types for which special handling had been implemented are covered by the examples below. If a doctest requires special handling for additional types, this function may be extended appropriately. It is an error if an argument to this function has a non-reproducible repr implementation and is not explicitly mentioned in an example case below.

INPUT:

  • val – an object to be represented

OUTPUT:

A string representation of that object, similar to what repr returns but for certain cases with more guarantees to ensure exactly the same result for semantically equivalent objects.

EXAMPLES:

sage: from sage.doctest.fixtures import reproducible_repr
sage: print(reproducible_repr(set(["a", "c", "b", "d"])))
set(['a', 'b', 'c', 'd'])
sage: print(reproducible_repr(frozenset(["a", "c", "b", "d"])))
frozenset(['a', 'b', 'c', 'd'])
sage: print(reproducible_repr([1, frozenset("cab"), set("bar"), 0]))
[1, frozenset(['a', 'b', 'c']), set(['a', 'b', 'r']), 0]
sage: print(reproducible_repr({3.0: "three", "2": "two", 1: "one"}))            # optional - sage.rings.real_mpfr
{'2': 'two', 1: 'one', 3.00000000000000: 'three'}
sage: print(reproducible_repr("foo\nbar"))  # demonstrate default case
'foo\nbar'
>>> from sage.all import *
>>> from sage.doctest.fixtures import reproducible_repr
>>> print(reproducible_repr(set(["a", "c", "b", "d"])))
set(['a', 'b', 'c', 'd'])
>>> print(reproducible_repr(frozenset(["a", "c", "b", "d"])))
frozenset(['a', 'b', 'c', 'd'])
>>> print(reproducible_repr([Integer(1), frozenset("cab"), set("bar"), Integer(0)]))
[1, frozenset(['a', 'b', 'c']), set(['a', 'b', 'r']), 0]
>>> print(reproducible_repr({RealNumber('3.0'): "three", "2": "two", Integer(1): "one"}))            # optional - sage.rings.real_mpfr
{'2': 'two', 1: 'one', 3.00000000000000: 'three'}
>>> print(reproducible_repr("foo\nbar"))  # demonstrate default case
'foo\nbar'
sage.doctest.fixtures.trace_method(obj, meth, **kwds)[source]#

Trace the doings of a given method. It prints method entry with arguments, access to members and other methods during method execution as well as method exit with return value.

INPUT:

  • obj – the object containing the method.

  • meth – the name of the method to be traced.

  • prefix – (default: "  ") string to prepend to each printed output.

  • reads – (default: True) whether to trace read access as well.

EXAMPLES:

sage: class Foo():
....:     def f(self, arg=None):
....:         self.y = self.g(self.x)
....:         if arg: return arg*arg
....:     def g(self, arg):
....:         return arg + 1
....:
sage: foo = Foo()
sage: foo.x = 3
sage: from sage.doctest.fixtures import trace_method
sage: trace_method(foo, "f")
sage: foo.f()
enter f()
  read x = 3
  call g(3) -> 4
  write y = 4
exit f -> None
sage: foo.f(3)
enter f(3)
  read x = 3
  call g(3) -> 4
  write y = 4
exit f -> 9
9
>>> from sage.all import *
>>> class Foo():
...     def f(self, arg=None):
...         self.y = self.g(self.x)
...         if arg: return arg*arg
...     def g(self, arg):
...         return arg + Integer(1)
....:
>>> foo = Foo()
>>> foo.x = Integer(3)
>>> from sage.doctest.fixtures import trace_method
>>> trace_method(foo, "f")
>>> foo.f()
enter f()
  read x = 3
  call g(3) -> 4
  write y = 4
exit f -> None
>>> foo.f(Integer(3))
enter f(3)
  read x = 3
  call g(3) -> 4
  write y = 4
exit f -> 9
9