Commit 80859e38 authored by Charles Ferguson's avatar Charles Ferguson
Browse files

Add more tests for the Timer, and some fixes.

There were some bits that were wrong, which were found when I started
writing tests for the problems.
parent 9654b7d8
......@@ -8,6 +8,8 @@ import new
import signal
import time
from functools import total_ordering
# The smallest amount of time we may use for alarms, which must be greater than 0.
MINIMUM_ALARM_INTERVAL = 0.001
......@@ -17,6 +19,7 @@ class AlarmError(Exception):
pass
@total_ordering
class AlarmInstance(object):
"""
A record of the point at which an alarm should be triggered.
......@@ -32,17 +35,25 @@ class AlarmInstance(object):
def reset(self, seconds):
if seconds is not None:
self.seconds = seconds
self.end_time = time.time() + seconds
self.end_time = time.time() + self.seconds
return self
def __repr__(self):
left = self.end_time - time.time()
return "AlarmInstance(in %.2f seconds)" % (left,)
if left <= 0:
return "AlarmInstance(passed)"
else:
return "AlarmInstance(in %.2f seconds)" % (left,)
def __lt__(self, other):
if not isinstance(other, AlarmInstance):
raise TypeError('AlarmInstances can only be compared with other AlarmInstances')
return self.end_time < other.end_time
def __cmp__(self, other):
def __eq__(self, other):
if not isinstance(other, AlarmInstance):
raise TypeError('AlarmInstances can only be compared with other AlarmInstances')
return self.end_time - other.end_time
return self.end_time == other.end_time
class AlarmManager(object):
......@@ -159,6 +170,13 @@ class TimerError(Exception):
pass
class TimerTimeout(TimerError):
def __init__(self, message=None, timer=None, *args, **kwargs):
super(TimerTimeout, self).__init__(message, *args, **kwargs)
self.timer = timer
class TimerCannotStartError(Exception):
pass
......@@ -168,7 +186,7 @@ class Timer(object):
Timer, optionally with timeout handler.
If no handler is defined, we will raise an exception for this instance of the Alarm.
If no timeout is defined, we
If no timeout is defined, we only time from start() until stop().
The objects can be used directly, or as a context handler.
......@@ -225,9 +243,9 @@ class Timer(object):
self.start_time = None
self.end_time = None
if self.timeout_in_seconds:
self.exception = new.classobj('TimerError_%s' % (self.idname,), (AlarmError,), {})
self.exception = new.classobj('Timeout_%s' % (self.idname,), (TimerTimeout,), {})
else:
self.exception = None
self.exception = TimerTimeout
self.alarm = None
@property
......@@ -238,8 +256,17 @@ class Timer(object):
return elapsed
def __repr__(self):
state = 'running' if self.running else 'waiting'
return "Timer(%s, %s, %.2f of %.2f elapsed)" % (self.name, state, self.elapsed, self.timeout_in_seconds)
vals = []
vals.append('"%s"' % (self.name,))
vals.append('running' if self.running else 'waiting')
if self.running or self.elapsed > 0:
vals.append('%.2fs elapsed' % (self.elapsed,))
if self.timeout_in_seconds:
vals.append('%.2fs timeout' % (self.timeout_in_seconds,))
else:
vals.append('no timeout')
return "Timer(%s)" % (", ".join(vals),)
def __del__(self):
if getattr(self, 'alarm', None):
......@@ -251,7 +278,7 @@ class Timer(object):
self.handler(frame)
def default_handler(self, frame):
raise self.exception('Timeout of %s seconds reached' % (self.timeout_in_seconds,))
raise self.exception('Timeout of %s seconds reached' % (self.timeout_in_seconds,), self)
def reset(self, timeout):
self.stop()
......
......@@ -36,7 +36,51 @@ class Test01Import(unittest.TestCase):
import Timer # pylint: disable=redefined-outer-name
class Test02Construct(unittest.TestCase):
class Test02ConstructAlarmInstance(unittest.TestCase):
def test_001_basic(self):
_ = Timer.AlarmInstance(1, None)
def test_002_zero(self):
with self.assertRaises(ValueError):
_ = Timer.AlarmInstance(0, None)
def test_003_negative(self):
with self.assertRaises(ValueError):
_ = Timer.AlarmInstance(-1, None)
def test_010_compare_lower(self):
timer1 = Timer.AlarmInstance(1, None)
timer2 = Timer.AlarmInstance(2, None)
self.assertLess(timer1, timer2)
def test_011_compare_higher(self):
timer1 = Timer.AlarmInstance(2, None)
timer2 = Timer.AlarmInstance(1, None)
self.assertGreater(timer1, timer2)
def test_021_compare_invalid_right(self):
timer1 = Timer.AlarmInstance(2, None)
with self.assertRaises(TypeError):
_ = timer1 > 2
def test_022_compare_invalid_left(self):
timer1 = Timer.AlarmInstance(2, None)
with self.assertRaises(TypeError):
_ = 2 > timer1
def test_023_equal_invalid_right(self):
timer1 = Timer.AlarmInstance(2, None)
with self.assertRaises(TypeError):
_ = timer1 == 2
def test_024_equal_invalid_left(self):
timer1 = Timer.AlarmInstance(2, None)
with self.assertRaises(TypeError):
_ = 2 == timer1
class Test05ConstructTimer(unittest.TestCase):
def test_001_basic(self):
_ = Timer.Timer()
......@@ -96,7 +140,7 @@ class Test20Timeout(unittest.TestCase):
timer = Timer.Timer(1)
try:
timer.start()
with self.assertRaises(Timer.AlarmError):
with self.assertRaises(Timer.TimerTimeout):
time.sleep(2)
finally:
timer.stop()
......@@ -121,20 +165,216 @@ class Test20Timeout(unittest.TestCase):
finally:
timer.stop()
def test_004_reset_the_timer(self):
timer = Timer.Timer(1)
try:
timer.start()
time.sleep(0.5)
timer.reset(1)
time.sleep(0.5)
timer.stop()
finally:
timer.stop()
def test_010_dedicated_exception(self):
timer = Timer.Timer(1)
try:
timer.start()
with self.assertRaises(timer.exception):
time.sleep(2)
finally:
timer.stop()
class Test30Repr(unittest.TestCase):
def test_001_before_start(self):
timer = Timer.Timer(1)
self.assertIn('waiting', repr(timer))
self.assertNotIn('elapsed', repr(timer))
def test_002_with_name(self):
timer = Timer.Timer(name='myname')
self.assertIn('"myname"', repr(timer))
if False:
with Timer.Timer(2, name='Outer') as t1:
def test_003_without_name(self):
timer = Timer.Timer()
self.assertIn('"<unnamed>"', repr(timer))
def test_004_no_timeout(self):
timer = Timer.Timer()
self.assertIn('no timeout', repr(timer))
def test_005_timeout(self):
timer = Timer.Timer(5)
self.assertIn('5.00s timeout', repr(timer))
def test_006_elapsed(self):
timer = Timer.Timer(5)
try:
for i in range(0, 12):
print("Iteration %s (t1=%s)" % (i, t1))
t2 = Timer.Timer(5, name='Inner')
timer.start()
time.sleep(1)
self.assertIn('s elapsed', repr(timer))
finally:
timer.stop()
class Test40MultipleTimers(unittest.TestCase):
def setUp(self):
self.timers = [None] * 3
def tearDown(self):
for timer in self.timers:
if timer is not None:
timer.stop()
def test_001_ordered(self):
self.timers[0] = Timer.Timer(0.1, name='1st')
self.timers[1] = Timer.Timer(0.2, name='2nd')
self.timers[2] = Timer.Timer(0.3, name='3rd')
expected_order = ['1st', '2nd', '3rd']
actual_order = []
for i in range(3):
self.timers[i].start()
for _ in range(3):
try:
time.sleep(0.5)
except Timer.TimerTimeout as exc:
actual_order.append(exc.timer.name)
self.assertEqual(actual_order, expected_order)
def test_002_reverse_ordered(self):
self.timers[0] = Timer.Timer(0.3, name='1st')
self.timers[1] = Timer.Timer(0.2, name='2nd')
self.timers[2] = Timer.Timer(0.1, name='3rd')
expected_order = ['3rd', '2nd', '1st']
actual_order = []
for i in range(3):
self.timers[i].start()
for _ in range(3):
try:
time.sleep(0.5)
except Timer.TimerTimeout as exc:
actual_order.append(exc.timer.name)
self.assertEqual(actual_order, expected_order)
def test_003_last_is_first(self):
self.timers[0] = Timer.Timer(0.2, name='1st')
self.timers[1] = Timer.Timer(0.3, name='2nd')
self.timers[2] = Timer.Timer(0.1, name='3rd')
expected_order = ['3rd', '1st', '2nd']
actual_order = []
for i in range(3):
self.timers[i].start()
for _ in range(3):
try:
time.sleep(0.5)
except Timer.TimerTimeout as exc:
actual_order.append(exc.timer.name)
self.assertEqual(actual_order, expected_order)
class Test50Nesting(unittest.TestCase):
def setUp(self):
self.timers = [None] * 3
def tearDown(self):
for timer in self.timers:
if timer is not None:
timer.stop()
def test_001_ordered(self):
self.timers[0] = Timer.Timer(0.1, name='1st')
self.timers[1] = Timer.Timer(0.2, name='2nd')
self.timers[2] = Timer.Timer(0.3, name='3rd')
expected_order = ['1st']
actual_order = []
try:
try:
try:
for i in range(3):
self.timers[i].start()
time.sleep(0.5)
except self.timers[2].exception as exc:
actual_order.append(exc.timer.name)
finally:
self.timers[2].stop()
time.sleep(0.5)
except self.timers[1].exception as exc:
actual_order.append(exc.timer.name)
finally:
self.timers[1].stop()
time.sleep(0.5)
except self.timers[0].exception as exc:
actual_order.append(exc.timer.name)
finally:
self.timers[0].stop()
self.assertEqual(actual_order, expected_order)
def test_002_reverse_ordered(self):
self.timers[0] = Timer.Timer(0.3, name='1st')
self.timers[1] = Timer.Timer(0.2, name='2nd')
self.timers[2] = Timer.Timer(0.1, name='3rd')
expected_order = ['3rd', '2nd', '1st']
actual_order = []
try:
try:
try:
with t2:
time.sleep(10)
except t2.exception as ex:
print("Got a 't2' exception: %s" % (ex,))
except t1.exception as ex:
print("!!! Got a 't1' exception: %s" % (ex,))
for i in range(3):
self.timers[i].start()
time.sleep(0.5)
except self.timers[2].exception as exc:
actual_order.append(exc.timer.name)
finally:
self.timers[2].stop()
time.sleep(0.5)
except self.timers[1].exception as exc:
actual_order.append(exc.timer.name)
finally:
self.timers[1].stop()
time.sleep(0.5)
except self.timers[0].exception as exc:
actual_order.append(exc.timer.name)
finally:
self.timers[0].stop()
self.assertEqual(actual_order, expected_order)
class Test60ContextHandler(unittest.TestCase):
def test_001_simple(self):
with Timer.Timer(1) as timer:
self.assertTrue(timer.running)
time.sleep(0.5)
self.assertTrue(timer.running)
self.assertFalse(timer.running)
def test_002_plain_timer(self):
with Timer.Timer() as timer:
time.sleep(0.5)
self.assertFalse(timer.running)
self.assertGreater(timer.elapsed, 0.5)
if __name__ == '__main__':
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment