Completed
Push — master ( 90029a...fd9e90 )
by Ionel Cristian
28s
created

test_callprinter_indent()   B

Complexity

Conditions 2

Size

Total Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 2
c 1
b 1
f 0
dl 0
loc 31
rs 8.8571
1
from __future__ import print_function
2
3
import inspect
4
import os
5
import platform
6
import subprocess
7
import sys
8
import threading
9
import tokenize
10
from pprint import pprint
11
12
import pytest
13
from fields import Fields
14
15
import hunter
16
from hunter import And
17
from hunter import CallPrinter
18
from hunter import CodePrinter
19
from hunter import Debugger
20
from hunter import Not
21
from hunter import Or
22
from hunter import Q
23
from hunter import Query
24
from hunter import VarsPrinter
25
from hunter import When
26
27
try:
28
    from cStringIO import StringIO
29
except ImportError:
30
    from io import StringIO
31
try:
32
    from itertools import izip_longest
33
except ImportError:
34
    from itertools import zip_longest as izip_longest
35
36
pytest_plugins = 'pytester',
37
38
39
class FakeCallable(Fields.value):
40
    def __call__(self):
41
        raise NotImplementedError("Nope")
42
43
    def __repr__(self):
44
        return repr(self.value)
45
46
    def __str__(self):
47
        return str(self.value)
48
49
C = FakeCallable
50
51
52
class EvilTracer(object):
53
    def __init__(self, *args, **kwargs):
54
        self._calls = []
55
        threading_support = kwargs.pop('threading_support', False)
56
        self.handler = hunter._prepare_predicate(*args, **kwargs)
57
        self._tracer = hunter.trace(self._append, threading_support=threading_support)
58
59
    def _append(self, event):
60
        # Make sure the lineno is cached. Frames are reused
61
        # and later on the events would be very broken ..
62
        event.lineno
63
        self._calls.append(event)
64
65
    def __enter__(self):
66
        return self
67
68
    def __exit__(self, exc_type, exc_val, exc_tb):
69
        self._tracer.stop()
70
        predicate = self.handler
71
        for call in self._calls:
72
            predicate(call)
73
74
75
trace = EvilTracer
76
77
78
def _get_func_spec(func):
79
    spec = inspect.getargspec(func)
80
    return inspect.formatargspec(spec.args, spec.varargs)
81
82
83
def test_pth_activation():
84
    module_name = os.path.__name__
85
    expected_module = "{0}.py".format(module_name)
86
    hunter_env = "action=CodePrinter,module={!r},function=\"join\"".format(module_name)
87
    func_spec = _get_func_spec(os.path.join)
88
    expected_call = "call      def join{0}:".format(func_spec)
89
90
    output = subprocess.check_output(
91
        [sys.executable, os.path.join(os.path.dirname(__file__), 'sample.py')],
92
        env=dict(os.environ, PYTHONHUNTER=hunter_env),
93
        stderr=subprocess.STDOUT,
94
    )
95
    assert expected_module.encode() in output
96
    assert expected_call.encode() in output
97
98
99
def test_pth_sample4():
100
    env = dict(os.environ, PYTHONHUNTER="CodePrinter")
101
    env.pop('COVERAGE_PROCESS_START', None)
102
    env.pop('COV_CORE_SOURCE', None)
103
    output = subprocess.check_output(
104
        [sys.executable, os.path.join(os.path.dirname(__file__), 'sample4.py')],
105
        env=env,
106
        stderr=subprocess.STDOUT,
107
    )
108
    assert output
109
110
111
def test_pth_sample2(LineMatcher):
112
    env = dict(os.environ, PYTHONHUNTER="module='__main__',action=CodePrinter")
113
    env.pop('COVERAGE_PROCESS_START', None)
114
    env.pop('COV_CORE_SOURCE', None)
115
    output = subprocess.check_output(
116
        [sys.executable, os.path.join(os.path.dirname(__file__), 'sample2.py')],
117
        env=env,
118
        stderr=subprocess.STDOUT,
119
    )
120
    lm = LineMatcher(output.decode('utf-8').splitlines())
