Completed
Push — master ( 22d30d...66c74e )
by Ionel Cristian
39s
created

test_threading_support()   F

Complexity

Conditions 9

Size

Total Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 6
Bugs 0 Features 0
Metric Value
cc 9
c 6
b 0
f 0
dl 0
loc 39
rs 3

2 Methods

Rating   Name   Duplication   Size   Complexity  
A record() 0 4 1
A main() 0 2 1
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 'MainThread' in names
322
    assert any(name.startswith('Thread-') for name in names)
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=[CodePrinter,
715
                 VarsPrinter("a", "node", "foo", "test_debugger", globals=True, stream=out),
716
                 Debugger(klass=FakePDB, foobar=2)]
717
    ):
718
        def foo():
719
            a = 1
720
            node = "Foobar"
721
            node += "x"
722
            a += 2
723
            return a
724
725
        foo()
726
    print(out.getvalue())
727
    assert calls == [2, 'foo']
728
    lm = LineMatcher(out.getvalue().splitlines())
729
    pprint(lm.lines)
730
    lm.fnmatch_lines_random([
731
        "*      test_debugger => <function test_debugger at *",
732
        "*      node => 'Foobar'",
733
        "*      a => 1",
734
    ])
735
736
737
def test_custom_action():
738
    calls = []
739
740
    with trace(action=lambda event: calls.append(event.function), kind="return"):
741
        def foo():
742
            return 1
743
744
        foo()
745
    assert 'foo' in calls
746
747
748
def test_trace_with_class_actions():
749
    with trace(CodePrinter):
750
        def a():
751
            pass
752
753
        a()
754
755
756
def test_predicate_no_inf_recursion():
757
    assert Or(And(1)) == 1
758
    assert Or(Or(1)) == 1
759
    assert And(Or(1)) == 1
760
    assert And(And(1)) == 1
761
    predicate = Q(Q(lambda ev: 1, module='wat'))
762
    print('predicate:', predicate)
763
    predicate({'module': 'foo'})
764
765
766
def test_predicate_compression():
767
    assert Or(Or(1, 2), And(3)) == Or(1, 2, 3)
768
    assert Or(Or(1, 2), 3) == Or(1, 2, 3)
769
    assert Or(1, Or(2, 3), 4) == Or(1, 2, 3, 4)
770
    assert And(1, 2, Or(3, 4)).predicates == (1, 2, Or(3, 4))
771
772
    assert repr(Or(Or(1, 2), And(3))) == repr(Or(1, 2, 3))
773
    assert repr(Or(Or(1, 2), 3)) == repr(Or(1, 2, 3))
774
    assert repr(Or(1, Or(2, 3), 4)) == repr(Or(1, 2, 3, 4))
775
776
777
def test_predicate_not():
778
    assert Not(1).predicate == 1
779
    assert ~Or(1, 2) == Not(Or(1, 2))
780
    assert ~And(1, 2) == Not(And(1, 2))
781
782
    assert ~Not(1) == 1
783
784
    assert ~Query(module=1) | ~Query(module=2) == Not(And(Query(module=1), Query(module=2)))
785
    assert ~Query(module=1) & ~Query(module=2) == Not(Or(Query(module=1), Query(module=2)))
786
787
    assert ~Query(module=1) | Query(module=2) == Or(Not(Query(module=1)), Query(module=2))
788
    assert ~Query(module=1) & Query(module=2) == And(Not(Query(module=1)), Query(module=2))
789
790
    assert ~(Query(module=1) & Query(module=2)) == Not(And(Query(module=1), Query(module=2)))
791
    assert ~(Query(module=1) | Query(module=2)) == Not(Or(Query(module=1), Query(module=2)))
792
793
    assert repr(~Or(1, 2)) == repr(Not(Or(1, 2)))
794
    assert repr(~And(1, 2)) == repr(Not(And(1, 2)))
795
796
    assert repr(~Query(module=1) | ~Query(module=2)) == repr(Not(And(Query(module=1), Query(module=2))))
797
    assert repr(~Query(module=1) & ~Query(module=2)) == repr(Not(Or(Query(module=1), Query(module=2))))
798
799
    assert repr(~(Query(module=1) & Query(module=2))) == repr(Not(And(Query(module=1), Query(module=2))))
