Completed
Push — master ( 00eab1...09aa0b )
by Ionel Cristian
56s
created

tests.FakeCallable.__repr__()   A

Complexity

Conditions 1

Size

Total Lines 2

Duplication

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