121
    lm.fnmatch_lines([
122
        '*tests*sample2.py:* call      if __name__ == "__main__":  #*',
123
        '*tests*sample2.py:* line      if __name__ == "__main__":  #*',
124
        '*tests*sample2.py:* line          import functools',
125
        '*tests*sample2.py:* line          def deco(opt):',
126
        '*tests*sample2.py:* line          @deco(1)',
127
        '*tests*sample2.py:* call          def deco(opt):',
128
        '*tests*sample2.py:* line              def decorator(func):',
129
        '*tests*sample2.py:* line              return decorator',
130
        '*tests*sample2.py:* return            return decorator',
131
        '*                 * ...       return value: <function deco*',
132
        '*tests*sample2.py:* line          @deco(2)',
133
        '*tests*sample2.py:* call          def deco(opt):',
134
        '*tests*sample2.py:* line              def decorator(func):',
135
        '*tests*sample2.py:* line              return decorator',
136
        '*tests*sample2.py:* return            return decorator',
137
        '*                 * ...       return value: <function deco*',
138
        '*tests*sample2.py:* line          @deco(3)',
139
        '*tests*sample2.py:* call          def deco(opt):',
140
        '*tests*sample2.py:* line              def decorator(func):',
141
        '*tests*sample2.py:* line              return decorator',
142
        '*tests*sample2.py:* return            return decorator',
143
        '*                 * ...       return value: <function deco*',
144
        '*tests*sample2.py:* call              def decorator(func):',
145
        '*tests*sample2.py:* line                  @functools.wraps(func)',
146
        '*tests*sample2.py:* line                  return wrapper',
147
        '*tests*sample2.py:* return                return wrapper',
148
        '*                 * ...       return value: <function foo *',
149
        '*tests*sample2.py:* call              def decorator(func):',
150
        '*tests*sample2.py:* line                  @functools.wraps(func)',
151
        '*tests*sample2.py:* line                  return wrapper',
152
        '*tests*sample2.py:* return                return wrapper',
153
        '*                 * ...       return value: <function foo *',
154
        '*tests*sample2.py:* call              def decorator(func):',
155
        '*tests*sample2.py:* line                  @functools.wraps(func)',
156
        '*tests*sample2.py:* line                  return wrapper',
157
        '*tests*sample2.py:* return                return wrapper',
158
        '*                 * ...       return value: <function foo *',
159
        '*tests*sample2.py:* line          foo(',
160
        "*tests*sample2.py:* line              'a*',",
161
        "*tests*sample2.py:* line              'b'",
162
        '*tests*sample2.py:* call                  @functools.wraps(func)',
163
        '*                 *    |                  def wrapper(*args):',
164
        '*tests*sample2.py:* line                      return func(*args)',
165
        '*tests*sample2.py:* call                  @functools.wraps(func)',
166
        '*                 *    |                  def wrapper(*args):',
167
        '*tests*sample2.py:* line                      return func(*args)',
168
        '*tests*sample2.py:* call                  @functools.wraps(func)',
169
        '*                 *    |                  def wrapper(*args):',
170
        '*tests*sample2.py:* line                      return func(*args)',
171
        '*tests*sample2.py:* call          @deco(1)',
172
        '*                 *    |          @deco(2)',
173
        '*                 *    |          @deco(3)',
174
        '*                 *    |          def foo(*args):',
175
        '*tests*sample2.py:* line              return args',
176
        '*tests*sample2.py:* return            return args',
177
        "*                 * ...       return value: ('a*', 'b')",
178
        "*tests*sample2.py:* return                    return func(*args)",
179
        "*                 * ...       return value: ('a*', 'b')",
180
        "*tests*sample2.py:* return                    return func(*args)",
181
        "*                 * ...       return value: ('a*', 'b')",
182
        "*tests*sample2.py:* return                    return func(*args)",
183
        "*                 * ...       return value: ('a*', 'b')",
184
        "*tests*sample2.py:* line          try:",
185
        "*tests*sample2.py:* line              None(",
186
        "*tests*sample2.py:* line                  'a',",
187
        "*tests*sample2.py:* line                  'b'",
188
        "*tests*sample2.py:* exception             'b'",
189
        "*                 * ...       exception value: *",
190
        "*tests*sample2.py:* line          except:",
191
        "*tests*sample2.py:* line              pass",
192
        "*tests*sample2.py:* return            pass",
193
        "*                   ...       return value: None",
194
    ])
195
196
197
def test_predicate_str_repr():
198
    assert repr(Q(module='a', function='b')).endswith("predicates.Query: query_eq=(('function', 'b'), ('module', 'a'))>")
199
    assert str(Q(module='a', function='b')) == "Query(function='b', module='a')"
200
201
    assert repr(Q(module='a')).endswith("predicates.Query: query_eq=(('module', 'a'),)>")
202
    assert str(Q(module='a')) == "Query(module='a')"
203
204
    assert "predicates.When: condition=<hunter." in repr(Q(module='a', action=C('foo')))
205
    assert "predicates.Query: query_eq=(('module', 'a'),)>, actions=('foo',)>" in repr(Q(module='a', action=C('foo')))
206
    assert str(Q(module='a', action=C('foo'))) == "When(Query(module='a'), 'foo')"
207
208
    assert "predicates.Not: predicate=<hunter." in repr(~Q(module='a'))
209
    assert "predicates.Query: query_eq=(('module', 'a'),)>>" in repr(~Q(module='a'))
210
    assert str(~Q(module='a')) == "Not(Query(module='a'))"
211
212
    assert "predicates.Or: predicates=(<hunter." in repr(Q(module='a') | Q(module='b'))
213
    assert "predicates.Query: query_eq=(('module', 'a'),)>, " in repr(Q(module='a') | Q(module='b'))
214
    assert repr(Q(module='a') | Q(module='b')).endswith("predicates.Query: query_eq=(('module', 'b'),)>)>")
215
    assert str(Q(module='a') | Q(module='b')) == "Or(Query(module='a'), Query(module='b'))"
216
217
    assert "predicates.And: predicates=(<hunter." in repr(Q(module='a') & Q(module='b'))
218
    assert "predicates.Query: query_eq=(('module', 'a'),)>," in repr(Q(module='a') & Q(module='b'))
219
    assert repr(Q(module='a') & Q(module='b')).endswith("predicates.Query: query_eq=(('module', 'b'),)>)>")
220
    assert str(Q(module='a') & Q(module='b')) == "And(Query(module='a'), Query(module='b'))"
221
222
223
def test_predicate_q_nest_1():
224
    assert repr(Q(Q(module='a'))).endswith("predicates.Query: query_eq=(('module', 'a'),)>")
