1import os
2import sys
3from test.test_support import (run_unittest, TESTFN, rmtree, unlink,
4                               captured_stdout)
5import unittest
6
7import trace
8from trace import CoverageResults, Trace
9
10from test.tracedmodules import testmod
11
12
13#------------------------------- Utilities -----------------------------------#
14
15def fix_ext_py(filename):
16    """Given a .pyc/.pyo filename converts it to the appropriate .py"""
17    if filename.endswith(('.pyc', '.pyo')):
18        filename = filename[:-1]
19    return filename
20
21def my_file_and_modname():
22    """The .py file and module name of this file (__file__)"""
23    modname = os.path.splitext(os.path.basename(__file__))[0]
24    return fix_ext_py(__file__), modname
25
26def get_firstlineno(func):
27    return func.__code__.co_firstlineno
28
29#-------------------- Target functions for tracing ---------------------------#
30#
31# The relative line numbers of lines in these functions matter for verifying
32# tracing. Please modify the appropriate tests if you change one of the
33# functions. Absolute line numbers don't matter.
34#
35
36def traced_func_linear(x, y):
37    a = x
38    b = y
39    c = a + b
40    return c
41
42def traced_func_loop(x, y):
43    c = x
44    for i in range(5):
45        c += y
46    return c
47
48def traced_func_importing(x, y):
49    return x + y + testmod.func(1)
50
51def traced_func_simple_caller(x):
52    c = traced_func_linear(x, x)
53    return c + x
54
55def traced_func_importing_caller(x):
56    k = traced_func_simple_caller(x)
57    k += traced_func_importing(k, x)
58    return k
59
60def traced_func_generator(num):
61    c = 5       # executed once
62    for i in range(num):
63        yield i + c
64
65def traced_func_calling_generator():
66    k = 0
67    for i in traced_func_generator(10):
68        k += i
69
70def traced_doubler(num):
71    return num * 2
72
73def traced_caller_list_comprehension():
74    k = 10
75    mylist = [traced_doubler(i) for i in range(k)]
76    return mylist
77
78
79class TracedClass(object):
80    def __init__(self, x):
81        self.a = x
82
83    def inst_method_linear(self, y):
84        return self.a + y
85
86    def inst_method_calling(self, x):
87        c = self.inst_method_linear(x)
88        return c + traced_func_linear(x, c)
89
90    @classmethod
91    def class_method_linear(cls, y):
92        return y * 2
93
94    @staticmethod
95    def static_method_linear(y):
96        return y * 2
97
98
99#------------------------------ Test cases -----------------------------------#
100
101
102class TestLineCounts(unittest.TestCase):
103    """White-box testing of line-counting, via runfunc"""
104    def setUp(self):
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            (self.my_py_filename, firstlineno_calling + 2): 11,
167            (self.my_py_filename, firstlineno_calling + 3): 1,
168            (self.my_py_filename, firstlineno_called + 1): 10,
169        }
170        self.assertEqual(self.tracer.results().counts, expected)
171
172
173    def test_linear_methods(self):
174        # XXX todo: later add 'static_method_linear' and 'class_method_linear'
175        # here, once issue1764286 is resolved
176        #
177        for methname in ['inst_method_linear',]:
178            tracer = Trace(count=1, trace=0, countfuncs=0, countcallers=0)
179            traced_obj = TracedClass(25)
180            method = getattr(traced_obj, methname)
181            tracer.runfunc(method, 20)
182
183            firstlineno = get_firstlineno(method)
184            expected = {
185                (self.my_py_filename, firstlineno + 1): 1,
186            }
187            self.assertEqual(tracer.results().counts, expected)
188
189class TestRunExecCounts(unittest.TestCase):
190    """A simple sanity test of line-counting, via runctx (exec)"""
191    def setUp(self):
192        self.my_py_filename = fix_ext_py(__file__)
193
194    def test_exec_counts(self):
195        self.tracer = Trace(count=1, trace=0, countfuncs=0, countcallers=0)
196        code = r'''traced_func_loop(2, 5)'''
197        code = compile(code, __file__, 'exec')
198        self.tracer.runctx(code, globals(), vars())
199
200        firstlineno = get_firstlineno(traced_func_loop)
201        expected = {
202            (self.my_py_filename, firstlineno + 1): 1,
203            (self.my_py_filename, firstlineno + 2): 6,
204            (self.my_py_filename, firstlineno + 3): 5,
205            (self.my_py_filename, firstlineno + 4): 1,
206        }
207
208        # When used through 'run', some other spurious counts are produced, like
209        # the settrace of threading, which we ignore, just making sure that the
210        # counts fo traced_func_loop were right.
211        #
212        for k in expected.keys():
213            self.assertEqual(self.tracer.results().counts[k], expected[k])
214
215
216class TestFuncs(unittest.TestCase):
217    """White-box testing of funcs tracing"""
218    def setUp(self):
219        self.tracer = Trace(count=0, trace=0, countfuncs=1)
220        self.filemod = my_file_and_modname()
221
222    def test_simple_caller(self):
223        self.tracer.runfunc(traced_func_simple_caller, 1)
224
225        expected = {
226            self.filemod + ('traced_func_simple_caller',): 1,
227            self.filemod + ('traced_func_linear',): 1,
228        }
229        self.assertEqual(self.tracer.results().calledfuncs, expected)
230
231    def test_loop_caller_importing(self):
232        self.tracer.runfunc(traced_func_importing_caller, 1)
233
234        expected = {
235            self.filemod + ('traced_func_simple_caller',): 1,
236            self.filemod + ('traced_func_linear',): 1,
237            self.filemod + ('traced_func_importing_caller',): 1,
238            self.filemod + ('traced_func_importing',): 1,
239            (fix_ext_py(testmod.__file__), 'testmod', 'func'): 1,
240        }
241        self.assertEqual(self.tracer.results().calledfuncs, expected)
242
243    def test_inst_method_calling(self):
244        obj = TracedClass(20)
245        self.tracer.runfunc(obj.inst_method_calling, 1)
246
247        expected = {
248            self.filemod + ('TracedClass.inst_method_calling',): 1,
249            self.filemod + ('TracedClass.inst_method_linear',): 1,
250            self.filemod + ('traced_func_linear',): 1,
251        }
252        self.assertEqual(self.tracer.results().calledfuncs, expected)
253
254
255class TestCallers(unittest.TestCase):
256    """White-box testing of callers tracing"""
257    def setUp(self):
258        self.tracer = Trace(count=0, trace=0, countcallers=1)
259        self.filemod = my_file_and_modname()
260
261    def test_loop_caller_importing(self):
262        self.tracer.runfunc(traced_func_importing_caller, 1)
263
264        expected = {
265            ((os.path.splitext(trace.__file__)[0] + '.py', 'trace', 'Trace.runfunc'),
266                (self.filemod + ('traced_func_importing_caller',))): 1,
267            ((self.filemod + ('traced_func_simple_caller',)),
268                (self.filemod + ('traced_func_linear',))): 1,
269            ((self.filemod + ('traced_func_importing_caller',)),
270                (self.filemod + ('traced_func_simple_caller',))): 1,
271            ((self.filemod + ('traced_func_importing_caller',)),
272                (self.filemod + ('traced_func_importing',))): 1,
273            ((self.filemod + ('traced_func_importing',)),
274                (fix_ext_py(testmod.__file__), 'testmod', 'func')): 1,
275        }
276        self.assertEqual(self.tracer.results().callers, expected)
277
278
279# Created separately for issue #3821
280class TestCoverage(unittest.TestCase):
281    def tearDown(self):
282        rmtree(TESTFN)
283        unlink(TESTFN)
284
285    def _coverage(self, tracer,
286                  cmd='from test import test_pprint; test_pprint.test_main()'):
287        tracer.run(cmd)
288        r = tracer.results()
289        r.write_results(show_missing=True, summary=True, coverdir=TESTFN)
290
291    def test_coverage(self):
292        tracer = trace.Trace(trace=0, count=1)
293        with captured_stdout() as stdout:
294            self._coverage(tracer)
295        stdout = stdout.getvalue()
296        self.assertIn("pprint.py", stdout)
297        self.assertIn("case.py", stdout)   # from unittest
298        files = os.listdir(TESTFN)
299        self.assertIn("pprint.cover", files)
300        self.assertIn("unittest.case.cover", files)
301
302    def test_coverage_ignore(self):
303        # Ignore all files, nothing should be traced nor printed
304        libpath = os.path.normpath(os.path.dirname(os.__file__))
305        # sys.prefix does not work when running from a checkout
306        tracer = trace.Trace(ignoredirs=[sys.prefix, sys.exec_prefix, libpath],
307                             trace=0, count=1)
308        with captured_stdout() as stdout:
309            self._coverage(tracer)
310        if os.path.exists(TESTFN):
311            files = os.listdir(TESTFN)
312            self.assertEqual(files, [])
313
314    def test_issue9936(self):
315        tracer = trace.Trace(trace=0, count=1)
316        modname = 'test.tracedmodules.testmod'
317        # Ensure that the module is executed in import
318        if modname in sys.modules:
319            del sys.modules[modname]
320        cmd = ("import test.tracedmodules.testmod as t;"
321               "t.func(0); t.func2();")
322        with captured_stdout() as stdout:
323            self._coverage(tracer, cmd)
324        stdout.seek(0)
325        stdout.readline()
326        coverage = {}
327        for line in stdout:
328            lines, cov, module = line.split()[:3]
329            coverage[module] = (int(lines), int(cov[:-1]))
330        # XXX This is needed to run regrtest.py as a script
331        modname = trace.fullmodname(sys.modules[modname].__file__)
332        self.assertIn(modname, coverage)
333        self.assertEqual(coverage[modname], (5, 100))
334
335
336def test_main():
337    run_unittest(__name__)
338
339
340if __name__ == '__main__':
341    test_main()
342