Completed
Push — master ( c47a1a...d9aecd )
by Ionel Cristian
33s
created

test_wraps()   A

Complexity

Conditions 3

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
c 0
b 0
f 0
dl 0
loc 13
rs 9.4285
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 = "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__'")
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 names.issuperset({'MainThread', 'Thread-1'})
322
    pprint(lm.lines)
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"), CodePrinter)
480
481
    # "or" by expression
482
    with trace(module="foo", function="foobar") as t:
483
        assert t.handler == When(Q(module="foo", function="foobar"), CodePrinter)
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
        ), CodePrinter))
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
        ), CodePrinter))
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
        ), CodePrinter))
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
        ), CodePrinter))
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_source(LineMatcher):
587
    calls = []
588
    with trace(action=lambda event: calls.append(event.source)):
589
        foo = bar = lambda x: x
590
591
        @foo
592
        @bar
593
        def foo():
594
            return 1
595
596
        foo()
597
598
    lm = LineMatcher(calls)
599
    lm.fnmatch_lines([
600
        '        foo = bar = lambda x: x\n',
601
        '        @foo\n',
602
        '            return 1\n',
603
    ])
604
605
606
def test_wraps(LineMatcher):
607
    calls = []
608
609
    @hunter.wrap(action=lambda event: calls.append("%06s calls=%s depth=%s %s" % (event.kind, event.calls, event.depth, event.fullsource)))
610
    def foo():
611
        return 1
612
613
    foo()
614
    lm = LineMatcher(calls)
615
    lm.fnmatch_lines([
616
        '  call calls=0 depth=0     @hunter.wrap*',
617
        '  line calls=1 depth=1         return 1\n',
618
        'return calls=1 depth=1         return 1\n',
619
    ])
620
621
622
def test_depth():
623
    calls = []
624
    tracer = hunter.trace(action=lambda event: calls.append((event.kind, event.module, event.function, event.depth)))
625
    try:
626
        def bar():
627
            for i in range(2):
628
                yield i
629
630
        def foo():
631
            gen = bar()
632
            next(gen)
633
            while True:
634
                try:
635
                    gen.send('foo')
636
                except StopIteration:
637
                    break
638
            list(i for i in range(2))
639
            x = [i for i in range(2)]
640
641
        foo()
642
    finally:
643
        tracer.stop()
644
    pprint(calls)
645
    assert ('call', 'test_hunter', 'bar', 1) in calls
646
    assert ('return', 'test_hunter', 'foo', 1) in calls
647
648
649
def test_source_cython(LineMatcher):
650
    pytest.importorskip('sample5')
651
    calls = []
652
    from sample5 import foo
653
    with trace(action=lambda event: calls.append(event.source)):
654
        foo()
655
656
    lm = LineMatcher(calls)
657
    lm.fnmatch_lines([
658
        'def foo():\n',
659
        '    return 1\n',
660
    ])
661
662
663
def test_fullsource(LineMatcher):
664
    calls = []
665
    with trace(action=lambda event: calls.append(event.fullsource)):
666
        foo = bar = lambda x: x
667
668
        @foo
669
        @bar
670
        def foo():
671
            return 1
672
673
        foo()
674
675
    lm = LineMatcher(calls)
676
    lm.fnmatch_lines([
677
        '        foo = bar = lambda x: x\n',
678
        '        @foo\n        @bar\n        def foo():\n',
679
        '            return 1\n',
680
    ])
681
682
683
def test_fullsource_cython(LineMatcher):
684
    pytest.importorskip('sample5')
685
    calls = []
686
    from sample5 import foo
687
    with trace(action=lambda event: calls.append(event.fullsource)):
688
        foo()
689
690
    lm = LineMatcher(calls)
691
    lm.fnmatch_lines([
692
        'def foo():\n',
693
        '    return 1\n',
694
    ])
695
696
697
def test_debugger(LineMatcher):
698
    out = StringIO()
699
    calls = []
700
701
    class FakePDB:
702
        def __init__(self, foobar=1):
703
            calls.append(foobar)
704
705
        def set_trace(self, frame):
706
            calls.append(frame.f_code.co_name)
