The PEAK Developers' Center   UnitTrace UserPreferences
 
HelpContents Search Diffs Info Edit Subscribe XML Print View
The following 336 words could not be found in the dictionary of 50 words (including 50 LocalSpellingWords) and are highlighted below:
An   And   Args   As   Before   Both   Bound   But   Call   Callable   Calls   Contents   Each   Error   Events   Finally   For   Func   Function   Here   Histories   History   How   Implementation   Invocations   It   Iteration   Likewise   No   None   Now   Objects   Once   Only   Otherwise   Preface   Profiling   Python   Sometimes   Specifically   Stop   Table   Testing   The   They   This   To   Traceback   Type   Unfortunately   We   What   Works   able   about   access   active   addition   against   all   also   an   and   another   any   are   args   argument   arguments   as   assert   at   attribute   attributes   back   be   become   been   begin   below   but   by   call   callable   callables   called   calls   can   cannot   cause   certain   chain   change   checked   class   close   code   collection   compare   contents   copied   corresponding   corrupted   couple   create   created   creation   current   currently   deeply   def   default   dependent   depth   detection   dictionary   differ   directly   document   does   during   each   effect   equal   error   even   event   events   exactly   examples   exceeded   exception   exceptions   existing   extra   f1   f2   f3   f4   f5   f6   find   found   frame   frametest   frequently   from   func   function   functions   further   getframe   given   had   has   have   history   hook   how   identical   if   import   important   in   innermost   input   instance   instances   intact   into   invocation   invocations   invoke   invoked   is   isn   it   iter   iterable   iterator   iterators   keyword   keywords   known   last   least   less   let   level   limited   list   ll   locals   logged   makes   match   matched   matches   matching   maximum   means   method   methods   module   more   most   need   nested   new   next   no   not   note   number   object   objects   obtain   occur   occurred   occurs   of   offer   offers   on   once   one   only   optional   or   order   original   other   over   parameters   part   particular   pass   passed   peak   populated   positional   print   private   profiled   profiler   profiling   prove   provides   queriable   queryable   raise   raises   raising   rebinds   receives   recent   record   recorded   registered   remain   representing   result   results   return   returned   returns   run   saw   second   see   self   set   setprofile   setting   should   simple   so   specified   specify   split   splits   stored   subject   sufficient   supplied   sys   system   takes   target   targets   tells   test   testing   tests   than   that   the   their   then   they   third   this   though   time   to   too   trace   traced   tracing   track   treat   true   tuple   twice   unit   unittrace   until   use   used   using   usual   util   value   various   verifying   via   want   was   way   we   what   whether   which   with   wrappers   you  

Clear message


Testing Invocations with unittrace

Table of Contents

Preface

The unittrace module offers a simple way to test whether certain function or method calls occurred, with what arguments, and in what order.

Before we begin, it's important to note that the unittrace module makes use of the system profiling hook, sys.setprofile(). This means that unit tests that use unittrace cannot be profiled, and may cause an active profiler's results to become corrupted. (Unfortunately, Python does not currently offer a way to obtain the current profiler or trace hook, so unittrace cannot call the existing hook(s) and so chain back to an existing profiler.)

For the tests/examples in this document, we'll be testing whether the foo method of this class has been called:

>>> class Foo:
...     def foo(self,x=None):
...         pass

We need to be able to test function-call detection, too:

>>> foo = Foo.foo

And we'll frequently be verifying that a method of a particular instance was called, so we need a couple of "particular instances" to call methods on:

>>> aFoo = Foo()
>>> bFoo = Foo()

The unittrace API

History Objects

History objects are a queriable collection of Call events, that can be populated via profiler tracing:

>>> from peak.util.unittrace import History
>>> h = History()

Calls are traced using the trace() method, which takes a callable object, and any positional or keyword arguments that should be passed into it:

>>> h.trace(aFoo.foo,42)
>>> h.trace(bFoo.foo,x=27)

Once calls are logged, the history is an iterable collection of Call objects, which compare equal to the corresponding functions or methods invoked:

>>> list(h)
[Call(...), Call(...)]

>>> list(h) == [aFoo.foo, bFoo.foo]
1