800
    assert repr(~(Query(module=1) | Query(module=2))) == repr(Not(Or(Query(module=1), Query(module=2))))
801
802
    assert Not(Q(module=1))({'module': 1}) == False
803
804
805
def test_predicate_query_allowed():
806
    pytest.raises(TypeError, Query, 1)
807
    pytest.raises(TypeError, Query, a=1)
808
809
810
def test_predicate_when_allowed():
811
    pytest.raises(TypeError, When, 1)
812
813
814
@pytest.mark.parametrize('expr,inp,expected', [
815
    ({'module': "abc"}, {'module': "abc"}, True),
816
    ({'module': "abcd"}, {'module': "abc"}, False),
817
    ({'module': "abcd"}, {'module': "abce"}, False),
818
    ({'module_startswith': "abc"}, {'module': "abcd"}, True),
819
    ({'module__startswith': "abc"}, {'module': "abcd"}, True),
820
    ({'module_contains': "bc"}, {'module': "abcd"}, True),
821
    ({'module_contains': "bcde"}, {'module': "abcd"}, False),
822
823
    ({'module_endswith': "abc"}, {'module': "abcd"}, False),
824
    ({'module__endswith': "bcd"}, {'module': "abcd"}, True),
825
826
    ({'module_in': "abcd"}, {'module': "bc"}, True),
827
    ({'module': "abcd"}, {'module': "bc"}, False),
828
    ({'module': ["abcd"]}, {'module': "bc"}, False),
829
    ({'module_in': ["abcd"]}, {'module': "bc"}, False),
830
    ({'module_in': ["a", "bc", "d"]}, {'module': "bc"}, True),
831
832
    ({'module': "abcd"}, {'module': "abc"}, False),
833
834
    ({'module_startswith': ("abc", "xyz")}, {'module': "abc"}, True),
835
    ({'module_startswith': {"abc", "xyz"}}, {'module': "abc"}, True),
836
    ({'module_startswith': ["abc", "xyz"]}, {'module': "abc"}, True),
837
    ({'module_startswith': ("abc", "xyz")}, {'module': "abcd"}, True),
838
    ({'module_startswith': ("abc", "xyz")}, {'module': "xyzw"}, True),
839
    ({'module_startswith': ("abc", "xyz")}, {'module': "fooabc"}, False),
840
841
    ({'module_endswith': ("abc", "xyz")}, {'module': "abc"}, True),
842
    ({'module_endswith': {"abc", "xyz"}}, {'module': "abc"}, True),
843
    ({'module_endswith': ["abc", "xyz"]}, {'module': "abc"}, True),
844
    ({'module_endswith': ("abc", "xyz")}, {'module': "1abc"}, True),
845
    ({'module_endswith': ("abc", "xyz")}, {'module': "1xyz"}, True),
846
    ({'module_endswith': ("abc", "xyz")}, {'module': "abcfoo"}, False),
847
848
    ({'module': "abc"}, {'module': 1}, False),
849
850
    ({'module_regex': r"(re|sre.*)\b"}, {'module': "regex"}, False),
851
    ({'module_regex': r"(re|sre.*)\b"}, {'module': "re.gex"}, True),
852
    ({'module_regex': r"(re|sre.*)\b"}, {'module': "sregex"}, True),
853
    ({'module_regex': r"(re|sre.*)\b"}, {'module': "re"}, True),
854
])
855
def test_predicate_matching(expr, inp, expected):
856
    assert Query(**expr)(inp) == expected
857
858
859
@pytest.mark.parametrize('exc_type,expr', [
860
    (TypeError, {'module_1': 1}),
861
    (TypeError, {'module1': 1}),
862
    (ValueError, {'module_startswith': 1}),
863
    (ValueError, {'module_startswith': {1: 2}}),
864
    (ValueError, {'module_endswith': 1}),
865
    (ValueError, {'module_endswith': {1: 2}}),
866
    (TypeError, {'module_foo': 1}),
867
    (TypeError, {'module_a_b': 1}),
868
])
869
def test_predicate_bad_query(expr, exc_type):
870
    pytest.raises(exc_type, Query, **expr)
871
872
873
def test_predicate_when():
874
    called = []
