Completed
Push — master ( 3bc815...5816a7 )
by Ionel Cristian
01:13
created

test_source_cython()   A

Complexity

Conditions 3

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
c 0
b 0
f 0
dl 0
loc 10
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_trace_merge():
451
    with hunter.trace(function="a"):
452
        with hunter.trace(function="b"):
453
            with hunter.trace(function="c"):
454
                assert sys.gettrace().handler == When(Q(function="c"), CodePrinter)
455
            assert sys.gettrace().handler == When(Q(function="b"), CodePrinter)
456
        assert sys.gettrace().handler == When(Q(function="a"), CodePrinter)
457
458
459
def test_trace_api_expansion():
460
    # simple use
461
    with trace(function="foobar") as t:
462
        assert t.handler == When(Q(function="foobar"), CodePrinter)
463
464
    # "or" by expression
465
    with trace(module="foo", function="foobar") as t:
466
        assert t.handler == When(Q(module="foo", function="foobar"), CodePrinter)
467
468
    # pdb.set_trace
469
    with trace(function="foobar", action=Debugger) as t:
470
        assert str(t.handler) == str(When(Q(function="foobar"), Debugger))
471
472
    # pdb.set_trace on any hits
473
    with trace(module="foo", function="foobar", action=Debugger) as t:
474
        assert str(t.handler) == str(When(Q(module="foo", function="foobar"), Debugger))
475
476
    # pdb.set_trace when function is foobar, otherwise just print when module is foo
477
    with trace(Q(function="foobar", action=Debugger), module="foo") as t:
478
        assert str(t.handler) == str(When(And(
479
            When(Q(function="foobar"), Debugger),
480
            Q(module="foo")
481
        ), CodePrinter))
482
483
    # dumping variables from stack
484
    with trace(Q(function="foobar", action=VarsPrinter("foobar")), module="foo") as t:
485
        assert str(t.handler) == str(When(And(
486
            When(Q(function="foobar"), VarsPrinter("foobar")),
487
            Q(module="foo"),
488
        ), CodePrinter))
489
490
    with trace(Q(function="foobar", action=VarsPrinter("foobar", "mumbojumbo")), module="foo") as t:
491
        assert str(t.handler) == str(When(And(
492
            When(Q(function="foobar"), VarsPrinter("foobar", "mumbojumbo")),
493
            Q(module="foo"),
494
        ), CodePrinter))
495
496
    # multiple actions
497
    with trace(Q(function="foobar", actions=[VarsPrinter("foobar"), Debugger]), module="foo") as t:
498
        assert str(t.handler) == str(When(And(
499
            When(Q(function="foobar"), VarsPrinter("foobar"), Debugger),
500
            Q(module="foo"),
501
        ), CodePrinter))
502
503
504
def test_locals():
505
    out = StringIO()
506
    with hunter.trace(
507
        lambda event: event.locals.get("node") == "Foobar",
508
        module="test_hunter",
509
        function="foo",
510
        action=CodePrinter(stream=out)
511
    ):
512
        def foo():
513
            a = 1
514
            node = "Foobar"
515
            node += "x"
516
            a += 2
517
            return a
518
519
        foo()
520
    assert out.getvalue().endswith('node += "x"\n')
521
522
523
def test_fullsource_decorator_issue(LineMatcher):
524
    out = StringIO()
525
    with trace(kind='call', action=CodePrinter(stream=out)):
526
        foo = bar = lambda x: x
527
528
        @foo
529
        @bar
530
        def foo():
531
            return 1
532
533
        foo()
534
535
    lm = LineMatcher(out.getvalue().splitlines())
536
    lm.fnmatch_lines([
537
        '* call              @foo',
538
        '*    |              @bar',
539
        '*    |              def foo():',
540
    ])
541
542
543
def test_callprinter(LineMatcher):
544
    out = StringIO()
545
    with trace(action=CallPrinter(stream=out)):
546
        foo = bar = lambda x: x
547
548
        @foo
549
        @bar
550
        def foo():
551
            return 1
552
553
        foo()
554
555
    lm = LineMatcher(out.getvalue().splitlines())
556
    lm.fnmatch_lines([
557
        '* call      => <lambda>(x=<function *foo at *>)',
558
        '* line         foo = bar = lambda x: x',
559
        '* return    <= <lambda>: <function *foo at *>',
560
        '* call      => <lambda>(x=<function *foo at *>)',
561
        '* line         foo = bar = lambda x: x',
562
        '* return    <= <lambda>: <function *foo at *>',
563
        '* call      => foo()',
564
        '* line         return 1',
565
        '* return    <= foo: 1',
566
    ])
567
568
569
def test_source(LineMatcher):
570
    calls = []
571
    with trace(action=lambda event: calls.append(event.source)):
