Skip to content

Commit 085afe0

Browse files
committed
Add registry, execution order tests and InvalidWatcherException handling
1 parent cca8008 commit 085afe0

File tree

1 file changed

+257
-32
lines changed

1 file changed

+257
-32
lines changed

src/Test.php

Lines changed: 257 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -46,24 +46,29 @@ function testLoopRunsCanBeConsecutiveAndNested()
4646
{
4747
$this->expectOutputString("123456");
4848
$this->start(function (Driver $loop) {
49-
$loop->defer(function() {
50-
echo 1;
49+
$loop->stop();
50+
$loop->defer(function() use (&$run) {
51+
echo $run = 1;
5152
});
5253
$loop->run();
54+
if (!$run) {
55+
$this->fail("A loop stop before a run must not impact that run");
56+
}
5357
$loop->defer(function() use ($loop) {
5458
$loop->run();
5559
echo 4;
5660
$loop->defer(function() use ($loop) {
5761
echo 6;
5862
$loop->stop();
5963
$loop->defer(function() {
60-
$this->fail("A loop stopped at all levels must not execute further deferreds");
64+
$this->fail("A loop stopped at all levels must not execute further defers");
6165
});
6266
});
6367
});
6468
$loop->defer(function() use ($loop) {
6569
echo 2;
6670
$loop->stop();
71+
$loop->stop();
6772
$loop->defer(function() use ($loop) {
6873
echo 5;
6974
});
@@ -92,7 +97,7 @@ function testWatcherUnrefRerefRunResult()
9297
$invoked = true;
9398
});
9499
$loop->unreference($watcher);
95-
$loop->reference($watcher);
100+
$loop->unreference($watcher);
96101
$loop->reference($watcher);
97102
});
98103
$this->assertTrue($invoked);
@@ -195,9 +200,7 @@ function provideRegistrationArgs()
195200
return $args;
196201
}
197202

198-
/**
199-
* @dataProvider provideRegistrationArgs
200-
*/
203+
/** @dataProvider provideRegistrationArgs */
201204
function testDisableWithConsecutiveCancel($type, $args)
202205
{
203206
if ($type === "onSignal") {
@@ -214,9 +217,7 @@ function testDisableWithConsecutiveCancel($type, $args)
214217
});
215218
}
216219

217-
/**
218-
* @dataProvider provideRegistrationArgs
219-
*/
220+
/** @dataProvider provideRegistrationArgs */
220221
function testWatcherReferenceInfo($type, $args)
221222
{
222223
if ($type === "onSignal") {
@@ -273,8 +274,6 @@ function testWatcherReferenceInfo($type, $args)
273274
// unreference() should just increment unreferenced count
274275
$watcherId2 = \call_user_func_array($func, $args);
275276
$loop->unreference($watcherId2);
276-
$loop->unreference($watcherId2);
277-
$loop->unreference($watcherId2);
278277
$info = $loop->info();
279278
$expected = ["enabled" => 2, "disabled" => 0];
280279
$this->assertSame($expected, $info[$type]);
@@ -285,9 +284,7 @@ function testWatcherReferenceInfo($type, $args)
285284
$loop->cancel($watcherId2);
286285
}
287286

288-
/**
289-
* @dataProvider provideRegistrationArgs
290-
*/
287+
/** @dataProvider provideRegistrationArgs */
291288
function testWatcherRegistrationAndCancellationInfo($type, $args)
292289
{
293290
if ($type === "onSignal") {
@@ -347,9 +344,7 @@ function testWatcherRegistrationAndCancellationInfo($type, $args)
347344
$this->assertSame($expected, $info[$type]);
348345
}
349346

350-
/**
351-
* @dataProvider provideRegistrationArgs
352-
*/
347+
/** @dataProvider provideRegistrationArgs */
353348
function testNoMemoryLeak($type, $args)
354349
{
355350
if ($type === "onSignal") {
@@ -463,15 +458,144 @@ function testNoMemoryLeak($type, $args)
463458
});
464459
}
465460

466-
/**
467-
* @expectedException \LogicException
468-
*/
469-
function testSuccessOnEnableNonexistentWatcher()
461+
function testExecutionOrderGuarantees()
462+
{
463+
$this->expectOutputString("01 02 03 04 05 05 10 11 12 13 13 20 21 22 23 ");
464+
$this->start(function(Driver $loop) use (&$ticks) {
465+
$f = function() use ($loop) {
466+
$args = func_get_args();
467+
return function($watcherId) use ($loop, &$args) {
468+
if (!$args) {
469+
$this->fail("Watcher callback called too often");
470+
}
471+
$loop->cancel($watcherId);
472+
echo array_shift($args) . array_shift($args), " ";
473+
};
474+
};
475+
$dep = function($i, $fn) use ($loop, &$max, &$cur) {
476+
$max || $max = new \SplObjectStorage;
477+
$cur || $cur = new \SplObjectStorage;
478+
$max[$fn] = max(isset($max[$fn]) ? $max[$fn] : 0, $i);
479+
$cur[$fn] = 0;
480+
return function($watcherId) use ($loop, $i, $fn, &$max, &$cur) {
481+
$this->assertSame($i, $cur[$fn]);
482+
if ($cur[$fn] == $max[$fn]) {
483+
$fn($watcherId);
484+
} else {
485+
$loop->cancel($watcherId);
486+
}
487+
$cur[$fn] = $cur[$fn] + 1;
488+
};
489+
};
490+
491+
$loop->onWritable(STDIN, $dep(0, $writ = $f(0, 5)));
492+
$writ1 = $loop->onWritable(STDIN, $dep(1, $writ));
493+
$writ2 = $loop->onWritable(STDIN, $dep(3, $writ));
494+
495+
$loop->delay($msDelay = 0, $dep(0, $del = $f(0, 5)));
496+
$del1 = $loop->delay($msDelay = 0, $dep(1, $del));
497+
$del2 = $loop->delay($msDelay = 0, $dep(3, $del));
498+
$del3 = $loop->delay($msDelay = 0, $f());
499+
$del4 = $loop->delay($msDelay = 0, $f(1, 3));
500+
$loop->cancel($del3);
501+
$loop->disable($del1);
502+
$loop->disable($del2);
503+
504+
$writ3 = $loop->onWritable(STDIN, $f());
505+
$loop->cancel($writ3);
506+
$loop->disable($writ1);
507+
$loop->disable($writ2);
508+
$loop->enable($writ1);
509+
$writ4 = $loop->onWritable(STDIN, $f(1, 3));
510+
$loop->onWritable(STDIN, $dep(2, $writ));
511+
$loop->enable($writ2);
512+
$loop->disable($writ4);
513+
$loop->defer(function() use ($loop, $writ4) {
514+
$loop->enable($writ4);
515+
});
516+
517+
$loop->enable($del1);
518+
$loop->delay($msDelay = 0, $dep(2, $del));
519+
$loop->enable($del2);
520+
$loop->disable($del4);
521+
$loop->defer(function() use ($loop, $del4) {
522+
$loop->enable($del4);
523+
});
524+
525+
$loop->delay($msDelay = 5, $f(2, 0));
526+
$loop->repeat($msDelay = 5, $f(2, 1));
527+
$rep1 = $loop->repeat($msDelay = 5, $f(2, 3));
528+
$loop->disable($rep1);
529+
$loop->delay($msDelay = 5, $f(2, 2));
530+
$loop->enable($rep1);
531+
532+
$loop->defer($f(0, 1));
533+
$def1 = $loop->defer($f(0, 3));
534+
$def2 = $loop->defer($f(1, 1));
535+
$def3 = $loop->defer($f());
536+
$loop->defer($f(0, 2));
537+
$loop->disable($def1);
538+
$loop->cancel($def3);
539+
$loop->enable($def1);
540+
$loop->defer(function() use ($loop, $def2, $del4, $f) {
541+
$tick = $f(0, 4);
542+
$tick("invalid");
543+
$loop->defer($f(1, 0));
544+
$loop->enable($def2);
545+
$loop->defer($f(1, 2));
546+
});
547+
$loop->disable($def2);
548+
});
549+
}
550+
551+
/** @depends testSignalCapability */
552+
function testSignalExecutionOrder()
553+
{
554+
if (!\extension_loaded("posix")) {
555+
$this->markTestSkipped("ext/posix required to test signal handlers");
556+
}
557+
558+
$this->expectOutputString("123456");
559+
$this->start(function(Driver $loop) {
560+
$f = function($i) use ($loop) {
561+
return function($watcherId) use ($loop, $i) {
562+
$loop->cancel($watcherId);
563+
echo $i;
564+
};
565+
};
566+
567+
$loop->defer($f(1));
568+
$loop->onSignal(SIGUSR1, $f(2));
569+
$sig1 = $loop->onSignal(SIGUSR1, $f(4));
570+
$sig2 = $loop->onSignal(SIGUSR1, $f(6));
571+
$sig3 = $loop->onSignal(SIGUSR1, $f(" FAIL - MUST NOT BE CALLED "));
572+
$loop->disable($sig1);
573+
$loop->onSignal(SIGUSR1, $f(3));
574+
$loop->disable($sig2);
575+
$loop->enable($sig1);
576+
$loop->cancel($sig3);
577+
$loop->onSignal(SIGUSR1, $f(5));
578+
$loop->defer(function() use ($loop, $sig2) {
579+
$loop->enable($sig2);
580+
});
581+
582+
$loop->delay($msDelay = 1, function() use ($loop) {
583+
\posix_kill(\getmypid(), \SIGUSR1);
584+
$loop->delay($msDelay = 10, function() use ($loop) {
585+
$loop->stop();
586+
});
587+
});
588+
});
589+
}
590+
591+
/** @expectedException InvalidWatcherException */
592+
function testExceptionOnEnableNonexistentWatcher()
470593
{
471594
$this->loop->enable("nonexistentWatcher");
472595
}
473596

474-
function testSuccessOnDisableNonexistentWatcher()
597+
/** @expectedException InvalidWatcherException */
598+
function testExceptionOnDisableNonexistentWatcher()
475599
{
476600
$this->loop->disable("nonexistentWatcher");
477601
}
@@ -481,6 +605,78 @@ function testSuccessOnCancelNonexistentWatcher()
481605
$this->loop->cancel("nonexistentWatcher");
482606
}
483607

608+
/** @expectedException InvalidWatcherException */
609+
function testExceptionOnReferenceNonexistentWatcher()
610+
{
611+
$this->loop->reference("nonexistentWatcher");
612+
}
613+
614+
/** @expectedException InvalidWatcherException */
615+
function testExceptionOnUnreferenceNonexistentWatcher()
616+
{
617+
$this->loop->unreference("nonexistentWatcher");
618+
}
619+
620+
/** @expectedException InvalidWatcherException */
621+
function testWatcherInvalidityOnDefer() {
622+
$this->start(function(Driver $loop) {
623+
$loop->defer(function($watcher) use ($loop) {
624+
$loop->disable($watcher);
625+
});
626+
});
627+
}
628+
629+
/** @expectedException InvalidWatcherException */
630+
function testWatcherInvalidityOnDelay() {
631+
$this->start(function(Driver $loop) {
632+
$loop->delay($msDelay = 0, function($watcher) use ($loop) {
633+
$loop->disable($watcher);
634+
});
635+
});
636+
}
637+
638+
function testEventsNotExecutedInSameTickAsEnabled()
639+
{
640+
$this->start(function (Driver $loop) {
641+
$loop->defer(function() use ($loop) {
642+
$loop->defer(function() use ($loop, &$diswatchers, &$watchers) {
643+
foreach ($watchers as $watcher) {
644+
$loop->cancel($watcher);
645+
}
646+
foreach ($diswatchers as $watcher) {
647+
$loop->disable($watcher);
648+
$loop->enable($watcher);
649+
}
650+
$loop->defer(function() use ($loop, $diswatchers) {
651+
foreach ($diswatchers as $watcher) {
652+
$loop->disable($watcher);
653+
}
654+
$loop->defer(function() use ($loop, $diswatchers) {
655+
foreach ($diswatchers as $watcher) {
656+
$loop->enable($watcher);
657+
}
658+
$loop->defer(function() use ($loop, $diswatchers) {
659+
foreach ($diswatchers as $watcher) {
660+
$loop->cancel($watcher);
661+
}
662+
});
663+
});
664+
});
665+
});
666+
667+
$f = function() use ($loop) {
668+
$watchers[] = $loop->defer([$this, "fail"]);
669+
$watchers[] = $loop->delay($msDelay = 0, [$this, "fail"]);
670+
$watchers[] = $loop->repeat($msDelay = 0, [$this, "fail"]);
671+
$watchers[] = $loop->onWritable(STDIN, [$this, "fail"]);
672+
return $watchers;
673+
};
674+
$watchers = $f();
675+
$diswatchers = $f();
676+
});
677+
});
678+
}
679+
484680
function testEnablingWatcherAllowsSubsequentInvocation()
485681
{
486682
$loop = $this->loop;
@@ -687,9 +883,7 @@ function testLoopException()
687883
});
688884
}
689885

