Skip to content

Commit 8f44d6c

Browse files
committed
ENH: Tests!
1 parent 14808ac commit 8f44d6c

File tree

5 files changed

+208
-27
lines changed

5 files changed

+208
-27
lines changed

_line_profiler.pyx

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,8 @@ cdef class LineTiming:
5858
"""
5959
cdef public object code
6060
cdef public int lineno
61-
# Note: leave at least total_time private. This should help compile under
62-
# Python 2.4.
63-
cdef PY_LONG_LONG total_time
64-
cdef long nhits
61+
cdef public PY_LONG_LONG total_time
62+
cdef public long nhits
6563

6664
def __init__(self, object code, int lineno):
6765
self.code = code
@@ -106,9 +104,9 @@ class LineStats(object):
106104
cdef class LineProfiler:
107105
""" Time the execution of lines of Python code.
108106
"""
109-
cdef public object functions
110-
cdef public object code_map
111-
cdef public object last_time
107+
cdef public list functions
108+
cdef public dict code_map
109+
cdef public dict last_time
112110
cdef public double timer_unit
113111
cdef public long enable_count
114112

@@ -186,21 +184,26 @@ cdef class LastTime:
186184
self.time = time
187185

188186

189-
cdef int python_trace_callback(object self, PyFrameObject *py_frame, int what,
187+
cdef int python_trace_callback(object self_, PyFrameObject *py_frame, int what,
190188
PyObject *arg):
191189
""" The PyEval_SetTrace() callback.
192190
"""
193-
cdef object code, line_entries, key
191+
cdef LineProfiler self
192+
cdef object code, key
193+
cdef dict line_entries, last_time
194194
cdef LineTiming entry
195195
cdef LastTime old
196196
cdef PY_LONG_LONG time
197197

198+
self = <LineProfiler>self_
199+
last_time = self.last_time
200+
198201
if what == PyTrace_LINE or what == PyTrace_RETURN:
199202
code = <object>py_frame.f_code
200203
if code in self.code_map:
201204
time = hpTimer()
202-
if code in self.last_time:
203-
old = self.last_time[code]
205+
if code in last_time:
206+
old = last_time[code]
204207
line_entries = self.code_map[code]
205208
key = old.f_lineno
206209
if key not in line_entries:
@@ -212,11 +215,13 @@ cdef int python_trace_callback(object self, PyFrameObject *py_frame, int what,
212215
if what == PyTrace_LINE:
213216
# Get the time again. This way, we don't record much time wasted
214217
# in this function.
215-
self.last_time[code] = LastTime(py_frame.f_lineno, hpTimer())
218+
last_time[code] = LastTime(py_frame.f_lineno, hpTimer())
216219
else:
217220
# We are returning from a function, not executing a line. Delete
218-
# the last_time record.
219-
del self.last_time[code]
221+
# the last_time record. It may have already been deleted if we
222+
# are profiling a generator that is being pumped past its end.
223+
if code in last_time:
224+
del last_time[code]
220225

221226
return 0
222227

line_profiler.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from cStringIO import StringIO
1111
except ImportError:
1212
from io import StringIO
13+
import functools
1314
import inspect
1415
import linecache
1516
import optparse
@@ -60,19 +61,16 @@ def __call__(self, func):
6061
"""
6162
self.add_function(func)
6263
if is_generator(func):
63-
f = self.wrap_generator(func)
64+
wrapper = self.wrap_generator(func)
6465
else:
65-
f = self.wrap_function(func)
66-
f.__module__ = func.__module__
67-
f.__name__ = func.__name__
68-
f.__doc__ = func.__doc__
69-
f.__dict__.update(getattr(func, '__dict__', {}))
70-
return f
66+
wrapper = self.wrap_function(func)
67+
return wrapper
7168

