##// END OF EJS Templates
util: create a context manager to handle timing...
Martijn Pieters -
r38833:8751d1e2 default
parent child Browse files
Show More
@@ -0,0 +1,135 b''
1 # unit tests for mercuril.util utilities
2 from __future__ import absolute_import
3
4 import contextlib
5 import itertools
6 import unittest
7
8 from mercurial import pycompat, util, utils
9
10 @contextlib.contextmanager
11 def mocktimer(incr=0.1, *additional_targets):
12 """Replaces util.timer and additional_targets with a mock
13
14 The timer starts at 0. On each call the time incremented by the value
15 of incr. If incr is an iterable, then the time is incremented by the
16 next value from that iterable, looping in a cycle when reaching the end.
17
18 additional_targets must be a sequence of (object, attribute_name) tuples;
19 the mock is set with setattr(object, attribute_name, mock).
20
21 """
22 time = [0]
23 try:
24 incr = itertools.cycle(incr)
25 except TypeError:
26 incr = itertools.repeat(incr)
27
28 def timer():
29 time[0] += next(incr)
30 return time[0]
31
32 # record original values
33 orig = util.timer
34 additional_origs = [(o, a, getattr(o, a)) for o, a in additional_targets]
35
36 # mock out targets
37 util.timer = timer
38 for obj, attr in additional_targets:
39 setattr(obj, attr, timer)
40
41 try:
42 yield
43 finally:
44 # restore originals
45 util.timer = orig
46 for args in additional_origs:
47 setattr(*args)
48
49 # attr.s default factory for util.timedstats.start binds the timer we
50 # need to mock out.
51 _start_default = (util.timedcmstats.start.default, 'factory')
52
53 @contextlib.contextmanager
54 def capturestderr():
55 """Replace utils.procutil.stderr with a pycompat.bytesio instance
56
57 The instance is made available as the return value of __enter__.
58
59 This contextmanager is reentrant.
60
61 """
62 orig = utils.procutil.stderr
63 utils.procutil.stderr = pycompat.bytesio()
64 try:
65 yield utils.procutil.stderr
66 finally:
67 utils.procutil.stderr = orig
68
69 class timedtests(unittest.TestCase):
70 def testtimedcmstatsstr(self):
71 stats = util.timedcmstats()
72 self.assertEqual(str(stats), '<unknown>')
73 stats.elapsed = 12.34
74 self.assertEqual(str(stats), util.timecount(12.34))
75
76 def testtimedcmcleanexit(self):
77 # timestamps 1, 4, elapsed time of 4 - 1 = 3
78 with mocktimer([1, 3], _start_default):
79 with util.timedcm() as stats:
80 # actual context doesn't matter
81 pass
82
83 self.assertEqual(stats.start, 1)
84 self.assertEqual(stats.elapsed, 3)
85 self.assertEqual(stats.level, 1)
86
87 def testtimedcmnested(self):
88 # timestamps 1, 3, 6, 10, elapsed times of 6 - 3 = 3 and 10 - 1 = 9
89 with mocktimer([1, 2, 3, 4], _start_default):
90 with util.timedcm() as outer_stats:
91 with util.timedcm() as inner_stats:
92 # actual context doesn't matter
93 pass
94
95 self.assertEqual(outer_stats.start, 1)
96 self.assertEqual(outer_stats.elapsed, 9)
97 self.assertEqual(outer_stats.level, 1)
98
99 self.assertEqual(inner_stats.start, 3)
100 self.assertEqual(inner_stats.elapsed, 3)
101 self.assertEqual(inner_stats.level, 2)
102
103 def testtimedcmexception(self):
104 # timestamps 1, 4, elapsed time of 4 - 1 = 3
105 with mocktimer([1, 3], _start_default):
106 try:
107 with util.timedcm() as stats:
108 raise ValueError()
109 except ValueError:
110 pass
111
112 self.assertEqual(stats.start, 1)
113 self.assertEqual(stats.elapsed, 3)
114 self.assertEqual(stats.level, 1)
115
116 def testtimeddecorator(self):
117 @util.timed
118 def testfunc(callcount=1):
119 callcount -= 1
120 if callcount:
121 testfunc(callcount)
122
123 # timestamps 1, 2, 3, 4, elapsed time of 3 - 2 = 1 and 4 - 1 = 3
124 with mocktimer(1, _start_default):
125 with capturestderr() as out:
126 testfunc(2)
127
128 self.assertEqual(out.getvalue(), (
129 b' testfunc: 1.000 s\n'
130 b' testfunc: 3.000 s\n'
131 ))
132
133 if __name__ == '__main__':
134 import silenttestrunner
135 silenttestrunner.main(__name__)
@@ -36,6 +36,7 b' allowsymbolimports = ('
36 'mercurial.pure.parsers',
36 'mercurial.pure.parsers',
37 # third-party imports should be directly imported
37 # third-party imports should be directly imported
38 'mercurial.thirdparty',
38 'mercurial.thirdparty',
39 'mercurial.thirdparty.attr',
39 'mercurial.thirdparty.cbor',
40 'mercurial.thirdparty.cbor',
40 'mercurial.thirdparty.cbor.cbor2',
41 'mercurial.thirdparty.cbor.cbor2',
41 'mercurial.thirdparty.zope',
42 'mercurial.thirdparty.zope',
@@ -36,6 +36,9 b' import traceback'
36 import warnings
36 import warnings
37 import zlib
37 import zlib
38
38
39 from .thirdparty import (
40 attr,
41 )
39 from . import (
42 from . import (
40 encoding,
43 encoding,
41 error,
44 error,
@@ -2874,7 +2877,41 b' timecount = unitcountfn('
2874 (1, 0.000000001, _('%.3f ns')),
2877 (1, 0.000000001, _('%.3f ns')),
2875 )
2878 )
2876
2879
2877 _timenesting = [0]
2880 @attr.s
2881 class timedcmstats(object):
2882 """Stats information produced by the timedcm context manager on entering."""
2883
2884 # the starting value of the timer as a float (meaning and resulution is
2885 # platform dependent, see util.timer)
2886 start = attr.ib(default=attr.Factory(lambda: timer()))
2887 # the number of seconds as a floating point value; starts at 0, updated when
2888 # the context is exited.
2889 elapsed = attr.ib(default=0)
2890 # the number of nested timedcm context managers.
2891 level = attr.ib(default=1)
2892
2893 def __str__(self):
2894 return timecount(self.elapsed) if self.elapsed else '<unknown>'
2895
2896 @contextlib.contextmanager
2897 def timedcm():
2898 """A context manager that produces timing information for a given context.
2899
2900 On entering a timedcmstats instance is produced.
2901
2902 This context manager is reentrant.
2903
2904 """
2905 # track nested context managers
2906 timedcm._nested += 1
2907 timing_stats = timedcmstats(level=timedcm._nested)
2908 try:
2909 yield timing_stats
2910 finally:
2911 timing_stats.elapsed = timer() - timing_stats.start
2912 timedcm._nested -= 1
2913
2914 timedcm._nested = 0
2878
2915
2879 def timed(func):
2916 def timed(func):
2880 '''Report the execution time of a function call to stderr.
2917 '''Report the execution time of a function call to stderr.
@@ -2888,18 +2925,12 b' def timed(func):'
2888 '''
2925 '''
2889
2926
2890 def wrapper(*args, **kwargs):
2927 def wrapper(*args, **kwargs):
2891 start = timer()
2928 with timedcm() as time_stats:
2892 indent = 2
2929 result = func(*args, **kwargs)
2893 _timenesting[0] += indent
2930 stderr = procutil.stderr
2894 try:
2931 stderr.write('%s%s: %s\n' % (
2895 return func(*args, **kwargs)
2932 ' ' * time_stats.level * 2, func.__name__, time_stats))
2896 finally:
2933 return result
2897 elapsed = timer() - start
2898 _timenesting[0] -= indent
2899 stderr = procutil.stderr
2900 stderr.write('%s%s: %s\n' %
2901 (' ' * _timenesting[0], func.__name__,
2902 timecount(elapsed)))
2903 return wrapper
2934 return wrapper
2904
2935
2905 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
2936 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
General Comments 0
You need to be logged in to leave comments. Login now