1"""Test suite for the profile module.""" 2 3import sys 4import pstats 5import unittest 6import os 7from difflib import unified_diff 8from io import StringIO 9from test.support import TESTFN, run_unittest, unlink 10from contextlib import contextmanager 11 12import profile 13from test.profilee import testfunc, timer 14 15 16class ProfileTest(unittest.TestCase): 17 18 profilerclass = profile.Profile 19 profilermodule = profile 20 methodnames = ['print_stats', 'print_callers', 'print_callees'] 21 expected_max_output = ':0(max)' 22 23 def tearDown(self): 24 unlink(TESTFN) 25 26 def get_expected_output(self): 27 return _ProfileOutput 28 29 @classmethod 30 def do_profiling(cls): 31 results = [] 32 prof = cls.profilerclass(timer, 0.001) 33 start_timer = timer() 34 prof.runctx("testfunc()", globals(), locals()) 35 results.append(timer() - start_timer) 36 for methodname in cls.methodnames: 37 s = StringIO() 38 stats = pstats.Stats(prof, stream=s) 39 stats.strip_dirs().sort_stats("stdname") 40 getattr(stats, methodname)() 41 output = s.getvalue().splitlines() 42 mod_name = testfunc.__module__.rsplit('.', 1)[1] 43 # Only compare against stats originating from the test file. 44 # Prevents outside code (e.g., the io module) from causing 45 # unexpected output. 46 output = [line.rstrip() for line in output if mod_name in line] 47 results.append('\n'.join(output)) 48 return results 49 50 def test_cprofile(self): 51 results = self.do_profiling() 52 expected = self.get_expected_output() 53 self.assertEqual(results[0], 1000) 54 for i, method in enumerate(self.methodnames): 55 if results[i+1] != expected[method]: 56 print("Stats.%s output for %s doesn't fit expectation!" % 57 (method, self.profilerclass.__name__)) 58 print('\n'.join(unified_diff( 59 results[i+1].split('\n'), 60 expected[method].split('\n')))) 61 62 def test_calling_conventions(self): 63 # Issue #5330: profile and cProfile wouldn't report C functions called 64 # with keyword arguments. We test all calling conventions. 65 stmts = [ 66 "max([0])", 67 "max([0], key=int)", 68 "max([0], **dict(key=int))", 69 "max(*([0],))", 70 "max(*([0],), key=int)", 71 "max(*([0],), **dict(key=int))", 72 ] 73 for stmt in stmts: 74 s = StringIO() 75 prof = self.profilerclass(timer, 0.001) 76 prof.runctx(stmt, globals(), locals()) 77 stats = pstats.Stats(prof, stream=s) 78 stats.print_stats() 79 res = s.getvalue() 80 self.assertIn(self.expected_max_output, res, 81 "Profiling {0!r} didn't report max:\n{1}".format(stmt, res)) 82 83 def test_run(self): 84 with silent(): 85 self.profilermodule.run("int('1')") 86 self.profilermodule.run("int('1')", filename=TESTFN) 87 self.assertTrue(os.path.exists(TESTFN)) 88 89 def test_runctx(self): 90 with silent(): 91 self.profilermodule.runctx("testfunc()", globals(), locals()) 92 self.profilermodule.runctx("testfunc()", globals(), locals(), 93 filename=TESTFN) 94 self.assertTrue(os.path.exists(TESTFN)) 95 96 97def regenerate_expected_output(filename, cls): 98 filename = filename.rstrip('co') 99 print('Regenerating %s...' % filename) 100 results = cls.do_profiling() 101 102 newfile = [] 103 with open(filename, 'r') as f: 104 for line in f: 105 newfile.append(line) 106 if line.startswith('#--cut'): 107 break 108 109 with open(filename, 'w') as f: 110 f.writelines(newfile) 111 f.write("_ProfileOutput = {}\n") 112 for i, method in enumerate(cls.methodnames): 113 f.write('_ProfileOutput[%r] = """\\\n%s"""\n' % ( 114 method, results[i+1])) 115 f.write('\nif __name__ == "__main__":\n main()\n') 116 117@contextmanager 118def silent(): 119 stdout = sys.stdout 120 try: 121 sys.stdout = StringIO() 122 yield 123 finally: 124 sys.stdout = stdout 125 126def test_main(): 127 run_unittest(ProfileTest) 128 129def main(): 130 if '-r' not in sys.argv: 131 test_main() 132 else: 133 regenerate_expected_output(__file__, ProfileTest) 134 135 136# Don't remove this comment. Everything below it is auto-generated. 137#--cut-------------------------------------------------------------------------- 138_ProfileOutput = {} 139_ProfileOutput['print_stats'] = """\ 140 28 27.972 0.999 27.972 0.999 profilee.py:110(__getattr__) 141 1 269.996 269.996 999.769 999.769 profilee.py:25(testfunc) 142 23/3 149.937 6.519 169.917 56.639 profilee.py:35(factorial) 143 20 19.980 0.999 19.980 0.999 profilee.py:48(mul) 144 2 39.986 19.993 599.830 299.915 profilee.py:55(helper) 145 4 115.984 28.996 119.964 29.991 profilee.py:73(helper1) 146 2 -0.006 -0.003 139.946 69.973 profilee.py:84(helper2_indirect) 147 8 311.976 38.997 399.912 49.989 profilee.py:88(helper2) 148 8 63.976 7.997 79.960 9.995 profilee.py:98(subhelper)""" 149_ProfileOutput['print_callers'] = """\ 150:0(append) <- profilee.py:73(helper1)(4) 119.964 151:0(exc_info) <- profilee.py:73(helper1)(4) 119.964 152:0(hasattr) <- profilee.py:73(helper1)(4) 119.964 153 profilee.py:88(helper2)(8) 399.912 154profilee.py:110(__getattr__) <- :0(hasattr)(12) 11.964 155 profilee.py:98(subhelper)(16) 79.960 156profilee.py:25(testfunc) <- <string>:1(<module>)(1) 999.767 157profilee.py:35(factorial) <- profilee.py:25(testfunc)(1) 999.769 158 profilee.py:35(factorial)(20) 169.917 159 profilee.py:84(helper2_indirect)(2) 139.946 160profilee.py:48(mul) <- profilee.py:35(factorial)(20) 169.917 161profilee.py:55(helper) <- profilee.py:25(testfunc)(2) 999.769 162profilee.py:73(helper1) <- profilee.py:55(helper)(4) 599.830 163profilee.py:84(helper2_indirect) <- profilee.py:55(helper)(2) 599.830 164profilee.py:88(helper2) <- profilee.py:55(helper)(6) 599.830 165 profilee.py:84(helper2_indirect)(2) 139.946 166profilee.py:98(subhelper) <- profilee.py:88(helper2)(8) 399.912""" 167_ProfileOutput['print_callees'] = """\ 168:0(hasattr) -> profilee.py:110(__getattr__)(12) 27.972 169<string>:1(<module>) -> profilee.py:25(testfunc)(1) 999.769 170profilee.py:110(__getattr__) -> 171profilee.py:25(testfunc) -> profilee.py:35(factorial)(1) 169.917 172 profilee.py:55(helper)(2) 599.830 173profilee.py:35(factorial) -> profilee.py:35(factorial)(20) 169.917 174 profilee.py:48(mul)(20) 19.980 175profilee.py:48(mul) -> 176profilee.py:55(helper) -> profilee.py:73(helper1)(4) 119.964 177 profilee.py:84(helper2_indirect)(2) 139.946 178 profilee.py:88(helper2)(6) 399.912 179profilee.py:73(helper1) -> :0(append)(4) -0.004 180profilee.py:84(helper2_indirect) -> profilee.py:35(factorial)(2) 169.917 181 profilee.py:88(helper2)(2) 399.912 182profilee.py:88(helper2) -> :0(hasattr)(8) 11.964 183 profilee.py:98(subhelper)(8) 79.960 184profilee.py:98(subhelper) -> profilee.py:110(__getattr__)(16) 27.972""" 185 186if __name__ == "__main__": 187 main() 188