1 | import os
|
---|
2 | import sys
|
---|
3 | from test.test_support import (run_unittest, TESTFN, rmtree, unlink,
|
---|
4 | captured_stdout)
|
---|
5 | import unittest
|
---|
6 |
|
---|
7 | import trace
|
---|
8 | from trace import CoverageResults, Trace
|
---|
9 |
|
---|
10 | from test.tracedmodules import testmod
|
---|
11 |
|
---|
12 |
|
---|
13 | #------------------------------- Utilities -----------------------------------#
|
---|
14 |
|
---|
15 | def 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 |
|
---|
21 | def 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 |
|
---|
26 | def 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 |
|
---|
36 | def traced_func_linear(x, y):
|
---|
37 | a = x
|
---|
38 | b = y
|
---|
39 | c = a + b
|
---|
40 | return c
|
---|
41 |
|
---|
42 | def traced_func_loop(x, y):
|
---|
43 | c = x
|
---|
44 | for i in range(5):
|
---|
45 | c += y
|
---|
46 | return c
|
---|
47 |
|
---|
48 | def traced_func_importing(x, y):
|
---|
49 | return x + y + testmod.func(1)
|
---|
50 |
|
---|
51 | def traced_func_simple_caller(x):
|
---|
52 | c = traced_func_linear(x, x)
|
---|
53 | return c + x
|
---|
54 |
|
---|
55 | def traced_func_importing_caller(x):
|
---|
56 | k = traced_func_simple_caller(x)
|
---|
57 | k += traced_func_importing(k, x)
|
---|
58 | return k
|
---|
59 |
|
---|
60 | def traced_func_generator(num):
|
---|
61 | c = 5 # executed once
|
---|
62 | for i in range(num):
|
---|
63 | yield i + c
|
---|
64 |
|
---|
65 | def traced_func_calling_generator():
|
---|
66 | k = 0
|
---|
67 | for i in traced_func_generator(10):
|
---|
68 | k += i
|
---|
69 |
|
---|
70 | def traced_doubler(num):
|
---|
71 | return num * 2
|
---|
72 |
|
---|
73 | def traced_caller_list_comprehension():
|
---|
74 | k = 10
|
---|
75 | mylist = [traced_doubler(i) for i in range(k)]
|
---|
76 | return mylist
|
---|
77 |
|
---|
78 |
|
---|
79 | class 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 |
|
---|
102 | class 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 |
|
---|
189 | class 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 |
|
---|
216 | class 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 |
|
---|
255 | class 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
|
---|
280 | class 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.assertTrue("pprint.py" in stdout)
|
---|
297 | self.assertTrue("case.py" in stdout) # from unittest
|
---|
298 | files = os.listdir(TESTFN)
|
---|
299 | self.assertTrue("pprint.cover" in files)
|
---|
300 | self.assertTrue("unittest.case.cover" in 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 |
|
---|
336 | def test_main():
|
---|
337 | run_unittest(__name__)
|
---|
338 |
|
---|
339 |
|
---|
340 | if __name__ == '__main__':
|
---|
341 | test_main()
|
---|