707
708
    with hunter.trace(
709
        lambda event: event.locals.get("node") == "Foobar",
710
        module="test_hunter",
711
        function="foo",
712
        actions=[VarsPrinter("a", "node", "foo", "test_debugger", globals=True, stream=out),
713
                 Debugger(klass=FakePDB, foobar=2)]
714
    ):
715
        def foo():
716
            a = 1
717
            node = "Foobar"
718
            node += "x"
719
            a += 2
720
            return a
721
722
        foo()
723
    print(out.getvalue())
724
    assert calls == [2, 'foo']
725
    lm = LineMatcher(out.getvalue().splitlines())
726
    pprint(lm.lines)
727
    lm.fnmatch_lines_random([
728
        "*      test_debugger => <function test_debugger at *",
729
        "*      node => 'Foobar'",
730
        "*      a => 1",
731
    ])
732
733
734
def test_custom_action():
735
    calls = []
736
737
    with trace(action=lambda event: calls.append(event.function), kind="return"):
738
        def foo():
739
            return 1
740
741
        foo()
742
    assert 'foo' in calls
743
744
745
def test_trace_with_class_actions():
746
    with trace(CodePrinter):
747
        def a():
748
            pass
749
750
        a()
751
752
753
def test_predicate_no_inf_recursion():
754
    assert Or(And(1)) == 1
755
    assert Or(Or(1)) == 1
756
    assert And(Or(1)) == 1
757
    assert And(And(1)) == 1
758
    predicate = Q(Q(lambda ev: 1, module='wat'))
759
    print('predicate:', predicate)
760
    predicate({'module': 'foo'})
761
762
763
def test_predicate_compression():
764
    assert Or(Or(1, 2), And(3)) == Or(1, 2, 3)
765
    assert Or(Or(1, 2), 3) == Or(1, 2, 3)
766
    assert Or(1, Or(2, 3), 4) == Or(1, 2, 3, 4)
767
    assert And(1, 2, Or(3, 4)).predicates == (1, 2, Or(3, 4))
768
769
    assert repr(Or(Or(1, 2), And(3))) == repr(Or(1, 2, 3))
770
    assert repr(Or(Or(1, 2), 3)) == repr(Or(1, 2, 3))
771
    assert repr(Or(1, Or(2, 3), 4)) == repr(Or(1, 2, 3, 4))
772
773
774
def test_predicate_not():
775
    assert Not(1).predicate == 1
776
    assert ~Or(1, 2) == Not(Or(1, 2))
777
    assert ~And(1, 2) == Not(And(1, 2))
778
779
    assert ~Not(1) == 1
780
781
    assert ~Query(module=1) | ~Query(module=2) == Not(And(Query(module=1), Query(module=2)))
782
    assert ~Query(module=1) & ~Query(module=2) == Not(Or(Query(module=1), Query(module=2)))
783
784
    assert ~Query(module=1) | Query(module=2) == Or(Not(Query(module=1)), Query(module=2))
785
    assert ~Query(module=1) & Query(module=2) == And(Not(Query(module=1)), Query(module=2))
786
787
    assert ~(Query(module=1) & Query(module=2)) == Not(And(Query(module=1), Query(module=2)))
788
    assert ~(Query(module=1) | Query(module=2)) == Not(Or(Query(module=1), Query(module=2)))
789
790
    assert repr(~Or(1, 2)) == repr(Not(Or(1, 2)))
791
    assert repr(~And(1, 2)) == repr(Not(And(1, 2)))
792
793
    assert repr(~Query(module=1) | ~Query(module=2)) == repr(Not(And(Query(module=1), Query(module=2))))
794
    assert repr(~Query(module=1) & ~Query(module=2)) == repr(Not(Or(Query(module=1), Query(module=2))))
795
796
    assert repr(~(Query(module=1) & Query(module=2))) == repr(Not(And(Query(module=1), Query(module=2))))
797
    assert repr(~(Query(module=1) | Query(module=2))) == repr(Not(Or(Query(module=1), Query(module=2))))
798
799
    assert Not(Q(module=1))({'module': 1}) == False
