test_profilehooks.py 10.7 KB
Newer Older
1 2 3 4
import pprint
import sys
import unittest

5
from test import test_support
6 7 8 9 10 11 12 13


class HookWatcher:
    def __init__(self):
        self.frames = []
        self.events = []

    def callback(self, frame, event, arg):
14 15 16 17
        if (event == "call"
            or event == "return"
            or event == "exception"):
            self.add_event(event, frame)
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33

    def add_event(self, event, frame=None):
        """Add an event to the log."""
        if frame is None:
            frame = sys._getframe(1)

        try:
            frameno = self.frames.index(frame)
        except ValueError:
            frameno = len(self.frames)
            self.frames.append(frame)

        self.events.append((frameno, event, ident(frame)))

    def get_events(self):
        """Remove calls to add_event()."""
34 35
        disallowed = [ident(self.add_event.im_func), ident(ident)]
        self.frames = None
36

37
        return [item for item in self.events if item[2] not in disallowed]
38 39


40
class ProfileSimulator(HookWatcher):
41 42
    def __init__(self, testcase):
        self.testcase = testcase
43 44 45 46
        self.stack = []
        HookWatcher.__init__(self)

    def callback(self, frame, event, arg):
47
        # Callback registered with sys.setprofile()/sys.settrace()
48 49 50 51 52 53 54 55 56 57 58
        self.dispatch[event](self, frame)

    def trace_call(self, frame):
        self.add_event('call', frame)
        self.stack.append(frame)

    def trace_return(self, frame):
        self.add_event('return', frame)
        self.stack.pop()

    def trace_exception(self, frame):
59 60
        self.testcase.fail(
            "the profiler should never receive exception events")
61

62 63 64
    def trace_pass(self, frame):
        pass

65 66 67 68
    dispatch = {
        'call': trace_call,
        'exception': trace_exception,
        'return': trace_return,
69 70 71
        'c_call': trace_pass,
        'c_return': trace_pass,
        'c_exception': trace_pass,
72
        }
73

74 75

class TestCaseBase(unittest.TestCase):
76
    def check_events(self, callable, expected):
77
        events = capture_events(callable, self.new_watcher())
78 79 80 81
        if events != expected:
            self.fail("Expected events:\n%s\nReceived events:\n%s"
                      % (pprint.pformat(expected), pprint.pformat(events)))

82 83 84 85 86

class ProfileHookTestCase(TestCaseBase):
    def new_watcher(self):
        return HookWatcher()

87 88 89 90
    def test_simple(self):
        def f(p):
            pass
        f_ident = ident(f)
91 92
        self.check_events(f, [(1, 'call', f_ident),
                              (1, 'return', f_ident),
93 94 95
                              ])

    def test_exception(self):
96 97 98 99
        def f(p):
            1/0
        f_ident = ident(f)
        self.check_events(f, [(1, 'call', f_ident),
100
                              (1, 'return', f_ident),
101 102 103
                              ])

    def test_caught_exception(self):
104
        def f(p):
105 106
            try: 1/0
            except: pass
107
        f_ident = ident(f)
108 109
        self.check_events(f, [(1, 'call', f_ident),
                              (1, 'return', f_ident),
110 111
                              ])

112 113 114 115 116
    def test_caught_nested_exception(self):
        def f(p):
            try: 1/0
            except: pass
        f_ident = ident(f)
117
        self.check_events(f, [(1, 'call', f_ident),
118 119 120
                              (1, 'return', f_ident),
                              ])

121
    def test_nested_exception(self):
122 123 124
        def f(p):
            1/0
        f_ident = ident(f)
125
        self.check_events(f, [(1, 'call', f_ident),
126
                              # This isn't what I expected:
127
                              # (0, 'exception', protect_ident),
128
                              # I expected this again:
129
                              (1, 'return', f_ident),
130 131 132
                              ])

    def test_exception_in_except_clause(self):
133 134 135 136 137 138
        def f(p):
            1/0
        def g(p):
            try:
                f(p)
            except:
139 140
                try: f(p)
                except: pass
141 142
        f_ident = ident(f)
        g_ident = ident(g)
143
        self.check_events(g, [(1, 'call', g_ident),
144
                              (2, 'call', f_ident),
145
                              (2, 'return', f_ident),
146
                              (3, 'call', f_ident),
147
                              (3, 'return', f_ident),
148
                              (1, 'return', g_ident),
149 150
                              ])

151 152 153 154 155 156 157 158
    def test_exception_propogation(self):
        def f(p):
            1/0
        def g(p):
            try: f(p)
            finally: p.add_event("falling through")
        f_ident = ident(f)
        g_ident = ident(g)
159
        self.check_events(g, [(1, 'call', g_ident),
160
                              (2, 'call', f_ident),
161
                              (2, 'return', f_ident),
162
                              (1, 'falling through', g_ident),
163
                              (1, 'return', g_ident),
164
                              ])
165

