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