572
        foo = bar = lambda x: x
573
574
        @foo
575
        @bar
576
        def foo():
577
            return 1
578
579
        foo()
580
581
    lm = LineMatcher(calls)
582
    lm.fnmatch_lines([
583
        '        foo = bar = lambda x: x\n',
584
        '        @foo\n',
585
        '            return 1\n',
586
    ])
587
588
589
def test_source_cython(LineMatcher):
590
    calls = []
591
    from sample5 import foo
592
    with trace(action=lambda event: calls.append(event.source)):
593
        foo()
594
595
    lm = LineMatcher(calls)
596
    lm.fnmatch_lines([
597
        'def foo():\n',
598
        '    return 1\n',
599
    ])
600
601
602
def test_fullsource(LineMatcher):
603
    calls = []
604
    with trace(action=lambda event: calls.append(event.fullsource)):
605
        foo = bar = lambda x: x
606
607
        @foo
608
        @bar
609
        def foo():
610
            return 1
611
612
        foo()
613
614
    lm = LineMatcher(calls)
615
    lm.fnmatch_lines([
616
        '        foo = bar = lambda x: x\n',
617
        '        @foo\n        @bar\n        def foo():\n',
618
        '            return 1\n',
619
    ])
620
621
622
def test_fullsource_cython(LineMatcher):
623
    calls = []
624
    from sample5 import foo
625
    with trace(action=lambda event: calls.append(event.fullsource)):
626
        foo()
627
628
    lm = LineMatcher(calls)
629
    lm.fnmatch_lines([
630
        'def foo():\n',
631
        '    return 1\n',
632
    ])
633
634
635
def test_debugger(LineMatcher):
636
    out = StringIO()
637
    calls = []
638
639
    class FakePDB:
640
        def __init__(self, foobar=1):
641
            calls.append(foobar)
642
643
        def set_trace(self, frame):
644
            calls.append(frame.f_code.co_name)
645
646
    with hunter.trace(
647
        lambda event: event.locals.get("node") == "Foobar",
648
        module="test_hunter",
649
        function="foo",
650
        actions=[VarsPrinter("a", "node", "foo", "test_debugger", globals=True, stream=out),
651
                 Debugger(klass=FakePDB, foobar=2)]
652
    ):
653
        def foo():
654
            a = 1
655
            node = "Foobar"
656
            node += "x"
657
            a += 2
658
            return a
659
660
        foo()
661
    print(out.getvalue())
662
    assert calls == [2, 'foo']
663
    lm = LineMatcher(out.getvalue().splitlines())
664
    pprint(lm.lines)
665
    lm.fnmatch_lines_random([
666
        "*      test_debugger => <function test_debugger at *",
667
        "*      node => 'Foobar'",
668
        "*      a => 1",
669
    ])
670
671
672
def test_custom_action():
673
    calls = []
674
675
    with trace(action=lambda event: calls.append(event.function), kind="return"):
676
        def foo():
677
            return 1
678
679
        foo()
680
    assert 'foo' in calls
681
682
683
def test_trace_with_class_actions():
684
    with trace(CodePrinter):
685
        def a():
686
            pass
687
688
        a()
689
690
691
def test_predicate_no_inf_recursion():
692
    assert Or(And(1)) == 1
693
    assert Or(Or(1)) == 1
694
    assert And(Or(1)) == 1
695
    assert And(And(1)) == 1
696
    predicate = Q(Q(lambda ev: 1, module='wat'))
697
    print('predicate:', predicate)
698
    predicate({'module': 'foo'})
699
700
701
def test_predicate_compression():
702
    assert Or(Or(1, 2), And(3)) == Or(1, 2, 3)
703
    assert Or(Or(1, 2), 3) == Or(1, 2, 3)
704
    assert Or(1, Or(2, 3), 4) == Or(1, 2, 3, 4)
705
    assert And(1, 2, Or(3, 4)).predicates == (1, 2, Or(3, 4))
706
707
    assert repr(Or(Or(1, 2), And(3))) == repr(Or(1, 2, 3))
708
    assert repr(Or(Or(1, 2), 3)) == repr(Or(1, 2, 3))
709
    assert repr(Or(1, Or(2, 3), 4)) == repr(Or(1, 2, 3, 4))
710
711
712
def test_predicate_not():
713
    assert Not(1).predicate == 1
714
    assert ~Or(1, 2) == Not(Or(1, 2))
715
    assert ~And(1, 2) == Not(And(1, 2))
716
717
    assert ~Not(1) == 1
718
719
    assert ~Query(module=1) | ~Query(module=2) == Not(And(Query(module=1), Query(module=2)))
720
    assert ~Query(module=1) & ~Query(module=2) == Not(Or(Query(module=1), Query(module=2)))
