Completed
Push — master ( 8dc756...b3c98e )
by Ionel Cristian
01:24
created

tests.test_callprinter()   B

Complexity

Conditions 4

Size

Total Lines 23

Duplication

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