Completed
Push — master ( 449e73...c6f33b )
by Ionel Cristian
01:09
created

test_trace_api_expansion()   F

Complexity

Conditions 17

Size

Total Lines 40

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 17
dl 0
loc 40
rs 2.7204

How to fix   Complexity   

Complexity

Complex classes like test_trace_api_expansion() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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 Q
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 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_fullsource(LineMatcher):
590
    calls = []
591
    with trace(action=lambda event: calls.append(event.fullsource)):
592
        foo = bar = lambda x: x
593
594
        @foo
595
        @bar
596
        def foo():
597
            return 1
598
599
        foo()
600
601
    lm = LineMatcher(calls)
602
    lm.fnmatch_lines([
603
        '        foo = bar = lambda x: x\n',
604
        '        @foo\n        @bar\n        def foo():\n',
605
        '            return 1\n',
606
    ])
607
608
609
def test_debugger(LineMatcher):
610
    out = StringIO()
611
    calls = []
612
613
    class FakePDB:
614
        def __init__(self, foobar=1):
615
            calls.append(foobar)
616
617
        def set_trace(self, frame):
618
            calls.append(frame.f_code.co_name)
619
620
    with hunter.trace(
621
        lambda event: event.locals.get("node") == "Foobar",
622
        module="test_hunter",
623
        function="foo",
624
        actions=[VarsPrinter("a", "node", "foo", "test_debugger", globals=True, stream=out),
625
                 Debugger(klass=FakePDB, foobar=2)]
626
    ):
627
        def foo():
628
            a = 1
629
            node = "Foobar"
630
            node += "x"
631
            a += 2
632
            return a
633
634
        foo()
635
    print(out.getvalue())
636
    assert calls == [2, 'foo']
637
    lm = LineMatcher(out.getvalue().splitlines())
638
    pprint(lm.lines)
639
    lm.fnmatch_lines_random([
640
        "*      test_debugger => <function test_debugger at *",
641
        "*      node => 'Foobar'",
642
        "*      a => 1",
643
    ])
644
645
646
def test_custom_action():
647
    calls = []
648
649
    with trace(action=lambda event: calls.append(event.function), kind="return"):
650
        def foo():
651
            return 1
652
653
        foo()
654
    assert 'foo' in calls
655
656
657
def test_trace_with_class_actions():
658
    with trace(CodePrinter):
659
        def a():
660
            pass
661
662
        a()
663
664
665
def test_predicate_no_inf_recursion():
666
    assert Or(And(1)) == 1
667
    assert Or(Or(1)) == 1
668
    assert And(Or(1)) == 1
669
    assert And(And(1)) == 1
670
    predicate = Q(Q(lambda ev: 1, module='wat'))
671
    print('predicate:', predicate)
672
    predicate({'module': 'foo'})
673
674
675
def test_predicate_compression():
676
    assert Or(Or(1, 2), And(3)) == Or(1, 2, 3)
677
    assert Or(Or(1, 2), 3) == Or(1, 2, 3)
678
    assert Or(1, Or(2, 3), 4) == Or(1, 2, 3, 4)
679
    assert And(1, 2, Or(3, 4)).predicates == (1, 2, Or(3, 4))
680
681
    assert repr(Or(Or(1, 2), And(3))) == repr(Or(1, 2, 3))
682
    assert repr(Or(Or(1, 2), 3)) == repr(Or(1, 2, 3))
683
    assert repr(Or(1, Or(2, 3), 4)) == repr(Or(1, 2, 3, 4))
684
685
686
def test_predicate_not():
687
    assert Not(1).predicate == 1
688
    assert ~Or(1, 2) == Not(Or(1, 2))
689
    assert ~And(1, 2) == Not(And(1, 2))
690
691
    assert ~Not(1) == 1
692
693
    assert ~Query(module=1) | ~Query(module=2) == Not(And(Query(module=1), Query(module=2)))
694
    assert ~Query(module=1) & ~Query(module=2) == Not(Or(Query(module=1), Query(module=2)))
695
696
    assert ~Query(module=1) | Query(module=2) == Or(Not(Query(module=1)), Query(module=2))
697
    assert ~Query(module=1) & Query(module=2) == And(Not(Query(module=1)), Query(module=2))
698
699
    assert ~(Query(module=1) & Query(module=2)) == Not(And(Query(module=1), Query(module=2)))
700
    assert ~(Query(module=1) | Query(module=2)) == Not(Or(Query(module=1), Query(module=2)))