225
226
227
def test_predicate_q_not_callable():
228
    exc = pytest.raises(TypeError, Q, 'foobar')
229
    assert exc.value.args == ("Predicate 'foobar' is not callable.",)
230
231
232
def test_predicate_q_expansion():
233
    assert Q(C(1), C(2), module=3) == And(C(1), C(2), Q(module=3))
234
    assert Q(C(1), C(2), module=3, action=C(4)) == When(And(C(1), C(2), Q(module=3)), C(4))
235
    assert Q(C(1), C(2), module=3, actions=[C(4), C(5)]) == When(And(C(1), C(2), Q(module=3)), C(4), C(5))
236
237
238
def test_predicate_and():
239
    assert And(C(1), C(2)) == And(C(1), C(2))
240
    assert Q(module=1) & Q(module=2) == And(Q(module=1), Q(module=2))
241
    assert Q(module=1) & Q(module=2) & Q(module=3) == And(Q(module=1), Q(module=2), Q(module=3))
242
243
    assert (Q(module=1) & Q(module=2))({'module': 3}) == False
244
    assert (Q(module=1) & Q(function=2))({'module': 1, 'function': 2}) == True
245
246
    assert And(1, 2) | 3 == Or(And(1, 2), 3)
247
248
249
def test_predicate_or():
250
    assert Q(module=1) | Q(module=2) == Or(Q(module=1), Q(module=2))
251
    assert Q(module=1) | Q(module=2) | Q(module=3) == Or(Q(module=1), Q(module=2), Q(module=3))
252
253
    assert (Q(module=1) | Q(module=2))({'module': 3}) == False
254
    assert (Q(module=1) | Q(module=2))({'module': 2}) == True
255
256
    assert Or(1, 2) & 3 == And(Or(1, 2), 3)
257
258
259
def test_tracing_bare(LineMatcher):
260
    lines = StringIO()
261
    with hunter.trace(CodePrinter(stream=lines)):
262
        def a():
263
            return 1
264
265
        b = a()
266
        b = 2
267
        try:
268
            raise Exception("BOOM!")
269
        except Exception:
270
            pass
271
    print(lines.getvalue())
272
    lm = LineMatcher(lines.getvalue().splitlines())
273
    lm.fnmatch_lines([
274
        "*test_hunter.py* call              def a():",
275
        "*test_hunter.py* line                  return 1",
276
        "*test_hunter.py* return                return 1",
277
        "* ...       return value: 1",
278
    ])
279
280
281
def test_mix_predicates_with_callables():
282
    hunter._prepare_predicate(Q(module=1) | Q(lambda: 2))
283
    hunter._prepare_predicate(Q(lambda: 2) | Q(module=1))
284
    hunter._prepare_predicate(Q(module=1) & Q(lambda: 2))
285
    hunter._prepare_predicate(Q(lambda: 2) & Q(module=1))
286
287
    hunter._prepare_predicate(Q(module=1) | (lambda: 2))
288
    hunter._prepare_predicate((lambda: 2) | Q(module=1))
289
    hunter._prepare_predicate(Q(module=1) & (lambda: 2))
290
    hunter._prepare_predicate((lambda: 2) & Q(module=1))
291
292
293
def test_threading_support(LineMatcher):
294
    lines = StringIO()
295
    idents = set()
296
    names = set()
297
    started = threading.Event()
298
299
    def record(event):
300
        idents.add(event.threadid)
301
        names.add(event.threadname)
302
        return True
303
304
    with hunter.trace(record,
305
                      actions=[CodePrinter(stream=lines), VarsPrinter('a', stream=lines), CallPrinter(stream=lines)],
306
                      threading_support=True):
307
        def foo(a=1):
308
            started.set()
309
            print(a)
310
311
        def main():
312
            foo()
313
314
        t = threading.Thread(target=foo)
315
        t.start()
316
        started.wait(10)
317
        main()
318
319
    lm = LineMatcher(lines.getvalue().splitlines())
320
    assert idents - {t.ident} == {None}
321
    assert 'MainThread' in names
322
    assert any(name.startswith('Thread-') for name in names)
323
    lm.fnmatch_lines_random([
324
        'Thread-*   *test_hunter.py:*   call              def foo(a=1):',
325
        'Thread-*   *                   vars      a => 1',
326
        'Thread-*   *test_hunter.py:*   call         => foo(a=1)',
327
        'Thread-*   *                   vars      a => 1',
328
        'MainThread *test_hunter.py:*   call              def foo(a=1):',
329
        'MainThread *                   vars      a => 1',
330
        'MainThread *test_hunter.py:*   call         => foo(a=1)',
331
        'MainThread *                   vars      a => 1',
332
    ])
333
334
335
@pytest.mark.parametrize('query', [{'threadid': None}, {'threadname': 'MainThread'}])
336
def test_thread_filtering(LineMatcher, query):
337
    lines = StringIO()
338
    idents = set()
339
    names = set()
340
    started = threading.Event()
341
342
    def record(event):
343
        idents.add(event.threadid)
344
        names.add(event.threadname)
345
        return True
346
347
    with hunter.trace(~Q(**query), record,
348
                      actions=[CodePrinter(stream=lines), VarsPrinter('a', stream=lines), CallPrinter(stream=lines)],
349
                      threading_support=True):
350
        def foo(a=1):
351
            started.set()
352
            print(a)
353
354
        def main():
355
            foo()
