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