800
801
802
def test_predicate_query_allowed():
803
    pytest.raises(TypeError, Query, 1)
804
    pytest.raises(TypeError, Query, a=1)
805
806
807
def test_predicate_when_allowed():
808
    pytest.raises(TypeError, When, 1)
809
810
811
@pytest.mark.parametrize('expr,inp,expected', [
812
    ({'module': "abc"}, {'module': "abc"}, True),
813
    ({'module': "abcd"}, {'module': "abc"}, False),
814
    ({'module': "abcd"}, {'module': "abce"}, False),
815
    ({'module_startswith': "abc"}, {'module': "abcd"}, True),
816
    ({'module__startswith': "abc"}, {'module': "abcd"}, True),
817
    ({'module_contains': "bc"}, {'module': "abcd"}, True),
818
    ({'module_contains': "bcde"}, {'module': "abcd"}, False),
819
820
    ({'module_endswith': "abc"}, {'module': "abcd"}, False),
821
    ({'module__endswith': "bcd"}, {'module': "abcd"}, True),
822
823
    ({'module_in': "abcd"}, {'module': "bc"}, True),
824
    ({'module': "abcd"}, {'module': "bc"}, False),
825
    ({'module': ["abcd"]}, {'module': "bc"}, False),
826
    ({'module_in': ["abcd"]}, {'module': "bc"}, False),
827
    ({'module_in': ["a", "bc", "d"]}, {'module': "bc"}, True),
828
829
    ({'module': "abcd"}, {'module': "abc"}, False),
830
831
    ({'module_startswith': ("abc", "xyz")}, {'module': "abc"}, True),
832
    ({'module_startswith': {"abc", "xyz"}}, {'module': "abc"}, True),
833
    ({'module_startswith': ["abc", "xyz"]}, {'module': "abc"}, True),
834
    ({'module_startswith': ("abc", "xyz")}, {'module': "abcd"}, True),
835
    ({'module_startswith': ("abc", "xyz")}, {'module': "xyzw"}, True),
836
    ({'module_startswith': ("abc", "xyz")}, {'module': "fooabc"}, False),
837
838
    ({'module_endswith': ("abc", "xyz")}, {'module': "abc"}, True),
839
    ({'module_endswith': {"abc", "xyz"}}, {'module': "abc"}, True),
840
    ({'module_endswith': ["abc", "xyz"]}, {'module': "abc"}, True),
841
    ({'module_endswith': ("abc", "xyz")}, {'module': "1abc"}, True),
842
    ({'module_endswith': ("abc", "xyz")}, {'module': "1xyz"}, True),
843
    ({'module_endswith': ("abc", "xyz")}, {'module': "abcfoo"}, False),
844
845
    ({'module': "abc"}, {'module': 1}, False),
846
847
    ({'module_regex': r"(re|sre.*)\b"}, {'module': "regex"}, False),
848
    ({'module_regex': r"(re|sre.*)\b"}, {'module': "re.gex"}, True),
849
    ({'module_regex': r"(re|sre.*)\b"}, {'module': "sregex"}, True),
850
    ({'module_regex': r"(re|sre.*)\b"}, {'module': "re"}, True),
851
])
852
def test_predicate_matching(expr, inp, expected):
853
    assert Query(**expr)(inp) == expected
854
855
856
@pytest.mark.parametrize('exc_type,expr', [
857
    (TypeError, {'module_1': 1}),
858
    (TypeError, {'module1': 1}),
859
    (ValueError, {'module_startswith': 1}),
860
    (ValueError, {'module_startswith': {1: 2}}),
861
    (ValueError, {'module_endswith': 1}),
862
    (ValueError, {'module_endswith': {1: 2}}),
863
    (TypeError, {'module_foo': 1}),
864
    (TypeError, {'module_a_b': 1}),
865
])
866
def test_predicate_bad_query(expr, exc_type):
867
    pytest.raises(exc_type, Query, **expr)
868
869
870
def test_predicate_when():
871
    called = []
872
    assert When(Q(module=1), lambda ev: called.append(ev))({'module': 2}) == False
873
    assert called == []
874
875
    assert When(Q(module=1), lambda ev: called.append(ev))({'module': 1}) == True
