1 import os 2 import sys 3 from test.test_support import (run_unittest, TESTFN, rmtree, unlink, 4 captured_stdout) 5 import unittest 6 7 import trace 8 from trace import CoverageResults, Trace 9 10 from test.tracedmodules import testmod 11 12 13 #------------------------------- Utilities -----------------------------------# 14 15 def fix_ext_py(filename): 16 """Given a .pyc/.pyo filename converts it to the appropriate .py""" 17 if filename.endswith(('.pyc', '.pyo')): 18 filename = filename[:-1] 19 return filename 20 21 def 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 26 def 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 36 def traced_func_linear(x, y): 37 a = x 38 b = y 39 c = a + b 40 return c 41 42 def traced_func_loop(x, y): 43 c = x 44 for i in range(5): 45 c += y 46 return c 47 48 def traced_func_importing(x, y): 49 return x + y + testmod.func(1) 50 51 def traced_func_simple_caller(x): 52 c = traced_func_linear(x, x) 53 return c + x 54 55 def traced_func_importing_caller(x): 56 k = traced_func_simple_caller(x) 57 k += traced_func_importing(k, x) 58 return k 59 60 def traced_func_generator(num): 61 c = 5 # executed once 62 for i in range(num): 63 yield i + c 64 65 def traced_func_calling_generator(): 66 k = 0 67 for i in traced_func_generator(10): 68 k += i 69 70 def traced_doubler(num): 71 return num * 2 72 73 def traced_caller_list_comprehension(): 74 k = 10 75 mylist = [traced_doubler(i) for i in range(k)] 76 return mylist 77 78 79 class 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 102 class TestLineCounts(unittest.TestCase): 103 """White-box testing of line-counting, via runfunc""" 104 def setUp(self): 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 (self.my_py_filename, firstlineno_calling + 2): 11, 167 (self.my_py_filename, firstlineno_calling + 3): 1, 168 (self.my_py_filename, firstlineno_called + 1): 10, 169 } 170 self.assertEqual(self.tracer.results().counts, expected) 171 172 173 def test_linear_methods(self): 174 # XXX todo: later add 'static_method_linear' and 'class_method_linear' 175 # here, once issue1764286 is resolved 176 # 177 for methname in ['inst_method_linear',]: 178 tracer = Trace(count=1, trace=0, countfuncs=0, countcallers=0) 179 traced_obj = TracedClass(25) 180 method = getattr(traced_obj, methname) 181 tracer.runfunc(method, 20) 182 183 firstlineno = get_firstlineno(method) 184 expected = { 185 (self.my_py_filename, firstlineno + 1): 1, 186 } 187 self.assertEqual(tracer.results().counts, expected) 188 189 class TestRunExecCounts(unittest.TestCase): 190 """A simple sanity test of line-counting, via runctx (exec)""" 191 def setUp(self): 192 self.my_py_filename = fix_ext_py(__file__) 193 194 def test_exec_counts(self): 195 self.tracer = Trace(count=1, trace=0, countfuncs=0, countcallers=0) 196 code = r'''traced_func_loop(2, 5)''' 197 code = compile(code, __file__, 'exec') 198 self.tracer.runctx(code, globals(), vars()) 199 200 firstlineno = get_firstlineno(traced_func_loop) 201 expected = { 202 (self.my_py_filename, firstlineno + 1): 1, 203 (self.my_py_filename, firstlineno + 2): 6, 204 (self.my_py_filename, firstlineno + 3): 5, 205 (self.my_py_filename, firstlineno + 4): 1, 206 } 207 208 # When used through 'run', some other spurious counts are produced, like 209 # the settrace of threading, which we ignore, just making sure that the 210 # counts fo traced_func_loop were right. 211 # 212 for k in expected.keys(): 213 self.assertEqual(self.tracer.results().counts[k], expected[k]) 214 215 216 class TestFuncs(unittest.TestCase): 217 """White-box testing of funcs tracing""" 218 def setUp(self): 219 self.tracer = Trace(count=0, trace=0, countfuncs=1) 220 self.filemod = my_file_and_modname() 221 222 def test_simple_caller(self): 223 self.tracer.runfunc(traced_func_simple_caller, 1) 224 225 expected = { 226 self.filemod + ('traced_func_simple_caller',): 1, 227 self.filemod + ('traced_func_linear',): 1, 228 } 229 self.assertEqual(self.tracer.results().calledfuncs, expected) 230 231 def test_loop_caller_importing(self): 232 self.tracer.runfunc(traced_func_importing_caller, 1) 233 234 expected = { 235 self.filemod + ('traced_func_simple_caller',): 1, 236 self.filemod + ('traced_func_linear',): 1, 237 self.filemod + ('traced_func_importing_caller',): 1, 238 self.filemod + ('traced_func_importing',): 1, 239 (fix_ext_py(testmod.__file__), 'testmod', 'func'): 1, 240 } 241 self.assertEqual(self.tracer.results().calledfuncs, expected) 242 243 def test_inst_method_calling(self): 244 obj = TracedClass(20) 245 self.tracer.runfunc(obj.inst_method_calling, 1) 246 247 expected = { 248 self.filemod + ('TracedClass.inst_method_calling',): 1, 249 self.filemod + ('TracedClass.inst_method_linear',): 1, 250 self.filemod + ('traced_func_linear',): 1, 251 } 252 self.assertEqual(self.tracer.results().calledfuncs, expected) 253 254 255 class TestCallers(unittest.TestCase): 256 """White-box testing of callers tracing""" 257 def setUp(self): 258 self.tracer = Trace(count=0, trace=0, countcallers=1) 259 self.filemod = my_file_and_modname() 260 261 def test_loop_caller_importing(self): 262 self.tracer.runfunc(traced_func_importing_caller, 1) 263 264 expected = { 265 ((os.path.splitext(trace.__file__)[0] + '.py', 'trace', 'Trace.runfunc'), 266 (self.filemod + ('traced_func_importing_caller',))): 1, 267 ((self.filemod + ('traced_func_simple_caller',)), 268 (self.filemod + ('traced_func_linear',))): 1, 269 ((self.filemod + ('traced_func_importing_caller',)), 270 (self.filemod + ('traced_func_simple_caller',))): 1, 271 ((self.filemod + ('traced_func_importing_caller',)), 272 (self.filemod + ('traced_func_importing',))): 1, 273 ((self.filemod + ('traced_func_importing',)), 274 (fix_ext_py(testmod.__file__), 'testmod', 'func')): 1, 275 } 276 self.assertEqual(self.tracer.results().callers, expected) 277 278 279 # Created separately for issue #3821 280 class TestCoverage(unittest.TestCase): 281 def tearDown(self): 282 rmtree(TESTFN) 283 unlink(TESTFN) 284 285 def _coverage(self, tracer, 286 cmd='from test import test_pprint; test_pprint.test_main()'): 287 tracer.run(cmd) 288 r = tracer.results() 289 r.write_results(show_missing=True, summary=True, coverdir=TESTFN) 290 291 def test_coverage(self): 292 tracer = trace.Trace(trace=0, count=1) 293 with captured_stdout() as stdout: 294 self._coverage(tracer) 295 stdout = stdout.getvalue() 296 self.assertTrue("pprint.py" in stdout) 297 self.assertTrue("case.py" in stdout) # from unittest 298 files = os.listdir(TESTFN) 299 self.assertTrue("pprint.cover" in files) 300 self.assertTrue("unittest.case.cover" in files) 301 302 def test_coverage_ignore(self): 303 # Ignore all files, nothing should be traced nor printed 304 libpath = os.path.normpath(os.path.dirname(os.__file__)) 305 # sys.prefix does not work when running from a checkout 306 tracer = trace.Trace(ignoredirs=[sys.prefix, sys.exec_prefix, libpath], 307 trace=0, count=1) 308 with captured_stdout() as stdout: 309 self._coverage(tracer) 310 if os.path.exists(TESTFN): 311 files = os.listdir(TESTFN) 312 self.assertEqual(files, []) 313 314 def test_issue9936(self): 315 tracer = trace.Trace(trace=0, count=1) 316 modname = 'test.tracedmodules.testmod' 317 # Ensure that the module is executed in import 318 if modname in sys.modules: 319 del sys.modules[modname] 320 cmd = ("import test.tracedmodules.testmod as t;" 321 "t.func(0); t.func2();") 322 with captured_stdout() as stdout: 323 self._coverage(tracer, cmd) 324 stdout.seek(0) 325 stdout.readline() 326 coverage = {} 327 for line in stdout: 328 lines, cov, module = line.split()[:3] 329 coverage[module] = (int(lines), int(cov[:-1])) 330 # XXX This is needed to run regrtest.py as a script 331 modname = trace.fullmodname(sys.modules[modname].__file__) 332 self.assertIn(modname, coverage) 333 self.assertEqual(coverage[modname], (5, 100)) 334 335 336 def test_main(): 337 run_unittest(__name__) 338 339 340 if __name__ == '__main__': 341 test_main() 342