Mercurial > public > mercurial-scm > hg-stable
diff tests/test-util.py @ 38837:8751d1e2a7ff
util: create a context manager to handle timing
The context manager is pulled out of the timed decorator function, and
refactored to provide a stats instance, with added tests.
author | Martijn Pieters <mj@zopatista.com> |
---|---|
date | Wed, 01 Aug 2018 16:05:41 +0200 |
parents | |
children | 9d49bb117dde |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-util.py Wed Aug 01 16:05:41 2018 +0200 @@ -0,0 +1,135 @@ +# unit tests for mercuril.util utilities +from __future__ import absolute_import + +import contextlib +import itertools +import unittest + +from mercurial import pycompat, util, utils + +@contextlib.contextmanager +def mocktimer(incr=0.1, *additional_targets): + """Replaces util.timer and additional_targets with a mock + + The timer starts at 0. On each call the time incremented by the value + of incr. If incr is an iterable, then the time is incremented by the + next value from that iterable, looping in a cycle when reaching the end. + + additional_targets must be a sequence of (object, attribute_name) tuples; + the mock is set with setattr(object, attribute_name, mock). + + """ + time = [0] + try: + incr = itertools.cycle(incr) + except TypeError: + incr = itertools.repeat(incr) + + def timer(): + time[0] += next(incr) + return time[0] + + # record original values + orig = util.timer + additional_origs = [(o, a, getattr(o, a)) for o, a in additional_targets] + + # mock out targets + util.timer = timer + for obj, attr in additional_targets: + setattr(obj, attr, timer) + + try: + yield + finally: + # restore originals + util.timer = orig + for args in additional_origs: + setattr(*args) + +# attr.s default factory for util.timedstats.start binds the timer we +# need to mock out. +_start_default = (util.timedcmstats.start.default, 'factory') + +@contextlib.contextmanager +def capturestderr(): + """Replace utils.procutil.stderr with a pycompat.bytesio instance + + The instance is made available as the return value of __enter__. + + This contextmanager is reentrant. + + """ + orig = utils.procutil.stderr + utils.procutil.stderr = pycompat.bytesio() + try: + yield utils.procutil.stderr + finally: + utils.procutil.stderr = orig + +class timedtests(unittest.TestCase): + def testtimedcmstatsstr(self): + stats = util.timedcmstats() + self.assertEqual(str(stats), '<unknown>') + stats.elapsed = 12.34 + self.assertEqual(str(stats), util.timecount(12.34)) + + def testtimedcmcleanexit(self): + # timestamps 1, 4, elapsed time of 4 - 1 = 3 + with mocktimer([1, 3], _start_default): + with util.timedcm() as stats: + # actual context doesn't matter + pass + + self.assertEqual(stats.start, 1) + self.assertEqual(stats.elapsed, 3) + self.assertEqual(stats.level, 1) + + def testtimedcmnested(self): + # timestamps 1, 3, 6, 10, elapsed times of 6 - 3 = 3 and 10 - 1 = 9 + with mocktimer([1, 2, 3, 4], _start_default): + with util.timedcm() as outer_stats: + with util.timedcm() as inner_stats: + # actual context doesn't matter + pass + + self.assertEqual(outer_stats.start, 1) + self.assertEqual(outer_stats.elapsed, 9) + self.assertEqual(outer_stats.level, 1) + + self.assertEqual(inner_stats.start, 3) + self.assertEqual(inner_stats.elapsed, 3) + self.assertEqual(inner_stats.level, 2) + + def testtimedcmexception(self): + # timestamps 1, 4, elapsed time of 4 - 1 = 3 + with mocktimer([1, 3], _start_default): + try: + with util.timedcm() as stats: + raise ValueError() + except ValueError: + pass + + self.assertEqual(stats.start, 1) + self.assertEqual(stats.elapsed, 3) + self.assertEqual(stats.level, 1) + + def testtimeddecorator(self): + @util.timed + def testfunc(callcount=1): + callcount -= 1 + if callcount: + testfunc(callcount) + + # timestamps 1, 2, 3, 4, elapsed time of 3 - 2 = 1 and 4 - 1 = 3 + with mocktimer(1, _start_default): + with capturestderr() as out: + testfunc(2) + + self.assertEqual(out.getvalue(), ( + b' testfunc: 1.000 s\n' + b' testfunc: 3.000 s\n' + )) + +if __name__ == '__main__': + import silenttestrunner + silenttestrunner.main(__name__)