876
    assert called == [{'module': 1}]
877
878
    called = []
879
    assert Q(module=1, action=lambda ev: called.append(ev))({'module': 1}) == True
880
    assert called == [{'module': 1}]
881
882
    called = [[], []]
883
    predicate = (
884
        Q(module=1, action=lambda ev: called[0].append(ev)) |
885
        Q(module=2, action=lambda ev: called[1].append(ev))
886
    )
887
    assert predicate({'module': 1}) == True
888
    assert called == [[{'module': 1}], []]
889
890
    assert predicate({'module': 2}) == True
891
    assert called == [[{'module': 1}], [{'module': 2}]]
892
893
    called = [[], []]
894
    predicate = (
895
        Q(module=1, action=lambda ev: called[0].append(ev)) &
896
        Q(function=2, action=lambda ev: called[1].append(ev))
897
    )
898
    assert predicate({'module': 2}) == False
899
    assert called == [[], []]
900
901
    assert predicate({'module': 1, 'function': 2}) == True
902
    assert called == [[{'module': 1, 'function': 2}], [{'module': 1, 'function': 2}]]
903
904
905
def test_and_or_kwargs():
906
    assert And(module=1, function=2) == Query(module=1, function=2)
907
    assert Or(module=1, function=2) == Or(Query(module=1), Query(function=2))
908
909
    assert And(3, module=1, function=2) == And(3, Query(module=1, function=2))
910
    assert Or(3, module=1, function=2) == Or(3, Query(module=1), Query(function=2))
911
912
913
def test_proper_backend():
914
    if os.environ.get('PUREPYTHONHUNTER') or platform.python_implementation() == 'PyPy':
915
        assert 'hunter.tracer.Tracer' in repr(hunter.Tracer)
916
    else:
917
        assert 'hunter._tracer.Tracer' in repr(hunter.Tracer)
918
919
920
@pytest.fixture(scope="session", params=['pure', 'cython'])
921
def tracer_impl(request):
922
    if request.param == 'pure':
923
        return pytest.importorskip('hunter.tracer').Tracer
924
    elif request.param == 'cython':
925
        return pytest.importorskip('hunter._tracer').Tracer
926
927
928
def _tokenize():
929
    with open(tokenize.__file__, 'rb') as fh:
930
        toks = []
931
        try:
932
            for tok in getattr(tokenize, 'generate_tokens', tokenize.tokenize)(fh.readline):
933
                toks.append(tok)
934
        except tokenize.TokenError as exc:
935
            toks.append(exc)
936
937
938
def test_perf_filter(tracer_impl, benchmark):
939
    t = tracer_impl()
940
941
    @benchmark
942
    def run():
943
        output = StringIO()
944
        with t.trace(Q(
945
            Q(module="does-not-exist") | Q(module="does not exist".split()),
946
            action=CodePrinter(stream=output)
947
        )):
948
            _tokenize()
949
        return output
950
951
    assert run.getvalue() == ''
952
953
954
def test_perf_stdlib(tracer_impl, benchmark):
955
    t = tracer_impl()
956
957
    @benchmark
958
    def run():
959
        output = StringIO()
960
        with t.trace(Q(
961
            ~Q(module_contains='pytest'),
962
            ~Q(module_contains='hunter'),
963
            ~Q(filename=''),
964
            stdlib=False,
965
            action=CodePrinter(stream=output)
966
        )):
967
            _tokenize()
968
        return output
969
970
    assert run.getvalue() == ''
971
972
973
def test_perf_actions(tracer_impl, benchmark):
974
    t = tracer_impl()
975
976
    @benchmark
977
    def run():
978
        output = StringIO()
979
        with t.trace(Q(
980
            ~Q(module_in=['re', 'sre', 'sre_parse']) & ~Q(module_startswith='namedtuple') & Q(kind="call"),
981
            actions=[
982
                CodePrinter(
983
                    stream=output
984
                ),
985
                VarsPrinter(
986
                    'line',
987
                    globals=True,
988
                    stream=output
989
                )
990
            ]
991
        )):
992
            _tokenize()
993