diff options
| author | Eole <josselin.35@live.fr> | 2016-01-21 10:29:26 +0100 |
|---|---|---|
| committer | Eole <josselin.35@live.fr> | 2016-01-21 10:29:26 +0100 |
| commit | a44cc1d2e3c0f147e91a5c052ac7fd879e34e706 (patch) | |
| tree | bdd6f72e0ba732c4fcc0479d1cfcf4d0baa5885d /server/vendor/guzzlehttp/promises | |
| parent | 35db27b0e62b4cdcb03b0d21bceb4efc769e6161 (diff) | |
Init Server Composer Components
Diffstat (limited to 'server/vendor/guzzlehttp/promises')
31 files changed, 4019 insertions, 0 deletions
diff --git a/server/vendor/guzzlehttp/promises/.gitignore b/server/vendor/guzzlehttp/promises/.gitignore new file mode 100644 index 0000000..83ec41e --- /dev/null +++ b/server/vendor/guzzlehttp/promises/.gitignore @@ -0,0 +1,11 @@ +phpunit.xml +composer.phar +composer.lock +composer-test.lock +vendor/ +build/artifacts/ +artifacts/ +docs/_build +docs/*.pyc +.idea +.DS_STORE diff --git a/server/vendor/guzzlehttp/promises/.travis.yml b/server/vendor/guzzlehttp/promises/.travis.yml new file mode 100644 index 0000000..4f4d2b8 --- /dev/null +++ b/server/vendor/guzzlehttp/promises/.travis.yml @@ -0,0 +1,19 @@ +language: php + +php: + - 5.5 + - 5.6 + - 7.0 + - hhvm + +sudo: false + +install: + - travis_retry composer install --no-interaction --prefer-source + +script: make test + +matrix: + allow_failures: + - php: hhvm + fast_finish: true diff --git a/server/vendor/guzzlehttp/promises/CHANGELOG.md b/server/vendor/guzzlehttp/promises/CHANGELOG.md new file mode 100644 index 0000000..4031cb8 --- /dev/null +++ b/server/vendor/guzzlehttp/promises/CHANGELOG.md @@ -0,0 +1,21 @@ +# CHANGELOG + +## 1.0.3 - 2015-10-15 + +* Update EachPromise to immediately resolve when the underlying promise iterator + is empty. Previously, such a promise would throw an exception when its `wait` + function was called. + +## 1.0.2 - 2015-05-15 + +* Conditionally require functions.php. + +## 1.0.1 - 2015-06-24 + +* Updating EachPromise to call next on the underlying promise iterator as late + as possible to ensure that generators that generate new requests based on + callbacks are not iterated until after callbacks are invoked. + +## 1.0.0 - 2015-05-12 + +* Initial release diff --git a/server/vendor/guzzlehttp/promises/LICENSE b/server/vendor/guzzlehttp/promises/LICENSE new file mode 100644 index 0000000..581d95f --- /dev/null +++ b/server/vendor/guzzlehttp/promises/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015 Michael Dowling, https://github.com/mtdowling <mtdowling@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/server/vendor/guzzlehttp/promises/Makefile b/server/vendor/guzzlehttp/promises/Makefile new file mode 100644 index 0000000..8d5b3ef --- /dev/null +++ b/server/vendor/guzzlehttp/promises/Makefile @@ -0,0 +1,13 @@ +all: clean test + +test: + vendor/bin/phpunit + +coverage: + vendor/bin/phpunit --coverage-html=artifacts/coverage + +view-coverage: + open artifacts/coverage/index.html + +clean: + rm -rf artifacts/* diff --git a/server/vendor/guzzlehttp/promises/README.md b/server/vendor/guzzlehttp/promises/README.md new file mode 100644 index 0000000..c6780ab --- /dev/null +++ b/server/vendor/guzzlehttp/promises/README.md @@ -0,0 +1,501 @@ +# Guzzle Promises + +[Promises/A+](https://promisesaplus.com/) implementation that handles promise +chaining and resolution iteratively, allowing for "infinite" promise chaining +while keeping the stack size constant. Read [this blog post](https://blog.domenic.me/youre-missing-the-point-of-promises/) +for a general introduction to promises. + +- [Features](#features) +- [Quick start](#quick-start) +- [Synchronous wait](#synchronous-wait) +- [Cancellation](#cancellation) +- [API](#api) + - [Promise](#promise) + - [FulfilledPromise](#fulfilledpromise) + - [RejectedPromise](#rejectedpromise) +- [Promise interop](#promise-interop) +- [Implementation notes](#implementation-notes) + + +# Features + +- [Promises/A+](https://promisesaplus.com/) implementation. +- Promise resolution and chaining is handled iteratively, allowing for + "infinite" promise chaining. +- Promises have a synchronous `wait` method. +- Promises can be cancelled. +- Works with any object that has a `then` function. +- C# style async/await coroutine promises using + `GuzzleHttp\Promise\coroutine()`. + + +# Quick start + +A *promise* represents the eventual result of an asynchronous operation. The +primary way of interacting with a promise is through its `then` method, which +registers callbacks to receive either a promise's eventual value or the reason +why the promise cannot be fulfilled. + + +## Callbacks + +Callbacks are registered with the `then` method by providing an optional +`$onFulfilled` followed by an optional `$onRejected` function. + + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise->then( + // $onFulfilled + function ($value) { + echo 'The promise was fulfilled.'; + }, + // $onRejected + function ($reason) { + echo 'The promise was rejected.'; + } +); +``` + +*Resolving* a promise means that you either fulfill a promise with a *value* or +reject a promise with a *reason*. Resolving a promises triggers callbacks +registered with the promises's `then` method. These callbacks are triggered +only once and in the order in which they were added. + + +## Resolving a promise + +Promises are fulfilled using the `resolve($value)` method. Resolving a promise +with any value other than a `GuzzleHttp\Promise\RejectedPromise` will trigger +all of the onFulfilled callbacks (resolving a promise with a rejected promise +will reject the promise and trigger the `$onRejected` callbacks). + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise + ->then(function ($value) { + // Return a value and don't break the chain + return "Hello, " . $value; + }) + // This then is executed after the first then and receives the value + // returned from the first then. + ->then(function ($value) { + echo $value; + }); + +// Resolving the promise triggers the $onFulfilled callbacks and outputs +// "Hello, reader". +$promise->resolve('reader.'); +``` + + +## Promise forwarding + +Promises can be chained one after the other. Each then in the chain is a new +promise. The return value of of a promise is what's forwarded to the next +promise in the chain. Returning a promise in a `then` callback will cause the +subsequent promises in the chain to only be fulfilled when the returned promise +has been fulfilled. The next promise in the chain will be invoked with the +resolved value of the promise. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$nextPromise = new Promise(); + +$promise + ->then(function ($value) use ($nextPromise) { + echo $value; + return $nextPromise; + }) + ->then(function ($value) { + echo $value; + }); + +// Triggers the first callback and outputs "A" +$promise->resolve('A'); +// Triggers the second callback and outputs "B" +$nextPromise->resolve('B'); +``` + +## Promise rejection + +When a promise is rejected, the `$onRejected` callbacks are invoked with the +rejection reason. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise->then(null, function ($reason) { + echo $reason; +}); + +$promise->reject('Error!'); +// Outputs "Error!" +``` + +## Rejection forwarding + +If an exception is thrown in an `$onRejected` callback, subsequent +`$onRejected` callbacks are invoked with the thrown exception as the reason. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise->then(null, function ($reason) { + throw new \Exception($reason); +})->then(null, function ($reason) { + assert($reason->getMessage() === 'Error!'); +}); + +$promise->reject('Error!'); +``` + +You can also forward a rejection down the promise chain by returning a +`GuzzleHttp\Promise\RejectedPromise` in either an `$onFulfilled` or +`$onRejected` callback. + +```php +use GuzzleHttp\Promise\Promise; +use GuzzleHttp\Promise\RejectedPromise; + +$promise = new Promise(); +$promise->then(null, function ($reason) { + return new RejectedPromise($reason); +})->then(null, function ($reason) { + assert($reason === 'Error!'); +}); + +$promise->reject('Error!'); +``` + +If an exception is not thrown in a `$onRejected` callback and the callback +does not return a rejected promise, downstream `$onFulfilled` callbacks are +invoked using the value returned from the `$onRejected` callback. + +```php +use GuzzleHttp\Promise\Promise; +use GuzzleHttp\Promise\RejectedPromise; + +$promise = new Promise(); +$promise + ->then(null, function ($reason) { + return "It's ok"; + }) + ->then(function ($value) { + assert($value === "It's ok"); + }); + +$promise->reject('Error!'); +``` + +# Synchronous wait + +You can synchronously force promises to complete using a promise's `wait` +method. When creating a promise, you can provide a wait function that is used +to synchronously force a promise to complete. When a wait function is invoked +it is expected to deliver a value to the promise or reject the promise. If the +wait function does not deliver a value, then an exception is thrown. The wait +function provided to a promise constructor is invoked when the `wait` function +of the promise is called. + +```php +$promise = new Promise(function () use (&$promise) { + $promise->deliver('foo'); +}); + +// Calling wait will return the value of the promise. +echo $promise->wait(); // outputs "foo" +``` + +If an exception is encountered while invoking the wait function of a promise, +the promise is rejected with the exception and the exception is thrown. + +```php +$promise = new Promise(function () use (&$promise) { + throw new \Exception('foo'); +}); + +$promise->wait(); // throws the exception. +``` + +Calling `wait` on a promise that has been fulfilled will not trigger the wait +function. It will simply return the previously delivered value. + +```php +$promise = new Promise(function () { die('this is not called!'); }); +$promise->deliver('foo'); +echo $promise->wait(); // outputs "foo" +``` + +Calling `wait` on a promise that has been rejected will throw an exception. If +the rejection reason is an instance of `\Exception` the reason is thrown. +Otherwise, a `GuzzleHttp\Promise\RejectionException` is thrown and the reason +can be obtained by calling the `getReason` method of the exception. + +```php +$promise = new Promise(); +$promise->reject('foo'); +$promise->wait(); +``` + +> PHP Fatal error: Uncaught exception 'GuzzleHttp\Promise\RejectionException' with message 'The promise was rejected with value: foo' + + +## Unwrapping a promise + +When synchronously waiting on a promise, you are joining the state of the +promise into the current state of execution (i.e., return the value of the +promise if it was fulfilled or throw an exception if it was rejected). This is +called "unwrapping" the promise. Waiting on a promise will by default unwrap +the promise state. + +You can force a promise to resolve and *not* unwrap the state of the promise +by passing `false` to the first argument of the `wait` function: + +```php +$promise = new Promise(); +$promise->reject('foo'); +// This will not throw an exception. It simply ensures the promise has +// been resolved. +$promise->wait(false); +``` + +When unwrapping a promise, the delivered value of the promise will be waited +upon until the unwrapped value is not a promise. This means that if you resolve +promise A with a promise B and unwrap promise A, the value returned by the +wait function will be the value delivered to promise B. + +**Note**: when you do not unwrap the promise, no value is returned. + + +# Cancellation + +You can cancel a promise that has not yet been fulfilled using the `cancel()` +method of a promise. When creating a promise you can provide an optional +cancel function that when invoked cancels the action of computing a resolution +of the promise. + + +# API + + +## Promise + +When creating a promise object, you can provide an optional `$waitFn` and +`$cancelFn`. `$waitFn` is a function that is invoked with no arguments and is +expected to resolve the promise. `$cancelFn` is a function with no arguments +that is expected to cancel the computation of a promise. It is invoked when the +`cancel()` method of a promise is called. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise( + function () use (&$promise) { + $promise->resolve('waited'); + }, + function () { + // do something that will cancel the promise computation (e.g., close + // a socket, cancel a database query, etc...) + } +); + +assert('waited' === $promise->wait()); +``` + +A promise has the following methods: + +- `then(callable $onFulfilled, callable $onRejected) : PromiseInterface` + + Creates a new promise that is fulfilled or rejected when the promise is + resolved. + +- `wait($unwrap = true) : mixed` + + Synchronously waits on the promise to complete. + + `$unwrap` controls whether or not the value of the promise is returned for a + fulfilled promise or if an exception is thrown if the promise is rejected. + This is set to `true` by default. + +- `cancel()` + + Attempts to cancel the promise if possible. The promise being cancelled and + the parent most ancestor that has not yet been resolved will also be + cancelled. Any promises waiting on the cancelled promise to resolve will also + be cancelled. + +- `getState() : string` + + Returns the state of the promise. One of `pending`, `fulfilled`, or + `rejected`. + +- `resolve($value)` + + Fulfills the promise with the given `$value`. + +- `reject($reason)` + + Rejects the promise with the given `$reason`. + + +## FulfilledPromise + +A fulfilled promise can be created to represent a promise that has been +fulfilled. + +```php +use GuzzleHttp\Promise\FulfilledPromise; + +$promise = new FulfilledPromise('value'); + +// Fulfilled callbacks are immediately invoked. +$promise->then(function ($value) { + echo $value; +}); +``` + + +## RejectedPromise + +A rejected promise can be created to represent a promise that has been +rejected. + +```php +use GuzzleHttp\Promise\RejectedPromise; + +$promise = new RejectedPromise('Error'); + +// Rejected callbacks are immediately invoked. +$promise->then(null, function ($reason) { + echo $reason; +}); +``` + + +# Promise interop + +This library works with foreign promises that have a `then` method. This means +you can use Guzzle promises with [React promises](https://github.com/reactphp/promise) +for example. When a foreign promise is returned inside of a then method +callback, promise resolution will occur recursively. + +```php +// Create a React promise +$deferred = new React\Promise\Deferred(); +$reactPromise = $deferred->promise(); + +// Create a Guzzle promise that is fulfilled with a React promise. +$guzzlePromise = new \GuzzleHttp\Promise\Promise(); +$guzzlePromise->then(function ($value) use ($reactPromise) { + // Do something something with the value... + // Return the React promise + return $reactPromise; +}); +``` + +Please note that wait and cancel chaining is no longer possible when forwarding +a foreign promise. You will need to wrap a third-party promise with a Guzzle +promise in order to utilize wait and cancel functions with foreign promises. + + +## Event Loop Integration + +In order to keep the stack size constant, Guzzle promises are resolved +asynchronously using a task queue. When waiting on promises synchronously, the +task queue will be automatically run to ensure that the blocking promise and +any forwarded promises are resolved. When using promises asynchronously in an +event loop, you will need to run the task queue on each tick of the loop. If +you do not run the task queue, then promises will not be resolved. + +You can run the task queue using the `run()` method of the global task queue +instance. + +```php +// Get the global task queue +$queue = \GuzzleHttp\Promise\queue(); +$queue->run(); +``` + +For example, you could use Guzzle promises with React using a periodic timer: + +```php +$loop = React\EventLoop\Factory::create(); +$loop->addPeriodicTimer(0, [$queue, 'run']); +``` + +*TODO*: Perhaps adding a `futureTick()` on each tick would be faster? + + +# Implementation notes + + +## Promise resolution and chaining is handled iteratively + +By shuffling pending handlers from one owner to another, promises are +resolved iteratively, allowing for "infinite" then chaining. + +```php +<?php +require 'vendor/autoload.php'; + +use GuzzleHttp\Promise\Promise; + +$parent = new Promise(); +$p = $parent; + +for ($i = 0; $i < 1000; $i++) { + $p = $p->then(function ($v) { + // The stack size remains constant (a good thing) + echo xdebug_get_stack_depth() . ', '; + return $v + 1; + }); +} + +$parent->resolve(0); +var_dump($p->wait()); // int(1000) + +``` + +When a promise is fulfilled or rejected with a non-promise value, the promise +then takes ownership of the handlers of each child promise and delivers values +down the chain without using recursion. + +When a promise is resolved with another promise, the original promise transfers +all of its pending handlers to the new promise. When the new promise is +eventually resolved, all of the pending handlers are delivered the forwarded +value. + + +## A promise is the deferred. + +Some promise libraries implement promises using a deferred object to represent +a computation and a promise object to represent the delivery of the result of +the computation. This is a nice separation of computation and delivery because +consumers of the promise cannot modify the value that will be eventually +delivered. + +One side effect of being able to implement promise resolution and chaining +iteratively is that you need to be able for one promise to reach into the state +of another promise to shuffle around ownership of handlers. In order to achieve +this without making the handlers of a promise publicly mutable, a promise is +also the deferred value, allowing promises of the same parent class to reach +into and modify the private properties of promises of the same type. While this +does allow consumers of the value to modify the resolution or rejection of the +deferred, it is a small price to pay for keeping the stack size constant. + +```php +$promise = new Promise(); +$promise->then(function ($value) { echo $value; }); +// The promise is the deferred value, so you can deliver a value to it. +$promise->deliver('foo'); +// prints "foo" +``` diff --git a/server/vendor/guzzlehttp/promises/composer.json b/server/vendor/guzzlehttp/promises/composer.json new file mode 100644 index 0000000..f13844b --- /dev/null +++ b/server/vendor/guzzlehttp/promises/composer.json @@ -0,0 +1,31 @@ +{ + "name": "guzzlehttp/promises", + "type": "library", + "description": "Guzzle promises library", + "keywords": ["promise"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": ["src/functions_include.php"] + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + } +} diff --git a/server/vendor/guzzlehttp/promises/phpunit.xml.dist b/server/vendor/guzzlehttp/promises/phpunit.xml.dist new file mode 100644 index 0000000..500cd53 --- /dev/null +++ b/server/vendor/guzzlehttp/promises/phpunit.xml.dist @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<phpunit bootstrap="./tests/bootstrap.php" + colors="true"> + <testsuites> + <testsuite> + <directory>tests</directory> + </testsuite> + </testsuites> + <filter> + <whitelist> + <directory suffix=".php">src</directory> + <exclude> + <directory suffix="Interface.php">src/</directory> + </exclude> + </whitelist> + </filter> +</phpunit> diff --git a/server/vendor/guzzlehttp/promises/src/AggregateException.php b/server/vendor/guzzlehttp/promises/src/AggregateException.php new file mode 100644 index 0000000..6a5690c --- /dev/null +++ b/server/vendor/guzzlehttp/promises/src/AggregateException.php @@ -0,0 +1,16 @@ +<?php +namespace GuzzleHttp\Promise; + +/** + * Exception thrown when too many errors occur in the some() or any() methods. + */ +class AggregateException extends RejectionException +{ + public function __construct($msg, array $reasons) + { + parent::__construct( + $reasons, + sprintf('%s; %d rejected promises', $msg, count($reasons)) + ); + } +} diff --git a/server/vendor/guzzlehttp/promises/src/CancellationException.php b/server/vendor/guzzlehttp/promises/src/CancellationException.php new file mode 100644 index 0000000..cb360b8 --- /dev/null +++ b/server/vendor/guzzlehttp/promises/src/CancellationException.php @@ -0,0 +1,9 @@ +<?php +namespace GuzzleHttp\Promise; + +/** + * Exception that is set as the reason for a promise that has been cancelled. + */ +class CancellationException extends RejectionException +{ +} diff --git a/server/vendor/guzzlehttp/promises/src/EachPromise.php b/server/vendor/guzzlehttp/promises/src/EachPromise.php new file mode 100644 index 0000000..5918429 --- /dev/null +++ b/server/vendor/guzzlehttp/promises/src/EachPromise.php @@ -0,0 +1,207 @@ +<?php +namespace GuzzleHttp\Promise; + +/** + * Represents a promise that iterates over many promises and invokes + * side-effect functions in the process. + */ +class EachPromise implements PromisorInterface +{ + private $pending = []; + + /** @var \Iterator */ + private $iterable; + + /** @var callable|int */ + private $concurrency; + + /** @var callable */ + private $onFulfilled; + + /** @var callable */ + private $onRejected; + + /** @var Promise */ + private $aggregate; + + /** + * Configuration hash can include the following key value pairs: + * + * - fulfilled: (callable) Invoked when a promise fulfills. The function + * is invoked with three arguments: the fulfillment value, the index + * position from the iterable list of the promise, and the aggregate + * promise that manages all of the promises. The aggregate promise may + * be resolved from within the callback to short-circuit the promise. + * - rejected: (callable) Invoked when a promise is rejected. The + * function is invoked with three arguments: the rejection reason, the + * index position from the iterable list of the promise, and the + * aggregate promise that manages all of the promises. The aggregate + * promise may be resolved from within the callback to short-circuit + * the promise. + * - concurrency: (integer) Pass this configuration option to limit the + * allowed number of outstanding concurrently executing promises, + * creating a capped pool of promises. There is no limit by default. + * + * @param mixed $iterable Promises or values to iterate. + * @param array $config Configuration options + */ + public function __construct($iterable, array $config = []) + { + $this->iterable = iter_for($iterable); + + if (isset($config['concurrency'])) { + $this->concurrency = $config['concurrency']; + } + + if (isset($config['fulfilled'])) { + $this->onFulfilled = $config['fulfilled']; + } + + if (isset($config['rejected'])) { + $this->onRejected = $config['rejected']; + } + } + + public function promise() + { + if ($this->aggregate) { + return $this->aggregate; + } + + try { + $this->createPromise(); + $this->iterable->rewind(); + $this->refillPending(); + } catch (\Exception $e) { + $this->aggregate->reject($e); + } + + return $this->aggregate; + } + + private function createPromise() + { + $this->aggregate = new Promise(function () { + reset($this->pending); + if (empty($this->pending) && !$this->iterable->valid()) { + $this->aggregate->resolve(null); + return; + } + + // Consume a potentially fluctuating list of promises while + // ensuring that indexes are maintained (precluding array_shift). + while ($promise = current($this->pending)) { + next($this->pending); + $promise->wait(); + if ($this->aggregate->getState() !== PromiseInterface::PENDING) { + return; + } + } + }); + + // Clear the references when the promise is resolved. + $clearFn = function () { + $this->iterable = $this->concurrency = $this->pending = null; + $this->onFulfilled = $this->onRejected = null; + }; + + $this->aggregate->then($clearFn, $clearFn); + } + + private function refillPending() + { + if (!$this->concurrency) { + // Add all pending promises. + while ($this->addPending() && $this->advanceIterator()); + return; + } + + // Add only up to N pending promises. + $concurrency = is_callable($this->concurrency) + ? call_user_func($this->concurrency, count($this->pending)) + : $this->concurrency; + $concurrency = max($concurrency - count($this->pending), 0); + // Concurrency may be set to 0 to disallow new promises. + if (!$concurrency) { + return; + } + // Add the first pending promise. + $this->addPending(); + // Note this is special handling for concurrency=1 so that we do + // not advance the iterator after adding the first promise. This + // helps work around issues with generators that might not have the + // next value to yield until promise callbacks are called. + while (--$concurrency + && $this->advanceIterator() + && $this->addPending()); + } + + private function addPending() + { + if (!$this->iterable || !$this->iterable->valid()) { + return false; + } + + $promise = promise_for($this->iterable->current()); + $idx = $this->iterable->key(); + + $this->pending[$idx] = $promise->then( + function ($value) use ($idx) { + if ($this->onFulfilled) { + call_user_func( + $this->onFulfilled, $value, $idx, $this->aggregate + ); + } + $this->step($idx); + }, + function ($reason) use ($idx) { + if ($this->onRejected) { + call_user_func( + $this->onRejected, $reason, $idx, $this->aggregate + ); + } + $this->step($idx); + } + ); + + return true; + } + + private function advanceIterator() + { + try { + $this->iterable->next(); + return true; + } catch (\Exception $e) { + $this->aggregate->reject($e); + return false; + } + } + + private function step($idx) + { + // If the promise was already resolved, then ignore this step. + if ($this->aggregate->getState() !== PromiseInterface::PENDING) { + return; + } + + unset($this->pending[$idx]); + $this->advanceIterator(); + + if (!$this->checkIfFinished()) { + // Add more pending promises if possible. + $this->refillPending(); + } + } + + private function checkIfFinished() + { + if (!$this->pending && !$this->iterable->valid()) { + // Resolve the promise if there's nothing left to do. + $this->aggregate->resolve(null); + return true; + } + + return false; + } +} diff --git a/server/vendor/guzzlehttp/promises/src/FulfilledPromise.php b/server/vendor/guzzlehttp/promises/src/FulfilledPromise.php new file mode 100644 index 0000000..5596296 --- /dev/null +++ b/server/vendor/guzzlehttp/promises/src/FulfilledPromise.php @@ -0,0 +1,80 @@ +<?php +namespace GuzzleHttp\Promise; + +/** + * A promise that has been fulfilled. + * + * Thenning off of this promise will invoke the onFulfilled callback + * immediately and ignore other callbacks. + */ +class FulfilledPromise implements PromiseInterface +{ + private $value; + + public function __construct($value) + { + if (method_exists($value, 'then')) { + throw new \InvalidArgumentException( + 'You cannot create a FulfilledPromise with a promise.'); + } + + $this->value = $value; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + // Return itself if there is no onFulfilled function. + if (!$onFulfilled) { + return $this; + } + + $queue = queue(); + $p = new Promise([$queue, 'run']); + $value = $this->value; + $queue->add(static function () use ($p, $value, $onFulfilled) { + if ($p->getState() === self::PENDING) { + try { + $p->resolve($onFulfilled($value)); + } catch (\Exception $e) { + $p->reject($e); + } + } + }); + + return $p; + } + + public function otherwise(callable $onRejected) + { + return $this->then(null, $onRejected); + } + + public function wait($unwrap = true, $defaultDelivery = null) + { + return $unwrap ? $this->value : null; + } + + public function getState() + { + return self::FULFILLED; + } + + public function resolve($value) + { + if ($value !== $this->value) { + throw new \LogicException("Cannot resolve a fulfilled promise"); + } + } + + public function reject($reason) + { + throw new \LogicException("Cannot reject a fulfilled promise"); + } + + public function cancel() + { + // pass + } +} diff --git a/server/vendor/guzzlehttp/promises/src/Promise.php b/server/vendor/guzzlehttp/promises/src/Promise.php new file mode 100644 index 0000000..c2cf969 --- /dev/null +++ b/server/vendor/guzzlehttp/promises/src/Promise.php @@ -0,0 +1,268 @@ +<?php +namespace GuzzleHttp\Promise; + +/** + * Promises/A+ implementation that avoids recursion when possible. + * + * @link https://promisesaplus.com/ + */ +class Promise implements PromiseInterface +{ + private $state = self::PENDING; + private $result; + private $cancelFn; + private $waitFn; + private $waitList; + private $handlers = []; + + /** + * @param callable $waitFn Fn that when invoked resolves the promise. + * @param callable $cancelFn Fn that when invoked cancels the promise. + */ + public function __construct( + callable $waitFn = null, + callable $cancelFn = null + ) { + $this->waitFn = $waitFn; + $this->cancelFn = $cancelFn; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + if ($this->state === self::PENDING) { + $p = new Promise(null, [$this, 'cancel']); + $this->handlers[] = [$p, $onFulfilled, $onRejected]; + $p->waitList = $this->waitList; + $p->waitList[] = $this; + return $p; + } + + // Return a fulfilled promise and immediately invoke any callbacks. + if ($this->state === self::FULFILLED) { + return $onFulfilled + ? promise_for($this->result)->then($onFulfilled) + : promise_for($this->result); + } + + // It's either cancelled or rejected, so return a rejected promise + // and immediately invoke any callbacks. + $rejection = rejection_for($this->result); + return $onRejected ? $rejection->then(null, $onRejected) : $rejection; + } + + public function otherwise(callable $onRejected) + { + return $this->then(null, $onRejected); + } + + public function wait($unwrap = true) + { + $this->waitIfPending(); + + if (!$unwrap) { + return null; + } + + if ($this->result instanceof PromiseInterface) { + return $this->result->wait($unwrap); + } elseif ($this->state === self::FULFILLED) { + return $this->result; + } else { + // It's rejected so "unwrap" and throw an exception. + throw exception_for($this->result); + } + } + + public function getState() + { + return $this->state; + } + + public function cancel() + { + if ($this->state !== self::PENDING) { + return; + } + + $this->waitFn = $this->waitList = null; + + if ($this->cancelFn) { + $fn = $this->cancelFn; + $this->cancelFn = null; + try { + $fn(); + } catch (\Exception $e) { + $this->reject($e); + } + } + + // Reject the promise only if it wasn't rejected in a then callback. + if ($this->state === self::PENDING) { + $this->reject(new CancellationException('Promise has been cancelled')); + } + } + + public function resolve($value) + { + $this->settle(self::FULFILLED, $value); + } + + public function reject($reason) + { + $this->settle(self::REJECTED, $reason); + } + + private function settle($state, $value) + { + if ($this->state !== self::PENDING) { + // Ignore calls with the same resolution. + if ($state === $this->state && $value === $this->result) { + return; + } + throw $this->state === $state + ? new \LogicException("The promise is already {$state}.") + : new \LogicException("Cannot change a {$this->state} promise to {$state}"); + } + + if ($value === $this) { + throw new \LogicException('Cannot fulfill or reject a promise with itself'); + } + + // Clear out the state of the promise but stash the handlers. + $this->state = $state; + $this->result = $value; + $handlers = $this->handlers; + $this->handlers = null; + $this->waitList = $this->waitFn = null; + $this->cancelFn = null; + + if (!$handlers) { + return; + } + + // If the value was not a settled promise or a thenable, then resolve + // it in the task queue using the correct ID. + if (!method_exists($value, 'then')) { + $id = $state === self::FULFILLED ? 1 : 2; + // It's a success, so resolve the handlers in the queue. + queue()->add(static function () use ($id, $value, $handlers) { + foreach ($handlers as $handler) { + self::callHandler($id, $value, $handler); + } + }); + } elseif ($value instanceof Promise + && $value->getState() === self::PENDING + ) { + // We can just merge our handlers onto the next promise. + $value->handlers = array_merge($value->handlers, $handlers); + } else { + // Resolve the handlers when the forwarded promise is resolved. + $value->then( + static function ($value) use ($handlers) { + foreach ($handlers as $handler) { + self::callHandler(1, $value, $handler); + } + }, + static function ($reason) use ($handlers) { + foreach ($handlers as $handler) { + self::callHandler(2, $reason, $handler); + } + } + ); + } + } + + /** + * Call a stack of handlers using a specific callback index and value. + * + * @param int $index 1 (resolve) or 2 (reject). + * @param mixed $value Value to pass to the callback. + * @param array $handler Array of handler data (promise and callbacks). + * + * @return array Returns the next group to resolve. + */ + private static function callHandler($index, $value, array $handler) + { + /** @var PromiseInterface $promise */ + $promise = $handler[0]; + + // The promise may have been cancelled or resolved before placing + // this thunk in the queue. + if ($promise->getState() !== self::PENDING) { + return; + } + + try { + if (isset($handler[$index])) { + $promise->resolve($handler[$index]($value)); + } elseif ($index === 1) { + // Forward resolution values as-is. + $promise->resolve($value); + } else { + // Forward rejections down the chain. + $promise->reject($value); + } + } catch (\Exception $reason) { + $promise->reject($reason); + } + } + + private function waitIfPending() + { + if ($this->state !== self::PENDING) { + return; + } elseif ($this->waitFn) { + $this->invokeWaitFn(); + } elseif ($this->waitList) { + $this->invokeWaitList(); + } else { + // If there's not wait function, then reject the promise. + $this->reject('Cannot wait on a promise that has ' + . 'no internal wait function. You must provide a wait ' + . 'function when constructing the promise to be able to ' + . 'wait on a promise.'); + } + + queue()->run(); + + if ($this->state === self::PENDING) { + $this->reject('Invoking the wait callback did not resolve the promise'); + } + } + + private function invokeWaitFn() + { + try { + $wfn = $this->waitFn; + $this->waitFn = null; + $wfn(true); + } catch (\Exception $reason) { + if ($this->state === self::PENDING) { + // The promise has not been resolved yet, so reject the promise + // with the exception. + $this->reject($reason); + } else { + // The promise was already resolved, so there's a problem in + // the application. + throw $reason; + } + } + } + + private function invokeWaitList() + { + $waitList = $this->waitList; + $this->waitList = null; + + foreach ($waitList as $result) { + descend: + $result->waitIfPending(); + if ($result->result instanceof Promise) { + $result = $result->result; + goto descend; + } + } + } +} diff --git a/server/vendor/guzzlehttp/promises/src/PromiseInterface.php b/server/vendor/guzzlehttp/promises/src/PromiseInterface.php new file mode 100644 index 0000000..8f5f4b9 --- /dev/null +++ b/server/vendor/guzzlehttp/promises/src/PromiseInterface.php @@ -0,0 +1,93 @@ +<?php +namespace GuzzleHttp\Promise; + +/** + * A promise represents the eventual result of an asynchronous operation. + * + * The primary way of interacting with a promise is through its then method, + * which registers callbacks to receive either a promise’s eventual value or + * the reason why the promise cannot be fulfilled. + * + * @link https://promisesaplus.com/ + */ +interface PromiseInterface +{ + const PENDING = 'pending'; + const FULFILLED = 'fulfilled'; + const REJECTED = 'rejected'; + + /** + * Appends fulfillment and rejection handlers to the promise, and returns + * a new promise resolving to the return value of the called handler. + * + * @param callable $onFulfilled Invoked when the promise fulfills. + * @param callable $onRejected Invoked when the promise is rejected. + * + * @return PromiseInterface + */ + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ); + + /** + * Appends a rejection handler callback to the promise, and returns a new + * promise resolving to the return value of the callback if it is called, + * or to its original fulfillment value if the promise is instead + * fulfilled. + * + * @param callable $onRejected Invoked when the promise is rejected. + * + * @return PromiseInterface + */ + public function otherwise(callable $onRejected); + + /** + * Get the state of the promise ("pending", "rejected", or "fulfilled"). + * + * The three states can be checked against the constants defined on + * PromiseInterface: PENDING, FULFILLED, and REJECTED. + * + * @return string + */ + public function getState(); + + /** + * Resolve the promise with the given value. + * + * @param mixed $value + * @throws \RuntimeException if the promise is already resolved. + */ + public function resolve($value); + + /** + * Reject the promise with the given reason. + * + * @param mixed $reason + * @throws \RuntimeException if the promise is already resolved. + */ + public function reject($reason); + + /** + * Cancels the promise if possible. + * + * @link https://github.com/promises-aplus/cancellation-spec/issues/7 + */ + public function cancel(); + + /** + * Waits until the promise completes if possible. + * + * Pass $unwrap as true to unwrap the result of the promise, either + * returning the resolved value or throwing the rejected exception. + * + * If the promise cannot be waited on, then the promise will be rejected. + * + * @param bool $unwrap + * + * @return mixed + * @throws \LogicException if the promise has no wait function or if the + * promise does not settle after waiting. + */ + public function wait($unwrap = true); +} diff --git a/server/vendor/guzzlehttp/promises/src/PromisorInterface.php b/server/vendor/guzzlehttp/promises/src/PromisorInterface.php new file mode 100644 index 0000000..b07fe32 --- /dev/null +++ b/server/vendor/guzzlehttp/promises/src/PromisorInterface.php @@ -0,0 +1,15 @@ +<?php +namespace GuzzleHttp\Promise; + +/** + * Interface used with classes that return a promise. + */ +interface PromisorInterface +{ + /** + * Returns a promise. + * + * @return PromiseInterface + */ + public function promise(); +} diff --git a/server/vendor/guzzlehttp/promises/src/RejectedPromise.php b/server/vendor/guzzlehttp/promises/src/RejectedPromise.php new file mode 100644 index 0000000..bd499e6 --- /dev/null +++ b/server/vendor/guzzlehttp/promises/src/RejectedPromise.php @@ -0,0 +1,84 @@ +<?php +namespace GuzzleHttp\Promise; + +/** + * A promise that has been rejected. + * + * Thenning off of this promise will invoke the onRejected callback + * immediately and ignore other callbacks. + */ +class RejectedPromise implements PromiseInterface +{ + private $reason; + + public function __construct($reason) + { + if (method_exists($reason, 'then')) { + throw new \InvalidArgumentException( + 'You cannot create a RejectedPromise with a promise.'); + } + + $this->reason = $reason; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + // If there's no onRejected callback then just return self. + if (!$onRejected) { + return $this; + } + + $queue = queue(); + $reason = $this->reason; + $p = new Promise([$queue, 'run']); + $queue->add(static function () use ($p, $reason, $onRejected) { + if ($p->getState() === self::PENDING) { + try { + // Return a resolved promise if onRejected does not throw. + $p->resolve($onRejected($reason)); + } catch (\Exception $e) { + // onRejected threw, so return a rejected promise. + $p->reject($e); + } + } + }); + + return $p; + } + + public function otherwise(callable $onRejected) + { + return $this->then(null, $onRejected); + } + + public function wait($unwrap = true, $defaultDelivery = null) + { + if ($unwrap) { + throw exception_for($this->reason); + } + } + + public function getState() + { + return self::REJECTED; + } + + public function resolve($value) + { + throw new \LogicException("Cannot resolve a rejected promise"); + } + + public function reject($reason) + { + if ($reason !== $this->reason) { + throw new \LogicException("Cannot reject a rejected promise"); + } + } + + public function cancel() + { + // pass + } +} diff --git a/server/vendor/guzzlehttp/promises/src/RejectionException.php b/server/vendor/guzzlehttp/promises/src/RejectionException.php new file mode 100644 index 0000000..07c1136 --- /dev/null +++ b/server/vendor/guzzlehttp/promises/src/RejectionException.php @@ -0,0 +1,47 @@ +<?php +namespace GuzzleHttp\Promise; + +/** + * A special exception that is thrown when waiting on a rejected promise. + * + * The reason value is available via the getReason() method. + */ +class RejectionException extends \RuntimeException +{ + /** @var mixed Rejection reason. */ + private $reason; + + /** + * @param mixed $reason Rejection reason. + * @param string $description Optional description + */ + public function __construct($reason, $description = null) + { + $this->reason = $reason; + + $message = 'The promise was rejected'; + + if ($description) { + $message .= ' with reason: ' . $description; + } elseif (is_string($reason) + || (is_object($reason) && method_exists($reason, '__toString')) + ) { + $message .= ' with reason: ' . $this->reason; + } elseif ($reason instanceof \JsonSerializable) { + $message .= ' with reason: ' + . json_encode($this->reason, JSON_PRETTY_PRINT); + } + + parent::__construct($message); + } + + /** + * Returns the rejection reason. + * + * @return mixed + */ + public function getReason() + { + return $this->reason; + } +} diff --git a/server/vendor/guzzlehttp/promises/src/TaskQueue.php b/server/vendor/guzzlehttp/promises/src/TaskQueue.php new file mode 100644 index 0000000..5026363 --- /dev/null +++ b/server/vendor/guzzlehttp/promises/src/TaskQueue.php @@ -0,0 +1,79 @@ +<?php +namespace GuzzleHttp\Promise; + +/** + * A task queue that executes tasks in a FIFO order. + * + * This task queue class is used to settle promises asynchronously and + * maintains a constant stack size. You can use the task queue asynchronously + * by calling the `run()` function of the global task queue in an event loop. + * + * GuzzleHttp\Promise\queue()->run(); + */ +class TaskQueue +{ + private $enableShutdown = true; + private $queue = []; + + public function __construct($withShutdown = true) + { + if ($withShutdown) { + register_shutdown_function(function () { + if ($this->enableShutdown) { + // Only run the tasks if an E_ERROR didn't occur. + $err = error_get_last(); + if (!$err || ($err['type'] ^ E_ERROR)) { + $this->run(); + } + } + }); + } + } + + /** + * Returns true if the queue is empty. + * + * @return bool + */ + public function isEmpty() + { + return !$this->queue; + } + + /** + * Adds a task to the queue that will be executed the next time run is + * called. + * + * @param callable $task + */ + public function add(callable $task) + { + $this->queue[] = $task; + } + + /** + * Execute all of the pending task in the queue. + */ + public function run() + { + while ($task = array_shift($this->queue)) { + $task(); + } + } + + /** + * The task queue will be run and exhausted by default when the process + * exits IFF the exit is not the result of a PHP E_ERROR error. + * + * You can disable running the automatic shutdown of the queue by calling + * this function. If you disable the task queue shutdown process, then you + * MUST either run the task queue (as a result of running your event loop + * or manually using the run() method) or wait on each outstanding promise. + * + * Note: This shutdown will occur before any destructors are triggered. + */ + public function disableShutdown() + { + $this->enableShutdown = false; + } +} diff --git a/server/vendor/guzzlehttp/promises/src/functions.php b/server/vendor/guzzlehttp/promises/src/functions.php new file mode 100644 index 0000000..89c6569 --- /dev/null +++ b/server/vendor/guzzlehttp/promises/src/functions.php @@ -0,0 +1,495 @@ +<?php +namespace GuzzleHttp\Promise; + +/** + * Get the global task queue used for promise resolution. + * + * This task queue MUST be run in an event loop in order for promises to be + * settled asynchronously. It will be automatically run when synchronously + * waiting on a promise. + * + * <code> + * while ($eventLoop->isRunning()) { + * GuzzleHttp\Promise\queue()->run(); + * } + * </code> + * + * @return TaskQueue + */ +function queue() +{ + static $queue; + + if (!$queue) { + $queue = new TaskQueue(); + } + + return $queue; +} + +/** + * Adds a function to run in the task queue when it is next `run()` and returns + * a promise that is fulfilled or rejected with the result. + * + * @param callable $task Task function to run. + * + * @return PromiseInterface + */ +function task(callable $task) +{ + $queue = queue(); + $promise = new Promise([$queue, 'run']); + $queue->add(function () use ($task, $promise) { + try { + $promise->resolve($task()); + } catch (\Exception $e) { + $promise->reject($e); + } + }); + + return $promise; +} + +/** + * Creates a promise for a value if the value is not a promise. + * + * @param mixed $value Promise or value. + * + * @return PromiseInterface + */ +function promise_for($value) +{ + if ($value instanceof PromiseInterface) { + return $value; + } + + // Return a Guzzle promise that shadows the given promise. + if (method_exists($value, 'then')) { + $wfn = method_exists($value, 'wait') ? [$value, 'wait'] : null; + $cfn = method_exists($value, 'cancel') ? [$value, 'cancel'] : null; + $promise = new Promise($wfn, $cfn); + $value->then([$promise, 'resolve'], [$promise, 'reject']); + return $promise; + } + + return new FulfilledPromise($value); +} + +/** + * Creates a rejected promise for a reason if the reason is not a promise. If + * the provided reason is a promise, then it is returned as-is. + * + * @param mixed $reason Promise or reason. + * + * @return PromiseInterface + */ +function rejection_for($reason) +{ + if ($reason instanceof PromiseInterface) { + return $reason; + } + + return new RejectedPromise($reason); +} + +/** + * Create an exception for a rejected promise value. + * + * @param mixed $reason + * + * @return \Exception + */ +function exception_for($reason) +{ + return $reason instanceof \Exception + ? $reason + : new RejectionException($reason); +} + +/** + * Returns an iterator for the given value. + * + * @param mixed $value + * + * @return \Iterator + */ +function iter_for($value) +{ + if ($value instanceof \Iterator) { + return $value; + } elseif (is_array($value)) { + return new \ArrayIterator($value); + } else { + return new \ArrayIterator([$value]); + } +} + +/** + * Synchronously waits on a promise to resolve and returns an inspection state + * array. + * + * Returns a state associative array containing a "state" key mapping to a + * valid promise state. If the state of the promise is "fulfilled", the array + * will contain a "value" key mapping to the fulfilled value of the promise. If + * the promise is rejected, the array will contain a "reason" key mapping to + * the rejection reason of the promise. + * + * @param PromiseInterface $promise Promise or value. + * + * @return array + */ +function inspect(PromiseInterface $promise) +{ + try { + return [ + 'state' => PromiseInterface::FULFILLED, + 'value' => $promise->wait() + ]; + } catch (RejectionException $e) { + return ['state' => 'rejected', 'reason' => $e->getReason()]; + } catch (\Exception $e) { + return ['state' => 'rejected', 'reason' => $e]; + } +} + +/** + * Waits on all of the provided promises, but does not unwrap rejected promises + * as thrown exception. + * + * Returns an array of inspection state arrays. + * + * @param PromiseInterface[] $promises Traversable of promises to wait upon. + * + * @return array + * @see GuzzleHttp\Promise\inspect for the inspection state array format. + */ +function inspect_all($promises) +{ + $results = []; + foreach ($promises as $key => $promise) { + $results[$key] = inspect($promise); + } + + return $results; +} + +/** + * Waits on all of the provided promises and returns the fulfilled values. + * + * Returns an array that contains the value of each promise (in the same order + * the promises were provided). An exception is thrown if any of the promises + * are rejected. + * + * @param mixed $promises Iterable of PromiseInterface objects to wait on. + * + * @return array + * @throws \Exception on error + */ +function unwrap($promises) +{ + $results = []; + foreach ($promises as $key => $promise) { + $results[$key] = $promise->wait(); + } + + return $results; +} + +/** + * Given an array of promises, return a promise that is fulfilled when all the + * items in the array are fulfilled. + * + * The promise's fulfillment value is an array with fulfillment values at + * respective positions to the original array. If any promise in the array + * rejects, the returned promise is rejected with the rejection reason. + * + * @param mixed $promises Promises or values. + * + * @return Promise + */ +function all($promises) +{ + $results = []; + return each( + $promises, + function ($value, $idx) use (&$results) { + $results[$idx] = $value; + }, + function ($reason, $idx, Promise $aggregate) { + $aggregate->reject($reason); + } + )->then(function () use (&$results) { + ksort($results); + return $results; + }); +} + +/** + * Initiate a competitive race between multiple promises or values (values will + * become immediately fulfilled promises). + * + * When count amount of promises have been fulfilled, the returned promise is + * fulfilled with an array that contains the fulfillment values of the winners + * in order of resolution. + * + * This prommise is rejected with a {@see GuzzleHttp\Promise\AggregateException} + * if the number of fulfilled promises is less than the desired $count. + * + * @param int $count Total number of promises. + * @param mixed $promises Promises or values. + * + * @return Promise + */ +function some($count, $promises) +{ + $results = []; + $rejections = []; + + return each( + $promises, + function ($value, $idx, PromiseInterface $p) use (&$results, $count) { + if ($p->getState() !== PromiseInterface::PENDING) { + return; + } + $results[$idx] = $value; + if (count($results) >= $count) { + $p->resolve(null); + } + }, + function ($reason) use (&$rejections) { + $rejections[] = $reason; + } + )->then( + function () use (&$results, &$rejections, $count) { + if (count($results) !== $count) { + throw new AggregateException( + 'Not enough promises to fulfill count', + $rejections + ); + } + ksort($results); + return array_values($results); + } + ); +} + +/** + * Like some(), with 1 as count. However, if the promise fulfills, the + * fulfillment value is not an array of 1 but the value directly. + * + * @param mixed $promises Promises or values. + * + * @return PromiseInterface + */ +function any($promises) +{ + return some(1, $promises)->then(function ($values) { return $values[0]; }); +} + +/** + * Returns a promise that is fulfilled when all of the provided promises have + * been fulfilled or rejected. + * + * The returned promise is fulfilled with an array of inspection state arrays. + * + * @param mixed $promises Promises or values. + * + * @return Promise + * @see GuzzleHttp\Promise\inspect for the inspection state array format. + */ +function settle($promises) +{ + $results = []; + + return each( + $promises, + function ($value, $idx) use (&$results) { + $results[$idx] = ['state' => 'fulfilled', 'value' => $value]; + }, + function ($reason, $idx) use (&$results) { + $results[$idx] = ['state' => 'rejected', 'reason' => $reason]; + } + )->then(function () use (&$results) { + ksort($results); + return $results; + }); +} + +/** + * Given an iterator that yields promises or values, returns a promise that is + * fulfilled with a null value when the iterator has been consumed or the + * aggregate promise has been fulfilled or rejected. + * + * $onFulfilled is a function that accepts the fulfilled value, iterator + * index, and the aggregate promise. The callback can invoke any necessary side + * effects and choose to resolve or reject the aggregate promise if needed. + * + * $onRejected is a function that accepts the rejection reason, iterator + * index, and the aggregate promise. The callback can invoke any necessary side + * effects and choose to resolve or reject the aggregate promise if needed. + * + * @param mixed $iterable Iterator or array to iterate over. + * @param callable $onFulfilled + * @param callable $onRejected + * + * @return Promise + */ +function each( + $iterable, + callable $onFulfilled = null, + callable $onRejected = null +) { + return (new EachPromise($iterable, [ + 'fulfilled' => $onFulfilled, + 'rejected' => $onRejected + ]))->promise(); +} + +/** + * Like each, but only allows a certain number of outstanding promises at any + * given time. + * + * $concurrency may be an integer or a function that accepts the number of + * pending promises and returns a numeric concurrency limit value to allow for + * dynamic a concurrency size. + * + * @param mixed $iterable + * @param int|callable $concurrency + * @param callable $onFulfilled + * @param callable $onRejected + * + * @return mixed + */ +function each_limit( + $iterable, + $concurrency, + callable $onFulfilled = null, + callable $onRejected = null +) { + return (new EachPromise($iterable, [ + 'fulfilled' => $onFulfilled, + 'rejected' => $onRejected, + 'concurrency' => $concurrency + ]))->promise(); +} + +/** + * Like each_limit, but ensures that no promise in the given $iterable argument + * is rejected. If any promise is rejected, then the aggregate promise is + * rejected with the encountered rejection. + * + * @param mixed $iterable + * @param int|callable $concurrency + * @param callable $onFulfilled + * + * @return mixed + */ +function each_limit_all( + $iterable, + $concurrency, + callable $onFulfilled = null +) { + return each_limit( + $iterable, + $concurrency, + $onFulfilled, + function ($reason, $idx, PromiseInterface $aggregate) { + $aggregate->reject($reason); + } + ); +} + +/** + * Returns true if a promise is fulfilled. + * + * @param PromiseInterface $promise + * + * @return bool + */ +function is_fulfilled(PromiseInterface $promise) +{ + return $promise->getState() === PromiseInterface::FULFILLED; +} + +/** + * Returns true if a promise is rejected. + * + * @param PromiseInterface $promise + * + * @return bool + */ +function is_rejected(PromiseInterface $promise) +{ + return $promise->getState() === PromiseInterface::REJECTED; +} + +/** + * Returns true if a promise is fulfilled or rejected. + * + * @param PromiseInterface $promise + * + * @return bool + */ +function is_settled(PromiseInterface $promise) +{ + return $promise->getState() !== PromiseInterface::PENDING; +} + +/** + * Creates a promise that is resolved using a generator that yields values or + * promises (somewhat similar to C#'s async keyword). + * + * When called, the coroutine function will start an instance of the generator + * and returns a promise that is fulfilled with its final yielded value. + * + * Control is returned back to the generator when the yielded promise settles. + * This can lead to less verbose code when doing lots of sequential async calls + * with minimal processing in between. + * + * use GuzzleHttp\Promise; + * + * function createPromise($value) { + * return new Promise\FulfilledPromise($value); + * } + * + * $promise = Promise\coroutine(function () { + * $value = (yield createPromise('a')); + * try { + * $value = (yield createPromise($value . 'b')); + * } catch (\Exception $e) { + * // The promise was rejected. + * } + * yield $value . 'c'; + * }); + * + * // Outputs "abc" + * $promise->then(function ($v) { echo $v; }); + * + * @param callable $generatorFn Generator function to wrap into a promise. + * + * @return Promise + * @link https://github.com/petkaantonov/bluebird/blob/master/API.md#generators inspiration + */ +function coroutine(callable $generatorFn) +{ + $generator = $generatorFn(); + return __next_coroutine($generator->current(), $generator)->then(); +} + +/** @internal */ +function __next_coroutine($yielded, \Generator $generator) +{ + return promise_for($yielded)->then( + function ($value) use ($generator) { + $nextYield = $generator->send($value); + return $generator->valid() + ? __next_coroutine($nextYield, $generator) + : $value; + }, + function ($reason) use ($generator) { + $nextYield = $generator->throw(exception_for($reason)); + // The throw was caught, so keep iterating on the coroutine + return __next_coroutine($nextYield, $generator); + } + ); +} diff --git a/server/vendor/guzzlehttp/promises/src/functions_include.php b/server/vendor/guzzlehttp/promises/src/functions_include.php new file mode 100644 index 0000000..34cd171 --- /dev/null +++ b/server/vendor/guzzlehttp/promises/src/functions_include.php @@ -0,0 +1,6 @@ +<?php + +// Don't redefine the functions if included multiple times. +if (!function_exists('GuzzleHttp\Promise\promise_for')) { + require __DIR__ . '/functions.php'; +} diff --git a/server/vendor/guzzlehttp/promises/tests/AggregateExceptionTest.php b/server/vendor/guzzlehttp/promises/tests/AggregateExceptionTest.php new file mode 100644 index 0000000..eaa7703 --- /dev/null +++ b/server/vendor/guzzlehttp/promises/tests/AggregateExceptionTest.php @@ -0,0 +1,14 @@ +<?php +namespace GuzzleHttp\Promise\Tests; + +use GuzzleHttp\Promise\AggregateException; + +class AggregateExceptionTest extends \PHPUnit_Framework_TestCase +{ + public function testHasReason() + { + $e = new AggregateException('foo', ['baz', 'bar']); + $this->assertContains('foo', $e->getMessage()); + $this->assertEquals(['baz', 'bar'], $e->getReason()); + } +} diff --git a/server/vendor/guzzlehttp/promises/tests/EachPromiseTest.php b/server/vendor/guzzlehttp/promises/tests/EachPromiseTest.php new file mode 100644 index 0000000..0a0a851 --- /dev/null +++ b/server/vendor/guzzlehttp/promises/tests/EachPromiseTest.php @@ -0,0 +1,294 @@ +<?php +namespace GuzzleHttp\Promise\Tests; + +use GuzzleHttp\Promise\RejectedPromise; +use GuzzleHttp\Promise\FulfilledPromise; +use GuzzleHttp\Promise\Promise; +use GuzzleHttp\Promise\PromiseInterface; +use GuzzleHttp\Promise\EachPromise; +use GuzzleHttp\Promise as P; + +/** + * @covers GuzzleHttp\Promise\EachPromise + */ +class EachPromiseTest extends \PHPUnit_Framework_TestCase +{ + public function testReturnsSameInstance() + { + $each = new EachPromise([], ['concurrency' => 100]); + $this->assertSame($each->promise(), $each->promise()); + } + + public function testInvokesAllPromises() + { + $promises = [new Promise(), new Promise(), new Promise()]; + $called = []; + $each = new EachPromise($promises, [ + 'fulfilled' => function ($value) use (&$called) { + $called[] = $value; + } + ]); + $p = $each->promise(); + $promises[0]->resolve('a'); + $promises[1]->resolve('c'); + $promises[2]->resolve('b'); + P\queue()->run(); + $this->assertEquals(['a', 'c', 'b'], $called); + $this->assertEquals(PromiseInterface::FULFILLED, $p->getState()); + } + + public function testIsWaitable() + { + $a = new Promise(function () use (&$a) { $a->resolve('a'); }); + $b = new Promise(function () use (&$b) { $b->resolve('b'); }); + $called = []; + $each = new EachPromise([$a, $b], [ + 'fulfilled' => function ($value) use (&$called) { $called[] = $value; } + ]); + $p = $each->promise(); + $this->assertNull($p->wait()); + $this->assertEquals(PromiseInterface::FULFILLED, $p->getState()); + $this->assertEquals(['a', 'b'], $called); + } + + public function testCanResolveBeforeConsumingAll() + { + $called = 0; + $a = new Promise(function () use (&$a) { $a->resolve('a'); }); + $b = new Promise(function () { $this->fail(); }); + $each = new EachPromise([$a, $b], [ + 'fulfilled' => function ($value, $idx, Promise $aggregate) use (&$called) { + $this->assertSame($idx, 0); + $this->assertEquals('a', $value); + $aggregate->resolve(null); + $called++; + }, + 'rejected' => function (\Exception $reason) { + $this->fail($reason->getMessage()); + } + ]); + $p = $each->promise(); + $p->wait(); + $this->assertNull($p->wait()); + $this->assertEquals(1, $called); + $this->assertEquals(PromiseInterface::FULFILLED, $a->getState()); + $this->assertEquals(PromiseInterface::PENDING, $b->getState()); + // Resolving $b has no effect on the aggregate promise. + $b->resolve('foo'); + $this->assertEquals(1, $called); + } + + public function testLimitsPendingPromises() + { + $pending = [new Promise(), new Promise(), new Promise(), new Promise()]; + $promises = new \ArrayIterator($pending); + $each = new EachPromise($promises, ['concurrency' => 2]); + $p = $each->promise(); + $this->assertCount(2, $this->readAttribute($each, 'pending')); + $pending[0]->resolve('a'); + $this->assertCount(2, $this->readAttribute($each, 'pending')); + $this->assertTrue($promises->valid()); + $pending[1]->resolve('b'); + P\queue()->run(); + $this->assertCount(2, $this->readAttribute($each, 'pending')); + $this->assertTrue($promises->valid()); + $promises[2]->resolve('c'); + P\queue()->run(); + $this->assertCount(1, $this->readAttribute($each, 'pending')); + $this->assertEquals(PromiseInterface::PENDING, $p->getState()); + $promises[3]->resolve('d'); + P\queue()->run(); + $this->assertNull($this->readAttribute($each, 'pending')); + $this->assertEquals(PromiseInterface::FULFILLED, $p->getState()); + $this->assertFalse($promises->valid()); + } + + public function testDynamicallyLimitsPendingPromises() + { + $calls = []; + $pendingFn = function ($count) use (&$calls) { + $calls[] = $count; + return 2; + }; + $pending = [new Promise(), new Promise(), new Promise(), new Promise()]; + $promises = new \ArrayIterator($pending); + $each = new EachPromise($promises, ['concurrency' => $pendingFn]); + $p = $each->promise(); + $this->assertCount(2, $this->readAttribute($each, 'pending')); + $pending[0]->resolve('a'); + $this->assertCount(2, $this->readAttribute($each, 'pending')); + $this->assertTrue($promises->valid()); + $pending[1]->resolve('b'); + $this->assertCount(2, $this->readAttribute($each, 'pending')); + P\queue()->run(); + $this->assertTrue($promises->valid()); + $promises[2]->resolve('c'); + P\queue()->run(); + $this->assertCount(1, $this->readAttribute($each, 'pending')); + $this->assertEquals(PromiseInterface::PENDING, $p->getState()); + $promises[3]->resolve('d'); + P\queue()->run(); + $this->assertNull($this->readAttribute($each, 'pending')); + $this->assertEquals(PromiseInterface::FULFILLED, $p->getState()); + $this->assertEquals([0, 1, 1, 1], $calls); + $this->assertFalse($promises->valid()); + } + + public function testClearsReferencesWhenResolved() + { + $called = false; + $a = new Promise(function () use (&$a, &$called) { + $a->resolve('a'); + $called = true; + }); + $each = new EachPromise([$a], [ + 'concurrency' => function () { return 1; }, + 'fulfilled' => function () {}, + 'rejected' => function () {} + ]); + $each->promise()->wait(); + $this->assertNull($this->readAttribute($each, 'onFulfilled')); + $this->assertNull($this->readAttribute($each, 'onRejected')); + $this->assertNull($this->readAttribute($each, 'iterable')); + $this->assertNull($this->readAttribute($each, 'pending')); + $this->assertNull($this->readAttribute($each, 'concurrency')); + $this->assertTrue($called); + } + + public function testCanBeCancelled() + { + $this->markTestIncomplete(); + } + + public function testFulfillsImmediatelyWhenGivenAnEmptyIterator() + { + $each = new EachPromise(new \ArrayIterator([])); + $result = $each->promise()->wait(); + } + + public function testDoesNotBlowStackWithFulfilledPromises() + { + $pending = []; + for ($i = 0; $i < 100; $i++) { + $pending[] = new FulfilledPromise($i); + } + $values = []; + $each = new EachPromise($pending, [ + 'fulfilled' => function ($value) use (&$values) { + $values[] = $value; + } + ]); + $called = false; + $each->promise()->then(function () use (&$called) { + $called = true; + }); + $this->assertFalse($called); + P\queue()->run(); + $this->assertTrue($called); + $this->assertEquals(range(0, 99), $values); + } + + public function testDoesNotBlowStackWithRejectedPromises() + { + $pending = []; + for ($i = 0; $i < 100; $i++) { + $pending[] = new RejectedPromise($i); + } + $values = []; + $each = new EachPromise($pending, [ + 'rejected' => function ($value) use (&$values) { + $values[] = $value; + } + ]); + $called = false; + $each->promise()->then( + function () use (&$called) { $called = true; }, + function () { $this->fail('Should not have rejected.'); } + ); + $this->assertFalse($called); + P\queue()->run(); + $this->assertTrue($called); + $this->assertEquals(range(0, 99), $values); + } + + public function testReturnsPromiseForWhatever() + { + $called = []; + $arr = ['a', 'b']; + $each = new EachPromise($arr, [ + 'fulfilled' => function ($v) use (&$called) { $called[] = $v; } + ]); + $p = $each->promise(); + $this->assertNull($p->wait()); + $this->assertEquals(['a', 'b'], $called); + } + + public function testRejectsAggregateWhenNextThrows() + { + $iter = function () { + yield 'a'; + throw new \Exception('Failure'); + }; + $each = new EachPromise($iter()); + $p = $each->promise(); + $e = null; + $received = null; + $p->then(null, function ($reason) use (&$e) { $e = $reason; }); + P\queue()->run(); + $this->assertInstanceOf('Exception', $e); + $this->assertEquals('Failure', $e->getMessage()); + } + + public function testDoesNotCallNextOnIteratorUntilNeededWhenWaiting() + { + $results = []; + $values = [10]; + $remaining = 9; + $iter = function () use (&$values) { + while ($value = array_pop($values)) { + yield $value; + } + }; + $each = new EachPromise($iter(), [ + 'concurrency' => 1, + 'fulfilled' => function ($r) use (&$results, &$values, &$remaining) { + $results[] = $r; + if ($remaining > 0) { + $values[] = $remaining--; + } + } + ]); + $each->promise()->wait(); + $this->assertEquals(range(10, 1), $results); + } + + public function testDoesNotCallNextOnIteratorUntilNeededWhenAsync() + { + $firstPromise = new Promise(); + $pending = [$firstPromise]; + $values = [$firstPromise]; + $results = []; + $remaining = 9; + $iter = function () use (&$values) { + while ($value = array_pop($values)) { + yield $value; + } + }; + $each = new EachPromise($iter(), [ + 'concurrency' => 1, + 'fulfilled' => function ($r) use (&$results, &$values, &$remaining, &$pending) { + $results[] = $r; + if ($remaining-- > 0) { + $pending[] = $values[] = new Promise(); + } + } + ]); + $i = 0; + $each->promise(); + while ($promise = array_pop($pending)) { + $promise->resolve($i++); + P\queue()->run(); + } + $this->assertEquals(range(0, 9), $results); + } +} diff --git a/server/vendor/guzzlehttp/promises/tests/FulfilledPromiseTest.php b/server/vendor/guzzlehttp/promises/tests/FulfilledPromiseTest.php new file mode 100644 index 0000000..554c150 --- /dev/null +++ b/server/vendor/guzzlehttp/promises/tests/FulfilledPromiseTest.php @@ -0,0 +1,108 @@ +<?php +namespace GuzzleHttp\Tests\Promise; + +use GuzzleHttp\Promise\Promise; +use GuzzleHttp\Promise\FulfilledPromise; + +/** + * @covers GuzzleHttp\Promise\FulfilledPromise + */ +class FulfilledPromiseTest extends \PHPUnit_Framework_TestCase +{ + public function testReturnsValueWhenWaitedUpon() + { + $p = new FulfilledPromise('foo'); + $this->assertEquals('fulfilled', $p->getState()); + $this->assertEquals('foo', $p->wait(true)); + } + + public function testCannotCancel() + { + $p = new FulfilledPromise('foo'); + $this->assertEquals('fulfilled', $p->getState()); + $p->cancel(); + $this->assertEquals('foo', $p->wait()); + } + + /** + * @expectedException \LogicException + * @exepctedExceptionMessage Cannot resolve a fulfilled promise + */ + public function testCannotResolve() + { + $p = new FulfilledPromise('foo'); + $p->resolve('bar'); + } + + /** + * @expectedException \LogicException + * @exepctedExceptionMessage Cannot reject a fulfilled promise + */ + public function testCannotReject() + { + $p = new FulfilledPromise('foo'); + $p->reject('bar'); + } + + public function testCanResolveWithSameValue() + { + $p = new FulfilledPromise('foo'); + $p->resolve('foo'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testCannotResolveWithPromise() + { + new FulfilledPromise(new Promise()); + } + + public function testReturnsSelfWhenNoOnFulfilled() + { + $p = new FulfilledPromise('a'); + $this->assertSame($p, $p->then()); + } + + public function testAsynchronouslyInvokesOnFulfilled() + { + $p = new FulfilledPromise('a'); + $r = null; + $f = function ($d) use (&$r) { $r = $d; }; + $p2 = $p->then($f); + $this->assertNotSame($p, $p2); + $this->assertNull($r); + \GuzzleHttp\Promise\queue()->run(); + $this->assertEquals('a', $r); + } + + public function testReturnsNewRejectedWhenOnFulfilledFails() + { + $p = new FulfilledPromise('a'); + $f = function () { throw new \Exception('b'); }; + $p2 = $p->then($f); + $this->assertNotSame($p, $p2); + try { + $p2->wait(); + $this->fail(); + } catch (\Exception $e) { + $this->assertEquals('b', $e->getMessage()); + } + } + + public function testOtherwiseIsSugarForRejections() + { + $c = null; + $p = new FulfilledPromise('foo'); + $p->otherwise(function ($v) use (&$c) { $c = $v; }); + $this->assertNull($c); + } + + public function testDoesNotTryToFulfillTwiceDuringTrampoline() + { + $fp = new FulfilledPromise('a'); + $t1 = $fp->then(function ($v) { return $v . ' b'; }); + $t1->resolve('why!'); + $this->assertEquals('why!', $t1->wait()); + } +} diff --git a/server/vendor/guzzlehttp/promises/tests/NotPromiseInstance.php b/server/vendor/guzzlehttp/promises/tests/NotPromiseInstance.php new file mode 100644 index 0000000..6288aa8 --- /dev/null +++ b/server/vendor/guzzlehttp/promises/tests/NotPromiseInstance.php @@ -0,0 +1,50 @@ +<?php +namespace GuzzleHttp\Promise\Tests; + +use GuzzleHttp\Promise\Promise; +use GuzzleHttp\Promise\PromiseInterface; + +class NotPromiseInstance extends Thennable implements PromiseInterface +{ + private $nextPromise = null; + + public function __construct() + { + $this->nextPromise = new Promise(); + } + + public function then(callable $res = null, callable $rej = null) + { + return $this->nextPromise->then($res, $rej); + } + + public function otherwise(callable $onRejected) + { + return $this->then($onRejected); + } + + public function resolve($value) + { + $this->nextPromise->resolve($value); + } + + public function reject($reason) + { + $this->nextPromise->reject($reason); + } + + public function wait($unwrap = true, $defaultResolution = null) + { + + } + + public function cancel() + { + + } + + public function getState() + { + return $this->nextPromise->getState(); + } +} diff --git a/server/vendor/guzzlehttp/promises/tests/PromiseTest.php b/server/vendor/guzzlehttp/promises/tests/PromiseTest.php new file mode 100644 index 0000000..946c627 --- /dev/null +++ b/server/vendor/guzzlehttp/promises/tests/PromiseTest.php @@ -0,0 +1,579 @@ +<?php +namespace GuzzleHttp\Promise\Tests; + +use GuzzleHttp\Promise\CancellationException; +use GuzzleHttp\Promise as P; +use GuzzleHttp\Promise\Promise; +use GuzzleHttp\Promise\RejectedPromise; +use GuzzleHttp\Promise\RejectionException; + +/** + * @covers GuzzleHttp\Promise\Promise + */ +class PromiseTest extends \PHPUnit_Framework_TestCase +{ + /** + * @expectedException \LogicException + * @expectedExceptionMessage The promise is already fulfilled + */ + public function testCannotResolveNonPendingPromise() + { + $p = new Promise(); + $p->resolve('foo'); + $p->resolve('bar'); + $this->assertEquals('foo', $p->wait()); + } + + public function testCanResolveWithSameValue() + { + $p = new Promise(); + $p->resolve('foo'); + $p->resolve('foo'); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Cannot change a fulfilled promise to rejected + */ + public function testCannotRejectNonPendingPromise() + { + $p = new Promise(); + $p->resolve('foo'); + $p->reject('bar'); + $this->assertEquals('foo', $p->wait()); + } + + public function testCanRejectWithSameValue() + { + $p = new Promise(); + $p->reject('foo'); + $p->reject('foo'); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Cannot change a fulfilled promise to rejected + */ + public function testCannotRejectResolveWithSameValue() + { + $p = new Promise(); + $p->resolve('foo'); + $p->reject('foo'); + } + + public function testInvokesWaitFunction() + { + $p = new Promise(function () use (&$p) { $p->resolve('10'); }); + $this->assertEquals('10', $p->wait()); + } + + /** + * @expectedException \GuzzleHttp\Promise\RejectionException + */ + public function testRejectsAndThrowsWhenWaitFailsToResolve() + { + $p = new Promise(function () {}); + $p->wait(); + } + + /** + * @expectedException \GuzzleHttp\Promise\RejectionException + * @expectedExceptionMessage The promise was rejected with reason: foo + */ + public function testThrowsWhenUnwrapIsRejectedWithNonException() + { + $p = new Promise(function () use (&$p) { $p->reject('foo'); }); + $p->wait(); + } + + /** + * @expectedException \UnexpectedValueException + * @expectedExceptionMessage foo + */ + public function testThrowsWhenUnwrapIsRejectedWithException() + { + $e = new \UnexpectedValueException('foo'); + $p = new Promise(function () use (&$p, $e) { $p->reject($e); }); + $p->wait(); + } + + public function testDoesNotUnwrapExceptionsWhenDisabled() + { + $p = new Promise(function () use (&$p) { $p->reject('foo'); }); + $this->assertEquals('pending', $p->getState()); + $p->wait(false); + $this->assertEquals('rejected', $p->getState()); + } + + public function testRejectsSelfWhenWaitThrows() + { + $e = new \UnexpectedValueException('foo'); + $p = new Promise(function () use ($e) { throw $e; }); + try { + $p->wait(); + $this->fail(); + } catch (\UnexpectedValueException $e) { + $this->assertEquals('rejected', $p->getState()); + } + } + + public function testWaitsOnNestedPromises() + { + $p = new Promise(function () use (&$p) { $p->resolve('_'); }); + $p2 = new Promise(function () use (&$p2) { $p2->resolve('foo'); }); + $p3 = $p->then(function () use ($p2) { return $p2; }); + $this->assertSame('foo', $p3->wait()); + } + + /** + * @expectedException \GuzzleHttp\Promise\RejectionException + */ + public function testThrowsWhenWaitingOnPromiseWithNoWaitFunction() + { + $p = new Promise(); + $p->wait(); + } + + public function testThrowsWaitExceptionAfterPromiseIsResolved() + { + $p = new Promise(function () use (&$p) { + $p->reject('Foo!'); + throw new \Exception('Bar?'); + }); + + try { + $p->wait(); + $this->fail(); + } catch (\Exception $e) { + $this->assertEquals('Bar?', $e->getMessage()); + } + } + + public function testGetsActualWaitValueFromThen() + { + $p = new Promise(function () use (&$p) { $p->reject('Foo!'); }); + $p2 = $p->then(null, function ($reason) { + return new RejectedPromise([$reason]); + }); + + try { + $p2->wait(); + $this->fail('Should have thrown'); + } catch (RejectionException $e) { + $this->assertEquals(['Foo!'], $e->getReason()); + } + } + + public function testWaitBehaviorIsBasedOnLastPromiseInChain() + { + $p3 = new Promise(function () use (&$p3) { $p3->resolve('Whoop'); }); + $p2 = new Promise(function () use (&$p2, $p3) { $p2->reject($p3); }); + $p = new Promise(function () use (&$p, $p2) { $p->reject($p2); }); + $this->assertEquals('Whoop', $p->wait()); + } + + public function testCannotCancelNonPending() + { + $p = new Promise(); + $p->resolve('foo'); + $p->cancel(); + $this->assertEquals('fulfilled', $p->getState()); + } + + /** + * @expectedException \GuzzleHttp\Promise\CancellationException + */ + public function testCancelsPromiseWhenNoCancelFunction() + { + $p = new Promise(); + $p->cancel(); + $this->assertEquals('rejected', $p->getState()); + $p->wait(); + } + + public function testCancelsPromiseWithCancelFunction() + { + $called = false; + $p = new Promise(null, function () use (&$called) { $called = true; }); + $p->cancel(); + $this->assertEquals('rejected', $p->getState()); + $this->assertTrue($called); + } + + public function testCancelsUppermostPendingPromise() + { + $called = false; + $p1 = new Promise(null, function () use (&$called) { $called = true; }); + $p2 = $p1->then(function () {}); + $p3 = $p2->then(function () {}); + $p4 = $p3->then(function () {}); + $p3->cancel(); + $this->assertEquals('rejected', $p1->getState()); + $this->assertEquals('rejected', $p2->getState()); + $this->assertEquals('rejected', $p3->getState()); + $this->assertEquals('pending', $p4->getState()); + $this->assertTrue($called); + + try { + $p3->wait(); + $this->fail(); + } catch (CancellationException $e) { + $this->assertContains('cancelled', $e->getMessage()); + } + + try { + $p4->wait(); + $this->fail(); + } catch (CancellationException $e) { + $this->assertContains('cancelled', $e->getMessage()); + } + + $this->assertEquals('rejected', $p4->getState()); + } + + public function testCancelsChildPromises() + { + $called1 = $called2 = $called3 = false; + $p1 = new Promise(null, function () use (&$called1) { $called1 = true; }); + $p2 = new Promise(null, function () use (&$called2) { $called2 = true; }); + $p3 = new Promise(null, function () use (&$called3) { $called3 = true; }); + $p4 = $p2->then(function () use ($p3) { return $p3; }); + $p5 = $p4->then(function () { $this->fail(); }); + $p4->cancel(); + $this->assertEquals('pending', $p1->getState()); + $this->assertEquals('rejected', $p2->getState()); + $this->assertEquals('rejected', $p4->getState()); + $this->assertEquals('pending', $p5->getState()); + $this->assertFalse($called1); + $this->assertTrue($called2); + $this->assertFalse($called3); + } + + public function testRejectsPromiseWhenCancelFails() + { + $called = false; + $p = new Promise(null, function () use (&$called) { + $called = true; + throw new \Exception('e'); + }); + $p->cancel(); + $this->assertEquals('rejected', $p->getState()); + $this->assertTrue($called); + try { + $p->wait(); + $this->fail(); + } catch (\Exception $e) { + $this->assertEquals('e', $e->getMessage()); + } + } + + public function testCreatesPromiseWhenFulfilledAfterThen() + { + $p = new Promise(); + $carry = null; + $p2 = $p->then(function ($v) use (&$carry) { $carry = $v; }); + $this->assertNotSame($p, $p2); + $p->resolve('foo'); + P\queue()->run(); + + $this->assertEquals('foo', $carry); + } + + public function testCreatesPromiseWhenFulfilledBeforeThen() + { + $p = new Promise(); + $p->resolve('foo'); + $carry = null; + $p2 = $p->then(function ($v) use (&$carry) { $carry = $v; }); + $this->assertNotSame($p, $p2); + $this->assertNull($carry); + \GuzzleHttp\Promise\queue()->run(); + $this->assertEquals('foo', $carry); + } + + public function testCreatesPromiseWhenFulfilledWithNoCallback() + { + $p = new Promise(); + $p->resolve('foo'); + $p2 = $p->then(); + $this->assertNotSame($p, $p2); + $this->assertInstanceOf('GuzzleHttp\Promise\FulfilledPromise', $p2); + } + + public function testCreatesPromiseWhenRejectedAfterThen() + { + $p = new Promise(); + $carry = null; + $p2 = $p->then(null, function ($v) use (&$carry) { $carry = $v; }); + $this->assertNotSame($p, $p2); + $p->reject('foo'); + P\queue()->run(); + $this->assertEquals('foo', $carry); + } + + public function testCreatesPromiseWhenRejectedBeforeThen() + { + $p = new Promise(); + $p->reject('foo'); + $carry = null; + $p2 = $p->then(null, function ($v) use (&$carry) { $carry = $v; }); + $this->assertNotSame($p, $p2); + $this->assertNull($carry); + P\queue()->run(); + $this->assertEquals('foo', $carry); + } + + public function testCreatesPromiseWhenRejectedWithNoCallback() + { + $p = new Promise(); + $p->reject('foo'); + $p2 = $p->then(); + $this->assertNotSame($p, $p2); + $this->assertInstanceOf('GuzzleHttp\Promise\RejectedPromise', $p2); + } + + public function testInvokesWaitFnsForThens() + { + $p = new Promise(function () use (&$p) { $p->resolve('a'); }); + $p2 = $p + ->then(function ($v) { return $v . '-1-'; }) + ->then(function ($v) { return $v . '2'; }); + $this->assertEquals('a-1-2', $p2->wait()); + } + + public function testStacksThenWaitFunctions() + { + $p1 = new Promise(function () use (&$p1) { $p1->resolve('a'); }); + $p2 = new Promise(function () use (&$p2) { $p2->resolve('b'); }); + $p3 = new Promise(function () use (&$p3) { $p3->resolve('c'); }); + $p4 = $p1 + ->then(function () use ($p2) { return $p2; }) + ->then(function () use ($p3) { return $p3; }); + $this->assertEquals('c', $p4->wait()); + } + + public function testForwardsFulfilledDownChainBetweenGaps() + { + $p = new Promise(); + $r = $r2 = null; + $p->then(null, null) + ->then(function ($v) use (&$r) { $r = $v; return $v . '2'; }) + ->then(function ($v) use (&$r2) { $r2 = $v; }); + $p->resolve('foo'); + P\queue()->run(); + $this->assertEquals('foo', $r); + $this->assertEquals('foo2', $r2); + } + + public function testForwardsRejectedPromisesDownChainBetweenGaps() + { + $p = new Promise(); + $r = $r2 = null; + $p->then(null, null) + ->then(null, function ($v) use (&$r) { $r = $v; return $v . '2'; }) + ->then(function ($v) use (&$r2) { $r2 = $v; }); + $p->reject('foo'); + P\queue()->run(); + $this->assertEquals('foo', $r); + $this->assertEquals('foo2', $r2); + } + + public function testForwardsThrownPromisesDownChainBetweenGaps() + { + $e = new \Exception(); + $p = new Promise(); + $r = $r2 = null; + $p->then(null, null) + ->then(null, function ($v) use (&$r, $e) { + $r = $v; + throw $e; + }) + ->then( + null, + function ($v) use (&$r2) { $r2 = $v; } + ); + $p->reject('foo'); + P\queue()->run(); + $this->assertEquals('foo', $r); + $this->assertSame($e, $r2); + } + + public function testForwardsReturnedRejectedPromisesDownChainBetweenGaps() + { + $p = new Promise(); + $rejected = new RejectedPromise('bar'); + $r = $r2 = null; + $p->then(null, null) + ->then(null, function ($v) use (&$r, $rejected) { + $r = $v; + return $rejected; + }) + ->then( + null, + function ($v) use (&$r2) { $r2 = $v; } + ); + $p->reject('foo'); + P\queue()->run(); + $this->assertEquals('foo', $r); + $this->assertEquals('bar', $r2); + try { + $p->wait(); + } catch (RejectionException $e) { + $this->assertEquals('foo', $e->getReason()); + } + } + + public function testForwardsHandlersToNextPromise() + { + $p = new Promise(); + $p2 = new Promise(); + $resolved = null; + $p + ->then(function ($v) use ($p2) { return $p2; }) + ->then(function ($value) use (&$resolved) { $resolved = $value; }); + $p->resolve('a'); + $p2->resolve('b'); + P\queue()->run(); + $this->assertEquals('b', $resolved); + } + + public function testRemovesReferenceFromChildWhenParentWaitedUpon() + { + $r = null; + $p = new Promise(function () use (&$p) { $p->resolve('a'); }); + $p2 = new Promise(function () use (&$p2) { $p2->resolve('b'); }); + $pb = $p->then( + function ($v) use ($p2, &$r) { + $r = $v; + return $p2; + }) + ->then(function ($v) { return $v . '.'; }); + $this->assertEquals('a', $p->wait()); + $this->assertEquals('b', $p2->wait()); + $this->assertEquals('b.', $pb->wait()); + $this->assertEquals('a', $r); + } + + public function testForwardsHandlersWhenFulfilledPromiseIsReturned() + { + $res = []; + $p = new Promise(); + $p2 = new Promise(); + $p2->resolve('foo'); + $p2->then(function ($v) use (&$res) { $res[] = 'A:' . $v; }); + // $res is A:foo + $p + ->then(function () use ($p2, &$res) { $res[] = 'B'; return $p2; }) + ->then(function ($v) use (&$res) { $res[] = 'C:' . $v; }); + $p->resolve('a'); + $p->then(function ($v) use (&$res) { $res[] = 'D:' . $v; }); + P\queue()->run(); + $this->assertEquals(['A:foo', 'B', 'D:a', 'C:foo'], $res); + } + + public function testForwardsHandlersWhenRejectedPromiseIsReturned() + { + $res = []; + $p = new Promise(); + $p2 = new Promise(); + $p2->reject('foo'); + $p2->then(null, function ($v) use (&$res) { $res[] = 'A:' . $v; }); + $p->then(null, function () use ($p2, &$res) { $res[] = 'B'; return $p2; }) + ->then(null, function ($v) use (&$res) { $res[] = 'C:' . $v; }); + $p->reject('a'); + $p->then(null, function ($v) use (&$res) { $res[] = 'D:' . $v; }); + P\queue()->run(); + $this->assertEquals(['A:foo', 'B', 'D:a', 'C:foo'], $res); + } + + public function testDoesNotForwardRejectedPromise() + { + $res = []; + $p = new Promise(); + $p2 = new Promise(); + $p2->cancel(); + $p2->then(function ($v) use (&$res) { $res[] = "B:$v"; return $v; }); + $p->then(function ($v) use ($p2, &$res) { $res[] = "B:$v"; return $p2; }) + ->then(function ($v) use (&$res) { $res[] = 'C:' . $v; }); + $p->resolve('a'); + $p->then(function ($v) use (&$res) { $res[] = 'D:' . $v; }); + P\queue()->run(); + $this->assertEquals(['B:a', 'D:a'], $res); + } + + public function testRecursivelyForwardsWhenOnlyThennable() + { + $res = []; + $p = new Promise(); + $p2 = new Thennable(); + $p2->resolve('foo'); + $p2->then(function ($v) use (&$res) { $res[] = 'A:' . $v; }); + $p->then(function () use ($p2, &$res) { $res[] = 'B'; return $p2; }) + ->then(function ($v) use (&$res) { $res[] = 'C:' . $v; }); + $p->resolve('a'); + $p->then(function ($v) use (&$res) { $res[] = 'D:' . $v; }); + P\queue()->run(); + $this->assertEquals(['A:foo', 'B', 'D:a', 'C:foo'], $res); + } + + public function testRecursivelyForwardsWhenNotInstanceOfPromise() + { + $res = []; + $p = new Promise(); + $p2 = new NotPromiseInstance(); + $p2->then(function ($v) use (&$res) { $res[] = 'A:' . $v; }); + $p->then(function () use ($p2, &$res) { $res[] = 'B'; return $p2; }) + ->then(function ($v) use (&$res) { $res[] = 'C:' . $v; }); + $p->resolve('a'); + $p->then(function ($v) use (&$res) { $res[] = 'D:' . $v; }); + P\queue()->run(); + $this->assertEquals(['B', 'D:a'], $res); + $p2->resolve('foo'); + P\queue()->run(); + $this->assertEquals(['B', 'D:a', 'A:foo', 'C:foo'], $res); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Cannot fulfill or reject a promise with itself + */ + public function testCannotResolveWithSelf() + { + $p = new Promise(); + $p->resolve($p); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Cannot fulfill or reject a promise with itself + */ + public function testCannotRejectWithSelf() + { + $p = new Promise(); + $p->reject($p); + } + + public function testDoesNotBlowStackWhenWaitingOnNestedThens() + { + $inner = new Promise(function () use (&$inner) { $inner->resolve(0); }); + $prev = $inner; + for ($i = 1; $i < 100; $i++) { + $prev = $prev->then(function ($i) { return $i + 1; }); + } + + $parent = new Promise(function () use (&$parent, $prev) { + $parent->resolve($prev); + }); + + $this->assertEquals(99, $parent->wait()); + } + + public function testOtherwiseIsSugarForRejections() + { + $p = new Promise(); + $p->reject('foo'); + $p->otherwise(function ($v) use (&$c) { $c = $v; }); + P\queue()->run(); + $this->assertEquals($c, 'foo'); + } +} diff --git a/server/vendor/guzzlehttp/promises/tests/RejectedPromiseTest.php b/server/vendor/guzzlehttp/promises/tests/RejectedPromiseTest.php new file mode 100644 index 0000000..60f926e --- /dev/null +++ b/server/vendor/guzzlehttp/promises/tests/RejectedPromiseTest.php @@ -0,0 +1,143 @@ +<?php +namespace GuzzleHttp\Promise\Tests; + +use GuzzleHttp\Promise\Promise; +use GuzzleHttp\Promise\RejectedPromise; + +/** + * @covers GuzzleHttp\Promise\RejectedPromise + */ +class RejectedPromiseTest extends \PHPUnit_Framework_TestCase +{ + public function testThrowsReasonWhenWaitedUpon() + { + $p = new RejectedPromise('foo'); + $this->assertEquals('rejected', $p->getState()); + try { + $p->wait(true); + $this->fail(); + } catch (\Exception $e) { + $this->assertEquals('rejected', $p->getState()); + $this->assertContains('foo', $e->getMessage()); + } + } + + public function testCannotCancel() + { + $p = new RejectedPromise('foo'); + $p->cancel(); + $this->assertEquals('rejected', $p->getState()); + } + + /** + * @expectedException \LogicException + * @exepctedExceptionMessage Cannot resolve a rejected promise + */ + public function testCannotResolve() + { + $p = new RejectedPromise('foo'); + $p->resolve('bar'); + } + + /** + * @expectedException \LogicException + * @exepctedExceptionMessage Cannot reject a rejected promise + */ + public function testCannotReject() + { + $p = new RejectedPromise('foo'); + $p->reject('bar'); + } + + public function testCanRejectWithSameValue() + { + $p = new RejectedPromise('foo'); + $p->reject('foo'); + } + + public function testThrowsSpecificException() + { + $e = new \Exception(); + $p = new RejectedPromise($e); + try { + $p->wait(true); + $this->fail(); + } catch (\Exception $e2) { + $this->assertSame($e, $e2); + } + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testCannotResolveWithPromise() + { + new RejectedPromise(new Promise()); + } + + public function testReturnsSelfWhenNoOnReject() + { + $p = new RejectedPromise('a'); + $this->assertSame($p, $p->then()); + } + + public function testInvokesOnRejectedAsynchronously() + { + $p = new RejectedPromise('a'); + $r = null; + $f = function ($reason) use (&$r) { $r = $reason; }; + $p->then(null, $f); + $this->assertNull($r); + \GuzzleHttp\Promise\queue()->run(); + $this->assertEquals('a', $r); + } + + public function testReturnsNewRejectedWhenOnRejectedFails() + { + $p = new RejectedPromise('a'); + $f = function () { throw new \Exception('b'); }; + $p2 = $p->then(null, $f); + $this->assertNotSame($p, $p2); + try { + $p2->wait(); + $this->fail(); + } catch (\Exception $e) { + $this->assertEquals('b', $e->getMessage()); + } + } + + public function testWaitingIsNoOp() + { + $p = new RejectedPromise('a'); + $p->wait(false); + } + + public function testOtherwiseIsSugarForRejections() + { + $p = new RejectedPromise('foo'); + $p->otherwise(function ($v) use (&$c) { $c = $v; }); + \GuzzleHttp\Promise\queue()->run(); + $this->assertSame('foo', $c); + } + + public function testCanResolveThenWithSuccess() + { + $actual = null; + $p = new RejectedPromise('foo'); + $p->otherwise(function ($v) { + return $v . ' bar'; + })->then(function ($v) use (&$actual) { + $actual = $v; + }); + \GuzzleHttp\Promise\queue()->run(); + $this->assertEquals('foo bar', $actual); + } + + public function testDoesNotTryToRejectTwiceDuringTrampoline() + { + $fp = new RejectedPromise('a'); + $t1 = $fp->then(null, function ($v) { return $v . ' b'; }); + $t1->resolve('why!'); + $this->assertEquals('why!', $t1->wait()); + } +} diff --git a/server/vendor/guzzlehttp/promises/tests/RejectionExceptionTest.php b/server/vendor/guzzlehttp/promises/tests/RejectionExceptionTest.php new file mode 100644 index 0000000..36c6a88 --- /dev/null +++ b/server/vendor/guzzlehttp/promises/tests/RejectionExceptionTest.php @@ -0,0 +1,47 @@ +<?php +namespace GuzzleHttp\Promise\Tests; + +use GuzzleHttp\Promise\RejectionException; + +class Thing1 +{ + public function __construct($message) + { + $this->message = $message; + } + + public function __toString() + { + return $this->message; + } +} + +class Thing2 implements \JsonSerializable +{ + public function jsonSerialize() + { + return '{}'; + } +} + +/** + * @covers GuzzleHttp\Promise\RejectionException + */ +class RejectionExceptionTest extends \PHPUnit_Framework_TestCase +{ + public function testCanGetReasonFromException() + { + $thing = new Thing1('foo'); + $e = new RejectionException($thing); + + $this->assertSame($thing, $e->getReason()); + $this->assertEquals('The promise was rejected with reason: foo', $e->getMessage()); + } + + public function testCanGetReasonMessageFromJson() + { + $reason = new Thing2(); + $e = new RejectionException($reason); + $this->assertContains("{}", $e->getMessage()); + } +} diff --git a/server/vendor/guzzlehttp/promises/tests/TaskQueueTest.php b/server/vendor/guzzlehttp/promises/tests/TaskQueueTest.php new file mode 100644 index 0000000..845b263 --- /dev/null +++ b/server/vendor/guzzlehttp/promises/tests/TaskQueueTest.php @@ -0,0 +1,31 @@ +<?php +namespace GuzzleHttp\Promise\Test; + +use GuzzleHttp\Promise\TaskQueue; + +class TaskQueueTest extends \PHPUnit_Framework_TestCase +{ + public function testKnowsIfEmpty() + { + $tq = new TaskQueue(false); + $this->assertTrue($tq->isEmpty()); + } + + public function testKnowsIfFull() + { + $tq = new TaskQueue(false); + $tq->add(function () {}); + $this->assertFalse($tq->isEmpty()); + } + + public function testExecutesTasksInOrder() + { + $tq = new TaskQueue(false); + $called = []; + $tq->add(function () use (&$called) { $called[] = 'a'; }); + $tq->add(function () use (&$called) { $called[] = 'b'; }); + $tq->add(function () use (&$called) { $called[] = 'c'; }); + $tq->run(); + $this->assertEquals(['a', 'b', 'c'], $called); + } +} diff --git a/server/vendor/guzzlehttp/promises/tests/Thennable.php b/server/vendor/guzzlehttp/promises/tests/Thennable.php new file mode 100644 index 0000000..398954d --- /dev/null +++ b/server/vendor/guzzlehttp/promises/tests/Thennable.php @@ -0,0 +1,24 @@ +<?php +namespace GuzzleHttp\Promise\Tests; + +use GuzzleHttp\Promise\Promise; + +class Thennable +{ + private $nextPromise = null; + + public function __construct() + { + $this->nextPromise = new Promise(); + } + + public function then(callable $res = null, callable $rej = null) + { + return $this->nextPromise->then($res, $rej); + } + + public function resolve($value) + { + $this->nextPromise->resolve($value); + } +} diff --git a/server/vendor/guzzlehttp/promises/tests/bootstrap.php b/server/vendor/guzzlehttp/promises/tests/bootstrap.php new file mode 100644 index 0000000..a63d264 --- /dev/null +++ b/server/vendor/guzzlehttp/promises/tests/bootstrap.php @@ -0,0 +1,4 @@ +<?php +require __DIR__ . '/../vendor/autoload.php'; +require __DIR__ . '/Thennable.php'; +require __DIR__ . '/NotPromiseInstance.php'; diff --git a/server/vendor/guzzlehttp/promises/tests/functionsTest.php b/server/vendor/guzzlehttp/promises/tests/functionsTest.php new file mode 100644 index 0000000..8e6fcf4 --- /dev/null +++ b/server/vendor/guzzlehttp/promises/tests/functionsTest.php @@ -0,0 +1,694 @@ +<?php +namespace GuzzleHttp\Promise\Tests; + +use GuzzleHttp\Promise as P; +use GuzzleHttp\Promise\FulfilledPromise; +use GuzzleHttp\Promise\Promise; +use GuzzleHttp\Promise\RejectedPromise; + +class FunctionsTest extends \PHPUnit_Framework_TestCase +{ + public function testCreatesPromiseForValue() + { + $p = \GuzzleHttp\Promise\promise_for('foo'); + $this->assertInstanceOf('GuzzleHttp\Promise\FulfilledPromise', $p); + } + + public function testReturnsPromiseForPromise() + { + $p = new Promise(); + $this->assertSame($p, \GuzzleHttp\Promise\promise_for($p)); + } + + public function testReturnsPromiseForThennable() + { + $p = new Thennable(); + $wrapped = \GuzzleHttp\Promise\promise_for($p); + $this->assertNotSame($p, $wrapped); + $this->assertInstanceOf('GuzzleHttp\Promise\PromiseInterface', $wrapped); + $p->resolve('foo'); + P\queue()->run(); + $this->assertEquals('foo', $wrapped->wait()); + } + + public function testReturnsRejection() + { + $p = \GuzzleHttp\Promise\rejection_for('fail'); + $this->assertInstanceOf('GuzzleHttp\Promise\RejectedPromise', $p); + $this->assertEquals('fail', $this->readAttribute($p, 'reason')); + } + + public function testReturnsPromisesAsIsInRejectionFor() + { + $a = new Promise(); + $b = \GuzzleHttp\Promise\rejection_for($a); + $this->assertSame($a, $b); + } + + public function testWaitsOnAllPromisesIntoArray() + { + $e = new \Exception(); + $a = new Promise(function () use (&$a) { $a->resolve('a'); }); + $b = new Promise(function () use (&$b) { $b->reject('b'); }); + $c = new Promise(function () use (&$c, $e) { $c->reject($e); }); + $results = \GuzzleHttp\Promise\inspect_all([$a, $b, $c]); + $this->assertEquals([ + ['state' => 'fulfilled', 'value' => 'a'], + ['state' => 'rejected', 'reason' => 'b'], + ['state' => 'rejected', 'reason' => $e] + ], $results); + } + + /** + * @expectedException \GuzzleHttp\Promise\RejectionException + */ + public function testUnwrapsPromisesWithNoDefaultAndFailure() + { + $promises = [new FulfilledPromise('a'), new Promise()]; + \GuzzleHttp\Promise\unwrap($promises); + } + + public function testUnwrapsPromisesWithNoDefault() + { + $promises = [new FulfilledPromise('a')]; + $this->assertEquals(['a'], \GuzzleHttp\Promise\unwrap($promises)); + } + + public function testUnwrapsPromisesWithKeys() + { + $promises = [ + 'foo' => new FulfilledPromise('a'), + 'bar' => new FulfilledPromise('b'), + ]; + $this->assertEquals([ + 'foo' => 'a', + 'bar' => 'b' + ], \GuzzleHttp\Promise\unwrap($promises)); + } + + public function testAllAggregatesSortedArray() + { + $a = new Promise(); + $b = new Promise(); + $c = new Promise(); + $d = \GuzzleHttp\Promise\all([$a, $b, $c]); + $b->resolve('b'); + $a->resolve('a'); + $c->resolve('c'); + $d->then( + function ($value) use (&$result) { $result = $value; }, + function ($reason) use (&$result) { $result = $reason; } + ); + P\queue()->run(); + $this->assertEquals(['a', 'b', 'c'], $result); + } + + public function testAllThrowsWhenAnyRejected() + { + $a = new Promise(); + $b = new Promise(); + $c = new Promise(); + $d = \GuzzleHttp\Promise\all([$a, $b, $c]); + $b->resolve('b'); + $a->reject('fail'); + $c->resolve('c'); + $d->then( + function ($value) use (&$result) { $result = $value; }, + function ($reason) use (&$result) { $result = $reason; } + ); + P\queue()->run(); + $this->assertEquals('fail', $result); + } + + public function testSomeAggregatesSortedArrayWithMax() + { + $a = new Promise(); + $b = new Promise(); + $c = new Promise(); + $d = \GuzzleHttp\Promise\some(2, [$a, $b, $c]); + $b->resolve('b'); + $c->resolve('c'); + $a->resolve('a'); + $d->then(function ($value) use (&$result) { $result = $value; }); + P\queue()->run(); + $this->assertEquals(['b', 'c'], $result); + } + + public function testSomeRejectsWhenTooManyRejections() + { + $a = new Promise(); + $b = new Promise(); + $d = \GuzzleHttp\Promise\some(2, [$a, $b]); + $a->reject('bad'); + $b->resolve('good'); + P\queue()->run(); + $this->assertEquals($a::REJECTED, $d->getState()); + $d->then(null, function ($reason) use (&$called) { + $called = $reason; + }); + P\queue()->run(); + $this->assertInstanceOf('GuzzleHttp\Promise\AggregateException', $called); + $this->assertContains('bad', $called->getReason()); + } + + public function testCanWaitUntilSomeCountIsSatisfied() + { + $a = new Promise(function () use (&$a) { $a->resolve('a'); }); + $b = new Promise(function () use (&$b) { $b->resolve('b'); }); + $c = new Promise(function () use (&$c) { $c->resolve('c'); }); + $d = \GuzzleHttp\Promise\some(2, [$a, $b, $c]); + $this->assertEquals(['a', 'b'], $d->wait()); + } + + /** + * @expectedException \GuzzleHttp\Promise\AggregateException + * @expectedExceptionMessage Not enough promises to fulfill count + */ + public function testThrowsIfImpossibleToWaitForSomeCount() + { + $a = new Promise(function () use (&$a) { $a->resolve('a'); }); + $d = \GuzzleHttp\Promise\some(2, [$a]); + $d->wait(); + } + + /** + * @expectedException \GuzzleHttp\Promise\AggregateException + * @expectedExceptionMessage Not enough promises to fulfill count + */ + public function testThrowsIfResolvedWithoutCountTotalResults() + { + $a = new Promise(); + $b = new Promise(); + $d = \GuzzleHttp\Promise\some(3, [$a, $b]); + $a->resolve('a'); + $b->resolve('b'); + $d->wait(); + } + + public function testAnyReturnsFirstMatch() + { + $a = new Promise(); + $b = new Promise(); + $c = \GuzzleHttp\Promise\any([$a, $b]); + $b->resolve('b'); + $a->resolve('a'); + //P\queue()->run(); + //$this->assertEquals('fulfilled', $c->getState()); + $c->then(function ($value) use (&$result) { $result = $value; }); + P\queue()->run(); + $this->assertEquals('b', $result); + } + + public function testSettleFulfillsWithFulfilledAndRejected() + { + $a = new Promise(); + $b = new Promise(); + $c = new Promise(); + $d = \GuzzleHttp\Promise\settle([$a, $b, $c]); + $b->resolve('b'); + $c->resolve('c'); + $a->reject('a'); + P\queue()->run(); + $this->assertEquals('fulfilled', $d->getState()); + $d->then(function ($value) use (&$result) { $result = $value; }); + P\queue()->run(); + $this->assertEquals([ + ['state' => 'rejected', 'reason' => 'a'], + ['state' => 'fulfilled', 'value' => 'b'], + ['state' => 'fulfilled', 'value' => 'c'] + ], $result); + } + + public function testCanInspectFulfilledPromise() + { + $p = new FulfilledPromise('foo'); + $this->assertEquals([ + 'state' => 'fulfilled', + 'value' => 'foo' + ], \GuzzleHttp\Promise\inspect($p)); + } + + public function testCanInspectRejectedPromise() + { + $p = new RejectedPromise('foo'); + $this->assertEquals([ + 'state' => 'rejected', + 'reason' => 'foo' + ], \GuzzleHttp\Promise\inspect($p)); + } + + public function testCanInspectRejectedPromiseWithNormalException() + { + $e = new \Exception('foo'); + $p = new RejectedPromise($e); + $this->assertEquals([ + 'state' => 'rejected', + 'reason' => $e + ], \GuzzleHttp\Promise\inspect($p)); + } + + public function testCallsEachLimit() + { + $p = new Promise(); + $aggregate = \GuzzleHttp\Promise\each_limit($p, 2); + $p->resolve('a'); + P\queue()->run(); + $this->assertEquals($p::FULFILLED, $aggregate->getState()); + } + + public function testEachLimitAllRejectsOnFailure() + { + $p = [new FulfilledPromise('a'), new RejectedPromise('b')]; + $aggregate = \GuzzleHttp\Promise\each_limit_all($p, 2); + P\queue()->run(); + $this->assertEquals(P\PromiseInterface::REJECTED, $aggregate->getState()); + $result = \GuzzleHttp\Promise\inspect($aggregate); + $this->assertEquals('b', $result['reason']); + } + + public function testIterForReturnsIterator() + { + $iter = new \ArrayIterator(); + $this->assertSame($iter, \GuzzleHttp\Promise\iter_for($iter)); + } + + public function testKnowsIfFulfilled() + { + $p = new FulfilledPromise(null); + $this->assertTrue(P\is_fulfilled($p)); + $this->assertFalse(P\is_rejected($p)); + } + + public function testKnowsIfRejected() + { + $p = new RejectedPromise(null); + $this->assertTrue(P\is_rejected($p)); + $this->assertFalse(P\is_fulfilled($p)); + } + + public function testKnowsIfSettled() + { + $p = new RejectedPromise(null); + $this->assertTrue(P\is_settled($p)); + $p = new Promise(); + $this->assertFalse(P\is_settled($p)); + } + + public function testReturnsTrampoline() + { + $this->assertInstanceOf('GuzzleHttp\Promise\TaskQueue', P\queue()); + $this->assertSame(P\queue(), P\queue()); + } + + public function testCanScheduleThunk() + { + $tramp = P\queue(); + $promise = P\task(function () { return 'Hi!'; }); + $c = null; + $promise->then(function ($v) use (&$c) { $c = $v; }); + $this->assertNull($c); + $tramp->run(); + $this->assertEquals('Hi!', $c); + } + + public function testCanScheduleThunkWithRejection() + { + $tramp = P\queue(); + $promise = P\task(function () { throw new \Exception('Hi!'); }); + $c = null; + $promise->otherwise(function ($v) use (&$c) { $c = $v; }); + $this->assertNull($c); + $tramp->run(); + $this->assertEquals('Hi!', $c->getMessage()); + } + + public function testCanScheduleThunkWithWait() + { + $tramp = P\queue(); + $promise = P\task(function () { return 'a'; }); + $this->assertEquals('a', $promise->wait()); + $tramp->run(); + } + + public function testYieldsFromCoroutine() + { + $promise = P\coroutine(function () { + $value = (yield new P\FulfilledPromise('a')); + yield $value . 'b'; + }); + $promise->then(function ($value) use (&$result) { $result = $value; }); + P\queue()->run(); + $this->assertEquals('ab', $result); + } + + public function testCanCatchExceptionsInCoroutine() + { + $promise = P\coroutine(function () { + try { + yield new P\RejectedPromise('a'); + $this->fail('Should have thrown into the coroutine!'); + } catch (P\RejectionException $e) { + $value = (yield new P\FulfilledPromise($e->getReason())); + yield $value . 'b'; + } + }); + $promise->then(function ($value) use (&$result) { $result = $value; }); + P\queue()->run(); + $this->assertEquals(P\PromiseInterface::FULFILLED, $promise->getState()); + $this->assertEquals('ab', $result); + } + + public function testRejectsParentExceptionWhenException() + { + $promise = P\coroutine(function () { + yield new P\FulfilledPromise(0); + throw new \Exception('a'); + }); + $promise->then( + function () { $this->fail(); }, + function ($reason) use (&$result) { $result = $reason; } + ); + P\queue()->run(); + $this->assertInstanceOf('Exception', $result); + $this->assertEquals('a', $result->getMessage()); + } + + public function testCanRejectFromRejectionCallback() + { + $promise = P\coroutine(function () { + yield new P\FulfilledPromise(0); + yield new P\RejectedPromise('no!'); + }); + $promise->then( + function () { $this->fail(); }, + function ($reason) use (&$result) { $result = $reason; } + ); + P\queue()->run(); + $this->assertInstanceOf('GuzzleHttp\Promise\RejectionException', $result); + $this->assertEquals('no!', $result->getReason()); + } + + public function testCanAsyncReject() + { + $rej = new P\Promise(); + $promise = P\coroutine(function () use ($rej) { + yield new P\FulfilledPromise(0); + yield $rej; + }); + $promise->then( + function () { $this->fail(); }, + function ($reason) use (&$result) { $result = $reason; } + ); + $rej->reject('no!'); + P\queue()->run(); + $this->assertInstanceOf('GuzzleHttp\Promise\RejectionException', $result); + $this->assertEquals('no!', $result->getReason()); + } + + public function testCanCatchAndThrowOtherException() + { + $promise = P\coroutine(function () { + try { + yield new P\RejectedPromise('a'); + $this->fail('Should have thrown into the coroutine!'); + } catch (P\RejectionException $e) { + throw new \Exception('foo'); + } + }); + $promise->otherwise(function ($value) use (&$result) { $result = $value; }); + P\queue()->run(); + $this->assertEquals(P\PromiseInterface::REJECTED, $promise->getState()); + $this->assertContains('foo', $result->getMessage()); + } + + public function testCanCatchAndYieldOtherException() + { + $promise = P\coroutine(function () { + try { + yield new P\RejectedPromise('a'); + $this->fail('Should have thrown into the coroutine!'); + } catch (P\RejectionException $e) { + yield new P\RejectedPromise('foo'); + } + }); + $promise->otherwise(function ($value) use (&$result) { $result = $value; }); + P\queue()->run(); + $this->assertEquals(P\PromiseInterface::REJECTED, $promise->getState()); + $this->assertContains('foo', $result->getMessage()); + } + + public function createLotsOfSynchronousPromise() + { + return P\coroutine(function () { + $value = 0; + for ($i = 0; $i < 1000; $i++) { + $value = (yield new P\FulfilledPromise($i)); + } + yield $value; + }); + } + + public function testLotsOfSynchronousDoesNotBlowStack() + { + $promise = $this->createLotsOfSynchronousPromise(); + $promise->then(function ($v) use (&$r) { $r = $v; }); + P\queue()->run(); + $this->assertEquals(999, $r); + } + + public function testLotsOfSynchronousWaitDoesNotBlowStack() + { + $promise = $this->createLotsOfSynchronousPromise(); + $promise->then(function ($v) use (&$r) { $r = $v; }); + $this->assertEquals(999, $promise->wait()); + $this->assertEquals(999, $r); + } + + private function createLotsOfFlappingPromise() + { + return P\coroutine(function () { + $value = 0; + for ($i = 0; $i < 1000; $i++) { + try { + if ($i % 2) { + $value = (yield new P\FulfilledPromise($i)); + } else { + $value = (yield new P\RejectedPromise($i)); + } + } catch (\Exception $e) { + $value = (yield new P\FulfilledPromise($i)); + } + } + yield $value; + }); + } + + public function testLotsOfTryCatchingDoesNotBlowStack() + { + $promise = $this->createLotsOfFlappingPromise(); + $promise->then(function ($v) use (&$r) { $r = $v; }); + P\queue()->run(); + $this->assertEquals(999, $r); + } + + public function testLotsOfTryCatchingWaitingDoesNotBlowStack() + { + $promise = $this->createLotsOfFlappingPromise(); + $promise->then(function ($v) use (&$r) { $r = $v; }); + $this->assertEquals(999, $promise->wait()); + $this->assertEquals(999, $r); + } + + public function testAsyncPromisesWithCorrectlyYieldedValues() + { + $promises = [ + new P\Promise(), + new P\Promise(), + new P\Promise() + ]; + + $promise = P\coroutine(function () use ($promises) { + $value = null; + $this->assertEquals('skip', (yield new P\FulfilledPromise('skip'))); + foreach ($promises as $idx => $p) { + $value = (yield $p); + $this->assertEquals($value, $idx); + $this->assertEquals('skip', (yield new P\FulfilledPromise('skip'))); + } + $this->assertEquals('skip', (yield new P\FulfilledPromise('skip'))); + yield $value; + }); + + $promises[0]->resolve(0); + $promises[1]->resolve(1); + $promises[2]->resolve(2); + + $promise->then(function ($v) use (&$r) { $r = $v; }); + P\queue()->run(); + $this->assertEquals(2, $r); + } + + public function testYieldFinalWaitablePromise() + { + $p1 = new P\Promise(function () use (&$p1) { + $p1->resolve('skip me'); + }); + $p2 = new P\Promise(function () use (&$p2) { + $p2->resolve('hello!'); + }); + $co = P\coroutine(function() use ($p1, $p2) { + yield $p1; + yield $p2; + }); + P\queue()->run(); + $this->assertEquals('hello!', $co->wait()); + } + + public function testCanYieldFinalPendingPromise() + { + $p1 = new P\Promise(); + $p2 = new P\Promise(); + $co = P\coroutine(function() use ($p1, $p2) { + yield $p1; + yield $p2; + }); + $p1->resolve('a'); + $p2->resolve('b'); + $co->then(function ($value) use (&$result) { $result = $value; }); + P\queue()->run(); + $this->assertEquals('b', $result); + } + + public function testCanNestYieldsAndFailures() + { + $p1 = new P\Promise(); + $p2 = new P\Promise(); + $p3 = new P\Promise(); + $p4 = new P\Promise(); + $p5 = new P\Promise(); + $co = P\coroutine(function() use ($p1, $p2, $p3, $p4, $p5) { + try { + yield $p1; + } catch (\Exception $e) { + yield $p2; + try { + yield $p3; + yield $p4; + } catch (\Exception $e) { + yield $p5; + } + } + }); + $p1->reject('a'); + $p2->resolve('b'); + $p3->resolve('c'); + $p4->reject('d'); + $p5->resolve('e'); + $co->then(function ($value) use (&$result) { $result = $value; }); + P\queue()->run(); + $this->assertEquals('e', $result); + } + + public function testCanYieldErrorsAndSuccessesWithoutRecursion() + { + $promises = []; + for ($i = 0; $i < 20; $i++) { + $promises[] = new P\Promise(); + } + + $co = P\coroutine(function() use ($promises) { + for ($i = 0; $i < 20; $i += 4) { + try { + yield $promises[$i]; + yield $promises[$i + 1]; + } catch (\Exception $e) { + yield $promises[$i + 2]; + yield $promises[$i + 3]; + } + } + }); + + for ($i = 0; $i < 20; $i += 4) { + $promises[$i]->resolve($i); + $promises[$i + 1]->reject($i + 1); + $promises[$i + 2]->resolve($i + 2); + $promises[$i + 3]->resolve($i + 3); + } + + $co->then(function ($value) use (&$result) { $result = $value; }); + P\queue()->run(); + $this->assertEquals('19', $result); + } + + public function testCanWaitOnPromiseAfterFulfilled() + { + $f = function () { + static $i = 0; + $i++; + return $p = new P\Promise(function () use (&$p, $i) { + $p->resolve($i . '-bar'); + }); + }; + + $promises = []; + for ($i = 0; $i < 20; $i++) { + $promises[] = $f(); + } + + $p = P\coroutine(function () use ($promises) { + yield new P\FulfilledPromise('foo!'); + foreach ($promises as $promise) { + yield $promise; + } + }); + + $this->assertEquals('20-bar', $p->wait()); + } + + public function testCanWaitOnErroredPromises() + { + $p1 = new P\Promise(function () use (&$p1) { $p1->reject('a'); }); + $p2 = new P\Promise(function () use (&$p2) { $p2->resolve('b'); }); + $p3 = new P\Promise(function () use (&$p3) { $p3->resolve('c'); }); + $p4 = new P\Promise(function () use (&$p4) { $p4->reject('d'); }); + $p5 = new P\Promise(function () use (&$p5) { $p5->resolve('e'); }); + $p6 = new P\Promise(function () use (&$p6) { $p6->reject('f'); }); + + $co = P\coroutine(function() use ($p1, $p2, $p3, $p4, $p5, $p6) { + try { + yield $p1; + } catch (\Exception $e) { + yield $p2; + try { + yield $p3; + yield $p4; + } catch (\Exception $e) { + yield $p5; + yield $p6; + } + } + }); + + $res = P\inspect($co); + $this->assertEquals('f', $res['reason']); + } + + public function testCoroutineOtherwiseIntegrationTest() + { + $a = new P\Promise(); + $b = new P\Promise(); + $promise = P\coroutine(function () use ($a, $b) { + // Execute the pool of commands concurrently, and process errors. + yield $a; + yield $b; + })->otherwise(function (\Exception $e) { + // Throw errors from the operations as a specific Multipart error. + throw new \OutOfBoundsException('a', 0, $e); + }); + $a->resolve('a'); + $b->reject('b'); + $reason = P\inspect($promise)['reason']; + $this->assertInstanceOf('OutOfBoundsException', $reason); + $this->assertInstanceOf('GuzzleHttp\Promise\RejectionException', $reason->getPrevious()); + } +} |