356
357
        t = threading.Thread(target=foo)
358
        t.start()
359
        started.wait(10)
360
        main()
361
362
    lm = LineMatcher(lines.getvalue().splitlines())
363
    print(lines.getvalue())
364
    assert None not in idents
365
    assert 'MainThread' not in names
366
    pprint(lm.lines)
367
    lm.fnmatch_lines_random([
368
        'Thread-*   *test_hunter.py:*   call              def foo(a=1):',
369
        'Thread-*   *                   vars      a => 1',
370
        'Thread-*   *test_hunter.py:*   call         => foo(a=1)',
371
        'Thread-*   *                   vars      a => 1',
372
    ])
373
374
375
def test_tracing_printing_failures(LineMatcher):
376
    lines = StringIO()
377
    with trace(actions=[CodePrinter(stream=lines), VarsPrinter("x", stream=lines)]):
378
        class Bad(Exception):
379
            def __repr__(self):
380
                raise RuntimeError("I'm a bad class!")
381
382
        def a():
383
            x = Bad()
384
            return x
385
386
        def b():
387
            x = Bad()
388
            raise x
389
390
        a()
391
        try:
392
            b()
393
        except Exception as exc:
394
            pass
395
    lm = LineMatcher(lines.getvalue().splitlines())
396
    lm.fnmatch_lines([
397
        """*tests*test_hunter.py:* call              class Bad(Exception):""",
398
        """*tests*test_hunter.py:* line              class Bad(Exception):""",
399
        """*tests*test_hunter.py:* line                  def __repr__(self):""",
400
        """*tests*test_hunter.py:* return                def __repr__(self):""",
401
        """* ...       return value: *""",
402
        """*tests*test_hunter.py:* call              def a():""",
403
        """*tests*test_hunter.py:* line                  x = Bad()""",
404
        """*tests*test_hunter.py:* line                  return x""",
405
        """* vars      x => !!! FAILED REPR: RuntimeError("I'm a bad class!",)""",
406
        """*tests*test_hunter.py:* return                return x""",
407
        """* ...       return value: !!! FAILED REPR: RuntimeError("I'm a bad class!",)""",
408
        """* vars      x => !!! FAILED REPR: RuntimeError("I'm a bad class!",)""",
409
        """*tests*test_hunter.py:* call              def b():""",
410
        """*tests*test_hunter.py:* line                  x = Bad()""",
411
        """*tests*test_hunter.py:* line                  raise x""",
412
        """* vars      x => !!! FAILED REPR: RuntimeError("I'm a bad class!",)""",
413
        """*tests*test_hunter.py:* exception             raise x""",
414
        """* ...       exception value: !!! FAILED REPR: RuntimeError("I'm a bad class!",)""",
415
        """* vars      x => !!! FAILED REPR: RuntimeError("I'm a bad class!",)""",
416
        """*tests*test_hunter.py:* return                raise x""",
417
        """* ...       return value: None""",
418
        """* vars      x => !!! FAILED REPR: RuntimeError("I'm a bad class!",)""",
419
    ])
420
421
422
def test_tracing_vars(LineMatcher):
423
    lines = StringIO()
424
    with hunter.trace(actions=[VarsPrinter('b', stream=lines), CodePrinter(stream=lines)]):
425
        def a():
426
            b = 1
427
            b = 2
428
            return 1
429
430
        b = a()
431
        b = 2
432
        try:
433
            raise Exception("BOOM!")
434
        except Exception:
435
            pass
436
    print(lines.getvalue())
437
    lm = LineMatcher(lines.getvalue().splitlines())
438
    lm.fnmatch_lines([
439
        "*test_hunter.py* call              def a():",
440
        "*test_hunter.py* line                  b = 1",
441
        "* vars      b => 1",
442
        "*test_hunter.py* line                  b = 2",
443
        "* vars      b => 2",
444
        "*test_hunter.py* line                  return 1",
445
        "* vars      b => 2",
446
        "*test_hunter.py* return                return 1",
447
        "* ...       return value: 1",
448
    ])
449
450
451
def test_tracing_vars_expressions(LineMatcher):
452
    lines = StringIO()
453
    with hunter.trace(actions=[VarsPrinter('Foo.bar', 'Foo.__dict__["bar"]', stream=lines)]):
454
        def main():
455
            class Foo(object):
456
                bar = 1
457
458
        main()
459
    print(lines.getvalue())
460
    lm = LineMatcher(lines.getvalue().splitlines())
461
    lm.fnmatch_lines_random([
462
        '*      Foo.bar => 1',
463
        '*      Foo.__dict__[[]"bar"[]] => 1',
464
    ])
465
466
467
def test_trace_merge():
468
    with hunter.trace(function="a"):
469
        with hunter.trace(function="b"):
470
            with hunter.trace(function="c"):
471
                assert sys.gettrace().handler == When(Q(function="c"), CodePrinter)
472
            assert sys.gettrace().handler == When(Q(function="b"), CodePrinter)
473
        assert sys.gettrace().handler == When(Q(function="a"), CodePrinter)
474
475
476
def test_trace_api_expansion():
477
    # simple use
478
    with trace(function="foobar") as t:
479
        assert t.handler == When(Q(function="foobar"), CallPrinter)
480
481
    # "or" by expression
482
    with trace(module="foo", function="foobar") as t:
483
        assert t.handler == When(Q(module="foo", function="foobar"), CallPrinter)
