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