blob: 55a8bcea3e546ad9657155816b3b59ebc0330363 [file] [log] [blame]
Alexander Belopolsky4d770172010-09-13 18:14:34 +00001import os
2import sys
Serhiy Storchakacbfe07e2015-05-20 19:37:10 +03003from test.support import TESTFN, rmtree, unlink, captured_stdout
Senthil Kumaran436831d2016-01-13 07:46:54 -08004from test.support.script_helper import assert_python_ok, assert_python_failure
Miss Islington (bot)e4eeb6e2018-04-30 21:06:00 -07005import textwrap
Georg Brandl283b1252010-08-02 12:48:46 +00006import unittest
Georg Brandl283b1252010-08-02 12:48:46 +00007
Alexander Belopolsky4d770172010-09-13 18:14:34 +00008import trace
Senthil Kumaran96b531a2016-01-11 07:09:42 -08009from trace import Trace
Alexander Belopolsky4d770172010-09-13 18:14:34 +000010
11from test.tracedmodules import testmod
12
Alexander Belopolsky4d770172010-09-13 18:14:34 +000013#------------------------------- Utilities -----------------------------------#
14
15def fix_ext_py(filename):
Brett Cannonf299abd2015-04-13 14:21:02 -040016 """Given a .pyc filename converts it to the appropriate .py"""
17 if filename.endswith('.pyc'):
Alexander Belopolsky4d770172010-09-13 18:14:34 +000018 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):
Brett Cannon31f59292011-02-21 19:29:56 +0000105 self.addCleanup(sys.settrace, sys.gettrace())
Alexander Belopolsky4d770172010-09-13 18:14:34 +0000106 self.tracer = Trace(count=1, trace=0, countfuncs=0, countcallers=0)
107 self.my_py_filename = fix_ext_py(__file__)
Alexander Belopolsky4d770172010-09-13 18:14:34 +0000108
109 def test_traced_func_linear(self):
110 result = self.tracer.runfunc(traced_func_linear, 2, 5)
111 self.assertEqual(result, 7)
112
113 # all lines are executed once
114 expected = {}
115 firstlineno = get_firstlineno(traced_func_linear)
116 for i in range(1, 5):
117 expected[(self.my_py_filename, firstlineno + i)] = 1
118
119 self.assertEqual(self.tracer.results().counts, expected)
120
121 def test_traced_func_loop(self):
122 self.tracer.runfunc(traced_func_loop, 2, 3)
123
124 firstlineno = get_firstlineno(traced_func_loop)
125 expected = {
126 (self.my_py_filename, firstlineno + 1): 1,
127 (self.my_py_filename, firstlineno + 2): 6,
128 (self.my_py_filename, firstlineno + 3): 5,
129 (self.my_py_filename, firstlineno + 4): 1,
130 }
131 self.assertEqual(self.tracer.results().counts, expected)
132
133 def test_traced_func_importing(self):
134 self.tracer.runfunc(traced_func_importing, 2, 5)
135
136 firstlineno = get_firstlineno(traced_func_importing)
137 expected = {
138 (self.my_py_filename, firstlineno + 1): 1,
139 (fix_ext_py(testmod.__file__), 2): 1,
140 (fix_ext_py(testmod.__file__), 3): 1,
141 }
142
143 self.assertEqual(self.tracer.results().counts, expected)
144
145 def test_trace_func_generator(self):
146 self.tracer.runfunc(traced_func_calling_generator)
147
148 firstlineno_calling = get_firstlineno(traced_func_calling_generator)
149 firstlineno_gen = get_firstlineno(traced_func_generator)
150 expected = {
151 (self.my_py_filename, firstlineno_calling + 1): 1,
152 (self.my_py_filename, firstlineno_calling + 2): 11,
153 (self.my_py_filename, firstlineno_calling + 3): 10,
154 (self.my_py_filename, firstlineno_gen + 1): 1,
155 (self.my_py_filename, firstlineno_gen + 2): 11,
156 (self.my_py_filename, firstlineno_gen + 3): 10,
157 }
158 self.assertEqual(self.tracer.results().counts, expected)
159
160 def test_trace_list_comprehension(self):
161 self.tracer.runfunc(traced_caller_list_comprehension)
162
163 firstlineno_calling = get_firstlineno(traced_caller_list_comprehension)
164 firstlineno_called = get_firstlineno(traced_doubler)
165 expected = {
166 (self.my_py_filename, firstlineno_calling + 1): 1,
167 # List compehentions work differently in 3.x, so the count
168 # below changed compared to 2.x.
169 (self.my_py_filename, firstlineno_calling + 2): 12,
170 (self.my_py_filename, firstlineno_calling + 3): 1,
171 (self.my_py_filename, firstlineno_called + 1): 10,
172 }
173 self.assertEqual(self.tracer.results().counts, expected)
174
175
176 def test_linear_methods(self):
177 # XXX todo: later add 'static_method_linear' and 'class_method_linear'
178 # here, once issue1764286 is resolved
179 #
180 for methname in ['inst_method_linear',]:
181 tracer = Trace(count=1, trace=0, countfuncs=0, countcallers=0)
182 traced_obj = TracedClass(25)
183 method = getattr(traced_obj, methname)
184 tracer.runfunc(method, 20)
185
186 firstlineno = get_firstlineno(method)
187 expected = {
188 (self.my_py_filename, firstlineno + 1): 1,
189 }
190 self.assertEqual(tracer.results().counts, expected)
191
Alexander Belopolsky4d770172010-09-13 18:14:34 +0000192class TestRunExecCounts(unittest.TestCase):
193 """A simple sanity test of line-counting, via runctx (exec)"""
194 def setUp(self):
195 self.my_py_filename = fix_ext_py(__file__)
Brett Cannon31f59292011-02-21 19:29:56 +0000196 self.addCleanup(sys.settrace, sys.gettrace())
Alexander Belopolsky4d770172010-09-13 18:14:34 +0000197
198 def test_exec_counts(self):
199 self.tracer = Trace(count=1, trace=0, countfuncs=0, countcallers=0)
200 code = r'''traced_func_loop(2, 5)'''
201 code = compile(code, __file__, 'exec')
202 self.tracer.runctx(code, globals(), vars())
203
204 firstlineno = get_firstlineno(traced_func_loop)
205 expected = {
206 (self.my_py_filename, firstlineno + 1): 1,
207 (self.my_py_filename, firstlineno + 2): 6,
208 (self.my_py_filename, firstlineno + 3): 5,
209 (self.my_py_filename, firstlineno + 4): 1,
210 }
211
Ezio Melotti13925002011-03-16 11:05:33 +0200212 # When used through 'run', some other spurious counts are produced, like
Alexander Belopolsky4d770172010-09-13 18:14:34 +0000213 # the settrace of threading, which we ignore, just making sure that the
214 # counts fo traced_func_loop were right.
215 #
216 for k in expected.keys():
217 self.assertEqual(self.tracer.results().counts[k], expected[k])
218
219
220class TestFuncs(unittest.TestCase):
221 """White-box testing of funcs tracing"""
222 def setUp(self):
Brett Cannon31f59292011-02-21 19:29:56 +0000223 self.addCleanup(sys.settrace, sys.gettrace())
Alexander Belopolsky4d770172010-09-13 18:14:34 +0000224 self.tracer = Trace(count=0, trace=0, countfuncs=1)
225 self.filemod = my_file_and_modname()
Alexander Belopolskyf026dae2014-06-29 17:44:05 -0400226 self._saved_tracefunc = sys.gettrace()
227
228 def tearDown(self):
229 if self._saved_tracefunc is not None:
230 sys.settrace(self._saved_tracefunc)
Alexander Belopolsky4d770172010-09-13 18:14:34 +0000231
232 def test_simple_caller(self):
233 self.tracer.runfunc(traced_func_simple_caller, 1)
234
235 expected = {
236 self.filemod + ('traced_func_simple_caller',): 1,
237 self.filemod + ('traced_func_linear',): 1,
238 }
239 self.assertEqual(self.tracer.results().calledfuncs, expected)
240
241 def test_loop_caller_importing(self):
242 self.tracer.runfunc(traced_func_importing_caller, 1)
243
244 expected = {
245 self.filemod + ('traced_func_simple_caller',): 1,
246 self.filemod + ('traced_func_linear',): 1,
247 self.filemod + ('traced_func_importing_caller',): 1,
248 self.filemod + ('traced_func_importing',): 1,
249 (fix_ext_py(testmod.__file__), 'testmod', 'func'): 1,
250 }
251 self.assertEqual(self.tracer.results().calledfuncs, expected)
252
Brett Cannon7a540732011-02-22 03:04:06 +0000253 @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
254 'pre-existing trace function throws off measurements')
Alexander Belopolsky4d770172010-09-13 18:14:34 +0000255 def test_inst_method_calling(self):
256 obj = TracedClass(20)
257 self.tracer.runfunc(obj.inst_method_calling, 1)
258
259 expected = {
260 self.filemod + ('TracedClass.inst_method_calling',): 1,
261 self.filemod + ('TracedClass.inst_method_linear',): 1,
262 self.filemod + ('traced_func_linear',): 1,
263 }
264 self.assertEqual(self.tracer.results().calledfuncs, expected)
265
266
267class TestCallers(unittest.TestCase):
268 """White-box testing of callers tracing"""
269 def setUp(self):
Brett Cannon31f59292011-02-21 19:29:56 +0000270 self.addCleanup(sys.settrace, sys.gettrace())
Alexander Belopolsky4d770172010-09-13 18:14:34 +0000271 self.tracer = Trace(count=0, trace=0, countcallers=1)
272 self.filemod = my_file_and_modname()
273
Brett Cannon7a540732011-02-22 03:04:06 +0000274 @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
275 'pre-existing trace function throws off measurements')
Alexander Belopolsky4d770172010-09-13 18:14:34 +0000276 def test_loop_caller_importing(self):
277 self.tracer.runfunc(traced_func_importing_caller, 1)
278
279 expected = {
280 ((os.path.splitext(trace.__file__)[0] + '.py', 'trace', 'Trace.runfunc'),
281 (self.filemod + ('traced_func_importing_caller',))): 1,
282 ((self.filemod + ('traced_func_simple_caller',)),
283 (self.filemod + ('traced_func_linear',))): 1,
284 ((self.filemod + ('traced_func_importing_caller',)),
285 (self.filemod + ('traced_func_simple_caller',))): 1,
286 ((self.filemod + ('traced_func_importing_caller',)),
287 (self.filemod + ('traced_func_importing',))): 1,
288 ((self.filemod + ('traced_func_importing',)),
289 (fix_ext_py(testmod.__file__), 'testmod', 'func')): 1,
290 }
291 self.assertEqual(self.tracer.results().callers, expected)
292
293
294# Created separately for issue #3821
Georg Brandl283b1252010-08-02 12:48:46 +0000295class TestCoverage(unittest.TestCase):
Brett Cannon31f59292011-02-21 19:29:56 +0000296 def setUp(self):
297 self.addCleanup(sys.settrace, sys.gettrace())
298
Georg Brandl283b1252010-08-02 12:48:46 +0000299 def tearDown(self):
300 rmtree(TESTFN)
301 unlink(TESTFN)
302
Alexander Belopolskyff09ce22010-09-24 18:03:12 +0000303 def _coverage(self, tracer,
Serhiy Storchakacbfe07e2015-05-20 19:37:10 +0300304 cmd='import test.support, test.test_pprint;'
305 'test.support.run_unittest(test.test_pprint.QueryTestCase)'):
Alexander Belopolskyff09ce22010-09-24 18:03:12 +0000306 tracer.run(cmd)
Georg Brandl283b1252010-08-02 12:48:46 +0000307 r = tracer.results()
308 r.write_results(show_missing=True, summary=True, coverdir=TESTFN)
309
310 def test_coverage(self):
311 tracer = trace.Trace(trace=0, count=1)
312 with captured_stdout() as stdout:
313 self._coverage(tracer)
314 stdout = stdout.getvalue()
Serhiy Storchaka62e32d62016-11-11 12:05:01 +0200315 self.assertIn("pprint.py", stdout)
316 self.assertIn("case.py", stdout) # from unittest
Georg Brandl283b1252010-08-02 12:48:46 +0000317 files = os.listdir(TESTFN)
Serhiy Storchaka62e32d62016-11-11 12:05:01 +0200318 self.assertIn("pprint.cover", files)
319 self.assertIn("unittest.case.cover", files)
Georg Brandl283b1252010-08-02 12:48:46 +0000320
321 def test_coverage_ignore(self):
322 # Ignore all files, nothing should be traced nor printed
323 libpath = os.path.normpath(os.path.dirname(os.__file__))
324 # sys.prefix does not work when running from a checkout
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100325 tracer = trace.Trace(ignoredirs=[sys.base_prefix, sys.base_exec_prefix,
326 libpath], trace=0, count=1)
Georg Brandl283b1252010-08-02 12:48:46 +0000327 with captured_stdout() as stdout:
328 self._coverage(tracer)
Georg Brandl283b1252010-08-02 12:48:46 +0000329 if os.path.exists(TESTFN):
330 files = os.listdir(TESTFN)
Brett Cannonfd074152012-04-14 14:10:13 -0400331 self.assertEqual(files, ['_importlib.cover']) # Ignore __import__
Georg Brandl283b1252010-08-02 12:48:46 +0000332
Alexander Belopolskyff09ce22010-09-24 18:03:12 +0000333 def test_issue9936(self):
334 tracer = trace.Trace(trace=0, count=1)
335 modname = 'test.tracedmodules.testmod'
336 # Ensure that the module is executed in import
337 if modname in sys.modules:
338 del sys.modules[modname]
339 cmd = ("import test.tracedmodules.testmod as t;"
340 "t.func(0); t.func2();")
341 with captured_stdout() as stdout:
342 self._coverage(tracer, cmd)
343 stdout.seek(0)
344 stdout.readline()
345 coverage = {}
346 for line in stdout:
347 lines, cov, module = line.split()[:3]
348 coverage[module] = (int(lines), int(cov[:-1]))
Alexander Belopolskya847c812010-09-24 22:04:22 +0000349 # XXX This is needed to run regrtest.py as a script
Alexander Belopolsky1f75f5d2010-11-26 18:51:39 +0000350 modname = trace._fullmodname(sys.modules[modname].__file__)
Alexander Belopolskyff09ce22010-09-24 18:03:12 +0000351 self.assertIn(modname, coverage)
352 self.assertEqual(coverage[modname], (5, 100))
353
Alexander Belopolsky6672ea92010-11-08 18:32:40 +0000354### Tests that don't mess with sys.settrace and can be traced
355### themselves TODO: Skip tests that do mess with sys.settrace when
356### regrtest is invoked with -T option.
357class Test_Ignore(unittest.TestCase):
358 def test_ignored(self):
Alexander Belopolsky18c33732010-11-08 23:10:20 +0000359 jn = os.path.join
Alexander Belopolsky1f75f5d2010-11-26 18:51:39 +0000360 ignore = trace._Ignore(['x', 'y.z'], [jn('foo', 'bar')])
Alexander Belopolsky6672ea92010-11-08 18:32:40 +0000361 self.assertTrue(ignore.names('x.py', 'x'))
362 self.assertFalse(ignore.names('xy.py', 'xy'))
363 self.assertFalse(ignore.names('y.py', 'y'))
Alexander Belopolsky18c33732010-11-08 23:10:20 +0000364 self.assertTrue(ignore.names(jn('foo', 'bar', 'baz.py'), 'baz'))
365 self.assertFalse(ignore.names(jn('bar', 'z.py'), 'z'))
Alexander Belopolsky6672ea92010-11-08 18:32:40 +0000366 # Matched before.
Alexander Belopolsky18c33732010-11-08 23:10:20 +0000367 self.assertTrue(ignore.names(jn('bar', 'baz.py'), 'baz'))
Alexander Belopolsky6672ea92010-11-08 18:32:40 +0000368
Miss Islington (bot)e4eeb6e2018-04-30 21:06:00 -0700369# Created for Issue 31908 -- CLI utility not writing cover files
370class TestCoverageCommandLineOutput(unittest.TestCase):
371
372 codefile = 'tmp.py'
373 coverfile = 'tmp.cover'
374
375 def setUp(self):
376 with open(self.codefile, 'w') as f:
377 f.write(textwrap.dedent('''\
378 x = 42
379 if []:
380 print('unreachable')
381 '''))
382
383 def tearDown(self):
384 unlink(self.codefile)
385 unlink(self.coverfile)
386
387 def test_cover_files_written_no_highlight(self):
388 argv = '-m trace --count'.split() + [self.codefile]
389 status, stdout, stderr = assert_python_ok(*argv)
390 self.assertTrue(os.path.exists(self.coverfile))
391 with open(self.coverfile) as f:
392 self.assertEqual(f.read(),
393 " 1: x = 42\n"
394 " 1: if []:\n"
395 " print('unreachable')\n"
396 )
397
398 def test_cover_files_written_with_highlight(self):
399 argv = '-m trace --count --missing'.split() + [self.codefile]
400 status, stdout, stderr = assert_python_ok(*argv)
401 self.assertTrue(os.path.exists(self.coverfile))
402 with open(self.coverfile) as f:
403 self.assertEqual(f.read(), textwrap.dedent('''\
404 1: x = 42
405 1: if []:
406 >>>>>> print('unreachable')
407 '''))
408
Senthil Kumaran436831d2016-01-13 07:46:54 -0800409class TestCommandLine(unittest.TestCase):
410
411 def test_failures(self):
412 _errors = (
413 (b'filename is missing: required with the main options', '-l', '-T'),
414 (b'cannot specify both --listfuncs and (--trace or --count)', '-lc'),
415 (b'argument -R/--no-report: not allowed with argument -r/--report', '-rR'),
416 (b'must specify one of --trace, --count, --report, --listfuncs, or --trackcalls', '-g'),
417 (b'-r/--report requires -f/--file', '-r'),
418 (b'--summary can only be used with --count or --report', '-sT'),
419 (b'unrecognized arguments: -y', '-y'))
420 for message, *args in _errors:
421 *_, stderr = assert_python_failure('-m', 'trace', *args)
422 self.assertIn(message, stderr)
423
424 def test_listfuncs_flag_success(self):
425 with open(TESTFN, 'w') as fd:
426 self.addCleanup(unlink, TESTFN)
427 fd.write("a = 1\n")
428 status, stdout, stderr = assert_python_ok('-m', 'trace', '-l', TESTFN)
429 self.assertIn(b'functions called:', stdout)
Georg Brandl283b1252010-08-02 12:48:46 +0000430
Miss Islington (bot)afb5e552018-02-16 22:53:24 -0800431 def test_sys_argv_list(self):
432 with open(TESTFN, 'w') as fd:
433 self.addCleanup(unlink, TESTFN)
434 fd.write("import sys\n")
435 fd.write("print(type(sys.argv))\n")
436
437 status, direct_stdout, stderr = assert_python_ok(TESTFN)
438 status, trace_stdout, stderr = assert_python_ok('-m', 'trace', '-l', TESTFN)
439 self.assertIn(direct_stdout.strip(), trace_stdout)
440
Alexander Belopolsky4d770172010-09-13 18:14:34 +0000441if __name__ == '__main__':
Serhiy Storchakacbfe07e2015-05-20 19:37:10 +0300442 unittest.main()