484
485
    # pdb.set_trace
486
    with trace(function="foobar", action=Debugger) as t:
487
        assert str(t.handler) == str(When(Q(function="foobar"), Debugger))
488
489
    # pdb.set_trace on any hits
490
    with trace(module="foo", function="foobar", action=Debugger) as t:
491
        assert str(t.handler) == str(When(Q(module="foo", function="foobar"), Debugger))
492
493
    # pdb.set_trace when function is foobar, otherwise just print when module is foo
494
    with trace(Q(function="foobar", action=Debugger), module="foo") as t:
495
        assert str(t.handler) == str(When(And(
496
            When(Q(function="foobar"), Debugger),
497
            Q(module="foo")
498
        ), CallPrinter))
499
500
    # dumping variables from stack
501
    with trace(Q(function="foobar", action=VarsPrinter("foobar")), module="foo") as t:
502
        assert str(t.handler) == str(When(And(
503
            When(Q(function="foobar"), VarsPrinter("foobar")),
504
            Q(module="foo"),
505
        ), CallPrinter))
506
507
    with trace(Q(function="foobar", action=VarsPrinter("foobar", "mumbojumbo")), module="foo") as t:
508
        assert str(t.handler) == str(When(And(
509
            When(Q(function="foobar"), VarsPrinter("foobar", "mumbojumbo")),
510
            Q(module="foo"),
511
        ), CallPrinter))
512
513
    # multiple actions
514
    with trace(Q(function="foobar", actions=[VarsPrinter("foobar"), Debugger]), module="foo") as t:
515
        assert str(t.handler) == str(When(And(
516
            When(Q(function="foobar"), VarsPrinter("foobar"), Debugger),
517
            Q(module="foo"),
518
        ), CallPrinter))
519
520
521
def test_locals():
522
    out = StringIO()
523
    with hunter.trace(
524
        lambda event: event.locals.get("node") == "Foobar",
525
        module="test_hunter",
526
        function="foo",
527
        action=CodePrinter(stream=out)
528
    ):
529
        def foo():
530
            a = 1
531
            node = "Foobar"
532
            node += "x"
533
            a += 2
534
            return a
535
536
        foo()
537
    assert out.getvalue().endswith('node += "x"\n')
538
539
540
def test_fullsource_decorator_issue(LineMatcher):
541
    out = StringIO()
542
    with trace(kind='call', action=CodePrinter(stream=out)):
543
        foo = bar = lambda x: x
544
545
        @foo
546
        @bar
547
        def foo():
548
            return 1
549
550
        foo()
551
552
    lm = LineMatcher(out.getvalue().splitlines())
553
    lm.fnmatch_lines([
554
        '* call              @foo',
555
        '*    |              @bar',
556
        '*    |              def foo():',
557
    ])
558
559
560
def test_callprinter(LineMatcher):
561
    out = StringIO()
562
    with trace(action=CallPrinter(stream=out)):
563
        foo = bar = lambda x: x
564
565
        @foo
566
        @bar
567
        def foo():
568
            return 1
569
570
        foo()
571
572
    lm = LineMatcher(out.getvalue().splitlines())
573
    lm.fnmatch_lines([
574
        '* call      => <lambda>(x=<function *foo at *>)',
575
        '* line         foo = bar = lambda x: x',
576
        '* return    <= <lambda>: <function *foo at *>',
577
        '* call      => <lambda>(x=<function *foo at *>)',
578
        '* line         foo = bar = lambda x: x',
579
        '* return    <= <lambda>: <function *foo at *>',
580
        '* call      => foo()',
581
        '* line         return 1',
582
        '* return    <= foo: 1',
583
    ])
584
585
586
def test_callprinter_indent(LineMatcher):
587
    from sample6 import bar
588
    out = StringIO()
589
    with trace(action=CallPrinter(stream=out)):
590
        bar()
591
592
    lm = LineMatcher(out.getvalue().splitlines())
593
    lm.fnmatch_lines([
594
        "*sample6.py:1     call      => bar()",
595
        "*sample6.py:2     line         foo()",
596
        "*sample6.py:5     call         => foo()",
597
        "*sample6.py:6     line            try:",
598
        "*sample6.py:7     line            asdf()",
599
        "*sample6.py:16    call            => asdf()",
600
        "*sample6.py:17    line               raise Exception()",
601
        "*sample6.py:17    exception        ! asdf: (<*Exception'>, Exception(), <traceback object at *>)",
602
        "*sample6.py:17    return          <= asdf: None",
603
        "*sample6.py:7     exception     ! foo: (<*Exception'>, Exception(), <traceback object at *>)",
604
        "*sample6.py:8     line            except:",
605
        "*sample6.py:9     line            pass",
606
        "*sample6.py:10    line            try:",
607
        "*sample6.py:11    line            asdf()",
608
        "*sample6.py:16    call            => asdf()",
609
        "*sample6.py:17    line               raise Exception()",
610
        "*sample6.py:17    exception        ! asdf: (<*Exception'>, Exception(), <traceback object at *>)",
611
        "*sample6.py:17    return          <= asdf: None",
612
        "*sample6.py:11    exception     ! foo: (<*Exception'>, Exception(), <traceback object at *>)",
613
        "*sample6.py:12    line            except:",
614
        "*sample6.py:13    line            pass",
615
        "*sample6.py:13    return       <= foo: None",
616
        "*sample6.py:2     return    <= bar: None",
617
618
    ])
