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