166 167 168 169 170 171
    def test_raise_twice(self):
        def f(p):
            try: 1/0
            except: 1/0
        f_ident = ident(f)
        self.check_events(f, [(1, 'call', f_ident),
172
                              (1, 'return', f_ident),
173 174 175 176 177 178 179 180
                              ])

    def test_raise_reraise(self):
        def f(p):
            try: 1/0
            except: raise
        f_ident = ident(f)
        self.check_events(f, [(1, 'call', f_ident),
181
                              (1, 'return', f_ident),
182 183 184 185 186 187 188
                              ])

    def test_raise(self):
        def f(p):
            raise Exception()
        f_ident = ident(f)
        self.check_events(f, [(1, 'call', f_ident),
189
                              (1, 'return', f_ident),
190 191
                              ])

192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
    def test_distant_exception(self):
        def f():
            1/0
        def g():
            f()
        def h():
            g()
        def i():
            h()
        def j(p):
            i()
        f_ident = ident(f)
        g_ident = ident(g)
        h_ident = ident(h)
        i_ident = ident(i)
        j_ident = ident(j)
        self.check_events(j, [(1, 'call', j_ident),
                              (2, 'call', i_ident),
                              (3, 'call', h_ident),
                              (4, 'call', g_ident),
                              (5, 'call', f_ident),
213 214 215 216 217
                              (5, 'return', f_ident),
                              (4, 'return', g_ident),
                              (3, 'return', h_ident),
                              (2, 'return', i_ident),
                              (1, 'return', j_ident),
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
                              ])

    def test_generator(self):
        def f():
            for i in range(2):
                yield i
        def g(p):
            for i in f():
                pass
        f_ident = ident(f)
        g_ident = ident(g)
        self.check_events(g, [(1, 'call', g_ident),
                              # call the iterator twice to generate values
                              (2, 'call', f_ident),
                              (2, 'return', f_ident),
                              (2, 'call', f_ident),
                              (2, 'return', f_ident),
                              # once more; returns end-of-iteration with
                              # actually raising an exception
                              (2, 'call', f_ident),
                              (2, 'return', f_ident),
                              (1, 'return', g_ident),
                              ])

    def test_stop_iteration(self):
        def f():
            for i in range(2):
                yield i
            raise StopIteration
        def g(p):
            for i in f():
                pass
        f_ident = ident(f)
        g_ident = ident(g)
        self.check_events(g, [(1, 'call', g_ident),
                              # call the iterator twice to generate values
                              (2, 'call', f_ident),
                              (2, 'return', f_ident),
                              (2, 'call', f_ident),
                              (2, 'return', f_ident),
                              # once more to hit the raise:
                              (2, 'call', f_ident),
260
                              (2, 'return', f_ident),
261 262 263
                              (1, 'return', g_ident),
                              ])

264

265 266
class ProfileSimulatorTestCase(TestCaseBase):
    def new_watcher(self):
267
        return ProfileSimulator(self)
268 269 270 271 272 273 274 275 276 277 278 279 280 281

    def test_simple(self):
        def f(p):
            pass
        f_ident = ident(f)
        self.check_events(f, [(1, 'call', f_ident),
                              (1, 'return', f_ident),
                              ])

    def test_basic_exception(self):
        def f(p):
            1/0
        f_ident = ident(f)
        self.check_events(f, [(1, 'call', f_ident),
282
                              (1, 'return', f_ident),
283 284
                              ])

285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
    def test_caught_exception(self):
        def f(p):
            try: 1/0
            except: pass
        f_ident = ident(f)
        self.check_events(f, [(1, 'call', f_ident),
                              (1, 'return', f_ident),
                              ])

    def test_distant_exception(self):
        def f():
            1/0
        def g():
            f()
        def h():
            g()
        def i():
            h()
        def j(p):
            i()
        f_ident = ident(f)
        g_ident = ident(g)
        h_ident = ident(h)
        i_ident = ident(i)
        j_ident = ident(j)
        self.check_events(j, [(1, 'call', j_ident),
                              (2, 'call', i_ident),
                              (3, 'call', h_ident),
                              (4, 'call', g_ident),
                              (5, 'call', f_ident),
315 316 317 318 319
                              (5, 'return', f_ident),
                              (4, 'return', g_ident),
                              (3, 'return', h_ident),
                              (2, 'return', i_ident),
                              (1, 'return', j_ident),
320 321
                              ])

322

323 324 325 326 327 328 329 330
def ident(function):
    if hasattr(function, "f_code"):
        code = function.f_code
    else:
        code = function.func_code
    return code.co_firstlineno, code.co_name


331 332 333 334 335 336 337
def protect(f, p):
    try: f(p)
    except: pass

protect_ident = ident(protect)


338
def capture_events(callable, p=None):
339 340 341 342 343 344 345
    try:
        sys.setprofile()
    except TypeError:
        pass
    else:
        raise test_support.TestFailed(
            'sys.setprofile() did not raise TypeError')
346

347 348
    if p is None:
        p = HookWatcher()
349
    sys.setprofile(p.callback)
350
    protect(callable, p)
351
    sys.setprofile(None)
352
    return p.get_events()[1:-1]
353 354 355 356 357 358 359 360


def show_events(callable):
    import pprint
    pprint.pprint(capture_events(callable))


def test_main():
361 362 363 364
    test_support.run_unittest(
        ProfileHookTestCase,
        ProfileSimulatorTestCase
    )
365 366 367 368


if __name__ == "__main__":
    test_main()