diff --git a/README.md b/README.md index 1f37288..f382b34 100644 --- a/README.md +++ b/README.md @@ -333,6 +333,7 @@ MultiCurl::setProxyAuth($auth) MultiCurl::setProxyTunnel($tunnel = true) MultiCurl::setProxyType($type) MultiCurl::setRange($range) +MultiCurl::setRateLimit($rate_limit) MultiCurl::setReferer($referer) MultiCurl::setReferrer($referrer) MultiCurl::setRetry($mixed) diff --git a/src/Curl/MultiCurl.php b/src/Curl/MultiCurl.php index 973bbad..83b4603 100644 --- a/src/Curl/MultiCurl.php +++ b/src/Curl/MultiCurl.php @@ -15,6 +15,16 @@ class MultiCurl private $concurrency = 25; private $nextCurlId = 0; + private $rateLimit = null; + private $rateLimitEnabled = false; + private $rateLimitReached = false; + private $maxRequests = null; + private $interval = null; + private $intervalSeconds = null; + private $unit = null; + private $currentStartTime = null; + private $currentRequestCount = 0; + private $beforeSendCallback = null; private $successCallback = null; private $errorCallback = null; @@ -713,6 +723,57 @@ class MultiCurl $this->setOpt(CURLOPT_RANGE, $range); } + /** + * Set Rate Limit + * + * @access public + * @param $rate_limit string (e.g. "60/1m"). + */ + public function setRateLimit($rate_limit) + { + $rate_limit_pattern = + '/' . // delimiter + '^' . // assert start + '(\d+)' . // digit(s) + '\/' . // slash + '(\d+)?' . // digit(s), optional + '(s|m|h)' . // unit, s for seconds, m for minutes, h for hours + '$' . // assert end + '/' . // delimiter + 'i' . // case-insensitive matches + ''; + if (!preg_match($rate_limit_pattern, $rate_limit, $matches)) { + throw new \UnexpectedValueException( + 'rate limit must be formatted as $max_requests/$interval(s|m|h) ' . + '(e.g. "60/1m" for a maximum of 60 requests per 1 minute)' + ); + } + + $max_requests = (int)$matches['1']; + if ($matches['2'] === '') { + $interval = 1; + } else { + $interval = (int)$matches['2']; + } + $unit = strtolower($matches['3']); + + // Convert interval to seconds based on unit. + if ($unit === 's') { + $interval_seconds = $interval * 1; + } elseif ($unit === 'm') { + $interval_seconds = $interval * 60; + } elseif ($unit === 'h') { + $interval_seconds = $interval * 3600; + } + + $this->rateLimit = $max_requests . '/' . $interval . $unit; + $this->rateLimitEnabled = true; + $this->maxRequests = $max_requests; + $this->interval = $interval; + $this->intervalSeconds = $interval_seconds; + $this->unit = $unit; + } + /** * Set Referer * @@ -823,17 +884,21 @@ class MultiCurl } $this->isStarted = true; - - $concurrency = $this->concurrency; - if ($concurrency > count($this->curls)) { - $concurrency = count($this->curls); - } - - for ($i = 0; $i < $concurrency; $i++) { - $this->initHandle(array_shift($this->curls)); - } + $this->currentStartTime = microtime(true); + $this->currentRequestCount = 0; do { + while (count($this->curls) && + count($this->activeCurls) < $this->concurrency && + (!$this->rateLimitEnabled || $this->hasRequestQuota()) + ) { + $this->initHandle(); + } + + if ($this->rateLimitEnabled && !count($this->activeCurls) && !$this->hasRequestQuota()) { + $this->waitUntilRequestQuotaAvailable(); + } + // Wait for activity on any curl_multi connection when curl_multi_select (libcurl) fails to correctly block. // https://bugs.php.net/bug.php?id=63411 if (curl_multi_select($this->multiCurl) === -1) { @@ -842,7 +907,7 @@ class MultiCurl curl_multi_exec($this->multiCurl, $active); - while (!($info_array = curl_multi_info_read($this->multiCurl)) === false) { + while (($info_array = curl_multi_info_read($this->multiCurl)) !== false) { if ($info_array['msg'] === CURLMSG_DONE) { foreach ($this->activeCurls as $key => $curl) { if ($curl->curl === $info_array['handle']) { @@ -868,10 +933,7 @@ class MultiCurl // Remove completed instance from active curls. unset($this->activeCurls[$key]); - // Start new requests before removing the handle of the completed one. - while (count($this->curls) >= 1 && count($this->activeCurls) < $this->concurrency) { - $this->initHandle(array_shift($this->curls)); - } + // Remove handle of the completed instance. curl_multi_remove_handle($this->multiCurl, $curl->curl); // Clean up completed instance. @@ -883,11 +945,7 @@ class MultiCurl } } } - - if (!$active) { - $active = count($this->activeCurls); - } - } while ($active > 0); + } while ($active || count($this->activeCurls) || count($this->curls)); $this->isStarted = false; } @@ -993,8 +1051,17 @@ class MultiCurl * @param $curl * @throws \ErrorException */ - private function initHandle($curl) + private function initHandle() { + $curl = array_shift($this->curls); + if ($curl === null) { + return; + } + + // Add instance to list of active curls. + $this->currentRequestCount += 1; + $this->activeCurls[$curl->id] = $curl; + // Set callbacks if not already individually set. if ($curl->beforeSendCallback === null) { $curl->beforeSend($this->beforeSendCallback); @@ -1033,7 +1100,57 @@ class MultiCurl throw new \ErrorException('cURL multi add handle error: ' . curl_multi_strerror($curlm_error_code)); } - $this->activeCurls[$curl->id] = $curl; $curl->call($curl->beforeSendCallback); } + + /** + * Has Request Quota + * + * Checks if there is any available quota to make additional requests while + * rate limiting is enabled. + * + * @access private + */ + private function hasRequestQuota() + { + // Calculate if there's request quota since ratelimiting is enabled. + if ($this->rateLimitEnabled) { + // Determine if the limit of requests per interval has been reached. + if ($this->currentRequestCount >= $this->maxRequests) { + $elapsed_seconds = microtime(true) - $this->currentStartTime; + if ($elapsed_seconds <= $this->intervalSeconds) { + $this->rateLimitReached = true; + return false; + } elseif ($this->rateLimitReached) { + $this->rateLimitReached = false; + $this->currentStartTime = microtime(true); + $this->currentRequestCount = 0; + } + } + + return true; + } else { + return true; + } + } + + /** + * Wait Until Request Quota Available + * + * Waits until there is available request quota available based on the rate limit. + * + * @access private + */ + private function waitUntilRequestQuotaAvailable() + { + $sleep_until = $this->currentStartTime + $this->intervalSeconds; + $sleep_until_relative = $sleep_until - $this->currentStartTime; + $sleep_seconds = $sleep_until - microtime(true); + + // Avoid using time_sleep_until() as it appears to be less precise and not sleep long enough. + usleep($sleep_seconds * 1000000); + + $this->currentStartTime = microtime(true); + $this->currentRequestCount = 0; + } } diff --git a/tests/PHPCurlClass/Helper.php b/tests/PHPCurlClass/Helper.php index 02d2f52..bceb1a8 100644 --- a/tests/PHPCurlClass/Helper.php +++ b/tests/PHPCurlClass/Helper.php @@ -50,6 +50,16 @@ class Test $this->chainedRequest($first, $data); $this->chainedRequest($second, $data); } + + public static function getTestUrl($port) + { + if (getenv('PHP_CURL_CLASS_LOCAL_TEST') === 'yes' || + in_array(getenv('TRAVIS_PHP_VERSION'), array('7.0', '7.1', '7.2', '7.3', '7.4', 'nightly'))) { + return 'http://127.0.0.1:' . $port . '/'; + } else { + return self::TEST_URL; + } + } } function create_png() @@ -127,3 +137,11 @@ function remove_file_from_server($uploaded_file_path) { ))); assert(file_exists($uploaded_file_path) === false); } + +function get_multi_curl_property_value($instance, $property_name) +{ + $reflector = new \ReflectionClass('\Curl\MultiCurl'); + $property = $reflector->getProperty($property_name); + $property->setAccessible(true); + return $property->getValue($instance); +} diff --git a/tests/PHPCurlClass/PHPMultiCurlClassTest.php b/tests/PHPCurlClass/PHPMultiCurlClassTest.php index 639c67d..4c2eaeb 100644 --- a/tests/PHPCurlClass/PHPMultiCurlClassTest.php +++ b/tests/PHPCurlClass/PHPMultiCurlClassTest.php @@ -3421,4 +3421,1138 @@ class MultiCurlTest extends \PHPUnit\Framework\TestCase }); $multi_curl->start(); } + + public function testSetRateLimitUnits() + { + foreach (array( + array( + 'rate_limit' => '1/s', + 'expected' => array( + 'rate_limit' => '1/1s', + 'max_requests' => '1', + 'interval' => '1', + 'unit' => 's', + 'interval_seconds' => '1', + ), + ), + array( + 'rate_limit' => '1/1s', + 'expected' => array( + 'rate_limit' => '1/1s', + 'max_requests' => '1', + 'interval' => '1', + 'unit' => 's', + 'interval_seconds' => '1', + ), + ), + array( + 'rate_limit' => '10/60s', + 'expected' => array( + 'rate_limit' => '10/60s', + 'max_requests' => '10', + 'interval' => '60', + 'unit' => 's', + 'interval_seconds' => '60', + ), + ), + array( + 'rate_limit' => '1/m', + 'expected' => array( + 'rate_limit' => '1/1m', + 'max_requests' => '1', + 'interval' => '1', + 'unit' => 'm', + 'interval_seconds' => '60', + ), + ), + array( + 'rate_limit' => '1/1m', + 'expected' => array( + 'rate_limit' => '1/1m', + 'max_requests' => '1', + 'interval' => '1', + 'unit' => 'm', + 'interval_seconds' => '60', + ), + ), + array( + 'rate_limit' => '5000/60m', + 'expected' => array( + 'rate_limit' => '5000/60m', + 'max_requests' => '5000', + 'interval' => '60', + 'unit' => 'm', + 'interval_seconds' => '3600', + ), + ), + array( + 'rate_limit' => '1/h', + 'expected' => array( + 'rate_limit' => '1/1h', + 'max_requests' => '1', + 'interval' => '1', + 'unit' => 'h', + 'interval_seconds' => '3600', + ), + ), + array( + 'rate_limit' => '1/1h', + 'expected' => array( + 'rate_limit' => '1/1h', + 'max_requests' => '1', + 'interval' => '1', + 'unit' => 'h', + 'interval_seconds' => '3600', + ), + ), + array( + 'rate_limit' => '5000/1h', + 'expected' => array( + 'rate_limit' => '5000/1h', + 'max_requests' => '5000', + 'interval' => '1', + 'unit' => 'h', + 'interval_seconds' => '3600', + ), + ), + array( + 'rate_limit' => '100000/24h', + 'expected' => array( + 'rate_limit' => '100000/24h', + 'max_requests' => '100000', + 'interval' => '24', + 'unit' => 'h', + 'interval_seconds' => '86400', + ), + ), + ) as $test) { + $multi_curl = new MultiCurl(); + $multi_curl->setRateLimit($test['rate_limit']); + + $this->assertEquals( + $test['expected']['rate_limit'], + \Helper\get_multi_curl_property_value($multi_curl, 'rateLimit') + ); + $this->assertEquals( + $test['expected']['max_requests'], + \Helper\get_multi_curl_property_value($multi_curl, 'maxRequests') + ); + $this->assertEquals( + $test['expected']['interval'], + \Helper\get_multi_curl_property_value($multi_curl, 'interval') + ); + $this->assertEquals( + $test['expected']['unit'], + \Helper\get_multi_curl_property_value($multi_curl, 'unit') + ); + $this->assertEquals( + $test['expected']['interval_seconds'], + \Helper\get_multi_curl_property_value($multi_curl, 'intervalSeconds') + ); + } + } + + public function testSetRateLimitPerSecond1() + { + // R0--| + // R1--| + // W---------------| + // R2--| + // R3--| + // W---------------| + // R4--| + // 0 1 2 3 4 5 6 7 8 9 10 11 12 + + $request_stats = array( + 'start' => '', + 'stop' => '', + ); + + $multi_curl = new MultiCurl(); + $multi_curl->setHeader('X-DEBUG-TEST', 'timeout'); + $multi_curl->setRateLimit('2/5s'); + $multi_curl->beforeSend(function ($instance) use (&$request_stats) { + $request_stats[$instance->id] = array(); + $request_stats[$instance->id]['start'] = microtime(true); + }); + $multi_curl->complete(function ($instance) use (&$request_stats) { + $request_stats[$instance->id]['stop'] = microtime(true); + }); + + $multi_curl->addGet(Test::getTestUrl('8001') . '?seconds=1'); + $multi_curl->addGet(Test::getTestUrl('8002') . '?seconds=1'); + $multi_curl->addGet(Test::getTestUrl('8003') . '?seconds=1'); + $multi_curl->addGet(Test::getTestUrl('8004') . '?seconds=1'); + $multi_curl->addGet(Test::getTestUrl('8005') . '?seconds=1'); + + $request_stats['start'] = microtime(true); + $multi_curl->start(); + $request_stats['stop'] = microtime(true); + + foreach ($request_stats as $key => &$value) { + if (is_int($key)) { + $value['start_diff'] = $value['start'] - $request_stats['start']; + $value['stop_diff'] = $value['stop'] - $request_stats['start']; + } + } + + // Assert R0 starts around 0 and not before. + $this->assertGreaterThanOrEqual(0, $request_stats['0']['start_diff']); + $this->assertLessThanOrEqual(0.5, $request_stats['0']['start_diff']); + // Assert R0 ends around 1. + $this->assertGreaterThanOrEqual(0.8, $request_stats['0']['stop_diff']); + $this->assertLessThanOrEqual(1.5, $request_stats['0']['stop_diff']); + + // Assert R1 starts around 0 and not before. + $this->assertGreaterThanOrEqual(0, $request_stats['1']['start_diff']); + $this->assertLessThanOrEqual(0.5, $request_stats['1']['start_diff']); + // Assert R1 ends around 1. + $this->assertGreaterThanOrEqual(0.8, $request_stats['1']['stop_diff']); + $this->assertLessThanOrEqual(1.5, $request_stats['1']['stop_diff']); + + // Assert R2 starts around 5 and not before. + $this->assertGreaterThanOrEqual(5, $request_stats['2']['start_diff']); + $this->assertLessThanOrEqual(5.5, $request_stats['2']['start_diff']); + // Assert R2 ends around 6. + $this->assertGreaterThanOrEqual(5.8, $request_stats['2']['stop_diff']); + $this->assertLessThanOrEqual(6.5, $request_stats['2']['stop_diff']); + + // Assert R3 starts around 5 and not before. + $this->assertGreaterThanOrEqual(5, $request_stats['3']['start_diff']); + $this->assertLessThanOrEqual(5.5, $request_stats['3']['start_diff']); + // Assert R3 ends around 6. + $this->assertGreaterThanOrEqual(5.8, $request_stats['3']['stop_diff']); + $this->assertLessThanOrEqual(6.5, $request_stats['3']['stop_diff']); + + // Assert R4 starts around 10 and not before. + $this->assertGreaterThanOrEqual(10, $request_stats['4']['start_diff']); + $this->assertLessThanOrEqual(10.5, $request_stats['4']['start_diff']); + // Assert R4 ends around 11. + $this->assertGreaterThanOrEqual(10.8, $request_stats['4']['stop_diff']); + $this->assertLessThanOrEqual(11.5, $request_stats['4']['stop_diff']); + } + + public function testSetRateLimitPerSecond2() + { + // R0--| + // R1------| + // W-----------| + // R2--| + // R3------| + // W-----------| + // R4--| + // 0 1 2 3 4 5 6 7 8 9 10 11 12 + + $request_stats = array( + 'start' => '', + 'stop' => '', + ); + + $multi_curl = new MultiCurl(); + $multi_curl->setHeader('X-DEBUG-TEST', 'timeout'); + $multi_curl->setRateLimit('2/5s'); + $multi_curl->beforeSend(function ($instance) use (&$request_stats) { + $request_stats[$instance->id] = array(); + $request_stats[$instance->id]['start'] = microtime(true); + }); + $multi_curl->complete(function ($instance) use (&$request_stats) { + $request_stats[$instance->id]['stop'] = microtime(true); + }); + + $multi_curl->addGet(Test::getTestUrl('8001') . '?seconds=1'); + $multi_curl->addGet(Test::getTestUrl('8002') . '?seconds=2'); + $multi_curl->addGet(Test::getTestUrl('8003') . '?seconds=1'); + $multi_curl->addGet(Test::getTestUrl('8004') . '?seconds=2'); + $multi_curl->addGet(Test::getTestUrl('8005') . '?seconds=1'); + + $request_stats['start'] = microtime(true); + $multi_curl->start(); + $request_stats['stop'] = microtime(true); + + foreach ($request_stats as $key => &$value) { + if (is_int($key)) { + $value['start_diff'] = $value['start'] - $request_stats['start']; + $value['stop_diff'] = $value['stop'] - $request_stats['start']; + } + } + + // Assert R0 starts around 0 and not before. + $this->assertGreaterThanOrEqual(0, $request_stats['0']['start_diff']); + $this->assertLessThanOrEqual(0.5, $request_stats['0']['start_diff']); + // Assert R0 ends around 1. + $this->assertGreaterThanOrEqual(0.8, $request_stats['0']['stop_diff']); + $this->assertLessThanOrEqual(1.5, $request_stats['0']['stop_diff']); + + // Assert R1 starts around 0 and not before. + $this->assertGreaterThanOrEqual(0, $request_stats['1']['start_diff']); + $this->assertLessThanOrEqual(0.5, $request_stats['1']['start_diff']); + // Assert R1 ends around 2. + $this->assertGreaterThanOrEqual(1.8, $request_stats['1']['stop_diff']); + $this->assertLessThanOrEqual(2.5, $request_stats['1']['stop_diff']); + + // Assert R2 starts around 5 and not before. + $this->assertGreaterThanOrEqual(5, $request_stats['2']['start_diff']); + $this->assertLessThanOrEqual(5.5, $request_stats['2']['start_diff']); + // Assert R2 ends around 6. + $this->assertGreaterThanOrEqual(5.8, $request_stats['2']['stop_diff']); + $this->assertLessThanOrEqual(6.5, $request_stats['2']['stop_diff']); + + // Assert R3 starts around 5 and not before. + $this->assertGreaterThanOrEqual(5, $request_stats['3']['start_diff']); + $this->assertLessThanOrEqual(5.5, $request_stats['3']['start_diff']); + // Assert R3 ends around 7. + $this->assertGreaterThanOrEqual(6.8, $request_stats['3']['stop_diff']); + $this->assertLessThanOrEqual(7.5, $request_stats['3']['stop_diff']); + + // Assert R4 starts around 10 and not before. + $this->assertGreaterThanOrEqual(10, $request_stats['4']['start_diff']); + $this->assertLessThanOrEqual(10.5, $request_stats['4']['start_diff']); + // Assert R4 ends around 11. + $this->assertGreaterThanOrEqual(10.8, $request_stats['4']['stop_diff']); + $this->assertLessThanOrEqual(11.5, $request_stats['4']['stop_diff']); + } + + public function testSetRateLimitPerSecond3() + { + // R0------| + // R1------------------| + // R2------| + // R3------------------| + // R4--| + // 0 1 2 3 4 5 6 7 8 9 10 11 12 + + $request_stats = array( + 'start' => '', + 'stop' => '', + ); + + $multi_curl = new MultiCurl(); + $multi_curl->setHeader('X-DEBUG-TEST', 'timeout'); + $multi_curl->setRateLimit('2/5s'); + $multi_curl->beforeSend(function ($instance) use (&$request_stats) { + $request_stats[$instance->id] = array(); + $request_stats[$instance->id]['start'] = microtime(true); + }); + $multi_curl->complete(function ($instance) use (&$request_stats) { + $request_stats[$instance->id]['stop'] = microtime(true); + }); + + $multi_curl->addGet(Test::getTestUrl('8001') . '?seconds=2'); + $multi_curl->addGet(Test::getTestUrl('8002') . '?seconds=5'); + $multi_curl->addGet(Test::getTestUrl('8003') . '?seconds=2'); + $multi_curl->addGet(Test::getTestUrl('8004') . '?seconds=5'); + $multi_curl->addGet(Test::getTestUrl('8005') . '?seconds=1'); + + $request_stats['start'] = microtime(true); + $multi_curl->start(); + $request_stats['stop'] = microtime(true); + + foreach ($request_stats as $key => &$value) { + if (is_int($key)) { + $value['start_diff'] = $value['start'] - $request_stats['start']; + $value['stop_diff'] = $value['stop'] - $request_stats['start']; + } + } + + // Assert R0 starts around 0 and not before. + $this->assertGreaterThanOrEqual(0, $request_stats['0']['start_diff']); + $this->assertLessThanOrEqual(0.5, $request_stats['0']['start_diff']); + // Assert R0 ends around 2. + $this->assertGreaterThanOrEqual(1.8, $request_stats['0']['stop_diff']); + $this->assertLessThanOrEqual(2.5, $request_stats['0']['stop_diff']); + + // Assert R1 starts around 0 and not before. + $this->assertGreaterThanOrEqual(0, $request_stats['1']['start_diff']); + $this->assertLessThanOrEqual(0.5, $request_stats['1']['start_diff']); + // Assert R1 ends around 5. + $this->assertGreaterThanOrEqual(4.8, $request_stats['1']['stop_diff']); + $this->assertLessThanOrEqual(5.5, $request_stats['1']['stop_diff']); + + // Assert R2 starts around 5 and not before. + $this->assertGreaterThanOrEqual(5, $request_stats['2']['start_diff']); + $this->assertLessThanOrEqual(5.5, $request_stats['2']['start_diff']); + // Assert R2 ends around 7. + $this->assertGreaterThanOrEqual(6.8, $request_stats['2']['stop_diff']); + $this->assertLessThanOrEqual(7.5, $request_stats['2']['stop_diff']); + + // Assert R3 starts around 5 and not before. + $this->assertGreaterThanOrEqual(5, $request_stats['3']['start_diff']); + $this->assertLessThanOrEqual(5.5, $request_stats['3']['start_diff']); + // Assert R3 ends around 10. + $this->assertGreaterThanOrEqual(9.8, $request_stats['3']['stop_diff']); + $this->assertLessThanOrEqual(10.5, $request_stats['3']['stop_diff']); + + // Assert R4 starts around 10 and not before. + $this->assertGreaterThanOrEqual(10, $request_stats['4']['start_diff']); + $this->assertLessThanOrEqual(10.5, $request_stats['4']['start_diff']); + // Assert R4 ends around 11. + $this->assertGreaterThanOrEqual(10.8, $request_stats['4']['stop_diff']); + $this->assertLessThanOrEqual(11.5, $request_stats['4']['stop_diff']); + } + + public function testSetRateLimitPerSecond4() + { + // R0------| + // R1----------------------------------| + // R2----------| + // R3----------------------| + // R4--| + // 0 1 2 3 4 5 6 7 8 9 10 11 12 + + $request_stats = array( + 'start' => '', + 'stop' => '', + ); + + $multi_curl = new MultiCurl(); + $multi_curl->setHeader('X-DEBUG-TEST', 'timeout'); + $multi_curl->setRateLimit('2/5s'); + $multi_curl->beforeSend(function ($instance) use (&$request_stats) { + $request_stats[$instance->id] = array(); + $request_stats[$instance->id]['start'] = microtime(true); + }); + $multi_curl->complete(function ($instance) use (&$request_stats) { + $request_stats[$instance->id]['stop'] = microtime(true); + }); + + $multi_curl->addGet(Test::getTestUrl('8001') . '?seconds=2'); + $multi_curl->addGet(Test::getTestUrl('8002') . '?seconds=9'); + $multi_curl->addGet(Test::getTestUrl('8003') . '?seconds=3'); + $multi_curl->addGet(Test::getTestUrl('8004') . '?seconds=6'); + $multi_curl->addGet(Test::getTestUrl('8005') . '?seconds=1'); + + $request_stats['start'] = microtime(true); + $multi_curl->start(); + $request_stats['stop'] = microtime(true); + + foreach ($request_stats as $key => &$value) { + if (is_int($key)) { + $value['start_diff'] = $value['start'] - $request_stats['start']; + $value['stop_diff'] = $value['stop'] - $request_stats['start']; + } + } + + // Assert R0 starts around 0 and not before. + $this->assertGreaterThanOrEqual(0, $request_stats['0']['start_diff']); + $this->assertLessThanOrEqual(0.5, $request_stats['0']['start_diff']); + // Assert R0 ends around 2. + $this->assertGreaterThanOrEqual(1.8, $request_stats['0']['stop_diff']); + $this->assertLessThanOrEqual(2.5, $request_stats['0']['stop_diff']); + + // Assert R1 starts around 0 and not before. + $this->assertGreaterThanOrEqual(0, $request_stats['1']['start_diff']); + $this->assertLessThanOrEqual(0.5, $request_stats['1']['start_diff']); + // Assert R1 ends around 9. + $this->assertGreaterThanOrEqual(8.8, $request_stats['1']['stop_diff']); + $this->assertLessThanOrEqual(9.5, $request_stats['1']['stop_diff']); + + // Assert R2 starts around 5 and not before. + $this->assertGreaterThanOrEqual(5, $request_stats['2']['start_diff']); + $this->assertLessThanOrEqual(5.5, $request_stats['2']['start_diff']); + // Assert R2 ends around 8. + $this->assertGreaterThanOrEqual(7.8, $request_stats['2']['stop_diff']); + $this->assertLessThanOrEqual(8.5 + 1, $request_stats['2']['stop_diff']); + + // Assert R3 starts around 5 and not before. + $this->assertGreaterThanOrEqual(5, $request_stats['3']['start_diff']); + $this->assertLessThanOrEqual(5.5, $request_stats['3']['start_diff']); + // Assert R3 ends around 11. + $this->assertGreaterThanOrEqual(10.8, $request_stats['3']['stop_diff']); + $this->assertLessThanOrEqual(11.5 + 1, $request_stats['3']['stop_diff']); + + // Assert R4 starts around 10 and not before. + $this->assertGreaterThanOrEqual(10, $request_stats['4']['start_diff']); + $this->assertLessThanOrEqual(10.5, $request_stats['4']['start_diff']); + // Assert R4 ends around 11. + $this->assertGreaterThanOrEqual(10.8, $request_stats['4']['stop_diff']); + $this->assertLessThanOrEqual(11.5 + 1, $request_stats['4']['stop_diff']); + } + + public function testSetRateLimitPerSecond5() + { + // R0--------------------------| + // R1--------------------------| + // R2------| + // R3------| + // W-----------| + // R4------| + // 0 1 2 3 4 5 6 7 8 9 10 11 12 + + $request_stats = array( + 'start' => '', + 'stop' => '', + ); + + $multi_curl = new MultiCurl(); + $multi_curl->setHeader('X-DEBUG-TEST', 'timeout'); + $multi_curl->setRateLimit('2/5s'); + $multi_curl->beforeSend(function ($instance) use (&$request_stats) { + $request_stats[$instance->id] = array(); + $request_stats[$instance->id]['start'] = microtime(true); + }); + $multi_curl->complete(function ($instance) use (&$request_stats) { + $request_stats[$instance->id]['stop'] = microtime(true); + }); + + $multi_curl->addGet(Test::getTestUrl('8001') . '?seconds=7'); + $multi_curl->addGet(Test::getTestUrl('8002') . '?seconds=7'); + $multi_curl->addGet(Test::getTestUrl('8003') . '?seconds=2'); + $multi_curl->addGet(Test::getTestUrl('8004') . '?seconds=2'); + $multi_curl->addGet(Test::getTestUrl('8005') . '?seconds=2'); + + $request_stats['start'] = microtime(true); + $multi_curl->start(); + $request_stats['stop'] = microtime(true); + + foreach ($request_stats as $key => &$value) { + if (is_int($key)) { + $value['start_diff'] = $value['start'] - $request_stats['start']; + $value['stop_diff'] = $value['stop'] - $request_stats['start']; + } + } + + // Assert R0 starts around 0 and not before. + $this->assertGreaterThanOrEqual(0, $request_stats['0']['start_diff']); + $this->assertLessThanOrEqual(0.5, $request_stats['0']['start_diff']); + // Assert R0 ends around 7. + $this->assertGreaterThanOrEqual(6.8, $request_stats['0']['stop_diff']); + $this->assertLessThanOrEqual(7.5, $request_stats['0']['stop_diff']); + + // Assert R1 starts around 0 and not before. + $this->assertGreaterThanOrEqual(0, $request_stats['1']['start_diff']); + $this->assertLessThanOrEqual(0.5, $request_stats['1']['start_diff']); + // Assert R1 ends around 7. + $this->assertGreaterThanOrEqual(6.8, $request_stats['1']['stop_diff']); + $this->assertLessThanOrEqual(7.5, $request_stats['1']['stop_diff']); + + // Assert R2 starts around 5 and not before. + $this->assertGreaterThanOrEqual(5, $request_stats['2']['start_diff']); + $this->assertLessThanOrEqual(5.5, $request_stats['2']['start_diff']); + // Assert R2 ends around 7. + $this->assertGreaterThanOrEqual(6.8, $request_stats['2']['stop_diff']); + $this->assertLessThanOrEqual(7.5 + 1, $request_stats['2']['stop_diff']); + + // Assert R3 starts around 5 and not before. + $this->assertGreaterThanOrEqual(5, $request_stats['3']['start_diff']); + $this->assertLessThanOrEqual(5.5, $request_stats['3']['start_diff']); + // Assert R3 ends around 7. + $this->assertGreaterThanOrEqual(6.8, $request_stats['3']['stop_diff']); + $this->assertLessThanOrEqual(7.5 + 1, $request_stats['3']['stop_diff']); + + // Assert R4 starts around 10 and not before. + $this->assertGreaterThanOrEqual(10, $request_stats['4']['start_diff']); + $this->assertLessThanOrEqual(10.5, $request_stats['4']['start_diff']); + // Assert R4 ends around 12. + $this->assertGreaterThanOrEqual(11.8, $request_stats['4']['stop_diff']); + $this->assertLessThanOrEqual(12.5, $request_stats['4']['stop_diff']); + } + + public function testSetRateLimitPerSecond6() + { + // R0--------------------------| + // R1--------------------------| + // R2--------------| + // R3------| + // W---| + // R4------| + // 0 1 2 3 4 5 6 7 8 9 10 11 12 + + $request_stats = array( + 'start' => '', + 'stop' => '', + ); + + $multi_curl = new MultiCurl(); + $multi_curl->setHeader('X-DEBUG-TEST', 'timeout'); + $multi_curl->setRateLimit('2/5s'); + $multi_curl->beforeSend(function ($instance) use (&$request_stats) { + $request_stats[$instance->id] = array(); + $request_stats[$instance->id]['start'] = microtime(true); + }); + $multi_curl->complete(function ($instance) use (&$request_stats) { + $request_stats[$instance->id]['stop'] = microtime(true); + }); + + $multi_curl->addGet(Test::getTestUrl('8001') . '?seconds=7'); + $multi_curl->addGet(Test::getTestUrl('8002') . '?seconds=7'); + $multi_curl->addGet(Test::getTestUrl('8003') . '?seconds=4'); + $multi_curl->addGet(Test::getTestUrl('8004') . '?seconds=2'); + $multi_curl->addGet(Test::getTestUrl('8005') . '?seconds=2'); + + $request_stats['start'] = microtime(true); + $multi_curl->start(); + $request_stats['stop'] = microtime(true); + + foreach ($request_stats as $key => &$value) { + if (is_int($key)) { + $value['start_diff'] = $value['start'] - $request_stats['start']; + $value['stop_diff'] = $value['stop'] - $request_stats['start']; + } + } + + // Assert R0 starts around 0 and not before. + $this->assertGreaterThanOrEqual(0, $request_stats['0']['start_diff']); + $this->assertLessThanOrEqual(0.5, $request_stats['0']['start_diff']); + // Assert R0 ends around 7. + $this->assertGreaterThanOrEqual(6.8, $request_stats['0']['stop_diff']); + $this->assertLessThanOrEqual(7.5, $request_stats['0']['stop_diff']); + + // Assert R1 starts around 0 and not before. + $this->assertGreaterThanOrEqual(0, $request_stats['1']['start_diff']); + $this->assertLessThanOrEqual(0.5, $request_stats['1']['start_diff']); + // Assert R1 ends around 7. + $this->assertGreaterThanOrEqual(6.8, $request_stats['1']['stop_diff']); + $this->assertLessThanOrEqual(7.5, $request_stats['1']['stop_diff']); + + // Assert R2 starts around 5 and not before. + $this->assertGreaterThanOrEqual(5, $request_stats['2']['start_diff']); + $this->assertLessThanOrEqual(5.5, $request_stats['2']['start_diff']); + // Assert R2 ends around 9. + $this->assertGreaterThanOrEqual(8.8, $request_stats['2']['stop_diff']); + $this->assertLessThanOrEqual(9.5 + 1, $request_stats['2']['stop_diff']); + + // Assert R3 starts around 5 and not before. + $this->assertGreaterThanOrEqual(5, $request_stats['3']['start_diff']); + $this->assertLessThanOrEqual(5.5, $request_stats['3']['start_diff']); + // Assert R3 ends around 7. + $this->assertGreaterThanOrEqual(6.8, $request_stats['3']['stop_diff']); + $this->assertLessThanOrEqual(7.5 + 1, $request_stats['3']['stop_diff']); + + // Assert R4 starts around 10 and not before. + $this->assertGreaterThanOrEqual(10, $request_stats['4']['start_diff']); + $this->assertLessThanOrEqual(10.5, $request_stats['4']['start_diff']); + // Assert R4 ends around 12. + $this->assertGreaterThanOrEqual(11.8, $request_stats['4']['stop_diff']); + $this->assertLessThanOrEqual(12.5, $request_stats['4']['stop_diff']); + } + + public function testSetRateLimitPerSecond7() + { + // R0------| + // R1----------------------------------------------| + // R2----------------------| + // R3----------------------| + // R4------| + // R5------| + // 0 1 2 3 4 5 6 7 8 9 10 11 12 + + $request_stats = array( + 'start' => '', + 'stop' => '', + ); + + $multi_curl = new MultiCurl(); + $multi_curl->setHeader('X-DEBUG-TEST', 'timeout'); + $multi_curl->setRateLimit('2/5s'); + $multi_curl->beforeSend(function ($instance) use (&$request_stats) { + $request_stats[$instance->id] = array(); + $request_stats[$instance->id]['start'] = microtime(true); + }); + $multi_curl->complete(function ($instance) use (&$request_stats) { + $request_stats[$instance->id]['stop'] = microtime(true); + }); + + $multi_curl->addGet(Test::getTestUrl('8001') . '?seconds=2'); + $multi_curl->addGet(Test::getTestUrl('8002') . '?seconds=12'); + $multi_curl->addGet(Test::getTestUrl('8003') . '?seconds=6'); + $multi_curl->addGet(Test::getTestUrl('8004') . '?seconds=6'); + $multi_curl->addGet(Test::getTestUrl('8005') . '?seconds=2'); + $multi_curl->addGet(Test::getTestUrl('8006') . '?seconds=2'); + + $request_stats['start'] = microtime(true); + $multi_curl->start(); + $request_stats['stop'] = microtime(true); + + foreach ($request_stats as $key => &$value) { + if (is_int($key)) { + $value['start_diff'] = $value['start'] - $request_stats['start']; + $value['stop_diff'] = $value['stop'] - $request_stats['start']; + } + } + + // Assert R0 starts around 0 and not before. + $this->assertGreaterThanOrEqual(0, $request_stats['0']['start_diff']); + $this->assertLessThanOrEqual(0.5, $request_stats['0']['start_diff']); + // Assert R0 ends around 2. + $this->assertGreaterThanOrEqual(1.8, $request_stats['0']['stop_diff']); + $this->assertLessThanOrEqual(2.5, $request_stats['0']['stop_diff']); + + // Assert R1 starts around 0 and not before. + $this->assertGreaterThanOrEqual(0, $request_stats['1']['start_diff']); + $this->assertLessThanOrEqual(0.5, $request_stats['1']['start_diff']); + // Assert R1 ends around 12. + $this->assertGreaterThanOrEqual(11.8, $request_stats['1']['stop_diff']); + $this->assertLessThanOrEqual(12.5, $request_stats['1']['stop_diff']); + + // Assert R2 starts around 5 and not before. + $this->assertGreaterThanOrEqual(5, $request_stats['2']['start_diff']); + $this->assertLessThanOrEqual(5.5, $request_stats['2']['start_diff']); + // Assert R2 ends around 11. + $this->assertGreaterThanOrEqual(10.8, $request_stats['2']['stop_diff']); + $this->assertLessThanOrEqual(11.5 + 1, $request_stats['2']['stop_diff']); + + // Assert R3 starts around 5 and not before. + $this->assertGreaterThanOrEqual(5, $request_stats['3']['start_diff']); + $this->assertLessThanOrEqual(5.5, $request_stats['3']['start_diff']); + // Assert R3 ends around 11. + $this->assertGreaterThanOrEqual(10.8, $request_stats['3']['stop_diff']); + $this->assertLessThanOrEqual(11.5 + 1, $request_stats['3']['stop_diff']); + + // Assert R4 starts around 10 and not before. + $this->assertGreaterThanOrEqual(10, $request_stats['4']['start_diff']); + $this->assertLessThanOrEqual(10.5, $request_stats['4']['start_diff']); + // Assert R4 ends around 12. + $this->assertGreaterThanOrEqual(11.8, $request_stats['4']['stop_diff']); + $this->assertLessThanOrEqual(12.5 + 1, $request_stats['4']['stop_diff']); + + // Assert R5 starts around 10 and not before. + $this->assertGreaterThanOrEqual(10, $request_stats['5']['start_diff']); + $this->assertLessThanOrEqual(10.5, $request_stats['5']['start_diff']); + // Assert R5 ends around 12. + $this->assertGreaterThanOrEqual(11.8, $request_stats['5']['stop_diff']); + $this->assertLessThanOrEqual(12.5 + 1, $request_stats['5']['stop_diff']); + } + + public function testSetRateLimitPerSecond8() + { + // R0------------------------------| + // R1----------------------------------------------| + // R2--------------------------| + // R3--------------| + // R4--| + // R5------| + // 0 1 2 3 4 5 6 7 8 9 10 11 12 + + $request_stats = array( + 'start' => '', + 'stop' => '', + ); + + $multi_curl = new MultiCurl(); + $multi_curl->setHeader('X-DEBUG-TEST', 'timeout'); + $multi_curl->setRateLimit('2/5s'); + $multi_curl->beforeSend(function ($instance) use (&$request_stats) { + $request_stats[$instance->id] = array(); + $request_stats[$instance->id]['start'] = microtime(true); + }); + $multi_curl->complete(function ($instance) use (&$request_stats) { + $request_stats[$instance->id]['stop'] = microtime(true); + }); + + $multi_curl->addGet(Test::getTestUrl('8001') . '?seconds=8'); + $multi_curl->addGet(Test::getTestUrl('8002') . '?seconds=12'); + $multi_curl->addGet(Test::getTestUrl('8003') . '?seconds=7'); + $multi_curl->addGet(Test::getTestUrl('8004') . '?seconds=4'); + $multi_curl->addGet(Test::getTestUrl('8005') . '?seconds=1'); + $multi_curl->addGet(Test::getTestUrl('8006') . '?seconds=2'); + + $request_stats['start'] = microtime(true); + $multi_curl->start(); + $request_stats['stop'] = microtime(true); + + foreach ($request_stats as $key => &$value) { + if (is_int($key)) { + $value['start_diff'] = $value['start'] - $request_stats['start']; + $value['stop_diff'] = $value['stop'] - $request_stats['start']; + } + } + + // Assert R0 starts around 0 and not before. + $this->assertGreaterThanOrEqual(0, $request_stats['0']['start_diff']); + $this->assertLessThanOrEqual(0.5, $request_stats['0']['start_diff']); + // Assert R0 ends around 8. + $this->assertGreaterThanOrEqual(7.8, $request_stats['0']['stop_diff']); + $this->assertLessThanOrEqual(8.5, $request_stats['0']['stop_diff']); + + // Assert R1 starts around 0 and not before. + $this->assertGreaterThanOrEqual(0, $request_stats['1']['start_diff']); + $this->assertLessThanOrEqual(0.5, $request_stats['1']['start_diff']); + // Assert R1 ends around 12. + $this->assertGreaterThanOrEqual(11.8, $request_stats['1']['stop_diff']); + $this->assertLessThanOrEqual(12.5, $request_stats['1']['stop_diff']); + + // Assert R2 starts around 5 and not before. + $this->assertGreaterThanOrEqual(5, $request_stats['2']['start_diff']); + $this->assertLessThanOrEqual(5.5, $request_stats['2']['start_diff']); + // Assert R2 ends around 12. + $this->assertGreaterThanOrEqual(11.8, $request_stats['2']['stop_diff']); + $this->assertLessThanOrEqual(12.5 + 1, $request_stats['2']['stop_diff']); + + // Assert R3 starts around 5 and not before. + $this->assertGreaterThanOrEqual(5, $request_stats['3']['start_diff']); + $this->assertLessThanOrEqual(5.5, $request_stats['3']['start_diff']); + // Assert R3 ends around 9. + $this->assertGreaterThanOrEqual(8.8, $request_stats['3']['stop_diff']); + $this->assertLessThanOrEqual(9.5 + 1, $request_stats['3']['stop_diff']); + + // Assert R4 starts around 10 and not before. + $this->assertGreaterThanOrEqual(10, $request_stats['4']['start_diff']); + $this->assertLessThanOrEqual(10.5, $request_stats['4']['start_diff']); + // Assert R4 ends around 11. + $this->assertGreaterThanOrEqual(10.8, $request_stats['4']['stop_diff']); + $this->assertLessThanOrEqual(11.5, $request_stats['4']['stop_diff']); + + // Assert R5 starts around 10 and not before. + $this->assertGreaterThanOrEqual(10, $request_stats['5']['start_diff']); + $this->assertLessThanOrEqual(10.5, $request_stats['5']['start_diff']); + // Assert R5 ends around 12. + $this->assertGreaterThanOrEqual(11.8, $request_stats['5']['stop_diff']); + $this->assertLessThanOrEqual(12.5, $request_stats['5']['stop_diff']); + } + + public function testSetRateLimitPerSecond9() + { + // R0----------------------------------------------| + // R1----------------------------------------------| + // R2--------------------------| + // R3--------------------------| + // R4------| + // R5------| + // 0 1 2 3 4 5 6 7 8 9 10 11 12 + + $request_stats = array( + 'start' => '', + 'stop' => '', + ); + + $multi_curl = new MultiCurl(); + $multi_curl->setHeader('X-DEBUG-TEST', 'timeout'); + $multi_curl->setRateLimit('2/5s'); + $multi_curl->beforeSend(function ($instance) use (&$request_stats) { + $request_stats[$instance->id] = array(); + $request_stats[$instance->id]['start'] = microtime(true); + }); + $multi_curl->complete(function ($instance) use (&$request_stats) { + $request_stats[$instance->id]['stop'] = microtime(true); + }); + + $multi_curl->addGet(Test::getTestUrl('8001') . '?seconds=12'); + $multi_curl->addGet(Test::getTestUrl('8002') . '?seconds=12'); + $multi_curl->addGet(Test::getTestUrl('8003') . '?seconds=7'); + $multi_curl->addGet(Test::getTestUrl('8004') . '?seconds=7'); + $multi_curl->addGet(Test::getTestUrl('8005') . '?seconds=2'); + $multi_curl->addGet(Test::getTestUrl('8006') . '?seconds=2'); + + $request_stats['start'] = microtime(true); + $multi_curl->start(); + $request_stats['stop'] = microtime(true); + + foreach ($request_stats as $key => &$value) { + if (is_int($key)) { + $value['start_diff'] = $value['start'] - $request_stats['start']; + $value['stop_diff'] = $value['stop'] - $request_stats['start']; + } + } + + // Assert R0 starts around 0 and not before. + $this->assertGreaterThanOrEqual(0, $request_stats['0']['start_diff']); + $this->assertLessThanOrEqual(0.5, $request_stats['0']['start_diff']); + // Assert R0 ends around 12. + $this->assertGreaterThanOrEqual(11.8, $request_stats['0']['stop_diff']); + $this->assertLessThanOrEqual(12.5, $request_stats['0']['stop_diff']); + + // Assert R1 starts around 0 and not before. + $this->assertGreaterThanOrEqual(0, $request_stats['1']['start_diff']); + $this->assertLessThanOrEqual(0.5, $request_stats['1']['start_diff']); + // Assert R1 ends around 12. + $this->assertGreaterThanOrEqual(11.8, $request_stats['1']['stop_diff']); + $this->assertLessThanOrEqual(12.5, $request_stats['1']['stop_diff']); + + // Assert R2 starts around 5 and not before. + $this->assertGreaterThanOrEqual(5, $request_stats['2']['start_diff']); + $this->assertLessThanOrEqual(5.5, $request_stats['2']['start_diff']); + // Assert R2 ends around 12. + $this->assertGreaterThanOrEqual(11.8, $request_stats['2']['stop_diff']); + $this->assertLessThanOrEqual(12.5 + 1, $request_stats['2']['stop_diff']); + + // Assert R3 starts around 5 and not before. + $this->assertGreaterThanOrEqual(5, $request_stats['3']['start_diff']); + $this->assertLessThanOrEqual(5.5, $request_stats['3']['start_diff']); + // Assert R3 ends around 12. + $this->assertGreaterThanOrEqual(11.8, $request_stats['3']['stop_diff']); + $this->assertLessThanOrEqual(12.5 + 1, $request_stats['3']['stop_diff']); + + // Assert R4 starts around 10 and not before. + $this->assertGreaterThanOrEqual(10, $request_stats['4']['start_diff']); + $this->assertLessThanOrEqual(10.5, $request_stats['4']['start_diff']); + // Assert R4 ends around 12. + $this->assertGreaterThanOrEqual(11.8, $request_stats['4']['stop_diff']); + $this->assertLessThanOrEqual(12.5 + 1, $request_stats['4']['stop_diff']); + + // Assert R5 starts around 10 and not before. + $this->assertGreaterThanOrEqual(10, $request_stats['5']['start_diff']); + $this->assertLessThanOrEqual(10.5, $request_stats['5']['start_diff']); + // Assert R5 ends around 12. + $this->assertGreaterThanOrEqual(11.8, $request_stats['5']['stop_diff']); + $this->assertLessThanOrEqual(12.5 + 1, $request_stats['5']['stop_diff']); + } + + public function testSetRateLimitPerSecondOnePerSecond() + { + $request_stats = array( + 'start' => '', + 'stop' => '', + ); + + $multi_curl = new MultiCurl(); + $multi_curl->setRateLimit('1/1s'); + $multi_curl->beforeSend(function ($instance) use (&$request_stats) { + $request_stats[$instance->id] = array(); + $request_stats[$instance->id]['start'] = microtime(true); + }); + $multi_curl->complete(function ($instance) use (&$request_stats) { + $request_stats[$instance->id]['stop'] = microtime(true); + }); + + $multi_curl->addGet(Test::getTestUrl('8001')); + $multi_curl->addGet(Test::getTestUrl('8002')); + $multi_curl->addGet(Test::getTestUrl('8003')); + + $request_stats['start'] = microtime(true); + $multi_curl->start(); + $request_stats['stop'] = microtime(true); + + foreach ($request_stats as $key => &$value) { + if (is_int($key)) { + $value['start_diff'] = $value['start'] - $request_stats['start']; + $value['stop_diff'] = $value['stop'] - $request_stats['start']; + } + } + + // Assert R0 starts around 0 and not before. + $this->assertGreaterThanOrEqual(0, $request_stats['0']['start_diff']); + $this->assertLessThanOrEqual(0.5, $request_stats['0']['start_diff']); + + // Assert R1 starts around 1 and not before. + $this->assertGreaterThanOrEqual(1, $request_stats['1']['start_diff']); + $this->assertLessThanOrEqual(1.5, $request_stats['1']['start_diff']); + + // Assert R2 starts around 2 and not before. + $this->assertGreaterThanOrEqual(2, $request_stats['2']['start_diff']); + $this->assertLessThanOrEqual(2.5, $request_stats['2']['start_diff']); + } + + public function testSetRateLimitTenPerThirtySecond() + { + $request_stats = array( + 'start' => '', + 'stop' => '', + ); + + $multi_curl = new MultiCurl(); + $multi_curl->setHeader('X-DEBUG-TEST', 'timeout'); + $multi_curl->setRateLimit('5/30s'); + $multi_curl->beforeSend(function ($instance) use (&$request_stats) { + $request_stats[$instance->id] = array(); + $request_stats[$instance->id]['start'] = microtime(true); + }); + $multi_curl->complete(function ($instance) use (&$request_stats) { + $request_stats[$instance->id]['stop'] = microtime(true); + }); + + $multi_curl->addGet(Test::getTestUrl('8003') . '?seconds=15'); + $multi_curl->addGet(Test::getTestUrl('8001') . '?seconds=25'); + $multi_curl->addGet(Test::getTestUrl('8006') . '?seconds=35'); + $multi_curl->addGet(Test::getTestUrl('8005') . '?seconds=45'); + $multi_curl->addGet(Test::getTestUrl('8004') . '?seconds=20'); + $multi_curl->addGet(Test::getTestUrl('8002') . '?seconds=10'); + + $request_stats['start'] = microtime(true); + $multi_curl->start(); + $request_stats['stop'] = microtime(true); + + foreach ($request_stats as $key => &$value) { + if (is_int($key)) { + $value['start_diff'] = $value['start'] - $request_stats['start']; + $value['stop_diff'] = $value['stop'] - $request_stats['start']; + } + } + + // Assert R5 starts around 30 and not before. + $this->assertGreaterThanOrEqual(30, $request_stats['5']['start_diff']); + $this->assertLessThanOrEqual(30.5, $request_stats['5']['start_diff']); + } + + public function testSetRateLimitOnePerOneMinute() + { + $request_stats = array( + 'start' => '', + 'stop' => '', + ); + + $multi_curl = new MultiCurl(); + $multi_curl->setHeader('X-DEBUG-TEST', 'timeout'); + $multi_curl->setRateLimit('1/1m'); + $multi_curl->beforeSend(function ($instance) use (&$request_stats) { + $request_stats[$instance->id] = array(); + $request_stats[$instance->id]['start'] = microtime(true); + }); + $multi_curl->complete(function ($instance) use (&$request_stats) { + $request_stats[$instance->id]['stop'] = microtime(true); + }); + + $multi_curl->addGet(Test::getTestUrl('8002') . '?seconds=30'); + $multi_curl->addGet(Test::getTestUrl('8001') . '?seconds=70'); + $multi_curl->addGet(Test::getTestUrl('8003') . '?seconds=10'); + + $request_stats['start'] = microtime(true); + $multi_curl->start(); + $request_stats['stop'] = microtime(true); + + foreach ($request_stats as $key => &$value) { + if (is_int($key)) { + $value['start_diff'] = $value['start'] - $request_stats['start']; + $value['stop_diff'] = $value['stop'] - $request_stats['start']; + } + } + + // Assert R1 starts around 60 and not before. + $this->assertGreaterThanOrEqual(60, $request_stats['1']['start_diff']); + $this->assertLessThanOrEqual(60.5, $request_stats['1']['start_diff']); + // Assert R2 starts around 120 and not before. + $this->assertGreaterThanOrEqual(120, $request_stats['2']['start_diff']); + $this->assertLessThanOrEqual(120.5, $request_stats['2']['start_diff']); + } + + public function testSetRateLimitThreePerOneMinute() + { + $request_stats = array( + 'start' => '', + 'stop' => '', + ); + + $multi_curl = new MultiCurl(); + $multi_curl->setHeader('X-DEBUG-TEST', 'timeout'); + $multi_curl->setRateLimit('3/1m'); + $multi_curl->beforeSend(function ($instance) use (&$request_stats) { + $request_stats[$instance->id] = array(); + $request_stats[$instance->id]['start'] = microtime(true); + }); + $multi_curl->complete(function ($instance) use (&$request_stats) { + $request_stats[$instance->id]['stop'] = microtime(true); + }); + + $multi_curl->addGet(Test::getTestUrl('8004') . '?seconds=20'); + $multi_curl->addGet(Test::getTestUrl('8002') . '?seconds=65'); + $multi_curl->addGet(Test::getTestUrl('8001') . '?seconds=45'); + $multi_curl->addGet(Test::getTestUrl('8003') . '?seconds=10'); + + $request_stats['start'] = microtime(true); + $multi_curl->start(); + $request_stats['stop'] = microtime(true); + + foreach ($request_stats as $key => &$value) { + if (is_int($key)) { + $value['start_diff'] = $value['start'] - $request_stats['start']; + $value['stop_diff'] = $value['stop'] - $request_stats['start']; + } + } + + // Assert R3 starts around 60 and not before. + $this->assertGreaterThanOrEqual(60, $request_stats['3']['start_diff']); + $this->assertLessThanOrEqual(60.5, $request_stats['3']['start_diff']); + } + + public function testSetRateLimitThreePerSixtyFiveSeconds() + { + $request_stats = array( + 'start' => '', + 'stop' => '', + ); + + $multi_curl = new MultiCurl(); + $multi_curl->setHeader('X-DEBUG-TEST', 'timeout'); + $multi_curl->setRateLimit('3/65s'); + $multi_curl->beforeSend(function ($instance) use (&$request_stats) { + $request_stats[$instance->id] = array(); + $request_stats[$instance->id]['start'] = microtime(true); + }); + $multi_curl->complete(function ($instance) use (&$request_stats) { + $request_stats[$instance->id]['stop'] = microtime(true); + }); + + $multi_curl->addGet(Test::getTestUrl('8001') . '?seconds=5'); + $multi_curl->addGet(Test::getTestUrl('8002') . '?seconds=5'); + $multi_curl->addGet(Test::getTestUrl('8003') . '?seconds=5'); + $multi_curl->addGet(Test::getTestUrl('8004') . '?seconds=5'); + + $request_stats['start'] = microtime(true); + $multi_curl->start(); + $request_stats['stop'] = microtime(true); + + foreach ($request_stats as $key => &$value) { + if (is_int($key)) { + $value['start_diff'] = $value['start'] - $request_stats['start']; + $value['stop_diff'] = $value['stop'] - $request_stats['start']; + } + } + + // Assert R3 starts around 65 and not before. + $this->assertGreaterThanOrEqual(65, $request_stats['3']['start_diff']); + $this->assertLessThanOrEqual(65.5, $request_stats['3']['start_diff']); + } + + public function testSetRateLimitTenPerTwoMinutes() + { + $request_stats = array( + 'start' => '', + 'stop' => '', + ); + + $multi_curl = new MultiCurl(); + $multi_curl->setHeader('X-DEBUG-TEST', 'timeout'); + $multi_curl->setRateLimit('10/2m'); + $multi_curl->beforeSend(function ($instance) use (&$request_stats) { + $request_stats[$instance->id] = array(); + $request_stats[$instance->id]['start'] = microtime(true); + }); + $multi_curl->complete(function ($instance) use (&$request_stats) { + $request_stats[$instance->id]['stop'] = microtime(true); + }); + + for ($i = 0; $i <= 30; $i++) { + $multi_curl->addGet(Test::TEST_URL . '?seconds=1'); + } + + $request_stats['start'] = microtime(true); + $multi_curl->start(); + $request_stats['stop'] = microtime(true); + + foreach ($request_stats as $key => &$value) { + if (is_int($key)) { + $value['start_diff'] = $value['start'] - $request_stats['start']; + $value['stop_diff'] = $value['stop'] - $request_stats['start']; + } + } + + // 0-9 starts >= 0. + // 10-19 starts >= 120. + // 20-29 starts >= 240. + // 30-39 starts >= 360. + + // Assert R0 starts around 0 and not before. + $this->assertGreaterThanOrEqual(0, $request_stats['0']['start_diff']); + $this->assertLessThanOrEqual(0.5, $request_stats['0']['start_diff']); + // Assert R9 starts around 0 and not before. + $this->assertGreaterThanOrEqual(0, $request_stats['9']['start_diff']); + $this->assertLessThanOrEqual(0.5, $request_stats['9']['start_diff']); + + // Assert R10 starts around 120 and not before. + $this->assertGreaterThanOrEqual(120, $request_stats['10']['start_diff']); + $this->assertLessThanOrEqual(120.5, $request_stats['10']['start_diff']); + // Assert R19 starts around 120 and not before. + $this->assertGreaterThanOrEqual(120, $request_stats['19']['start_diff']); + $this->assertLessThanOrEqual(120.5, $request_stats['19']['start_diff']); + + // Assert R20 starts around 240. Allow for some drift. + $this->assertGreaterThanOrEqual(239, $request_stats['20']['start_diff']); + $this->assertLessThanOrEqual(241, $request_stats['20']['start_diff']); + // Assert R29 starts around 240. Allow for some drift. + $this->assertGreaterThanOrEqual(239, $request_stats['29']['start_diff']); + $this->assertLessThanOrEqual(241, $request_stats['29']['start_diff']); + + // Assert R30 starts around 360. Allow for some drift. + $this->assertGreaterThanOrEqual(359, $request_stats['30']['start_diff']);// + $this->assertLessThanOrEqual(361, $request_stats['30']['start_diff']); + } } diff --git a/tests/PHPCurlClass/server.php b/tests/PHPCurlClass/server.php index fc4ab7b..aec56d2 100644 --- a/tests/PHPCurlClass/server.php +++ b/tests/PHPCurlClass/server.php @@ -1,4 +1,6 @@ /dev/null & -pid="${!}" +# Let test server know this is a local test. +export PHP_CURL_CLASS_LOCAL_TEST="yes" + +# Start test servers. Run servers on different ports to allow simultaneous +# requests without blocking. +server_count=7 +declare -A pids +for i in $(seq 0 $(("${server_count}" - 1))); do + port=8000 + (( port += $i )) + + php -S "127.0.0.1:${port}" -t PHPCurlClass/ &> /dev/null & + pid="${!}" + + pids["${i}"]="${pid}" +done # Determine which phpunit to use. if [[ -f "../vendor/phpunit/phpunit/phpunit" ]]; then @@ -24,4 +37,8 @@ extra_args="${@}" --debug \ --verbose \ ${extra_args} -kill "${pid}" + +# Stop test servers. +for pid in "${pids[@]}"; do + kill "${pid}" +done diff --git a/tests/start_servers.sh b/tests/start_servers.sh deleted file mode 100755 index e4bb660..0000000 --- a/tests/start_servers.sh +++ /dev/null @@ -1,24 +0,0 @@ -screen_name="my_screen" -server_count=5 -# screen_binary="screen" -screen_binary="byobu-screen" - -# Start screen in detached mode with a session name. -screen -S "${screen_name}" -t "master" -d -m - -# Wait for screen to be ready before opening new sessions. -sleep 1 - -# Create tabs and send commands to each. -for i in $(seq 1 "${server_count}"); do - # Create tab. - screen -S "${screen_name}" -X "screen" -t "my_screen_${i}" - - # Start development server in tab. - port=8000 - (( port += $i )) - command="php -S 127.0.0.1:${port} -t PHPCurlClass/" - screen -S "${screen_name}" -p "my_screen_${i}" -X stuff "${command}"$'\n' -done - -screen -r "${screen_name}"