blob: 66fff1cef4708c8e624822c82179e73055e38b9b [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
Serhiy Storchakaa37f3562019-04-01 10:59:24 +030073def traced_capturer(*args, **kwargs):
74 return args, kwargs
75
Alexander Belopolsky4d770172010-09-13 18:14:34 +000076def traced_caller_list_comprehension():
77 k = 10
78 mylist = [traced_doubler(i) for i in range(k)]
79 return mylist
80
81
82class TracedClass(object):
83 def __init__(self, x):
84 self.a = x
85
86 def inst_method_linear(self, y):
87 return self.a + y
88
89 def inst_method_calling(self, x):
90 c = self.inst_method_linear(x)
91 return c + traced_func_linear(x, c)
92
93 @classmethod
94 def class_method_linear(cls, y):
95 return y * 2
96
97 @staticmethod
98 def static_method_linear(y):
99 return y * 2
100
101
102#------------------------------ Test cases -----------------------------------#
103
104
105class TestLineCounts(unittest.TestCase):
106 """White-box testing of line-counting, via runfunc"""
107 def setUp(self):
Brett Cannon31f59292011-02-21 19:29:56 +0000108 self.addCleanup(sys.settrace, sys.gettrace())
Alexander Belopolsky4d770172010-09-13 18:14:34 +0000109 self.tracer = Trace(count=1, trace=0, countfuncs=0, countcallers=0)
110 self.my_py_filename = fix_ext_py(__file__)
Alexander Belopolsky4d770172010-09-13 18:14:34 +0000111
112 def test_traced_func_linear(self):
113 result = self.tracer.runfunc(traced_func_linear, 2, 5)
114 self.assertEqual(result, 7)
115
116 # all lines are executed once
117 expected = {}
118 firstlineno = get_firstlineno(traced_func_linear)
119 for i in range(1, 5):
120 expected[(self.my_py_filename, firstlineno + i)] = 1
121
122 self.assertEqual(self.tracer.results().counts, expected)
123
124 def test_traced_func_loop(self):
125 self.tracer.runfunc(traced_func_loop, 2, 3)
126
127 firstlineno = get_firstlineno(traced_func_loop)
128 expected = {
129 (self.my_py_filename, firstlineno + 1): 1,
130 (self.my_py_filename, firstlineno + 2): 6,
131 (self.my_py_filename, firstlineno + 3): 5,
132 (self.my_py_filename, firstlineno + 4): 1,
133 }
134 self.assertEqual(self.tracer.results().counts, expected)
135
136 def test_traced_func_importing(self):
137 self.tracer.runfunc(traced_func_importing, 2, 5)
138
139 firstlineno = get_firstlineno(traced_func_importing)
140 expected = {
141 (self.my_py_filename, firstlineno + 1): 1,
142 (fix_ext_py(testmod.__file__), 2): 1,
143 (fix_ext_py(testmod.__file__), 3): 1,
144 }
145
146 self.assertEqual(self.tracer.results().counts, expected)
147
148 def test_trace_func_generator(self):
149 self.tracer.runfunc(traced_func_calling_generator)
150
151 firstlineno_calling = get_firstlineno(traced_func_calling_generator)
152 firstlineno_gen = get_firstlineno(traced_func_generator)
153 expected = {
154 (self.my_py_filename, firstlineno_calling + 1): 1,
155 (self.my_py_filename, firstlineno_calling + 2): 11,
156 (self.my_py_filename, firstlineno_calling + 3): 10,
157 (self.my_py_filename, firstlineno_gen + 1): 1,
158 (self.my_py_filename, firstlineno_gen + 2): 11,
159 (self.my_py_filename, firstlineno_gen + 3): 10,
160 }
161 self.assertEqual(self.tracer.results().counts, expected)
162
163 def test_trace_list_comprehension(self):
164 self.tracer.runfunc(traced_caller_list_comprehension)
165
166 firstlineno_calling = get_firstlineno(traced_caller_list_comprehension)
167 firstlineno_called = get_firstlineno(traced_doubler)
168 expected = {
169 (self.my_py_filename, firstlineno_calling + 1): 1,
170 # List compehentions work differently in 3.x, so the count
171 # below changed compared to 2.x.
172 (self.my_py_filename, firstlineno_calling + 2): 12,
173 (self.my_py_filename, firstlineno_calling + 3): 1,
174 (self.my_py_filename, firstlineno_called + 1): 10,
175 }
176 self.assertEqual(self.tracer.results().counts, expected)
177
178
179 def test_linear_methods(self):
180 # XXX todo: later add 'static_method_linear' and 'class_method_linear'
181 # here, once issue1764286 is resolved
182 #
183 for methname in ['inst_method_linear',]:
184 tracer = Trace(count=1, trace=0, countfuncs=0, countcallers=0)
185 traced_obj = TracedClass(25)
186 method = getattr(traced_obj, methname)
187 tracer.runfunc(method, 20)
188
189 firstlineno = get_firstlineno(method)
190 expected = {
191 (self.my_py_filename, firstlineno + 1): 1,
192 }
193 self.assertEqual(tracer.results().counts, expected)
194
Alexander Belopolsky4d770172010-09-13 18:14:34 +0000195class TestRunExecCounts(unittest.TestCase):
196 """A simple sanity test of line-counting, via runctx (exec)"""
197 def setUp(self):
198 self.my_py_filename = fix_ext_py(__file__)
Brett Cannon31f59292011-02-21 19:29:56 +0000199 self.addCleanup(sys.settrace, sys.gettrace())
Alexander Belopolsky4d770172010-09-13 18:14:34 +0000200
201 def test_exec_counts(self):
202 self.tracer = Trace(count=1, trace=0, countfuncs=0, countcallers=0)
203 code = r'''traced_func_loop(2, 5)'''
204 code = compile(code, __file__, 'exec')
205 self.tracer.runctx(code, globals(), vars())
206
207 firstlineno = get_firstlineno(traced_func_loop)
208 expected = {
209 (self.my_py_filename, firstlineno + 1): 1,
210 (self.my_py_filename, firstlineno + 2): 6,
211 (self.my_py_filename, firstlineno + 3): 5,
212 (self.my_py_filename, firstlineno + 4): 1,
213 }
214
Ezio Melotti13925002011-03-16 11:05:33 +0200215 # When used through 'run', some other spurious counts are produced, like
Alexander Belopolsky4d770172010-09-13 18:14:34 +0000216 # the settrace of threading, which we ignore, just making sure that the
217 # counts fo traced_func_loop were right.
218 #
219 for k in expected.keys():
220 self.assertEqual(self.tracer.results().counts[k], expected[k])
221
222
223class TestFuncs(unittest.TestCase):
224 """White-box testing of funcs tracing"""
225 def setUp(self):
Brett Cannon31f59292011-02-21 19:29:56 +0000226 self.addCleanup(sys.settrace, sys.gettrace())
Alexander Belopolsky4d770172010-09-13 18:14:34 +0000227 self.tracer = Trace(count=0, trace=0, countfuncs=1)
228 self.filemod = my_file_and_modname()
Alexander Belopolskyf026dae2014-06-29 17:44:05 -0400229 self._saved_tracefunc = sys.gettrace()
230
231 def tearDown(self):
232 if self._saved_tracefunc is not None:
233 sys.settrace(self._saved_tracefunc)
Alexander Belopolsky4d770172010-09-13 18:14:34 +0000234
235 def test_simple_caller(self):
236 self.tracer.runfunc(traced_func_simple_caller, 1)
237
238 expected = {
239 self.filemod + ('traced_func_simple_caller',): 1,
240 self.filemod + ('traced_func_linear',): 1,
241 }
242 self.assertEqual(self.tracer.results().calledfuncs, expected)
243
Serhiy Storchakaa37f3562019-04-01 10:59:24 +0300244 def test_arg_errors(self):
245 res = self.tracer.runfunc(traced_capturer, 1, 2, self=3, func=4)
246 self.assertEqual(res, ((1, 2), {'self': 3, 'func': 4}))
247 res = self.tracer.runfunc(func=traced_capturer, arg=1)
248 self.assertEqual(res, ((), {'arg': 1}))
249 with self.assertRaises(TypeError):
250 self.tracer.runfunc()
251
Alexander Belopolsky4d770172010-09-13 18:14:34 +0000252 def test_loop_caller_importing(self):
253 self.tracer.runfunc(traced_func_importing_caller, 1)
254
255 expected = {
256 self.filemod + ('traced_func_simple_caller',): 1,
257 self.filemod + ('traced_func_linear',): 1,
258 self.filemod + ('traced_func_importing_caller',): 1,
259 self.filemod + ('traced_func_importing',): 1,
260 (fix_ext_py(testmod.__file__), 'testmod', 'func'): 1,
261 }
262 self.assertEqual(self.tracer.results().calledfuncs, expected)
263
Brett Cannon7a540732011-02-22 03:04:06 +0000264 @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
265 'pre-existing trace function throws off measurements')
Alexander Belopolsky4d770172010-09-13 18:14:34 +0000266 def test_inst_method_calling(self):
267 obj = TracedClass(20)
268 self.tracer.runfunc(obj.inst_method_calling, 1)
269
270 expected = {
271 self.filemod + ('TracedClass.inst_method_calling',): 1,
272 self.filemod + ('TracedClass.inst_method_linear',): 1,
273 self.filemod + ('traced_func_linear',): 1,
274 }
275 self.assertEqual(self.tracer.results().calledfuncs, expected)
276
277
278class TestCallers(unittest.TestCase):
279 """White-box testing of callers tracing"""
280 def setUp(self):
Brett Cannon31f59292011-02-21 19:29:56 +0000281 self.addCleanup(sys.settrace, sys.gettrace())
Alexander Belopolsky4d770172010-09-13 18:14:34 +0000282 self.tracer = Trace(count=0, trace=0, countcallers=1)
283 self.filemod = my_file_and_modname()
284
Brett Cannon7a540732011-02-22 03:04:06 +0000285 @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
286 'pre-existing trace function throws off measurements')
Alexander Belopolsky4d770172010-09-13 18:14:34 +0000287 def test_loop_caller_importing(self):
288 self.tracer.runfunc(traced_func_importing_caller, 1)
289
290 expected = {
291 ((os.path.splitext(trace.__file__)[0] + '.py', 'trace', 'Trace.runfunc'),
292 (self.filemod + ('traced_func_importing_caller',))): 1,
293 ((self.filemod + ('traced_func_simple_caller',)),
294 (self.filemod + ('traced_func_linear',))): 1,
295 ((self.filemod + ('traced_func_importing_caller',)),
296 (self.filemod + ('traced_func_simple_caller',))): 1,
297 ((self.filemod + ('traced_func_importing_caller',)),
298 (self.filemod + ('traced_func_importing',))): 1,
299 ((self.filemod + ('traced_func_importing',)),
300 (fix_ext_py(testmod.__file__), 'testmod', 'func')): 1,
301 }
302 self.assertEqual(self.tracer.results().callers, expected)
303
304
305# Created separately for issue #3821
Georg Brandl283b1252010-08-02 12:48:46 +0000306class TestCoverage(unittest.TestCase):
Brett Cannon31f59292011-02-21 19:29:56 +0000307 def setUp(self):
308 self.addCleanup(sys.settrace, sys.gettrace())
309
Georg Brandl283b1252010-08-02 12:48:46 +0000310 def tearDown(self):
311 rmtree(TESTFN)
312 unlink(TESTFN)
313
Alexander Belopolskyff09ce22010-09-24 18:03:12 +0000314 def _coverage(self, tracer,
Serhiy Storchakacbfe07e2015-05-20 19:37:10 +0300315 cmd='import test.support, test.test_pprint;'
316 'test.support.run_unittest(test.test_pprint.QueryTestCase)'):
Alexander Belopolskyff09ce22010-09-24 18:03:12 +0000317 tracer.run(cmd)
Georg Brandl283b1252010-08-02 12:48:46 +0000318 r = tracer.results()
319 r.write_results(show_missing=True, summary=True, coverdir=TESTFN)
320
321 def test_coverage(self):
322 tracer = trace.Trace(trace=0, count=1)
323 with captured_stdout() as stdout:
324 self._coverage(tracer)
325 stdout = stdout.getvalue()
Serhiy Storchaka62e32d62016-11-11 12:05:01 +0200326 self.assertIn("pprint.py", stdout)
327 self.assertIn("case.py", stdout) # from unittest
Georg Brandl283b1252010-08-02 12:48:46 +0000328 files = os.listdir(TESTFN)
Serhiy Storchaka62e32d62016-11-11 12:05:01 +0200329 self.assertIn("pprint.cover", files)
330 self.assertIn("unittest.case.cover", files)
Georg Brandl283b1252010-08-02 12:48:46 +0000331
332 def test_coverage_ignore(self):
333 # Ignore all files, nothing should be traced nor printed
334 libpath = os.path.normpath(os.path.dirname(os.__file__))
335 # sys.prefix does not work when running from a checkout
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100336 tracer = trace.Trace(ignoredirs=[sys.base_prefix, sys.base_exec_prefix,
337 libpath], trace=0, count=1)
Georg Brandl283b1252010-08-02 12:48:46 +0000338 with captured_stdout() as stdout:
339 self._coverage(tracer)
Georg Brandl283b1252010-08-02 12:48:46 +0000340 if os.path.exists(TESTFN):
341 files = os.listdir(TESTFN)
Brett Cannonfd074152012-04-14 14:10:13 -0400342 self.assertEqual(files, ['_importlib.cover']) # Ignore __import__
Georg Brandl283b1252010-08-02 12:48:46 +0000343
Alexander Belopolskyff09ce22010-09-24 18:03:12 +0000344 def test_issue9936(self):
345 tracer = trace.Trace(trace=0, count=1)
346 modname = 'test.tracedmodules.testmod'
347 # Ensure that the module is executed in import
348 if modname in sys.modules:
349 del sys.modules[modname]
350 cmd = ("import test.tracedmodules.testmod as t;"
351 "t.func(0); t.func2();")
352 with captured_stdout() as stdout:
353 self._coverage(tracer, cmd)
354 stdout.seek(0)
355 stdout.readline()
356 coverage = {}
357 for line in stdout:
358 lines, cov, module = line.split()[:3]
359 coverage[module] = (int(lines), int(cov[:-1]))
Alexander Belopolskya847c812010-09-24 22:04:22 +0000360 # XXX This is needed to run regrtest.py as a script
Alexander Belopolsky1f75f5d2010-11-26 18:51:39 +0000361 modname = trace._fullmodname(sys.modules[modname].__file__)
Alexander Belopolskyff09ce22010-09-24 18:03:12 +0000362 self.assertIn(modname, coverage)
363 self.assertEqual(coverage[modname], (5, 100))
364
Alexander Belopolsky6672ea92010-11-08 18:32:40 +0000365### Tests that don't mess with sys.settrace and can be traced
366### themselves TODO: Skip tests that do mess with sys.settrace when
367### regrtest is invoked with -T option.
368class Test_Ignore(unittest.TestCase):
369 def test_ignored(self):
Alexander Belopolsky18c33732010-11-08 23:10:20 +0000370 jn = os.path.join
Alexander Belopolsky1f75f5d2010-11-26 18:51:39 +0000371 ignore = trace._Ignore(['x', 'y.z'], [jn('foo', 'bar')])
Alexander Belopolsky6672ea92010-11-08 18:32:40 +0000372 self.assertTrue(ignore.names('x.py', 'x'))
373 self.assertFalse(ignore.names('xy.py', 'xy'))
374 self.assertFalse(ignore.names('y.py', 'y'))
Alexander Belopolsky18c33732010-11-08 23:10:20 +0000375 self.assertTrue(ignore.names(jn('foo', 'bar', 'baz.py'), 'baz'))
376 self.assertFalse(ignore.names(jn('bar', 'z.py'), 'z'))
Alexander Belopolsky6672ea92010-11-08 18:32:40 +0000377 # Matched before.
Alexander Belopolsky18c33732010-11-08 23:10:20 +0000378 self.assertTrue(ignore.names(jn('bar', 'baz.py'), 'baz'))
Alexander Belopolsky6672ea92010-11-08 18:32:40 +0000379
Miss Islington (bot)e4eeb6e2018-04-30 21:06:00 -0700380# Created for Issue 31908 -- CLI utility not writing cover files
381class TestCoverageCommandLineOutput(unittest.TestCase):
382
383 codefile = 'tmp.py'
384 coverfile = 'tmp.cover'
385
386 def setUp(self):
387 with open(self.codefile, 'w') as f:
388 f.write(textwrap.dedent('''\
389 x = 42
390 if []:
391 print('unreachable')
392 '''))
393
394 def tearDown(self):
395 unlink(self.codefile)
396 unlink(self.coverfile)
397
398 def test_cover_files_written_no_highlight(self):
Miss Islington (bot)e3f20822018-08-28 04:41:43 -0400399 # Test also that the cover file for the trace module is not created
400 # (issue #34171).
401 tracedir = os.path.dirname(os.path.abspath(trace.__file__))
402 tracecoverpath = os.path.join(tracedir, 'trace.cover')
403 unlink(tracecoverpath)
404
Miss Islington (bot)e4eeb6e2018-04-30 21:06:00 -0700405 argv = '-m trace --count'.split() + [self.codefile]
406 status, stdout, stderr = assert_python_ok(*argv)
Miss Islington (bot)80e9fed2018-08-25 03:47:22 -0400407 self.assertEqual(stderr, b'')
Miss Islington (bot)80e9fed2018-08-25 03:47:22 -0400408 self.assertFalse(os.path.exists(tracecoverpath))
Miss Islington (bot)e4eeb6e2018-04-30 21:06:00 -0700409 self.assertTrue(os.path.exists(self.coverfile))
410 with open(self.coverfile) as f:
411 self.assertEqual(f.read(),
412 " 1: x = 42\n"
413 " 1: if []:\n"
414 " print('unreachable')\n"
415 )
416
417 def test_cover_files_written_with_highlight(self):
418 argv = '-m trace --count --missing'.split() + [self.codefile]
419 status, stdout, stderr = assert_python_ok(*argv)
420 self.assertTrue(os.path.exists(self.coverfile))
421 with open(self.coverfile) as f:
422 self.assertEqual(f.read(), textwrap.dedent('''\
423 1: x = 42
424 1: if []:
425 >>>>>> print('unreachable')
426 '''))
427
Senthil Kumaran436831d2016-01-13 07:46:54 -0800428class TestCommandLine(unittest.TestCase):
429
430 def test_failures(self):
431 _errors = (
432 (b'filename is missing: required with the main options', '-l', '-T'),
433 (b'cannot specify both --listfuncs and (--trace or --count)', '-lc'),
434 (b'argument -R/--no-report: not allowed with argument -r/--report', '-rR'),
435 (b'must specify one of --trace, --count, --report, --listfuncs, or --trackcalls', '-g'),
436 (b'-r/--report requires -f/--file', '-r'),
437 (b'--summary can only be used with --count or --report', '-sT'),
438 (b'unrecognized arguments: -y', '-y'))
439 for message, *args in _errors:
440 *_, stderr = assert_python_failure('-m', 'trace', *args)
441 self.assertIn(message, stderr)
442
443 def test_listfuncs_flag_success(self):
444 with open(TESTFN, 'w') as fd:
445 self.addCleanup(unlink, TESTFN)
446 fd.write("a = 1\n")
447 status, stdout, stderr = assert_python_ok('-m', 'trace', '-l', TESTFN)
448 self.assertIn(b'functions called:', stdout)
Georg Brandl283b1252010-08-02 12:48:46 +0000449
Miss Islington (bot)afb5e552018-02-16 22:53:24 -0800450 def test_sys_argv_list(self):
451 with open(TESTFN, 'w') as fd:
452 self.addCleanup(unlink, TESTFN)
453 fd.write("import sys\n")
454 fd.write("print(type(sys.argv))\n")
455
456 status, direct_stdout, stderr = assert_python_ok(TESTFN)
457 status, trace_stdout, stderr = assert_python_ok('-m', 'trace', '-l', TESTFN)
458 self.assertIn(direct_stdout.strip(), trace_stdout)
459
Miss Islington (bot)8fc21c82018-08-10 23:28:34 -0700460 def test_count_and_summary(self):
461 filename = f'{TESTFN}.py'
462 coverfilename = f'{TESTFN}.cover'
463 with open(filename, 'w') as fd:
464 self.addCleanup(unlink, filename)
465 self.addCleanup(unlink, coverfilename)
466 fd.write(textwrap.dedent("""\
467 x = 1
468 y = 2
469
470 def f():
471 return x + y
472
473 for i in range(10):
474 f()
475 """))
476 status, stdout, _ = assert_python_ok('-m', 'trace', '-cs', filename)
477 stdout = stdout.decode()
478 self.assertEqual(status, 0)
479 self.assertIn('lines cov% module (path)', stdout)
480 self.assertIn(f'6 100% {TESTFN} ({filename})', stdout)
481
Alexander Belopolsky4d770172010-09-13 18:14:34 +0000482if __name__ == '__main__':
Serhiy Storchakacbfe07e2015-05-20 19:37:10 +0300483 unittest.main()