diff --git a/composer.json b/composer.json index 7962557..0147b1d 100644 --- a/composer.json +++ b/composer.json @@ -16,6 +16,9 @@ ], "prefer-stable": true, "minimum-stability": "dev", + "config": { + "secure-http": true + }, "require": { "php": "^7.1", "ext-curl": "*", @@ -30,7 +33,8 @@ "guzzlehttp/psr7": "^1.0", "php-http/client-integration-tests": "^2.0", "phpunit/phpunit": "^7.5", - "zendframework/zend-diactoros": "^2.0" + "zendframework/zend-diactoros": "^2.0", + "donatj/mock-webserver": "^2.0" }, "autoload": { "psr-4": { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 3f3b615..2c9938e 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -23,6 +23,10 @@ tests/Unit + + tests/Integration + + tests/Functional diff --git a/src/MultiRunner.php b/src/MultiRunner.php index 7a2c50c..d56cb28 100644 --- a/src/MultiRunner.php +++ b/src/MultiRunner.php @@ -14,6 +14,11 @@ */ class MultiRunner { + /** + * Timeout for curl_multi_select in seconds. + */ + private const SELECT_TIMEOUT = 0.1; + /** * cURL multi handle. * @@ -84,18 +89,27 @@ public function remove(PromiseCore $core): void public function wait(PromiseCore $targetCore = null): void { do { - $status = curl_multi_exec($this->multiHandle, $active); + if (curl_multi_select($this->multiHandle, self::SELECT_TIMEOUT) === -1) { + // See https://bugs.php.net/bug.php?id=61141 + usleep(250); + } + + do { + $status = curl_multi_exec($this->multiHandle, $active); + // TODO CURLM_CALL_MULTI_PERFORM never returned since cURL 7.20. + } while ($status === CURLM_CALL_MULTI_PERFORM); + $info = curl_multi_info_read($this->multiHandle); - if (false !== $info) { + if ($info !== false) { $core = $this->findCoreByHandle($info['handle']); - if (null === $core) { + if ($core === null) { // We have no promise for this handle. Drop it. curl_multi_remove_handle($this->multiHandle, $info['handle']); continue; } - if (CURLE_OK === $info['result']) { + if ($info['result'] === CURLM_OK) { $core->fulfill(); } else { $error = curl_error($core->getHandle()); @@ -108,7 +122,7 @@ public function wait(PromiseCore $targetCore = null): void return; } } - } while ($status === CURLM_CALL_MULTI_PERFORM || $active); + } while ($active); } /** diff --git a/tests/Integration/MultiRunnerTest.php b/tests/Integration/MultiRunnerTest.php new file mode 100644 index 0000000..b0d554f --- /dev/null +++ b/tests/Integration/MultiRunnerTest.php @@ -0,0 +1,91 @@ +start(); + } + + /** + * Cleanup environment after all tests. + */ + public static function tearDownAfterClass(): void + { + self::$server->stop(); + + parent::tearDownAfterClass(); + } + + public function testWait(): void + { + $runner = new MultiRunner(); + + $handle = $this->createCurlHandle('/'); + $core1 = $this->createConfiguredMock(PromiseCore::class, ['getHandle' => $handle]); + $core1 + ->expects(self::once()) + ->method('fulfill'); + + $handle = $this->createCurlHandle('/'); + $core2 = $this->createConfiguredMock(PromiseCore::class, ['getHandle' => $handle]); + $core2 + ->expects(self::once()) + ->method('fulfill'); + + $runner->add($core1); + $runner->add($core2); + + $runner->wait($core1); + $runner->wait($core2); + } + + /** + * Create cURL handle with given parameters. + * + * @param string $url Request URL relative to server root. + * + * @return resource + */ + private function createCurlHandle(string $url) + { + $handle = curl_init(); + self::assertNotFalse($handle); + + curl_setopt_array( + $handle, + [ + CURLOPT_URL => self::$server->getServerRoot() . $url + ] + ); + + return $handle; + } +}