1import os 2import sys 3from test.support import TESTFN, 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 compehentions 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.assertWarns(DeprecationWarning): 280 res = self.tracer.runfunc(func=traced_capturer, arg=1) 281 self.assertEqual(res, ((), {'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') as f: 433 f.write(textwrap.dedent('''\ 434 x = 42 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) as f: 456 self.assertEqual(f.read(), 457 " 1: x = 42\n" 458 " 1: if []:\n" 459 " print('unreachable')\n" 460 ) 461 462 def test_cover_files_written_with_highlight(self): 463 argv = '-m trace --count --missing'.split() + [self.codefile] 464 status, stdout, stderr = assert_python_ok(*argv) 465 self.assertTrue(os.path.exists(self.coverfile)) 466 with open(self.coverfile) as f: 467 self.assertEqual(f.read(), textwrap.dedent('''\ 468 1: x = 42 469 1: if []: 470 >>>>>> print('unreachable') 471 ''')) 472 473class TestCommandLine(unittest.TestCase): 474 475 def test_failures(self): 476 _errors = ( 477 (b'progname is missing: required with the main options', '-l', '-T'), 478 (b'cannot specify both --listfuncs and (--trace or --count)', '-lc'), 479 (b'argument -R/--no-report: not allowed with argument -r/--report', '-rR'), 480 (b'must specify one of --trace, --count, --report, --listfuncs, or --trackcalls', '-g'), 481 (b'-r/--report requires -f/--file', '-r'), 482 (b'--summary can only be used with --count or --report', '-sT'), 483 (b'unrecognized arguments: -y', '-y')) 484 for message, *args in _errors: 485 *_, stderr = assert_python_failure('-m', 'trace', *args) 486 self.assertIn(message, stderr) 487 488 def test_listfuncs_flag_success(self): 489 with open(TESTFN, 'w') as fd: 490 self.addCleanup(unlink, TESTFN) 491 fd.write("a = 1\n") 492 status, stdout, stderr = assert_python_ok('-m', 'trace', '-l', TESTFN) 493 self.assertIn(b'functions called:', stdout) 494 495 def test_sys_argv_list(self): 496 with open(TESTFN, 'w') as fd: 497 self.addCleanup(unlink, TESTFN) 498 fd.write("import sys\n") 499 fd.write("print(type(sys.argv))\n") 500 501 status, direct_stdout, stderr = assert_python_ok(TESTFN) 502 status, trace_stdout, stderr = assert_python_ok('-m', 'trace', '-l', TESTFN) 503 self.assertIn(direct_stdout.strip(), trace_stdout) 504 505 def test_count_and_summary(self): 506 filename = f'{TESTFN}.py' 507 coverfilename = f'{TESTFN}.cover' 508 with open(filename, 'w') as fd: 509 self.addCleanup(unlink, filename) 510 self.addCleanup(unlink, coverfilename) 511 fd.write(textwrap.dedent("""\ 512 x = 1 513 y = 2 514 515 def f(): 516 return x + y 517 518 for i in range(10): 519 f() 520 """)) 521 status, stdout, _ = assert_python_ok('-m', 'trace', '-cs', filename) 522 stdout = stdout.decode() 523 self.assertEqual(status, 0) 524 self.assertIn('lines cov% module (path)', stdout) 525 self.assertIn(f'6 100% {TESTFN} ({filename})', stdout) 526 527 def test_run_as_module(self): 528 assert_python_ok('-m', 'trace', '-l', '--module', 'timeit', '-n', '1') 529 assert_python_failure('-m', 'trace', '-l', '--module', 'not_a_module_zzz') 530 531 532if __name__ == '__main__': 533 unittest.main() 534