• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import os
2import sys
3from test.support import TESTFN, rmtree, unlink, captured_stdout
4from test.support.script_helper import assert_python_ok, assert_python_failure
5import unittest
6
7import trace
8from trace import Trace
9
10from test.tracedmodules import testmod
11
12#------------------------------- Utilities -----------------------------------#
13
14def fix_ext_py(filename):
15    """Given a .pyc filename converts it to the appropriate .py"""
16    if filename.endswith('.pyc'):
17        filename = filename[:-1]
18    return filename
19
20def my_file_and_modname():
21    """The .py file and module name of this file (__file__)"""
22    modname = os.path.splitext(os.path.basename(__file__))[0]
23    return fix_ext_py(__file__), modname
24
25def get_firstlineno(func):
26    return func.__code__.co_firstlineno
27
28#-------------------- Target functions for tracing ---------------------------#
29#
30# The relative line numbers of lines in these functions matter for verifying
31# tracing. Please modify the appropriate tests if you change one of the
32# functions. Absolute line numbers don't matter.
33#
34
35def traced_func_linear(x, y):
36    a = x
37    b = y
38    c = a + b
39    return c
40
41def traced_func_loop(x, y):
42    c = x
43    for i in range(5):
44        c += y
45    return c
46
47def traced_func_importing(x, y):
48    return x + y + testmod.func(1)
49
50def traced_func_simple_caller(x):
51    c = traced_func_linear(x, x)
52    return c + x
53
54def traced_func_importing_caller(x):
55    k = traced_func_simple_caller(x)
56    k += traced_func_importing(k, x)
57    return k
58
59def traced_func_generator(num):
60    c = 5       # executed once
61    for i in range(num):
62        yield i + c
63
64def traced_func_calling_generator():
65    k = 0
66    for i in traced_func_generator(10):
67        k += i
68
69def traced_doubler(num):
70    return num * 2
71
72def traced_caller_list_comprehension():
73    k = 10
74    mylist = [traced_doubler(i) for i in range(k)]
75    return mylist
76
77
78class TracedClass(object):
79    def __init__(self, x):
80        self.a = x
81
82    def inst_method_linear(self, y):
83        return self.a + y
84
85    def inst_method_calling(self, x):
86        c = self.inst_method_linear(x)
87        return c + traced_func_linear(x, c)
88
89    @classmethod
90    def class_method_linear(cls, y):
91        return y * 2
92
93    @staticmethod
94    def static_method_linear(y):
95        return y * 2
96
97
98#------------------------------ Test cases -----------------------------------#
99
100
101class TestLineCounts(unittest.TestCase):
102    """White-box testing of line-counting, via runfunc"""
103    def setUp(self):
104        self.addCleanup(sys.settrace, sys.gettrace())
105        self.tracer = Trace(count=1, trace=0, countfuncs=0, countcallers=0)
106        self.my_py_filename = fix_ext_py(__file__)
107
108    def test_traced_func_linear(self):
109        result = self.tracer.runfunc(traced_func_linear, 2, 5)
110        self.assertEqual(result, 7)
111
112        # all lines are executed once
113        expected = {}
114        firstlineno = get_firstlineno(traced_func_linear)
115        for i in range(1, 5):
116            expected[(self.my_py_filename, firstlineno +  i)] = 1
117
118        self.assertEqual(self.tracer.results().counts, expected)
119
120    def test_traced_func_loop(self):
121        self.tracer.runfunc(traced_func_loop, 2, 3)
122
123        firstlineno = get_firstlineno(traced_func_loop)
124        expected = {
125            (self.my_py_filename, firstlineno + 1): 1,
126            (self.my_py_filename, firstlineno + 2): 6,
127            (self.my_py_filename, firstlineno + 3): 5,
128            (self.my_py_filename, firstlineno + 4): 1,
129        }
130        self.assertEqual(self.tracer.results().counts, expected)
131
132    def test_traced_func_importing(self):
133        self.tracer.runfunc(traced_func_importing, 2, 5)
134
135        firstlineno = get_firstlineno(traced_func_importing)
136        expected = {
137            (self.my_py_filename, firstlineno + 1): 1,
138            (fix_ext_py(testmod.__file__), 2): 1,
139            (fix_ext_py(testmod.__file__), 3): 1,
140        }
141
142        self.assertEqual(self.tracer.results().counts, expected)
143
144    def test_trace_func_generator(self):
145        self.tracer.runfunc(traced_func_calling_generator)
146
147        firstlineno_calling = get_firstlineno(traced_func_calling_generator)
148        firstlineno_gen = get_firstlineno(traced_func_generator)
149        expected = {
150            (self.my_py_filename, firstlineno_calling + 1): 1,
151            (self.my_py_filename, firstlineno_calling + 2): 11,
152            (self.my_py_filename, firstlineno_calling + 3): 10,
153            (self.my_py_filename, firstlineno_gen + 1): 1,
154            (self.my_py_filename, firstlineno_gen + 2): 11,
155            (self.my_py_filename, firstlineno_gen + 3): 10,
156        }
157        self.assertEqual(self.tracer.results().counts, expected)
158
159    def test_trace_list_comprehension(self):
160        self.tracer.runfunc(traced_caller_list_comprehension)
161
162        firstlineno_calling = get_firstlineno(traced_caller_list_comprehension)
163        firstlineno_called = get_firstlineno(traced_doubler)
164        expected = {
165            (self.my_py_filename, firstlineno_calling + 1): 1,
166            # List compehentions work differently in 3.x, so the count
167            # below changed compared to 2.x.
168            (self.my_py_filename, firstlineno_calling + 2): 12,
169            (self.my_py_filename, firstlineno_calling + 3): 1,
170            (self.my_py_filename, firstlineno_called + 1): 10,
171        }
172        self.assertEqual(self.tracer.results().counts, expected)
173
174
175    def test_linear_methods(self):
176        # XXX todo: later add 'static_method_linear' and 'class_method_linear'
177        # here, once issue1764286 is resolved
178        #
179        for methname in ['inst_method_linear',]:
180            tracer = Trace(count=1, trace=0, countfuncs=0, countcallers=0)
181            traced_obj = TracedClass(25)
182            method = getattr(traced_obj, methname)
183            tracer.runfunc(method, 20)
184
185            firstlineno = get_firstlineno(method)
186            expected = {
187                (self.my_py_filename, firstlineno + 1): 1,
188            }
189            self.assertEqual(tracer.results().counts, expected)
190
191class TestRunExecCounts(unittest.TestCase):
192    """A simple sanity test of line-counting, via runctx (exec)"""
193    def setUp(self):
194        self.my_py_filename = fix_ext_py(__file__)
195        self.addCleanup(sys.settrace, sys.gettrace())
196
197    def test_exec_counts(self):
198        self.tracer = Trace(count=1, trace=0, countfuncs=0, countcallers=0)
199        code = r'''traced_func_loop(2, 5)'''
200        code = compile(code, __file__, 'exec')
201        self.tracer.runctx(code, globals(), vars())
202
203        firstlineno = get_firstlineno(traced_func_loop)
204        expected = {
205            (self.my_py_filename, firstlineno + 1): 1,
206            (self.my_py_filename, firstlineno + 2): 6,
207            (self.my_py_filename, firstlineno + 3): 5,
208            (self.my_py_filename, firstlineno + 4): 1,
209        }
210
211        # When used through 'run', some other spurious counts are produced, like
212        # the settrace of threading, which we ignore, just making sure that the
213        # counts fo traced_func_loop were right.
214        #
215        for k in expected.keys():
216            self.assertEqual(self.tracer.results().counts[k], expected[k])
217
218
219class TestFuncs(unittest.TestCase):
220    """White-box testing of funcs tracing"""
221    def setUp(self):
222        self.addCleanup(sys.settrace, sys.gettrace())
223        self.tracer = Trace(count=0, trace=0, countfuncs=1)
224        self.filemod = my_file_and_modname()
225        self._saved_tracefunc = sys.gettrace()
226
227    def tearDown(self):
228        if self._saved_tracefunc is not None:
229            sys.settrace(self._saved_tracefunc)
230
231    def test_simple_caller(self):
232        self.tracer.runfunc(traced_func_simple_caller, 1)
233
234        expected = {
235            self.filemod + ('traced_func_simple_caller',): 1,
236            self.filemod + ('traced_func_linear',): 1,
237        }
238        self.assertEqual(self.tracer.results().calledfuncs, expected)
239
240    def test_loop_caller_importing(self):
241        self.tracer.runfunc(traced_func_importing_caller, 1)
242
243        expected = {
244            self.filemod + ('traced_func_simple_caller',): 1,
245            self.filemod + ('traced_func_linear',): 1,
246            self.filemod + ('traced_func_importing_caller',): 1,
247            self.filemod + ('traced_func_importing',): 1,
248            (fix_ext_py(testmod.__file__), 'testmod', 'func'): 1,
249        }
250        self.assertEqual(self.tracer.results().calledfuncs, expected)
251
252    @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
253                     'pre-existing trace function throws off measurements')
254    def test_inst_method_calling(self):
255        obj = TracedClass(20)
256        self.tracer.runfunc(obj.inst_method_calling, 1)
257
258        expected = {
259            self.filemod + ('TracedClass.inst_method_calling',): 1,
260            self.filemod + ('TracedClass.inst_method_linear',): 1,
261            self.filemod + ('traced_func_linear',): 1,
262        }
263        self.assertEqual(self.tracer.results().calledfuncs, expected)
264
265
266class TestCallers(unittest.TestCase):
267    """White-box testing of callers tracing"""
268    def setUp(self):
269        self.addCleanup(sys.settrace, sys.gettrace())
270        self.tracer = Trace(count=0, trace=0, countcallers=1)
271        self.filemod = my_file_and_modname()
272
273    @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
274                     'pre-existing trace function throws off measurements')
275    def test_loop_caller_importing(self):
276        self.tracer.runfunc(traced_func_importing_caller, 1)
277
278        expected = {
279            ((os.path.splitext(trace.__file__)[0] + '.py', 'trace', 'Trace.runfunc'),
280                (self.filemod + ('traced_func_importing_caller',))): 1,
281            ((self.filemod + ('traced_func_simple_caller',)),
282                (self.filemod + ('traced_func_linear',))): 1,
283            ((self.filemod + ('traced_func_importing_caller',)),
284                (self.filemod + ('traced_func_simple_caller',))): 1,
285            ((self.filemod + ('traced_func_importing_caller',)),
286                (self.filemod + ('traced_func_importing',))): 1,
287            ((self.filemod + ('traced_func_importing',)),
288                (fix_ext_py(testmod.__file__), 'testmod', 'func')): 1,
289        }
290        self.assertEqual(self.tracer.results().callers, expected)
291
292
293# Created separately for issue #3821
294class TestCoverage(unittest.TestCase):
295    def setUp(self):
296        self.addCleanup(sys.settrace, sys.gettrace())
297
298    def tearDown(self):
299        rmtree(TESTFN)
300        unlink(TESTFN)
301
302    def _coverage(self, tracer,
303                  cmd='import test.support, test.test_pprint;'
304                      'test.support.run_unittest(test.test_pprint.QueryTestCase)'):
305        tracer.run(cmd)
306        r = tracer.results()
307        r.write_results(show_missing=True, summary=True, coverdir=TESTFN)
308
309    def test_coverage(self):
310        tracer = trace.Trace(trace=0, count=1)
311        with captured_stdout() as stdout:
312            self._coverage(tracer)
313        stdout = stdout.getvalue()
314        self.assertIn("pprint.py", stdout)
315        self.assertIn("case.py", stdout)   # from unittest
316        files = os.listdir(TESTFN)
317        self.assertIn("pprint.cover", files)
318        self.assertIn("unittest.case.cover", files)
319
320    def test_coverage_ignore(self):
321        # Ignore all files, nothing should be traced nor printed
322        libpath = os.path.normpath(os.path.dirname(os.__file__))
323        # sys.prefix does not work when running from a checkout
324        tracer = trace.Trace(ignoredirs=[sys.base_prefix, sys.base_exec_prefix,
325                             libpath], trace=0, count=1)
326        with captured_stdout() as stdout:
327            self._coverage(tracer)
328        if os.path.exists(TESTFN):
329            files = os.listdir(TESTFN)
330            self.assertEqual(files, ['_importlib.cover'])  # Ignore __import__
331
332    def test_issue9936(self):
333        tracer = trace.Trace(trace=0, count=1)
334        modname = 'test.tracedmodules.testmod'
335        # Ensure that the module is executed in import
336        if modname in sys.modules:
337            del sys.modules[modname]
338        cmd = ("import test.tracedmodules.testmod as t;"
339               "t.func(0); t.func2();")
340        with captured_stdout() as stdout:
341            self._coverage(tracer, cmd)
342        stdout.seek(0)
343        stdout.readline()
344        coverage = {}
345        for line in stdout:
346            lines, cov, module = line.split()[:3]
347            coverage[module] = (int(lines), int(cov[:-1]))
348        # XXX This is needed to run regrtest.py as a script
349        modname = trace._fullmodname(sys.modules[modname].__file__)
350        self.assertIn(modname, coverage)
351        self.assertEqual(coverage[modname], (5, 100))
352
353### Tests that don't mess with sys.settrace and can be traced
354### themselves TODO: Skip tests that do mess with sys.settrace when
355### regrtest is invoked with -T option.
356class Test_Ignore(unittest.TestCase):
357    def test_ignored(self):
358        jn = os.path.join
359        ignore = trace._Ignore(['x', 'y.z'], [jn('foo', 'bar')])
360        self.assertTrue(ignore.names('x.py', 'x'))
361        self.assertFalse(ignore.names('xy.py', 'xy'))
362        self.assertFalse(ignore.names('y.py', 'y'))
363        self.assertTrue(ignore.names(jn('foo', 'bar', 'baz.py'), 'baz'))
364        self.assertFalse(ignore.names(jn('bar', 'z.py'), 'z'))
365        # Matched before.
366        self.assertTrue(ignore.names(jn('bar', 'baz.py'), 'baz'))
367
368class TestCommandLine(unittest.TestCase):
369
370    def test_failures(self):
371        _errors = (
372            (b'filename is missing: required with the main options', '-l', '-T'),
373            (b'cannot specify both --listfuncs and (--trace or --count)', '-lc'),
374            (b'argument -R/--no-report: not allowed with argument -r/--report', '-rR'),
375            (b'must specify one of --trace, --count, --report, --listfuncs, or --trackcalls', '-g'),
376            (b'-r/--report requires -f/--file', '-r'),
377            (b'--summary can only be used with --count or --report', '-sT'),
378            (b'unrecognized arguments: -y', '-y'))
379        for message, *args in _errors:
380            *_, stderr = assert_python_failure('-m', 'trace', *args)
381            self.assertIn(message, stderr)
382
383    def test_listfuncs_flag_success(self):
384        with open(TESTFN, 'w') as fd:
385            self.addCleanup(unlink, TESTFN)
386            fd.write("a = 1\n")
387            status, stdout, stderr = assert_python_ok('-m', 'trace', '-l', TESTFN)
388            self.assertIn(b'functions called:', stdout)
389
390if __name__ == '__main__':
391    unittest.main()
392