701
702
    assert repr(~Or(1, 2)) == repr(Not(Or(1, 2)))
703
    assert repr(~And(1, 2)) == repr(Not(And(1, 2)))
704
705
    assert repr(~Query(module=1) | ~Query(module=2)) == repr(Not(And(Query(module=1), Query(module=2))))
706
    assert repr(~Query(module=1) & ~Query(module=2)) == repr(Not(Or(Query(module=1), Query(module=2))))
707
708
    assert repr(~(Query(module=1) & Query(module=2))) == repr(Not(And(Query(module=1), Query(module=2))))
709
    assert repr(~(Query(module=1) | Query(module=2))) == repr(Not(Or(Query(module=1), Query(module=2))))
710
711
    assert Not(Q(module=1))({'module': 1}) == False
712
713
714
def test_predicate_query_allowed():
715
    pytest.raises(TypeError, Query, 1)
716
    pytest.raises(TypeError, Query, a=1)
717
718
719
def test_predicate_when_allowed():
720
    pytest.raises(TypeError, When, 1)
721
722
723
@pytest.mark.parametrize('expr,inp,expected', [
724
    ({'module': "abc"}, {'module': "abc"}, True),
725
    ({'module': "abcd"}, {'module': "abc"}, False),
726
    ({'module': "abcd"}, {'module': "abce"}, False),
727
    ({'module_startswith': "abc"}, {'module': "abcd"}, True),
728
    ({'module__startswith': "abc"}, {'module': "abcd"}, True),
729
    ({'module_contains': "bc"}, {'module': "abcd"}, True),
730
    ({'module_contains': "bcde"}, {'module': "abcd"}, False),
731
732
    ({'module_endswith': "abc"}, {'module': "abcd"}, False),
733
    ({'module__endswith': "bcd"}, {'module': "abcd"}, True),
734
735
    ({'module_in': "abcd"}, {'module': "bc"}, True),
736
    ({'module': "abcd"}, {'module': "bc"}, False),
737
    ({'module': ["abcd"]}, {'module': "bc"}, False),
738
    ({'module_in': ["abcd"]}, {'module': "bc"}, False),
739
    ({'module_in': ["a", "bc", "d"]}, {'module': "bc"}, True),
740
741
    ({'module': "abcd"}, {'module': "abc"}, False),
742
743
    ({'module_startswith': ("abc", "xyz")}, {'module': "abc"}, True),
744
    ({'module_startswith': {"abc", "xyz"}}, {'module': "abc"}, True),
745
    ({'module_startswith': ["abc", "xyz"]}, {'module': "abc"}, True),
746
    ({'module_startswith': ("abc", "xyz")}, {'module': "abcd"}, True),
747
    ({'module_startswith': ("abc", "xyz")}, {'module': "xyzw"}, True),
748
    ({'module_startswith': ("abc", "xyz")}, {'module': "fooabc"}, False),
749
750
    ({'module_endswith': ("abc", "xyz")}, {'module': "abc"}, True),
751
    ({'module_endswith': {"abc", "xyz"}}, {'module': "abc"}, True),
752
    ({'module_endswith': ["abc", "xyz"]}, {'module': "abc"}, True),
753
    ({'module_endswith': ("abc", "xyz")}, {'module': "1abc"}, True),
754
    ({'module_endswith': ("abc", "xyz")}, {'module': "1xyz"}, True),
755
    ({'module_endswith': ("abc", "xyz")}, {'module': "abcfoo"}, False),
756
757
    ({'module': "abc"}, {'module': 1}, False),
758
759
    ({'module_regex': r"(re|sre.*)\b"}, {'module': "regex"}, False),
760
    ({'module_regex': r"(re|sre.*)\b"}, {'module': "re.gex"}, True),
761
    ({'module_regex': r"(re|sre.*)\b"}, {'module': "sregex"}, True),
762
    ({'module_regex': r"(re|sre.*)\b"}, {'module': "re"}, True),
763
])
764
def test_predicate_matching(expr, inp, expected):
765
    assert Query(**expr)(inp) == expected
766
767
768
@pytest.mark.parametrize('exc_type,expr', [
769
    (TypeError, {'module_1': 1}),
770
    (TypeError, {'module1': 1}),
771
    (ValueError, {'module_startswith': 1}),
772
    (ValueError, {'module_startswith': {1: 2}}),
773
    (ValueError, {'module_endswith': 1}),
774
    (ValueError, {'module_endswith': {1: 2}}),
775
    (TypeError, {'module_foo': 1}),
776
    (TypeError, {'module_a_b': 1}),
777
])
778
def test_predicate_bad_query(expr, exc_type):
779
    pytest.raises(exc_type, Query, **expr)