619
620
621
def test_source(LineMatcher):
622
    calls = []
623
    with trace(action=lambda event: calls.append(event.source)):
624
        foo = bar = lambda x: x
625
626
        @foo
627
        @bar
628
        def foo():
629
            return 1
630
631
        foo()
632
633
    lm = LineMatcher(calls)
634
    lm.fnmatch_lines([
635
        '        foo = bar = lambda x: x\n',
636
        '        @foo\n',
637
        '            return 1\n',
638
    ])
639
640
641
@pytest.mark.skipif('os.environ.get("SETUPPY_CFLAGS") == "-DCYTHON_TRACE=1"')
642
def test_wraps(LineMatcher):
643
    calls = []
644
645
    @hunter.wrap(action=lambda event: calls.append("%06s calls=%s depth=%s %s" % (event.kind, event.calls, event.depth, event.fullsource)))
646
    def foo():
647
        return 1
648
649
    foo()
650
    lm = LineMatcher(calls)
651
    lm.fnmatch_lines([
652
        '  call calls=0 depth=0     @hunter.wrap*',
653
        '  line calls=1 depth=1         return 1\n',
654
        'return calls=1 depth=1         return 1\n',
655
    ])
656
657
658
@pytest.mark.skipif('os.environ.get("SETUPPY_CFLAGS") == "-DCYTHON_TRACE=1"')
659
def test_depth():
660
    calls = []
661
    tracer = hunter.trace(action=lambda event: calls.append((event.kind, event.module, event.function, event.depth)))
662
    try:
663
        def bar():
664
            for i in range(2):
665
                yield i
666
667
        def foo():
668
            gen = bar()
669
            next(gen)
670
            while True:
671
                try:
672
                    gen.send('foo')
673
                except StopIteration:
674
                    break
675
            list(i for i in range(2))
676
            x = [i for i in range(2)]
677
678
        foo()
679
    finally:
680
        tracer.stop()
681
    pprint(calls)
682
    assert ('call', 'test_hunter', 'bar', 1) in calls
683
    assert ('return', 'test_hunter', 'foo', 1) in calls
684
685
686
def test_source_cython(LineMatcher):
687
    pytest.importorskip('sample5')
688
    calls = []
689
    from sample5 import foo
690
    with trace(action=lambda event: calls.append(event.source)):
691
        foo()
692
693
    lm = LineMatcher(calls)
694
    lm.fnmatch_lines([
695
        'def foo():\n',
696
        '    return 1\n',
697
    ])
698
699
700
def test_fullsource(LineMatcher):
701
    calls = []
702
    with trace(action=lambda event: calls.append(event.fullsource)):
703
        foo = bar = lambda x: x
704
705
        @foo
706
        @bar
707
        def foo():
708
            return 1
709
710
        foo()
711
712
    lm = LineMatcher(calls)
713
    lm.fnmatch_lines([
714
        '        foo = bar = lambda x: x\n',
715
        '        @foo\n        @bar\n        def foo():\n',
716
        '            return 1\n',
717
    ])
718
719
720
def test_fullsource_cython(LineMatcher):
721
    pytest.importorskip('sample5')
722
    calls = []
723
    from sample5 import foo
724
    with trace(action=lambda event: calls.append(event.fullsource)):
725
        foo()
726
727
    lm = LineMatcher(calls)
728
    lm.fnmatch_lines([
729
        'def foo():\n',
730
        '    return 1\n',
731
    ])
732
733
734
def test_debugger(LineMatcher):
735
    out = StringIO()
736
    calls = []
737
738
    class FakePDB:
739
        def __init__(self, foobar=1):
740
            calls.append(foobar)
741
742
        def set_trace(self, frame):
743
            calls.append(frame.f_code.co_name)
744
745
    with hunter.trace(
746
        lambda event: event.locals.get("node") == "Foobar",
747
        module="test_hunter",
748
        function="foo",
749
        actions=[CodePrinter,
750
                 VarsPrinter("a", "node", "foo", "test_debugger", globals=True, stream=out),
751
                 Debugger(klass=FakePDB, foobar=2)]
752
    ):
753
        def foo():
754
            a = 1
755
            node = "Foobar"
756
            node += "x"
757
            a += 2
758
            return a
759
760
        foo()
761
    print(out.getvalue())
762
    assert calls == [2, 'foo']
763
    lm = LineMatcher(out.getvalue().splitlines())
764
    pprint(lm.lines)
765
    lm.fnmatch_lines_random([
766
        "*      test_debugger => <function test_debugger at *",
767
        "*      node => 'Foobar'",
768
        "*      a => 1",
769
    ])
770
771
772
def test_custom_action():
773
    calls = []
774
775
    with trace(action=lambda event: calls.append(event.function), kind="return"):
776
        def foo():
777
            return 1
778
779
        foo()
780
    assert 'foo' in calls
781
782
783
def test_trace_with_class_actions():
784
    with trace(CodePrinter):
785
        def a():
786
            pass
787
788
        a()
789
790
791
def test_predicate_no_inf_recursion():
792
    assert Or(And(1)) == 1
793
    assert Or(Or(1)) == 1
794
    assert And(Or(1)) == 1
795
    assert And(And(1)) == 1
796
    predicate = Q(Q(lambda ev: 1, module='wat'))
797
    print('predicate:', predicate)