721
722
    assert ~Query(module=1) | Query(module=2) == Or(Not(Query(module=1)), Query(module=2))
723
    assert ~Query(module=1) & Query(module=2) == And(Not(Query(module=1)), Query(module=2))
724
725
    assert ~(Query(module=1) & Query(module=2)) == Not(And(Query(module=1), Query(module=2)))
726
    assert ~(Query(module=1) | Query(module=2)) == Not(Or(Query(module=1), Query(module=2)))
727
728
    assert repr(~Or(1, 2)) == repr(Not(Or(1, 2)))
729
    assert repr(~And(1, 2)) == repr(Not(And(1, 2)))
730
731
    assert repr(~Query(module=1) | ~Query(module=2)) == repr(Not(And(Query(module=1), Query(module=2))))
732
    assert repr(~Query(module=1) & ~Query(module=2)) == repr(Not(Or(Query(module=1), Query(module=2))))
733
734
    assert repr(~(Query(module=1) & Query(module=2))) == repr(Not(And(Query(module=1), Query(module=2))))
735
    assert repr(~(Query(module=1) | Query(module=2))) == repr(Not(Or(Query(module=1), Query(module=2))))
736
737
    assert Not(Q(module=1))({'module': 1}) == False
738
739
740
def test_predicate_query_allowed():
741
    pytest.raises(TypeError, Query, 1)
742
    pytest.raises(TypeError, Query, a=1)
743
744
745
def test_predicate_when_allowed():
746
    pytest.raises(TypeError, When, 1)
747
748
749
@pytest.mark.parametrize('expr,inp,expected', [
750
    ({'module': "abc"}, {'module': "abc"}, True),
751
    ({'module': "abcd"}, {'module': "abc"}, False),
752
    ({'module': "abcd"}, {'module': "abce"}, False),
753
    ({'module_startswith': "abc"}, {'module': "abcd"}, True),
754
    ({'module__startswith': "abc"}, {'module': "abcd"}, True),
755
    ({'module_contains': "bc"}, {'module': "abcd"}, True),
756
    ({'module_contains': "bcde"}, {'module': "abcd"}, False),
757
758
    ({'module_endswith': "abc"}, {'module': "abcd"}, False),
759
    ({'module__endswith': "bcd"}, {'module': "abcd"}, True),
760
761
    ({'module_in': "abcd"}, {'module': "bc"}, True),
762
    ({'module': "abcd"}, {'module': "bc"}, False),
763
    ({'module': ["abcd"]}, {'module': "bc"}, False),
764
    ({'module_in': ["abcd"]}, {'module': "bc"}, False),
765
    ({'module_in': ["a", "bc", "d"]}, {'module': "bc"}, True),
766
767
    ({'module': "abcd"}, {'module': "abc"}, False),
768
769
    ({'module_startswith': ("abc", "xyz")}, {'module': "abc"}, True),
770
    ({'module_startswith': {"abc", "xyz"}}, {'module': "abc"}, True),
771
    ({'module_startswith': ["abc", "xyz"]}, {'module': "abc"}, True),
772
    ({'module_startswith': ("abc", "xyz")}, {'module': "abcd"}, True),
773
    ({'module_startswith': ("abc", "xyz")}, {'module': "xyzw"}, True),
774
    ({'module_startswith': ("abc", "xyz")}, {'module': "fooabc"}, False),
775
776
    ({'module_endswith': ("abc", "xyz")}, {'module': "abc"}, True),
777
    ({'module_endswith': {"abc", "xyz"}}, {'module': "abc"}, True),
778
    ({'module_endswith': ["abc", "xyz"]}, {'module': "abc"}, True),
779
    ({'module_endswith': ("abc", "xyz")}, {'module': "1abc"}, True),
780
    ({'module_endswith': ("abc", "xyz")}, {'module': "1xyz"}, True),
781
    ({'module_endswith': ("abc", "xyz")}, {'module': "abcfoo"}, False),
782
783
    ({'module': "abc"}, {'module': 1}, False),
784
785
    ({'module_regex': r"(re|sre.*)\b"}, {'module': "regex"}, False),
786
    ({'module_regex': r"(re|sre.*)\b"}, {'module': "re.gex"}, True),
787
    ({'module_regex': r"(re|sre.*)\b"}, {'module': "sregex"}, True),
788
    ({'module_regex': r"(re|sre.*)\b"}, {'module': "re"}, True),
789
])
790
def test_predicate_matching(expr, inp, expected):
791
    assert Query(**expr)(inp) == expected