690-
/**
691-
* @depends testSignalCapability
692-
*/
886+
/** @depends testSignalCapability */
693887
function testOnSignalWatcher()
694888
{
695889
if (!\extension_loaded("posix")) {
@@ -712,9 +906,7 @@ function testOnSignalWatcher()
712906
});
713907
}
714908

715-
/**
716-
* @depends testSignalCapability
717-
*/
909+
/** @depends testSignalCapability */
718910
function testInitiallyDisabledOnSignalWatcher()
719911
{
720912
if (!\extension_loaded("posix")) {
@@ -793,9 +985,7 @@ function testInitiallyDisabledWriteWatcherIsTriggeredOnceEnabled()
793985
});
794986
}
795987

796-
/**
797-
* @expectedException \RuntimeException
798-
*/
988+
/** @expectedException \RuntimeException */
799989
function testStreamWatcherDoesntSwallowExceptions()
800990
{
801991
$this->start(function(Driver $loop) {
@@ -874,4 +1064,39 @@ function testOptionalCallbackDataPassedOnInvocation()
8741064
$this->assertTrue($callbackData->repeat);
8751065
$this->assertTrue($callbackData->onWritable);
8761066
}
1067+
1068+
// implementations SHOULD use Interop\Async\Loop\Registry trait, but are not forced to, hence test it here again
1069+
function testRegistry() {
1070+
$this->assertNull($this->loop->fetchState("foo"));
1071+
$this->loop->storeState("foo", NAN);
1072+
$this->assertTrue(is_nan($this->loop->fetchState("foo")));
1073+
$this->loop->storeState("foo", "1");
1074+
$this->assertNull($this->loop->fetchState("bar"));
1075+
$this->loop->storeState("baz", -0.0);
1076+
// running must not affect state
1077+
$this->loop->defer([$this->loop, "stop"]);
1078+
$this->loop->run();
1079+
$this->assertSame(-INF, @1/$this->loop->fetchState("baz"));
1080+
$this->assertSame("1", $this->loop->fetchState("foo"));
1081+
}
1082+
1083+
/** @dataProvider provideRegistryValues */
1084+
function testRegistryValues($val) {
1085+
$this->loop->storeState("foo", $val);
1086+
$this->assertSame($val, $this->loop->fetchState("foo"));
1087+
}
1088+
1089+
function provideRegistryValues() {
1090+
return [
1091+
["string"],
1092+
[0],
1093+
[PHP_INT_MIN],
1094+
[-1.0],
1095+
[true],
1096+
[false],
1097+
[[]],
1098+
[null],
1099+
[new \StdClass],
1100+
];
1101+
}
8771102
}

0 commit comments

Comments
 (0)