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