Completed
Push — master ( a89eb3...1693f7 )
by Ionel Cristian
01:06
created

test_tracing_vars_expressions()   A

Complexity

Conditions 3

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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