Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 4d77017

Browse files
committed
Issue #9315: Fix for the trace module to record correct class name
when tracing methods. Unit tests. Patch by Eli Bendersky.
1 parent dc69e72 commit 4d77017

3 files changed

Lines changed: 291 additions & 14 deletions

File tree

Lib/test/test_trace.py

Lines changed: 282 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,286 @@
1-
# Testing the trace module
2-
3-
from test.support import run_unittest, TESTFN, rmtree, unlink, captured_stdout
1+
import os
2+
import sys
3+
from test.support import (run_unittest, TESTFN, rmtree, unlink,
4+
captured_stdout)
45
import unittest
6+
57
import trace
6-
import os, sys
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+
self.maxDiff = None
108+
109+
def test_traced_func_linear(self):
110+
result = self.tracer.runfunc(traced_func_linear, 2, 5)
111+
self.assertEqual(result, 7)
112+
113+
# all lines are executed once
114+
expected = {}
115+
firstlineno = get_firstlineno(traced_func_linear)
116+
for i in range(1, 5):
117+
expected[(self.my_py_filename, firstlineno + i)] = 1
118+
119+
self.assertEqual(self.tracer.results().counts, expected)
7120

121+
def test_traced_func_loop(self):
122+
self.tracer.runfunc(traced_func_loop, 2, 3)
123+
124+
firstlineno = get_firstlineno(traced_func_loop)
125+
expected = {
126+
(self.my_py_filename, firstlineno + 1): 1,
127+
(self.my_py_filename, firstlineno + 2): 6,
128+
(self.my_py_filename, firstlineno + 3): 5,
129+
(self.my_py_filename, firstlineno + 4): 1,
130+
}
131+
self.assertEqual(self.tracer.results().counts, expected)
132+
133+
def test_traced_func_importing(self):
134+
self.tracer.runfunc(traced_func_importing, 2, 5)
135+
136+
firstlineno = get_firstlineno(traced_func_importing)
137+
expected = {
138+
(self.my_py_filename, firstlineno + 1): 1,
139+
(fix_ext_py(testmod.__file__), 2): 1,
140+
(fix_ext_py(testmod.__file__), 3): 1,
141+
}
142+
143+
self.assertEqual(self.tracer.results().counts, expected)
144+
145+
def test_trace_func_generator(self):
146+
self.tracer.runfunc(traced_func_calling_generator)
147+
148+
firstlineno_calling = get_firstlineno(traced_func_calling_generator)
149+
firstlineno_gen = get_firstlineno(traced_func_generator)
150+
expected = {
151+
(self.my_py_filename, firstlineno_calling + 1): 1,
152+
(self.my_py_filename, firstlineno_calling + 2): 11,
153+
(self.my_py_filename, firstlineno_calling + 3): 10,
154+
(self.my_py_filename, firstlineno_gen + 1): 1,
155+
(self.my_py_filename, firstlineno_gen + 2): 11,
156+
(self.my_py_filename, firstlineno_gen + 3): 10,
157+
}
158+
self.assertEqual(self.tracer.results().counts, expected)
159+
160+
def test_trace_list_comprehension(self):
161+
self.tracer.runfunc(traced_caller_list_comprehension)
162+
163+
firstlineno_calling = get_firstlineno(traced_caller_list_comprehension)
164+
firstlineno_called = get_firstlineno(traced_doubler)
165+
expected = {
166+
(self.my_py_filename, firstlineno_calling + 1): 1,
167+
# List compehentions work differently in 3.x, so the count
168+
# below changed compared to 2.x.
169+
(self.my_py_filename, firstlineno_calling + 2): 12,
170+
(self.my_py_filename, firstlineno_calling + 3): 1,
171+
(self.my_py_filename, firstlineno_called + 1): 10,
172+
}
173+
self.assertEqual(self.tracer.results().counts, expected)
174+
175+
176+
def test_linear_methods(self):
177+
# XXX todo: later add 'static_method_linear' and 'class_method_linear'
178+
# here, once issue1764286 is resolved
179+
#
180+
for methname in ['inst_method_linear',]:
181+
tracer = Trace(count=1, trace=0, countfuncs=0, countcallers=0)
182+
traced_obj = TracedClass(25)
183+
method = getattr(traced_obj, methname)
184+
tracer.runfunc(method, 20)
185+
186+
firstlineno = get_firstlineno(method)
187+
expected = {
188+
(self.my_py_filename, firstlineno + 1): 1,
189+
}
190+
self.assertEqual(tracer.results().counts, expected)
191+
192+
193+
class TestRunExecCounts(unittest.TestCase):
194+
"""A simple sanity test of line-counting, via runctx (exec)"""
195+
def setUp(self):
196+
self.my_py_filename = fix_ext_py(__file__)
197+
198+
def test_exec_counts(self):
199+
self.tracer = Trace(count=1, trace=0, countfuncs=0, countcallers=0)
200+
code = r'''traced_func_loop(2, 5)'''
201+
code = compile(code, __file__, 'exec')
202+
self.tracer.runctx(code, globals(), vars())
203+
204+
firstlineno = get_firstlineno(traced_func_loop)
205+
expected = {
206+
(self.my_py_filename, firstlineno + 1): 1,
207+
(self.my_py_filename, firstlineno + 2): 6,
208+
(self.my_py_filename, firstlineno + 3): 5,
209+
(self.my_py_filename, firstlineno + 4): 1,
210+
}
211+
212+
# When used through 'run', some other spurios counts are produced, like
213+
# the settrace of threading, which we ignore, just making sure that the
214+
# counts fo traced_func_loop were right.
215+
#
216+
for k in expected.keys():
217+
self.assertEqual(self.tracer.results().counts[k], expected[k])
218+
219+
220+
class TestFuncs(unittest.TestCase):
221+
"""White-box testing of funcs tracing"""
222+
def setUp(self):
223+
self.tracer = Trace(count=0, trace=0, countfuncs=1)
224+
self.filemod = my_file_and_modname()
225+
226+
def test_simple_caller(self):
227+
self.tracer.runfunc(traced_func_simple_caller, 1)
228+
229+
expected = {
230+
self.filemod + ('traced_func_simple_caller',): 1,
231+
self.filemod + ('traced_func_linear',): 1,
232+
}
233+
self.assertEqual(self.tracer.results().calledfuncs, expected)
234+
235+
def test_loop_caller_importing(self):
236+
self.tracer.runfunc(traced_func_importing_caller, 1)
237+
238+
expected = {
239+
self.filemod + ('traced_func_simple_caller',): 1,
240+
self.filemod + ('traced_func_linear',): 1,
241+
self.filemod + ('traced_func_importing_caller',): 1,
242+
self.filemod + ('traced_func_importing',): 1,
243+
(fix_ext_py(testmod.__file__), 'testmod', 'func'): 1,
244+
}
245+
self.assertEqual(self.tracer.results().calledfuncs, expected)
246+
247+
def test_inst_method_calling(self):
248+
obj = TracedClass(20)
249+
self.tracer.runfunc(obj.inst_method_calling, 1)
250+
251+
expected = {
252+
self.filemod + ('TracedClass.inst_method_calling',): 1,
253+
self.filemod + ('TracedClass.inst_method_linear',): 1,
254+
self.filemod + ('traced_func_linear',): 1,
255+
}
256+
self.assertEqual(self.tracer.results().calledfuncs, expected)
257+
258+
259+
class TestCallers(unittest.TestCase):
260+
"""White-box testing of callers tracing"""
261+
def setUp(self):
262+
self.tracer = Trace(count=0, trace=0, countcallers=1)
263+
self.filemod = my_file_and_modname()
264+
265+
def test_loop_caller_importing(self):
266+
self.tracer.runfunc(traced_func_importing_caller, 1)
267+
268+
expected = {
269+
((os.path.splitext(trace.__file__)[0] + '.py', 'trace', 'Trace.runfunc'),
270+
(self.filemod + ('traced_func_importing_caller',))): 1,
271+
((self.filemod + ('traced_func_simple_caller',)),
272+
(self.filemod + ('traced_func_linear',))): 1,
273+
((self.filemod + ('traced_func_importing_caller',)),
274+
(self.filemod + ('traced_func_simple_caller',))): 1,
275+
((self.filemod + ('traced_func_importing_caller',)),
276+
(self.filemod + ('traced_func_importing',))): 1,
277+
((self.filemod + ('traced_func_importing',)),
278+
(fix_ext_py(testmod.__file__), 'testmod', 'func')): 1,
279+
}
280+
self.assertEqual(self.tracer.results().callers, expected)
281+
282+
283+
# Created separately for issue #3821
8284
class TestCoverage(unittest.TestCase):
9285
def tearDown(self):
10286
rmtree(TESTFN)
@@ -34,7 +310,6 @@ def test_coverage_ignore(self):
34310
trace=0, count=1)
35311
with captured_stdout() as stdout:
36312
self._coverage(tracer)
37-
self.assertEquals(stdout.getvalue(), "")
38313
if os.path.exists(TESTFN):
39314
files = os.listdir(TESTFN)
40315
self.assertEquals(files, [])
@@ -43,5 +318,6 @@ def test_coverage_ignore(self):
43318
def test_main():
44319
run_unittest(__name__)
45320