Histories are also queryable; you can use the callsTo() method to return an iterator over matching calls. Calls are matched by function/method, and optional keyword arguments. Only the supplied keywords are checked:

>>> list(h.callsTo(foo))
[Call(...), Call(...)]
>>> list(h.callsTo(aFoo.foo)) == [aFoo.foo]
1
>>> list(h.callsTo(bFoo.foo)) == [bFoo.foo]
1
>>> list(h.callsTo(foo,x=42)) == [aFoo.foo]
1
>>> list(h.callsTo(foo,x=27)) == [bFoo.foo]
1

Histories have a called() method, that tells you whether a given callable was called at least once during a trace(). As with callsTo(), you can match methods, functions, and arguments:

>>> assert h.called(foo)
>>> assert h.called(aFoo.foo)
>>> assert h.called(bFoo.foo)
>>> assert h.called(foo, x=42)
>>> assert h.called(foo, x=27)
>>> assert h.called(aFoo.foo, x=42)
>>> assert h.called(bFoo.foo, x=27)
>>> assert not h.called(aFoo.foo, x=27)
>>> assert not h.called(bFoo.foo, x=42)
>>> assert not h.called(foo, x=99)

Sometimes, you want to test that a function was called exactly once (no more, no less). This is what the calledOnce() method does:

>>> assert h.calledOnce(aFoo.foo)
>>> assert h.calledOnce(bFoo.foo)
>>> assert not h.calledOnce(foo)    # foo function was called twice!
>>> assert h.calledOnce(foo, x=42)
>>> assert h.calledOnce(foo, x=27)
>>> assert h.calledOnce(aFoo.foo, x=42)
>>> assert h.calledOnce(bFoo.foo, x=27)

Both called() and calledOnce() return a Call object (see Call objects, below), if they find a match. Otherwise, they return None:

>>> h.called(foo)
Call(...)

>>> h.calledOnce(aFoo.foo)
Call(...)

>>> print h.called(foo, x=99)
None

Histories are iterable, but their iterators have an extra find() method in addition to the usual next() method. find() raises StopIteration if the target isn't found:

>>> it = iter(h)
>>> it.find(foo) == foo
1
>>> it.find(foo) == foo
1
>>> it.find(foo) == foo
Traceback (most recent call last):
...
StopIteration

>>> it = iter(h)
>>> it.find(aFoo.foo) == aFoo.foo
1
>>> it.find(aFoo.foo) == aFoo.foo
Traceback (most recent call last):
...
StopIteration

>>> it = iter(h)
>>> it.find(foo,x=27) == bFoo.foo
1
>>> it.find(foo,x=27) == bFoo.foo
Traceback (most recent call last):
...
StopIteration

Histories can be limited as to how deeply they should track nested calls, and you can specify the maximum depth at creation time. (The default depth is 5.) Here, we'll create a set of functions that call each other, with the innermost one raising an error, to prove that even though all the functions are called, the number of invocations recorded is dependent on the maximum depth setting:

>>> def f1(): f2()
>>> def f2(): f3()
>>> def f3(): f4()
>>> def f4(): f5()
>>> def f5(): f6()
>>> def f6(): raise TypeError
>>> h = History()
>>> h.trace(f1)
Traceback (most recent call last):
...
TypeError
>>> list(h) == [f1,f2,f3,f4,f5]
1
>>> h = History(2)
>>> h.trace(f1)
Traceback (most recent call last):
...
TypeError
>>> list(h) == [f1,f2]
1

Call objects

Call objects are used to record invocations of callables, and are what are returned by History iterators. They have an args attribute that provides access to the various arguments, as attributes:

>>> h = History()
>>> h.trace(aFoo.foo,42)
>>> [call] = list(h)
>>> call.args.x
42

Calls also have a hadArgs() method that can be used to match a set of input parameters against the call. The supplied keyword arguments are matched against the stored arguments, and if all of the supplied arguments are equal, then hadArgs() returns a true value:

>>> assert call.hadArgs(x=42)
>>> assert call.hadArgs(self=aFoo)
>>> assert call.hadArgs(self=aFoo, x=42)
>>> assert not call.hadArgs(a='q')
>>> assert not call.hadArgs(b=42)

