Completed
Push — master ( 82185e...4d55e8 )
by Ionel Cristian
01:08
created

tests.test_mix_predicates_with_callables()   C

Complexity

Conditions 9

Size

Total Lines 10

Duplication

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