1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
|
"""Profiling support for unit and performance tests."""
import testbase
import os, sys
from testlib.config import parser, post_configure
import testlib.config
__all__ = 'profiled', 'function_call_count'
all_targets = set()
profile_config = { 'targets': set(),
'report': True,
'sort': ('time', 'calls'),
'limit': None }
def profiled(target=None, **target_opts):
"""Optional function profiling.
@profiled('label')
or
@profiled('label', report=True, sort=('calls',), limit=20)
Enables profiling for a function when 'label' is targetted for
profiling. Report options can be supplied, and override the global
configuration and command-line options.
"""
import time, hotshot, hotshot.stats
# manual or automatic namespacing by module would remove conflict issues
if target is None:
target = 'anonymous_target'
elif target in all_targets:
print "Warning: redefining profile target '%s'" % target
all_targets.add(target)
filename = "%s.prof" % target
def decorator(fn):
def profiled(*args, **kw):
if (target not in profile_config['targets'] and
not target_opts.get('always', None)):
return fn(*args, **kw)
prof = hotshot.Profile(filename)
began = time.time()
prof.start()
try:
result = fn(*args, **kw)
finally:
prof.stop()
ended = time.time()
prof.close()
if not testlib.config.options.quiet:
print "Profiled target '%s', wall time: %.2f seconds" % (
target, ended - began)
report = target_opts.get('report', profile_config['report'])
if report and testlib.config.options.verbose:
sort_ = target_opts.get('sort', profile_config['sort'])
limit = target_opts.get('limit', profile_config['limit'])
print "Profile report for target '%s' (%s)" % (
target, filename)
stats = hotshot.stats.load(filename)
stats.sort_stats(*sort_)
if limit:
stats.print_stats(limit)
else:
stats.print_stats()
assert_range = target_opts.get('call_range')
if assert_range:
if isinstance(assert_range, dict):
assert_range = assert_range.get(testlib.config.db, 'default')
stats = hotshot.stats.load(filename)
assert stats.total_calls >= assert_range[0] and stats.total_calls <= assert_range[1], stats.total_calls
os.unlink(filename)
return result
try:
profiled.__name__ = fn.__name__
except:
pass
return profiled
return decorator
def function_call_count(count=None, versions={}, variance=0.05):
"""Assert a target for a test case's function call count.
count
Optional, general target function call count.
versions
Optional, a dictionary of Python version strings to counts,
for example::
{ '2.5.1': 110,
'2.5': 100,
'2.4': 150 }
The best match for the current running python will be used.
If none match, 'count' will be used as the fallback.
variance
An +/- deviation percentage, defaults to 5%.
"""
version_info = list(sys.version_info)
py_version = '.'.join([str(v) for v in sys.version_info])
while version_info:
version = '.'.join([str(v) for v in version_info])
if version in versions:
count = versions[version]
break
version_info.pop()
if count is None:
return lambda fn: fn
import hotshot, hotshot.stats
def decorator(fn):
def counted(*args, **kw):
try:
filename = "%s.prof" % fn.__name__
prof = hotshot.Profile(filename)
prof.start()
try:
result = fn(*args, **kw)
finally:
prof.stop()
prof.close()
stats = hotshot.stats.load(filename)
calls = stats.total_calls
deviance = int(count * variance)
if (calls < (count - deviance) or
calls > (count + deviance)):
raise AssertionError(
"Function call count %s not within %s%% "
"of expected %s. (Python version %s)" % (
calls, variance, count, py_version))
return result
finally:
if os.path.exists(filename):
os.unlink(filename)
try:
counted.__name__ = fn.__name__
except:
pass
return counted
return decorator
|