780
781
782
def test_predicate_when():
783
    called = []
784
    assert When(Q(module=1), lambda ev: called.append(ev))({'module': 2}) == False
785
    assert called == []
786
787
    assert When(Q(module=1), lambda ev: called.append(ev))({'module': 1}) == True
788
    assert called == [{'module': 1}]
789
790
    called = []
791
    assert Q(module=1, action=lambda ev: called.append(ev))({'module': 1}) == True
792
    assert called == [{'module': 1}]
793
794
    called = [[], []]
795
    predicate = (
796
        Q(module=1, action=lambda ev: called[0].append(ev)) |
797
        Q(module=2, action=lambda ev: called[1].append(ev))
798
    )
799
    assert predicate({'module': 1}) == True
800
    assert called == [[{'module': 1}], []]
801
802
    assert predicate({'module': 2}) == True
803
    assert called == [[{'module': 1}], [{'module': 2}]]
804
805
    called = [[], []]
806
    predicate = (
807
        Q(module=1, action=lambda ev: called[0].append(ev)) &
808
        Q(function=2, action=lambda ev: called[1].append(ev))
809
    )
810
    assert predicate({'module': 2}) == False
811
    assert called == [[], []]
812
813
    assert predicate({'module': 1, 'function': 2}) == True
814
    assert called == [[{'module': 1, 'function': 2}], [{'module': 1, 'function': 2}]]
815
816
817
def test_and_or_kwargs():
818
    assert And(module=1, function=2) == Query(module=1, function=2)
819
    assert Or(module=1, function=2) == Or(Query(module=1), Query(function=2))
820
821
    assert And(3, module=1, function=2) == And(3, Query(module=1, function=2))
822
    assert Or(3, module=1, function=2) == Or(3, Query(module=1), Query(function=2))
823
824
825
def test_proper_backend():
826
    if os.environ.get('PUREPYTHONHUNTER') or platform.python_implementation() == 'PyPy':
827
        assert 'hunter.tracer.Tracer' in repr(hunter.Tracer)
828
    else:
829
        assert 'hunter._tracer.Tracer' in repr(hunter.Tracer)
830
831
832
@pytest.fixture(scope="session", params=['pure', 'cython'])
833
def tracer_impl(request):
834
    if request.param == 'pure':
835
        return pytest.importorskip('hunter.tracer').Tracer
836
    elif request.param == 'cython':
837
        return pytest.importorskip('hunter._tracer').Tracer
838
839
840
def _tokenize():
841
    with open(tokenize.__file__, 'rb') as fh:
842
        toks = []
843
        try:
844
            for tok in tokenize.tokenize(fh.readline):
845
                toks.append(tok)
846
        except tokenize.TokenError as exc:
847
            toks.append(exc)
848
849
850
def test_perf_filter(tracer_impl, benchmark):
851
    t = tracer_impl()
852
853
    @benchmark
854
    def run():
855
        output = StringIO()
856
        with t.trace(Q(
857
            Q(module="does-not-exist") | Q(module="does not exist".split()),
858
            action=CodePrinter(stream=output)
859
        )):
860
            _tokenize()
861
        return output
862
863
    assert run.getvalue() == ''
864
865
866
def test_perf_stdlib(tracer_impl, benchmark):
867
    t = tracer_impl()
868
869
    @benchmark
870
    def run():
871
        output = StringIO()
872
        with t.trace(Q(
873
            ~Q(module_contains='pytest'),
874
            ~Q(module_contains='hunter'),
875
            ~Q(filename=''),
876
            stdlib=False,
877
            action=CodePrinter(stream=output)
878
        )):
879
            _tokenize()
880
        return output
881
882
    assert run.getvalue() == ''
883
884
885
def test_perf_actions(tracer_impl, benchmark):
886
    t = tracer_impl()
887
888
    @benchmark
889
    def run():
890
        output = StringIO()
891
        with t.trace(Q(
892
            ~Q(module_in=['re', 'sre', 'sre_parse']) & ~Q(module_startswith='namedtuple') & Q(kind="call"),
893
            actions=[
894
                CodePrinter(
895
                    stream=output
896
                ),
897
                VarsPrinter(
898
                    'line',
899
                    globals=True,
900
                    stream=output
901
                )
902
            ]
903
        )):
904
            _tokenize()
905