test_trace.py 21 KB

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