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 unittest 6 7import trace 8from trace import Trace 9 10from test.tracedmodules import testmod 11 12#------------------------------- Utilities -----------------------------------# 13 14def fix_ext_py(filename): 15 """Given a .pyc filename converts it to the appropriate .py""" 16 if filename.endswith('.pyc'): 17 filename = filename[:-1] 18 return filename 19 20def my_file_and_modname(): 21 """The .py file and module name of this file (__file__)""" 22 modname = os.path.splitext(os.path.basename(__file__))[0] 23 return fix_ext_py(__file__), modname 24 25def get_firstlineno(func): 26 return func.__code__.co_firstlineno 27 28#-------------------- Target functions for tracing ---------------------------# 29# 30# The relative line numbers of lines in these functions matter for verifying 31# tracing. Please modify the appropriate tests if you change one of the 32# functions. Absolute line numbers don't matter. 33# 34 35def traced_func_linear(x, y): 36 a = x 37 b = y 38 c = a + b 39 return c 40 41def traced_func_loop(x, y): 42 c = x 43 for i in range(5): 44 c += y 45 return c 46 47def traced_func_importing(x, y): 48 return x + y + testmod.func(1) 49 50def traced_func_simple_caller(x): 51 c = traced_func_linear(x, x) 52 return c + x 53 54def traced_func_importing_caller(x): 55 k = traced_func_simple_caller(x) 56 k += traced_func_importing(k, x) 57 return k 58 59def traced_func_generator(num): 60 c = 5 # executed once 61 for i in range(num): 62 yield i + c 63 64def traced_func_calling_generator(): 65 k = 0 66 for i in traced_func_generator(10): 67 k += i 68 69def traced_doubler(num): 70 return num * 2 71 72def traced_caller_list_comprehension(): 73 k = 10 74 mylist = [traced_doubler(i) for i in range(k)] 75 return mylist 76 77 78class TracedClass(object): 79 def __init__(self, x): 80 self.a = x 81 82 def inst_method_linear(self, y): 83 return self.a + y 84 85 def inst_method_calling(self, x): 86 c = self.inst_method_linear(x) 87 return c + traced_func_linear(x, c) 88 89 @classmethod 90 def class_method_linear(cls, y): 91 return y * 2 92 93 @staticmethod 94 def static_method_linear(y): 95 return y * 2 96 97 98#------------------------------ Test cases -----------------------------------# 99 100 101class TestLineCounts(unittest.TestCase): 102 """White-box testing of line-counting, via runfunc""" 103 def setUp(self): 104 self.addCleanup(sys.settrace, sys.gettrace()) 105 self.tracer = Trace(count=1, trace=0, countfuncs=0, countcallers=0) 106 self.my_py_filename = fix_ext_py(__file__) 107 108 def test_traced_func_linear(self): 109 result = self.tracer.runfunc(traced_func_linear, 2, 5) 110 self.assertEqual(result, 7) 111 112 # all lines are executed once 113 expected = {} 114 firstlineno = get_firstlineno(traced_func_linear) 115 for i in range(1, 5): 116 expected[(self.my_py_filename, firstlineno + i)] = 1 117 118 self.assertEqual(self.tracer.results().counts, expected) 119 120 def test_traced_func_loop(self): 121 self.tracer.runfunc(traced_func_loop, 2, 3) 122 123 firstlineno = get_firstlineno(traced_func_loop) 124 expected = { 125 (self.my_py_filename, firstlineno + 1): 1, 126 (self.my_py_filename, firstlineno + 2): 6, 127 (self.my_py_filename, firstlineno + 3): 5, 128 (self.my_py_filename, firstlineno + 4): 1, 129 } 130 self.assertEqual(self.tracer.results().counts, expected) 131 132 def test_traced_func_importing(self): 133 self.tracer.runfunc(traced_func_importing, 2, 5) 134 135 firstlineno = get_firstlineno(traced_func_importing) 136 expected = { 137 (self.my_py_filename, firstlineno + 1): 1, 138 (fix_ext_py(testmod.__file__), 2): 1, 139 (fix_ext_py(testmod.__file__), 3): 1, 140 } 141 142 self.assertEqual(self.tracer.results().counts, expected) 143 144 def test_trace_func_generator(self): 145 self.tracer.runfunc(traced_func_calling_generator) 146 147 firstlineno_calling = get_firstlineno(traced_func_calling_generator) 148 firstlineno_gen = get_firstlineno(traced_func_generator) 149 expected = { 150 (self.my_py_filename, firstlineno_calling + 1): 1, 151 (self.my_py_filename, firstlineno_calling + 2): 11, 152 (self.my_py_filename, firstlineno_calling + 3): 10, 153 (self.my_py_filename, firstlineno_gen + 1): 1, 154 (self.my_py_filename, firstlineno_gen + 2): 11, 155 (self.my_py_filename, firstlineno_gen + 3): 10, 156 } 157 self.assertEqual(self.tracer.results().counts, expected) 158 159 def test_trace_list_comprehension(self): 160 self.tracer.runfunc(traced_caller_list_comprehension) 161 162 firstlineno_calling = get_firstlineno(traced_caller_list_comprehension) 163 firstlineno_called = get_firstlineno(traced_doubler) 164 expected = { 165 (self.my_py_filename, firstlineno_calling + 1): 1, 166 # List compehentions work differently in 3.x, so the count 167 # below changed compared to 2.x. 168 (self.my_py_filename, firstlineno_calling + 2): 12, 169 (self.my_py_filename, firstlineno_calling + 3): 1, 170 (self.my_py_filename, firstlineno_called + 1): 10, 171 } 172 self.assertEqual(self.tracer.results().counts, expected) 173 174 175 def test_linear_methods(self): 176 # XXX todo: later add 'static_method_linear' and 'class_method_linear' 177 # here, once issue1764286 is resolved 178 # 179 for methname in ['inst_method_linear',]: 180 tracer = Trace(count=1, trace=0, countfuncs=0, countcallers=0) 181 traced_obj = TracedClass(25) 182 method = getattr(traced_obj, methname) 183 tracer.runfunc(method, 20) 184 185 firstlineno = get_firstlineno(method) 186 expected = { 187 (self.my_py_filename, firstlineno + 1): 1, 188 } 189 self.assertEqual(tracer.results().counts, expected) 190 191class TestRunExecCounts(unittest.TestCase): 192 """A simple sanity test of line-counting, via runctx (exec)""" 193 def setUp(self): 194 self.my_py_filename = fix_ext_py(__file__) 195 self.addCleanup(sys.settrace, sys.gettrace()) 196 197 def test_exec_counts(self): 198 self.tracer = Trace(count=1, trace=0, countfuncs=0, countcallers=0) 199 code = r'''traced_func_loop(2, 5)''' 200 code = compile(code, __file__, 'exec') 201 self.tracer.runctx(code, globals(), vars()) 202 203 firstlineno = get_firstlineno(traced_func_loop) 204 expected = { 205 (self.my_py_filename, firstlineno + 1): 1, 206 (self.my_py_filename, firstlineno + 2): 6, 207 (self.my_py_filename, firstlineno + 3): 5, 208 (self.my_py_filename, firstlineno + 4): 1, 209 } 210 211 # When used through 'run', some other spurious counts are produced, like 212 # the settrace of threading, which we ignore, just making sure that the 213 # counts fo traced_func_loop were right. 214 # 215 for k in expected.keys(): 216 self.assertEqual(self.tracer.results().counts[k], expected[k]) 217 218 219class TestFuncs(unittest.TestCase): 220 """White-box testing of funcs tracing""" 221 def setUp(self): 222 self.addCleanup(sys.settrace, sys.gettrace()) 223 self.tracer = Trace(count=0, trace=0, countfuncs=1) 224 self.filemod = my_file_and_modname() 225 self._saved_tracefunc = sys.gettrace() 226 227 def tearDown(self): 228 if self._saved_tracefunc is not None: 229 sys.settrace(self._saved_tracefunc) 230 231 def test_simple_caller(self): 232 self.tracer.runfunc(traced_func_simple_caller, 1) 233 234 expected = { 235 self.filemod + ('traced_func_simple_caller',): 1, 236 self.filemod + ('traced_func_linear',): 1, 237 } 238 self.assertEqual(self.tracer.results().calledfuncs, expected) 239 240 def test_loop_caller_importing(self): 241 self.tracer.runfunc(traced_func_importing_caller, 1) 242 243 expected = { 244 self.filemod + ('traced_func_simple_caller',): 1, 245 self.filemod + ('traced_func_linear',): 1, 246 self.filemod + ('traced_func_importing_caller',): 1, 247 self.filemod + ('traced_func_importing',): 1, 248 (fix_ext_py(testmod.__file__), 'testmod', 'func'): 1, 249 } 250 self.assertEqual(self.tracer.results().calledfuncs, expected) 251 252 @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(), 253 'pre-existing trace function throws off measurements') 254 def test_inst_method_calling(self): 255 obj = TracedClass(20) 256 self.tracer.runfunc(obj.inst_method_calling, 1) 257 258 expected = { 259 self.filemod + ('TracedClass.inst_method_calling',): 1, 260 self.filemod + ('TracedClass.inst_method_linear',): 1, 261 self.filemod + ('traced_func_linear',): 1, 262 } 263 self.assertEqual(self.tracer.results().calledfuncs, expected) 264 265 266class TestCallers(unittest.TestCase): 267 """White-box testing of callers tracing""" 268 def setUp(self): 269 self.addCleanup(sys.settrace, sys.gettrace()) 270 self.tracer = Trace(count=0, trace=0, countcallers=1) 271 self.filemod = my_file_and_modname() 272 273 @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(), 274 'pre-existing trace function throws off measurements') 275 def test_loop_caller_importing(self): 276 self.tracer.runfunc(traced_func_importing_caller, 1) 277 278 expected = { 279 ((os.path.splitext(trace.__file__)[0] + '.py', 'trace', 'Trace.runfunc'), 280 (self.filemod + ('traced_func_importing_caller',))): 1, 281 ((self.filemod + ('traced_func_simple_caller',)), 282 (self.filemod + ('traced_func_linear',))): 1, 283 ((self.filemod + ('traced_func_importing_caller',)), 284 (self.filemod + ('traced_func_simple_caller',))): 1, 285 ((self.filemod + ('traced_func_importing_caller',)), 286 (self.filemod + ('traced_func_importing',))): 1, 287 ((self.filemod + ('traced_func_importing',)), 288 (fix_ext_py(testmod.__file__), 'testmod', 'func')): 1, 289 } 290 self.assertEqual(self.tracer.results().callers, expected) 291 292 293# Created separately for issue #3821 294class TestCoverage(unittest.TestCase): 295 def setUp(self): 296 self.addCleanup(sys.settrace, sys.gettrace()) 297 298 def tearDown(self): 299 rmtree(TESTFN) 300 unlink(TESTFN) 301 302 def _coverage(self, tracer, 303 cmd='import test.support, test.test_pprint;' 304 'test.support.run_unittest(test.test_pprint.QueryTestCase)'): 305 tracer.run(cmd) 306 r = tracer.results() 307 r.write_results(show_missing=True, summary=True, coverdir=TESTFN) 308 309 def test_coverage(self): 310 tracer = trace.Trace(trace=0, count=1) 311 with captured_stdout() as stdout: 312 self._coverage(tracer) 313 stdout = stdout.getvalue() 314 self.assertIn("pprint.py", stdout) 315 self.assertIn("case.py", stdout) # from unittest 316 files = os.listdir(TESTFN) 317 self.assertIn("pprint.cover", files) 318 self.assertIn("unittest.case.cover", files) 319 320 def test_coverage_ignore(self): 321 # Ignore all files, nothing should be traced nor printed 322 libpath = os.path.normpath(os.path.dirname(os.__file__)) 323 # sys.prefix does not work when running from a checkout 324 tracer = trace.Trace(ignoredirs=[sys.base_prefix, sys.base_exec_prefix, 325 libpath], trace=0, count=1) 326 with captured_stdout() as stdout: 327 self._coverage(tracer) 328 if os.path.exists(TESTFN): 329 files = os.listdir(TESTFN) 330 self.assertEqual(files, ['_importlib.cover']) # Ignore __import__ 331 332 def test_issue9936(self): 333 tracer = trace.Trace(trace=0, count=1) 334 modname = 'test.tracedmodules.testmod' 335 # Ensure that the module is executed in import 336 if modname in sys.modules: 337 del sys.modules[modname] 338 cmd = ("import test.tracedmodules.testmod as t;" 339 "t.func(0); t.func2();") 340 with captured_stdout() as stdout: 341 self._coverage(tracer, cmd) 342 stdout.seek(0) 343 stdout.readline() 344 coverage = {} 345 for line in stdout: 346 lines, cov, module = line.split()[:3] 347 coverage[module] = (int(lines), int(cov[:-1])) 348 # XXX This is needed to run regrtest.py as a script 349 modname = trace._fullmodname(sys.modules[modname].__file__) 350 self.assertIn(modname, coverage) 351 self.assertEqual(coverage[modname], (5, 100)) 352 353### Tests that don't mess with sys.settrace and can be traced 354### themselves TODO: Skip tests that do mess with sys.settrace when 355### regrtest is invoked with -T option. 356class Test_Ignore(unittest.TestCase): 357 def test_ignored(self): 358 jn = os.path.join 359 ignore = trace._Ignore(['x', 'y.z'], [jn('foo', 'bar')]) 360 self.assertTrue(ignore.names('x.py', 'x')) 361 self.assertFalse(ignore.names('xy.py', 'xy')) 362 self.assertFalse(ignore.names('y.py', 'y')) 363 self.assertTrue(ignore.names(jn('foo', 'bar', 'baz.py'), 'baz')) 364 self.assertFalse(ignore.names(jn('bar', 'z.py'), 'z')) 365 # Matched before. 366 self.assertTrue(ignore.names(jn('bar', 'baz.py'), 'baz')) 367 368class TestCommandLine(unittest.TestCase): 369 370 def test_failures(self): 371 _errors = ( 372 (b'filename is missing: required with the main options', '-l', '-T'), 373 (b'cannot specify both --listfuncs and (--trace or --count)', '-lc'), 374 (b'argument -R/--no-report: not allowed with argument -r/--report', '-rR'), 375 (b'must specify one of --trace, --count, --report, --listfuncs, or --trackcalls', '-g'), 376 (b'-r/--report requires -f/--file', '-r'), 377 (b'--summary can only be used with --count or --report', '-sT'), 378 (b'unrecognized arguments: -y', '-y')) 379 for message, *args in _errors: 380 *_, stderr = assert_python_failure('-m', 'trace', *args) 381 self.assertIn(message, stderr) 382 383 def test_listfuncs_flag_success(self): 384 with open(TESTFN, 'w') as fd: 385 self.addCleanup(unlink, TESTFN) 386 fd.write("a = 1\n") 387 status, stdout, stderr = assert_python_ok('-m', 'trace', '-l', TESTFN) 388 self.assertIn(b'functions called:', stdout) 389 390if __name__ == '__main__': 391 unittest.main() 392