Home | History | Annotate | Download | only in test
      1 import os
      2 import sys
      3 from test.support import TESTFN, rmtree, unlink, captured_stdout
      4 from test.support.script_helper import assert_python_ok, assert_python_failure
      5 import unittest
      6 
      7 import trace
      8 from trace import Trace
      9 
     10 from test.tracedmodules import testmod
     11 
     12 #------------------------------- Utilities -----------------------------------#
     13 
     14 def 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 
     20 def 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 
     25 def 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 
     35 def traced_func_linear(x, y):
     36     a = x
     37     b = y
     38     c = a + b
     39     return c
     40 
     41 def traced_func_loop(x, y):
     42     c = x
     43     for i in range(5):
     44         c += y
     45     return c
     46 
     47 def traced_func_importing(x, y):
     48     return x + y + testmod.func(1)
     49 
     50 def traced_func_simple_caller(x):
     51     c = traced_func_linear(x, x)
     52     return c + x
     53 
     54 def traced_func_importing_caller(x):
     55     k = traced_func_simple_caller(x)
     56     k += traced_func_importing(k, x)
     57     return k
     58 
     59 def traced_func_generator(num):
     60     c = 5       # executed once
     61     for i in range(num):
     62         yield i + c
     63 
     64 def traced_func_calling_generator():
     65     k = 0
     66     for i in traced_func_generator(10):
     67         k += i
     68 
     69 def traced_doubler(num):
     70     return num * 2
     71 
     72 def traced_caller_list_comprehension():
     73     k = 10
     74     mylist = [traced_doubler(i) for i in range(k)]
     75     return mylist
     76 
     77 
     78 class 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 
    101 class 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 
    191 class 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 
    219 class 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 
    266 class 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
    294 class 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.
    356 class 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 
    368 class 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 
    390 if __name__ == '__main__':
    391     unittest.main()
    392