init
This commit is contained in:
2
vendor/react/stream/.gitignore
vendored
Executable file
2
vendor/react/stream/.gitignore
vendored
Executable file
@@ -0,0 +1,2 @@
|
||||
composer.lock
|
||||
vendor
|
||||
51
vendor/react/stream/.travis.yml
vendored
Executable file
51
vendor/react/stream/.travis.yml
vendored
Executable file
@@ -0,0 +1,51 @@
|
||||
language: php
|
||||
|
||||
php:
|
||||
# - 5.3 # requires old distro, see below
|
||||
- 5.4
|
||||
- 5.5
|
||||
- 5.6
|
||||
- 7.0
|
||||
# - 7.0 # Mac OS X test setup, ignore errors, see below
|
||||
- 7.1
|
||||
- 7.2
|
||||
- 7.3
|
||||
- nightly # ignore errors, see below
|
||||
- hhvm # ignore errors, see below
|
||||
|
||||
# lock distro so new future defaults will not break the build
|
||||
dist: trusty
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- php: 5.3
|
||||
dist: precise
|
||||
include:
|
||||
- os: osx
|
||||
language: generic
|
||||
php: 7.0 # just to look right on travis
|
||||
env:
|
||||
- PACKAGE: php70
|
||||
allow_failures:
|
||||
- php: nightly
|
||||
- php: hhvm
|
||||
- os: osx
|
||||
|
||||
install:
|
||||
# OSX install inspired by https://github.com/kiler129/TravisCI-OSX-PHP
|
||||
- |
|
||||
if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then
|
||||
brew tap homebrew/homebrew-php
|
||||
echo "Installing PHP ..."
|
||||
brew install "${PACKAGE}"
|
||||
brew install "${PACKAGE}"-xdebug
|
||||
brew link "${PACKAGE}"
|
||||
echo "Installing composer ..."
|
||||
curl -s http://getcomposer.org/installer | php
|
||||
mv composer.phar /usr/local/bin/composer
|
||||
fi
|
||||
- composer install --no-interaction
|
||||
|
||||
script:
|
||||
- vendor/bin/phpunit --coverage-text
|
||||
- time php examples/91-benchmark-throughput.php
|
||||
398
vendor/react/stream/CHANGELOG.md
vendored
Executable file
398
vendor/react/stream/CHANGELOG.md
vendored
Executable file
@@ -0,0 +1,398 @@
|
||||
# Changelog
|
||||
|
||||
## 1.1.0 (2018-01-01)
|
||||
|
||||
* Improvement: Increase performance by optimizing global function and constant look ups
|
||||
(#137 by @WyriHaximus)
|
||||
* Travis: Test against PHP 7.3
|
||||
(#138 by @WyriHaximus)
|
||||
* Fix: Ignore empty reads
|
||||
(#139 by @WyriHaximus)
|
||||
|
||||
## 1.0.0 (2018-07-11)
|
||||
|
||||
* First stable LTS release, now following [SemVer](https://semver.org/).
|
||||
We'd like to emphasize that this component is production ready and battle-tested.
|
||||
We plan to support all long-term support (LTS) releases for at least 24 months,
|
||||
so you have a rock-solid foundation to build on top of.
|
||||
|
||||
> Contains no other changes, so it's actually fully compatible with the v0.7.7 release.
|
||||
|
||||
## 0.7.7 (2018-01-19)
|
||||
|
||||
* Improve test suite by fixing forward compatibility with upcoming EventLoop
|
||||
releases, avoid risky tests and add test group to skip integration tests
|
||||
relying on internet connection and apply appropriate test timeouts.
|
||||
(#128, #131 and #132 by @clue)
|
||||
|
||||
## 0.7.6 (2017-12-21)
|
||||
|
||||
* Fix: Work around reading from unbuffered pipe stream in legacy PHP < 5.4.28 and PHP < 5.5.12
|
||||
(#126 by @clue)
|
||||
|
||||
* Improve test suite by simplifying test bootstrapping logic via Composer and
|
||||
test against PHP 7.2
|
||||
(#127 by @clue and #124 by @carusogabriel)
|
||||
|
||||
## 0.7.5 (2017-11-20)
|
||||
|
||||
* Fix: Igore excessive `fopen()` mode flags for `WritableResourceStream`
|
||||
(#119 by @clue)
|
||||
|
||||
* Fix: Fix forward compatibility with upcoming EventLoop releases
|
||||
(#121 by @clue)
|
||||
|
||||
* Restructure examples to ease getting started
|
||||
(#123 by @clue)
|
||||
|
||||
* Improve test suite by adding forward compatibility with PHPUnit 6 and
|
||||
ignore Mac OS X test failures for now until Travis tests work again
|
||||
(#122 by @gabriel-caruso and #120 by @clue)
|
||||
|
||||
## 0.7.4 (2017-10-11)
|
||||
|
||||
* Fix: Remove event listeners from `CompositeStream` once closed and
|
||||
remove undocumented left-over `close` event argument
|
||||
(#116 by @clue)
|
||||
|
||||
* Minor documentation improvements: Fix wrong class name in example,
|
||||
fix typos in README and
|
||||
fix forward compatibility with upcoming EventLoop releases in example
|
||||
(#113 by @docteurklein and #114 and #115 by @clue)
|
||||
|
||||
* Improve test suite by running against Mac OS X on Travis
|
||||
(#112 by @clue)
|
||||
|
||||
## 0.7.3 (2017-08-05)
|
||||
|
||||
* Improvement: Support Événement 3.0 a long side 2.0 and 1.0
|
||||
(#108 by @WyriHaximus)
|
||||
|
||||
* Readme: Corrected loop initialization in usage example
|
||||
(#109 by @pulyavin)
|
||||
|
||||
* Travis: Lock linux distribution preventing future builds from breaking
|
||||
(#110 by @clue)
|
||||
|
||||
## 0.7.2 (2017-06-15)
|
||||
|
||||
* Bug fix: WritableResourceStream: Close the underlying stream when closing the stream.
|
||||
(#107 by @WyriHaximus)
|
||||
|
||||
## 0.7.1 (2017-05-20)
|
||||
|
||||
* Feature: Add optional `$writeChunkSize` parameter to limit maximum number of
|
||||
bytes to write at once.
|
||||
(#105 by @clue)
|
||||
|
||||
```php
|
||||
$stream = new WritableResourceStream(STDOUT, $loop, null, 8192);
|
||||
```
|
||||
|
||||
* Ignore HHVM test failures for now until Travis tests work again
|
||||
(#106 by @clue)
|
||||
|
||||
## 0.7.0 (2017-05-04)
|
||||
|
||||
* Removed / BC break: Remove deprecated and unneeded functionality
|
||||
(#45, #87, #90, #91 and #93 by @clue)
|
||||
|
||||
* Remove deprecated `Stream` class, use `DuplexResourceStream` instead
|
||||
(#87 by @clue)
|
||||
|
||||
* Remove public `$buffer` property, use new constructor parameters instead
|
||||
(#91 by @clue)
|
||||
|
||||
* Remove public `$stream` property from all resource streams
|
||||
(#90 by @clue)
|
||||
|
||||
* Remove undocumented and now unused `ReadableStream` and `WritableStream`
|
||||
(#93 by @clue)
|
||||
|
||||
* Remove `BufferedSink`
|
||||
(#45 by @clue)
|
||||
|
||||
* Feature / BC break: Simplify `ThroughStream` by using data callback instead of
|
||||
inheritance. It is now a direct implementation of `DuplexStreamInterface`.
|
||||
(#88 and #89 by @clue)
|
||||
|
||||
```php
|
||||
$through = new ThroughStream(function ($data) {
|
||||
return json_encode($data) . PHP_EOL;
|
||||
});
|
||||
$through->on('data', $this->expectCallableOnceWith("[2, true]\n"));
|
||||
|
||||
$through->write(array(2, true));
|
||||
```
|
||||
|
||||
* Feature / BC break: The `CompositeStream` starts closed if either side is
|
||||
already closed and forwards pause to pipe source on first write attempt.
|
||||
(#96 and #103 by @clue)
|
||||
|
||||
If either side of the composite stream closes, it will also close the other
|
||||
side. We now also ensure that if either side is already closed during
|
||||
instantiation, it will also close the other side.
|
||||
|
||||
* BC break: Mark all classes as `final` and
|
||||
mark internal API as `private` to discourage inheritance
|
||||
(#95 and #99 by @clue)
|
||||
|
||||
* Feature / BC break: Only emit `error` event for fatal errors
|
||||
(#92 by @clue)
|
||||
|
||||
> The `error` event was previously also allowed to be emitted for non-fatal
|
||||
errors, but our implementations actually only ever emitted this as a fatal
|
||||
error and then closed the stream.
|
||||
|
||||
* Feature: Explicitly allow custom events and exclude any semantics
|
||||
(#97 by @clue)
|
||||
|
||||
* Strict definition for event callback functions
|
||||
(#101 by @clue)
|
||||
|
||||
* Support legacy PHP 5.3 through PHP 7.1 and HHVM and improve usage documentation
|
||||
(#100 and #102 by @clue)
|
||||
|
||||
* Actually require all dependencies so this is self-contained and improve
|
||||
forward compatibility with EventLoop v1.0 and v0.5
|
||||
(#94 and #98 by @clue)
|
||||
|
||||
## 0.6.0 (2017-03-26)
|
||||
|
||||
* Feature / Fix / BC break: Add `DuplexResourceStream` and deprecate `Stream`
|
||||
(#85 by @clue)
|
||||
|
||||
```php
|
||||
// old (does still work for BC reasons)
|
||||
$stream = new Stream($connection, $loop);
|
||||
|
||||
// new
|
||||
$stream = new DuplexResourceStream($connection, $loop);
|
||||
```
|
||||
|
||||
Note that the `DuplexResourceStream` now rejects read-only or write-only
|
||||
streams, so this may affect BC. If you want a read-only or write-only
|
||||
resource, use `ReadableResourceStream` or `WritableResourceStream` instead of
|
||||
`DuplexResourceStream`.
|
||||
|
||||
> BC note: This class was previously called `Stream`. The `Stream` class still
|
||||
exists for BC reasons and will be removed in future versions of this package.
|
||||
|
||||
* Feature / BC break: Add `WritableResourceStream` (previously called `Buffer`)
|
||||
(#84 by @clue)
|
||||
|
||||
```php
|
||||
// old
|
||||
$stream = new Buffer(STDOUT, $loop);
|
||||
|
||||
// new
|
||||
$stream = new WritableResourceStream(STDOUT, $loop);
|
||||
```
|
||||
|
||||
* Feature: Add `ReadableResourceStream`
|
||||
(#83 by @clue)
|
||||
|
||||
```php
|
||||
$stream = new ReadableResourceStream(STDIN, $loop);
|
||||
```
|
||||
|
||||
* Fix / BC Break: Enforce using non-blocking I/O
|
||||
(#46 by @clue)
|
||||
|
||||
> BC note: This is known to affect process pipes on Windows which do not
|
||||
support non-blocking I/O and could thus block the whole EventLoop previously.
|
||||
|
||||
* Feature / Fix / BC break: Consistent semantics for
|
||||
`DuplexStreamInterface::end()` to ensure it SHOULD also end readable side
|
||||
(#86 by @clue)
|
||||
|
||||
* Fix: Do not use unbuffered reads on pipe streams for legacy PHP < 5.4
|
||||
(#80 by @clue)
|
||||
|
||||
## 0.5.0 (2017-03-08)
|
||||
|
||||
* Feature / BC break: Consistent `end` event semantics (EOF)
|
||||
(#70 by @clue)
|
||||
|
||||
The `end` event will now only be emitted for a *successful* end, not if the
|
||||
stream closes due to an unrecoverable `error` event or if you call `close()`
|
||||
explicitly.
|
||||
If you want to detect when the stream closes (terminates), use the `close`
|
||||
event instead.
|
||||
|
||||
* BC break: Remove custom (undocumented) `full-drain` event from `Buffer`
|
||||
(#63 and #68 by @clue)
|
||||
|
||||
> The `full-drain` event was undocumented and mostly used internally.
|
||||
Relying on this event has attracted some low-quality code in the past, so
|
||||
we've removed this from the public API in order to work out a better
|
||||
solution instead.
|
||||
If you want to detect when the buffer finishes flushing data to the stream,
|
||||
you may want to look into its `end()` method or the `close` event instead.
|
||||
|
||||
* Feature / BC break: Consistent event semantics and documentation,
|
||||
explicitly state *when* events will be emitted and *which* arguments they
|
||||
receive.
|
||||
(#73 and #69 by @clue)
|
||||
|
||||
The documentation now explicitly defines each event and its arguments.
|
||||
Custom events and event arguments are still supported.
|
||||
Most notably, all defined events only receive inherently required event
|
||||
arguments and no longer transmit the instance they are emitted on for
|
||||
consistency and performance reasons.
|
||||
|
||||
```php
|
||||
// old (inconsistent and not supported by all implementations)
|
||||
$stream->on('data', function ($data, $stream) {
|
||||
// process $data
|
||||
});
|
||||
|
||||
// new (consistent throughout the whole ecosystem)
|
||||
$stream->on('data', function ($data) use ($stream) {
|
||||
// process $data
|
||||
});
|
||||
```
|
||||
|
||||
> This mostly adds documentation (and thus some stricter, consistent
|
||||
definitions) for the existing behavior, it does NOT define any major
|
||||
changes otherwise.
|
||||
Most existing code should be compatible with these changes, unless
|
||||
it relied on some undocumented/unintended semantics.
|
||||
|
||||
* Feature / BC break: Consistent method semantics and documentation
|
||||
(#72 by @clue)
|
||||
|
||||
> This mostly adds documentation (and thus some stricter, consistent
|
||||
definitions) for the existing behavior, it does NOT define any major
|
||||
changes otherwise.
|
||||
Most existing code should be compatible with these changes, unless
|
||||
it relied on some undocumented/unintended semantics.
|
||||
|
||||
* Feature: Consistent `pipe()` semantics for closed and closing streams
|
||||
(#71 from @clue)
|
||||
|
||||
The source stream will now always be paused via `pause()` when the
|
||||
destination stream closes. Also, properly stop piping if the source
|
||||
stream closes and remove all event forwarding.
|
||||
|
||||
* Improve test suite by adding PHPUnit to `require-dev` and improving coverage.
|
||||
(#74 and #75 by @clue, #66 by @nawarian)
|
||||
|
||||
## 0.4.6 (2017-01-25)
|
||||
|
||||
* Feature: The `Buffer` can now be injected into the `Stream` (or be used standalone)
|
||||
(#62 by @clue)
|
||||
|
||||
* Fix: Forward `close` event only once for `CompositeStream` and `ThroughStream`
|
||||
(#60 by @clue)
|
||||
|
||||
* Fix: Consistent `close` event behavior for `Buffer`
|
||||
(#61 by @clue)
|
||||
|
||||
## 0.4.5 (2016-11-13)
|
||||
|
||||
* Feature: Support setting read buffer size to `null` (infinite)
|
||||
(#42 by @clue)
|
||||
|
||||
* Fix: Do not emit `full-drain` event if `Buffer` is closed during `drain` event
|
||||
(#55 by @clue)
|
||||
|
||||
* Vastly improved performance by factor of 10x to 20x.
|
||||
Raise default buffer sizes to 64 KiB and simplify and improve error handling
|
||||
and unneeded function calls.
|
||||
(#53, #55, #56 by @clue)
|
||||
|
||||
## 0.4.4 (2016-08-22)
|
||||
|
||||
* Bug fix: Emit `error` event and close `Stream` when accessing the underlying
|
||||
stream resource fails with a permanent error.
|
||||
(#52 and #40 by @clue, #25 by @lysenkobv)
|
||||
|
||||
* Bug fix: Do not emit empty `data` event if nothing has been read (stream reached EOF)
|
||||
(#39 by @clue)
|
||||
|
||||
* Bug fix: Ignore empty writes to `Buffer`
|
||||
(#51 by @clue)
|
||||
|
||||
* Add benchmarking script to measure throughput in CI
|
||||
(#41 by @clue)
|
||||
|
||||
## 0.4.3 (2015-10-07)
|
||||
|
||||
* Bug fix: Read buffer to 0 fixes error with libevent and large quantity of I/O (@mbonneau)
|
||||
* Bug fix: No double-write during drain call (@arnaud-lb)
|
||||
* Bug fix: Support HHVM (@clue)
|
||||
* Adjust compatibility to 5.3 (@clue)
|
||||
|
||||
## 0.4.2 (2014-09-09)
|
||||
|
||||
* Added DuplexStreamInterface
|
||||
* Stream sets stream resources to non-blocking
|
||||
* Fixed potential race condition in pipe
|
||||
|
||||
## 0.4.1 (2014-04-13)
|
||||
|
||||
* Bug fix: v0.3.4 changes merged for v0.4.1
|
||||
|
||||
## 0.3.4 (2014-03-30)
|
||||
|
||||
* Bug fix: [Stream] Fixed 100% CPU spike from non-empty write buffer on closed stream
|
||||
|
||||
## 0.4.0 (2014-02-02)
|
||||
|
||||
* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
|
||||
* BC break: Update to Evenement 2.0
|
||||
* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
|
||||
|
||||
## 0.3.3 (2013-07-08)
|
||||
|
||||
* Bug fix: [Stream] Correctly detect closed connections
|
||||
|
||||
## 0.3.2 (2013-05-10)
|
||||
|
||||
* Bug fix: [Stream] Make sure CompositeStream is closed properly
|
||||
|
||||
## 0.3.1 (2013-04-21)
|
||||
|
||||
* Bug fix: [Stream] Allow any `ReadableStreamInterface` on `BufferedSink::createPromise()`
|
||||
|
||||
## 0.3.0 (2013-04-14)
|
||||
|
||||
* Feature: [Stream] Factory method for BufferedSink
|
||||
|
||||
## 0.2.6 (2012-12-26)
|
||||
|
||||
* Version bump
|
||||
|
||||
## 0.2.5 (2012-11-26)
|
||||
|
||||
* Feature: Make BufferedSink trigger progress events on the promise (@jsor)
|
||||
|
||||
## 0.2.4 (2012-11-18)
|
||||
|
||||
* Feature: Added ThroughStream, CompositeStream, ReadableStream and WritableStream
|
||||
* Feature: Added BufferedSink
|
||||
|
||||
## 0.2.3 (2012-11-14)
|
||||
|
||||
* Version bump
|
||||
|
||||
## 0.2.2 (2012-10-28)
|
||||
|
||||
* Version bump
|
||||
|
||||
## 0.2.1 (2012-10-14)
|
||||
|
||||
* Bug fix: Check for EOF in `Buffer::write()`
|
||||
|
||||
## 0.2.0 (2012-09-10)
|
||||
|
||||
* Version bump
|
||||
|
||||
## 0.1.1 (2012-07-12)
|
||||
|
||||
* Bug fix: Testing and functional against PHP >= 5.3.3 and <= 5.3.8
|
||||
|
||||
## 0.1.0 (2012-07-11)
|
||||
|
||||
* First tagged release
|
||||
19
vendor/react/stream/LICENSE
vendored
Executable file
19
vendor/react/stream/LICENSE
vendored
Executable file
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2012 Igor Wiedler, Chris Boden
|
||||
|
||||
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.
|
||||
1225
vendor/react/stream/README.md
vendored
Executable file
1225
vendor/react/stream/README.md
vendored
Executable file
File diff suppressed because it is too large
Load Diff
25
vendor/react/stream/composer.json
vendored
Executable file
25
vendor/react/stream/composer.json
vendored
Executable file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "react/stream",
|
||||
"description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP",
|
||||
"keywords": ["event-driven", "readable", "writable", "stream", "non-blocking", "io", "pipe", "ReactPHP"],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": ">=5.3.8",
|
||||
"react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
|
||||
"evenement/evenement": "^3.0 || ^2.0 || ^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35",
|
||||
"clue/stream-filter": "~1.2"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"React\\Stream\\": "src"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"React\\Tests\\Stream\\": "tests"
|
||||
}
|
||||
}
|
||||
}
|
||||
40
vendor/react/stream/examples/01-http.php
vendored
Executable file
40
vendor/react/stream/examples/01-http.php
vendored
Executable file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
// Simple plaintext HTTP client example (for illustration purposes only).
|
||||
// This shows how a plaintext TCP/IP connection is established to then send an
|
||||
// application level protocol message (HTTP).
|
||||
// Real applications should use react/http-client instead!
|
||||
//
|
||||
// This simple example only accepts an optional host parameter to send the
|
||||
// request to.
|
||||
//
|
||||
// $ php examples/01-http.php
|
||||
// $ php examples/01-http.php reactphp.org
|
||||
|
||||
use React\EventLoop\Factory;
|
||||
use React\Stream\DuplexResourceStream;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$host = isset($argv[1]) ? $argv[1] : 'www.google.com';
|
||||
|
||||
// connect to tcp://www.google.com:80 (blocking call!)
|
||||
// for illustration purposes only, should use react/http-client or react/socket instead!
|
||||
$resource = stream_socket_client('tcp://' . $host . ':80');
|
||||
if (!$resource) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$loop = Factory::create();
|
||||
$stream = new DuplexResourceStream($resource, $loop);
|
||||
|
||||
$stream->on('data', function ($chunk) {
|
||||
echo $chunk;
|
||||
});
|
||||
$stream->on('close', function () {
|
||||
echo '[CLOSED]' . PHP_EOL;
|
||||
});
|
||||
|
||||
$stream->write("GET / HTTP/1.0\r\nHost: $host\r\n\r\n");
|
||||
|
||||
$loop->run();
|
||||
40
vendor/react/stream/examples/02-https.php
vendored
Executable file
40
vendor/react/stream/examples/02-https.php
vendored
Executable file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
// Simple secure HTTPS client example (for illustration purposes only).
|
||||
// This shows how a secure TLS connection is established to then send an
|
||||
// application level protocol message (HTTP).
|
||||
// Real applications should use react/http-client instead!
|
||||
//
|
||||
// This simple example only accepts an optional host parameter to send the
|
||||
// request to.
|
||||
//
|
||||
// $ php examples/02-https.php
|
||||
// $ php examples/02-https.php reactphp.org
|
||||
|
||||
use React\EventLoop\Factory;
|
||||
use React\Stream\DuplexResourceStream;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$host = isset($argv[1]) ? $argv[1] : 'www.google.com';
|
||||
|
||||
// connect to tls://www.google.com:443 (blocking call!)
|
||||
// for illustration purposes only, should use react/http-client or react/socket instead!
|
||||
$resource = stream_socket_client('tls://' . $host . ':443');
|
||||
if (!$resource) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$loop = Factory::create();
|
||||
$stream = new DuplexResourceStream($resource, $loop);
|
||||
|
||||
$stream->on('data', function ($chunk) {
|
||||
echo $chunk;
|
||||
});
|
||||
$stream->on('close', function () {
|
||||
echo '[CLOSED]' . PHP_EOL;
|
||||
});
|
||||
|
||||
$stream->write("GET / HTTP/1.0\r\nHost: $host\r\n\r\n");
|
||||
|
||||
$loop->run();
|
||||
28
vendor/react/stream/examples/11-cat.php
vendored
Executable file
28
vendor/react/stream/examples/11-cat.php
vendored
Executable file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
// Simple example piping everything from STDIN to STDOUT.
|
||||
// This allows you to output everything you type on your keyboard or to redirect
|
||||
// the pipes to show contents of files and other streams.
|
||||
//
|
||||
// $ php examples/11-cat.php
|
||||
// $ php examples/11-cat.php < README.md
|
||||
// $ echo hello | php examples/11-cat.php
|
||||
|
||||
use React\EventLoop\Factory;
|
||||
use React\Stream\ReadableResourceStream;
|
||||
use React\Stream\WritableResourceStream;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
if (DIRECTORY_SEPARATOR === '\\') {
|
||||
fwrite(STDERR, 'Non-blocking console I/O not supported on Microsoft Windows' . PHP_EOL);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$loop = Factory::create();
|
||||
|
||||
$stdout = new WritableResourceStream(STDOUT, $loop);
|
||||
$stdin = new ReadableResourceStream(STDIN, $loop);
|
||||
$stdin->pipe($stdout);
|
||||
|
||||
$loop->run();
|
||||
62
vendor/react/stream/examples/91-benchmark-throughput.php
vendored
Executable file
62
vendor/react/stream/examples/91-benchmark-throughput.php
vendored
Executable file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
// Benchmark to measure throughput performance piping an input stream to an output stream.
|
||||
// This allows you to get an idea of how fast stream processing with PHP can be
|
||||
// and also to play around with differnt types of input and output streams.
|
||||
//
|
||||
// This example accepts a number of parameters to control the timeout (-t 1),
|
||||
// the input file (-i /dev/zero) and the output file (-o /dev/null).
|
||||
//
|
||||
// $ php examples/91-benchmark-throughput.php
|
||||
// $ php examples/91-benchmark-throughput.php -t 10 -o zero.bin
|
||||
// $ php examples/91-benchmark-throughput.php -t 60 -i zero.bin
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
if (DIRECTORY_SEPARATOR === '\\') {
|
||||
fwrite(STDERR, 'Non-blocking console I/O not supported on Microsoft Windows' . PHP_EOL);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$args = getopt('i:o:t:');
|
||||
$if = isset($args['i']) ? $args['i'] : '/dev/zero';
|
||||
$of = isset($args['o']) ? $args['o'] : '/dev/null';
|
||||
$t = isset($args['t']) ? $args['t'] : 1;
|
||||
|
||||
// passing file descriptors requires mapping paths (https://bugs.php.net/bug.php?id=53465)
|
||||
$if = str_replace('/dev/fd/', 'php://fd/', $if);
|
||||
$of = str_replace('/dev/fd/', 'php://fd/', $of);
|
||||
|
||||
$loop = new React\EventLoop\StreamSelectLoop();
|
||||
|
||||
// setup information stream
|
||||
$info = new React\Stream\WritableResourceStream(STDERR, $loop);
|
||||
if (extension_loaded('xdebug')) {
|
||||
$info->write('NOTICE: The "xdebug" extension is loaded, this has a major impact on performance.' . PHP_EOL);
|
||||
}
|
||||
$info->write('piping from ' . $if . ' to ' . $of . ' (for max ' . $t . ' second(s)) ...'. PHP_EOL);
|
||||
|
||||
// setup input and output streams and pipe inbetween
|
||||
$fh = fopen($if, 'r');
|
||||
$in = new React\Stream\ReadableResourceStream($fh, $loop);
|
||||
$out = new React\Stream\WritableResourceStream(fopen($of, 'w'), $loop);
|
||||
$in->pipe($out);
|
||||
|
||||
// stop input stream in $t seconds
|
||||
$start = microtime(true);
|
||||
$timeout = $loop->addTimer($t, function () use ($in, &$bytes) {
|
||||
$in->close();
|
||||
});
|
||||
|
||||
// print stream position once stream closes
|
||||
$in->on('close', function () use ($fh, $start, $loop, $timeout, $info) {
|
||||
$t = microtime(true) - $start;
|
||||
$loop->cancelTimer($timeout);
|
||||
|
||||
$bytes = ftell($fh);
|
||||
|
||||
$info->write('read ' . $bytes . ' byte(s) in ' . round($t, 3) . ' second(s) => ' . round($bytes / 1024 / 1024 / $t, 1) . ' MiB/s' . PHP_EOL);
|
||||
$info->write('peak memory usage of ' . round(memory_get_peak_usage(true) / 1024 / 1024, 1) . ' MiB' . PHP_EOL);
|
||||
});
|
||||
|
||||
$loop->run();
|
||||
25
vendor/react/stream/phpunit.xml.dist
vendored
Executable file
25
vendor/react/stream/phpunit.xml.dist
vendored
Executable file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<phpunit backupGlobals="false"
|
||||
backupStaticAttributes="false"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false"
|
||||
syntaxCheck="false"
|
||||
bootstrap="vendor/autoload.php"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="React Test Suite">
|
||||
<directory>./tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory>./src/</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
||||
82
vendor/react/stream/src/CompositeStream.php
vendored
Executable file
82
vendor/react/stream/src/CompositeStream.php
vendored
Executable file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace React\Stream;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
|
||||
final class CompositeStream extends EventEmitter implements DuplexStreamInterface
|
||||
{
|
||||
private $readable;
|
||||
private $writable;
|
||||
private $closed = false;
|
||||
|
||||
public function __construct(ReadableStreamInterface $readable, WritableStreamInterface $writable)
|
||||
{
|
||||
$this->readable = $readable;
|
||||
$this->writable = $writable;
|
||||
|
||||
if (!$readable->isReadable() || !$writable->isWritable()) {
|
||||
return $this->close();
|
||||
}
|
||||
|
||||
Util::forwardEvents($this->readable, $this, array('data', 'end', 'error'));
|
||||
Util::forwardEvents($this->writable, $this, array('drain', 'error', 'pipe'));
|
||||
|
||||
$this->readable->on('close', array($this, 'close'));
|
||||
$this->writable->on('close', array($this, 'close'));
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return $this->readable->isReadable();
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
$this->readable->pause();
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
if (!$this->writable->isWritable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->readable->resume();
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
return Util::pipe($this, $dest, $options);
|
||||
}
|
||||
|
||||
public function isWritable()
|
||||
{
|
||||
return $this->writable->isWritable();
|
||||
}
|
||||
|
||||
public function write($data)
|
||||
{
|
||||
return $this->writable->write($data);
|
||||
}
|
||||
|
||||
public function end($data = null)
|
||||
{
|
||||
$this->readable->pause();
|
||||
$this->writable->end($data);
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->closed = true;
|
||||
$this->readable->close();
|
||||
$this->writable->close();
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
}
|
||||
224
vendor/react/stream/src/DuplexResourceStream.php
vendored
Executable file
224
vendor/react/stream/src/DuplexResourceStream.php
vendored
Executable file
@@ -0,0 +1,224 @@
|
||||
<?php
|
||||
|
||||
namespace React\Stream;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use InvalidArgumentException;
|
||||
|
||||
final class DuplexResourceStream extends EventEmitter implements DuplexStreamInterface
|
||||
{
|
||||
private $stream;
|
||||
private $loop;
|
||||
|
||||
/**
|
||||
* Controls the maximum buffer size in bytes to read at once from the stream.
|
||||
*
|
||||
* This can be a positive number which means that up to X bytes will be read
|
||||
* at once from the underlying stream resource. Note that the actual number
|
||||
* of bytes read may be lower if the stream resource has less than X bytes
|
||||
* currently available.
|
||||
*
|
||||
* This can be `-1` which means read everything available from the
|
||||
* underlying stream resource.
|
||||
* This should read until the stream resource is not readable anymore
|
||||
* (i.e. underlying buffer drained), note that this does not neccessarily
|
||||
* mean it reached EOF.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $bufferSize;
|
||||
private $buffer;
|
||||
|
||||
private $readable = true;
|
||||
private $writable = true;
|
||||
private $closing = false;
|
||||
private $listening = false;
|
||||
|
||||
public function __construct($stream, LoopInterface $loop, $readChunkSize = null, WritableStreamInterface $buffer = null)
|
||||
{
|
||||
if (!\is_resource($stream) || \get_resource_type($stream) !== "stream") {
|
||||
throw new InvalidArgumentException('First parameter must be a valid stream resource');
|
||||
}
|
||||
|
||||
// ensure resource is opened for reading and wrting (fopen mode must contain "+")
|
||||
$meta = \stream_get_meta_data($stream);
|
||||
if (isset($meta['mode']) && $meta['mode'] !== '' && \strpos($meta['mode'], '+') === false) {
|
||||
throw new InvalidArgumentException('Given stream resource is not opened in read and write mode');
|
||||
}
|
||||
|
||||
// this class relies on non-blocking I/O in order to not interrupt the event loop
|
||||
// e.g. pipes on Windows do not support this: https://bugs.php.net/bug.php?id=47918
|
||||
if (\stream_set_blocking($stream, 0) !== true) {
|
||||
throw new \RuntimeException('Unable to set stream resource to non-blocking mode');
|
||||
}
|
||||
|
||||
// Use unbuffered read operations on the underlying stream resource.
|
||||
// Reading chunks from the stream may otherwise leave unread bytes in
|
||||
// PHP's stream buffers which some event loop implementations do not
|
||||
// trigger events on (edge triggered).
|
||||
// This does not affect the default event loop implementation (level
|
||||
// triggered), so we can ignore platforms not supporting this (HHVM).
|
||||
// Pipe streams (such as STDIN) do not seem to require this and legacy
|
||||
// PHP versions cause SEGFAULTs on unbuffered pipe streams, so skip this.
|
||||
if (\function_exists('stream_set_read_buffer') && !$this->isLegacyPipe($stream)) {
|
||||
\stream_set_read_buffer($stream, 0);
|
||||
}
|
||||
|
||||
if ($buffer === null) {
|
||||
$buffer = new WritableResourceStream($stream, $loop);
|
||||
}
|
||||
|
||||
$this->stream = $stream;
|
||||
$this->loop = $loop;
|
||||
$this->bufferSize = ($readChunkSize === null) ? 65536 : (int)$readChunkSize;
|
||||
$this->buffer = $buffer;
|
||||
|
||||
$that = $this;
|
||||
|
||||
$this->buffer->on('error', function ($error) use ($that) {
|
||||
$that->emit('error', array($error));
|
||||
});
|
||||
|
||||
$this->buffer->on('close', array($this, 'close'));
|
||||
|
||||
$this->buffer->on('drain', function () use ($that) {
|
||||
$that->emit('drain');
|
||||
});
|
||||
|
||||
$this->resume();
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return $this->readable;
|
||||
}
|
||||
|
||||
public function isWritable()
|
||||
{
|
||||
return $this->writable;
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
if ($this->listening) {
|
||||
$this->loop->removeReadStream($this->stream);
|
||||
$this->listening = false;
|
||||
}
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
if (!$this->listening && $this->readable) {
|
||||
$this->loop->addReadStream($this->stream, array($this, 'handleData'));
|
||||
$this->listening = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function write($data)
|
||||
{
|
||||
if (!$this->writable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->buffer->write($data);
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if (!$this->writable && !$this->closing) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->closing = false;
|
||||
|
||||
$this->readable = false;
|
||||
$this->writable = false;
|
||||
|
||||
$this->emit('close');
|
||||
$this->pause();
|
||||
$this->buffer->close();
|
||||
$this->removeAllListeners();
|
||||
|
||||
if (\is_resource($this->stream)) {
|
||||
\fclose($this->stream);
|
||||
}
|
||||
}
|
||||
|
||||
public function end($data = null)
|
||||
{
|
||||
if (!$this->writable) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->closing = true;
|
||||
|
||||
$this->readable = false;
|
||||
$this->writable = false;
|
||||
$this->pause();
|
||||
|
||||
$this->buffer->end($data);
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
return Util::pipe($this, $dest, $options);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleData($stream)
|
||||
{
|
||||
$error = null;
|
||||
\set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) {
|
||||
$error = new \ErrorException(
|
||||
$errstr,
|
||||
0,
|
||||
$errno,
|
||||
$errfile,
|
||||
$errline
|
||||
);
|
||||
});
|
||||
|
||||
$data = \stream_get_contents($stream, $this->bufferSize);
|
||||
|
||||
\restore_error_handler();
|
||||
|
||||
if ($error !== null) {
|
||||
$this->emit('error', array(new \RuntimeException('Unable to read from stream: ' . $error->getMessage(), 0, $error)));
|
||||
$this->close();
|
||||
return;
|
||||
}
|
||||
|
||||
if ($data !== '') {
|
||||
$this->emit('data', array($data));
|
||||
} elseif (\feof($this->stream)) {
|
||||
// no data read => we reached the end and close the stream
|
||||
$this->emit('end');
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this is a pipe resource in a legacy environment
|
||||
*
|
||||
* This works around a legacy PHP bug (#61019) that was fixed in PHP 5.4.28+
|
||||
* and PHP 5.5.12+ and newer.
|
||||
*
|
||||
* @param resource $resource
|
||||
* @return bool
|
||||
* @link https://github.com/reactphp/child-process/issues/40
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
private function isLegacyPipe($resource)
|
||||
{
|
||||
if (\PHP_VERSION_ID < 50428 || (\PHP_VERSION_ID >= 50500 && \PHP_VERSION_ID < 50512)) {
|
||||
$meta = \stream_get_meta_data($resource);
|
||||
|
||||
if (isset($meta['stream_type']) && $meta['stream_type'] === 'STDIO') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
39
vendor/react/stream/src/DuplexStreamInterface.php
vendored
Executable file
39
vendor/react/stream/src/DuplexStreamInterface.php
vendored
Executable file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace React\Stream;
|
||||
|
||||
/**
|
||||
* The `DuplexStreamInterface` is responsible for providing an interface for
|
||||
* duplex streams (both readable and writable).
|
||||
*
|
||||
* It builds on top of the existing interfaces for readable and writable streams
|
||||
* and follows the exact same method and event semantics.
|
||||
* If you're new to this concept, you should look into the
|
||||
* `ReadableStreamInterface` and `WritableStreamInterface` first.
|
||||
*
|
||||
* Besides defining a few methods, this interface also implements the
|
||||
* `EventEmitterInterface` which allows you to react to the same events defined
|
||||
* on the `ReadbleStreamInterface` and `WritableStreamInterface`.
|
||||
*
|
||||
* The event callback functions MUST be a valid `callable` that obeys strict
|
||||
* parameter definitions and MUST accept event parameters exactly as documented.
|
||||
* The event callback functions MUST NOT throw an `Exception`.
|
||||
* The return value of the event callback functions will be ignored and has no
|
||||
* effect, so for performance reasons you're recommended to not return any
|
||||
* excessive data structures.
|
||||
*
|
||||
* Every implementation of this interface MUST follow these event semantics in
|
||||
* order to be considered a well-behaving stream.
|
||||
*
|
||||
* > Note that higher-level implementations of this interface may choose to
|
||||
* define additional events with dedicated semantics not defined as part of
|
||||
* this low-level stream specification. Conformance with these event semantics
|
||||
* is out of scope for this interface, so you may also have to refer to the
|
||||
* documentation of such a higher-level implementation.
|
||||
*
|
||||
* @see ReadableStreamInterface
|
||||
* @see WritableStreamInterface
|
||||
*/
|
||||
interface DuplexStreamInterface extends ReadableStreamInterface, WritableStreamInterface
|
||||
{
|
||||
}
|
||||
177
vendor/react/stream/src/ReadableResourceStream.php
vendored
Executable file
177
vendor/react/stream/src/ReadableResourceStream.php
vendored
Executable file
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
|
||||
namespace React\Stream;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use InvalidArgumentException;
|
||||
|
||||
final class ReadableResourceStream extends EventEmitter implements ReadableStreamInterface
|
||||
{
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
private $stream;
|
||||
|
||||
private $loop;
|
||||
|
||||
/**
|
||||
* Controls the maximum buffer size in bytes to read at once from the stream.
|
||||
*
|
||||
* This value SHOULD NOT be changed unless you know what you're doing.
|
||||
*
|
||||
* This can be a positive number which means that up to X bytes will be read
|
||||
* at once from the underlying stream resource. Note that the actual number
|
||||
* of bytes read may be lower if the stream resource has less than X bytes
|
||||
* currently available.
|
||||
*
|
||||
* This can be `-1` which means read everything available from the
|
||||
* underlying stream resource.
|
||||
* This should read until the stream resource is not readable anymore
|
||||
* (i.e. underlying buffer drained), note that this does not neccessarily
|
||||
* mean it reached EOF.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $bufferSize;
|
||||
|
||||
private $closed = false;
|
||||
private $listening = false;
|
||||
|
||||
public function __construct($stream, LoopInterface $loop, $readChunkSize = null)
|
||||
{
|
||||
if (!\is_resource($stream) || \get_resource_type($stream) !== "stream") {
|
||||
throw new InvalidArgumentException('First parameter must be a valid stream resource');
|
||||
}
|
||||
|
||||
// ensure resource is opened for reading (fopen mode must contain "r" or "+")
|
||||
$meta = \stream_get_meta_data($stream);
|
||||
if (isset($meta['mode']) && $meta['mode'] !== '' && \strpos($meta['mode'], 'r') === \strpos($meta['mode'], '+')) {
|
||||
throw new InvalidArgumentException('Given stream resource is not opened in read mode');
|
||||
}
|
||||
|
||||
// this class relies on non-blocking I/O in order to not interrupt the event loop
|
||||
// e.g. pipes on Windows do not support this: https://bugs.php.net/bug.php?id=47918
|
||||
if (\stream_set_blocking($stream, 0) !== true) {
|
||||
throw new \RuntimeException('Unable to set stream resource to non-blocking mode');
|
||||
}
|
||||
|
||||
// Use unbuffered read operations on the underlying stream resource.
|
||||
// Reading chunks from the stream may otherwise leave unread bytes in
|
||||
// PHP's stream buffers which some event loop implementations do not
|
||||
// trigger events on (edge triggered).
|
||||
// This does not affect the default event loop implementation (level
|
||||
// triggered), so we can ignore platforms not supporting this (HHVM).
|
||||
// Pipe streams (such as STDIN) do not seem to require this and legacy
|
||||
// PHP versions cause SEGFAULTs on unbuffered pipe streams, so skip this.
|
||||
if (\function_exists('stream_set_read_buffer') && !$this->isLegacyPipe($stream)) {
|
||||
\stream_set_read_buffer($stream, 0);
|
||||
}
|
||||
|
||||
$this->stream = $stream;
|
||||
$this->loop = $loop;
|
||||
$this->bufferSize = ($readChunkSize === null) ? 65536 : (int)$readChunkSize;
|
||||
|
||||
$this->resume();
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return !$this->closed;
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
if ($this->listening) {
|
||||
$this->loop->removeReadStream($this->stream);
|
||||
$this->listening = false;
|
||||
}
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
if (!$this->listening && !$this->closed) {
|
||||
$this->loop->addReadStream($this->stream, array($this, 'handleData'));
|
||||
$this->listening = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
return Util::pipe($this, $dest, $options);
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->closed = true;
|
||||
|
||||
$this->emit('close');
|
||||
$this->pause();
|
||||
$this->removeAllListeners();
|
||||
|
||||
if (\is_resource($this->stream)) {
|
||||
\fclose($this->stream);
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleData()
|
||||
{
|
||||
$error = null;
|
||||
\set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) {
|
||||
$error = new \ErrorException(
|
||||
$errstr,
|
||||
0,
|
||||
$errno,
|
||||
$errfile,
|
||||
$errline
|
||||
);
|
||||
});
|
||||
|
||||
$data = \stream_get_contents($this->stream, $this->bufferSize);
|
||||
|
||||
\restore_error_handler();
|
||||
|
||||
if ($error !== null) {
|
||||
$this->emit('error', array(new \RuntimeException('Unable to read from stream: ' . $error->getMessage(), 0, $error)));
|
||||
$this->close();
|
||||
return;
|
||||
}
|
||||
|
||||
if ($data !== '') {
|
||||
$this->emit('data', array($data));
|
||||
} elseif (\feof($this->stream)) {
|
||||
// no data read => we reached the end and close the stream
|
||||
$this->emit('end');
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this is a pipe resource in a legacy environment
|
||||
*
|
||||
* This works around a legacy PHP bug (#61019) that was fixed in PHP 5.4.28+
|
||||
* and PHP 5.5.12+ and newer.
|
||||
*
|
||||
* @param resource $resource
|
||||
* @return bool
|
||||
* @link https://github.com/reactphp/child-process/issues/40
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
private function isLegacyPipe($resource)
|
||||
{
|
||||
if (\PHP_VERSION_ID < 50428 || (\PHP_VERSION_ID >= 50500 && \PHP_VERSION_ID < 50512)) {
|
||||
$meta = \stream_get_meta_data($resource);
|
||||
|
||||
if (isset($meta['stream_type']) && $meta['stream_type'] === 'STDIO') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
362
vendor/react/stream/src/ReadableStreamInterface.php
vendored
Executable file
362
vendor/react/stream/src/ReadableStreamInterface.php
vendored
Executable file
@@ -0,0 +1,362 @@
|
||||
<?php
|
||||
|
||||
namespace React\Stream;
|
||||
|
||||
use Evenement\EventEmitterInterface;
|
||||
|
||||
/**
|
||||
* The `ReadableStreamInterface` is responsible for providing an interface for
|
||||
* read-only streams and the readable side of duplex streams.
|
||||
*
|
||||
* Besides defining a few methods, this interface also implements the
|
||||
* `EventEmitterInterface` which allows you to react to certain events:
|
||||
*
|
||||
* data event:
|
||||
* The `data` event will be emitted whenever some data was read/received
|
||||
* from this source stream.
|
||||
* The event receives a single mixed argument for incoming data.
|
||||
*
|
||||
* ```php
|
||||
* $stream->on('data', function ($data) {
|
||||
* echo $data;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* This event MAY be emitted any number of times, which may be zero times if
|
||||
* this stream does not send any data at all.
|
||||
* It SHOULD not be emitted after an `end` or `close` event.
|
||||
*
|
||||
* The given `$data` argument may be of mixed type, but it's usually
|
||||
* recommended it SHOULD be a `string` value or MAY use a type that allows
|
||||
* representation as a `string` for maximum compatibility.
|
||||
*
|
||||
* Many common streams (such as a TCP/IP connection or a file-based stream)
|
||||
* will emit the raw (binary) payload data that is received over the wire as
|
||||
* chunks of `string` values.
|
||||
*
|
||||
* Due to the stream-based nature of this, the sender may send any number
|
||||
* of chunks with varying sizes. There are no guarantees that these chunks
|
||||
* will be received with the exact same framing the sender intended to send.
|
||||
* In other words, many lower-level protocols (such as TCP/IP) transfer the
|
||||
* data in chunks that may be anywhere between single-byte values to several
|
||||
* dozens of kilobytes. You may want to apply a higher-level protocol to
|
||||
* these low-level data chunks in order to achieve proper message framing.
|
||||
*
|
||||
* end event:
|
||||
* The `end` event will be emitted once the source stream has successfully
|
||||
* reached the end of the stream (EOF).
|
||||
*
|
||||
* ```php
|
||||
* $stream->on('end', function () {
|
||||
* echo 'END';
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* This event SHOULD be emitted once or never at all, depending on whether
|
||||
* a successful end was detected.
|
||||
* It SHOULD NOT be emitted after a previous `end` or `close` event.
|
||||
* It MUST NOT be emitted if the stream closes due to a non-successful
|
||||
* end, such as after a previous `error` event.
|
||||
*
|
||||
* After the stream is ended, it MUST switch to non-readable mode,
|
||||
* see also `isReadable()`.
|
||||
*
|
||||
* This event will only be emitted if the *end* was reached successfully,
|
||||
* not if the stream was interrupted by an unrecoverable error or explicitly
|
||||
* closed. Not all streams know this concept of a "successful end".
|
||||
* Many use-cases involve detecting when the stream closes (terminates)
|
||||
* instead, in this case you should use the `close` event.
|
||||
* After the stream emits an `end` event, it SHOULD usually be followed by a
|
||||
* `close` event.
|
||||
*
|
||||
* Many common streams (such as a TCP/IP connection or a file-based stream)
|
||||
* will emit this event if either the remote side closes the connection or
|
||||
* a file handle was successfully read until reaching its end (EOF).
|
||||
*
|
||||
* Note that this event should not be confused with the `end()` method.
|
||||
* This event defines a successful end *reading* from a source stream, while
|
||||
* the `end()` method defines *writing* a successful end to a destination
|
||||
* stream.
|
||||
*
|
||||
* error event:
|
||||
* The `error` event will be emitted once a fatal error occurs, usually while
|
||||
* trying to read from this stream.
|
||||
* The event receives a single `Exception` argument for the error instance.
|
||||
*
|
||||
* ```php
|
||||
* $stream->on('error', function (Exception $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* This event SHOULD be emitted once the stream detects a fatal error, such
|
||||
* as a fatal transmission error or after an unexpected `data` or premature
|
||||
* `end` event.
|
||||
* It SHOULD NOT be emitted after a previous `error`, `end` or `close` event.
|
||||
* It MUST NOT be emitted if this is not a fatal error condition, such as
|
||||
* a temporary network issue that did not cause any data to be lost.
|
||||
*
|
||||
* After the stream errors, it MUST close the stream and SHOULD thus be
|
||||
* followed by a `close` event and then switch to non-readable mode, see
|
||||
* also `close()` and `isReadable()`.
|
||||
*
|
||||
* Many common streams (such as a TCP/IP connection or a file-based stream)
|
||||
* only deal with data transmission and do not make assumption about data
|
||||
* boundaries (such as unexpected `data` or premature `end` events).
|
||||
* In other words, many lower-level protocols (such as TCP/IP) may choose
|
||||
* to only emit this for a fatal transmission error once and will then
|
||||
* close (terminate) the stream in response.
|
||||
*
|
||||
* If this stream is a `DuplexStreamInterface`, you should also notice
|
||||
* how the writable side of the stream also implements an `error` event.
|
||||
* In other words, an error may occur while either reading or writing the
|
||||
* stream which should result in the same error processing.
|
||||
*
|
||||
* close event:
|
||||
* The `close` event will be emitted once the stream closes (terminates).
|
||||
*
|
||||
* ```php
|
||||
* $stream->on('close', function () {
|
||||
* echo 'CLOSED';
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* This event SHOULD be emitted once or never at all, depending on whether
|
||||
* the stream ever terminates.
|
||||
* It SHOULD NOT be emitted after a previous `close` event.
|
||||
*
|
||||
* After the stream is closed, it MUST switch to non-readable mode,
|
||||
* see also `isReadable()`.
|
||||
*
|
||||
* Unlike the `end` event, this event SHOULD be emitted whenever the stream
|
||||
* closes, irrespective of whether this happens implicitly due to an
|
||||
* unrecoverable error or explicitly when either side closes the stream.
|
||||
* If you only want to detect a *successful* end, you should use the `end`
|
||||
* event instead.
|
||||
*
|
||||
* Many common streams (such as a TCP/IP connection or a file-based stream)
|
||||
* will likely choose to emit this event after reading a *successful* `end`
|
||||
* event or after a fatal transmission `error` event.
|
||||
*
|
||||
* If this stream is a `DuplexStreamInterface`, you should also notice
|
||||
* how the writable side of the stream also implements a `close` event.
|
||||
* In other words, after receiving this event, the stream MUST switch into
|
||||
* non-writable AND non-readable mode, see also `isWritable()`.
|
||||
* Note that this event should not be confused with the `end` event.
|
||||
*
|
||||
* The event callback functions MUST be a valid `callable` that obeys strict
|
||||
* parameter definitions and MUST accept event parameters exactly as documented.
|
||||
* The event callback functions MUST NOT throw an `Exception`.
|
||||
* The return value of the event callback functions will be ignored and has no
|
||||
* effect, so for performance reasons you're recommended to not return any
|
||||
* excessive data structures.
|
||||
*
|
||||
* Every implementation of this interface MUST follow these event semantics in
|
||||
* order to be considered a well-behaving stream.
|
||||
*
|
||||
* > Note that higher-level implementations of this interface may choose to
|
||||
* define additional events with dedicated semantics not defined as part of
|
||||
* this low-level stream specification. Conformance with these event semantics
|
||||
* is out of scope for this interface, so you may also have to refer to the
|
||||
* documentation of such a higher-level implementation.
|
||||
*
|
||||
* @see EventEmitterInterface
|
||||
*/
|
||||
interface ReadableStreamInterface extends EventEmitterInterface
|
||||
{
|
||||
/**
|
||||
* Checks whether this stream is in a readable state (not closed already).
|
||||
*
|
||||
* This method can be used to check if the stream still accepts incoming
|
||||
* data events or if it is ended or closed already.
|
||||
* Once the stream is non-readable, no further `data` or `end` events SHOULD
|
||||
* be emitted.
|
||||
*
|
||||
* ```php
|
||||
* assert($stream->isReadable() === false);
|
||||
*
|
||||
* $stream->on('data', assertNeverCalled());
|
||||
* $stream->on('end', assertNeverCalled());
|
||||
* ```
|
||||
*
|
||||
* A successfully opened stream always MUST start in readable mode.
|
||||
*
|
||||
* Once the stream ends or closes, it MUST switch to non-readable mode.
|
||||
* This can happen any time, explicitly through `close()` or
|
||||
* implicitly due to a remote close or an unrecoverable transmission error.
|
||||
* Once a stream has switched to non-readable mode, it MUST NOT transition
|
||||
* back to readable mode.
|
||||
*
|
||||
* If this stream is a `DuplexStreamInterface`, you should also notice
|
||||
* how the writable side of the stream also implements an `isWritable()`
|
||||
* method. Unless this is a half-open duplex stream, they SHOULD usually
|
||||
* have the same return value.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isReadable();
|
||||
|
||||
/**
|
||||
* Pauses reading incoming data events.
|
||||
*
|
||||
* Removes the data source file descriptor from the event loop. This
|
||||
* allows you to throttle incoming data.
|
||||
*
|
||||
* Unless otherwise noted, a successfully opened stream SHOULD NOT start
|
||||
* in paused state.
|
||||
*
|
||||
* Once the stream is paused, no futher `data` or `end` events SHOULD
|
||||
* be emitted.
|
||||
*
|
||||
* ```php
|
||||
* $stream->pause();
|
||||
*
|
||||
* $stream->on('data', assertShouldNeverCalled());
|
||||
* $stream->on('end', assertShouldNeverCalled());
|
||||
* ```
|
||||
*
|
||||
* This method is advisory-only, though generally not recommended, the
|
||||
* stream MAY continue emitting `data` events.
|
||||
*
|
||||
* You can continue processing events by calling `resume()` again.
|
||||
*
|
||||
* Note that both methods can be called any number of times, in particular
|
||||
* calling `pause()` more than once SHOULD NOT have any effect.
|
||||
*
|
||||
* @see self::resume()
|
||||
* @return void
|
||||
*/
|
||||
public function pause();
|
||||
|
||||
/**
|
||||
* Resumes reading incoming data events.
|
||||
*
|
||||
* Re-attach the data source after a previous `pause()`.
|
||||
*
|
||||
* ```php
|
||||
* $stream->pause();
|
||||
*
|
||||
* $loop->addTimer(1.0, function () use ($stream) {
|
||||
* $stream->resume();
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* Note that both methods can be called any number of times, in particular
|
||||
* calling `resume()` without a prior `pause()` SHOULD NOT have any effect.
|
||||
*
|
||||
* @see self::pause()
|
||||
* @return void
|
||||
*/
|
||||
public function resume();
|
||||
|
||||
/**
|
||||
* Pipes all the data from this readable source into the given writable destination.
|
||||
*
|
||||
* Automatically sends all incoming data to the destination.
|
||||
* Automatically throttles the source based on what the destination can handle.
|
||||
*
|
||||
* ```php
|
||||
* $source->pipe($dest);
|
||||
* ```
|
||||
*
|
||||
* Similarly, you can also pipe an instance implementing `DuplexStreamInterface`
|
||||
* into itself in order to write back all the data that is received.
|
||||
* This may be a useful feature for a TCP/IP echo service:
|
||||
*
|
||||
* ```php
|
||||
* $connection->pipe($connection);
|
||||
* ```
|
||||
*
|
||||
* This method returns the destination stream as-is, which can be used to
|
||||
* set up chains of piped streams:
|
||||
*
|
||||
* ```php
|
||||
* $source->pipe($decodeGzip)->pipe($filterBadWords)->pipe($dest);
|
||||
* ```
|
||||
*
|
||||
* By default, this will call `end()` on the destination stream once the
|
||||
* source stream emits an `end` event. This can be disabled like this:
|
||||
*
|
||||
* ```php
|
||||
* $source->pipe($dest, array('end' => false));
|
||||
* ```
|
||||
*
|
||||
* Note that this only applies to the `end` event.
|
||||
* If an `error` or explicit `close` event happens on the source stream,
|
||||
* you'll have to manually close the destination stream:
|
||||
*
|
||||
* ```php
|
||||
* $source->pipe($dest);
|
||||
* $source->on('close', function () use ($dest) {
|
||||
* $dest->end('BYE!');
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* If the source stream is not readable (closed state), then this is a NO-OP.
|
||||
*
|
||||
* ```php
|
||||
* $source->close();
|
||||
* $source->pipe($dest); // NO-OP
|
||||
* ```
|
||||
*
|
||||
* If the destinantion stream is not writable (closed state), then this will simply
|
||||
* throttle (pause) the source stream:
|
||||
*
|
||||
* ```php
|
||||
* $dest->close();
|
||||
* $source->pipe($dest); // calls $source->pause()
|
||||
* ```
|
||||
*
|
||||
* Similarly, if the destination stream is closed while the pipe is still
|
||||
* active, it will also throttle (pause) the source stream:
|
||||
*
|
||||
* ```php
|
||||
* $source->pipe($dest);
|
||||
* $dest->close(); // calls $source->pause()
|
||||
* ```
|
||||
*
|
||||
* Once the pipe is set up successfully, the destination stream MUST emit
|
||||
* a `pipe` event with this source stream an event argument.
|
||||
*
|
||||
* @param WritableStreamInterface $dest
|
||||
* @param array $options
|
||||
* @return WritableStreamInterface $dest stream as-is
|
||||
*/
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array());
|
||||
|
||||
/**
|
||||
* Closes the stream (forcefully).
|
||||
*
|
||||
* This method can be used to (forcefully) close the stream.
|
||||
*
|
||||
* ```php
|
||||
* $stream->close();
|
||||
* ```
|
||||
*
|
||||
* Once the stream is closed, it SHOULD emit a `close` event.
|
||||
* Note that this event SHOULD NOT be emitted more than once, in particular
|
||||
* if this method is called multiple times.
|
||||
*
|
||||
* After calling this method, the stream MUST switch into a non-readable
|
||||
* mode, see also `isReadable()`.
|
||||
* This means that no further `data` or `end` events SHOULD be emitted.
|
||||
*
|
||||
* ```php
|
||||
* $stream->close();
|
||||
* assert($stream->isReadable() === false);
|
||||
*
|
||||
* $stream->on('data', assertNeverCalled());
|
||||
* $stream->on('end', assertNeverCalled());
|
||||
* ```
|
||||
*
|
||||
* If this stream is a `DuplexStreamInterface`, you should also notice
|
||||
* how the writable side of the stream also implements a `close()` method.
|
||||
* In other words, after calling this method, the stream MUST switch into
|
||||
* non-writable AND non-readable mode, see also `isWritable()`.
|
||||
* Note that this method should not be confused with the `end()` method.
|
||||
*
|
||||
* @return void
|
||||
* @see WritableStreamInterface::close()
|
||||
*/
|
||||
public function close();
|
||||
}
|
||||
190
vendor/react/stream/src/ThroughStream.php
vendored
Executable file
190
vendor/react/stream/src/ThroughStream.php
vendored
Executable file
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
namespace React\Stream;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* The `ThroughStream` implements the
|
||||
* [`DuplexStreamInterface`](#duplexstreaminterface) and will simply pass any data
|
||||
* you write to it through to its readable end.
|
||||
*
|
||||
* ```php
|
||||
* $through = new ThroughStream();
|
||||
* $through->on('data', $this->expectCallableOnceWith('hello'));
|
||||
*
|
||||
* $through->write('hello');
|
||||
* ```
|
||||
*
|
||||
* Similarly, the [`end()` method](#end) will end the stream and emit an
|
||||
* [`end` event](#end-event) and then [`close()`](#close-1) the stream.
|
||||
* The [`close()` method](#close-1) will close the stream and emit a
|
||||
* [`close` event](#close-event).
|
||||
* Accordingly, this is can also be used in a [`pipe()`](#pipe) context like this:
|
||||
*
|
||||
* ```php
|
||||
* $through = new ThroughStream();
|
||||
* $source->pipe($through)->pipe($dest);
|
||||
* ```
|
||||
*
|
||||
* Optionally, its constructor accepts any callable function which will then be
|
||||
* used to *filter* any data written to it. This function receives a single data
|
||||
* argument as passed to the writable side and must return the data as it will be
|
||||
* passed to its readable end:
|
||||
*
|
||||
* ```php
|
||||
* $through = new ThroughStream('strtoupper');
|
||||
* $source->pipe($through)->pipe($dest);
|
||||
* ```
|
||||
*
|
||||
* Note that this class makes no assumptions about any data types. This can be
|
||||
* used to convert data, for example for transforming any structured data into
|
||||
* a newline-delimited JSON (NDJSON) stream like this:
|
||||
*
|
||||
* ```php
|
||||
* $through = new ThroughStream(function ($data) {
|
||||
* return json_encode($data) . PHP_EOL;
|
||||
* });
|
||||
* $through->on('data', $this->expectCallableOnceWith("[2, true]\n"));
|
||||
*
|
||||
* $through->write(array(2, true));
|
||||
* ```
|
||||
*
|
||||
* The callback function is allowed to throw an `Exception`. In this case,
|
||||
* the stream will emit an `error` event and then [`close()`](#close-1) the stream.
|
||||
*
|
||||
* ```php
|
||||
* $through = new ThroughStream(function ($data) {
|
||||
* if (!is_string($data)) {
|
||||
* throw new \UnexpectedValueException('Only strings allowed');
|
||||
* }
|
||||
* return $data;
|
||||
* });
|
||||
* $through->on('error', $this->expectCallableOnce()));
|
||||
* $through->on('close', $this->expectCallableOnce()));
|
||||
* $through->on('data', $this->expectCallableNever()));
|
||||
*
|
||||
* $through->write(2);
|
||||
* ```
|
||||
*
|
||||
* @see WritableStreamInterface::write()
|
||||
* @see WritableStreamInterface::end()
|
||||
* @see DuplexStreamInterface::close()
|
||||
* @see WritableStreamInterface::pipe()
|
||||
*/
|
||||
final class ThroughStream extends EventEmitter implements DuplexStreamInterface
|
||||
{
|
||||
private $readable = true;
|
||||
private $writable = true;
|
||||
private $closed = false;
|
||||
private $paused = false;
|
||||
private $drain = false;
|
||||
private $callback;
|
||||
|
||||
public function __construct($callback = null)
|
||||
{
|
||||
if ($callback !== null && !\is_callable($callback)) {
|
||||
throw new InvalidArgumentException('Invalid transformation callback given');
|
||||
}
|
||||
|
||||
$this->callback = $callback;
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
$this->paused = true;
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
if ($this->drain) {
|
||||
$this->drain = false;
|
||||
$this->emit('drain');
|
||||
}
|
||||
$this->paused = false;
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
return Util::pipe($this, $dest, $options);
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return $this->readable;
|
||||
}
|
||||
|
||||
public function isWritable()
|
||||
{
|
||||
return $this->writable;
|
||||
}
|
||||
|
||||
public function write($data)
|
||||
{
|
||||
if (!$this->writable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->callback !== null) {
|
||||
try {
|
||||
$data = \call_user_func($this->callback, $data);
|
||||
} catch (\Exception $e) {
|
||||
$this->emit('error', array($e));
|
||||
$this->close();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$this->emit('data', array($data));
|
||||
|
||||
if ($this->paused) {
|
||||
$this->drain = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function end($data = null)
|
||||
{
|
||||
if (!$this->writable) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (null !== $data) {
|
||||
$this->write($data);
|
||||
|
||||
// return if write() already caused the stream to close
|
||||
if (!$this->writable) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->readable = false;
|
||||
$this->writable = false;
|
||||
$this->paused = true;
|
||||
$this->drain = false;
|
||||
|
||||
$this->emit('end');
|
||||
$this->close();
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->readable = false;
|
||||
$this->writable = false;
|
||||
$this->closed = true;
|
||||
$this->paused = true;
|
||||
$this->drain = false;
|
||||
$this->callback = null;
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
}
|
||||
75
vendor/react/stream/src/Util.php
vendored
Executable file
75
vendor/react/stream/src/Util.php
vendored
Executable file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace React\Stream;
|
||||
|
||||
final class Util
|
||||
{
|
||||
/**
|
||||
* Pipes all the data from the given $source into the $dest
|
||||
*
|
||||
* @param ReadableStreamInterface $source
|
||||
* @param WritableStreamInterface $dest
|
||||
* @param array $options
|
||||
* @return WritableStreamInterface $dest stream as-is
|
||||
* @see ReadableStreamInterface::pipe() for more details
|
||||
*/
|
||||
public static function pipe(ReadableStreamInterface $source, WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
// source not readable => NO-OP
|
||||
if (!$source->isReadable()) {
|
||||
return $dest;
|
||||
}
|
||||
|
||||
// destination not writable => just pause() source
|
||||
if (!$dest->isWritable()) {
|
||||
$source->pause();
|
||||
|
||||
return $dest;
|
||||
}
|
||||
|
||||
$dest->emit('pipe', array($source));
|
||||
|
||||
// forward all source data events as $dest->write()
|
||||
$source->on('data', $dataer = function ($data) use ($source, $dest) {
|
||||
$feedMore = $dest->write($data);
|
||||
|
||||
if (false === $feedMore) {
|
||||
$source->pause();
|
||||
}
|
||||
});
|
||||
$dest->on('close', function () use ($source, $dataer) {
|
||||
$source->removeListener('data', $dataer);
|
||||
$source->pause();
|
||||
});
|
||||
|
||||
// forward destination drain as $source->resume()
|
||||
$dest->on('drain', $drainer = function () use ($source) {
|
||||
$source->resume();
|
||||
});
|
||||
$source->on('close', function () use ($dest, $drainer) {
|
||||
$dest->removeListener('drain', $drainer);
|
||||
});
|
||||
|
||||
// forward end event from source as $dest->end()
|
||||
$end = isset($options['end']) ? $options['end'] : true;
|
||||
if ($end) {
|
||||
$source->on('end', $ender = function () use ($dest) {
|
||||
$dest->end();
|
||||
});
|
||||
$dest->on('close', function () use ($source, $ender) {
|
||||
$source->removeListener('end', $ender);
|
||||
});
|
||||
}
|
||||
|
||||
return $dest;
|
||||
}
|
||||
|
||||
public static function forwardEvents($source, $target, array $events)
|
||||
{
|
||||
foreach ($events as $event) {
|
||||
$source->on($event, function () use ($event, $target) {
|
||||
$target->emit($event, \func_get_args());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
171
vendor/react/stream/src/WritableResourceStream.php
vendored
Executable file
171
vendor/react/stream/src/WritableResourceStream.php
vendored
Executable file
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
namespace React\Stream;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\EventLoop\LoopInterface;
|
||||
|
||||
final class WritableResourceStream extends EventEmitter implements WritableStreamInterface
|
||||
{
|
||||
private $stream;
|
||||
private $loop;
|
||||
private $softLimit;
|
||||
private $writeChunkSize;
|
||||
|
||||
private $listening = false;
|
||||
private $writable = true;
|
||||
private $closed = false;
|
||||
private $data = '';
|
||||
|
||||
public function __construct($stream, LoopInterface $loop, $writeBufferSoftLimit = null, $writeChunkSize = null)
|
||||
{
|
||||
if (!\is_resource($stream) || \get_resource_type($stream) !== "stream") {
|
||||
throw new \InvalidArgumentException('First parameter must be a valid stream resource');
|
||||
}
|
||||
|
||||
// ensure resource is opened for writing (fopen mode must contain either of "waxc+")
|
||||
$meta = \stream_get_meta_data($stream);
|
||||
if (isset($meta['mode']) && $meta['mode'] !== '' && \strtr($meta['mode'], 'waxc+', '.....') === $meta['mode']) {
|
||||
throw new \InvalidArgumentException('Given stream resource is not opened in write mode');
|
||||
}
|
||||
|
||||
// this class relies on non-blocking I/O in order to not interrupt the event loop
|
||||
// e.g. pipes on Windows do not support this: https://bugs.php.net/bug.php?id=47918
|
||||
if (\stream_set_blocking($stream, 0) !== true) {
|
||||
throw new \RuntimeException('Unable to set stream resource to non-blocking mode');
|
||||
}
|
||||
|
||||
$this->stream = $stream;
|
||||
$this->loop = $loop;
|
||||
$this->softLimit = ($writeBufferSoftLimit === null) ? 65536 : (int)$writeBufferSoftLimit;
|
||||
$this->writeChunkSize = ($writeChunkSize === null) ? -1 : (int)$writeChunkSize;
|
||||
}
|
||||
|
||||
public function isWritable()
|
||||
{
|
||||
return $this->writable;
|
||||
}
|
||||
|
||||
public function write($data)
|
||||
{
|
||||
if (!$this->writable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->data .= $data;
|
||||
|
||||
if (!$this->listening && $this->data !== '') {
|
||||
$this->listening = true;
|
||||
|
||||
$this->loop->addWriteStream($this->stream, array($this, 'handleWrite'));
|
||||
}
|
||||
|
||||
return !isset($this->data[$this->softLimit - 1]);
|
||||
}
|
||||
|
||||
public function end($data = null)
|
||||
{
|
||||
if (null !== $data) {
|
||||
$this->write($data);
|
||||
}
|
||||
|
||||
$this->writable = false;
|
||||
|
||||
// close immediately if buffer is already empty
|
||||
// otherwise wait for buffer to flush first
|
||||
if ($this->data === '') {
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->listening) {
|
||||
$this->listening = false;
|
||||
$this->loop->removeWriteStream($this->stream);
|
||||
}
|
||||
|
||||
$this->closed = true;
|
||||
$this->writable = false;
|
||||
$this->data = '';
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
|
||||
if (\is_resource($this->stream)) {
|
||||
\fclose($this->stream);
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleWrite()
|
||||
{
|
||||
$error = null;
|
||||
\set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) {
|
||||
$error = array(
|
||||
'message' => $errstr,
|
||||
'number' => $errno,
|
||||
'file' => $errfile,
|
||||
'line' => $errline
|
||||
);
|
||||
});
|
||||
|
||||
if ($this->writeChunkSize === -1) {
|
||||
$sent = \fwrite($this->stream, $this->data);
|
||||
} else {
|
||||
$sent = \fwrite($this->stream, $this->data, $this->writeChunkSize);
|
||||
}
|
||||
|
||||
\restore_error_handler();
|
||||
|
||||
// Only report errors if *nothing* could be sent.
|
||||
// Any hard (permanent) error will fail to send any data at all.
|
||||
// Sending excessive amounts of data will only flush *some* data and then
|
||||
// report a temporary error (EAGAIN) which we do not raise here in order
|
||||
// to keep the stream open for further tries to write.
|
||||
// Should this turn out to be a permanent error later, it will eventually
|
||||
// send *nothing* and we can detect this.
|
||||
if ($sent === 0 || $sent === false) {
|
||||
if ($error !== null) {
|
||||
$error = new \ErrorException(
|
||||
$error['message'],
|
||||
0,
|
||||
$error['number'],
|
||||
$error['file'],
|
||||
$error['line']
|
||||
);
|
||||
}
|
||||
|
||||
$this->emit('error', array(new \RuntimeException('Unable to write to stream: ' . ($error !== null ? $error->getMessage() : 'Unknown error'), 0, $error)));
|
||||
$this->close();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$exceeded = isset($this->data[$this->softLimit - 1]);
|
||||
$this->data = (string) \substr($this->data, $sent);
|
||||
|
||||
// buffer has been above limit and is now below limit
|
||||
if ($exceeded && !isset($this->data[$this->softLimit - 1])) {
|
||||
$this->emit('drain');
|
||||
}
|
||||
|
||||
// buffer is now completely empty => stop trying to write
|
||||
if ($this->data === '') {
|
||||
// stop waiting for resource to be writable
|
||||
if ($this->listening) {
|
||||
$this->loop->removeWriteStream($this->stream);
|
||||
$this->listening = false;
|
||||
}
|
||||
|
||||
// buffer is end()ing and now completely empty => close buffer
|
||||
if (!$this->writable) {
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
347
vendor/react/stream/src/WritableStreamInterface.php
vendored
Executable file
347
vendor/react/stream/src/WritableStreamInterface.php
vendored
Executable file
@@ -0,0 +1,347 @@
|
||||
<?php
|
||||
|
||||
namespace React\Stream;
|
||||
|
||||
use Evenement\EventEmitterInterface;
|
||||
|
||||
/**
|
||||
* The `WritableStreamInterface` is responsible for providing an interface for
|
||||
* write-only streams and the writable side of duplex streams.
|
||||
*
|
||||
* Besides defining a few methods, this interface also implements the
|
||||
* `EventEmitterInterface` which allows you to react to certain events:
|
||||
*
|
||||
* drain event:
|
||||
* The `drain` event will be emitted whenever the write buffer became full
|
||||
* previously and is now ready to accept more data.
|
||||
*
|
||||
* ```php
|
||||
* $stream->on('drain', function () use ($stream) {
|
||||
* echo 'Stream is now ready to accept more data';
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* This event SHOULD be emitted once every time the buffer became full
|
||||
* previously and is now ready to accept more data.
|
||||
* In other words, this event MAY be emitted any number of times, which may
|
||||
* be zero times if the buffer never became full in the first place.
|
||||
* This event SHOULD NOT be emitted if the buffer has not become full
|
||||
* previously.
|
||||
*
|
||||
* This event is mostly used internally, see also `write()` for more details.
|
||||
*
|
||||
* pipe event:
|
||||
* The `pipe` event will be emitted whenever a readable stream is `pipe()`d
|
||||
* into this stream.
|
||||
* The event receives a single `ReadableStreamInterface` argument for the
|
||||
* source stream.
|
||||
*
|
||||
* ```php
|
||||
* $stream->on('pipe', function (ReadableStreamInterface $source) use ($stream) {
|
||||
* echo 'Now receiving piped data';
|
||||
*
|
||||
* // explicitly close target if source emits an error
|
||||
* $source->on('error', function () use ($stream) {
|
||||
* $stream->close();
|
||||
* });
|
||||
* });
|
||||
*
|
||||
* $source->pipe($stream);
|
||||
* ```
|
||||
*
|
||||
* This event MUST be emitted once for each readable stream that is
|
||||
* successfully piped into this destination stream.
|
||||
* In other words, this event MAY be emitted any number of times, which may
|
||||
* be zero times if no stream is ever piped into this stream.
|
||||
* This event MUST NOT be emitted if either the source is not readable
|
||||
* (closed already) or this destination is not writable (closed already).
|
||||
*
|
||||
* This event is mostly used internally, see also `pipe()` for more details.
|
||||
*
|
||||
* error event:
|
||||
* The `error` event will be emitted once a fatal error occurs, usually while
|
||||
* trying to write to this stream.
|
||||
* The event receives a single `Exception` argument for the error instance.
|
||||
*
|
||||
* ```php
|
||||
* $stream->on('error', function (Exception $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* This event SHOULD be emitted once the stream detects a fatal error, such
|
||||
* as a fatal transmission error.
|
||||
* It SHOULD NOT be emitted after a previous `error` or `close` event.
|
||||
* It MUST NOT be emitted if this is not a fatal error condition, such as
|
||||
* a temporary network issue that did not cause any data to be lost.
|
||||
*
|
||||
* After the stream errors, it MUST close the stream and SHOULD thus be
|
||||
* followed by a `close` event and then switch to non-writable mode, see
|
||||
* also `close()` and `isWritable()`.
|
||||
*
|
||||
* Many common streams (such as a TCP/IP connection or a file-based stream)
|
||||
* only deal with data transmission and may choose
|
||||
* to only emit this for a fatal transmission error once and will then
|
||||
* close (terminate) the stream in response.
|
||||
*
|
||||
* If this stream is a `DuplexStreamInterface`, you should also notice
|
||||
* how the readable side of the stream also implements an `error` event.
|
||||
* In other words, an error may occur while either reading or writing the
|
||||
* stream which should result in the same error processing.
|
||||
*
|
||||
* close event:
|
||||
* The `close` event will be emitted once the stream closes (terminates).
|
||||
*
|
||||
* ```php
|
||||
* $stream->on('close', function () {
|
||||
* echo 'CLOSED';
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* This event SHOULD be emitted once or never at all, depending on whether
|
||||
* the stream ever terminates.
|
||||
* It SHOULD NOT be emitted after a previous `close` event.
|
||||
*
|
||||
* After the stream is closed, it MUST switch to non-writable mode,
|
||||
* see also `isWritable()`.
|
||||
*
|
||||
* This event SHOULD be emitted whenever the stream closes, irrespective of
|
||||
* whether this happens implicitly due to an unrecoverable error or
|
||||
* explicitly when either side closes the stream.
|
||||
*
|
||||
* Many common streams (such as a TCP/IP connection or a file-based stream)
|
||||
* will likely choose to emit this event after flushing the buffer from
|
||||
* the `end()` method, after receiving a *successful* `end` event or after
|
||||
* a fatal transmission `error` event.
|
||||
*
|
||||
* If this stream is a `DuplexStreamInterface`, you should also notice
|
||||
* how the readable side of the stream also implements a `close` event.
|
||||
* In other words, after receiving this event, the stream MUST switch into
|
||||
* non-writable AND non-readable mode, see also `isReadable()`.
|
||||
* Note that this event should not be confused with the `end` event.
|
||||
*
|
||||
* The event callback functions MUST be a valid `callable` that obeys strict
|
||||
* parameter definitions and MUST accept event parameters exactly as documented.
|
||||
* The event callback functions MUST NOT throw an `Exception`.
|
||||
* The return value of the event callback functions will be ignored and has no
|
||||
* effect, so for performance reasons you're recommended to not return any
|
||||
* excessive data structures.
|
||||
*
|
||||
* Every implementation of this interface MUST follow these event semantics in
|
||||
* order to be considered a well-behaving stream.
|
||||
*
|
||||
* > Note that higher-level implementations of this interface may choose to
|
||||
* define additional events with dedicated semantics not defined as part of
|
||||
* this low-level stream specification. Conformance with these event semantics
|
||||
* is out of scope for this interface, so you may also have to refer to the
|
||||
* documentation of such a higher-level implementation.
|
||||
*
|
||||
* @see EventEmitterInterface
|
||||
* @see DuplexStreamInterface
|
||||
*/
|
||||
interface WritableStreamInterface extends EventEmitterInterface
|
||||
{
|
||||
/**
|
||||
* Checks whether this stream is in a writable state (not closed already).
|
||||
*
|
||||
* This method can be used to check if the stream still accepts writing
|
||||
* any data or if it is ended or closed already.
|
||||
* Writing any data to a non-writable stream is a NO-OP:
|
||||
*
|
||||
* ```php
|
||||
* assert($stream->isWritable() === false);
|
||||
*
|
||||
* $stream->write('end'); // NO-OP
|
||||
* $stream->end('end'); // NO-OP
|
||||
* ```
|
||||
*
|
||||
* A successfully opened stream always MUST start in writable mode.
|
||||
*
|
||||
* Once the stream ends or closes, it MUST switch to non-writable mode.
|
||||
* This can happen any time, explicitly through `end()` or `close()` or
|
||||
* implicitly due to a remote close or an unrecoverable transmission error.
|
||||
* Once a stream has switched to non-writable mode, it MUST NOT transition
|
||||
* back to writable mode.
|
||||
*
|
||||
* If this stream is a `DuplexStreamInterface`, you should also notice
|
||||
* how the readable side of the stream also implements an `isReadable()`
|
||||
* method. Unless this is a half-open duplex stream, they SHOULD usually
|
||||
* have the same return value.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isWritable();
|
||||
|
||||
/**
|
||||
* Write some data into the stream.
|
||||
*
|
||||
* A successful write MUST be confirmed with a boolean `true`, which means
|
||||
* that either the data was written (flushed) immediately or is buffered and
|
||||
* scheduled for a future write. Note that this interface gives you no
|
||||
* control over explicitly flushing the buffered data, as finding the
|
||||
* appropriate time for this is beyond the scope of this interface and left
|
||||
* up to the implementation of this interface.
|
||||
*
|
||||
* Many common streams (such as a TCP/IP connection or file-based stream)
|
||||
* may choose to buffer all given data and schedule a future flush by using
|
||||
* an underlying EventLoop to check when the resource is actually writable.
|
||||
*
|
||||
* If a stream cannot handle writing (or flushing) the data, it SHOULD emit
|
||||
* an `error` event and MAY `close()` the stream if it can not recover from
|
||||
* this error.
|
||||
*
|
||||
* If the internal buffer is full after adding `$data`, then `write()`
|
||||
* SHOULD return `false`, indicating that the caller should stop sending
|
||||
* data until the buffer drains.
|
||||
* The stream SHOULD send a `drain` event once the buffer is ready to accept
|
||||
* more data.
|
||||
*
|
||||
* Similarly, if the the stream is not writable (already in a closed state)
|
||||
* it MUST NOT process the given `$data` and SHOULD return `false`,
|
||||
* indicating that the caller should stop sending data.
|
||||
*
|
||||
* The given `$data` argument MAY be of mixed type, but it's usually
|
||||
* recommended it SHOULD be a `string` value or MAY use a type that allows
|
||||
* representation as a `string` for maximum compatibility.
|
||||
*
|
||||
* Many common streams (such as a TCP/IP connection or a file-based stream)
|
||||
* will only accept the raw (binary) payload data that is transferred over
|
||||
* the wire as chunks of `string` values.
|
||||
*
|
||||
* Due to the stream-based nature of this, the sender may send any number
|
||||
* of chunks with varying sizes. There are no guarantees that these chunks
|
||||
* will be received with the exact same framing the sender intended to send.
|
||||
* In other words, many lower-level protocols (such as TCP/IP) transfer the
|
||||
* data in chunks that may be anywhere between single-byte values to several
|
||||
* dozens of kilobytes. You may want to apply a higher-level protocol to
|
||||
* these low-level data chunks in order to achieve proper message framing.
|
||||
*
|
||||
* @param mixed|string $data
|
||||
* @return bool
|
||||
*/
|
||||
public function write($data);
|
||||
|
||||
/**
|
||||
* Successfully ends the stream (after optionally sending some final data).
|
||||
*
|
||||
* This method can be used to successfully end the stream, i.e. close
|
||||
* the stream after sending out all data that is currently buffered.
|
||||
*
|
||||
* ```php
|
||||
* $stream->write('hello');
|
||||
* $stream->write('world');
|
||||
* $stream->end();
|
||||
* ```
|
||||
*
|
||||
* If there's no data currently buffered and nothing to be flushed, then
|
||||
* this method MAY `close()` the stream immediately.
|
||||
*
|
||||
* If there's still data in the buffer that needs to be flushed first, then
|
||||
* this method SHOULD try to write out this data and only then `close()`
|
||||
* the stream.
|
||||
* Once the stream is closed, it SHOULD emit a `close` event.
|
||||
*
|
||||
* Note that this interface gives you no control over explicitly flushing
|
||||
* the buffered data, as finding the appropriate time for this is beyond the
|
||||
* scope of this interface and left up to the implementation of this
|
||||
* interface.
|
||||
*
|
||||
* Many common streams (such as a TCP/IP connection or file-based stream)
|
||||
* may choose to buffer all given data and schedule a future flush by using
|
||||
* an underlying EventLoop to check when the resource is actually writable.
|
||||
*
|
||||
* You can optionally pass some final data that is written to the stream
|
||||
* before ending the stream. If a non-`null` value is given as `$data`, then
|
||||
* this method will behave just like calling `write($data)` before ending
|
||||
* with no data.
|
||||
*
|
||||
* ```php
|
||||
* // shorter version
|
||||
* $stream->end('bye');
|
||||
*
|
||||
* // same as longer version
|
||||
* $stream->write('bye');
|
||||
* $stream->end();
|
||||
* ```
|
||||
*
|
||||
* After calling this method, the stream MUST switch into a non-writable
|
||||
* mode, see also `isWritable()`.
|
||||
* This means that no further writes are possible, so any additional
|
||||
* `write()` or `end()` calls have no effect.
|
||||
*
|
||||
* ```php
|
||||
* $stream->end();
|
||||
* assert($stream->isWritable() === false);
|
||||
*
|
||||
* $stream->write('nope'); // NO-OP
|
||||
* $stream->end(); // NO-OP
|
||||
* ```
|
||||
*
|
||||
* If this stream is a `DuplexStreamInterface`, calling this method SHOULD
|
||||
* also end its readable side, unless the stream supports half-open mode.
|
||||
* In other words, after calling this method, these streams SHOULD switch
|
||||
* into non-writable AND non-readable mode, see also `isReadable()`.
|
||||
* This implies that in this case, the stream SHOULD NOT emit any `data`
|
||||
* or `end` events anymore.
|
||||
* Streams MAY choose to use the `pause()` method logic for this, but
|
||||
* special care may have to be taken to ensure a following call to the
|
||||
* `resume()` method SHOULD NOT continue emitting readable events.
|
||||
*
|
||||
* Note that this method should not be confused with the `close()` method.
|
||||
*
|
||||
* @param mixed|string|null $data
|
||||
* @return void
|
||||
*/
|
||||
public function end($data = null);
|
||||
|
||||
/**
|
||||
* Closes the stream (forcefully).
|
||||
*
|
||||
* This method can be used to forcefully close the stream, i.e. close
|
||||
* the stream without waiting for any buffered data to be flushed.
|
||||
* If there's still data in the buffer, this data SHOULD be discarded.
|
||||
*
|
||||
* ```php
|
||||
* $stream->close();
|
||||
* ```
|
||||
*
|
||||
* Once the stream is closed, it SHOULD emit a `close` event.
|
||||
* Note that this event SHOULD NOT be emitted more than once, in particular
|
||||
* if this method is called multiple times.
|
||||
*
|
||||
* After calling this method, the stream MUST switch into a non-writable
|
||||
* mode, see also `isWritable()`.
|
||||
* This means that no further writes are possible, so any additional
|
||||
* `write()` or `end()` calls have no effect.
|
||||
*
|
||||
* ```php
|
||||
* $stream->close();
|
||||
* assert($stream->isWritable() === false);
|
||||
*
|
||||
* $stream->write('nope'); // NO-OP
|
||||
* $stream->end(); // NO-OP
|
||||
* ```
|
||||
*
|
||||
* Note that this method should not be confused with the `end()` method.
|
||||
* Unlike the `end()` method, this method does not take care of any existing
|
||||
* buffers and simply discards any buffer contents.
|
||||
* Likewise, this method may also be called after calling `end()` on a
|
||||
* stream in order to stop waiting for the stream to flush its final data.
|
||||
*
|
||||
* ```php
|
||||
* $stream->end();
|
||||
* $loop->addTimer(1.0, function () use ($stream) {
|
||||
* $stream->close();
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* If this stream is a `DuplexStreamInterface`, you should also notice
|
||||
* how the readable side of the stream also implements a `close()` method.
|
||||
* In other words, after calling this method, the stream MUST switch into
|
||||
* non-writable AND non-readable mode, see also `isReadable()`.
|
||||
*
|
||||
* @return void
|
||||
* @see ReadableStreamInterface::close()
|
||||
*/
|
||||
public function close();
|
||||
}
|
||||
10
vendor/react/stream/tests/CallableStub.php
vendored
Executable file
10
vendor/react/stream/tests/CallableStub.php
vendored
Executable file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\Stream;
|
||||
|
||||
class CallableStub
|
||||
{
|
||||
public function __invoke()
|
||||
{
|
||||
}
|
||||
}
|
||||
267
vendor/react/stream/tests/CompositeStreamTest.php
vendored
Executable file
267
vendor/react/stream/tests/CompositeStreamTest.php
vendored
Executable file
@@ -0,0 +1,267 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\Stream;
|
||||
|
||||
use React\Stream\CompositeStream;
|
||||
use React\Stream\ThroughStream;
|
||||
|
||||
/**
|
||||
* @covers React\Stream\CompositeStream
|
||||
*/
|
||||
class CompositeStreamTest extends TestCase
|
||||
{
|
||||
/** @test */
|
||||
public function itShouldCloseReadableIfNotWritable()
|
||||
{
|
||||
$readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
|
||||
$readable
|
||||
->expects($this->once())
|
||||
->method('isReadable')
|
||||
->willReturn(true);
|
||||
$readable
|
||||
->expects($this->once())
|
||||
->method('close');
|
||||
|
||||
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
|
||||
$writable
|
||||
->expects($this->once())
|
||||
->method('isWritable')
|
||||
->willReturn(false);
|
||||
|
||||
$composite = new CompositeStream($readable, $writable);
|
||||
|
||||
$composite->on('close', $this->expectCallableNever());
|
||||
$composite->close();
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function itShouldCloseWritableIfNotReadable()
|
||||
{
|
||||
$readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
|
||||
$readable
|
||||
->expects($this->once())
|
||||
->method('isReadable')
|
||||
->willReturn(false);
|
||||
|
||||
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
|
||||
$writable
|
||||
->expects($this->once())
|
||||
->method('close');
|
||||
|
||||
$composite = new CompositeStream($readable, $writable);
|
||||
|
||||
$composite->on('close', $this->expectCallableNever());
|
||||
$composite->close();
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function itShouldForwardWritableCallsToWritableStream()
|
||||
{
|
||||
$readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
|
||||
$readable
|
||||
->expects($this->once())
|
||||
->method('isReadable')
|
||||
->willReturn(true);
|
||||
|
||||
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
|
||||
$writable
|
||||
->expects($this->once())
|
||||
->method('write')
|
||||
->with('foo');
|
||||
$writable
|
||||
->expects($this->exactly(2))
|
||||
->method('isWritable')
|
||||
->willReturn(true);
|
||||
|
||||
$composite = new CompositeStream($readable, $writable);
|
||||
$composite->write('foo');
|
||||
$composite->isWritable();
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function itShouldForwardReadableCallsToReadableStream()
|
||||
{
|
||||
$readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
|
||||
$readable
|
||||
->expects($this->exactly(2))
|
||||
->method('isReadable')
|
||||
->willReturn(true);
|
||||
$readable
|
||||
->expects($this->once())
|
||||
->method('pause');
|
||||
$readable
|
||||
->expects($this->once())
|
||||
->method('resume');
|
||||
|
||||
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
|
||||
$writable
|
||||
->expects($this->any())
|
||||
->method('isWritable')
|
||||
->willReturn(true);
|
||||
|
||||
$composite = new CompositeStream($readable, $writable);
|
||||
$composite->isReadable();
|
||||
$composite->pause();
|
||||
$composite->resume();
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function itShouldNotForwardResumeIfStreamIsNotWritable()
|
||||
{
|
||||
$readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
|
||||
$readable
|
||||
->expects($this->once())
|
||||
->method('isReadable')
|
||||
->willReturn(true);
|
||||
$readable
|
||||
->expects($this->never())
|
||||
->method('resume');
|
||||
|
||||
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
|
||||
$writable
|
||||
->expects($this->exactly(2))
|
||||
->method('isWritable')
|
||||
->willReturnOnConsecutiveCalls(true, false);
|
||||
|
||||
$composite = new CompositeStream($readable, $writable);
|
||||
$composite->resume();
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function endShouldDelegateToWritableWithData()
|
||||
{
|
||||
$readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
|
||||
$readable
|
||||
->expects($this->once())
|
||||
->method('isReadable')
|
||||
->willReturn(true);
|
||||
|
||||
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
|
||||
$writable
|
||||
->expects($this->once())
|
||||
->method('isWritable')
|
||||
->willReturn(true);
|
||||
$writable
|
||||
->expects($this->once())
|
||||
->method('end')
|
||||
->with('foo');
|
||||
|
||||
$composite = new CompositeStream($readable, $writable);
|
||||
$composite->end('foo');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function closeShouldCloseBothStreams()
|
||||
{
|
||||
$readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
|
||||
$readable
|
||||
->expects($this->once())
|
||||
->method('isReadable')
|
||||
->willReturn(true);
|
||||
$readable
|
||||
->expects($this->once())
|
||||
->method('close');
|
||||
|
||||
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
|
||||
$writable
|
||||
->expects($this->once())
|
||||
->method('isWritable')
|
||||
->willReturn(true);
|
||||
$writable
|
||||
->expects($this->once())
|
||||
->method('close');
|
||||
|
||||
$composite = new CompositeStream($readable, $writable);
|
||||
$composite->close();
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function itShouldForwardCloseOnlyOnce()
|
||||
{
|
||||
$readable = new ThroughStream();
|
||||
$writable = new ThroughStream();
|
||||
|
||||
$composite = new CompositeStream($readable, $writable);
|
||||
$composite->on('close', $this->expectCallableOnce());
|
||||
|
||||
$readable->close();
|
||||
$writable->close();
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function itShouldForwardCloseAndRemoveAllListeners()
|
||||
{
|
||||
$in = new ThroughStream();
|
||||
|
||||
$composite = new CompositeStream($in, $in);
|
||||
$composite->on('close', $this->expectCallableOnce());
|
||||
|
||||
$this->assertTrue($composite->isReadable());
|
||||
$this->assertTrue($composite->isWritable());
|
||||
$this->assertCount(1, $composite->listeners('close'));
|
||||
|
||||
$composite->close();
|
||||
|
||||
$this->assertFalse($composite->isReadable());
|
||||
$this->assertFalse($composite->isWritable());
|
||||
$this->assertCount(0, $composite->listeners('close'));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function itShouldReceiveForwardedEvents()
|
||||
{
|
||||
$readable = new ThroughStream();
|
||||
$writable = new ThroughStream();
|
||||
|
||||
$composite = new CompositeStream($readable, $writable);
|
||||
$composite->on('data', $this->expectCallableOnce());
|
||||
$composite->on('drain', $this->expectCallableOnce());
|
||||
|
||||
$readable->emit('data', array('foo'));
|
||||
$writable->emit('drain');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function itShouldHandlePipingCorrectly()
|
||||
{
|
||||
$readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
|
||||
$readable
|
||||
->expects($this->once())
|
||||
->method('isReadable')
|
||||
->willReturn(true);
|
||||
|
||||
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
|
||||
$writable->expects($this->any())->method('isWritable')->willReturn(True);
|
||||
$writable
|
||||
->expects($this->once())
|
||||
->method('write')
|
||||
->with('foo');
|
||||
|
||||
$composite = new CompositeStream($readable, $writable);
|
||||
|
||||
$input = new ThroughStream();
|
||||
$input->pipe($composite);
|
||||
$input->emit('data', array('foo'));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function itShouldForwardPipeCallsToReadableStream()
|
||||
{
|
||||
$readable = new ThroughStream();
|
||||
|
||||
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
|
||||
$writable->expects($this->any())->method('isWritable')->willReturn(True);
|
||||
|
||||
$composite = new CompositeStream($readable, $writable);
|
||||
|
||||
$output = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
|
||||
$output->expects($this->any())->method('isWritable')->willReturn(True);
|
||||
$output
|
||||
->expects($this->once())
|
||||
->method('write')
|
||||
->with('foo');
|
||||
|
||||
$composite->pipe($output);
|
||||
$readable->emit('data', array('foo'));
|
||||
}
|
||||
}
|
||||
390
vendor/react/stream/tests/DuplexResourceStreamIntegrationTest.php
vendored
Executable file
390
vendor/react/stream/tests/DuplexResourceStreamIntegrationTest.php
vendored
Executable file
@@ -0,0 +1,390 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\Stream;
|
||||
|
||||
use Clue\StreamFilter as Filter;
|
||||
use React\Stream\DuplexResourceStream;
|
||||
use React\Stream\ReadableResourceStream;
|
||||
use React\EventLoop\ExtEventLoop;
|
||||
use React\EventLoop\ExtLibeventLoop;
|
||||
use React\EventLoop\ExtLibevLoop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\EventLoop\LibEventLoop;
|
||||
use React\EventLoop\LibEvLoop;
|
||||
use React\EventLoop\StreamSelectLoop;
|
||||
|
||||
class DuplexResourceStreamIntegrationTest extends TestCase
|
||||
{
|
||||
public function loopProvider()
|
||||
{
|
||||
return array(
|
||||
array(
|
||||
function() {
|
||||
return true;
|
||||
},
|
||||
function () {
|
||||
return new StreamSelectLoop();
|
||||
}
|
||||
),
|
||||
array(
|
||||
function () {
|
||||
return function_exists('event_base_new');
|
||||
},
|
||||
function () {
|
||||
return class_exists('React\EventLoop\ExtLibeventLoop') ? new ExtLibeventLoop() : new LibEventLoop();
|
||||
}
|
||||
),
|
||||
array(
|
||||
function () {
|
||||
return class_exists('libev\EventLoop');
|
||||
},
|
||||
function () {
|
||||
return class_exists('React\EventLoop\ExtLibevLoop') ? new ExtLibevLoop() : new LibEvLoop();
|
||||
}
|
||||
),
|
||||
array(
|
||||
function () {
|
||||
return class_exists('EventBase') && class_exists('React\EventLoop\ExtEventLoop');
|
||||
},
|
||||
function () {
|
||||
return new ExtEventLoop();
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider loopProvider
|
||||
*/
|
||||
public function testBufferReadsLargeChunks($condition, $loopFactory)
|
||||
{
|
||||
if (true !== $condition()) {
|
||||
return $this->markTestSkipped('Loop implementation not available');
|
||||
}
|
||||
|
||||
$loop = $loopFactory();
|
||||
|
||||
list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
|
||||
|
||||
$bufferSize = 4096;
|
||||
$streamA = new DuplexResourceStream($sockA, $loop, $bufferSize);
|
||||
$streamB = new DuplexResourceStream($sockB, $loop, $bufferSize);
|
||||
|
||||
$testString = str_repeat("*", $bufferSize + 1);
|
||||
|
||||
$buffer = "";
|
||||
$streamB->on('data', function ($data) use (&$buffer) {
|
||||
$buffer .= $data;
|
||||
});
|
||||
|
||||
$streamA->write($testString);
|
||||
|
||||
$this->loopTick($loop);
|
||||
$this->loopTick($loop);
|
||||
$this->loopTick($loop);
|
||||
|
||||
$streamA->close();
|
||||
$streamB->close();
|
||||
|
||||
$this->assertEquals($testString, $buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider loopProvider
|
||||
*/
|
||||
public function testWriteLargeChunk($condition, $loopFactory)
|
||||
{
|
||||
if (true !== $condition()) {
|
||||
return $this->markTestSkipped('Loop implementation not available');
|
||||
}
|
||||
|
||||
$loop = $loopFactory();
|
||||
|
||||
list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
|
||||
|
||||
$streamA = new DuplexResourceStream($sockA, $loop);
|
||||
$streamB = new DuplexResourceStream($sockB, $loop);
|
||||
|
||||
// limit seems to be 192 KiB
|
||||
$size = 256 * 1024;
|
||||
|
||||
// sending side sends and expects clean close with no errors
|
||||
$streamA->end(str_repeat('*', $size));
|
||||
$streamA->on('close', $this->expectCallableOnce());
|
||||
$streamA->on('error', $this->expectCallableNever());
|
||||
|
||||
// receiving side counts bytes and expects clean close with no errors
|
||||
$received = 0;
|
||||
$streamB->on('data', function ($chunk) use (&$received) {
|
||||
$received += strlen($chunk);
|
||||
});
|
||||
$streamB->on('close', $this->expectCallableOnce());
|
||||
$streamB->on('error', $this->expectCallableNever());
|
||||
|
||||
$loop->run();
|
||||
|
||||
$streamA->close();
|
||||
$streamB->close();
|
||||
|
||||
$this->assertEquals($size, $received);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider loopProvider
|
||||
*/
|
||||
public function testDoesNotEmitDataIfNothingHasBeenWritten($condition, $loopFactory)
|
||||
{
|
||||
if (true !== $condition()) {
|
||||
return $this->markTestSkipped('Loop implementation not available');
|
||||
}
|
||||
|
||||
$loop = $loopFactory();
|
||||
|
||||
list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
|
||||
|
||||
$streamA = new DuplexResourceStream($sockA, $loop);
|
||||
$streamB = new DuplexResourceStream($sockB, $loop);
|
||||
|
||||
// end streamA without writing any data
|
||||
$streamA->end();
|
||||
|
||||
// streamB should not emit any data
|
||||
$streamB->on('data', $this->expectCallableNever());
|
||||
|
||||
$loop->run();
|
||||
|
||||
$streamA->close();
|
||||
$streamB->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider loopProvider
|
||||
*/
|
||||
public function testDoesNotWriteDataIfRemoteSideFromPairHasBeenClosed($condition, $loopFactory)
|
||||
{
|
||||
if (true !== $condition()) {
|
||||
return $this->markTestSkipped('Loop implementation not available');
|
||||
}
|
||||
|
||||
$loop = $loopFactory();
|
||||
|
||||
list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
|
||||
|
||||
$streamA = new DuplexResourceStream($sockA, $loop);
|
||||
$streamB = new DuplexResourceStream($sockB, $loop);
|
||||
|
||||
// end streamA without writing any data
|
||||
$streamA->pause();
|
||||
$streamA->write('hello');
|
||||
$streamA->on('close', $this->expectCallableOnce());
|
||||
|
||||
$streamB->on('data', $this->expectCallableNever());
|
||||
$streamB->close();
|
||||
|
||||
$loop->run();
|
||||
|
||||
$streamA->close();
|
||||
$streamB->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider loopProvider
|
||||
*/
|
||||
public function testDoesNotWriteDataIfServerSideHasBeenClosed($condition, $loopFactory)
|
||||
{
|
||||
if (true !== $condition()) {
|
||||
return $this->markTestSkipped('Loop implementation not available');
|
||||
}
|
||||
|
||||
$loop = $loopFactory();
|
||||
|
||||
$server = stream_socket_server('tcp://127.0.0.1:0');
|
||||
|
||||
$client = stream_socket_client(stream_socket_get_name($server, false));
|
||||
$peer = stream_socket_accept($server);
|
||||
|
||||
$streamA = new DuplexResourceStream($client, $loop);
|
||||
$streamB = new DuplexResourceStream($peer, $loop);
|
||||
|
||||
// end streamA without writing any data
|
||||
$streamA->pause();
|
||||
$streamA->write('hello');
|
||||
$streamA->on('close', $this->expectCallableOnce());
|
||||
|
||||
$streamB->on('data', $this->expectCallableNever());
|
||||
$streamB->close();
|
||||
|
||||
$loop->run();
|
||||
|
||||
$streamA->close();
|
||||
$streamB->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider loopProvider
|
||||
*/
|
||||
public function testDoesNotWriteDataIfClientSideHasBeenClosed($condition, $loopFactory)
|
||||
{
|
||||
if (true !== $condition()) {
|
||||
return $this->markTestSkipped('Loop implementation not available');
|
||||
}
|
||||
|
||||
$loop = $loopFactory();
|
||||
|
||||
$server = stream_socket_server('tcp://127.0.0.1:0');
|
||||
|
||||
$client = stream_socket_client(stream_socket_get_name($server, false));
|
||||
$peer = stream_socket_accept($server);
|
||||
|
||||
$streamA = new DuplexResourceStream($peer, $loop);
|
||||
$streamB = new DuplexResourceStream($client, $loop);
|
||||
|
||||
// end streamA without writing any data
|
||||
$streamA->pause();
|
||||
$streamA->write('hello');
|
||||
$streamA->on('close', $this->expectCallableOnce());
|
||||
|
||||
$streamB->on('data', $this->expectCallableNever());
|
||||
$streamB->close();
|
||||
|
||||
$loop->run();
|
||||
|
||||
$streamA->close();
|
||||
$streamB->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider loopProvider
|
||||
*/
|
||||
public function testReadsSingleChunkFromProcessPipe($condition, $loopFactory)
|
||||
{
|
||||
if (true !== $condition()) {
|
||||
return $this->markTestSkipped('Loop implementation not available');
|
||||
}
|
||||
|
||||
$loop = $loopFactory();
|
||||
|
||||
$stream = new ReadableResourceStream(popen('echo test', 'r'), $loop);
|
||||
$stream->on('data', $this->expectCallableOnceWith("test\n"));
|
||||
$stream->on('end', $this->expectCallableOnce());
|
||||
$stream->on('error', $this->expectCallableNever());
|
||||
|
||||
$loop->run();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider loopProvider
|
||||
*/
|
||||
public function testReadsMultipleChunksFromProcessPipe($condition, $loopFactory)
|
||||
{
|
||||
if (true !== $condition()) {
|
||||
return $this->markTestSkipped('Loop implementation not available');
|
||||
}
|
||||
|
||||
$loop = $loopFactory();
|
||||
|
||||
$stream = new ReadableResourceStream(popen('echo a;sleep 0.1;echo b;sleep 0.1;echo c', 'r'), $loop);
|
||||
|
||||
$buffer = '';
|
||||
$stream->on('data', function ($chunk) use (&$buffer) {
|
||||
$buffer .= $chunk;
|
||||
});
|
||||
|
||||
$stream->on('end', $this->expectCallableOnce());
|
||||
$stream->on('error', $this->expectCallableNever());
|
||||
|
||||
$loop->run();
|
||||
|
||||
$this->assertEquals("a\n" . "b\n" . "c\n", $buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider loopProvider
|
||||
*/
|
||||
public function testReadsLongChunksFromProcessPipe($condition, $loopFactory)
|
||||
{
|
||||
if (true !== $condition()) {
|
||||
return $this->markTestSkipped('Loop implementation not available');
|
||||
}
|
||||
|
||||
$loop = $loopFactory();
|
||||
|
||||
$stream = new ReadableResourceStream(popen('dd if=/dev/zero bs=12345 count=1234 2>&-', 'r'), $loop);
|
||||
|
||||
$bytes = 0;
|
||||
$stream->on('data', function ($chunk) use (&$bytes) {
|
||||
$bytes += strlen($chunk);
|
||||
});
|
||||
|
||||
$stream->on('end', $this->expectCallableOnce());
|
||||
$stream->on('error', $this->expectCallableNever());
|
||||
|
||||
$loop->run();
|
||||
|
||||
$this->assertEquals(12345 * 1234, $bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider loopProvider
|
||||
*/
|
||||
public function testReadsNothingFromProcessPipeWithNoOutput($condition, $loopFactory)
|
||||
{
|
||||
if (true !== $condition()) {
|
||||
return $this->markTestSkipped('Loop implementation not available');
|
||||
}
|
||||
|
||||
$loop = $loopFactory();
|
||||
|
||||
$stream = new ReadableResourceStream(popen('true', 'r'), $loop);
|
||||
$stream->on('data', $this->expectCallableNever());
|
||||
$stream->on('end', $this->expectCallableOnce());
|
||||
$stream->on('error', $this->expectCallableNever());
|
||||
|
||||
$loop->run();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\ReadableResourceStream::handleData
|
||||
* @dataProvider loopProvider
|
||||
*/
|
||||
public function testEmptyReadShouldntFcloseStream($condition, $loopFactory)
|
||||
{
|
||||
if (true !== $condition()) {
|
||||
return $this->markTestSkipped('Loop implementation not available');
|
||||
}
|
||||
|
||||
$server = stream_socket_server('tcp://127.0.0.1:0');
|
||||
|
||||
$client = stream_socket_client(stream_socket_get_name($server, false));
|
||||
$stream = stream_socket_accept($server);
|
||||
|
||||
|
||||
// add a filter which returns an error when encountering an 'a' when reading
|
||||
Filter\append($stream, function ($chunk) {
|
||||
return '';
|
||||
}, STREAM_FILTER_READ);
|
||||
|
||||
$loop = $loopFactory();
|
||||
|
||||
$conn = new DuplexResourceStream($stream, $loop);
|
||||
$conn->on('error', $this->expectCallableNever());
|
||||
$conn->on('data', $this->expectCallableNever());
|
||||
$conn->on('end', $this->expectCallableNever());
|
||||
|
||||
fwrite($client, "foobar\n");
|
||||
|
||||
$conn->handleData($stream);
|
||||
|
||||
fclose($stream);
|
||||
fclose($client);
|
||||
fclose($server);
|
||||
}
|
||||
|
||||
private function loopTick(LoopInterface $loop)
|
||||
{
|
||||
$loop->addTimer(0, function () use ($loop) {
|
||||
$loop->stop();
|
||||
});
|
||||
$loop->run();
|
||||
}
|
||||
}
|
||||
495
vendor/react/stream/tests/DuplexResourceStreamTest.php
vendored
Executable file
495
vendor/react/stream/tests/DuplexResourceStreamTest.php
vendored
Executable file
@@ -0,0 +1,495 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\Stream;
|
||||
|
||||
use React\Stream\DuplexResourceStream;
|
||||
use Clue\StreamFilter as Filter;
|
||||
use React\Stream\WritableResourceStream;
|
||||
|
||||
class DuplexResourceStreamTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @covers React\Stream\DuplexResourceStream::__construct
|
||||
* @doesNotPerformAssertions
|
||||
*/
|
||||
public function testConstructor()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
new DuplexResourceStream($stream, $loop);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\DuplexResourceStream::__construct
|
||||
* @doesNotPerformAssertions
|
||||
*/
|
||||
public function testConstructorWithExcessiveMode()
|
||||
{
|
||||
// excessive flags are ignored for temp streams, so we have to use a file stream
|
||||
$name = tempnam(sys_get_temp_dir(), 'test');
|
||||
$stream = @fopen($name, 'r+eANYTHING');
|
||||
unlink($name);
|
||||
|
||||
$loop = $this->createLoopMock();
|
||||
$buffer = new DuplexResourceStream($stream, $loop);
|
||||
$buffer->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\DuplexResourceStream::__construct
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testConstructorThrowsExceptionOnInvalidStream()
|
||||
{
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
new DuplexResourceStream('breakme', $loop);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\DuplexResourceStream::__construct
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testConstructorThrowsExceptionOnWriteOnlyStream()
|
||||
{
|
||||
if (defined('HHVM_VERSION')) {
|
||||
$this->markTestSkipped('HHVM does not report fopen mode for STDOUT');
|
||||
}
|
||||
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
new DuplexResourceStream(STDOUT, $loop);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\DuplexResourceStream::__construct
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testConstructorThrowsExceptionOnWriteOnlyStreamWithExcessiveMode()
|
||||
{
|
||||
// excessive flags are ignored for temp streams, so we have to use a file stream
|
||||
$name = tempnam(sys_get_temp_dir(), 'test');
|
||||
$stream = fopen($name, 'weANYTHING');
|
||||
unlink($name);
|
||||
|
||||
$loop = $this->createLoopMock();
|
||||
new DuplexResourceStream($stream, $loop);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\DuplexResourceStream::__construct
|
||||
* @expectedException RunTimeException
|
||||
*/
|
||||
public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking()
|
||||
{
|
||||
if (!in_array('blocking', stream_get_wrappers())) {
|
||||
stream_wrapper_register('blocking', 'React\Tests\Stream\EnforceBlockingWrapper');
|
||||
}
|
||||
|
||||
$stream = fopen('blocking://test', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
new DuplexResourceStream($stream, $loop);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\DuplexResourceStream::__construct
|
||||
* @doesNotPerformAssertions
|
||||
*/
|
||||
public function testConstructorAcceptsBuffer()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$buffer = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
|
||||
|
||||
$conn = new DuplexResourceStream($stream, $loop, null, $buffer);
|
||||
}
|
||||
|
||||
public function testCloseShouldEmitCloseEvent()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$conn = new DuplexResourceStream($stream, $loop);
|
||||
$conn->on('close', $this->expectCallableOnce());
|
||||
$conn->on('end', $this->expectCallableNever());
|
||||
|
||||
$conn->close();
|
||||
|
||||
$this->assertFalse($conn->isReadable());
|
||||
}
|
||||
|
||||
public function testEndShouldEndBuffer()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$buffer = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
|
||||
$buffer->expects($this->once())->method('end')->with('foo');
|
||||
|
||||
$conn = new DuplexResourceStream($stream, $loop, null, $buffer);
|
||||
$conn->end('foo');
|
||||
}
|
||||
|
||||
|
||||
public function testEndAfterCloseIsNoOp()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$buffer = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
|
||||
$buffer->expects($this->never())->method('end');
|
||||
|
||||
$conn = new DuplexResourceStream($stream, $loop);
|
||||
$conn->close();
|
||||
$conn->end();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\DuplexResourceStream::__construct
|
||||
* @covers React\Stream\DuplexResourceStream::handleData
|
||||
*/
|
||||
public function testDataEvent()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$capturedData = null;
|
||||
|
||||
$conn = new DuplexResourceStream($stream, $loop);
|
||||
$conn->on('data', function ($data) use (&$capturedData) {
|
||||
$capturedData = $data;
|
||||
});
|
||||
|
||||
fwrite($stream, "foobar\n");
|
||||
rewind($stream);
|
||||
|
||||
$conn->handleData($stream);
|
||||
$this->assertSame("foobar\n", $capturedData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\DuplexResourceStream::__construct
|
||||
* @covers React\Stream\DuplexResourceStream::handleData
|
||||
*/
|
||||
public function testDataEventDoesEmitOneChunkMatchingBufferSize()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$capturedData = null;
|
||||
|
||||
$conn = new DuplexResourceStream($stream, $loop, 4321);
|
||||
$conn->on('data', function ($data) use (&$capturedData) {
|
||||
$capturedData = $data;
|
||||
});
|
||||
|
||||
fwrite($stream, str_repeat("a", 100000));
|
||||
rewind($stream);
|
||||
|
||||
$conn->handleData($stream);
|
||||
|
||||
$this->assertTrue($conn->isReadable());
|
||||
$this->assertEquals(4321, strlen($capturedData));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\DuplexResourceStream::__construct
|
||||
* @covers React\Stream\DuplexResourceStream::handleData
|
||||
*/
|
||||
public function testDataEventDoesEmitOneChunkUntilStreamEndsWhenBufferSizeIsInfinite()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$capturedData = null;
|
||||
|
||||
$conn = new DuplexResourceStream($stream, $loop, -1);
|
||||
|
||||
$conn->on('data', function ($data) use (&$capturedData) {
|
||||
$capturedData = $data;
|
||||
});
|
||||
|
||||
fwrite($stream, str_repeat("a", 100000));
|
||||
rewind($stream);
|
||||
|
||||
$conn->handleData($stream);
|
||||
|
||||
$this->assertTrue($conn->isReadable());
|
||||
$this->assertEquals(100000, strlen($capturedData));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\DuplexResourceStream::handleData
|
||||
*/
|
||||
public function testEmptyStreamShouldNotEmitData()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$conn = new DuplexResourceStream($stream, $loop);
|
||||
$conn->on('data', $this->expectCallableNever());
|
||||
|
||||
$conn->handleData($stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\DuplexResourceStream::write
|
||||
*/
|
||||
public function testWrite()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createWriteableLoopMock();
|
||||
|
||||
$conn = new DuplexResourceStream($stream, $loop);
|
||||
$conn->write("foo\n");
|
||||
|
||||
rewind($stream);
|
||||
$this->assertSame("foo\n", fgets($stream));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\DuplexResourceStream::end
|
||||
* @covers React\Stream\DuplexResourceStream::isReadable
|
||||
* @covers React\Stream\DuplexResourceStream::isWritable
|
||||
*/
|
||||
public function testEnd()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$conn = new DuplexResourceStream($stream, $loop);
|
||||
$conn->end();
|
||||
|
||||
$this->assertFalse(is_resource($stream));
|
||||
$this->assertFalse($conn->isReadable());
|
||||
$this->assertFalse($conn->isWritable());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\DuplexResourceStream::end
|
||||
*/
|
||||
public function testEndRemovesReadStreamFromLoop()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
$loop->expects($this->once())->method('addReadStream')->with($stream);
|
||||
$loop->expects($this->once())->method('removeReadStream')->with($stream);
|
||||
|
||||
$conn = new DuplexResourceStream($stream, $loop);
|
||||
$conn->end('bye');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\DuplexResourceStream::pause
|
||||
*/
|
||||
public function testPauseRemovesReadStreamFromLoop()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
$loop->expects($this->once())->method('addReadStream')->with($stream);
|
||||
$loop->expects($this->once())->method('removeReadStream')->with($stream);
|
||||
|
||||
$conn = new DuplexResourceStream($stream, $loop);
|
||||
$conn->pause();
|
||||
$conn->pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\DuplexResourceStream::pause
|
||||
*/
|
||||
public function testResumeDoesAddStreamToLoopOnlyOnce()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
$loop->expects($this->once())->method('addReadStream')->with($stream);
|
||||
|
||||
$conn = new DuplexResourceStream($stream, $loop);
|
||||
$conn->resume();
|
||||
$conn->resume();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\DuplexResourceStream::close
|
||||
*/
|
||||
public function testCloseRemovesReadStreamFromLoop()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
$loop->expects($this->once())->method('addReadStream')->with($stream);
|
||||
$loop->expects($this->once())->method('removeReadStream')->with($stream);
|
||||
|
||||
$conn = new DuplexResourceStream($stream, $loop);
|
||||
$conn->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\DuplexResourceStream::close
|
||||
*/
|
||||
public function testCloseAfterPauseRemovesReadStreamFromLoopOnlyOnce()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
$loop->expects($this->once())->method('addReadStream')->with($stream);
|
||||
$loop->expects($this->once())->method('removeReadStream')->with($stream);
|
||||
|
||||
$conn = new DuplexResourceStream($stream, $loop);
|
||||
$conn->pause();
|
||||
$conn->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\DuplexResourceStream::close
|
||||
*/
|
||||
public function testResumeAfterCloseDoesAddReadStreamToLoopOnlyOnce()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
$loop->expects($this->once())->method('addReadStream')->with($stream);
|
||||
|
||||
$conn = new DuplexResourceStream($stream, $loop);
|
||||
$conn->close();
|
||||
$conn->resume();
|
||||
}
|
||||
|
||||
public function testEndedStreamsShouldNotWrite()
|
||||
{
|
||||
$file = tempnam(sys_get_temp_dir(), 'reactphptest_');
|
||||
$stream = fopen($file, 'r+');
|
||||
$loop = $this->createWriteableLoopMock();
|
||||
|
||||
$conn = new DuplexResourceStream($stream, $loop);
|
||||
$conn->write("foo\n");
|
||||
$conn->end();
|
||||
|
||||
$res = $conn->write("bar\n");
|
||||
$stream = fopen($file, 'r');
|
||||
|
||||
$this->assertSame("foo\n", fgets($stream));
|
||||
$this->assertFalse($res);
|
||||
|
||||
unlink($file);
|
||||
}
|
||||
|
||||
public function testPipeShouldReturnDestination()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$conn = new DuplexResourceStream($stream, $loop);
|
||||
$dest = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
|
||||
|
||||
$this->assertSame($dest, $conn->pipe($dest));
|
||||
}
|
||||
|
||||
public function testBufferEventsShouldBubbleUp()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$buffer = new WritableResourceStream($stream, $loop);
|
||||
$conn = new DuplexResourceStream($stream, $loop, null, $buffer);
|
||||
|
||||
$conn->on('drain', $this->expectCallableOnce());
|
||||
$conn->on('error', $this->expectCallableOnce());
|
||||
|
||||
$buffer->emit('drain');
|
||||
$buffer->emit('error', array(new \RuntimeException('Whoops')));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\DuplexResourceStream::handleData
|
||||
*/
|
||||
public function testClosingStreamInDataEventShouldNotTriggerError()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$conn = new DuplexResourceStream($stream, $loop);
|
||||
$conn->on('error', $this->expectCallableNever());
|
||||
$conn->on('data', function ($data) use ($conn) {
|
||||
$conn->close();
|
||||
});
|
||||
|
||||
fwrite($stream, "foobar\n");
|
||||
rewind($stream);
|
||||
|
||||
$conn->handleData($stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\DuplexResourceStream::handleData
|
||||
*/
|
||||
public function testDataFiltered()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
|
||||
// add a filter which removes every 'a' when reading
|
||||
Filter\append($stream, function ($chunk) {
|
||||
return str_replace('a', '', $chunk);
|
||||
}, STREAM_FILTER_READ);
|
||||
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$capturedData = null;
|
||||
|
||||
$conn = new DuplexResourceStream($stream, $loop);
|
||||
$conn->on('data', function ($data) use (&$capturedData) {
|
||||
$capturedData = $data;
|
||||
});
|
||||
|
||||
fwrite($stream, "foobar\n");
|
||||
rewind($stream);
|
||||
|
||||
$conn->handleData($stream);
|
||||
$this->assertSame("foobr\n", $capturedData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\DuplexResourceStream::handleData
|
||||
*/
|
||||
public function testDataErrorShouldEmitErrorAndClose()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
|
||||
// add a filter which returns an error when encountering an 'a' when reading
|
||||
Filter\append($stream, function ($chunk) {
|
||||
if (strpos($chunk, 'a') !== false) {
|
||||
throw new \Exception('Invalid');
|
||||
}
|
||||
return $chunk;
|
||||
}, STREAM_FILTER_READ);
|
||||
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$conn = new DuplexResourceStream($stream, $loop);
|
||||
$conn->on('data', $this->expectCallableNever());
|
||||
$conn->on('error', $this->expectCallableOnce());
|
||||
$conn->on('close', $this->expectCallableOnce());
|
||||
|
||||
fwrite($stream, "foobar\n");
|
||||
rewind($stream);
|
||||
|
||||
$conn->handleData($stream);
|
||||
}
|
||||
|
||||
private function createWriteableLoopMock()
|
||||
{
|
||||
$loop = $this->createLoopMock();
|
||||
$loop
|
||||
->expects($this->once())
|
||||
->method('addWriteStream')
|
||||
->will($this->returnCallback(function ($stream, $listener) {
|
||||
call_user_func($listener, $stream);
|
||||
}));
|
||||
|
||||
return $loop;
|
||||
}
|
||||
|
||||
private function createLoopMock()
|
||||
{
|
||||
return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
|
||||
}
|
||||
}
|
||||
35
vendor/react/stream/tests/EnforceBlockingWrapper.php
vendored
Executable file
35
vendor/react/stream/tests/EnforceBlockingWrapper.php
vendored
Executable file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\Stream;
|
||||
|
||||
/**
|
||||
* Used to test dummy stream resources that do not support setting non-blocking mode
|
||||
*
|
||||
* @link http://php.net/manual/de/class.streamwrapper.php
|
||||
*/
|
||||
class EnforceBlockingWrapper
|
||||
{
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function stream_cast($cast_as)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_eof()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_set_option($option, $arg1, $arg2)
|
||||
{
|
||||
if ($option === STREAM_OPTION_BLOCKING) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
122
vendor/react/stream/tests/FunctionalInternetTest.php
vendored
Executable file
122
vendor/react/stream/tests/FunctionalInternetTest.php
vendored
Executable file
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\Stream;
|
||||
|
||||
use React\EventLoop\Factory;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Stream\DuplexResourceStream;
|
||||
use React\Stream\WritableResourceStream;
|
||||
|
||||
/**
|
||||
* @group internet
|
||||
*/
|
||||
class FunctionalInternetTest extends TestCase
|
||||
{
|
||||
public function testUploadKilobytePlain()
|
||||
{
|
||||
$size = 1000;
|
||||
$stream = stream_socket_client('tcp://httpbin.org:80');
|
||||
|
||||
$loop = Factory::create();
|
||||
$stream = new DuplexResourceStream($stream, $loop);
|
||||
|
||||
$buffer = '';
|
||||
$stream->on('data', function ($chunk) use (&$buffer) {
|
||||
$buffer .= $chunk;
|
||||
});
|
||||
|
||||
$stream->on('error', $this->expectCallableNever());
|
||||
|
||||
$stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size));
|
||||
|
||||
$this->awaitStreamClose($stream, $loop);
|
||||
|
||||
$this->assertNotEquals('', $buffer);
|
||||
}
|
||||
|
||||
public function testUploadBiggerBlockPlain()
|
||||
{
|
||||
$size = 50 * 1000;
|
||||
$stream = stream_socket_client('tcp://httpbin.org:80');
|
||||
|
||||
$loop = Factory::create();
|
||||
$stream = new DuplexResourceStream($stream, $loop);
|
||||
|
||||
$buffer = '';
|
||||
$stream->on('data', function ($chunk) use (&$buffer) {
|
||||
$buffer .= $chunk;
|
||||
});
|
||||
|
||||
$stream->on('error', $this->expectCallableNever());
|
||||
|
||||
$stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size));
|
||||
|
||||
$this->awaitStreamClose($stream, $loop);
|
||||
|
||||
$this->assertNotEquals('', $buffer);
|
||||
}
|
||||
|
||||
public function testUploadKilobyteSecure()
|
||||
{
|
||||
$size = 1000;
|
||||
$stream = stream_socket_client('tls://httpbin.org:443');
|
||||
|
||||
$loop = Factory::create();
|
||||
$stream = new DuplexResourceStream($stream, $loop);
|
||||
|
||||
$buffer = '';
|
||||
$stream->on('data', function ($chunk) use (&$buffer) {
|
||||
$buffer .= $chunk;
|
||||
});
|
||||
|
||||
$stream->on('error', $this->expectCallableNever());
|
||||
|
||||
$stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size));
|
||||
|
||||
$this->awaitStreamClose($stream, $loop);
|
||||
|
||||
$this->assertNotEquals('', $buffer);
|
||||
}
|
||||
|
||||
public function testUploadBiggerBlockSecureRequiresSmallerChunkSize()
|
||||
{
|
||||
$size = 50 * 1000;
|
||||
$stream = stream_socket_client('tls://httpbin.org:443');
|
||||
|
||||
$loop = Factory::create();
|
||||
$stream = new DuplexResourceStream(
|
||||
$stream,
|
||||
$loop,
|
||||
null,
|
||||
new WritableResourceStream($stream, $loop, null, 8192)
|
||||
);
|
||||
|
||||
$buffer = '';
|
||||
$stream->on('data', function ($chunk) use (&$buffer) {
|
||||
$buffer .= $chunk;
|
||||
});
|
||||
|
||||
$stream->on('error', $this->expectCallableNever());
|
||||
|
||||
$stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size));
|
||||
|
||||
$this->awaitStreamClose($stream, $loop);
|
||||
|
||||
$this->assertNotEquals('', $buffer);
|
||||
}
|
||||
|
||||
private function awaitStreamClose(DuplexResourceStream $stream, LoopInterface $loop, $timeout = 10.0)
|
||||
{
|
||||
$stream->on('close', function () use ($loop) {
|
||||
$loop->stop();
|
||||
});
|
||||
|
||||
$that = $this;
|
||||
$loop->addTimer($timeout, function () use ($loop, $that) {
|
||||
$loop->stop();
|
||||
$that->fail('Timed out while waiting for stream to close');
|
||||
});
|
||||
|
||||
$loop->run();
|
||||
}
|
||||
}
|
||||
391
vendor/react/stream/tests/ReadableResourceStreamTest.php
vendored
Executable file
391
vendor/react/stream/tests/ReadableResourceStreamTest.php
vendored
Executable file
@@ -0,0 +1,391 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\Stream;
|
||||
|
||||
use React\Stream\ReadableResourceStream;
|
||||
use Clue\StreamFilter as Filter;
|
||||
|
||||
class ReadableResourceStreamTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @covers React\Stream\ReadableResourceStream::__construct
|
||||
* @doesNotPerformAssertions
|
||||
*/
|
||||
public function testConstructor()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
new ReadableResourceStream($stream, $loop);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\ReadableResourceStream::__construct
|
||||
* @doesNotPerformAssertions
|
||||
*/
|
||||
public function testConstructorWithExcessiveMode()
|
||||
{
|
||||
// excessive flags are ignored for temp streams, so we have to use a file stream
|
||||
$name = tempnam(sys_get_temp_dir(), 'test');
|
||||
$stream = @fopen($name, 'r+eANYTHING');
|
||||
unlink($name);
|
||||
|
||||
$loop = $this->createLoopMock();
|
||||
$buffer = new ReadableResourceStream($stream, $loop);
|
||||
$buffer->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\ReadableResourceStream::__construct
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testConstructorThrowsExceptionOnInvalidStream()
|
||||
{
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
new ReadableResourceStream(false, $loop);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\ReadableResourceStream::__construct
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testConstructorThrowsExceptionOnWriteOnlyStream()
|
||||
{
|
||||
if (defined('HHVM_VERSION')) {
|
||||
$this->markTestSkipped('HHVM does not report fopen mode for STDOUT');
|
||||
}
|
||||
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
new ReadableResourceStream(STDOUT, $loop);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\ReadableResourceStream::__construct
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testConstructorThrowsExceptionOnWriteOnlyStreamWithExcessiveMode()
|
||||
{
|
||||
// excessive flags are ignored for temp streams, so we have to use a file stream
|
||||
$name = tempnam(sys_get_temp_dir(), 'test');
|
||||
$stream = fopen($name, 'weANYTHING');
|
||||
unlink($name);
|
||||
|
||||
$loop = $this->createLoopMock();
|
||||
new ReadableResourceStream($stream, $loop);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\ReadableResourceStream::__construct
|
||||
* @expectedException RuntimeException
|
||||
*/
|
||||
public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking()
|
||||
{
|
||||
if (!in_array('blocking', stream_get_wrappers())) {
|
||||
stream_wrapper_register('blocking', 'React\Tests\Stream\EnforceBlockingWrapper');
|
||||
}
|
||||
|
||||
$stream = fopen('blocking://test', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
new ReadableResourceStream($stream, $loop);
|
||||
}
|
||||
|
||||
|
||||
public function testCloseShouldEmitCloseEvent()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$conn = new ReadableResourceStream($stream, $loop);
|
||||
$conn->on('close', $this->expectCallableOnce());
|
||||
|
||||
$conn->close();
|
||||
|
||||
$this->assertFalse($conn->isReadable());
|
||||
}
|
||||
|
||||
public function testCloseTwiceShouldEmitCloseEventOnce()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$conn = new ReadableResourceStream($stream, $loop);
|
||||
$conn->on('close', $this->expectCallableOnce());
|
||||
|
||||
$conn->close();
|
||||
$conn->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\ReadableResourceStream::__construct
|
||||
* @covers React\Stream\ReadableResourceStream::handleData
|
||||
*/
|
||||
public function testDataEvent()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$capturedData = null;
|
||||
|
||||
$conn = new ReadableResourceStream($stream, $loop);
|
||||
$conn->on('data', function ($data) use (&$capturedData) {
|
||||
$capturedData = $data;
|
||||
});
|
||||
|
||||
fwrite($stream, "foobar\n");
|
||||
rewind($stream);
|
||||
|
||||
$conn->handleData($stream);
|
||||
$this->assertSame("foobar\n", $capturedData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\ReadableResourceStream::__construct
|
||||
* @covers React\Stream\ReadableResourceStream::handleData
|
||||
*/
|
||||
public function testDataEventDoesEmitOneChunkMatchingBufferSize()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$capturedData = null;
|
||||
|
||||
$conn = new ReadableResourceStream($stream, $loop, 4321);
|
||||
$conn->on('data', function ($data) use (&$capturedData) {
|
||||
$capturedData = $data;
|
||||
});
|
||||
|
||||
fwrite($stream, str_repeat("a", 100000));
|
||||
rewind($stream);
|
||||
|
||||
$conn->handleData($stream);
|
||||
|
||||
$this->assertTrue($conn->isReadable());
|
||||
$this->assertEquals(4321, strlen($capturedData));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\ReadableResourceStream::__construct
|
||||
* @covers React\Stream\ReadableResourceStream::handleData
|
||||
*/
|
||||
public function testDataEventDoesEmitOneChunkUntilStreamEndsWhenBufferSizeIsInfinite()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$capturedData = null;
|
||||
|
||||
$conn = new ReadableResourceStream($stream, $loop, -1);
|
||||
|
||||
$conn->on('data', function ($data) use (&$capturedData) {
|
||||
$capturedData = $data;
|
||||
});
|
||||
|
||||
fwrite($stream, str_repeat("a", 100000));
|
||||
rewind($stream);
|
||||
|
||||
$conn->handleData($stream);
|
||||
|
||||
$this->assertTrue($conn->isReadable());
|
||||
$this->assertEquals(100000, strlen($capturedData));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\ReadableResourceStream::handleData
|
||||
*/
|
||||
public function testEmptyStreamShouldNotEmitData()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$conn = new ReadableResourceStream($stream, $loop);
|
||||
$conn->on('data', $this->expectCallableNever());
|
||||
|
||||
$conn->handleData($stream);
|
||||
}
|
||||
|
||||
public function testPipeShouldReturnDestination()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$conn = new ReadableResourceStream($stream, $loop);
|
||||
$dest = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
|
||||
|
||||
$this->assertSame($dest, $conn->pipe($dest));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\ReadableResourceStream::handleData
|
||||
*/
|
||||
public function testClosingStreamInDataEventShouldNotTriggerError()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$conn = new ReadableResourceStream($stream, $loop);
|
||||
$conn->on('error', $this->expectCallableNever());
|
||||
$conn->on('data', function ($data) use ($conn) {
|
||||
$conn->close();
|
||||
});
|
||||
|
||||
fwrite($stream, "foobar\n");
|
||||
rewind($stream);
|
||||
|
||||
$conn->handleData($stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\ReadableResourceStream::pause
|
||||
*/
|
||||
public function testPauseRemovesReadStreamFromLoop()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
$loop->expects($this->once())->method('addReadStream')->with($stream);
|
||||
$loop->expects($this->once())->method('removeReadStream')->with($stream);
|
||||
|
||||
$conn = new ReadableResourceStream($stream, $loop);
|
||||
$conn->pause();
|
||||
$conn->pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\ReadableResourceStream::pause
|
||||
*/
|
||||
public function testResumeDoesAddStreamToLoopOnlyOnce()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
$loop->expects($this->once())->method('addReadStream')->with($stream);
|
||||
|
||||
$conn = new ReadableResourceStream($stream, $loop);
|
||||
$conn->resume();
|
||||
$conn->resume();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\ReadableResourceStream::close
|
||||
*/
|
||||
public function testCloseRemovesReadStreamFromLoop()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
$loop->expects($this->once())->method('addReadStream')->with($stream);
|
||||
$loop->expects($this->once())->method('removeReadStream')->with($stream);
|
||||
|
||||
$conn = new ReadableResourceStream($stream, $loop);
|
||||
$conn->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\ReadableResourceStream::close
|
||||
*/
|
||||
public function testCloseAfterPauseRemovesReadStreamFromLoopOnce()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
$loop->expects($this->once())->method('addReadStream')->with($stream);
|
||||
$loop->expects($this->once())->method('removeReadStream')->with($stream);
|
||||
|
||||
$conn = new ReadableResourceStream($stream, $loop);
|
||||
$conn->pause();
|
||||
$conn->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\ReadableResourceStream::close
|
||||
*/
|
||||
public function testResumeAfterCloseDoesAddReadStreamToLoopOnlyOnce()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
$loop->expects($this->once())->method('addReadStream')->with($stream);
|
||||
|
||||
$conn = new ReadableResourceStream($stream, $loop);
|
||||
$conn->close();
|
||||
$conn->resume();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\ReadableResourceStream::handleData
|
||||
*/
|
||||
public function testDataFiltered()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
|
||||
// add a filter which removes every 'a' when reading
|
||||
Filter\append($stream, function ($chunk) {
|
||||
return str_replace('a', '', $chunk);
|
||||
}, STREAM_FILTER_READ);
|
||||
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$capturedData = null;
|
||||
|
||||
$conn = new ReadableResourceStream($stream, $loop);
|
||||
$conn->on('data', function ($data) use (&$capturedData) {
|
||||
$capturedData = $data;
|
||||
});
|
||||
|
||||
fwrite($stream, "foobar\n");
|
||||
rewind($stream);
|
||||
|
||||
$conn->handleData($stream);
|
||||
$this->assertSame("foobr\n", $capturedData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\ReadableResourceStream::handleData
|
||||
*/
|
||||
public function testDataErrorShouldEmitErrorAndClose()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
|
||||
// add a filter which returns an error when encountering an 'a' when reading
|
||||
Filter\append($stream, function ($chunk) {
|
||||
if (strpos($chunk, 'a') !== false) {
|
||||
throw new \Exception('Invalid');
|
||||
}
|
||||
return $chunk;
|
||||
}, STREAM_FILTER_READ);
|
||||
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$conn = new ReadableResourceStream($stream, $loop);
|
||||
$conn->on('data', $this->expectCallableNever());
|
||||
$conn->on('error', $this->expectCallableOnce());
|
||||
$conn->on('close', $this->expectCallableOnce());
|
||||
|
||||
fwrite($stream, "foobar\n");
|
||||
rewind($stream);
|
||||
|
||||
$conn->handleData($stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\ReadableResourceStream::handleData
|
||||
*/
|
||||
public function testEmptyReadShouldntFcloseStream()
|
||||
{
|
||||
list($stream, $_) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$conn = new ReadableResourceStream($stream, $loop);
|
||||
$conn->on('error', $this->expectCallableNever());
|
||||
$conn->on('data', $this->expectCallableNever());
|
||||
$conn->on('end', $this->expectCallableNever());
|
||||
|
||||
$conn->handleData();
|
||||
|
||||
fclose($stream);
|
||||
fclose($_);
|
||||
}
|
||||
|
||||
private function createLoopMock()
|
||||
{
|
||||
return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
|
||||
}
|
||||
}
|
||||
61
vendor/react/stream/tests/Stub/ReadableStreamStub.php
vendored
Executable file
61
vendor/react/stream/tests/Stub/ReadableStreamStub.php
vendored
Executable file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\Stream\Stub;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
use React\Stream\Util;
|
||||
|
||||
class ReadableStreamStub extends EventEmitter implements ReadableStreamInterface
|
||||
{
|
||||
public $readable = true;
|
||||
public $paused = false;
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// trigger data event
|
||||
public function write($data)
|
||||
{
|
||||
$this->emit('data', array($data));
|
||||
}
|
||||
|
||||
// trigger error event
|
||||
public function error($error)
|
||||
{
|
||||
$this->emit('error', array($error));
|
||||
}
|
||||
|
||||
// trigger end event
|
||||
public function end()
|
||||
{
|
||||
$this->emit('end', array());
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
$this->paused = true;
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
$this->paused = false;
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
$this->readable = false;
|
||||
|
||||
$this->emit('close');
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
Util::pipe($this, $dest, $options);
|
||||
|
||||
return $dest;
|
||||
}
|
||||
}
|
||||
54
vendor/react/stream/tests/TestCase.php
vendored
Executable file
54
vendor/react/stream/tests/TestCase.php
vendored
Executable file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\Stream;
|
||||
|
||||
use PHPUnit\Framework\TestCase as BaseTestCase;
|
||||
|
||||
class TestCase extends BaseTestCase
|
||||
{
|
||||
protected function expectCallableExactly($amount)
|
||||
{
|
||||
$mock = $this->createCallableMock();
|
||||
$mock
|
||||
->expects($this->exactly($amount))
|
||||
->method('__invoke');
|
||||
|
||||
return $mock;
|
||||
}
|
||||
|
||||
protected function expectCallableOnce()
|
||||
{
|
||||
$mock = $this->createCallableMock();
|
||||
$mock
|
||||
->expects($this->once())
|
||||
->method('__invoke');
|
||||
|
||||
return $mock;
|
||||
}
|
||||
|
||||
protected function expectCallableOnceWith($value)
|
||||
{
|
||||
$callback = $this->createCallableMock();
|
||||
$callback
|
||||
->expects($this->once())
|
||||
->method('__invoke')
|
||||
->with($value);
|
||||
|
||||
return $callback;
|
||||
}
|
||||
|
||||
protected function expectCallableNever()
|
||||
{
|
||||
$mock = $this->createCallableMock();
|
||||
$mock
|
||||
->expects($this->never())
|
||||
->method('__invoke');
|
||||
|
||||
return $mock;
|
||||
}
|
||||
|
||||
protected function createCallableMock()
|
||||
{
|
||||
return $this->getMockBuilder('React\Tests\Stream\CallableStub')->getMock();
|
||||
}
|
||||
}
|
||||
267
vendor/react/stream/tests/ThroughStreamTest.php
vendored
Executable file
267
vendor/react/stream/tests/ThroughStreamTest.php
vendored
Executable file
@@ -0,0 +1,267 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\Stream;
|
||||
|
||||
use React\Stream\ThroughStream;
|
||||
|
||||
/**
|
||||
* @covers React\Stream\ThroughStream
|
||||
*/
|
||||
class ThroughStreamTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @test
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function itShouldRejectInvalidCallback()
|
||||
{
|
||||
new ThroughStream(123);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function itShouldReturnTrueForAnyDataWrittenToIt()
|
||||
{
|
||||
$through = new ThroughStream();
|
||||
$ret = $through->write('foo');
|
||||
|
||||
$this->assertTrue($ret);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function itShouldEmitAnyDataWrittenToIt()
|
||||
{
|
||||
$through = new ThroughStream();
|
||||
$through->on('data', $this->expectCallableOnceWith('foo'));
|
||||
$through->write('foo');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function itShouldEmitAnyDataWrittenToItPassedThruFunction()
|
||||
{
|
||||
$through = new ThroughStream('strtoupper');
|
||||
$through->on('data', $this->expectCallableOnceWith('FOO'));
|
||||
$through->write('foo');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function itShouldEmitAnyDataWrittenToItPassedThruCallback()
|
||||
{
|
||||
$through = new ThroughStream('strtoupper');
|
||||
$through->on('data', $this->expectCallableOnceWith('FOO'));
|
||||
$through->write('foo');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function itShouldEmitErrorAndCloseIfCallbackThrowsException()
|
||||
{
|
||||
$through = new ThroughStream(function () {
|
||||
throw new \RuntimeException();
|
||||
});
|
||||
$through->on('error', $this->expectCallableOnce());
|
||||
$through->on('close', $this->expectCallableOnce());
|
||||
$through->on('data', $this->expectCallableNever());
|
||||
$through->on('end', $this->expectCallableNever());
|
||||
|
||||
$through->write('foo');
|
||||
|
||||
$this->assertFalse($through->isReadable());
|
||||
$this->assertFalse($through->isWritable());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function itShouldEmitErrorAndCloseIfCallbackThrowsExceptionOnEnd()
|
||||
{
|
||||
$through = new ThroughStream(function () {
|
||||
throw new \RuntimeException();
|
||||
});
|
||||
$through->on('error', $this->expectCallableOnce());
|
||||
$through->on('close', $this->expectCallableOnce());
|
||||
$through->on('data', $this->expectCallableNever());
|
||||
$through->on('end', $this->expectCallableNever());
|
||||
|
||||
$through->end('foo');
|
||||
|
||||
$this->assertFalse($through->isReadable());
|
||||
$this->assertFalse($through->isWritable());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function itShouldReturnFalseForAnyDataWrittenToItWhenPaused()
|
||||
{
|
||||
$through = new ThroughStream();
|
||||
$through->pause();
|
||||
$ret = $through->write('foo');
|
||||
|
||||
$this->assertFalse($ret);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function itShouldEmitDrainOnResumeAfterReturnFalseForAnyDataWrittenToItWhenPaused()
|
||||
{
|
||||
$through = new ThroughStream();
|
||||
$through->pause();
|
||||
$through->write('foo');
|
||||
|
||||
$through->on('drain', $this->expectCallableOnce());
|
||||
$through->resume();
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function itShouldReturnTrueForAnyDataWrittenToItWhenResumedAfterPause()
|
||||
{
|
||||
$through = new ThroughStream();
|
||||
$through->on('drain', $this->expectCallableNever());
|
||||
$through->pause();
|
||||
$through->resume();
|
||||
$ret = $through->write('foo');
|
||||
|
||||
$this->assertTrue($ret);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function pipingStuffIntoItShouldWork()
|
||||
{
|
||||
$readable = new ThroughStream();
|
||||
|
||||
$through = new ThroughStream();
|
||||
$through->on('data', $this->expectCallableOnceWith('foo'));
|
||||
|
||||
$readable->pipe($through);
|
||||
$readable->emit('data', array('foo'));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function endShouldEmitEndAndClose()
|
||||
{
|
||||
$through = new ThroughStream();
|
||||
$through->on('data', $this->expectCallableNever());
|
||||
$through->on('end', $this->expectCallableOnce());
|
||||
$through->on('close', $this->expectCallableOnce());
|
||||
$through->end();
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function endShouldCloseTheStream()
|
||||
{
|
||||
$through = new ThroughStream();
|
||||
$through->on('data', $this->expectCallableNever());
|
||||
$through->end();
|
||||
|
||||
$this->assertFalse($through->isReadable());
|
||||
$this->assertFalse($through->isWritable());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function endShouldWriteDataBeforeClosing()
|
||||
{
|
||||
$through = new ThroughStream();
|
||||
$through->on('data', $this->expectCallableOnceWith('foo'));
|
||||
$through->end('foo');
|
||||
|
||||
$this->assertFalse($through->isReadable());
|
||||
$this->assertFalse($through->isWritable());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function endTwiceShouldOnlyEmitOnce()
|
||||
{
|
||||
$through = new ThroughStream();
|
||||
$through->on('data', $this->expectCallableOnce('first'));
|
||||
$through->end('first');
|
||||
$through->end('ignored');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function writeAfterEndShouldReturnFalse()
|
||||
{
|
||||
$through = new ThroughStream();
|
||||
$through->on('data', $this->expectCallableNever());
|
||||
$through->end();
|
||||
|
||||
$this->assertFalse($through->write('foo'));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function writeDataWillCloseStreamShouldReturnFalse()
|
||||
{
|
||||
$through = new ThroughStream();
|
||||
$through->on('data', array($through, 'close'));
|
||||
|
||||
$this->assertFalse($through->write('foo'));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function writeDataToPausedShouldReturnFalse()
|
||||
{
|
||||
$through = new ThroughStream();
|
||||
$through->pause();
|
||||
|
||||
$this->assertFalse($through->write('foo'));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function writeDataToResumedShouldReturnTrue()
|
||||
{
|
||||
$through = new ThroughStream();
|
||||
$through->pause();
|
||||
$through->resume();
|
||||
|
||||
$this->assertTrue($through->write('foo'));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function itShouldBeReadableByDefault()
|
||||
{
|
||||
$through = new ThroughStream();
|
||||
$this->assertTrue($through->isReadable());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function itShouldBeWritableByDefault()
|
||||
{
|
||||
$through = new ThroughStream();
|
||||
$this->assertTrue($through->isWritable());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function closeShouldCloseOnce()
|
||||
{
|
||||
$through = new ThroughStream();
|
||||
|
||||
$through->on('close', $this->expectCallableOnce());
|
||||
|
||||
$through->close();
|
||||
|
||||
$this->assertFalse($through->isReadable());
|
||||
$this->assertFalse($through->isWritable());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function doubleCloseShouldCloseOnce()
|
||||
{
|
||||
$through = new ThroughStream();
|
||||
|
||||
$through->on('close', $this->expectCallableOnce());
|
||||
|
||||
$through->close();
|
||||
$through->close();
|
||||
|
||||
$this->assertFalse($through->isReadable());
|
||||
$this->assertFalse($through->isWritable());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function pipeShouldPipeCorrectly()
|
||||
{
|
||||
$output = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
|
||||
$output->expects($this->any())->method('isWritable')->willReturn(True);
|
||||
$output
|
||||
->expects($this->once())
|
||||
->method('write')
|
||||
->with('foo');
|
||||
|
||||
$through = new ThroughStream();
|
||||
$through->pipe($output);
|
||||
$through->write('foo');
|
||||
}
|
||||
}
|
||||
273
vendor/react/stream/tests/UtilTest.php
vendored
Executable file
273
vendor/react/stream/tests/UtilTest.php
vendored
Executable file
@@ -0,0 +1,273 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\Stream;
|
||||
|
||||
use React\Stream\WritableResourceStream;
|
||||
use React\Stream\Util;
|
||||
use React\Stream\CompositeStream;
|
||||
use React\Stream\ThroughStream;
|
||||
|
||||
/**
|
||||
* @covers React\Stream\Util
|
||||
*/
|
||||
class UtilTest extends TestCase
|
||||
{
|
||||
public function testPipeReturnsDestinationStream()
|
||||
{
|
||||
$readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
|
||||
|
||||
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
|
||||
|
||||
$ret = Util::pipe($readable, $writable);
|
||||
|
||||
$this->assertSame($writable, $ret);
|
||||
}
|
||||
|
||||
public function testPipeNonReadableSourceShouldDoNothing()
|
||||
{
|
||||
$readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
|
||||
$readable
|
||||
->expects($this->any())
|
||||
->method('isReadable')
|
||||
->willReturn(false);
|
||||
|
||||
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
|
||||
$writable
|
||||
->expects($this->never())
|
||||
->method('isWritable');
|
||||
$writable
|
||||
->expects($this->never())
|
||||
->method('end');
|
||||
|
||||
Util::pipe($readable, $writable);
|
||||
}
|
||||
|
||||
public function testPipeIntoNonWritableDestinationShouldPauseSource()
|
||||
{
|
||||
$readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
|
||||
$readable
|
||||
->expects($this->any())
|
||||
->method('isReadable')
|
||||
->willReturn(true);
|
||||
$readable
|
||||
->expects($this->once())
|
||||
->method('pause');
|
||||
|
||||
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
|
||||
$writable
|
||||
->expects($this->any())
|
||||
->method('isWritable')
|
||||
->willReturn(false);
|
||||
$writable
|
||||
->expects($this->never())
|
||||
->method('end');
|
||||
|
||||
Util::pipe($readable, $writable);
|
||||
}
|
||||
|
||||
public function testPipeClosingDestPausesSource()
|
||||
{
|
||||
$readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
|
||||
$readable
|
||||
->expects($this->any())
|
||||
->method('isReadable')
|
||||
->willReturn(true);
|
||||
$readable
|
||||
->expects($this->once())
|
||||
->method('pause');
|
||||
|
||||
$writable = new ThroughStream();
|
||||
|
||||
Util::pipe($readable, $writable);
|
||||
|
||||
$writable->close();
|
||||
}
|
||||
|
||||
public function testPipeWithEnd()
|
||||
{
|
||||
$readable = new Stub\ReadableStreamStub();
|
||||
|
||||
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
|
||||
$writable
|
||||
->expects($this->any())
|
||||
->method('isWritable')
|
||||
->willReturn(true);
|
||||
$writable
|
||||
->expects($this->once())
|
||||
->method('end');
|
||||
|
||||
Util::pipe($readable, $writable);
|
||||
|
||||
$readable->end();
|
||||
}
|
||||
|
||||
public function testPipeWithoutEnd()
|
||||
{
|
||||
$readable = new Stub\ReadableStreamStub();
|
||||
|
||||
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
|
||||
$writable
|
||||
->expects($this->any())
|
||||
->method('isWritable')
|
||||
->willReturn(true);
|
||||
$writable
|
||||
->expects($this->never())
|
||||
->method('end');
|
||||
|
||||
Util::pipe($readable, $writable, array('end' => false));
|
||||
|
||||
$readable->end();
|
||||
}
|
||||
|
||||
public function testPipeWithTooSlowWritableShouldPauseReadable()
|
||||
{
|
||||
$readable = new Stub\ReadableStreamStub();
|
||||
|
||||
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
|
||||
$writable
|
||||
->expects($this->any())
|
||||
->method('isWritable')
|
||||
->willReturn(true);
|
||||
$writable
|
||||
->expects($this->once())
|
||||
->method('write')
|
||||
->with('some data')
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$readable->pipe($writable);
|
||||
|
||||
$this->assertFalse($readable->paused);
|
||||
$readable->write('some data');
|
||||
$this->assertTrue($readable->paused);
|
||||
}
|
||||
|
||||
public function testPipeWithTooSlowWritableShouldResumeOnDrain()
|
||||
{
|
||||
$readable = new Stub\ReadableStreamStub();
|
||||
|
||||
$onDrain = null;
|
||||
|
||||
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
|
||||
$writable
|
||||
->expects($this->any())
|
||||
->method('isWritable')
|
||||
->willReturn(true);
|
||||
$writable
|
||||
->expects($this->any())
|
||||
->method('on')
|
||||
->will($this->returnCallback(function ($name, $callback) use (&$onDrain) {
|
||||
if ($name === 'drain') {
|
||||
$onDrain = $callback;
|
||||
}
|
||||
}));
|
||||
|
||||
$readable->pipe($writable);
|
||||
$readable->pause();
|
||||
|
||||
$this->assertTrue($readable->paused);
|
||||
$this->assertNotNull($onDrain);
|
||||
$onDrain();
|
||||
$this->assertFalse($readable->paused);
|
||||
}
|
||||
|
||||
public function testPipeWithWritableResourceStream()
|
||||
{
|
||||
$readable = new Stub\ReadableStreamStub();
|
||||
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
$buffer = new WritableResourceStream($stream, $loop);
|
||||
|
||||
$readable->pipe($buffer);
|
||||
|
||||
$readable->write('hello, I am some ');
|
||||
$readable->write('random data');
|
||||
|
||||
$buffer->handleWrite();
|
||||
rewind($stream);
|
||||
$this->assertSame('hello, I am some random data', stream_get_contents($stream));
|
||||
}
|
||||
|
||||
public function testPipeSetsUpListeners()
|
||||
{
|
||||
$source = new ThroughStream();
|
||||
$dest = new ThroughStream();
|
||||
|
||||
$this->assertCount(0, $source->listeners('data'));
|
||||
$this->assertCount(0, $source->listeners('end'));
|
||||
$this->assertCount(0, $dest->listeners('drain'));
|
||||
|
||||
Util::pipe($source, $dest);
|
||||
|
||||
$this->assertCount(1, $source->listeners('data'));
|
||||
$this->assertCount(1, $source->listeners('end'));
|
||||
$this->assertCount(1, $dest->listeners('drain'));
|
||||
}
|
||||
|
||||
public function testPipeClosingSourceRemovesListeners()
|
||||
{
|
||||
$source = new ThroughStream();
|
||||
$dest = new ThroughStream();
|
||||
|
||||
Util::pipe($source, $dest);
|
||||
|
||||
$source->close();
|
||||
|
||||
$this->assertCount(0, $source->listeners('data'));
|
||||
$this->assertCount(0, $source->listeners('end'));
|
||||
$this->assertCount(0, $dest->listeners('drain'));
|
||||
}
|
||||
|
||||
public function testPipeClosingDestRemovesListeners()
|
||||
{
|
||||
$source = new ThroughStream();
|
||||
$dest = new ThroughStream();
|
||||
|
||||
Util::pipe($source, $dest);
|
||||
|
||||
$dest->close();
|
||||
|
||||
$this->assertCount(0, $source->listeners('data'));
|
||||
$this->assertCount(0, $source->listeners('end'));
|
||||
$this->assertCount(0, $dest->listeners('drain'));
|
||||
}
|
||||
|
||||
public function testPipeDuplexIntoSelfEndsOnEnd()
|
||||
{
|
||||
$readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
|
||||
$readable->expects($this->any())->method('isReadable')->willReturn(true);
|
||||
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
|
||||
$writable->expects($this->any())->method('isWritable')->willReturn(true);
|
||||
$duplex = new CompositeStream($readable, $writable);
|
||||
|
||||
Util::pipe($duplex, $duplex);
|
||||
|
||||
$writable->expects($this->once())->method('end');
|
||||
|
||||
$duplex->emit('end');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function forwardEventsShouldSetupForwards()
|
||||
{
|
||||
$source = new ThroughStream();
|
||||
$target = new ThroughStream();
|
||||
|
||||
Util::forwardEvents($source, $target, array('data'));
|
||||
$target->on('data', $this->expectCallableOnce());
|
||||
$target->on('foo', $this->expectCallableNever());
|
||||
|
||||
$source->emit('data', array('hello'));
|
||||
$source->emit('foo', array('bar'));
|
||||
}
|
||||
|
||||
private function createLoopMock()
|
||||
{
|
||||
return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
|
||||
}
|
||||
|
||||
private function notEqualTo($value)
|
||||
{
|
||||
return new \PHPUnit_Framework_Constraint_Not($value);
|
||||
}
|
||||
}
|
||||
534
vendor/react/stream/tests/WritableStreamResourceTest.php
vendored
Executable file
534
vendor/react/stream/tests/WritableStreamResourceTest.php
vendored
Executable file
@@ -0,0 +1,534 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\Stream;
|
||||
|
||||
use Clue\StreamFilter as Filter;
|
||||
use React\Stream\WritableResourceStream;
|
||||
|
||||
class WritableResourceStreamTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @covers React\Stream\WritableResourceStream::__construct
|
||||
* @doesNotPerformAssertions
|
||||
*/
|
||||
public function testConstructor()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
new WritableResourceStream($stream, $loop);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\WritableResourceStream::__construct
|
||||
* @doesNotPerformAssertions
|
||||
*/
|
||||
public function testConstructorWithExcessiveMode()
|
||||
{
|
||||
// excessive flags are ignored for temp streams, so we have to use a file stream
|
||||
$name = tempnam(sys_get_temp_dir(), 'test');
|
||||
$stream = @fopen($name, 'w+eANYTHING');
|
||||
unlink($name);
|
||||
|
||||
$loop = $this->createLoopMock();
|
||||
$buffer = new WritableResourceStream($stream, $loop);
|
||||
$buffer->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\WritableResourceStream::__construct
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testConstructorThrowsIfNotAValidStreamResource()
|
||||
{
|
||||
$stream = null;
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
new WritableResourceStream($stream, $loop);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\WritableResourceStream::__construct
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testConstructorThrowsExceptionOnReadOnlyStream()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
new WritableResourceStream($stream, $loop);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\WritableResourceStream::__construct
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testConstructorThrowsExceptionOnReadOnlyStreamWithExcessiveMode()
|
||||
{
|
||||
// excessive flags are ignored for temp streams, so we have to use a file stream
|
||||
$name = tempnam(sys_get_temp_dir(), 'test');
|
||||
$stream = fopen($name, 'reANYTHING');
|
||||
unlink($name);
|
||||
|
||||
$loop = $this->createLoopMock();
|
||||
new WritableResourceStream($stream, $loop);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\WritableResourceStream::__construct
|
||||
* @expectedException RuntimeException
|
||||
*/
|
||||
public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking()
|
||||
{
|
||||
if (!in_array('blocking', stream_get_wrappers())) {
|
||||
stream_wrapper_register('blocking', 'React\Tests\Stream\EnforceBlockingWrapper');
|
||||
}
|
||||
|
||||
$stream = fopen('blocking://test', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
new WritableResourceStream($stream, $loop);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\WritableResourceStream::write
|
||||
* @covers React\Stream\WritableResourceStream::handleWrite
|
||||
*/
|
||||
public function testWrite()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createWriteableLoopMock();
|
||||
|
||||
$buffer = new WritableResourceStream($stream, $loop);
|
||||
$buffer->on('error', $this->expectCallableNever());
|
||||
|
||||
$buffer->write("foobar\n");
|
||||
rewind($stream);
|
||||
$this->assertSame("foobar\n", fread($stream, 1024));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\WritableResourceStream::write
|
||||
*/
|
||||
public function testWriteWithDataDoesAddResourceToLoop()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
$loop->expects($this->once())->method('addWriteStream')->with($this->equalTo($stream));
|
||||
|
||||
$buffer = new WritableResourceStream($stream, $loop);
|
||||
|
||||
$buffer->write("foobar\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\WritableResourceStream::write
|
||||
* @covers React\Stream\WritableResourceStream::handleWrite
|
||||
*/
|
||||
public function testEmptyWriteDoesNotAddToLoop()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
$loop->expects($this->never())->method('addWriteStream');
|
||||
|
||||
$buffer = new WritableResourceStream($stream, $loop);
|
||||
|
||||
$buffer->write("");
|
||||
$buffer->write(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\WritableResourceStream::write
|
||||
* @covers React\Stream\WritableResourceStream::handleWrite
|
||||
*/
|
||||
public function testWriteReturnsFalseWhenWritableResourceStreamIsFull()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createWriteableLoopMock();
|
||||
$loop->preventWrites = true;
|
||||
|
||||
$buffer = new WritableResourceStream($stream, $loop, 4);
|
||||
$buffer->on('error', $this->expectCallableNever());
|
||||
|
||||
$this->assertTrue($buffer->write("foo"));
|
||||
$loop->preventWrites = false;
|
||||
$this->assertFalse($buffer->write("bar\n"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\WritableResourceStream::write
|
||||
*/
|
||||
public function testWriteReturnsFalseWhenWritableResourceStreamIsExactlyFull()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$buffer = new WritableResourceStream($stream, $loop, 3);
|
||||
|
||||
$this->assertFalse($buffer->write("foo"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\WritableResourceStream::write
|
||||
* @covers React\Stream\WritableResourceStream::handleWrite
|
||||
*/
|
||||
public function testWriteDetectsWhenOtherSideIsClosed()
|
||||
{
|
||||
list($a, $b) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
|
||||
|
||||
$loop = $this->createWriteableLoopMock();
|
||||
|
||||
$buffer = new WritableResourceStream($a, $loop, 4);
|
||||
$buffer->on('error', $this->expectCallableOnce());
|
||||
|
||||
fclose($b);
|
||||
|
||||
$buffer->write("foo");
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\WritableResourceStream::write
|
||||
* @covers React\Stream\WritableResourceStream::handleWrite
|
||||
*/
|
||||
public function testEmitsDrainAfterWriteWhichExceedsBuffer()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$buffer = new WritableResourceStream($stream, $loop, 2);
|
||||
$buffer->on('error', $this->expectCallableNever());
|
||||
$buffer->on('drain', $this->expectCallableOnce());
|
||||
|
||||
$buffer->write("foo");
|
||||
$buffer->handleWrite();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\WritableResourceStream::write
|
||||
* @covers React\Stream\WritableResourceStream::handleWrite
|
||||
*/
|
||||
public function testWriteInDrain()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$buffer = new WritableResourceStream($stream, $loop, 2);
|
||||
$buffer->on('error', $this->expectCallableNever());
|
||||
|
||||
$buffer->once('drain', function () use ($buffer) {
|
||||
$buffer->write("bar\n");
|
||||
$buffer->handleWrite();
|
||||
});
|
||||
|
||||
$this->assertFalse($buffer->write("foo\n"));
|
||||
$buffer->handleWrite();
|
||||
|
||||
fseek($stream, 0);
|
||||
$this->assertSame("foo\nbar\n", stream_get_contents($stream));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\WritableResourceStream::write
|
||||
* @covers React\Stream\WritableResourceStream::handleWrite
|
||||
*/
|
||||
public function testDrainAfterWrite()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$buffer = new WritableResourceStream($stream, $loop, 2);
|
||||
|
||||
$buffer->on('drain', $this->expectCallableOnce());
|
||||
|
||||
$buffer->write("foo");
|
||||
$buffer->handleWrite();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\WritableResourceStream::handleWrite
|
||||
*/
|
||||
public function testDrainAfterWriteWillRemoveResourceFromLoopWithoutClosing()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
$loop->expects($this->once())->method('removeWriteStream')->with($stream);
|
||||
|
||||
$buffer = new WritableResourceStream($stream, $loop, 2);
|
||||
|
||||
$buffer->on('drain', $this->expectCallableOnce());
|
||||
|
||||
$buffer->on('close', $this->expectCallableNever());
|
||||
|
||||
$buffer->write("foo");
|
||||
$buffer->handleWrite();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\WritableResourceStream::handleWrite
|
||||
*/
|
||||
public function testClosingDuringDrainAfterWriteWillRemoveResourceFromLoopOnceAndClose()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
$loop->expects($this->once())->method('removeWriteStream')->with($stream);
|
||||
|
||||
$buffer = new WritableResourceStream($stream, $loop, 2);
|
||||
|
||||
$buffer->on('drain', function () use ($buffer) {
|
||||
$buffer->close();
|
||||
});
|
||||
|
||||
$buffer->on('close', $this->expectCallableOnce());
|
||||
|
||||
$buffer->write("foo");
|
||||
$buffer->handleWrite();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\WritableResourceStream::end
|
||||
*/
|
||||
public function testEndWithoutDataClosesImmediatelyIfWritableResourceStreamIsEmpty()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$buffer = new WritableResourceStream($stream, $loop);
|
||||
$buffer->on('error', $this->expectCallableNever());
|
||||
$buffer->on('close', $this->expectCallableOnce());
|
||||
|
||||
$this->assertTrue($buffer->isWritable());
|
||||
$buffer->end();
|
||||
$this->assertFalse($buffer->isWritable());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\WritableResourceStream::end
|
||||
*/
|
||||
public function testEndWithoutDataDoesNotCloseIfWritableResourceStreamIsFull()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$buffer = new WritableResourceStream($stream, $loop);
|
||||
$buffer->on('error', $this->expectCallableNever());
|
||||
$buffer->on('close', $this->expectCallableNever());
|
||||
|
||||
$buffer->write('foo');
|
||||
|
||||
$this->assertTrue($buffer->isWritable());
|
||||
$buffer->end();
|
||||
$this->assertFalse($buffer->isWritable());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\WritableResourceStream::end
|
||||
*/
|
||||
public function testEndWithDataClosesImmediatelyIfWritableResourceStreamFlushes()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$filterBuffer = '';
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$buffer = new WritableResourceStream($stream, $loop);
|
||||
$buffer->on('error', $this->expectCallableNever());
|
||||
$buffer->on('close', $this->expectCallableOnce());
|
||||
|
||||
Filter\append($stream, function ($chunk) use (&$filterBuffer) {
|
||||
$filterBuffer .= $chunk;
|
||||
return $chunk;
|
||||
});
|
||||
|
||||
$this->assertTrue($buffer->isWritable());
|
||||
$buffer->end('final words');
|
||||
$this->assertFalse($buffer->isWritable());
|
||||
|
||||
$buffer->handleWrite();
|
||||
$this->assertSame('final words', $filterBuffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\WritableResourceStream::end
|
||||
*/
|
||||
public function testEndWithDataDoesNotCloseImmediatelyIfWritableResourceStreamIsFull()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$buffer = new WritableResourceStream($stream, $loop);
|
||||
$buffer->on('error', $this->expectCallableNever());
|
||||
$buffer->on('close', $this->expectCallableNever());
|
||||
|
||||
$buffer->write('foo');
|
||||
|
||||
$this->assertTrue($buffer->isWritable());
|
||||
$buffer->end('final words');
|
||||
$this->assertFalse($buffer->isWritable());
|
||||
|
||||
rewind($stream);
|
||||
$this->assertSame('', stream_get_contents($stream));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\WritableResourceStream::isWritable
|
||||
* @covers React\Stream\WritableResourceStream::close
|
||||
*/
|
||||
public function testClose()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$buffer = new WritableResourceStream($stream, $loop);
|
||||
$buffer->on('error', $this->expectCallableNever());
|
||||
$buffer->on('close', $this->expectCallableOnce());
|
||||
|
||||
$this->assertTrue($buffer->isWritable());
|
||||
$buffer->close();
|
||||
$this->assertFalse($buffer->isWritable());
|
||||
|
||||
$this->assertEquals(array(), $buffer->listeners('close'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\WritableResourceStream::close
|
||||
*/
|
||||
public function testClosingAfterWriteRemovesStreamFromLoop()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
$buffer = new WritableResourceStream($stream, $loop);
|
||||
|
||||
$loop->expects($this->once())->method('removeWriteStream')->with($stream);
|
||||
|
||||
$buffer->write('foo');
|
||||
$buffer->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\WritableResourceStream::close
|
||||
*/
|
||||
public function testClosingWithoutWritingDoesNotRemoveStreamFromLoop()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
$buffer = new WritableResourceStream($stream, $loop);
|
||||
|
||||
$loop->expects($this->never())->method('removeWriteStream');
|
||||
|
||||
$buffer->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\WritableResourceStream::close
|
||||
*/
|
||||
public function testDoubleCloseWillEmitOnlyOnce()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$buffer = new WritableResourceStream($stream, $loop);
|
||||
$buffer->on('close', $this->expectCallableOnce());
|
||||
|
||||
$buffer->close();
|
||||
$buffer->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\WritableResourceStream::write
|
||||
* @covers React\Stream\WritableResourceStream::close
|
||||
*/
|
||||
public function testWritingToClosedWritableResourceStreamShouldNotWriteToStream()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$filterBuffer = '';
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$buffer = new WritableResourceStream($stream, $loop);
|
||||
|
||||
Filter\append($stream, function ($chunk) use (&$filterBuffer) {
|
||||
$filterBuffer .= $chunk;
|
||||
return $chunk;
|
||||
});
|
||||
|
||||
$buffer->close();
|
||||
|
||||
$buffer->write('foo');
|
||||
|
||||
$buffer->handleWrite();
|
||||
$this->assertSame('', $filterBuffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Stream\WritableResourceStream::handleWrite
|
||||
*/
|
||||
public function testErrorWhenStreamResourceIsInvalid()
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
$loop = $this->createWriteableLoopMock();
|
||||
|
||||
$error = null;
|
||||
|
||||
$buffer = new WritableResourceStream($stream, $loop);
|
||||
$buffer->on('error', function ($message) use (&$error) {
|
||||
$error = $message;
|
||||
});
|
||||
|
||||
// invalidate stream resource
|
||||
fclose($stream);
|
||||
|
||||
$buffer->write('Attempting to write to bad stream');
|
||||
|
||||
$this->assertInstanceOf('Exception', $error);
|
||||
|
||||
// the error messages differ between PHP versions, let's just check substrings
|
||||
$this->assertContains('Unable to write to stream: ', $error->getMessage());
|
||||
$this->assertContains(' not a valid stream resource', $error->getMessage(), '', true);
|
||||
}
|
||||
|
||||
public function testWritingToClosedStream()
|
||||
{
|
||||
if ('Darwin' === PHP_OS) {
|
||||
$this->markTestSkipped('OS X issue with shutting down pair for writing');
|
||||
}
|
||||
|
||||
list($a, $b) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
|
||||
$loop = $this->createLoopMock();
|
||||
|
||||
$error = null;
|
||||
|
||||
$buffer = new WritableResourceStream($a, $loop);
|
||||
$buffer->on('error', function($message) use (&$error) {
|
||||
$error = $message;
|
||||
});
|
||||
|
||||
$buffer->write('foo');
|
||||
$buffer->handleWrite();
|
||||
stream_socket_shutdown($b, STREAM_SHUT_RD);
|
||||
stream_socket_shutdown($a, STREAM_SHUT_RD);
|
||||
$buffer->write('bar');
|
||||
$buffer->handleWrite();
|
||||
|
||||
$this->assertInstanceOf('Exception', $error);
|
||||
$this->assertSame('Unable to write to stream: fwrite(): send of 3 bytes failed with errno=32 Broken pipe', $error->getMessage());
|
||||
}
|
||||
|
||||
private function createWriteableLoopMock()
|
||||
{
|
||||
$loop = $this->createLoopMock();
|
||||
$loop->preventWrites = false;
|
||||
$loop
|
||||
->expects($this->any())
|
||||
->method('addWriteStream')
|
||||
->will($this->returnCallback(function ($stream, $listener) use ($loop) {
|
||||
if (!$loop->preventWrites) {
|
||||
call_user_func($listener, $stream);
|
||||
}
|
||||
}));
|
||||
|
||||
return $loop;
|
||||
}
|
||||
|
||||
private function createLoopMock()
|
||||
{
|
||||
return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user