46-
if __name__ == "__main__":
321+
322+
if __name__ == '__main__':
47323
test_main()

Lib/trace.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
import time
5858
import token
5959
import tokenize
60-
import types
60+
import inspect
6161
import gc
6262

6363
import pickle
@@ -395,7 +395,7 @@ def find_lines(code, strs):
395395

396396
# and check the constants for references to other code objects
397397
for c in code.co_consts:
398-
if isinstance(c, types.CodeType):
398+
if inspect.iscode(c):
399399
# find another code object, so recurse into it
400400
linenos.update(find_lines(c, strs))
401401
return linenos
@@ -544,7 +544,7 @@ def file_module_function_of(self, frame):
544544
## use of gc.get_referrers() was suggested by Michael Hudson
545545
# all functions which refer to this code object
546546
funcs = [f for f in gc.get_referrers(code)
547-
if hasattr(f, "__doc__")]
547+
if inspect.isfunction(f)]
548548
# require len(func) == 1 to avoid ambiguity caused by calls to
549549
# new.function(): "In the face of ambiguity, refuse the
550550
# temptation to guess."
@@ -556,17 +556,13 @@ def file_module_function_of(self, frame):
556556
if hasattr(c, "__bases__")]
557557
if len(classes) == 1:
558558
# ditto for new.classobj()
559-
clsname = str(classes[0])
559+
clsname = classes[0].__name__
560560
# cache the result - assumption is that new.* is
561561
# not called later to disturb this relationship
562562
# _caller_cache could be flushed if functions in
563563
# the new module get called.
564564
self._caller_cache[code] = clsname
565565
if clsname is not None:
566-
# final hack - module name shows up in str(cls), but we've already
567-
# computed module name, so remove it
568-
clsname = clsname.split(".")[1:]
569-
clsname = ".".join(clsname)
570566
funcname = "%s.%s" % (clsname, funcname)
571567

572568
return filename, modulename, funcname

Misc/NEWS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ Tools/Demos
145145
Tests
146146
-----
147147

148+
- Issue #9315: Added tests for the trace module. Patch by Eli Bendersky.
149+
148150
- Issue #9323: Make test.regrtest.__file__ absolute, this was not always the
149151
case when running profile or trace, for example.
150152

@@ -2070,6 +2072,9 @@ Library
20702072
- Issue #8235: _socket: Add the constant ``SO_SETFIB``. SO_SETFIB is a socket
20712073
option available on FreeBSD 7.1 and newer.
20722074

2075+
- Issue #9315: Fix for the trace module to record correct class name
2076+
for tracing methods.
2077+
20732078
Extension Modules
20742079
-----------------
20752080

0 commit comments

Comments
 (0)