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