diff --git a/composer.json b/composer.json index e263791..27949d0 100644 --- a/composer.json +++ b/composer.json @@ -40,11 +40,8 @@ "concurrent-php/async-api": "dev-master" }, "require-dev": { - "amphp/phpunit-util": "dev-ext-async", - "react/promise": "^2", - "friendsofphp/php-cs-fixer": "^2.3", - "phpunit/phpunit": "^6.0.9", - "phpstan/phpstan": "^0.8.5" + "friendsofphp/php-cs-fixer": "^2.13", + "phpunit/phpunit": "^6.0.9" }, "autoload": { "psr-4": { diff --git a/lib/Cancellation/CancelledException.php b/lib/Cancellation/CancelledException.php deleted file mode 100644 index 6d95f8a..0000000 --- a/lib/Cancellation/CancelledException.php +++ /dev/null @@ -1,17 +0,0 @@ -throwIfRequested(); - * } - * ``` - * - * potentially multiple times, it allows writing - * - * ```php - * $token = $token ?? new NullCancellationToken; - * - * // ... - * - * $token->throwIfRequested(); - * ``` - * - * instead. - */ -final class NullToken implements Token -{ - /** @inheritdoc */ - public function subscribe(callable $callback): string - { - return "null-token"; - } - - /** @inheritdoc */ - public function unsubscribe(string $id): void - { - // nothing to do - } - - /** @inheritdoc */ - public function isRequested(): bool - { - return false; - } - - /** @inheritdoc */ - public function throwIfRequested(): void - { - // nothing to do - } -} diff --git a/lib/Cancellation/TimeoutToken.php b/lib/Cancellation/TimeoutToken.php deleted file mode 100644 index 34c0d6c..0000000 --- a/lib/Cancellation/TimeoutToken.php +++ /dev/null @@ -1,73 +0,0 @@ -token = $source->getToken(); - - $this->watcher = Loop::delay($timeout, static function () use ($source) { - $source->cancel(new TimeoutException); - }); - - Loop::unreference($this->watcher); - } - - /** - * Cancels the delay watcher. - */ - public function __destruct() - { - Loop::cancel($this->watcher); - } - - /** - * {@inheritdoc} - */ - public function subscribe(callable $callback): string - { - return $this->token->subscribe($callback); - } - - /** - * {@inheritdoc} - */ - public function unsubscribe(string $id): void - { - $this->token->unsubscribe($id); - } - - /** - * {@inheritdoc} - */ - public function isRequested(): bool - { - return $this->token->isRequested(); - } - - /** - * {@inheritdoc} - */ - public function throwIfRequested(): void - { - $this->token->throwIfRequested(); - } -} diff --git a/lib/Cancellation/Token.php b/lib/Cancellation/Token.php deleted file mode 100644 index a0ddab7..0000000 --- a/lib/Cancellation/Token.php +++ /dev/null @@ -1,49 +0,0 @@ -getToken(); - * - * $response = yield $httpClient->request("https://example.com/stream", $token); - * $responseBody = $response->getBody(); - * - * while (($chunk = yield $response->read()) !== null) { - * // consume $chunk - * - * if ($noLongerInterested) { - * $cancellationTokenSource->cancel(); - * break; - * } - * } - * ``` - * - * @see Token - * @see CancelledException - */ -final class TokenSource -{ - private $token; - private $onCancel; - - public function __construct() - { - $this->token = new class($this->onCancel) implements Token { - /** @var string */ - private $nextId = "a"; - - /** @var callable[] */ - private $callbacks = []; - - /** @var \Throwable|null */ - private $exception; - - public function __construct(&$onCancel) - { - $onCancel = function (\Throwable $exception) { - $this->exception = $exception; - - $callbacks = $this->callbacks; - $this->callbacks = []; - - foreach ($callbacks as $callback) { - $callback($this->exception); - } - }; - } - - public function subscribe(callable $callback): string - { - $id = $this->nextId++; - - if ($this->exception) { - $callback($this->exception); - } else { - $this->callbacks[$id] = $callback; - } - - return $id; - } - - public function unsubscribe(string $id): void - { - unset($this->callbacks[$id]); - } - - public function isRequested(): bool - { - return $this->exception !== null; - } - - public function throwIfRequested(): void - { - if ($this->exception !== null) { - throw $this->exception; - } - } - }; - } - - public function getToken(): Token - { - return $this->token; - } - - /** - * @param \Throwable|null $previous Exception to be used as the previous exception to CancelledException. - */ - public function cancel(\Throwable $previous = null): void - { - if ($this->onCancel === null) { - return; - } - - $onCancel = $this->onCancel; - $this->onCancel = null; - $onCancel(new CancelledException("The operation was cancelled", $previous)); - } -} diff --git a/lib/Emitter.php b/lib/Emitter.php index a833dec..abacd89 100644 --- a/lib/Emitter.php +++ b/lib/Emitter.php @@ -2,7 +2,6 @@ namespace Amp; -use Amp\Cancellation\CancelledException; use Concurrent\Awaitable; use Concurrent\Deferred; use Concurrent\Task; @@ -58,7 +57,7 @@ final class Emitter public $waiting; }; - $this->iterator = (static function () use (&$state) { + $this->iterator = (static function () use ($state) { while (true) { if (isset($state->backpressure[$state->position])) { /** @var Deferred $deferred */ @@ -92,7 +91,7 @@ final class Emitter public function __destruct() { if (!$this->state->complete) { - $this->fail(new CancelledException("The operation was cancelled, because the emitter was garbage collected without completing, created in {$this->createFile}:{$this->createLine}")); + $this->fail(new \Error("The emitter was garbage collected without completing, created in {$this->createFile}:{$this->createLine}")); } } @@ -106,7 +105,7 @@ final class Emitter public function extractIterator(): \Iterator { if ($this->iterator === null) { - throw new \Error("The emitter's iterator can only be extracted once!"); + throw new \Error("The emitter's iterator can only be extracted once"); } $iterator = $this->iterator; @@ -131,7 +130,7 @@ final class Emitter } $this->state->values[] = $value; - $this->state->backpressure[] = $pressure = new Deferred; + $this->state->backpressure[] = $backpressure = new Deferred; if ($this->state->waiting !== null) { /** @var Deferred $waiting */ @@ -140,7 +139,7 @@ final class Emitter $waiting->resolve(true); } - Task::await($pressure->awaitable()); + Task::await($backpressure->awaitable()); } /** @@ -149,7 +148,7 @@ final class Emitter public function complete(): void { if ($this->state->complete) { - throw new \Error("Emitters has already been " . ($this->state->exception === null ? "completed" : "failed")); + throw new \Error("Emitter has already been " . ($this->state->exception === null ? "completed" : "failed")); } $this->state->complete = true; @@ -170,7 +169,7 @@ final class Emitter public function fail(\Throwable $reason): void { if ($this->state->complete) { - throw new \Error("Emitters has already been " . ($this->state->exception === null ? "completed" : "failed")); + throw new \Error("Emitter has already been " . ($this->state->exception === null ? "completed" : "failed")); } $this->state->complete = true; diff --git a/lib/Struct.php b/lib/Struct.php index ff69beb..5dd3ae1 100644 --- a/lib/Struct.php +++ b/lib/Struct.php @@ -5,8 +5,8 @@ namespace Amp; /** * A "safe" struct trait for public property aggregators. * - * This trait is intended to make using public properties a little safer by throwing when - * nonexistent property names are read or written. + * This trait is intended to make using public properties a little safer by throwing when nonexistent property names are + * read or written. */ trait Struct { @@ -16,6 +16,13 @@ trait Struct */ private $__propertySuggestThreshold = 70; + public function __isset(string $property) + { + throw new \Error( + $this->generateStructPropertyError($property) + ); + } + public function __get(string $property) { throw new \Error( @@ -30,13 +37,20 @@ trait Struct ); } + public function __unset(string $property) + { + throw new \Error( + $this->generateStructPropertyError($property) + ); + } + private function generateStructPropertyError(string $property): string { $suggestion = $this->suggestPropertyName($property); - $suggestStr = ($suggestion == "") ? "" : " ... did you mean \"{$suggestion}?\""; + $suggestStr = $suggestion === "" ? "" : " ... did you mean '{$suggestion}'?"; return \sprintf( - "%s property \"%s\" does not exist%s", + "%s property '%s' does not exist%s", \str_replace("\0", "@", \get_class($this)), // Handle anonymous class names. $property, $suggestStr diff --git a/lib/TimeoutException.php b/lib/TimeoutException.php index 8f65d2c..cfebeb5 100644 --- a/lib/TimeoutException.php +++ b/lib/TimeoutException.php @@ -3,7 +3,7 @@ namespace Amp; /** - * Thrown if a promise doesn't resolve within a specified timeout. + * Thrown if an awaitable doesn't resolve within a specified timeout. * * @see \Amp\timeout() */ diff --git a/lib/functions.php b/lib/functions.php index 2aca47a..07a3a9b 100644 --- a/lib/functions.php +++ b/lib/functions.php @@ -43,11 +43,11 @@ namespace Amp * @param Awaitable $awaitable Awaitable to which the timeout is applied. * @param int $timeout Timeout in milliseconds. * - * @return Awaitable + * @return mixed * * @throws TimeoutException */ - function timeout(Awaitable $awaitable, int $timeout): Awaitable + function timeout(Awaitable $awaitable, int $timeout) { $timeoutAwaitable = Task::async(function () use ($timeout) { $timer = new Timer($timeout); @@ -62,16 +62,11 @@ namespace Amp function some(array $awaitables, int $required = 1) { if ($required < 0) { - throw new \Error("Number of promises required must be non-negative"); + throw new \Error("Number of awaitables required must be non-negative, was '{$required}'"); } - $pending = \count($awaitables); - - if ($required > $pending) { - throw new \Error("Too few promises provided"); - } if (empty($awaitables)) { - return [[], []]; + throw new \Error("Array of awaitables can't be empty"); } $values = []; @@ -88,11 +83,7 @@ namespace Amp $key, ?\Throwable $error, $value - ) use ( - &$values, - &$errors, - $required - ) { + ) use (&$values, &$errors, $required) { if ($error) { $errors[$key] = $error; unset($values[$key]); diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 70319e1..f1c7796 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,17 +1,6 @@ - + test @@ -25,7 +14,4 @@ lib - - - diff --git a/test/Cancellation/CancellationTest.php b/test/Cancellation/CancellationTest.php deleted file mode 100644 index 3ce010c..0000000 --- a/test/Cancellation/CancellationTest.php +++ /dev/null @@ -1,97 +0,0 @@ -subscribe(function () use (&$running) { - $running = false; - }); - - $i = 0; - while ($running) { - $emitter->emit($i++); - } - - $emitter->complete(); - })); - - return $emitter->extractIterator(); - } - - public function testCancellationCancelsIterator(): void - { - $source = new TokenSource; - $current = null; - - foreach ($this->createIterator($source->getToken()) as $current) { - $this->assertInternalType("int", $current); - - if ($current === 3) { - $source->cancel(); - } - } - - $this->assertSame(3, $current); - } - - public function testUnsubscribeWorks(): void - { - $cancellationSource = new TokenSource; - - $first = $cancellationSource->getToken()->subscribe(function () { - $this->fail("Callback has been called"); - }); - - $cancellationSource->getToken()->subscribe(function () { - $this->assertTrue(true); - }); - - $cancellationSource->getToken()->unsubscribe($first); - - $cancellationSource->cancel(); - } - - public function testThrowingCallback(): void - { - $this->expectException(TestException::class); - - $cancellationSource = new TokenSource; - $cancellationSource->getToken()->subscribe(function () { - throw new TestException(__LINE__); - }); - - $cancellationSource->cancel(); - } - - public function testDoubleCancelOnlyInvokesOnce(): void - { - $cancellationSource = new TokenSource; - $cancellationSource->getToken()->subscribe($this->createCallback(1)); - - $cancellationSource->cancel(); - $cancellationSource->cancel(); - } - - public function testCalledIfSubscribingAfterCancel(): void - { - $cancellationSource = new TokenSource; - $cancellationSource->cancel(); - $cancellationSource->getToken()->subscribe($this->createCallback(1)); - } -} diff --git a/test/Cancellation/TimeoutTokenTest.php b/test/Cancellation/TimeoutTokenTest.php deleted file mode 100644 index c8c5613..0000000 --- a/test/Cancellation/TimeoutTokenTest.php +++ /dev/null @@ -1,37 +0,0 @@ -assertFalse($token->isRequested()); - delay(20); - $this->assertTrue($token->isRequested()); - - try { - $token->throwIfRequested(); - $this->fail("Must throw exception"); - } catch (CancelledException $exception) { - $this->assertInstanceOf(TimeoutException::class, $exception->getPrevious()); - } - } - - public function testWatcherCancellation(): void - { - $token = new TimeoutToken(1); - $this->assertSame(1, Loop::getInfo()["delay"]["enabled"]); - unset($token); - $this->assertSame(0, Loop::getInfo()["delay"]["enabled"]); - } -} diff --git a/test/Loop/DriverStateTest.php b/test/Loop/DriverStateTest.php deleted file mode 100644 index f079828..0000000 --- a/test/Loop/DriverStateTest.php +++ /dev/null @@ -1,57 +0,0 @@ -loop = $this->getMockForAbstractClass(Driver::class); - } - - /** @test */ - public function defaultsToNull() - { - $this->assertNull($this->loop->getState("foobar")); - } - - /** - * @test - * @dataProvider provideValues - */ - public function getsPreviouslySetValue($value) - { - $this->loop->setState("foobar", $value); - $this->assertSame($value, $this->loop->getState("foobar")); - } - - /** - * @test - * @dataProvider provideValues - */ - public function getsPreviouslySetValueViaAccessor($value) - { - Loop::setState("foobar", $value); - $this->assertSame($value, Loop::getState("foobar")); - } - - public function provideValues() - { - return [ - ["string"], - [42], - [1.001], - [true], - [false], - [null], - [new \StdClass], - ]; - } -} diff --git a/test/Loop/DriverTest.php b/test/Loop/DriverTest.php deleted file mode 100644 index e024111..0000000 --- a/test/Loop/DriverTest.php +++ /dev/null @@ -1,1620 +0,0 @@ -loop = ($this->getFactory())(); - - if (!$this->loop instanceof Driver) { - $this->fail("Factory did not return a loop Driver"); - } - - // Required for error handler to work - Loop::set($this->loop); - \gc_collect_cycles(); - - $this->remainingWatchers = []; - } - - public function tearDown() - { - foreach ($this->remainingWatchers as $watcher) { - $this->loop->cancel($watcher); - } - - unset($this->loop); - } - - public function start($cb): void - { - $cb($this->loop); - $this->loop->run(); - } - - public function testEmptyLoop(): void - { - $this->assertNull($this->loop->run()); - } - - public function testStopWorksEvenIfNotCurrentlyRunning(): void - { - $this->assertNull($this->loop->stop()); - } - - // Note: The running nesting is important for being able to continue actually still running loops (i.e. running flag set, if the driver has one) inside register_shutdown_function() for example - public function testLoopRunsCanBeConsecutiveAndNested(): void - { - $this->expectOutputString("123456"); - $failingLine = 0; - - $this->start(function (Driver $loop) use (&$failingLine) { - $loop->stop(); - $loop->defer(function () use (&$run) { - echo $run = 1; - }); - $loop->run(); - if (!$run) { - $failingLine = __LINE__; - } - $loop->defer(function () use ($loop, &$failingLine) { - $loop->run(); - echo 5; - $loop->defer(function () use ($loop, &$failingLine) { - echo 6; - $loop->stop(); - $this->remainingWatchers[] = $loop->defer(function () use (&$failingLine) { - $failingLine = __LINE__; - }); - }); - $loop->run(); - }); - $loop->defer(function () use ($loop) { - echo 2; - $loop->defer(function () { - echo 4; - }); - }); - $loop->defer(function () { - echo 3; - }); - }); - - $this->assertSame(0, $failingLine, "Test failed"); - } - - public function testLoopTerminatesWithOnlyUnreferencedWatchers(): void - { - $this->start(function (Driver $loop) use (&$end) { - $loop->unreference($this->remainingWatchers[] = $loop->onReadable(STDIN, function () { - // empty - })); - - $w = $loop->delay(10000000, function () { - // empty - }); - - $loop->defer(function () use ($loop, $w, &$end) { - $loop->cancel($w); - $end = true; - }); - }); - - $this->assertTrue($end); - } - - /** This MUST NOT have a "test" prefix, otherwise it's executed as test and marked as risky. */ - public function checkForSignalCapability(): void - { - try { - $watcher = $this->loop->onSignal(SIGUSR1, function () { - }); - $this->loop->cancel($watcher); - } catch (UnsupportedFeatureException $e) { - $this->markTestSkipped("The loop is not capable of handling signals properly. Skipping."); - } - } - - public function testWatcherUnrefRerefRunResult(): void - { - $invoked = false; - - $this->start(function (Driver $loop) use (&$invoked) { - $watcher = $loop->defer(function () use (&$invoked) { - $invoked = true; - }); - - $loop->unreference($watcher); - $loop->unreference($watcher); - $loop->reference($watcher); - }); - - $this->assertTrue($invoked); - } - - public function testDeferWatcherUnrefRunResult(): void - { - $invoked = false; - $watcher = null; - /** @var Driver|null $loop */ - $loop = null; - - $this->start(function (Driver $givenLoop) use (&$invoked, &$watcher, &$loop) { - $loop = $givenLoop; - - $watcher = $loop->defer(function () use (&$invoked) { - $invoked = true; - }); - - $loop->unreference($watcher); - }); - - $this->assertFalse($invoked); - $loop->cancel($watcher); - } - - public function testOnceWatcherUnrefRunResult(): void - { - $invoked = false; - $watcher = null; - /** @var Driver|null $loop */ - $loop = null; - - $this->start(function (Driver $givenLoop) use (&$invoked, &$watcher, &$loop) { - $loop = $givenLoop; - - $watcher = $loop->delay(2000, function () use (&$invoked) { - $invoked = true; - }); - - $loop->unreference($watcher); - }); - - $this->assertFalse($invoked); - $loop->cancel($watcher); - } - - public function testRepeatWatcherUnrefRunResult(): void - { - $invoked = false; - $watcher = null; - /** @var Driver|null $loop */ - $loop = null; - - $this->start(function (Driver $givenLoop) use (&$invoked, &$watcher, &$loop) { - $loop = $givenLoop; - - $watcher = $loop->repeat(2000, function () use (&$invoked) { - $invoked = true; - }); - - $loop->unreference($watcher); - }); - - $this->assertFalse($invoked); - $loop->cancel($watcher); - } - - public function testOnReadableWatcherUnrefRunResult(): void - { - $invoked = false; - $watcher = null; - /** @var Driver|null $loop */ - $loop = null; - - $this->start(function (Driver $givenLoop) use (&$invoked, &$watcher, &$loop) { - $loop = $givenLoop; - - $watcher = $loop->onReadable(STDIN, function () use (&$invoked) { - $invoked = true; - }); - - $loop->unreference($watcher); - }); - - $this->assertFalse($invoked); - $loop->cancel($watcher); - } - - public function testOnWritableWatcherKeepAliveRunResult(): void - { - $invoked = false; - $watcher = null; - /** @var Driver|null $loop */ - $loop = null; - - $this->start(function (Driver $givenLoop) use (&$invoked, &$watcher, &$loop) { - $loop = $givenLoop; - - $watcher = $loop->onWritable(STDOUT, function () use (&$invoked) { - $invoked = true; - }); - - $loop->unreference($watcher); - }); - - $this->assertFalse($invoked); - $loop->cancel($watcher); - } - - public function testOnSignalWatcherKeepAliveRunResult(): void - { - $this->checkForSignalCapability(); - - $invoked = false; - - $this->start(function (Driver $loop) use (&$invoked) { - $this->remainingWatchers[] = $watcher = $loop->onSignal(SIGUSR1, function () { - // empty - }); - $watcher = $loop->delay(100, function () use (&$invoked, $loop, $watcher) { - $invoked = true; - $loop->unreference($watcher); - }); - $loop->unreference($watcher); - }); - $this->assertTrue($invoked); - } - - public function testUnreferencedDeferWatcherStillExecutes(): void - { - $invoked = false; - $this->start(function (Driver $loop) use (&$invoked) { - $watcher = $loop->defer(function () use (&$invoked) { - $invoked = true; - }); - $loop->unreference($watcher); - $loop->defer(function () { - // just to keep loop running - }); - }); - $this->assertTrue($invoked); - } - - public function testLoopDoesNotBlockOnNegativeTimerExpiration(): void - { - $invoked = false; - $this->start(function (Driver $loop) use (&$invoked) { - $loop->delay(1, function () use (&$invoked) { - $invoked = true; - }); - - \usleep(1000 * 10); - }); - $this->assertTrue($invoked); - } - - public function testDisabledDeferReenableInSubsequentTick(): void - { - $this->expectOutputString("123"); - $this->start(function (Driver $loop) { - $watcherId = $loop->defer(function ($watcherId) { - echo 3; - }); - $loop->disable($watcherId); - $loop->defer(function () use ($loop, $watcherId) { - $loop->enable($watcherId); - echo 2; - }); - echo 1; - }); - } - - public function provideRegistrationArgs(): array - { - $args = [ - ["defer", [function () { - }]], - ["delay", [5, function () { - }]], - ["repeat", [5, function () { - }]], - ["onWritable", [\STDOUT, function () { - }]], - ["onReadable", [\STDIN, function () { - }]], - ["onSignal", [\SIGUSR1, function () { - }]], - ]; - - return $args; - } - - /** - * @requires PHP 7 - * @dataProvider provideRegistrationArgs - */ - public function testWeakTypes($type, $args): void - { - if ($type === "onSignal") { - $this->checkForSignalCapability(); - } - - $this->start(function (Driver $loop) use ($type, $args, &$invoked) { - if ($type === "onReadable") { - $ends = \stream_socket_pair(\stripos(PHP_OS, "win") === 0 ? STREAM_PF_INET : STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); - \fwrite($ends[0], "trigger readability watcher"); - $args = [$ends[1]]; - } else { - \array_pop($args); - } - - $expectedData = 20.75; - if (0 === \strpos($type, "on")) { - $args[] = function ($watcherId, $arg, int $data) use ($loop, &$invoked, $expectedData) { - $invoked = true; - $this->assertSame((int) $expectedData, $data); - $loop->cancel($watcherId); - }; - } else { - $args[] = function ($watcherId, int $data) use ($loop, &$invoked, $expectedData, $type) { - $invoked = true; - $this->assertSame((int) $expectedData, $data); - if ($type === "repeat") { - $loop->cancel($watcherId); - } - }; - } - $args[] = $expectedData; - \call_user_func_array([$loop, $type], $args); - - if ($type === "onSignal") { - $loop->defer(function () { - \posix_kill(\getmypid(), \SIGUSR1); - }); - } - }); - - $this->assertTrue($invoked); - } - - /** @dataProvider provideRegistrationArgs */ - public function testDisableWithConsecutiveCancel($type, $args): void - { - if ($type === "onSignal") { - $this->checkForSignalCapability(); - } - - $invoked = false; - $this->start(function (Driver $loop) use (&$invoked, $type, $args) { - $func = [$loop, $type]; - $watcherId = \call_user_func_array($func, $args); - $loop->disable($watcherId); - $loop->defer(function () use (&$invoked, $loop, $watcherId) { - $loop->cancel($watcherId); - $invoked = true; - }); - $this->assertFalse($invoked); - }); - $this->assertTrue($invoked); - } - - /** @dataProvider provideRegistrationArgs */ - public function testWatcherReferenceInfo($type, $args): void - { - if ($type === "onSignal") { - $this->checkForSignalCapability(); - } - - $loop = $this->loop; - - $func = [$loop, $type]; - if (\substr($type, 0, 2) === "on") { - $type = "on_" . \lcfirst(\substr($type, 2)); - } - - // being referenced is the default - $watcherId1 = \call_user_func_array($func, $args); - $info = $loop->getInfo(); - $expected = ["enabled" => 1, "disabled" => 0]; - $this->assertSame($expected, $info[$type]); - $expected = ["referenced" => 1, "unreferenced" => 0]; - $this->assertSame($expected, $info["enabled_watchers"]); - - // explicitly reference() even though it's the default setting - $argsCopy = $args; - $watcherId2 = \call_user_func_array($func, $argsCopy); - $loop->reference($watcherId2); - $loop->reference($watcherId2); - $info = $loop->getInfo(); - $expected = ["enabled" => 2, "disabled" => 0]; - $this->assertSame($expected, $info[$type]); - $expected = ["referenced" => 2, "unreferenced" => 0]; - $this->assertSame($expected, $info["enabled_watchers"]); - - // disabling a referenced watcher should decrement the referenced count - $loop->disable($watcherId2); - $loop->disable($watcherId2); - $loop->disable($watcherId2); - $info = $loop->getInfo(); - $expected = ["referenced" => 1, "unreferenced" => 0]; - $this->assertSame($expected, $info["enabled_watchers"]); - - // enabling a referenced watcher should increment the referenced count - $loop->enable($watcherId2); - $loop->enable($watcherId2); - $info = $loop->getInfo(); - $expected = ["referenced" => 2, "unreferenced" => 0]; - $this->assertSame($expected, $info["enabled_watchers"]); - - // cancelling an referenced watcher should decrement the referenced count - $loop->cancel($watcherId2); - $info = $loop->getInfo(); - $expected = ["referenced" => 1, "unreferenced" => 0]; - $this->assertSame($expected, $info["enabled_watchers"]); - - // unreference() should just increment unreferenced count - $watcherId2 = \call_user_func_array($func, $args); - $loop->unreference($watcherId2); - $info = $loop->getInfo(); - $expected = ["enabled" => 2, "disabled" => 0]; - $this->assertSame($expected, $info[$type]); - $expected = ["referenced" => 1, "unreferenced" => 1]; - $this->assertSame($expected, $info["enabled_watchers"]); - - $loop->cancel($watcherId1); - $loop->cancel($watcherId2); - } - - /** @dataProvider provideRegistrationArgs */ - public function testWatcherRegistrationAndCancellationInfo($type, $args): void - { - if ($type === "onSignal") { - $this->checkForSignalCapability(); - } - - $loop = $this->loop; - - $func = [$loop, $type]; - if (\substr($type, 0, 2) === "on") { - $type = "on_" . \lcfirst(\substr($type, 2)); - } - - $watcherId = \call_user_func_array($func, $args); - $this->assertInternalType("string", $watcherId); - $info = $loop->getInfo(); - $expected = ["enabled" => 1, "disabled" => 0]; - $this->assertSame($expected, $info[$type]); - - // invoke enable() on active watcher to ensure it has no side-effects - $loop->enable($watcherId); - $info = $loop->getInfo(); - $expected = ["enabled" => 1, "disabled" => 0]; - $this->assertSame($expected, $info[$type]); - - // invoke disable() twice to ensure it has no side-effects - $loop->disable($watcherId); - $loop->disable($watcherId); - - $info = $loop->getInfo(); - $expected = ["enabled" => 0, "disabled" => 1]; - $this->assertSame($expected, $info[$type]); - - $loop->cancel($watcherId); - $info = $loop->getInfo(); - $expected = ["enabled" => 0, "disabled" => 0]; - $this->assertSame($expected, $info[$type]); - - $watcherId = \call_user_func_array($func, $args); - $info = $loop->getInfo(); - $expected = ["enabled" => 1, "disabled" => 0]; - $this->assertSame($expected, $info[$type]); - - $loop->disable($watcherId); - $info = $loop->getInfo(); - $expected = ["enabled" => 0, "disabled" => 1]; - $this->assertSame($expected, $info[$type]); - - $loop->enable($watcherId); - $info = $loop->getInfo(); - $expected = ["enabled" => 1, "disabled" => 0]; - $this->assertSame($expected, $info[$type]); - - $loop->cancel($watcherId); - $info = $loop->getInfo(); - $expected = ["enabled" => 0, "disabled" => 0]; - $this->assertSame($expected, $info[$type]); - - $loop->disable($watcherId); - $info = $loop->getInfo(); - $expected = ["enabled" => 0, "disabled" => 0]; - $this->assertSame($expected, $info[$type]); - } - - /** - * @dataProvider provideRegistrationArgs - * @group memoryleak - */ - public function testNoMemoryLeak($type, $args): void - { - if ($this->getTestResultObject()->getCollectCodeCoverageInformation()) { - $this->markTestSkipped("Cannot run this test with code coverage active [code coverage consumes memory which makes it impossible to rely on memory_get_usage()]"); - } - - $runs = 2000; - - if ($type === "onSignal") { - $this->checkForSignalCapability(); - } - - $this->start(function (Driver $loop) use ($type, $args, $runs) { - $initialMem = \memory_get_usage(); - $cb = function ($runs) use ($loop, $type, $args) { - $func = [$loop, $type]; - for ($watchers = [], $i = 0; $i < $runs; $i++) { - $watchers[] = \call_user_func_array($func, $args); - } - foreach ($watchers as $watcher) { - $loop->cancel($watcher); - } - for ($watchers = [], $i = 0; $i < $runs; $i++) { - $watchers[] = \call_user_func_array($func, $args); - } - foreach ($watchers as $watcher) { - $loop->disable($watcher); - $loop->cancel($watcher); - } - for ($watchers = [], $i = 0; $i < $runs; $i++) { - $watchers[] = \call_user_func_array($func, $args); - } - if ($type === "repeat") { - $loop->delay($msInterval = 7, function () use ($loop, $watchers) { - foreach ($watchers as $watcher) { - $loop->cancel($watcher); - } - }); - } elseif ($type !== "defer" && $type !== "delay") { - $loop->defer(function () use ($loop, $watchers) { - foreach ($watchers as $watcher) { - $loop->cancel($watcher); - } - }); - } - $loop->run(); - if ($type === "defer") { - $loop->defer($fn = function ($watcherId, $i) use (&$fn, $loop) { - if ($i) { - $loop->defer($fn, --$i); - } - }, $runs); - $loop->run(); - } - if ($type === "delay") { - $loop->delay($msDelay = 0, $fn = function ($watcherId, $i) use (&$fn, $loop) { - if ($i) { - $loop->delay($msDelay = 0, $fn, --$i); - } - }, $runs); - $loop->run(); - } - if ($type === "repeat") { - $loop->repeat($msDelay = 0, $fn = function ($watcherId, $i) use (&$fn, $loop) { - $loop->cancel($watcherId); - if ($i) { - $loop->repeat($msDelay = 0, $fn, --$i); - } - }, $runs); - $loop->run(); - } - if ($type === "onWritable") { - $loop->defer(function ($watcherId, $runs) use ($loop) { - $fn = function ($watcherId, $socket, $i) use (&$fn, $loop) { - $loop->cancel($watcherId); - if ($socket) { - \fwrite($socket, "."); - } - if ($i) { - // explicitly use *different* streams with *different* resource ids - $ends = \stream_socket_pair(\stripos(PHP_OS, "win") === 0 ? STREAM_PF_INET : STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); - $loop->onWritable($ends[0], $fn, --$i); - $loop->onReadable($ends[1], function ($watcherId) use ($loop) { - $loop->cancel($watcherId); - }); - } - }; - $fn($watcherId, null, $runs); - }, $runs + 1); - $loop->run(); - } - if ($type === "onSignal") { - $sendSignal = function () { - \posix_kill(\getmypid(), \SIGUSR1); - }; - $loop->onSignal(\SIGUSR1, $fn = function ($watcherId, $signo, $i) use (&$fn, $loop, $sendSignal) { - if ($i) { - $loop->onSignal(\SIGUSR1, $fn, --$i); - $loop->defer($sendSignal); - } - $loop->cancel($watcherId); - }, $runs); - $loop->defer($sendSignal); - $loop->run(); - } - }; - $closureMem = \memory_get_usage() - $initialMem; - $cb($runs); /* just to set up eventual structures inside loop without counting towards memory comparison */ - \gc_collect_cycles(); - $initialMem = \memory_get_usage() - $closureMem; - $cb($runs); - unset($cb); - - \gc_collect_cycles(); - $endMem = \memory_get_usage(); - - /* this is allowing some memory usage due to runtime caches etc., but nothing actually leaking */ - $this->assertLessThan($runs * 4, $endMem - $initialMem); // * 4, as 4 is minimal sizeof(void *) - }); - } - - /** - * The first number of each tuple indicates the tick in which the watcher is supposed to execute, the second digit - * indicates the order within the tick. - */ - public function testExecutionOrderGuarantees(): void - { - $this->expectOutputString("01 02 03 04 " . \str_repeat("05 ", 8) . "10 11 12 " . \str_repeat("13 ", 4) . "20 " . \str_repeat("21 ", 4) . "30 40 41 "); - $this->start(function (Driver $loop) { - // Wrap in extra defer, so driver creation time doesn't count for timers, as timers are driver creation - // relative instead of last tick relative before first tick. - $loop->defer(function () use ($loop) { - $f = function () use ($loop) { - $args = \func_get_args(); - return function ($watcherId) use ($loop, &$args) { - if (!$args) { - $this->fail("Watcher callback called too often"); - } - $loop->cancel($watcherId); - echo \array_shift($args) . \array_shift($args), " "; - }; - }; - - $loop->onWritable(STDOUT, $f(0, 5)); - $writ1 = $loop->onWritable(STDOUT, $f(0, 5)); - $writ2 = $loop->onWritable(STDOUT, $f(0, 5)); - - $loop->delay($msDelay = 0, $f(0, 5)); - $del1 = $loop->delay($msDelay = 0, $f(0, 5)); - $del2 = $loop->delay($msDelay = 0, $f(0, 5)); - $del3 = $loop->delay($msDelay = 0, $f()); - $del4 = $loop->delay($msDelay = 0, $f(1, 3)); - $del5 = $loop->delay($msDelay = 0, $f(2, 0)); - $loop->defer(function () use ($loop, $del5) { - $loop->disable($del5); - }); - $loop->cancel($del3); - $loop->disable($del1); - $loop->disable($del2); - - $writ3 = $loop->onWritable(STDOUT, $f()); - $loop->cancel($writ3); - $loop->disable($writ1); - $loop->disable($writ2); - $loop->enable($writ1); - $writ4 = $loop->onWritable(STDOUT, $f(1, 3)); - $loop->onWritable(STDOUT, $f(0, 5)); - $loop->enable($writ2); - $loop->disable($writ4); - $loop->defer(function () use ($loop, $writ4, $f) { - $loop->enable($writ4); - $loop->onWritable(STDOUT, $f(1, 3)); - }); - - $loop->enable($del1); - $loop->delay($msDelay = 0, $f(0, 5)); - $loop->enable($del2); - $loop->disable($del4); - $loop->defer(function () use ($loop, $del4, $f) { - $loop->enable($del4); - $loop->onWritable(STDOUT, $f(1, 3)); - }); - - $loop->delay($msDelay = 1000, $f(4, 1)); - $loop->delay($msDelay = 600, $f(3, 0)); - $loop->delay($msDelay = 500, $f(2, 1)); - $loop->repeat($msDelay = 500, $f(2, 1)); - $rep1 = $loop->repeat($msDelay = 250, $f(2, 1)); - $loop->disable($rep1); - $loop->delay($msDelay = 500, $f(2, 1)); - $loop->enable($rep1); - - $loop->defer($f(0, 1)); - $def1 = $loop->defer($f(0, 3)); - $def2 = $loop->defer($f(1, 1)); - $def3 = $loop->defer($f()); - $loop->defer($f(0, 2)); - $loop->disable($def1); - $loop->cancel($def3); - $loop->enable($def1); - $loop->defer(function () use ($loop, $def2, $del5, $f) { - $tick = $f(0, 4); - $tick("invalid"); - $loop->defer($f(1, 0)); - $loop->enable($def2); - $loop->defer($f(1, 2)); - $loop->defer(function () use ($loop, $del5, $f) { - $loop->enable($del5); - $loop->defer(function () use ($loop, $f) { - \usleep(700000); // to have $msDelay == 500 and $msDelay == 600 run at the same tick (but not $msDelay == 150) - $loop->defer(function () use ($loop, $f) { - $loop->defer($f(4, 0)); - }); - }); - }); - }); - $loop->disable($def2); - }); - }); - } - - public function testSignalExecutionOrder(): void - { - $this->checkForSignalCapability(); - - $this->expectOutputString("122222"); - $this->start(function (Driver $loop) { - $f = function ($i) use ($loop) { - return function ($watcherId) use ($loop, $i) { - $loop->cancel($watcherId); - echo $i; - }; - }; - - $loop->defer($f(1)); - $loop->onSignal(SIGUSR1, $f(2)); - $sig1 = $loop->onSignal(SIGUSR1, $f(2)); - $sig2 = $loop->onSignal(SIGUSR1, $f(2)); - $sig3 = $loop->onSignal(SIGUSR1, $f(" FAIL - MUST NOT BE CALLED ")); - $loop->disable($sig1); - $loop->onSignal(SIGUSR1, $f(2)); - $loop->disable($sig2); - $loop->enable($sig1); - $loop->cancel($sig3); - $loop->onSignal(SIGUSR1, $f(2)); - $loop->defer(function () use ($loop, $sig2) { - $loop->enable($sig2); - $loop->defer(function () use ($loop) { - \posix_kill(\getmypid(), \SIGUSR1); - $loop->delay($msDelay = 10, function () use ($loop) { - $loop->stop(); - }); - }); - }); - }); - } - - /** @expectedException \Amp\Loop\InvalidWatcherError */ - public function testExceptionOnEnableNonexistentWatcher(): void - { - try { - $this->loop->enable("nonexistentWatcher"); - } catch (InvalidWatcherError $e) { - $this->assertSame("nonexistentWatcher", $e->getWatcherId()); - throw $e; - } - } - - public function testSuccessOnDisableNonexistentWatcher(): void - { - $this->loop->disable("nonexistentWatcher"); - - // Otherwise risky, throwing fails the test - $this->assertTrue(true); - } - - public function testSuccessOnCancelNonexistentWatcher(): void - { - $this->loop->cancel("nonexistentWatcher"); - - // Otherwise risky, throwing fails the test - $this->assertTrue(true); - } - - /** @expectedException \Amp\Loop\InvalidWatcherError */ - public function testExceptionOnReferenceNonexistentWatcher(): void - { - try { - $this->loop->reference("nonexistentWatcher"); - } catch (InvalidWatcherError $e) { - $this->assertSame("nonexistentWatcher", $e->getWatcherId()); - throw $e; - } - } - - public function testSuccessOnUnreferenceNonexistentWatcher(): void - { - $this->loop->unreference("nonexistentWatcher"); - - // Otherwise risky, throwing fails the test - $this->assertTrue(true); - } - - /** @expectedException \Amp\Loop\InvalidWatcherError */ - public function testWatcherInvalidityOnDefer(): void - { - $this->start(function (Driver $loop) { - $loop->defer(function ($watcher) use ($loop) { - $loop->enable($watcher); - }); - }); - } - - /** @expectedException \Amp\Loop\InvalidWatcherError */ - public function testWatcherInvalidityOnDelay(): void - { - $this->start(function (Driver $loop) { - $loop->delay($msDelay = 0, function ($watcher) use ($loop) { - $loop->enable($watcher); - }); - }); - } - - public function testEventsNotExecutedInSameTickAsEnabled(): void - { - $this->start(function (Driver $loop) { - $loop->defer(function () use ($loop) { - $loop->defer(function () use ($loop, &$diswatchers, &$watchers) { - $loop->defer(function () use ($loop, $diswatchers) { - foreach ($diswatchers as $watcher) { - $loop->disable($watcher); - } - $loop->defer(function () use ($loop, $diswatchers) { - $loop->defer(function () use ($loop, $diswatchers) { - foreach ($diswatchers as $watcher) { - $loop->cancel($watcher); - } - }); - foreach ($diswatchers as $watcher) { - $loop->enable($watcher); - } - }); - }); - foreach ($watchers as $watcher) { - $loop->cancel($watcher); - } - foreach ($diswatchers as $watcher) { - $loop->disable($watcher); - $loop->enable($watcher); - } - }); - - $f = function () use ($loop) { - $watchers[] = $loop->defer([$this, "fail"]); - $watchers[] = $loop->delay($msDelay = 0, [$this, "fail"]); - $watchers[] = $loop->repeat($msDelay = 0, [$this, "fail"]); - $watchers[] = $loop->onWritable(STDIN, [$this, "fail"]); - return $watchers; - }; - $watchers = $f(); - $diswatchers = $f(); - }); - }); - - // Otherwise risky, as we only rely on $this->fail() - $this->assertTrue(true); - } - - public function testEnablingWatcherAllowsSubsequentInvocation(): void - { - $loop = $this->loop; - $increment = 0; - $watcherId = $loop->defer(function () use (&$increment) { - $increment++; - }); - $loop->disable($watcherId); - $loop->delay($msDelay = 5, [$loop, "stop"]); - $loop->run(); - $this->assertSame(0, $increment); - $loop->enable($watcherId); - $loop->delay($msDelay = 5, [$loop, "stop"]); - $loop->run(); - $this->assertSame(1, $increment); - } - - public function testUnresolvedEventsAreReenabledOnRunFollowingPreviousStop(): void - { - $increment = 0; - $this->start(function (Driver $loop) use (&$increment) { - $loop->defer([$loop, "stop"]); - $loop->run(); - - $loop->defer(function () use (&$increment, $loop) { - $loop->delay($msDelay = 100, function () use ($loop, &$increment) { - $increment++; - $loop->stop(); - }); - }); - - $this->assertSame(0, $increment); - \usleep(5000); - }); - $this->assertSame(1, $increment); - } - - public function testTimerWatcherParameterOrder(): void - { - $this->start(function (Driver $loop) { - $counter = 0; - $loop->defer(function ($watcherId) use ($loop, &$counter) { - $this->assertInternalType("string", $watcherId); - if (++$counter === 3) { - $loop->stop(); - } - }); - $loop->delay($msDelay = 5, function ($watcherId) use ($loop, &$counter) { - $this->assertInternalType("string", $watcherId); - if (++$counter === 3) { - $loop->stop(); - } - }); - $loop->repeat($msDelay = 5, function ($watcherId) use ($loop, &$counter) { - $this->assertInternalType("string", $watcherId); - $loop->cancel($watcherId); - if (++$counter === 3) { - $loop->stop(); - } - }); - }); - } - - public function testStreamWatcherParameterOrder(): void - { - $this->start(function (Driver $loop) use (&$invoked) { - $invoked = 0; - $loop->onWritable(STDOUT, function ($watcherId, $stream) use ($loop, &$invoked) { - $this->assertInternalType("string", $watcherId); - $this->assertSame(STDOUT, $stream); - $invoked++; - $loop->cancel($watcherId); - }); - }); - $this->assertSame(1, $invoked); - } - - public function testDisablingWatcherPreventsSubsequentInvocation(): void - { - $this->start(function (Driver $loop) { - $increment = 0; - $this->remainingWatchers[] = $watcherId = $loop->defer(function () use (&$increment) { - $increment++; - }); - - $loop->disable($watcherId); - $loop->delay($msDelay = 5, [$loop, "stop"]); - - $this->assertSame(0, $increment); - }); - } - - public function testImmediateExecution(): void - { - $loop = $this->loop; - $increment = 0; - $this->start(function (Driver $loop) use (&$increment) { - $loop->defer(function () use (&$increment) { - $increment++; - }); - $loop->defer([$loop, "stop"]); - }); - $this->assertSame(1, $increment); - } - - public function testImmediatelyCallbacksDoNotRecurseInSameTick(): void - { - $increment = 0; - $this->start(function (Driver $loop) use (&$increment) { - $loop->defer(function () use ($loop, &$increment) { - $increment++; - $this->remainingWatchers[] = $loop->defer(function () use (&$increment) { - $increment++; - }); - }); - $loop->defer([$loop, "stop"]); - }); - $this->assertSame(1, $increment); - } - - public function testRunExecutesEventsUntilExplicitlyStopped(): void - { - $increment = 0; - $this->start(function (Driver $loop) use (&$increment) { - $loop->repeat($msInterval = 5, function ($watcherId) use ($loop, &$increment) { - $increment++; - if ($increment === 10) { - $loop->cancel($watcherId); - } - }); - }); - $this->assertSame(10, $increment); - } - - /** - * @expectedException \Exception - * @expectedExceptionMessage loop error - */ - public function testLoopAllowsExceptionToBubbleUpDuringStart(): void - { - $this->start(function (Driver $loop) { - $loop->defer(function () { - throw new \Exception("loop error"); - }); - }); - } - - /** - * @expectedException \RuntimeException - * @expectedExceptionMessage test - */ - public function testLoopAllowsExceptionToBubbleUpFromRepeatingAlarmDuringStart(): void - { - $this->start(function (Driver $loop) { - $this->remainingWatchers[] = $loop->repeat($msInterval = 1, function () { - throw new \RuntimeException("test"); - }); - }); - } - - public function testErrorHandlerCapturesUncaughtException(): void - { - $msg = ""; - $this->loop->setErrorHandler($f = function () { - }); - $oldErrorHandler = $this->loop->setErrorHandler(function (\Exception $error) use (&$msg) { - $msg = $error->getMessage(); - }); - $this->assertSame($f, $oldErrorHandler); - $this->start(function (Driver $loop) { - $loop->defer(function () { - throw new \Exception("loop error"); - }); - }); - $this->assertSame("loop error", $msg); - } - - /** - * @expectedException \RuntimeException - * @expectedExceptionMessage test - */ - public function testLoopException(): void - { - $this->start(function (Driver $loop) { - $loop->defer(function () use ($loop) { - // force next tick, outside of primary startup tick - $loop->defer(function () { - throw new \RuntimeException("test"); - }); - }); - }); - } - - public function testOnSignalWatcher(): void - { - $this->checkForSignalCapability(); - - $this->expectOutputString("caught SIGUSR1"); - $this->start(function (Driver $loop) { - $loop->delay($msDelay = 1, function () use ($loop) { - \posix_kill(\getmypid(), \SIGUSR1); - $loop->delay($msDelay = 10, function () use ($loop) { - $loop->stop(); - }); - }); - - $loop->onSignal(SIGUSR1, function ($watcherId) use ($loop) { - $loop->cancel($watcherId); - echo "caught SIGUSR1"; - }); - }); - } - - public function testInitiallyDisabledOnSignalWatcher(): void - { - $this->checkForSignalCapability(); - - $this->expectOutputString("caught SIGUSR1"); - $this->start(function (Driver $loop) { - $this->remainingWatchers[] = $stop = $loop->delay($msDelay = 100, function () use ($loop) { - echo "ERROR: manual stop"; - $loop->stop(); - }); - $this->remainingWatchers[] = $watcherId = $loop->onSignal(SIGUSR1, function ($watcherId) use ( - $loop, - $stop - ) { - echo "caught SIGUSR1"; - $loop->disable($stop); - $loop->disable($watcherId); - }); - $loop->disable($watcherId); - - $loop->delay($msDelay = 1, function () use ($loop, $watcherId) { - $loop->enable($watcherId); - $loop->delay($msDelay = 1, function () { - \posix_kill(\getmypid(), SIGUSR1); - }); - }); - }); - } - - public function testNestedLoopSignalDispatch(): void - { - $this->checkForSignalCapability(); - - $this->expectOutputString("inner SIGUSR2\nouter SIGUSR1\n"); - $this->start(function (Driver $loop) { - $this->remainingWatchers[] = $loop->delay($msDelay = 300, function () use ($loop) { - $loop->stop(); - }); - $loop->onSignal(SIGUSR1, function ($watcherId) use ($loop) { - echo "outer SIGUSR1\n"; - $loop->cancel($watcherId); - $loop->stop(); - }); - - $loop->delay($msDelay = 1, function () { - /** @var Driver $loop */ - $loop = ($this->getFactory())(); - $stop = $loop->delay($msDelay = 100, function () use ($loop) { - echo "ERROR: manual stop"; - $loop->stop(); - }); - $loop->onSignal(SIGUSR2, function ($watcherId) use ($loop, $stop) { - echo "inner SIGUSR2\n"; - $loop->cancel($stop); - $loop->cancel($watcherId); - }); - $loop->delay($msDelay = 1, function () { - \posix_kill(\getmypid(), SIGUSR2); - }); - $loop->run(); - }); - $loop->delay($msDelay = 20, function () { - \posix_kill(\getmypid(), \SIGUSR1); - }); - }); - } - - public function testCancelRemovesWatcher(): void - { - $invoked = false; - - $this->start(function (Driver $loop) use (&$invoked) { - $watcherId = $loop->delay($msDelay = 10, function () { - $this->fail('Watcher was not cancelled as expected'); - }); - - $loop->defer(function () use ($loop, $watcherId, &$invoked) { - $loop->cancel($watcherId); - $invoked = true; - }); - - $loop->delay($msDelay = 5, [$loop, "stop"]); - }); - - $this->assertTrue($invoked); - } - - public function testOnWritableWatcher(): void - { - $flag = false; - $this->start(function (Driver $loop) use (&$flag) { - $loop->onWritable(STDOUT, function ($watcherId) use ($loop, &$flag) { - $flag = true; - $loop->cancel($watcherId); - $loop->stop(); - }); - $this->remainingWatchers[] = $loop->delay($msDelay = 5, [$loop, "stop"]); - }); - $this->assertTrue($flag); - } - - public function testInitiallyDisabledWriteWatcher(): void - { - $increment = 0; - $this->start(function (Driver $loop) { - $this->remainingWatchers[] = $watcherId = $loop->onWritable(STDOUT, function () use (&$increment) { - $increment++; - }); - $loop->disable($watcherId); - $loop->delay($msDelay = 5, [$loop, "stop"]); - }); - $this->assertSame(0, $increment); - } - - public function testInitiallyDisabledWriteWatcherIsTriggeredOnceEnabled(): void - { - $this->expectOutputString("12"); - $this->start(function (Driver $loop) { - $watcherId = $loop->onWritable(STDOUT, function ($watcherId) use ($loop) { - echo 2; - $loop->cancel($watcherId); - $loop->stop(); - }); - $loop->disable($watcherId); - $loop->defer(function () use ($loop, $watcherId) { - $loop->enable($watcherId); - echo 1; - }); - }); - } - - /** @expectedException \RuntimeException */ - public function testStreamWatcherDoesntSwallowExceptions(): void - { - $this->start(function (Driver $loop) { - $loop->onWritable(STDOUT, function ($watcherId) { - $this->loop->cancel($watcherId); - throw new \RuntimeException; - }); - $this->remainingWatchers[] = $loop->delay($msDelay = 5, [$loop, "stop"]); - }); - } - - public function testReactorRunsUntilNoWatchersRemain(): void - { - $var1 = $var2 = 0; - $this->start(function (Driver $loop) use (&$var1, &$var2) { - $loop->repeat($msDelay = 1, function ($watcherId) use ($loop, &$var1) { - if (++$var1 === 3) { - $loop->cancel($watcherId); - } - }); - - $loop->onWritable(STDOUT, function ($watcherId) use ($loop, &$var2) { - if (++$var2 === 4) { - $loop->cancel($watcherId); - } - }); - }); - $this->assertSame(3, $var1); - $this->assertSame(4, $var2); - } - - public function testReactorRunsUntilNoWatchersRemainWhenStartedDeferred(): void - { - $var1 = $var2 = 0; - $this->start(function (Driver $loop) use (&$var1, &$var2) { - $loop->defer(function () use ($loop, &$var1, &$var2) { - $loop->repeat($msDelay = 1, function ($watcherId) use ($loop, &$var1) { - if (++$var1 === 3) { - $loop->cancel($watcherId); - } - }); - - $loop->onWritable(STDOUT, function ($watcherId) use ($loop, &$var2) { - if (++$var2 === 4) { - $loop->cancel($watcherId); - } - }); - }); - }); - $this->assertSame(3, $var1); - $this->assertSame(4, $var2); - } - - public function testOptionalCallbackDataPassedOnInvocation(): void - { - $callbackData = new \StdClass; - - $this->start(function (Driver $loop) use ($callbackData) { - $loop->defer(function ($watcherId, $callbackData) { - $callbackData->defer = true; - }, $callbackData); - $loop->delay($msDelay = 1, function ($watcherId, $callbackData) { - $callbackData->delay = true; - }, $callbackData); - $loop->repeat($msDelay = 1, function ($watcherId, $callbackData) use ($loop) { - $callbackData->repeat = true; - $loop->cancel($watcherId); - }, $callbackData); - $loop->onWritable(STDERR, function ($watcherId, $stream, $callbackData) use ($loop) { - $callbackData->onWritable = true; - $loop->cancel($watcherId); - }, $callbackData); - }); - - $this->assertTrue($callbackData->defer); - $this->assertTrue($callbackData->delay); - $this->assertTrue($callbackData->repeat); - $this->assertTrue($callbackData->onWritable); - } - - public function testLoopStopPreventsTimerExecution(): void - { - $t = \microtime(1); - $this->start(function (Driver $loop) { - $this->remainingWatchers[] = $loop->delay($msDelay = 10000, function () { - $this->fail("Timer was executed despite stopped loop"); - }); - $loop->defer(function () use ($loop) { - $loop->stop(); - }); - }); - $this->assertTrue($t + 0.1 > \microtime(1)); - } - - public function testDeferEnabledInNextTick(): void - { - $tick = function () { - $this->loop->defer(function () { - $this->loop->stop(); - }); - $this->loop->run(); - }; - - $invoked = 0; - - $this->remainingWatchers[] = $watcher = $this->loop->onWritable(STDOUT, function () use (&$invoked) { - $invoked++; - }); - - $tick(); - $tick(); - $tick(); - - $this->loop->disable($watcher); - $this->loop->enable($watcher); - $tick(); // disable + immediate enable after a tick should have no effect either - - $this->assertSame(4, $invoked); - } - - // getState and setState are final, but test it here again to be sure - public function testRegistry(): void - { - $this->assertNull($this->loop->getState("foo")); - $this->loop->setState("foo", NAN); - $this->assertNan($this->loop->getState("foo")); - $this->loop->setState("foo", "1"); - $this->assertNull($this->loop->getState("bar")); - $this->loop->setState("baz", -INF); - // running must not affect state - $this->loop->defer([$this->loop, "stop"]); - $this->loop->run(); - $this->assertSame(-INF, $this->loop->getState("baz")); - $this->assertSame("1", $this->loop->getState("foo")); - } - - /** @dataProvider provideRegistryValues */ - public function testRegistryValues($val): void - { - $this->loop->setState("foo", $val); - $this->assertSame($val, $this->loop->getState("foo")); - } - - public function provideRegistryValues(): array - { - return [ - ["string"], - [0], - [PHP_INT_MIN], - [-1.0], - [true], - [false], - [[]], - [null], - [new \StdClass], - ]; - } - - public function provideWatchers() - { - return [["onReadable"], ["onWritable"], ["defer"], ["delay"], ["repeat"], ["onSignal"]]; - } - - /** @dataProvider provideWatchers */ - public function testRethrowsFromCallbacks(string $watcher): void - { - $exception = new TestException; - - $awaitables = [ - Deferred::error($exception), - null, - ]; - - if ($watcher === "onSignal") { - $this->checkForSignalCapability(); - } - - foreach ($awaitables as $awaitable) { - $args = []; - - switch ($watcher) { - case "onSignal": - $args[] = SIGUSR1; - break; - - case "onWritable": - $args[] = STDOUT; - break; - - case "onReadable": - $ends = \stream_socket_pair(\stripos(PHP_OS, "win") === 0 ? STREAM_PF_INET : STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); - \fwrite($ends[0], "trigger readability watcher"); - $args[] = $ends[1]; - break; - - case "delay": - case "repeat": - $args[] = 5; - break; - } - - if ($awaitable === null) { - $args[] = function ($watcherId) use ($exception) { - $this->loop->cancel($watcherId); - throw $exception; - }; - } else { - $args[] = function ($watcherId) use ($awaitable) { - $this->loop->cancel($watcherId); - return $awaitable; - }; - } - - \call_user_func_array([$this->loop, $watcher], $args); - - if ($watcher === "onSignal") { - $this->loop->delay(100, function () { - \posix_kill(\getmypid(), \SIGUSR1); - }); - } - - $this->loop->setErrorHandler($this->createCallback(1)); - $this->loop->run(); - } - } - - public function testMultipleWatchersOnSameDescriptor(): void - { - $sockets = \stream_socket_pair(\stripos(PHP_OS, "win") === 0 ? STREAM_PF_INET : STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); - \fwrite($sockets[1], "testing"); - - $invoked = 0; - $this->remainingWatchers[] = $watcher1 = $this->loop->onReadable($sockets[0], function ($watcher) use (&$invoked - ) { - $invoked += 1; - $this->loop->disable($watcher); - }); - $this->remainingWatchers[] = $watcher2 = $this->loop->onReadable($sockets[0], function ($watcher) use (&$invoked - ) { - $invoked += 10; - $this->loop->disable($watcher); - }); - $this->remainingWatchers[] = $watcher3 = $this->loop->onWritable($sockets[0], function ($watcher) use (&$invoked - ) { - $invoked += 100; - $this->loop->disable($watcher); - }); - $this->remainingWatchers[] = $watcher4 = $this->loop->onWritable($sockets[0], function ($watcher) use (&$invoked - ) { - $invoked += 1000; - $this->loop->disable($watcher); - }); - - $this->loop->defer(function () use ($watcher1, $watcher3) { - $this->loop->delay(200, function () use ($watcher1, $watcher3) { - $this->loop->enable($watcher1); - $this->loop->enable($watcher3); - }); - }); - - $this->loop->run(); - - $this->assertSame(1212, $invoked); - - $this->loop->enable($watcher1); - $this->loop->enable($watcher3); - - $this->loop->delay(100, function () use ($watcher2, $watcher4) { - $this->loop->enable($watcher2); - $this->loop->enable($watcher4); - }); - - $this->loop->run(); - - $this->assertSame(2323, $invoked); - } - - public function testTimerIntervalCountedWhenNotRunning(): void - { - \usleep(600000); // 600ms instead of 500ms to allow for variations in timing. - $start = \microtime(true); - $this->loop->delay(1000, function () use ($start) { - $this->assertLessThan(0.5, \microtime(true) - $start); - }); - $this->loop->run(); - } - - public function testShortTimerDoesNotBlockOtherTimers(): void - { - $this->remainingWatchers[] = $this->loop->repeat(0, function () { - static $i = 0; - - if (++$i === 5) { - $this->fail("Loop continues with repeat watcher"); - } - - \usleep(2000); - }); - - $this->loop->delay(2, function () { - $this->assertTrue(true); - $this->loop->stop(); - }); - - $this->loop->run(); - } - - public function testTwoShortRepeatTimersWorkAsExpected(): void - { - $this->remainingWatchers[] = $this->loop->repeat(0, function () use (&$j) { - static $i = 0; - if (++$i === 5) { - $this->loop->stop(); - } - $j = $i; - }); - $this->remainingWatchers[] = $this->loop->repeat(0, function () use (&$k) { - static $i = 0; - if (++$i === 5) { - $this->loop->stop(); - } - $k = $i; - }); - - $this->loop->run(); - $this->assertLessThan(2, \abs($j - $k)); - $this->assertNotSame(0, $j); - } - - public function testBug163ConsecutiveDelayed(): void - { - $emits = 3; - - $this->loop->defer(function () use (&$time, $emits) { - $time = \microtime(true); - for ($i = 0; $i < $emits; ++$i) { - delay(100); - } - $time = \microtime(true) - $time; - }); - - $this->loop->run(); - - $this->assertGreaterThan(100 * $emits - 2 /* 2ms grace period */, $time * 1000); - } -} diff --git a/test/Loop/EvDriverTest.php b/test/Loop/EvDriverTest.php deleted file mode 100644 index 212ec5f..0000000 --- a/test/Loop/EvDriverTest.php +++ /dev/null @@ -1,28 +0,0 @@ -assertInstanceOf(\EvLoop::class, $this->loop->getHandle()); - } - - public function testSupported() - { - $this->assertTrue(EvDriver::isSupported()); - } -} diff --git a/test/Loop/EventDriverTest.php b/test/Loop/EventDriverTest.php deleted file mode 100644 index da84c5a..0000000 --- a/test/Loop/EventDriverTest.php +++ /dev/null @@ -1,28 +0,0 @@ -assertInstanceOf(\EventBase::class, $this->loop->getHandle()); - } - - public function testSupported() - { - $this->assertTrue(EventDriver::isSupported()); - } -} diff --git a/test/Loop/NativeDriverTest.php b/test/Loop/NativeDriverTest.php deleted file mode 100644 index 6cd455b..0000000 --- a/test/Loop/NativeDriverTest.php +++ /dev/null @@ -1,20 +0,0 @@ -assertNull($this->loop->getHandle()); - } -} diff --git a/test/Loop/UvDriverTest.php b/test/Loop/UvDriverTest.php deleted file mode 100644 index a549423..0000000 --- a/test/Loop/UvDriverTest.php +++ /dev/null @@ -1,29 +0,0 @@ -loop->getHandle(); - $this->assertTrue(\is_resource($handle) || $handle instanceof \UVLoop); - } - - public function testSupported() - { - $this->assertTrue(UvDriver::isSupported()); - } -} diff --git a/test/Loop/UvLoopDestructShutdown.phpt b/test/Loop/UvLoopDestructShutdown.phpt deleted file mode 100644 index 2bfc2e6..0000000 --- a/test/Loop/UvLoopDestructShutdown.phpt +++ /dev/null @@ -1,31 +0,0 @@ ---TEST-- -Test order of destruction not interfering with access to UV handles ---SKIPIF-- - ---FILE-- -handle = Loop::repeat(10, function () {}); - } - public function __destruct() - { - Loop::cancel($this->handle); - print "ok"; - } -}); - -Loop::delay(0, [Loop::class, "stop"]); - -?> ---EXPECT-- -ok diff --git a/test/LoopTest.php b/test/LoopTest.php deleted file mode 100644 index af8c601..0000000 --- a/test/LoopTest.php +++ /dev/null @@ -1,60 +0,0 @@ -expectException(\Error::class); - - Loop::delay(-1, function () { - }); - } - - public function testRepeatWithNegativeInterval(): void - { - $this->expectException(\Error::class); - - Loop::repeat(-1, function () { - }); - } - - public function testOnReadable(): void - { - Loop::run(function () { - $ends = \stream_socket_pair(\stripos(PHP_OS, "win") === 0 ? STREAM_PF_INET : STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); - \fwrite($ends[0], "trigger readability watcher"); - - Loop::onReadable($ends[1], function ($watcherId) { - $this->assertTrue(true); - Loop::cancel($watcherId); - Loop::stop(); - }); - }); - } - - public function testOnWritable(): void - { - Loop::run(function () { - Loop::onWritable(STDOUT, function ($watcherId) { - $this->assertTrue(true); - Loop::cancel($watcherId); - Loop::stop(); - }); - }); - } - - public function testGet(): void - { - $this->assertInstanceOf(Loop\Driver::class, Loop::get()); - } - - public function testGetInto(): void - { - $this->assertSame(Loop::get()->getInfo(), Loop::getInfo()); - } -} diff --git a/test/RethrowTest.php b/test/RethrowTest.php deleted file mode 100644 index 3724288..0000000 --- a/test/RethrowTest.php +++ /dev/null @@ -1,23 +0,0 @@ -createCallback(1)); - - rethrow(Deferred::error($exception)); - delay(1); - } -} diff --git a/test/StructTest.php b/test/StructTest.php index 3575585..073a349 100644 --- a/test/StructTest.php +++ b/test/StructTest.php @@ -2,6 +2,8 @@ namespace Amp\Test; +use PHPUnit\Framework\TestCase; + class StructTestFixture { use \Amp\Struct; @@ -9,11 +11,11 @@ class StructTestFixture public $_foofoofoofoofoofoofoofoobar; } -class StructTest extends \PHPUnit\Framework\TestCase +class StructTest extends TestCase { /** * @expectedException \Error - * @expectedExceptionMessage Amp\Test\StructTestFixture property "callbac" does not exist ... did you mean + * @expectedExceptionMessage Amp\Test\StructTestFixture property 'callbac' does not exist ... did you mean * "callback?" */ public function testSetErrorWithSuggestion(): void @@ -25,7 +27,7 @@ class StructTest extends \PHPUnit\Framework\TestCase /** * @expectedException \Error - * @expectedExceptionMessage Amp\Test\StructTestFixture property "callbac" does not exist ... did you mean + * @expectedExceptionMessage Amp\Test\StructTestFixture property 'callbac' does not exist ... did you mean * "callback?" */ public function testGetErrorWithSuggestion(): void @@ -36,7 +38,7 @@ class StructTest extends \PHPUnit\Framework\TestCase /** * @expectedException \Error - * @expectedExceptionMessage Amp\Test\StructTestFixture property "callZZZZZZZZZZZ" does not exist + * @expectedExceptionMessage Amp\Test\StructTestFixture property 'callZZZZZZZZZZZ' does not exist */ public function testSetErrorWithoutSuggestion(): void { @@ -46,7 +48,7 @@ class StructTest extends \PHPUnit\Framework\TestCase /** * @expectedException \Error - * @expectedExceptionMessage Amp\Test\StructTestFixture property "callZZZZZZZZZZZ" does not exist + * @expectedExceptionMessage Amp\Test\StructTestFixture property 'callZZZZZZZZZZZ' does not exist */ public function testGetErrorWithoutSuggestion(): void { @@ -56,7 +58,7 @@ class StructTest extends \PHPUnit\Framework\TestCase /** * @expectedException \Error - * @expectedExceptionMessage Amp\Test\StructTestFixture property "__propertySuggestThreshold" does not exist + * @expectedExceptionMessage Amp\Test\StructTestFixture property '__propertySuggestThreshold' does not exist */ public function testSuggestionIgnoresPropertyStartingWithUnderscore(): void { @@ -68,7 +70,7 @@ class StructTest extends \PHPUnit\Framework\TestCase { // Use regexp to ensure no property is suggested, because expected message is a prefix then and still passes $this->expectException(\Error::class); - $this->expectExceptionMessageRegExp("(Amp\\\\Test\\\\StructTestFixture property \"foofoofoofoofoofoofoofoobar\" does not exist$)"); + $this->expectExceptionMessageRegExp("(Amp\\\\Test\\\\StructTestFixture property 'foofoofoofoofoofoofoofoobar' does not exist$)"); $struct = new StructTestFixture; $struct->foofoofoofoofoofoofoofoobar = "test"; @@ -78,7 +80,7 @@ class StructTest extends \PHPUnit\Framework\TestCase { // Use regexp to ensure no property is suggested, because expected message is a prefix then and still passes $this->expectException(\Error::class); - $this->expectExceptionMessageRegExp("(Amp\\\\Test\\\\StructTestFixture property \"foofoofoofoofoofoofoofoobar\" does not exist$)"); + $this->expectExceptionMessageRegExp("(Amp\\\\Test\\\\StructTestFixture property 'foofoofoofoofoofoofoofoobar' does not exist$)"); $struct = new StructTestFixture; $struct->foofoofoofoofoofoofoofoobar; diff --git a/test/TaskTest.php b/test/TaskTest.php index 45717f9..d5723b6 100644 --- a/test/TaskTest.php +++ b/test/TaskTest.php @@ -2,11 +2,11 @@ namespace Amp\Test; -use Amp\PHPUnit\TestCase; +use Concurrent\AsyncTestCase; use Concurrent\Task; use function Amp\delay; -class TaskTest extends TestCase +class TaskTest extends AsyncTestCase { public function testSequentialAwait(): void { diff --git a/test/TimeoutTest.php b/test/TimeoutTest.php index 5c4e0cb..06ec567 100644 --- a/test/TimeoutTest.php +++ b/test/TimeoutTest.php @@ -3,37 +3,34 @@ namespace Amp\Test; use Amp\TimeoutException; +use Concurrent\AsyncTestCase; use Concurrent\Deferred; use Concurrent\Task; -use PHPUnit\Framework\TestCase; use function Amp\delay; use function Amp\timeout; -class TimeoutTest extends TestCase +class TimeoutTest extends AsyncTestCase { - public function testSuccessfulPromise(): void + public function testSuccessful(): void { $value = 1; - $awaitable = Deferred::value($value); - $awaitable = timeout($awaitable, 100); - - $this->assertSame($value, Task::await($awaitable)); + $this->assertSame($value, timeout(Deferred::value($value), 100)); } - public function testFailedPromise(): void + public function testFailure(): void { $exception = new \Exception; $awaitable = Deferred::error($exception); - $awaitable = timeout($awaitable, 100); $this->expectExceptionObject($exception); - Task::await($awaitable); + + timeout($awaitable, 100); } /** - * @depends testSuccessfulPromise + * @depends testSuccessful */ public function testFastPending(): void { @@ -45,11 +42,11 @@ class TimeoutTest extends TestCase return $value; }); - $this->assertSame($value, Task::await(timeout($awaitable, 100))); + $this->assertSame($value, timeout($awaitable, 100)); } /** - * @depends testSuccessfulPromise + * @depends testSuccessful */ public function testSlowPending(): void { @@ -64,7 +61,7 @@ class TimeoutTest extends TestCase $this->expectException(TimeoutException::class); try { - Task::await(timeout($awaitable, 100)); + timeout($awaitable, 100); } finally { Task::await($awaitable); // await to clear pending watchers }