792
793
794
@pytest.mark.parametrize('exc_type,expr', [
795
    (TypeError, {'module_1': 1}),
796
    (TypeError, {'module1': 1}),
797
    (ValueError, {'module_startswith': 1}),
798
    (ValueError, {'module_startswith': {1: 2}}),
799
    (ValueError, {'module_endswith': 1}),
800
    (ValueError, {'module_endswith': {1: 2}}),
801
    (TypeError, {'module_foo': 1}),
802
    (TypeError, {'module_a_b': 1}),
803
])
804
def test_predicate_bad_query(expr, exc_type):
805
    pytest.raises(exc_type, Query, **expr)
806
807
808
def test_predicate_when():
809
    called = []
810
    assert When(Q(module=1), lambda ev: called.append(ev))({'module': 2}) == False
811
    assert called == []
812
813
    assert When(Q(module=1), lambda ev: called.append(ev))({'module': 1}) == True
814
    assert called == [{'module': 1}]
815
816
    called = []
817
    assert Q(module=1, action=lambda ev: called.append(ev))({'module': 1}) == True
818
    assert called == [{'module': 1}]
819
820
    called = [[], []]
821
    predicate = (
822
        Q(module=1, action=lambda ev: called[0].append(ev)) |
823
        Q(module=2, action=lambda ev: called[1].append(ev))
824
    )
825
    assert predicate({'module': 1}) == True
826
    assert called == [[{'module': 1}], []]
827
828
    assert predicate({'module': 2}) == True
829
    assert called == [[{'module': 1}], [{'module': 2}]]
830
831
    called = [[], []]
832
    predicate = (
833
        Q(module=1, action=lambda ev: called[0].append(ev)) &
834
        Q(function=2, action=lambda ev: called[1].append(ev))
835
    )
836
    assert predicate({'module': 2}) == False
837
    assert called == [[], []]
838
839
    assert predicate({'module': 1, 'function': 2}) == True
840
    assert called == [[{'module': 1, 'function': 2}], [{'module': 1, 'function': 2}]]
841
842
843
def test_and_or_kwargs():
844
    assert And(module=1, function=2) == Query(module=1, function=2)
845
    assert Or(module=1, function=2) == Or(Query(module=1), Query(function=2))
846
847
    assert And(3, module=1, function=2) == And(3, Query(module=1, function=2))
848
    assert Or(3, module=1, function=2) == Or(3, Query(module=1), Query(function=2))
849
850
851
def test_proper_backend():
852
    if os.environ.get('PUREPYTHONHUNTER') or platform.python_implementation() == 'PyPy':
853
        assert 'hunter.tracer.Tracer' in repr(hunter.Tracer)
854
    else:
855
        assert 'hunter._tracer.Tracer' in repr(hunter.Tracer)
856
857
858
@pytest.fixture(scope="session", params=['pure', 'cython'])
859
def tracer_impl(request):
860
    if request.param == 'pure':
861
        return pytest.importorskip('hunter.tracer').Tracer
862
    elif request.param == 'cython':
863
        return pytest.importorskip('hunter._tracer').Tracer
864
865
866
def _tokenize():
867
    with open(tokenize.__file__, 'rb') as fh:
868
        toks = []
869
        try:
870
            for tok in tokenize.tokenize(fh.readline):
871
                toks.append(tok)
872
        except tokenize.TokenError as exc:
873
            toks.append(exc)
874
875
876
def test_perf_filter(tracer_impl, benchmark):
877
    t = tracer_impl()
878
879
    @benchmark
880
    def run():
881
        output = StringIO()
882
        with t.trace(Q(
883
            Q(module="does-not-exist") | Q(module="does not exist".split()),
884
            action=CodePrinter(stream=output)
885
        )):
886
            _tokenize()
887
        return output
888
889
    assert run.getvalue() == ''
890
891
892
def test_perf_stdlib(tracer_impl, benchmark):
893
    t = tracer_impl()
894
895
    @benchmark
896
    def run():
897
        output = StringIO()
898
        with t.trace(Q(
899
            ~Q(module_contains='pytest'),
900
            ~Q(module_contains='hunter'),
901
            ~Q(filename=''),
902
            stdlib=False,
903
            action=CodePrinter(stream=output)
904
        )):
905
            _tokenize()
906
        return output
907
908
    assert run.getvalue() == ''
909
910
911
def test_perf_actions(tracer_impl, benchmark):
912
    t = tracer_impl()
913
914
    @benchmark
915
    def run():
916
        output = StringIO()
917
        with t.trace(Q(
918
            ~Q(module_in=['re', 'sre', 'sre_parse']) & ~Q(module_startswith='namedtuple') & Q(kind="call"),
919
            actions=[
920
                CodePrinter(
921
                    stream=output
922
                ),
923
                VarsPrinter(
924
                    'line',
925
                    globals=True,
926
                    stream=output
927
                )
928
            ]
929
        )):
930
            _tokenize()
931