typing: add trivial type hints to the convert extension's common modules
This started as ensuring that the `encoding` and `orig_encoding` attributes has
a type other than `Any`, so pytype can catch problems where it needs to be str
for stdlib encoding and decoding. It turns out that adding the hint in
`mercurial.encoding` is what was needed, but I picked a bunch of low hanging
fruit while here. There's definitely more to do, and I see a problem where
`shlex.shlex` is being fed bytes instead of str, but there are not enough type
hints yet to make pytype notice.
# unit tests for mercuril.util utilities
import contextlib
import io
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.__attrs_attrs__.start.default, 'factory')
@contextlib.contextmanager
def capturestderr():
"""Replace utils.procutil.stderr with an io.BytesIO instance
The instance is made available as the return value of __enter__.
This contextmanager is reentrant.
"""
orig = utils.procutil.stderr
utils.procutil.stderr = io.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>')
self.assertEqual(bytes(stats), b'<unknown>')
stats.elapsed = 12.34
self.assertEqual(str(stats), pycompat.sysstr(util.timecount(12.34)))
self.assertEqual(bytes(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('pass') 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('outer') as outer_stats:
with util.timedcm('inner') 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('exceptional') 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__)