Completed
Push — master ( 4d9ddb...9ef5ed )
by Ionel Cristian
27s
created

_tokenize()   A

Complexity

Conditions 4

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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