798
    predicate({'module': 'foo'})
799
800
801
def test_predicate_compression():
802
    assert Or(Or(1, 2), And(3)) == Or(1, 2, 3)
803
    assert Or(Or(1, 2), 3) == Or(1, 2, 3)
804
    assert Or(1, Or(2, 3), 4) == Or(1, 2, 3, 4)
805
    assert And(1, 2, Or(3, 4)).predicates == (1, 2, Or(3, 4))
806
807
    assert repr(Or(Or(1, 2), And(3))) == repr(Or(1, 2, 3))
808
    assert repr(Or(Or(1, 2), 3)) == repr(Or(1, 2, 3))
809
    assert repr(Or(1, Or(2, 3), 4)) == repr(Or(1, 2, 3, 4))
810
811
812
def test_predicate_not():
813
    assert Not(1).predicate == 1
814
    assert ~Or(1, 2) == Not(Or(1, 2))
815
    assert ~And(1, 2) == Not(And(1, 2))
816
817
    assert ~Not(1) == 1
818
819
    assert ~Query(module=1) | ~Query(module=2) == Not(And(Query(module=1), Query(module=2)))
820
    assert ~Query(module=1) & ~Query(module=2) == Not(Or(Query(module=1), Query(module=2)))
821
822
    assert ~Query(module=1) | Query(module=2) == Or(Not(Query(module=1)), Query(module=2))
823
    assert ~Query(module=1) & Query(module=2) == And(Not(Query(module=1)), Query(module=2))
824
825
    assert ~(Query(module=1) & Query(module=2)) == Not(And(Query(module=1), Query(module=2)))
826
    assert ~(Query(module=1) | Query(module=2)) == Not(Or(Query(module=1), Query(module=2)))
827
828
    assert repr(~Or(1, 2)) == repr(Not(Or(1, 2)))
829
    assert repr(~And(1, 2)) == repr(Not(And(1, 2)))
830
831
    assert repr(~Query(module=1) | ~Query(module=2)) == repr(Not(And(Query(module=1), Query(module=2))))
832
    assert repr(~Query(module=1) & ~Query(module=2)) == repr(Not(Or(Query(module=1), Query(module=2))))
833
834
    assert repr(~(Query(module=1) & Query(module=2))) == repr(Not(And(Query(module=1), Query(module=2))))
835
    assert repr(~(Query(module=1) | Query(module=2))) == repr(Not(Or(Query(module=1), Query(module=2))))
836
837
    assert Not(Q(module=1))({'module': 1}) == False
838
839
840
def test_predicate_query_allowed():
841
    pytest.raises(TypeError, Query, 1)
842
    pytest.raises(TypeError, Query, a=1)
843
844
845
def test_predicate_when_allowed():
846
    pytest.raises(TypeError, When, 1)
847
848
849
@pytest.mark.parametrize('expr,inp,expected', [
850
    ({'module': "abc"}, {'module': "abc"}, True),
851
    ({'module': "abcd"}, {'module': "abc"}, False),
852
    ({'module': "abcd"}, {'module': "abce"}, False),
853
    ({'module_startswith': "abc"}, {'module': "abcd"}, True),
854
    ({'module__startswith': "abc"}, {'module': "abcd"}, True),
855
    ({'module_contains': "bc"}, {'module': "abcd"}, True),
856
    ({'module_contains': "bcde"}, {'module': "abcd"}, False),
857
858
    ({'module_endswith': "abc"}, {'module': "abcd"}, False),
859
    ({'module__endswith': "bcd"}, {'module': "abcd"}, True),
860
861
    ({'module_in': "abcd"}, {'module': "bc"}, True),
862
    ({'module': "abcd"}, {'module': "bc"}, False),
863
    ({'module': ["abcd"]}, {'module': "bc"}, False),
864
    ({'module_in': ["abcd"]}, {'module': "bc"}, False),
865
    ({'module_in': ["a", "bc", "d"]}, {'module': "bc"}, True),
866
867
    ({'module': "abcd"}, {'module': "abc"}, False),
868
869
    ({'module_startswith': ("abc", "xyz")}, {'module': "abc"}, True),
870
    ({'module_startswith': {"abc", "xyz"}}, {'module': "abc"}, True),
871
    ({'module_startswith': ["abc", "xyz"]}, {'module': "abc"}, True),
872
    ({'module_startswith': ("abc", "xyz")}, {'module': "abcd"}, True),
873
    ({'module_startswith': ("abc", "xyz")}, {'module': "xyzw"}, True),
874
    ({'module_startswith': ("abc", "xyz")}, {'module': "fooabc"}, False),
875
876
    ({'module_endswith': ("abc", "xyz")}, {'module': "abc"}, True),
877
    ({'module_endswith': {"abc", "xyz"}}, {'module': "abc"}, True),
878
    ({'module_endswith': ["abc", "xyz"]}, {'module': "abc"}, True),
879
    ({'module_endswith': ("abc", "xyz")}, {'module': "1abc"}, True),
880
    ({'module_endswith': ("abc", "xyz")}, {'module': "1xyz"}, True),
881
    ({'module_endswith': ("abc", "xyz")}, {'module': "abcfoo"}, False),
882
883
    ({'module': "abc"}, {'module': 1}, False),
884
885
    ({'module_regex': r"(re|sre.*)\b"}, {'module': "regex"}, False),
886
    ({'module_regex': r"(re|sre.*)\b"}, {'module': "re.gex"}, True),
887
    ({'module_regex': r"(re|sre.*)\b"}, {'module': "sregex"}, True),
888
    ({'module_regex': r"(re|sre.*)\b"}, {'module': "re"}, True),
889
])
890
def test_predicate_matching(expr, inp, expected):
891
    assert Query(**expr)(inp) == expected
