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 | 36 | 'mercurial.pure.parsers', |
|
37 | 37 | # third-party imports should be directly imported |
|
38 | 38 | 'mercurial.thirdparty', |
|
39 | 'mercurial.thirdparty.attr', | |
|
39 | 40 | 'mercurial.thirdparty.cbor', |
|
40 | 41 | 'mercurial.thirdparty.cbor.cbor2', |
|
41 | 42 | 'mercurial.thirdparty.zope', |
@@ -36,6 +36,9 b' import traceback' | |||
|
36 | 36 | import warnings |
|
37 | 37 | import zlib |
|
38 | 38 | |
|
39 | from .thirdparty import ( | |
|
40 | attr, | |
|
41 | ) | |
|
39 | 42 | from . import ( |
|
40 | 43 | encoding, |
|
41 | 44 | error, |
@@ -2874,7 +2877,41 b' timecount = unitcountfn(' | |||
|
2874 | 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 | 2916 | def timed(func): |
|
2880 | 2917 | '''Report the execution time of a function call to stderr. |
@@ -2888,18 +2925,12 b' def timed(func):' | |||
|
2888 | 2925 | ''' |
|
2889 | 2926 | |
|
2890 | 2927 | def wrapper(*args, **kwargs): |
|
2891 | start = timer() | |
|
2892 | indent = 2 | |
|
2893 | _timenesting[0] += indent | |
|
2894 | try: | |
|
2895 | return func(*args, **kwargs) | |
|
2896 | finally: | |
|
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))) | |
|
2928 | with timedcm() as time_stats: | |
|
2929 | result = func(*args, **kwargs) | |
|
2930 | stderr = procutil.stderr | |
|
2931 | stderr.write('%s%s: %s\n' % ( | |
|
2932 | ' ' * time_stats.level * 2, func.__name__, time_stats)) | |
|
2933 | return result | |
|
2903 | 2934 | return wrapper |
|
2904 | 2935 | |
|
2905 | 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