Skip to content

Commit a4ec8c5

Browse files
author
Greg Bowler
committed
Test timer triggers
1 parent e78b339 commit a4ec8c5

4 files changed

Lines changed: 124 additions & 24 deletions

File tree

src/Loop.php

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,20 @@ class Loop {
1616
/** @var Timer[] */
1717
private array $timerList;
1818
private int $triggerCount;
19-
/** @var callable Function that delays execution by (int $milliseconds) */
19+
/** @var callable Function that delays execution by (float $seconds) */
2020
private $sleepFunction;
21+
/** @var callable Function that delivers the current time in milliseconds as a float */
22+
private $timeFunction;
2123

2224
public function __construct() {
2325
$this->timerList = [];
2426
$this->triggerCount = 0;
25-
$this->sleepFunction = "usleep";
27+
$this->sleepFunction = function(float $seconds):void {
28+
usleep((int)($seconds * 1_000_000));
29+
};
30+
$this->timeFunction = function():float {
31+
return microtime(true);
32+
};
2633
}
2734

2835
public function addTimer(Timer $timer):void {
@@ -33,6 +40,10 @@ public function setSleepFunction(callable $sleepFunction):void {
3340
$this->sleepFunction = $sleepFunction;
3441
}
3542

43+
public function setTimeFunction(callable $timeFunction):void {
44+
$this->timeFunction = $timeFunction;
45+
}
46+
3647
public function run(bool $forever = true):void {
3748
do {
3849
$numTriggered = $this->triggerNextTimers();
@@ -46,25 +57,15 @@ public function getTriggerCount():int {
4657
}
4758

4859
public function waitUntil(float $waitUntilEpoch):void {
49-
$epoch = microtime(true);
60+
$epoch = call_user_func($this->timeFunction);
5061
$diff = $waitUntilEpoch - $epoch;
5162
if($diff <= 0) {
5263
return;
5364
}
5465

55-
call_user_func(
56-
$this->sleepFunction,
57-
$diff * 1_000_000
58-
);
66+
call_user_func($this->sleepFunction, $diff);
5967
}
6068

61-
// /** return array[] */
62-
// public function getReadyTimers(array $epochList):array {
63-
// $now = microtime(true);
64-
// $epochList = array_filter($epochList, fn($a) => $a[0] <= $now);
65-
// return $epochList;
66-
// }
67-
6869
private function triggerNextTimers():int {
6970
$timerOrder = new TimerOrder($this->timerList);
7071

src/Timer/Timer.php

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,22 @@ abstract class Timer {
1313
protected array $triggerTimeQueue;
1414
/** @var callable[] */
1515
protected array $callbackList;
16+
/** @var callable Function that delivers the current time in milliseconds as a float */
17+
private $timeFunction;
1618

1719
public function __construct() {
1820
$this->triggerTimeQueue = [];
1921
$this->callbackList = [];
22+
$this->timeFunction = fn() => microtime(true);
2023
}
2124

22-
// public function addCallback(callable $callback):void {
23-
// $this->callbackList[] = $callback;
24-
// }
25+
public function setTimeFunction(callable $callable):void {
26+
$this->timeFunction = $callable;
27+
}
28+
29+
public function addCallback(callable $callback):void {
30+
$this->callbackList[] = $callback;
31+
}
2532

2633
public function isScheduled():bool {
2734
return !empty($this->triggerTimeQueue);
@@ -36,7 +43,7 @@ public function getNextRunTime():?float {
3643
* doesn't occur (is was not due).
3744
*/
3845
public function tick():bool {
39-
$now = microtime(true);
46+
$now = call_user_func($this->timeFunction);
4047

4148
do {
4249
$triggerTime = $this->triggerTimeQueue[0] ?? null;

test/phpunit/LoopTest.php

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,18 @@ public function testRunWithNoTimer() {
1414

1515
public function testWaitUntil() {
1616
$actualDelay = null;
17+
$epoch = 1000;
1718

1819
$sut = new Loop();
20+
$sut->setTimeFunction(fn() => $epoch);
1921
$sut->setSleepFunction(function(int $milliseconds) use (&$actualDelay) {
2022
$actualDelay = $milliseconds;
2123
});
2224

23-
$epoch = microtime(true);
2425
$epochPlus5s = $epoch + 5;
2526
$sut->waitUntil($epochPlus5s);
26-
self::assertEquals(
27-
round(5_000_000 / 100),
28-
// Check that the delayed time is within a threshold of 1/10,000 of a second:
29-
round($actualDelay / 100)
30-
);
27+
28+
self::assertEquals($epochPlus5s - $epoch, $actualDelay);
3129
}
3230

3331
public function testWaitUntilNegative() {

test/phpunit/Timer/IndividualTimerTest.php

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,98 @@ public function testTickWithPastTime() {
2828
$sut = new IndividualTimer(microtime(true) - 1);
2929
self::assertTrue($sut->tick());
3030
}
31+
32+
public function testIsScheduledFalseAfterRunning() {
33+
$sut = new IndividualTimer(microtime(true));
34+
self::assertTrue($sut->isScheduled());
35+
$sut->tick();
36+
self::assertFalse($sut->isScheduled());
37+
}
38+
39+
public function testIsScheduledTrueAfterRunningMultipleScheduled() {
40+
$sut = new IndividualTimer(microtime(true) + 1);
41+
$sut->addTriggerTime(microtime(true) + 2);
42+
$sut->addTriggerTime(microtime(true) + 3);
43+
self::assertTrue($sut->isScheduled());
44+
$sut->tick();
45+
$sut->tick();
46+
$sut->tick();
47+
// This should always remain true while the current epoch is earlier than the scheduled time(s).
48+
self::assertTrue($sut->isScheduled());
49+
}
50+
51+
public function testIsScheduledFalseAfterRunningMultipleScheduledAndTimeAdvances() {
52+
$epoch = 1000;
53+
54+
$sut = new IndividualTimer(1001);
55+
$sut->setTimeFunction(function() use(&$epoch) { return ++$epoch; });
56+
$sut->addTriggerTime(1002);
57+
$sut->addTriggerTime(1003);
58+
self::assertTrue($sut->isScheduled());
59+
$sut->tick();
60+
self::assertTrue($sut->isScheduled());
61+
$sut->tick();
62+
self::assertTrue($sut->isScheduled());
63+
$sut->tick();
64+
// After ticking three times, the internal epoch should advance past the point of the last trigger.
65+
self::assertFalse($sut->isScheduled());
66+
}
67+
68+
public function testAddCallbackNotTriggeredInFuture() {
69+
$callbackCount = 0;
70+
71+
$epoch = 1000;
72+
$sut = new IndividualTimer(1001);
73+
$sut->setTimeFunction(fn() => $epoch);
74+
$sut->addCallback(function() use(&$callbackCount) {
75+
$callbackCount++;
76+
});
77+
$sut->tick();
78+
self::assertEquals(0, $callbackCount);
79+
}
80+
81+
public function testAddCallbackTriggeredInPast() {
82+
$callbackCount = 0;
83+
84+
$epoch = 1000;
85+
$sut = new IndividualTimer(999);
86+
$sut->setTimeFunction(fn() => $epoch);
87+
$sut->addCallback(function() use(&$callbackCount) {
88+
$callbackCount++;
89+
});
90+
$sut->tick();
91+
self::assertEquals(1, $callbackCount);
92+
}
93+
94+
public function testAddCallbackTriggeredAtCorrectTime() {
95+
$callbackCount = 0;
96+
97+
$epoch = 1000;
98+
$sut = new IndividualTimer($epoch);
99+
$sut->addTriggerTime($epoch + 1);
100+
$sut->addTriggerTime($epoch + 2);
101+
$sut->addTriggerTime($epoch + 5);
102+
$sut->setTimeFunction(function() use(&$epoch) {
103+
return $epoch++;
104+
});
105+
106+
$sut->addCallback(function() use(&$callbackCount) {
107+
$callbackCount++;
108+
});
109+
110+
$sut->tick(); // 1000
111+
self::assertEquals(1, $callbackCount);
112+
$sut->tick(); // 1001
113+
self::assertEquals(2, $callbackCount);
114+
$sut->tick(); // 1002
115+
self::assertEquals(3, $callbackCount);
116+
$sut->tick(); // 1003
117+
self::assertEquals(3, $callbackCount);
118+
$sut->tick(); // 1004
119+
self::assertEquals(3, $callbackCount);
120+
$sut->tick(); // 1005
121+
self::assertEquals(4, $callbackCount);
122+
$sut->tick(); // 1006
123+
self::assertEquals(4, $callbackCount);
124+
}
31125
}

0 commit comments

Comments
 (0)