892
893
894
@pytest.mark.parametrize('exc_type,expr', [
895
    (TypeError, {'module_1': 1}),
896
    (TypeError, {'module1': 1}),
897
    (ValueError, {'module_startswith': 1}),
898
    (ValueError, {'module_startswith': {1: 2}}),
899
    (ValueError, {'module_endswith': 1}),
900
    (ValueError, {'module_endswith': {1: 2}}),
901
    (TypeError, {'module_foo': 1}),
902
    (TypeError, {'module_a_b': 1}),
903
])
904
def test_predicate_bad_query(expr, exc_type):
905
    pytest.raises(exc_type, Query, **expr)
906
907
908
def test_predicate_when():
909
    called = []
910
    assert When(Q(module=1), lambda ev: called.append(ev))({'module': 2}) == False
911
    assert called == []
912
913
    assert When(Q(module=1), lambda ev: called.append(ev))({'module': 1}) == True
914
    assert called == [{'module': 1}]
915
916
    called = []
917
    assert Q(module=1, action=lambda ev: called.append(ev))({'module': 1}) == True
918
    assert called == [{'module': 1}]
919
920
    called = [[], []]
921
    predicate = (
922
        Q(module=1, action=lambda ev: called[0].append(ev)) |
923
        Q(module=2, action=lambda ev: called[1].append(ev))
924
    )
925
    assert predicate({'module': 1}) == True
926
    assert called == [[{'module': 1}], []]
927
928
    assert predicate({'module': 2}) == True
929
    assert called == [[{'module': 1}], [{'module': 2}]]
930
931
    called = [[], []]
932
    predicate = (
933
        Q(module=1, action=lambda ev: called[0].append(ev)) &
934
        Q(function=2, action=lambda ev: called[1].append(ev))
935
    )
936
    assert predicate({'module': 2}) == False
937
    assert called == [[], []]
938
939
    assert predicate({'module': 1, 'function': 2}) == True
940
    assert called == [[{'module': 1, 'function': 2}], [{'module': 1, 'function': 2}]]
941
942
943
def test_and_or_kwargs():
944
    assert And(module=1, function=2) == Query(module=1, function=2)
945
    assert Or(module=1, function=2) == Or(Query(module=1), Query(function=2))
946
947
    assert And(3, module=1, function=2) == And(3, Query(module=1, function=2))
948
    assert Or(3, module=1, function=2) == Or(3, Query(module=1), Query(function=2))
949
950
951
def test_proper_backend():
952
    if os.environ.get('PUREPYTHONHUNTER') or platform.python_implementation() == 'PyPy':
953
        assert 'hunter.tracer.Tracer' in repr(hunter.Tracer)
954
    else:
955
        assert 'hunter._tracer.Tracer' in repr(hunter.Tracer)
956
957
958
@pytest.fixture(scope="session", params=['pure', 'cython'])
959
def tracer_impl(request):
960
    if request.param == 'pure':
961
        return pytest.importorskip('hunter.tracer').Tracer
962
    elif request.param == 'cython':
963
        return pytest.importorskip('hunter._tracer').Tracer
964
965
966
def _bulky_func_that_use_stdlib():
967
    import difflib
968
    a = list(map(str, range(500)))
969
    b = list(map(str, range(0, 500, 2)))
970
    list(difflib.unified_diff(a, b, 'a', 'b'))
971
972
973
def test_perf_filter(tracer_impl, benchmark):
974
    t = tracer_impl()
975
976
    @benchmark
977
    def run():
978
        output = StringIO()
979
        with t.trace(Q(
980
            Q(module="does-not-exist") | Q(module="does not exist".split()),
981
            action=CodePrinter(stream=output)
982
        )):
983
            _bulky_func_that_use_stdlib()
984
        return output
985
986
    assert run.getvalue() == ''
987
988
989
def test_perf_stdlib(tracer_impl, benchmark):
990
    t = tracer_impl()
991
992
    @benchmark
993
    def run():
994
        output = StringIO()
995
        with t.trace(Q(
996
            ~Q(module_contains='pytest'),
997
            ~Q(module_contains='hunter'),
998
            ~Q(filename=''),
999
            stdlib=False,
1000
            action=CodePrinter(stream=output)
1001
        )):
1002
            _bulky_func_that_use_stdlib()
1003
        return output
1004
1005
    assert run.getvalue() == ''
1006
1007
1008
def test_perf_actions(tracer_impl, benchmark):
1009
    t = tracer_impl()
1010
1011
    @benchmark
1012
    def run():
1013
        output = StringIO()
1014
        with t.trace(Q(
1015
            ~Q(module_in=['re', 'sre', 'sre_parse']) & ~Q(module_startswith='namedtuple') & Q(kind="call"),
1016
            actions=[
1017
                CodePrinter(
1018
                    stream=output
1019
                ),
1020
                VarsPrinter(
1021
                    'line',
1022
                    globals=True,
1023
                    stream=output
1024
                )
1025
            ]
1026
        )):
1027
            _bulky_func_that_use_stdlib()
1028