1import os 2from pickle import dump 3import sys 4from test.support import captured_stdout 5from test.support.os_helper import (TESTFN, rmtree, unlink) 6from test.support.script_helper import assert_python_ok, assert_python_failure 7import textwrap 8import unittest 9 10import trace 11from trace import Trace 12 13from test.tracedmodules import testmod 14 15## 16## See also test_sys_settrace.py, which contains tests that cover 17## tracing of many more code blocks. 18## 19 20#------------------------------- Utilities -----------------------------------# 21 22def fix_ext_py(filename): 23 """Given a .pyc filename converts it to the appropriate .py""" 24 if filename.endswith('.pyc'): 25 filename = filename[:-1] 26 return filename 27 28def my_file_and_modname(): 29 """The .py file and module name of this file (__file__)""" 30 modname = os.path.splitext(os.path.basename(__file__))[0] 31 return fix_ext_py(__file__), modname 32 33def get_firstlineno(func): 34 return func.__code__.co_firstlineno 35 36#-------------------- Target functions for tracing ---------------------------# 37# 38# The relative line numbers of lines in these functions matter for verifying 39# tracing. Please modify the appropriate tests if you change one of the 40# functions. Absolute line numbers don't matter. 41# 42 43def traced_func_linear(x, y): 44 a = x 45 b = y 46 c = a + b 47 return c 48 49def traced_func_loop(x, y): 50 c = x 51 for i in range(5): 52 c += y 53 return c 54 55def traced_func_importing(x, y): 56 return x + y + testmod.func(1) 57 58def traced_func_simple_caller(x): 59 c = traced_func_linear(x, x) 60 return c + x 61 62def traced_func_importing_caller(x): 63 k = traced_func_simple_caller(x) 64 k += traced_func_importing(k, x) 65 return k 66 67def traced_func_generator(num): 68 c = 5 # executed once 69 for i in range(num): 70 yield i + c 71 72def traced_func_calling_generator(): 73 k = 0 74 for i in traced_func_generator(10): 75 k += i 76 77def traced_doubler(num): 78 return num * 2 79 80def traced_capturer(*args, **kwargs): 81 return args, kwargs 82 83def traced_caller_list_comprehension(): 84 k = 10 85 mylist = [traced_doubler(i) for i in range(k)] 86 return mylist 87 88def traced_decorated_function(): 89 def decorator1(f): 90 return f 91 def decorator_fabric(): 92 def decorator2(f): 93 return f 94 return decorator2 95 @decorator1 96 @decorator_fabric() 97 def func(): 98 pass 99 func() 100 101 102class TracedClass(object): 103 def __init__(self, x): 104 self.a = x 105 106 def inst_method_linear(self, y): 107 return self.a + y 108 109 def inst_method_calling(self, x): 110 c = self.inst_method_linear(x) 111 return c + traced_func_linear(x, c) 112 113 @classmethod 114 def class_method_linear(cls, y): 115 return y * 2 116 117 @staticmethod 118 def static_method_linear(y): 119 return y * 2 120 121 122#------------------------------ Test cases -----------------------------------# 123 124 125class TestLineCounts(unittest.TestCase): 126 """White-box testing of line-counting, via runfunc""" 127 def setUp(self): 128 self.addCleanup(sys.settrace, sys.gettrace()) 129 self.tracer = Trace(count=1, trace=0, countfuncs=0, countcallers=0) 130 self.my_py_filename = fix_ext_py(__file__) 131 132 def test_traced_func_linear(self): 133 result = self.tracer.runfunc(traced_func_linear, 2, 5) 134 self.assertEqual(result, 7) 135 136 # all lines are executed once 137 expected = {} 138 firstlineno = get_firstlineno(traced_func_linear) 139 for i in range(1, 5): 140 expected[(self.my_py_filename, firstlineno + i)] = 1 141 142 self.assertEqual(self.tracer.results().counts, expected) 143 144 def test_traced_func_loop(self): 145 self.tracer.runfunc(traced_func_loop, 2, 3) 146 147 firstlineno = get_firstlineno(traced_func_loop) 148 expected = { 149 (self.my_py_filename, firstlineno + 1): 1, 150 (self.my_py_filename, firstlineno + 2): 6, 151 (self.my_py_filename, firstlineno + 3): 5, 152 (self.my_py_filename, firstlineno + 4): 1, 153 } 154 self.assertEqual(self.tracer.results().counts, expected) 155 156 def test_traced_func_importing(self): 157 self.tracer.runfunc(traced_func_importing, 2, 5) 158 159 firstlineno = get_firstlineno(traced_func_importing) 160 expected = { 161 (self.my_py_filename, firstlineno + 1): 1, 162 (fix_ext_py(testmod.__file__), 2): 1, 163 (fix_ext_py(testmod.__file__), 3): 1, 164 } 165 166 self.assertEqual(self.tracer.results().counts, expected) 167 168 def test_trace_func_generator(self): 169 self.tracer.runfunc(traced_func_calling_generator) 170 171 firstlineno_calling = get_firstlineno(traced_func_calling_generator) 172 firstlineno_gen = get_firstlineno(traced_func_generator) 173 expected = { 174 (self.my_py_filename, firstlineno_calling + 1): 1, 175 (self.my_py_filename, firstlineno_calling + 2): 11, 176 (self.my_py_filename, firstlineno_calling + 3): 10, 177 (self.my_py_filename, firstlineno_gen + 1): 1, 178 (self.my_py_filename, firstlineno_gen + 2): 11, 179 (self.my_py_filename, firstlineno_gen + 3): 10, 180 } 181 self.assertEqual(self.tracer.results().counts, expected) 182 183 def test_trace_list_comprehension(self): 184 self.tracer.runfunc(traced_caller_list_comprehension) 185 186 firstlineno_calling = get_firstlineno(traced_caller_list_comprehension) 187 firstlineno_called = get_firstlineno(traced_doubler) 188 expected = { 189 (self.my_py_filename, firstlineno_calling + 1): 1, 190 # List comprehensions work differently in 3.x, so the count 191 # below changed compared to 2.x. 192 (self.my_py_filename, firstlineno_calling + 2): 12, 193 (self.my_py_filename, firstlineno_calling + 3): 1, 194 (self.my_py_filename, firstlineno_called + 1): 10, 195 } 196 self.assertEqual(self.tracer.results().counts, expected) 197 198 def test_traced_decorated_function(self): 199 self.tracer.runfunc(traced_decorated_function) 200 201 firstlineno = get_firstlineno(traced_decorated_function) 202 expected = { 203 (self.my_py_filename, firstlineno + 1): 1, 204 (self.my_py_filename, firstlineno + 2): 1, 205 (self.my_py_filename, firstlineno + 3): 1, 206 (self.my_py_filename, firstlineno + 4): 1, 207 (self.my_py_filename, firstlineno + 5): 1, 208 (self.my_py_filename, firstlineno + 6): 1, 209 (self.my_py_filename, firstlineno + 7): 2, 210 (self.my_py_filename, firstlineno + 8): 2, 211 (self.my_py_filename, firstlineno + 9): 2, 212 (self.my_py_filename, firstlineno + 10): 1, 213 (self.my_py_filename, firstlineno + 11): 1, 214 } 215 self.assertEqual(self.tracer.results().counts, expected) 216 217 def test_linear_methods(self): 218 # XXX todo: later add 'static_method_linear' and 'class_method_linear' 219 # here, once issue1764286 is resolved 220 # 221 for methname in ['inst_method_linear',]: 222 tracer = Trace(count=1, trace=0, countfuncs=0, countcallers=0) 223 traced_obj = TracedClass(25) 224 method = getattr(traced_obj, methname) 225 tracer.runfunc(method, 20) 226 227 firstlineno = get_firstlineno(method) 228 expected = { 229 (self.my_py_filename, firstlineno + 1): 1, 230 } 231 self.assertEqual(tracer.results().counts, expected) 232 233 234class TestRunExecCounts(unittest.TestCase): 235 """A simple sanity test of line-counting, via runctx (exec)""" 236 def setUp(self): 237 self.my_py_filename = fix_ext_py(__file__) 238 self.addCleanup(sys.settrace, sys.gettrace()) 239 240 def test_exec_counts(self): 241 self.tracer = Trace(count=1, trace=0, countfuncs=0, countcallers=0) 242 code = r'''traced_func_loop(2, 5)''' 243 code = compile(code, __file__, 'exec') 244 self.tracer.runctx(code, globals(), vars()) 245 246 firstlineno = get_firstlineno(traced_func_loop) 247 expected = { 248 (self.my_py_filename, firstlineno + 1): 1, 249 (self.my_py_filename, firstlineno + 2): 6, 250 (self.my_py_filename, firstlineno + 3): 5, 251 (self.my_py_filename, firstlineno + 4): 1, 252 } 253 254 # When used through 'run', some other spurious counts are produced, like 255 # the settrace of threading, which we ignore, just making sure that the 256 # counts fo traced_func_loop were right. 257 # 258 for k in expected.keys(): 259 self.assertEqual(self.tracer.results().counts[k], expected[k]) 260 261 262class TestFuncs(unittest.TestCase): 263 """White-box testing of funcs tracing""" 264 def setUp(self): 265 self.addCleanup(sys.settrace, sys.gettrace()) 266 self.tracer = Trace(count=0, trace=0, countfuncs=1) 267 self.filemod = my_file_and_modname() 268 self._saved_tracefunc = sys.gettrace() 269 270 def tearDown(self): 271 if self._saved_tracefunc is not None: 272 sys.settrace(self._saved_tracefunc) 273 274 def test_simple_caller(self): 275 self.tracer.runfunc(traced_func_simple_caller, 1) 276 277 expected = { 278 self.filemod + ('traced_func_simple_caller',): 1, 279 self.filemod + ('traced_func_linear',): 1, 280 } 281 self.assertEqual(self.tracer.results().calledfuncs, expected) 282 283 def test_arg_errors(self): 284 res = self.tracer.runfunc(traced_capturer, 1, 2, self=3, func=4) 285 self.assertEqual(res, ((1, 2), {'self': 3, 'func': 4})) 286 with self.assertRaises(TypeError): 287 self.tracer.runfunc(func=traced_capturer, arg=1) 288 with self.assertRaises(TypeError): 289 self.tracer.runfunc() 290 291 def test_loop_caller_importing(self): 292 self.tracer.runfunc(traced_func_importing_caller, 1) 293 294 expected = { 295 self.filemod + ('traced_func_simple_caller',): 1, 296 self.filemod + ('traced_func_linear',): 1, 297 self.filemod + ('traced_func_importing_caller',): 1, 298 self.filemod + ('traced_func_importing',): 1, 299 (fix_ext_py(testmod.__file__), 'testmod', 'func'): 1, 300 } 301 self.assertEqual(self.tracer.results().calledfuncs, expected) 302 303 @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(), 304 'pre-existing trace function throws off measurements') 305 def test_inst_method_calling(self): 306 obj = TracedClass(20) 307 self.tracer.runfunc(obj.inst_method_calling, 1) 308 309 expected = { 310 self.filemod + ('TracedClass.inst_method_calling',): 1, 311 self.filemod + ('TracedClass.inst_method_linear',): 1, 312 self.filemod + ('traced_func_linear',): 1, 313 } 314 self.assertEqual(self.tracer.results().calledfuncs, expected) 315 316 def test_traced_decorated_function(self): 317 self.tracer.runfunc(traced_decorated_function) 318 319 expected = { 320 self.filemod + ('traced_decorated_function',): 1, 321 self.filemod + ('decorator_fabric',): 1, 322 self.filemod + ('decorator2',): 1, 323 self.filemod + ('decorator1',): 1, 324 self.filemod + ('func',): 1, 325 } 326 self.assertEqual(self.tracer.results().calledfuncs, expected) 327 328 329class TestCallers(unittest.TestCase): 330 """White-box testing of callers tracing""" 331 def setUp(self): 332 self.addCleanup(sys.settrace, sys.gettrace()) 333 self.tracer = Trace(count=0, trace=0, countcallers=1) 334 self.filemod = my_file_and_modname() 335 336 @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(), 337 'pre-existing trace function throws off measurements') 338 def test_loop_caller_importing(self): 339 self.tracer.runfunc(traced_func_importing_caller, 1) 340 341 expected = { 342 ((os.path.splitext(trace.__file__)[0] + '.py', 'trace', 'Trace.runfunc'), 343 (self.filemod + ('traced_func_importing_caller',))): 1, 344 ((self.filemod + ('traced_func_simple_caller',)), 345 (self.filemod + ('traced_func_linear',))): 1, 346 ((self.filemod + ('traced_func_importing_caller',)), 347 (self.filemod + ('traced_func_simple_caller',))): 1, 348 ((self.filemod + ('traced_func_importing_caller',)), 349 (self.filemod + ('traced_func_importing',))): 1, 350 ((self.filemod + ('traced_func_importing',)), 351 (fix_ext_py(testmod.__file__), 'testmod', 'func')): 1, 352 } 353 self.assertEqual(self.tracer.results().callers, expected) 354 355 356# Created separately for issue #3821 357class TestCoverage(unittest.TestCase): 358 def setUp(self): 359 self.addCleanup(sys.settrace, sys.gettrace()) 360 361 def tearDown(self): 362 rmtree(TESTFN) 363 unlink(TESTFN) 364 365 def _coverage(self, tracer, 366 cmd='import test.support, test.test_pprint;' 367 'test.support.run_unittest(test.test_pprint.QueryTestCase)'): 368 tracer.run(cmd) 369 r = tracer.results() 370 r.write_results(show_missing=True, summary=True, coverdir=TESTFN) 371 372 def test_coverage(self): 373 tracer = trace.Trace(trace=0, count=1) 374 with captured_stdout() as stdout: 375 self._coverage(tracer) 376 stdout = stdout.getvalue() 377 self.assertIn("pprint.py", stdout) 378 self.assertIn("case.py", stdout) # from unittest 379 files = os.listdir(TESTFN) 380 self.assertIn("pprint.cover", files) 381 self.assertIn("unittest.case.cover", files) 382 383 def test_coverage_ignore(self): 384 # Ignore all files, nothing should be traced nor printed 385 libpath = os.path.normpath(os.path.dirname(os.path.dirname(__file__))) 386 # sys.prefix does not work when running from a checkout 387 tracer = trace.Trace(ignoredirs=[sys.base_prefix, sys.base_exec_prefix, 388 libpath], trace=0, count=1) 389 with captured_stdout() as stdout: 390 self._coverage(tracer) 391 if os.path.exists(TESTFN): 392 files = os.listdir(TESTFN) 393 self.assertEqual(files, ['_importlib.cover']) # Ignore __import__ 394 395 def test_issue9936(self): 396 tracer = trace.Trace(trace=0, count=1) 397 modname = 'test.tracedmodules.testmod' 398 # Ensure that the module is executed in import 399 if modname in sys.modules: 400 del sys.modules[modname] 401 cmd = ("import test.tracedmodules.testmod as t;" 402 "t.func(0); t.func2();") 403 with captured_stdout() as stdout: 404 self._coverage(tracer, cmd) 405 stdout.seek(0) 406 stdout.readline() 407 coverage = {} 408 for line in stdout: 409 lines, cov, module = line.split()[:3] 410 coverage[module] = (int(lines), int(cov[:-1])) 411 # XXX This is needed to run regrtest.py as a script 412 modname = trace._fullmodname(sys.modules[modname].__file__) 413 self.assertIn(modname, coverage) 414 self.assertEqual(coverage[modname], (5, 100)) 415 416 def test_coverageresults_update(self): 417 # Update empty CoverageResults with a non-empty infile. 418 infile = TESTFN + '-infile' 419 with open(infile, 'wb') as f: 420 dump(({}, {}, {'caller': 1}), f, protocol=1) 421 self.addCleanup(unlink, infile) 422 results = trace.CoverageResults({}, {}, infile, {}) 423 self.assertEqual(results.callers, {'caller': 1}) 424 425### Tests that don't mess with sys.settrace and can be traced 426### themselves TODO: Skip tests that do mess with sys.settrace when 427### regrtest is invoked with -T option. 428class Test_Ignore(unittest.TestCase): 429 def test_ignored(self): 430 jn = os.path.join 431 ignore = trace._Ignore(['x', 'y.z'], [jn('foo', 'bar')]) 432 self.assertTrue(ignore.names('x.py', 'x')) 433 self.assertFalse(ignore.names('xy.py', 'xy')) 434 self.assertFalse(ignore.names('y.py', 'y')) 435 self.assertTrue(ignore.names(jn('foo', 'bar', 'baz.py'), 'baz')) 436 self.assertFalse(ignore.names(jn('bar', 'z.py'), 'z')) 437 # Matched before. 438 self.assertTrue(ignore.names(jn('bar', 'baz.py'), 'baz')) 439 440# Created for Issue 31908 -- CLI utility not writing cover files 441class TestCoverageCommandLineOutput(unittest.TestCase): 442 443 codefile = 'tmp.py' 444 coverfile = 'tmp.cover' 445 446 def setUp(self): 447 with open(self.codefile, 'w', encoding='iso-8859-15') as f: 448 f.write(textwrap.dedent('''\ 449 # coding: iso-8859-15 450 x = 'spœm' 451 if []: 452 print('unreachable') 453 ''')) 454 455 def tearDown(self): 456 unlink(self.codefile) 457 unlink(self.coverfile) 458 459 def test_cover_files_written_no_highlight(self): 460 # Test also that the cover file for the trace module is not created 461 # (issue #34171). 462 tracedir = os.path.dirname(os.path.abspath(trace.__file__)) 463 tracecoverpath = os.path.join(tracedir, 'trace.cover') 464 unlink(tracecoverpath) 465 466 argv = '-m trace --count'.split() + [self.codefile] 467 status, stdout, stderr = assert_python_ok(*argv) 468 self.assertEqual(stderr, b'') 469 self.assertFalse(os.path.exists(tracecoverpath)) 470 self.assertTrue(os.path.exists(self.coverfile)) 471 with open(self.coverfile, encoding='iso-8859-15') as f: 472 self.assertEqual(f.read(), 473 " # coding: iso-8859-15\n" 474 " 1: x = 'spœm'\n" 475 " 1: if []:\n" 476 " print('unreachable')\n" 477 ) 478 479 def test_cover_files_written_with_highlight(self): 480 argv = '-m trace --count --missing'.split() + [self.codefile] 481 status, stdout, stderr = assert_python_ok(*argv) 482 self.assertTrue(os.path.exists(self.coverfile)) 483 with open(self.coverfile, encoding='iso-8859-15') as f: 484 self.assertEqual(f.read(), textwrap.dedent('''\ 485 # coding: iso-8859-15 486 1: x = 'spœm' 487 1: if []: 488 >>>>>> print('unreachable') 489 ''')) 490 491class TestCommandLine(unittest.TestCase): 492 493 def test_failures(self): 494 _errors = ( 495 (b'progname is missing: required with the main options', '-l', '-T'), 496 (b'cannot specify both --listfuncs and (--trace or --count)', '-lc'), 497 (b'argument -R/--no-report: not allowed with argument -r/--report', '-rR'), 498 (b'must specify one of --trace, --count, --report, --listfuncs, or --trackcalls', '-g'), 499 (b'-r/--report requires -f/--file', '-r'), 500 (b'--summary can only be used with --count or --report', '-sT'), 501 (b'unrecognized arguments: -y', '-y')) 502 for message, *args in _errors: 503 *_, stderr = assert_python_failure('-m', 'trace', *args) 504 self.assertIn(message, stderr) 505 506 def test_listfuncs_flag_success(self): 507 filename = TESTFN + '.py' 508 modulename = os.path.basename(TESTFN) 509 with open(filename, 'w', encoding='utf-8') as fd: 510 self.addCleanup(unlink, filename) 511 fd.write("a = 1\n") 512 status, stdout, stderr = assert_python_ok('-m', 'trace', '-l', filename, 513 PYTHONIOENCODING='utf-8') 514 self.assertIn(b'functions called:', stdout) 515 expected = f'filename: {filename}, modulename: {modulename}, funcname: <module>' 516 self.assertIn(expected.encode(), stdout) 517 518 def test_sys_argv_list(self): 519 with open(TESTFN, 'w', encoding='utf-8') as fd: 520 self.addCleanup(unlink, TESTFN) 521 fd.write("import sys\n") 522 fd.write("print(type(sys.argv))\n") 523 524 status, direct_stdout, stderr = assert_python_ok(TESTFN) 525 status, trace_stdout, stderr = assert_python_ok('-m', 'trace', '-l', TESTFN, 526 PYTHONIOENCODING='utf-8') 527 self.assertIn(direct_stdout.strip(), trace_stdout) 528 529 def test_count_and_summary(self): 530 filename = f'{TESTFN}.py' 531 coverfilename = f'{TESTFN}.cover' 532 modulename = os.path.basename(TESTFN) 533 with open(filename, 'w', encoding='utf-8') as fd: 534 self.addCleanup(unlink, filename) 535 self.addCleanup(unlink, coverfilename) 536 fd.write(textwrap.dedent("""\ 537 x = 1 538 y = 2 539 540 def f(): 541 return x + y 542 543 for i in range(10): 544 f() 545 """)) 546 status, stdout, _ = assert_python_ok('-m', 'trace', '-cs', filename, 547 PYTHONIOENCODING='utf-8') 548 stdout = stdout.decode() 549 self.assertEqual(status, 0) 550 self.assertIn('lines cov% module (path)', stdout) 551 self.assertIn(f'6 100% {modulename} ({filename})', stdout) 552 553 def test_run_as_module(self): 554 assert_python_ok('-m', 'trace', '-l', '--module', 'timeit', '-n', '1') 555 assert_python_failure('-m', 'trace', '-l', '--module', 'not_a_module_zzz') 556 557 558if __name__ == '__main__': 559 unittest.main() 560