7269
def wrap_generator(self, func):
7370
""" Wrap a generator to profile it.
7471
"""
75-
def f(*args, **kwds):
72+
@functools.wraps(func)
73+
def wrapper(*args, **kwds):
7674
g = func(*args, **kwds)
7775
# The first iterate will not be a .send()
7876
self.enable_by_count()
@@ -89,19 +87,20 @@ def f(*args, **kwds):
8987
finally:
9088
self.disable_by_count()
9189
input = (yield item)
92-
return f
90+
return wrapper
9391

9492
def wrap_function(self, func):
9593
""" Wrap a function to profile it.
9694
"""
97-
def f(*args, **kwds):
95+
@functools.wraps(func)
96+
def wrapper(*args, **kwds):
9897
self.enable_by_count()
9998
try:
10099
result = func(*args, **kwds)
101100
finally:
102101
self.disable_by_count()
103102
return result
104-
return f
103+
return wrapper
105104

106105
def dump_stats(self, filename):
107106
""" Dump a representation of the data to a file as a pickled LineStats

python25.pxd

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -399,8 +399,12 @@ cdef extern from "Python.h":
399399
ctypedef struct PyThreadState:
400400
PyFrameObject * frame
401401
int recursion_depth
402-
void * curexc_type, * curexc_value, * curexc_traceback
403-
void * exc_type, * exc_value, * exc_traceback
402+
void * curexc_type
403+
void * curexc_value
404+
void * curexc_traceback
405+
void * exc_type
406+
void * exc_value
407+
void * exc_traceback
404408

405409
void PyEval_AcquireLock ()
406410
void PyEval_ReleaseLock ()

tests/test_kernprof.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import unittest
2+
3+
from kernprof import ContextualProfile
4+
5+
6+
def f(x):
7+
""" A function. """
8+
y = x + 10
9+
return y
10+
11+
12+
def g(x):
13+
""" A generator. """
14+
y = yield x + 10
15+
yield y + 20
16+
17+
18+
class TestKernprof(unittest.TestCase):
19+
20+
def test_enable_disable(self):
21+
profile = ContextualProfile()
22+
self.assertEqual(profile.enable_count, 0)
23+
profile.enable_by_count()
24+
self.assertEqual(profile.enable_count, 1)
25+
profile.enable_by_count()
26+
self.assertEqual(profile.enable_count, 2)
27+
profile.disable_by_count()
28+
self.assertEqual(profile.enable_count, 1)
29+
profile.disable_by_count()
30+
self.assertEqual(profile.enable_count, 0)
31+
profile.disable_by_count()
32+
self.assertEqual(profile.enable_count, 0)
33+
34+
with profile:
35+
self.assertEqual(profile.enable_count, 1)
36+
with profile:
37+
self.assertEqual(profile.enable_count, 2)
38+
self.assertEqual(profile.enable_count, 1)
39+
self.assertEqual(profile.enable_count, 0)
40+
41+
with self.assertRaises(RuntimeError):
42+
self.assertEqual(profile.enable_count, 0)
43+
with profile:
44+
self.assertEqual(profile.enable_count, 1)
45+
raise RuntimeError()
46+
self.assertEqual(profile.enable_count, 0)
47+
48+
def test_function_decorator(self):
49+
profile = ContextualProfile()
50+
f_wrapped = profile(f)
51+
self.assertEqual(f_wrapped.__name__, f.__name__)
52+
self.assertEqual(f_wrapped.__doc__, f.__doc__)
53+
54+
self.assertEqual(profile.enable_count, 0)
55+
value = f_wrapped(10)
56+
self.assertEqual(profile.enable_count, 0)
57+
self.assertEqual(value, f(10))
58+
59+
def test_gen_decorator(self):
60+
profile = ContextualProfile()
61+
g_wrapped = profile(g)
62+
self.assertEqual(g_wrapped.__name__, g.__name__)
63+
self.assertEqual(g_wrapped.__doc__, g.__doc__)
64+
65+
self.assertEqual(profile.enable_count, 0)
66+
i = g_wrapped(10)
67+
self.assertEqual(profile.enable_count, 0)
68+
self.assertEqual(next(i), 20)
69+
self.assertEqual(profile.enable_count, 0)
70+
self.assertEqual(i.send(30), 50)
71+
self.assertEqual(profile.enable_count, 0)
72+
with self.assertRaises(StopIteration):
73+
next(i)
74+
self.assertEqual(profile.enable_count, 0)

tests/test_line_profiler.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import unittest
2+
3+
from line_profiler import LineProfiler
4+
5+
6+
def f(x):
7+
y = x + 10
8+
return y
9+
10+
11+
def g(x):
12+
y = yield x + 10
13+
yield y + 20
14+
15+
16+
class TestLineProfiler(unittest.TestCase):
17+
18+
def test_init(self):
19+
lp = LineProfiler()
20+
self.assertEqual(lp.functions, [])
21+
self.assertEqual(lp.code_map, {})
22+
lp = LineProfiler(f)
23+
self.assertEqual(lp.functions, [f])
24+
self.assertEqual(lp.code_map, {f.__code__: {}})
25+
lp = LineProfiler(f, g)
26+
self.assertEqual(lp.functions, [f, g])
27+
self.assertEqual(lp.code_map, {
28+
f.__code__: {},
29+
g.__code__: {},
30+
})
31+
32+
def test_enable_disable(self):
33+
lp = LineProfiler()
34+
self.assertEqual(lp.enable_count, 0)
35+
lp.enable_by_count()
36+
self.assertEqual(lp.enable_count, 1)
37+
lp.enable_by_count()
38+
self.assertEqual(lp.enable_count, 2)
39+
lp.disable_by_count()
40+
self.assertEqual(lp.enable_count, 1)
41+
lp.disable_by_count()
42+
self.assertEqual(lp.enable_count, 0)
43+
self.assertEqual(lp.last_time, {})
44+
lp.disable_by_count()
45+
self.assertEqual(lp.enable_count, 0)
46+
47+
with lp:
48+
self.assertEqual(lp.enable_count, 1)
49+
with lp:
50+
self.assertEqual(lp.enable_count, 2)
51+
self.assertEqual(lp.enable_count, 1)
52+
self.assertEqual(lp.enable_count, 0)
53+
self.assertEqual(lp.last_time, {})
54+
55+
with self.assertRaises(RuntimeError):
56+
self.assertEqual(lp.enable_count, 0)
57+
with lp:
58+
self.assertEqual(lp.enable_count, 1)
59+
raise RuntimeError()
60+
self.assertEqual(lp.enable_count, 0)
61+
self.assertEqual(lp.last_time, {})
62+
63+
def test_function_decorator(self):
64+
profile = LineProfiler()
65+
f_wrapped = profile(f)
66+
self.assertEqual(f_wrapped.__name__, 'f')
67+
68+
self.assertEqual(profile.enable_count, 0)
69+
value = f_wrapped(10)
70+
self.assertEqual(profile.enable_count, 0)
71+
self.assertEqual(value, f(10))
72+
73+
timings = profile.code_map[f.__code__]
74+
self.assertEqual(len(timings), 2)
75+
for timing in timings.values():
76+
self.assertEqual(timing.nhits, 1)
77+
78+
def test_gen_decorator(self):
79+
profile = LineProfiler()
80+
g_wrapped = profile(g)
81+
self.assertEqual(g_wrapped.__name__, 'g')
82+
timings = profile.code_map[g.__code__]
83+
84+
self.assertEqual(profile.enable_count, 0)
85+
i = g_wrapped(10)
86+
self.assertEqual(profile.enable_count, 0)
87+
self.assertEqual(next(i), 20)
88+
self.assertEqual(profile.enable_count, 0)
89+
self.assertEqual(len(timings), 1)
90+
self.assertEqual(i.send(30), 50)
91+
self.assertEqual(profile.enable_count, 0)
92+
self.assertEqual(len(timings), 2)
93+
with self.assertRaises(StopIteration):
94+
next(i)
95+
self.assertEqual(profile.enable_count, 0)
96+
97+
self.assertEqual(len(timings), 2)
98+
for timing in timings.values():
99+
self.assertEqual(timing.nhits, 1)

0 commit comments

Comments
 (0)