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