875
    assert When(Q(module=1), lambda ev: called.append(ev))({'module': 2}) == False
876
    assert called == []
877
878
    assert When(Q(module=1), lambda ev: called.append(ev))({'module': 1}) == True
879
    assert called == [{'module': 1}]
880
881
    called = []
882
    assert Q(module=1, action=lambda ev: called.append(ev))({'module': 1}) == True
883
    assert called == [{'module': 1}]
884
885
    called = [[], []]
886
    predicate = (
887
        Q(module=1, action=lambda ev: called[0].append(ev)) |
888
        Q(module=2, action=lambda ev: called[1].append(ev))
889
    )
890
    assert predicate({'module': 1}) == True
891
    assert called == [[{'module': 1}], []]
892
893
    assert predicate({'module': 2}) == True
894
    assert called == [[{'module': 1}], [{'module': 2}]]
895
896
    called = [[], []]
897
    predicate = (
898
        Q(module=1, action=lambda ev: called[0].append(ev)) &
899
        Q(function=2, action=lambda ev: called[1].append(ev))
900
    )
901
    assert predicate({'module': 2}) == False
902
    assert called == [[], []]
903
904
    assert predicate({'module': 1, 'function': 2}) == True
905
    assert called == [[{'module': 1, 'function': 2}], [{'module': 1, 'function': 2}]]
906
907
908
def test_and_or_kwargs():
909
    assert And(module=1, function=2) == Query(module=1, function=2)
910
    assert Or(module=1, function=2) == Or(Query(module=1), Query(function=2))
911
912
    assert And(3, module=1, function=2) == And(3, Query(module=1, function=2))
913
    assert Or(3, module=1, function=2) == Or(3, Query(module=1), Query(function=2))
914
915
916
def test_proper_backend():
917
    if os.environ.get('PUREPYTHONHUNTER') or platform.python_implementation() == 'PyPy':
918
        assert 'hunter.tracer.Tracer' in repr(hunter.Tracer)
919
    else:
920
        assert 'hunter._tracer.Tracer' in repr(hunter.Tracer)
921
922
923
@pytest.fixture(scope="session", params=['pure', 'cython'])
924
def tracer_impl(request):
925
    if request.param == 'pure':
926
        return pytest.importorskip('hunter.tracer').Tracer
927
    elif request.param == 'cython':
928
        return pytest.importorskip('hunter._tracer').Tracer
929
930
931
def _bulky_func_that_use_stdlib():
932
    import difflib
933
    a = list(map(str, range(500)))
934
    b = list(map(str, range(0, 500, 2)))
935
    list(difflib.unified_diff(a, b, 'a', 'b'))
936
937
938
def test_perf_filter(tracer_impl, benchmark):
939
    t = tracer_impl()
940
941
    @benchmark
942
    def run():
943
        output = StringIO()
944
        with t.trace(Q(
945
            Q(module="does-not-exist") | Q(module="does not exist".split()),
946
            action=CodePrinter(stream=output)
947
        )):
948
            _bulky_func_that_use_stdlib()
949
        return output
950
951
    assert run.getvalue() == ''
952
953
954
def test_perf_stdlib(tracer_impl, benchmark):
955
    t = tracer_impl()
956
957
    @benchmark
958
    def run():
959
        output = StringIO()
960
        with t.trace(Q(
961
            ~Q(module_contains='pytest'),
962
            ~Q(module_contains='hunter'),
963
            ~Q(filename=''),
964
            stdlib=False,
965
            action=CodePrinter(stream=output)
966
        )):
967
            _bulky_func_that_use_stdlib()
968
        return output
969
970
    assert run.getvalue() == ''
971
972
973
def test_perf_actions(tracer_impl, benchmark):
974
    t = tracer_impl()
975
976
    @benchmark
977
    def run():
978
        output = StringIO()
979
        with t.trace(Q(
980
            ~Q(module_in=['re', 'sre', 'sre_parse']) & ~Q(module_startswith='namedtuple') & Q(kind="call"),
981
            actions=[
982
                CodePrinter(
983
                    stream=output
984
                ),
985
                VarsPrinter(
986
                    'line',
987
                    globals=True,
988
                    stream=output
989
                )
990
            ]
991
        )):
992
            _bulky_func_that_use_stdlib()
993