Finally, Call objects compare equal to callables that match their code object and/or arguments, or to identical invocations:

>>> h = History()
>>> h.trace(aFoo.foo)
>>> h.trace(bFoo.foo)
>>> [aCall, bCall] = list(h)

>>> aCall == bCall      # parameters differ, so not equal
0
>>> aCall == foo        # code matches, no args specified, so equal
1
>>> bCall == foo        # code matches, no args specified, so equal
1
>>> aCall == aFoo.foo   # code and argument matches, so equal
1
>>> bCall == bFoo.foo   # code and argument matches, so equal
1
>>> aCall == bFoo.foo   # argument specified, but doesn't match
0
>>> bCall == aFoo.foo   # argument specified, but doesn't match
0

How It Works (subject to change!)

Profiling Events

History objects obtain events via the system profiler hook. Specifically, they have a trace_event() method that receives a frame, event, and argument from the profiler hook. To test this, we need a frame object that was used to invoke a known function:

>>> import sys
>>> def frametest_func():
...     return sys._getframe()
>>> frame = frametest_func()

Each time a "call" event occurs, it should become part of the history:

>>> h = History(2)
>>> h.trace_event(frame,'call',None)
>>> list(h) == [frametest_func]
1
>>> h.trace_event(frame,'call',None)
>>> list(h) == [frametest_func]*2
1

But, once the call depth has been exceeded, no further call events should be registered:

>>> h.trace_event(frame,'call',None)
>>> list(h) == [frametest_func]*2
1

...until sufficient "return" events have occurred:

>>> h.trace_event(frame,'return',None)    # close the third level
>>> h.trace_event(frame,'return',None)    # close the second level
>>> h.trace_event(frame,'call',None)      # record a new second-level call
>>> list(h) == [frametest_func]*3
1

What about exceptions? An exception event can occur more than once in a given frame, so we should treat an exception as a return, if and only if the frame passed is not the frame we saw last:

>>> h.trace_event(frame,'exception',())   # No effect...
>>> h.trace_event(frame,'call',None)      # Likewise
>>> list(h) == [frametest_func]*3
1
>>> h.trace_event(frametest_func(),'exception',())    # close third-level
>>> h.trace_event(frametest_func(),'exception',())    # close second-level
>>> h.trace_event(frame,'call',None)      # record a new second-level call
>>> list(h) == [frametest_func]*4
1

Now let's see if we can run the profiler on a history:

>>> h = History(2)
>>> result = h.trace(frametest_func)
>>> list(h)==[frametest_func]
1

>>> def anotherFunc(x):
...     frametest_func()

>>> h = History(2)
>>> h.trace(anotherFunc, 27)
>>> list(h)==[anotherFunc, frametest_func]
1

>>> h.called(anotherFunc).args.x
27

Call Implementation

Call objects are used to record invocations of callables, and are what are returned by History iterators. They are created using a code object and a locals dictionary:

>>> foo_code = foo.func_code
>>> args = {'a':'b','q':42}
>>> from peak.util.unittrace import Call
>>> c = Call(foo_code, args)

They have an args attribute that provides access to the various arguments, as attributes:

>>> c.args.a
'b'
>>> c.args.q
42

The original input locals are copied, so that in the event the invocation rebinds any locals, the original arguments remain intact:

>>> args['a']=999
>>> c.args.a
'b'

The splitCallable() Function

This is a private function, so we need to import it directly:

>>> from peak.util.unittrace import splitCallable

It splits a callable (function or method) into a code object, and a tuple of positional arguments representing the targets of any method wrappers:

# Function -> code,()
>>> assert splitCallable(foo) == (foo_code,())

# Bound method -> code, (self,)
>>> assert splitCallable(aFoo.foo) == (foo_code,(aFoo,))
>>> assert splitCallable(aFoo.foo) == (foo_code,(aFoo,))

PythonPowered
EditText of this page (last modified 2005-01-06 22:02:45)
FindPage by browsing, title search , text search or an index
Or try one of these actions: AttachFile, DeletePage, LikePages, LocalSiteMap, SpellCheck