Home | History | Annotate | Download | only in test
      1 import gc
      2 import pprint
      3 import sys
      4 import unittest
      5 
      6 from test import test_support
      7 
      8 class TestGetProfile(unittest.TestCase):
      9     def setUp(self):
     10         sys.setprofile(None)
     11 
     12     def tearDown(self):
     13         sys.setprofile(None)
     14 
     15     def test_empty(self):
     16         self.assertIsNone(sys.getprofile())
     17 
     18     def test_setget(self):
     19         def fn(*args):
     20             pass
     21 
     22         sys.setprofile(fn)
     23         self.assertIs(sys.getprofile(), fn)
     24 
     25 class HookWatcher:
     26     def __init__(self):
     27         self.frames = []
     28         self.events = []
     29 
     30     def callback(self, frame, event, arg):
     31         if (event == "call"
     32             or event == "return"
     33             or event == "exception"):
     34             self.add_event(event, frame)
     35 
     36     def add_event(self, event, frame=None):
     37         """Add an event to the log."""
     38         if frame is None:
     39             frame = sys._getframe(1)
     40 
     41         try:
     42             frameno = self.frames.index(frame)
     43         except ValueError:
     44             frameno = len(self.frames)
     45             self.frames.append(frame)
     46 
     47         self.events.append((frameno, event, ident(frame)))
     48 
     49     def get_events(self):
     50         """Remove calls to add_event()."""
     51         disallowed = [ident(self.add_event.im_func), ident(ident)]
     52         self.frames = None
     53 
     54         return [item for item in self.events if item[2] not in disallowed]
     55 
     56 
     57 class ProfileSimulator(HookWatcher):
     58     def __init__(self, testcase):
     59         self.testcase = testcase
     60         self.stack = []
     61         HookWatcher.__init__(self)
     62 
     63     def callback(self, frame, event, arg):
     64         # Callback registered with sys.setprofile()/sys.settrace()
     65         self.dispatch[event](self, frame)
     66 
     67     def trace_call(self, frame):
     68         self.add_event('call', frame)
     69         self.stack.append(frame)
     70 
     71     def trace_return(self, frame):
     72         self.add_event('return', frame)
     73         self.stack.pop()
     74 
     75     def trace_exception(self, frame):
     76         self.testcase.fail(
     77             "the profiler should never receive exception events")
     78 
     79     def trace_pass(self, frame):
     80         pass
     81 
     82     dispatch = {
     83         'call': trace_call,
     84         'exception': trace_exception,
     85         'return': trace_return,
     86         'c_call': trace_pass,
     87         'c_return': trace_pass,
     88         'c_exception': trace_pass,
     89         }
     90 
     91 
     92 class TestCaseBase(unittest.TestCase):
     93     def check_events(self, callable, expected):
     94         events = capture_events(callable, self.new_watcher())
     95         if events != expected:
     96             self.fail("Expected events:\n%s\nReceived events:\n%s"
     97                       % (pprint.pformat(expected), pprint.pformat(events)))
     98 
     99 
    100 class ProfileHookTestCase(TestCaseBase):
    101     def new_watcher(self):
    102         return HookWatcher()
    103 
    104     def test_simple(self):
    105         def f(p):
    106             pass
    107         f_ident = ident(f)
    108         self.check_events(f, [(1, 'call', f_ident),
    109                               (1, 'return', f_ident),
    110                               ])
    111 
    112     def test_exception(self):
    113         def f(p):
    114             1./0
    115         f_ident = ident(f)
    116         self.check_events(f, [(1, 'call', f_ident),
    117                               (1, 'return', f_ident),
    118                               ])
    119 
    120     def test_caught_exception(self):
    121         def f(p):
    122             try: 1./0
    123             except: pass
    124         f_ident = ident(f)
    125         self.check_events(f, [(1, 'call', f_ident),
    126                               (1, 'return', f_ident),
    127                               ])
    128 
    129     def test_caught_nested_exception(self):
    130         def f(p):
    131             try: 1./0
    132             except: pass
    133         f_ident = ident(f)
    134         self.check_events(f, [(1, 'call', f_ident),
    135                               (1, 'return', f_ident),
    136                               ])
    137 
    138     def test_nested_exception(self):
    139         def f(p):
    140             1./0
    141         f_ident = ident(f)
    142         self.check_events(f, [(1, 'call', f_ident),
    143                               # This isn't what I expected:
    144                               # (0, 'exception', protect_ident),
    145                               # I expected this again:
    146                               (1, 'return', f_ident),
    147                               ])
    148 
    149     def test_exception_in_except_clause(self):
    150         def f(p):
    151             1./0
    152         def g(p):
    153             try:
    154                 f(p)
    155             except:
    156                 try: f(p)
    157                 except: pass
    158         f_ident = ident(f)
    159         g_ident = ident(g)
    160         self.check_events(g, [(1, 'call', g_ident),
    161                               (2, 'call', f_ident),
    162                               (2, 'return', f_ident),
    163                               (3, 'call', f_ident),
    164                               (3, 'return', f_ident),
    165                               (1, 'return', g_ident),
    166                               ])
    167 
    168     def test_exception_propogation(self):
    169         def f(p):
    170             1./0
    171         def g(p):
    172             try: f(p)
    173             finally: p.add_event("falling through")
    174         f_ident = ident(f)
    175         g_ident = ident(g)
    176         self.check_events(g, [(1, 'call', g_ident),
    177                               (2, 'call', f_ident),
    178                               (2, 'return', f_ident),
    179                               (1, 'falling through', g_ident),
    180                               (1, 'return', g_ident),
    181                               ])
    182 
    183     def test_raise_twice(self):
    184         def f(p):
    185             try: 1./0
    186             except: 1./0
    187         f_ident = ident(f)
    188         self.check_events(f, [(1, 'call', f_ident),
    189                               (1, 'return', f_ident),
    190                               ])
    191 
    192     def test_raise_reraise(self):
    193         def f(p):
    194             try: 1./0
    195             except: raise
    196         f_ident = ident(f)
    197         self.check_events(f, [(1, 'call', f_ident),
    198                               (1, 'return', f_ident),
    199                               ])
    200 
    201     def test_raise(self):
    202         def f(p):
    203             raise Exception()
    204         f_ident = ident(f)
    205         self.check_events(f, [(1, 'call', f_ident),
    206                               (1, 'return', f_ident),
    207                               ])
    208 
    209     def test_distant_exception(self):
    210         def f():
    211             1./0
    212         def g():
    213             f()
    214         def h():
    215             g()
    216         def i():
    217             h()
    218         def j(p):
    219             i()
    220         f_ident = ident(f)
    221         g_ident = ident(g)
    222         h_ident = ident(h)
    223         i_ident = ident(i)
    224         j_ident = ident(j)
    225         self.check_events(j, [(1, 'call', j_ident),
    226                               (2, 'call', i_ident),
    227                               (3, 'call', h_ident),
    228                               (4, 'call', g_ident),
    229                               (5, 'call', f_ident),
    230                               (5, 'return', f_ident),
    231                               (4, 'return', g_ident),
    232                               (3, 'return', h_ident),
    233                               (2, 'return', i_ident),
    234                               (1, 'return', j_ident),
    235                               ])
    236 
    237     def test_generator(self):
    238         def f():
    239             for i in range(2):
    240                 yield i
    241         def g(p):
    242             for i in f():
    243                 pass
    244         f_ident = ident(f)
    245         g_ident = ident(g)
    246         self.check_events(g, [(1, 'call', g_ident),
    247                               # call the iterator twice to generate values
    248                               (2, 'call', f_ident),
    249                               (2, 'return', f_ident),
    250                               (2, 'call', f_ident),
    251                               (2, 'return', f_ident),
    252                               # once more; returns end-of-iteration with
    253                               # actually raising an exception
    254                               (2, 'call', f_ident),
    255                               (2, 'return', f_ident),
    256                               (1, 'return', g_ident),
    257                               ])
    258 
    259     def test_stop_iteration(self):
    260         def f():
    261             for i in range(2):
    262                 yield i
    263             raise StopIteration
    264         def g(p):
    265             for i in f():
    266                 pass
    267         f_ident = ident(f)
    268         g_ident = ident(g)
    269         self.check_events(g, [(1, 'call', g_ident),
    270                               # call the iterator twice to generate values
    271                               (2, 'call', f_ident),
    272                               (2, 'return', f_ident),
    273                               (2, 'call', f_ident),
    274                               (2, 'return', f_ident),
    275                               # once more to hit the raise:
    276                               (2, 'call', f_ident),
    277                               (2, 'return', f_ident),
    278                               (1, 'return', g_ident),
    279                               ])
    280 
    281 
    282 class ProfileSimulatorTestCase(TestCaseBase):
    283     def new_watcher(self):
    284         return ProfileSimulator(self)
    285 
    286     def test_simple(self):
    287         def f(p):
    288             pass
    289         f_ident = ident(f)
    290         self.check_events(f, [(1, 'call', f_ident),
    291                               (1, 'return', f_ident),
    292                               ])
    293 
    294     def test_basic_exception(self):
    295         def f(p):
    296             1./0
    297         f_ident = ident(f)
    298         self.check_events(f, [(1, 'call', f_ident),
    299                               (1, 'return', f_ident),
    300                               ])
    301 
    302     def test_caught_exception(self):
    303         def f(p):
    304             try: 1./0
    305             except: pass
    306         f_ident = ident(f)
    307         self.check_events(f, [(1, 'call', f_ident),
    308                               (1, 'return', f_ident),
    309                               ])
    310 
    311     def test_distant_exception(self):
    312         def f():
    313             1./0
    314         def g():
    315             f()
    316         def h():
    317             g()
    318         def i():
    319             h()
    320         def j(p):
    321             i()
    322         f_ident = ident(f)
    323         g_ident = ident(g)
    324         h_ident = ident(h)
    325         i_ident = ident(i)
    326         j_ident = ident(j)
    327         self.check_events(j, [(1, 'call', j_ident),
    328                               (2, 'call', i_ident),
    329                               (3, 'call', h_ident),
    330                               (4, 'call', g_ident),
    331                               (5, 'call', f_ident),
    332                               (5, 'return', f_ident),
    333                               (4, 'return', g_ident),
    334                               (3, 'return', h_ident),
    335                               (2, 'return', i_ident),
    336                               (1, 'return', j_ident),
    337                               ])
    338 
    339 
    340 def ident(function):
    341     if hasattr(function, "f_code"):
    342         code = function.f_code
    343     else:
    344         code = function.func_code
    345     return code.co_firstlineno, code.co_name
    346 
    347 
    348 def protect(f, p):
    349     try: f(p)
    350     except: pass
    351 
    352 protect_ident = ident(protect)
    353 
    354 
    355 def capture_events(callable, p=None):
    356     if p is None:
    357         p = HookWatcher()
    358     # Disable the garbage collector. This prevents __del__s from showing up in
    359     # traces.
    360     old_gc = gc.isenabled()
    361     gc.disable()
    362     try:
    363         sys.setprofile(p.callback)
    364         protect(callable, p)
    365         sys.setprofile(None)
    366     finally:
    367         if old_gc:
    368             gc.enable()
    369     return p.get_events()[1:-1]
    370 
    371 
    372 def show_events(callable):
    373     import pprint
    374     pprint.pprint(capture_events(callable))
    375 
    376 
    377 def test_main():
    378     test_support.run_unittest(
    379         TestGetProfile,
    380         ProfileHookTestCase,
    381         ProfileSimulatorTestCase
    382     )
    383 
    384 
    385 if __name__ == "__main__":
    386     test_main()
    387