From 7a70e27c27392081e8517e638b42c6d22d2d94b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Thu, 9 Nov 2017 11:16:29 +0100 Subject: [PATCH 01/66] Igore excessive fopen() mode flags for WritableResourceStream --- src/WritableResourceStream.php | 3 ++- tests/DuplexResourceStreamTest.php | 30 ++++++++++++++++++++++++++++ tests/ReadableResourceStreamTest.php | 30 ++++++++++++++++++++++++++++ tests/WritableStreamResourceTest.php | 30 ++++++++++++++++++++++++++++ 4 files changed, 92 insertions(+), 1 deletion(-) diff --git a/src/WritableResourceStream.php b/src/WritableResourceStream.php index f43f95c..c2bac4d 100644 --- a/src/WritableResourceStream.php +++ b/src/WritableResourceStream.php @@ -23,8 +23,9 @@ public function __construct($stream, LoopInterface $loop, $writeBufferSoftLimit 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']) && str_replace(array('b', 't'), '', $meta['mode']) === 'r') { + if (isset($meta['mode']) && strtr($meta['mode'], 'waxc+', '.....') === $meta['mode']) { throw new \InvalidArgumentException('Given stream resource is not opened in write mode'); } diff --git a/tests/DuplexResourceStreamTest.php b/tests/DuplexResourceStreamTest.php index 9ea43f9..6a57c5d 100644 --- a/tests/DuplexResourceStreamTest.php +++ b/tests/DuplexResourceStreamTest.php @@ -19,6 +19,21 @@ public function testConstructor() $conn = new DuplexResourceStream($stream, $loop); } + /** + * @covers React\Stream\DuplexResourceStream::__construct + */ + 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 */ @@ -45,6 +60,21 @@ public function testConstructorThrowsExceptionOnWriteOnlyStream() 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 */ diff --git a/tests/ReadableResourceStreamTest.php b/tests/ReadableResourceStreamTest.php index a6909ba..75afd05 100644 --- a/tests/ReadableResourceStreamTest.php +++ b/tests/ReadableResourceStreamTest.php @@ -18,6 +18,21 @@ public function testConstructor() new ReadableResourceStream($stream, $loop); } + /** + * @covers React\Stream\ReadableResourceStream::__construct + */ + 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 */ @@ -44,6 +59,21 @@ public function testConstructorThrowsExceptionOnWriteOnlyStream() 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 */ diff --git a/tests/WritableStreamResourceTest.php b/tests/WritableStreamResourceTest.php index 5f0400e..23da14b 100644 --- a/tests/WritableStreamResourceTest.php +++ b/tests/WritableStreamResourceTest.php @@ -19,6 +19,21 @@ public function testConstructor() $buffer->on('error', $this->expectCallableNever()); } + /** + * @covers React\Stream\WritableResourceStream::__construct + */ + 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 @@ -43,6 +58,21 @@ public function testConstructorThrowsExceptionOnReadOnlyStream() 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 */ From 293828e79540f9ac169cc048b1086c3d258aecbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Thu, 9 Nov 2017 12:18:25 +0100 Subject: [PATCH 02/66] Work around HHVM not reporting fopen mode for pipe streams --- src/WritableResourceStream.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WritableResourceStream.php b/src/WritableResourceStream.php index c2bac4d..7e04205 100644 --- a/src/WritableResourceStream.php +++ b/src/WritableResourceStream.php @@ -25,7 +25,7 @@ public function __construct($stream, LoopInterface $loop, $writeBufferSoftLimit // ensure resource is opened for writing (fopen mode must contain either of "waxc+") $meta = stream_get_meta_data($stream); - if (isset($meta['mode']) && strtr($meta['mode'], 'waxc+', '.....') === $meta['mode']) { + if (isset($meta['mode']) && $meta['mode'] !== '' && strtr($meta['mode'], 'waxc+', '.....') === $meta['mode']) { throw new \InvalidArgumentException('Given stream resource is not opened in write mode'); } From 8b34249d9b6b7211f2614fad13b1af67b51dcc3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Thu, 9 Nov 2017 14:49:31 +0100 Subject: [PATCH 03/66] Ignore Mac OS X test failures for now until Travis tests work again --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 37a14d7..0ca378c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ php: - 5.5 - 5.6 - 7.0 -# - 7.0 # Mac OS X test setup, see below +# - 7.0 # Mac OS X test setup, ignore errors, see below - 7.1 - nightly # ignore errors, see below - hhvm # ignore errors, see below @@ -27,6 +27,7 @@ matrix: allow_failures: - php: nightly - php: hhvm + - os: osx install: # OSX install inspired by https://github.com/kiler129/TravisCI-OSX-PHP From 56f3100214d1274e876f657632913f397bbfd04d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 10 Nov 2017 17:05:37 +0100 Subject: [PATCH 04/66] Replace internal removeStream() with removeReadStream() --- src/DuplexResourceStream.php | 14 ++++---------- src/ReadableResourceStream.php | 14 ++++---------- tests/DuplexResourceStreamTest.php | 17 ++++++++++++++++- tests/ReadableResourceStreamTest.php | 14 ++++++++++++++ 4 files changed, 38 insertions(+), 21 deletions(-) diff --git a/src/DuplexResourceStream.php b/src/DuplexResourceStream.php index 7869eda..92e29b5 100644 --- a/src/DuplexResourceStream.php +++ b/src/DuplexResourceStream.php @@ -131,11 +131,13 @@ public function close() $this->writable = false; $this->emit('close'); - $this->loop->removeStream($this->stream); + $this->pause(); $this->buffer->close(); $this->removeAllListeners(); - $this->handleClose(); + if (is_resource($this->stream)) { + fclose($this->stream); + } } public function end($data = null) @@ -191,14 +193,6 @@ public function handleData($stream) } } - /** @internal */ - public function handleClose() - { - if (is_resource($this->stream)) { - fclose($this->stream); - } - } - /** * Returns whether this is a pipe resource in a legacy environment * diff --git a/src/ReadableResourceStream.php b/src/ReadableResourceStream.php index 6a9cd65..d61e178 100644 --- a/src/ReadableResourceStream.php +++ b/src/ReadableResourceStream.php @@ -105,10 +105,12 @@ public function close() $this->closed = true; $this->emit('close'); - $this->loop->removeStream($this->stream); + $this->pause(); $this->removeAllListeners(); - $this->handleClose(); + if (is_resource($this->stream)) { + fclose($this->stream); + } } /** @internal */ @@ -144,14 +146,6 @@ public function handleData() } } - /** @internal */ - public function handleClose() - { - if (is_resource($this->stream)) { - fclose($this->stream); - } - } - /** * Returns whether this is a pipe resource in a legacy environment * diff --git a/tests/DuplexResourceStreamTest.php b/tests/DuplexResourceStreamTest.php index 9ea43f9..402f85d 100644 --- a/tests/DuplexResourceStreamTest.php +++ b/tests/DuplexResourceStreamTest.php @@ -242,12 +242,27 @@ public function testEndRemovesReadStreamFromLoop() { $stream = fopen('php://temp', 'r+'); $loop = $this->createLoopMock(); - $loop->expects($this->once())->method('removeReadStream'); + $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::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(); + } + public function testEndedStreamsShouldNotWrite() { $file = tempnam(sys_get_temp_dir(), 'reactphptest_'); diff --git a/tests/ReadableResourceStreamTest.php b/tests/ReadableResourceStreamTest.php index a6909ba..5be1e15 100644 --- a/tests/ReadableResourceStreamTest.php +++ b/tests/ReadableResourceStreamTest.php @@ -204,6 +204,20 @@ public function testClosingStreamInDataEventShouldNotTriggerError() $conn->handleData($stream); } + /** + * @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::handleData */ From cf4ace0df32fdc1124090503789fba9380621929 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 10 Nov 2017 17:46:22 +0100 Subject: [PATCH 05/66] Only remove stream from loop if it was actually added --- src/DuplexResourceStream.php | 9 ++++- src/ReadableResourceStream.php | 9 ++++- tests/DuplexResourceStreamTest.php | 58 ++++++++++++++++++++++++++++ tests/ReadableResourceStreamTest.php | 58 ++++++++++++++++++++++++++++ 4 files changed, 130 insertions(+), 4 deletions(-) diff --git a/src/DuplexResourceStream.php b/src/DuplexResourceStream.php index 92e29b5..46f7a21 100644 --- a/src/DuplexResourceStream.php +++ b/src/DuplexResourceStream.php @@ -33,6 +33,7 @@ final class DuplexResourceStream extends EventEmitter implements DuplexStreamInt private $readable = true; private $writable = true; private $closing = false; + private $listening = false; public function __construct($stream, LoopInterface $loop, $readChunkSize = null, WritableStreamInterface $buffer = null) { @@ -100,13 +101,17 @@ public function isWritable() public function pause() { - $this->loop->removeReadStream($this->stream); + if ($this->listening) { + $this->loop->removeReadStream($this->stream); + $this->listening = false; + } } public function resume() { - if ($this->readable) { + if (!$this->listening && $this->readable) { $this->loop->addReadStream($this->stream, array($this, 'handleData')); + $this->listening = true; } } diff --git a/src/ReadableResourceStream.php b/src/ReadableResourceStream.php index d61e178..a0a0339 100644 --- a/src/ReadableResourceStream.php +++ b/src/ReadableResourceStream.php @@ -36,6 +36,7 @@ final class ReadableResourceStream extends EventEmitter implements ReadableStrea private $bufferSize; private $closed = false; + private $listening = false; public function __construct($stream, LoopInterface $loop, $readChunkSize = null) { @@ -81,13 +82,17 @@ public function isReadable() public function pause() { - $this->loop->removeReadStream($this->stream); + if ($this->listening) { + $this->loop->removeReadStream($this->stream); + $this->listening = false; + } } public function resume() { - if (!$this->closed) { + if (!$this->listening && !$this->closed) { $this->loop->addReadStream($this->stream, array($this, 'handleData')); + $this->listening = true; } } diff --git a/tests/DuplexResourceStreamTest.php b/tests/DuplexResourceStreamTest.php index 402f85d..6b63e4c 100644 --- a/tests/DuplexResourceStreamTest.php +++ b/tests/DuplexResourceStreamTest.php @@ -249,6 +249,35 @@ public function testEndRemovesReadStreamFromLoop() $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 */ @@ -263,6 +292,35 @@ public function testCloseRemovesReadStreamFromLoop() $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_'); diff --git a/tests/ReadableResourceStreamTest.php b/tests/ReadableResourceStreamTest.php index 5be1e15..71bbba0 100644 --- a/tests/ReadableResourceStreamTest.php +++ b/tests/ReadableResourceStreamTest.php @@ -204,6 +204,35 @@ public function testClosingStreamInDataEventShouldNotTriggerError() $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 */ @@ -218,6 +247,35 @@ public function testCloseRemovesReadStreamFromLoop() $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 */ From 6e58b163b9ee3843e592116fa760618095893070 Mon Sep 17 00:00:00 2001 From: Gabriel Caruso Date: Sun, 12 Nov 2017 00:25:08 -0200 Subject: [PATCH 06/66] Use PHPUnit\Framework\TestCase instead of PHPUnit_Framework_TestCase --- composer.json | 2 +- tests/TestCase.php | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index d110e2a..84e9766 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "evenement/evenement": "^3.0 || ^2.0 || ^1.0" }, "require-dev": { - "phpunit/phpunit": "^5.0 || ^4.8.10", + "phpunit/phpunit": "^5.7 || ^4.8.35", "clue/stream-filter": "~1.2" }, "suggest": { diff --git a/tests/TestCase.php b/tests/TestCase.php index 63746f9..c8fc1db 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,7 +2,9 @@ namespace React\Tests\Stream; -class TestCase extends \PHPUnit_Framework_TestCase +use PHPUnit\Framework\TestCase as BaseTestCase; + +class TestCase extends BaseTestCase { protected function expectCallableExactly($amount) { From 7a0706b2db140892f36dc5eefa3d13f4d6c9424f Mon Sep 17 00:00:00 2001 From: Gabriel Caruso Date: Sat, 18 Nov 2017 11:45:31 -0200 Subject: [PATCH 07/66] Added PHPUnit 6 support --- composer.json | 2 +- tests/DuplexResourceStreamTest.php | 6 +++--- tests/ReadableResourceStreamTest.php | 6 +++--- tests/WritableStreamResourceTest.php | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 84e9766..6f83a32 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "evenement/evenement": "^3.0 || ^2.0 || ^1.0" }, "require-dev": { - "phpunit/phpunit": "^5.7 || ^4.8.35", + "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35", "clue/stream-filter": "~1.2" }, "suggest": { diff --git a/tests/DuplexResourceStreamTest.php b/tests/DuplexResourceStreamTest.php index 4bc9326..0aba089 100644 --- a/tests/DuplexResourceStreamTest.php +++ b/tests/DuplexResourceStreamTest.php @@ -36,17 +36,18 @@ public function testConstructorWithExcessiveMode() /** * @covers React\Stream\DuplexResourceStream::__construct + * @expectedException InvalidArgumentException */ public function testConstructorThrowsExceptionOnInvalidStream() { $loop = $this->createLoopMock(); - $this->setExpectedException('InvalidArgumentException'); new DuplexResourceStream('breakme', $loop); } /** * @covers React\Stream\DuplexResourceStream::__construct + * @expectedException InvalidArgumentException */ public function testConstructorThrowsExceptionOnWriteOnlyStream() { @@ -56,7 +57,6 @@ public function testConstructorThrowsExceptionOnWriteOnlyStream() $loop = $this->createLoopMock(); - $this->setExpectedException('InvalidArgumentException'); new DuplexResourceStream(STDOUT, $loop); } @@ -77,6 +77,7 @@ public function testConstructorThrowsExceptionOnWriteOnlyStreamWithExcessiveMode /** * @covers React\Stream\DuplexResourceStream::__construct + * @expectedException RunTimeException */ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking() { @@ -87,7 +88,6 @@ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking( $stream = fopen('blocking://test', 'r+'); $loop = $this->createLoopMock(); - $this->setExpectedException('RuntimeException'); new DuplexResourceStream($stream, $loop); } diff --git a/tests/ReadableResourceStreamTest.php b/tests/ReadableResourceStreamTest.php index 64aa101..8a960e1 100644 --- a/tests/ReadableResourceStreamTest.php +++ b/tests/ReadableResourceStreamTest.php @@ -35,17 +35,18 @@ public function testConstructorWithExcessiveMode() /** * @covers React\Stream\ReadableResourceStream::__construct + * @expectedException InvalidArgumentException */ public function testConstructorThrowsExceptionOnInvalidStream() { $loop = $this->createLoopMock(); - $this->setExpectedException('InvalidArgumentException'); new ReadableResourceStream(false, $loop); } /** * @covers React\Stream\ReadableResourceStream::__construct + * @expectedException InvalidArgumentException */ public function testConstructorThrowsExceptionOnWriteOnlyStream() { @@ -55,7 +56,6 @@ public function testConstructorThrowsExceptionOnWriteOnlyStream() $loop = $this->createLoopMock(); - $this->setExpectedException('InvalidArgumentException'); new ReadableResourceStream(STDOUT, $loop); } @@ -76,6 +76,7 @@ public function testConstructorThrowsExceptionOnWriteOnlyStreamWithExcessiveMode /** * @covers React\Stream\ReadableResourceStream::__construct + * @expectedException RuntimeException */ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking() { @@ -86,7 +87,6 @@ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking( $stream = fopen('blocking://test', 'r+'); $loop = $this->createLoopMock(); - $this->setExpectedException('RuntimeException'); new ReadableResourceStream($stream, $loop); } diff --git a/tests/WritableStreamResourceTest.php b/tests/WritableStreamResourceTest.php index 23da14b..fdf69c3 100644 --- a/tests/WritableStreamResourceTest.php +++ b/tests/WritableStreamResourceTest.php @@ -75,6 +75,7 @@ public function testConstructorThrowsExceptionOnReadOnlyStreamWithExcessiveMode( /** * @covers React\Stream\WritableResourceStream::__construct + * @expectedException RuntimeException */ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking() { @@ -85,7 +86,6 @@ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking( $stream = fopen('blocking://test', 'r+'); $loop = $this->createLoopMock(); - $this->setExpectedException('RuntimeException'); new WritableResourceStream($stream, $loop); } From 195515b1558b34bf98002fa77e6176817453bf9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sun, 19 Nov 2017 14:11:38 +0100 Subject: [PATCH 08/66] Add documentation for examples --- examples/benchmark-throughput.php | 16 ++++++++++++++++ examples/cat.php | 13 +++++++++++++ 2 files changed, 29 insertions(+) diff --git a/examples/benchmark-throughput.php b/examples/benchmark-throughput.php index 477e4fc..0cda38c 100644 --- a/examples/benchmark-throughput.php +++ b/examples/benchmark-throughput.php @@ -1,7 +1,23 @@ Date: Sun, 19 Nov 2017 14:14:05 +0100 Subject: [PATCH 09/66] Add simple HTTP client example --- examples/http.php | 40 ++++++++++++++++++++++++++++++++++++++++ examples/https.php | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 examples/http.php create mode 100644 examples/https.php diff --git a/examples/http.php b/examples/http.php new file mode 100644 index 0000000..ad9f434 --- /dev/null +++ b/examples/http.php @@ -0,0 +1,40 @@ +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(); diff --git a/examples/https.php b/examples/https.php new file mode 100644 index 0000000..8518ad7 --- /dev/null +++ b/examples/https.php @@ -0,0 +1,40 @@ +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(); From f62f6dc5cbff6210cd2f350628372e7c380ced5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sun, 19 Nov 2017 14:15:29 +0100 Subject: [PATCH 10/66] Restructure examples to ease getting started --- .travis.yml | 2 +- examples/{http.php => 01-http.php} | 4 ++-- examples/{https.php => 02-https.php} | 4 ++-- examples/{cat.php => 11-cat.php} | 6 +++--- ...benchmark-throughput.php => 91-benchmark-throughput.php} | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) rename examples/{http.php => 01-http.php} (93%) rename examples/{https.php => 02-https.php} (93%) rename examples/{cat.php => 11-cat.php} (85%) rename examples/{benchmark-throughput.php => 91-benchmark-throughput.php} (92%) diff --git a/.travis.yml b/.travis.yml index 0ca378c..234249c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -46,4 +46,4 @@ install: script: - vendor/bin/phpunit --coverage-text - - time php examples/benchmark-throughput.php + - time php examples/91-benchmark-throughput.php diff --git a/examples/http.php b/examples/01-http.php similarity index 93% rename from examples/http.php rename to examples/01-http.php index ad9f434..3687f7c 100644 --- a/examples/http.php +++ b/examples/01-http.php @@ -8,8 +8,8 @@ // This simple example only accepts an optional host parameter to send the // request to. // -// $ php examples/http.php -// $ php examples/http.php reactphp.org +// $ php examples/01-http.php +// $ php examples/01-http.php reactphp.org use React\EventLoop\Factory; use React\Stream\DuplexResourceStream; diff --git a/examples/https.php b/examples/02-https.php similarity index 93% rename from examples/https.php rename to examples/02-https.php index 8518ad7..163f7c8 100644 --- a/examples/https.php +++ b/examples/02-https.php @@ -8,8 +8,8 @@ // This simple example only accepts an optional host parameter to send the // request to. // -// $ php examples/https.php -// $ php examples/https.php reactphp.org +// $ php examples/02-https.php +// $ php examples/02-https.php reactphp.org use React\EventLoop\Factory; use React\Stream\DuplexResourceStream; diff --git a/examples/cat.php b/examples/11-cat.php similarity index 85% rename from examples/cat.php rename to examples/11-cat.php index f918a05..90fadc0 100644 --- a/examples/cat.php +++ b/examples/11-cat.php @@ -4,9 +4,9 @@ // 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/cat.php -// $ php examples/cat.php < README.md -// $ echo hello | php examples/cat.php +// $ 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; diff --git a/examples/benchmark-throughput.php b/examples/91-benchmark-throughput.php similarity index 92% rename from examples/benchmark-throughput.php rename to examples/91-benchmark-throughput.php index 0cda38c..ecf695c 100644 --- a/examples/benchmark-throughput.php +++ b/examples/91-benchmark-throughput.php @@ -7,9 +7,9 @@ // 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/benchmark-throughput.php -// $ php examples/benchmark-throughput.php -t 10 -o zero.bin -// $ php examples/benchmark-throughput.php -t 60 -i zero.bin +// $ 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'; From a41bbffdda5e3f4282012f8e346cef48ecb28b24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 20 Nov 2017 12:55:48 +0100 Subject: [PATCH 11/66] Prepare v0.7.5 release --- CHANGELOG.md | 15 +++++++++++++++ README.md | 9 +++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee2ba60..7154748 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## 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 diff --git a/README.md b/README.md index 6f1a6bb..8735f5a 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,12 @@ Event-driven readable and writable streams for non-blocking I/O in [ReactPHP](https://reactphp.org/). In order to make the [EventLoop](https://github.com/reactphp/event-loop) -easier to use, this component introduces the concept of "streams". +easier to use, this component introduces the powerful concept of "streams". +Streams allow you to efficiently process huge amounts of data (such as a multi +Gigabyte file download) in small chunks without having to store everything in +memory at once. They are very similar to the streams found in PHP itself, but have an interface more suited for async, non-blocking I/O. -Mainly it provides interfaces for readable and writable streams, plus a file -descriptor based implementation with an in-memory write buffer. **Table of contents** @@ -1177,7 +1178,7 @@ The recommended way to install this library is [through Composer](https://getcom This will install the latest supported version: ```bash -$ composer require react/stream:^0.7.4 +$ composer require react/stream:^0.7.5 ``` More details about version upgrades can be found in the [CHANGELOG](CHANGELOG.md). From 33381e408f5483fd81a7e72f7f3e1c331091c115 Mon Sep 17 00:00:00 2001 From: Gabriel Caruso Date: Fri, 1 Dec 2017 11:02:29 -0200 Subject: [PATCH 12/66] Test against PHP 7.2 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 234249c..f4e3376 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ php: - 7.0 # - 7.0 # Mac OS X test setup, ignore errors, see below - 7.1 + - 7.2 - nightly # ignore errors, see below - hhvm # ignore errors, see below From 8a394f00b9972b862aa3c6ef3bacb37766c9580b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 20 Dec 2017 17:59:34 +0100 Subject: [PATCH 13/66] Work around reading from unbuffered pipe stream in legacy PHP --- src/DuplexResourceStream.php | 8 ++++++-- src/ReadableResourceStream.php | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/DuplexResourceStream.php b/src/DuplexResourceStream.php index 46f7a21..982ebb0 100644 --- a/src/DuplexResourceStream.php +++ b/src/DuplexResourceStream.php @@ -60,7 +60,7 @@ public function __construct($stream, LoopInterface $loop, $readChunkSize = null, // 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 < 5.4 causes SEGFAULTs on unbuffered pipe streams, so skip this. + // 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); } @@ -201,14 +201,18 @@ public function handleData($stream) /** * 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 < 50400) { + 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') { diff --git a/src/ReadableResourceStream.php b/src/ReadableResourceStream.php index a0a0339..015a96b 100644 --- a/src/ReadableResourceStream.php +++ b/src/ReadableResourceStream.php @@ -63,7 +63,7 @@ public function __construct($stream, LoopInterface $loop, $readChunkSize = null) // 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 < 5.4 causes SEGFAULTs on unbuffered pipe streams, so skip this. + // 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); } @@ -154,14 +154,18 @@ public function handleData() /** * 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 < 50400) { + 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') { From 031699e73847ffc7e2f0f2f13ddb016a3cb721fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Thu, 21 Dec 2017 13:51:49 +0100 Subject: [PATCH 14/66] Simplify test bootstrapping logic via Composer --- composer.json | 8 +++++--- phpunit.xml.dist | 2 +- tests/bootstrap.php | 7 ------- 3 files changed, 6 insertions(+), 11 deletions(-) delete mode 100644 tests/bootstrap.php diff --git a/composer.json b/composer.json index 6f83a32..b5e2f8a 100644 --- a/composer.json +++ b/composer.json @@ -12,12 +12,14 @@ "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35", "clue/stream-filter": "~1.2" }, - "suggest": { - "react/event-loop": "^0.4" - }, "autoload": { "psr-4": { "React\\Stream\\": "src" } + }, + "autoload-dev": { + "psr-4": { + "React\\Tests\\Stream\\": "tests" + } } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index cba6d4d..13d3fab 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -9,7 +9,7 @@ processIsolation="false" stopOnFailure="false" syntaxCheck="false" - bootstrap="tests/bootstrap.php" + bootstrap="vendor/autoload.php" > diff --git a/tests/bootstrap.php b/tests/bootstrap.php deleted file mode 100644 index 754ef06..0000000 --- a/tests/bootstrap.php +++ /dev/null @@ -1,7 +0,0 @@ -addPsr4('React\\Tests\\Stream\\', __DIR__); From 4e07a0014896cbbb73e2f2b2c28e86174b6e1d4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Thu, 21 Dec 2017 15:12:01 +0100 Subject: [PATCH 15/66] Prepare v0.7.6 release --- CHANGELOG.md | 9 +++++++++ README.md | 6 +++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7154748..2025b56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 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` diff --git a/README.md b/README.md index 8735f5a..9ad33bf 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Stream Component +# Stream [![Build Status](https://travis-ci.org/reactphp/stream.svg?branch=master)](https://travis-ci.org/reactphp/stream) @@ -1178,10 +1178,10 @@ The recommended way to install this library is [through Composer](https://getcom This will install the latest supported version: ```bash -$ composer require react/stream:^0.7.5 +$ composer require react/stream:^0.7.6 ``` -More details about version upgrades can be found in the [CHANGELOG](CHANGELOG.md). +See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. This project aims to run on any platform and thus does not require any PHP extensions and supports running on legacy PHP 5.3 through current PHP 7+ and HHVM. From 279ffd155b6fe8a24f75a7d97b324f00d3cc20d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 22 Dec 2017 15:32:26 +0100 Subject: [PATCH 16/66] Fix forward compatibility with upcoming EventLoop releases --- composer.json | 2 +- tests/DuplexResourceStreamIntegrationTest.php | 58 ++++++++++++++++--- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index b5e2f8a..f6faa66 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,7 @@ "license": "MIT", "require": { "php": ">=5.3.8", - "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3", + "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5", "evenement/evenement": "^3.0 || ^2.0 || ^1.0" }, "require-dev": { diff --git a/tests/DuplexResourceStreamIntegrationTest.php b/tests/DuplexResourceStreamIntegrationTest.php index f7f2860..4c2243c 100644 --- a/tests/DuplexResourceStreamIntegrationTest.php +++ b/tests/DuplexResourceStreamIntegrationTest.php @@ -3,18 +3,52 @@ namespace React\Tests\Stream; use React\Stream\DuplexResourceStream; -use React\EventLoop as rel; 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 rel\StreamSelectLoop; }), - array(function() { return function_exists('event_base_new'); }, function() { return new rel\LibEventLoop; }), - array(function() { return class_exists('libev\EventLoop'); }, function() { return new rel\LibEvLoop; }), - array(function() { return class_exists('EventBase'); }, function() { return new rel\ExtEventLoop; }) + 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() : 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(); + } + ) ); } @@ -44,9 +78,9 @@ public function testBufferReadsLargeChunks($condition, $loopFactory) $streamA->write($testString); - $loop->tick(); - $loop->tick(); - $loop->tick(); + $this->loopTick($loop); + $this->loopTick($loop); + $this->loopTick($loop); $streamA->close(); $streamB->close(); @@ -307,4 +341,12 @@ public function testReadsNothingFromProcessPipeWithNoOutput($condition, $loopFac $loop->run(); } + + private function loopTick(LoopInterface $loop) + { + $loop->addTimer(0, function () use ($loop) { + $loop->stop(); + }); + $loop->run(); + } } From f85ff19f2c96a8a1789bdc23bd6d876f045e1eb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 19 Jan 2018 12:16:04 +0100 Subject: [PATCH 17/66] Reduce traffic for functional tests to max 50KB per test --- tests/FunctionalInternetTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/FunctionalInternetTest.php b/tests/FunctionalInternetTest.php index fdbec53..773265a 100644 --- a/tests/FunctionalInternetTest.php +++ b/tests/FunctionalInternetTest.php @@ -2,8 +2,8 @@ namespace React\Tests\Stream; -use React\Stream\DuplexResourceStream; use React\EventLoop\Factory; +use React\Stream\DuplexResourceStream; use React\Stream\WritableResourceStream; /** @@ -35,7 +35,7 @@ public function testUploadKilobytePlain() public function testUploadBiggerBlockPlain() { - $size = 1000 * 30; + $size = 50 * 1000; $stream = stream_socket_client('tcp://httpbin.org:80'); $loop = Factory::create(); @@ -79,7 +79,7 @@ public function testUploadKilobyteSecure() public function testUploadBiggerBlockSecureRequiresSmallerChunkSize() { - $size = 1000 * 30000; + $size = 50 * 1000; $stream = stream_socket_client('tls://httpbin.org:443'); $loop = Factory::create(); From ef87054fd0031929a3468b0d042985ec7205b1f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 19 Jan 2018 12:24:53 +0100 Subject: [PATCH 18/66] Apply timeout for integration tests relying on internet connection --- README.md | 8 ++++++++ tests/FunctionalInternetTest.php | 24 ++++++++++++++++++++---- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9ad33bf..a0ed336 100644 --- a/README.md +++ b/README.md @@ -1203,6 +1203,14 @@ To run the test suite, go to the project root and run: $ php vendor/bin/phpunit ``` +The test suite also contains a number of functional integration tests that rely +on a stable internet connection. +If you do not want to run these, they can simply be skipped like this: + +```bash +$ php vendor/bin/phpunit --exclude-group internet +``` + ## License MIT, see [LICENSE file](LICENSE). diff --git a/tests/FunctionalInternetTest.php b/tests/FunctionalInternetTest.php index 773265a..4d31e8e 100644 --- a/tests/FunctionalInternetTest.php +++ b/tests/FunctionalInternetTest.php @@ -3,6 +3,7 @@ namespace React\Tests\Stream; use React\EventLoop\Factory; +use React\EventLoop\LoopInterface; use React\Stream\DuplexResourceStream; use React\Stream\WritableResourceStream; @@ -28,7 +29,7 @@ public function testUploadKilobytePlain() $stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size)); - $loop->run(); + $this->awaitStreamClose($stream, $loop); $this->assertNotEquals('', $buffer); } @@ -50,7 +51,7 @@ public function testUploadBiggerBlockPlain() $stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size)); - $loop->run(); + $this->awaitStreamClose($stream, $loop); $this->assertNotEquals('', $buffer); } @@ -72,7 +73,7 @@ public function testUploadKilobyteSecure() $stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size)); - $loop->run(); + $this->awaitStreamClose($stream, $loop); $this->assertNotEquals('', $buffer); } @@ -99,8 +100,23 @@ public function testUploadBiggerBlockSecureRequiresSmallerChunkSize() $stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size)); - $loop->run(); + $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(); + } } From d98ba35f34f6fedd8674db963f52a68bd5b1cbc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 19 Jan 2018 12:50:38 +0100 Subject: [PATCH 19/66] Fix testing against legacy LibEventLoop --- tests/DuplexResourceStreamIntegrationTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/DuplexResourceStreamIntegrationTest.php b/tests/DuplexResourceStreamIntegrationTest.php index 4c2243c..fb5f02a 100644 --- a/tests/DuplexResourceStreamIntegrationTest.php +++ b/tests/DuplexResourceStreamIntegrationTest.php @@ -30,7 +30,7 @@ function () { return function_exists('event_base_new'); }, function () { - return class_exists('React\EventLoop\ExtLibeventLoop') ? new ExtLibeventLoop() : LibEventLoop(); + return class_exists('React\EventLoop\ExtLibeventLoop') ? new ExtLibeventLoop() : new LibEventLoop(); } ), array( From 63112d6046d828b0332cf23d9c259aaf7c6033ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 19 Jan 2018 12:41:26 +0100 Subject: [PATCH 20/66] Avoid warnings about risky tests --- tests/DuplexResourceStreamTest.php | 6 +++++- tests/ReadableResourceStreamTest.php | 3 +++ tests/WritableStreamResourceTest.php | 5 +++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/tests/DuplexResourceStreamTest.php b/tests/DuplexResourceStreamTest.php index 0aba089..3212ae8 100644 --- a/tests/DuplexResourceStreamTest.php +++ b/tests/DuplexResourceStreamTest.php @@ -10,17 +10,19 @@ class DuplexResourceStreamTest extends TestCase { /** * @covers React\Stream\DuplexResourceStream::__construct + * @doesNotPerformAssertions */ public function testConstructor() { $stream = fopen('php://temp', 'r+'); $loop = $this->createLoopMock(); - $conn = new DuplexResourceStream($stream, $loop); + new DuplexResourceStream($stream, $loop); } /** * @covers React\Stream\DuplexResourceStream::__construct + * @doesNotPerformAssertions */ public function testConstructorWithExcessiveMode() { @@ -93,6 +95,7 @@ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking( /** * @covers React\Stream\DuplexResourceStream::__construct + * @doesNotPerformAssertions */ public function testConstructorAcceptsBuffer() { @@ -405,6 +408,7 @@ public function testClosingStreamInDataEventShouldNotTriggerError() $loop = $this->createLoopMock(); $conn = new DuplexResourceStream($stream, $loop); + $conn->on('error', $this->expectCallableNever()); $conn->on('data', function ($data) use ($conn) { $conn->close(); }); diff --git a/tests/ReadableResourceStreamTest.php b/tests/ReadableResourceStreamTest.php index 8a960e1..20da96f 100644 --- a/tests/ReadableResourceStreamTest.php +++ b/tests/ReadableResourceStreamTest.php @@ -9,6 +9,7 @@ class ReadableResourceStreamTest extends TestCase { /** * @covers React\Stream\ReadableResourceStream::__construct + * @doesNotPerformAssertions */ public function testConstructor() { @@ -20,6 +21,7 @@ public function testConstructor() /** * @covers React\Stream\ReadableResourceStream::__construct + * @doesNotPerformAssertions */ public function testConstructorWithExcessiveMode() { @@ -224,6 +226,7 @@ public function testClosingStreamInDataEventShouldNotTriggerError() $loop = $this->createLoopMock(); $conn = new ReadableResourceStream($stream, $loop); + $conn->on('error', $this->expectCallableNever()); $conn->on('data', function ($data) use ($conn) { $conn->close(); }); diff --git a/tests/WritableStreamResourceTest.php b/tests/WritableStreamResourceTest.php index fdf69c3..05bce9c 100644 --- a/tests/WritableStreamResourceTest.php +++ b/tests/WritableStreamResourceTest.php @@ -9,18 +9,19 @@ class WritableResourceStreamTest extends TestCase { /** * @covers React\Stream\WritableResourceStream::__construct + * @doesNotPerformAssertions */ public function testConstructor() { $stream = fopen('php://temp', 'r+'); $loop = $this->createLoopMock(); - $buffer = new WritableResourceStream($stream, $loop); - $buffer->on('error', $this->expectCallableNever()); + new WritableResourceStream($stream, $loop); } /** * @covers React\Stream\WritableResourceStream::__construct + * @doesNotPerformAssertions */ public function testConstructorWithExcessiveMode() { From 10100896018fd847a257cd81143b8e1b7be08e40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 19 Jan 2018 16:04:38 +0100 Subject: [PATCH 21/66] Prepare v0.7.7 release --- CHANGELOG.md | 7 +++++++ README.md | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2025b56..f64815d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 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 diff --git a/README.md b/README.md index a0ed336..c362534 100644 --- a/README.md +++ b/README.md @@ -1178,7 +1178,7 @@ The recommended way to install this library is [through Composer](https://getcom This will install the latest supported version: ```bash -$ composer require react/stream:^0.7.6 +$ composer require react/stream:^0.7.7 ``` See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. From fdd0140f42805d65bf9687636503db0b326d2244 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 11 Jul 2018 16:38:16 +0200 Subject: [PATCH 22/66] Prepare v1.0.0 release --- CHANGELOG.md | 12 ++++++++++++ README.md | 3 ++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f64815d..58aebca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 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 @@ -129,6 +138,9 @@ * 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) diff --git a/README.md b/README.md index c362534..b5bc907 100644 --- a/README.md +++ b/README.md @@ -1175,10 +1175,11 @@ $loop->run(); The recommended way to install this library is [through Composer](https://getcomposer.org). [New to Composer?](https://getcomposer.org/doc/00-intro.md) +This project follows [SemVer](https://semver.org/). This will install the latest supported version: ```bash -$ composer require react/stream:^0.7.7 +$ composer require react/stream:^1.0 ``` See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. From aea4ab49d506b3f6ff55dd4455e04a91923caf76 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Thu, 18 Oct 2018 22:55:48 +0200 Subject: [PATCH 23/66] Prefix all global functions calls with \ to skip the look up and resolve process and go straight to the global function --- src/DuplexResourceStream.php | 26 +++++++++++++------------- src/ReadableResourceStream.php | 26 +++++++++++++------------- src/ThroughStream.php | 4 ++-- src/Util.php | 2 +- src/WritableResourceStream.php | 22 +++++++++++----------- 5 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/DuplexResourceStream.php b/src/DuplexResourceStream.php index 982ebb0..cf9a894 100644 --- a/src/DuplexResourceStream.php +++ b/src/DuplexResourceStream.php @@ -37,19 +37,19 @@ final class DuplexResourceStream extends EventEmitter implements DuplexStreamInt public function __construct($stream, LoopInterface $loop, $readChunkSize = null, WritableStreamInterface $buffer = null) { - if (!is_resource($stream) || get_resource_type($stream) !== "stream") { + 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) { + $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) { + if (\stream_set_blocking($stream, 0) !== true) { throw new \RuntimeException('Unable to set stream resource to non-blocking mode'); } @@ -61,8 +61,8 @@ public function __construct($stream, LoopInterface $loop, $readChunkSize = null, // 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 (\function_exists('stream_set_read_buffer') && !$this->isLegacyPipe($stream)) { + \stream_set_read_buffer($stream, 0); } if ($buffer === null) { @@ -140,8 +140,8 @@ public function close() $this->buffer->close(); $this->removeAllListeners(); - if (is_resource($this->stream)) { - fclose($this->stream); + if (\is_resource($this->stream)) { + \fclose($this->stream); } } @@ -169,7 +169,7 @@ public function pipe(WritableStreamInterface $dest, array $options = array()) public function handleData($stream) { $error = null; - set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) { + \set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) { $error = new \ErrorException( $errstr, 0, @@ -179,9 +179,9 @@ public function handleData($stream) ); }); - $data = stream_get_contents($stream, $this->bufferSize); + $data = \stream_get_contents($stream, $this->bufferSize); - restore_error_handler(); + \restore_error_handler(); if ($error !== null) { $this->emit('error', array(new \RuntimeException('Unable to read from stream: ' . $error->getMessage(), 0, $error))); @@ -212,8 +212,8 @@ public function handleData($stream) */ private function isLegacyPipe($resource) { - if (PHP_VERSION_ID < 50428 || (PHP_VERSION_ID >= 50500 && PHP_VERSION_ID < 50512)) { - $meta = stream_get_meta_data($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; diff --git a/src/ReadableResourceStream.php b/src/ReadableResourceStream.php index 015a96b..231739b 100644 --- a/src/ReadableResourceStream.php +++ b/src/ReadableResourceStream.php @@ -40,19 +40,19 @@ final class ReadableResourceStream extends EventEmitter implements ReadableStrea public function __construct($stream, LoopInterface $loop, $readChunkSize = null) { - if (!is_resource($stream) || get_resource_type($stream) !== "stream") { + 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'], '+')) { + $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) { + if (\stream_set_blocking($stream, 0) !== true) { throw new \RuntimeException('Unable to set stream resource to non-blocking mode'); } @@ -64,8 +64,8 @@ public function __construct($stream, LoopInterface $loop, $readChunkSize = null) // 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 (\function_exists('stream_set_read_buffer') && !$this->isLegacyPipe($stream)) { + \stream_set_read_buffer($stream, 0); } $this->stream = $stream; @@ -113,8 +113,8 @@ public function close() $this->pause(); $this->removeAllListeners(); - if (is_resource($this->stream)) { - fclose($this->stream); + if (\is_resource($this->stream)) { + \fclose($this->stream); } } @@ -122,7 +122,7 @@ public function close() public function handleData() { $error = null; - set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) { + \set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) { $error = new \ErrorException( $errstr, 0, @@ -132,9 +132,9 @@ public function handleData() ); }); - $data = stream_get_contents($this->stream, $this->bufferSize); + $data = \stream_get_contents($this->stream, $this->bufferSize); - restore_error_handler(); + \restore_error_handler(); if ($error !== null) { $this->emit('error', array(new \RuntimeException('Unable to read from stream: ' . $error->getMessage(), 0, $error))); @@ -165,8 +165,8 @@ public function handleData() */ private function isLegacyPipe($resource) { - if (PHP_VERSION_ID < 50428 || (PHP_VERSION_ID >= 50500 && PHP_VERSION_ID < 50512)) { - $meta = stream_get_meta_data($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; diff --git a/src/ThroughStream.php b/src/ThroughStream.php index da2fbb0..6f73fb8 100644 --- a/src/ThroughStream.php +++ b/src/ThroughStream.php @@ -84,7 +84,7 @@ final class ThroughStream extends EventEmitter implements DuplexStreamInterface public function __construct($callback = null) { - if ($callback !== null && !is_callable($callback)) { + if ($callback !== null && !\is_callable($callback)) { throw new InvalidArgumentException('Invalid transformation callback given'); } @@ -128,7 +128,7 @@ public function write($data) if ($this->callback !== null) { try { - $data = call_user_func($this->callback, $data); + $data = \call_user_func($this->callback, $data); } catch (\Exception $e) { $this->emit('error', array($e)); $this->close(); diff --git a/src/Util.php b/src/Util.php index 14ddcfc..056b037 100644 --- a/src/Util.php +++ b/src/Util.php @@ -68,7 +68,7 @@ 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()); + $target->emit($event, \func_get_args()); }); } } diff --git a/src/WritableResourceStream.php b/src/WritableResourceStream.php index 7e04205..57c09b2 100644 --- a/src/WritableResourceStream.php +++ b/src/WritableResourceStream.php @@ -19,19 +19,19 @@ final class WritableResourceStream extends EventEmitter implements WritableStrea public function __construct($stream, LoopInterface $loop, $writeBufferSoftLimit = null, $writeChunkSize = null) { - if (!is_resource($stream) || get_resource_type($stream) !== "stream") { + 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']) { + $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) { + if (\stream_set_blocking($stream, 0) !== true) { throw new \RuntimeException('Unable to set stream resource to non-blocking mode'); } @@ -96,8 +96,8 @@ public function close() $this->emit('close'); $this->removeAllListeners(); - if (is_resource($this->stream)) { - fclose($this->stream); + if (\is_resource($this->stream)) { + \fclose($this->stream); } } @@ -105,7 +105,7 @@ public function close() public function handleWrite() { $error = null; - set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) { + \set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) { $error = array( 'message' => $errstr, 'number' => $errno, @@ -115,12 +115,12 @@ public function handleWrite() }); if ($this->writeChunkSize === -1) { - $sent = fwrite($this->stream, $this->data); + $sent = \fwrite($this->stream, $this->data); } else { - $sent = fwrite($this->stream, $this->data, $this->writeChunkSize); + $sent = \fwrite($this->stream, $this->data, $this->writeChunkSize); } - restore_error_handler(); + \restore_error_handler(); // Only report errors if *nothing* could be sent. // Any hard (permanent) error will fail to send any data at all. @@ -147,7 +147,7 @@ public function handleWrite() } $exceeded = isset($this->data[$this->softLimit - 1]); - $this->data = (string) substr($this->data, $sent); + $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])) { From 4302b6bc738a29206ed2a557f58cd399e4ce43f0 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Fri, 26 Oct 2018 17:28:52 +0200 Subject: [PATCH 24/66] Test against PHP 7.3 on travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index f4e3376..9ff354b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ php: # - 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 From 5b9d87dfdd217fe38dba12065d5d840b1aecc45e Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Sat, 24 Nov 2018 18:19:31 +0100 Subject: [PATCH 25/66] Only close stream when the we reached end of the stream --- src/DuplexResourceStream.php | 2 +- src/ReadableResourceStream.php | 2 +- tests/DuplexResourceStreamIntegrationTest.php | 38 +++++++++++++++++++ tests/ReadableResourceStreamTest.php | 19 ++++++++++ 4 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/DuplexResourceStream.php b/src/DuplexResourceStream.php index cf9a894..5f038c6 100644 --- a/src/DuplexResourceStream.php +++ b/src/DuplexResourceStream.php @@ -191,7 +191,7 @@ public function handleData($stream) if ($data !== '') { $this->emit('data', array($data)); - } else{ + } elseif (\feof($this->stream)) { // no data read => we reached the end and close the stream $this->emit('end'); $this->close(); diff --git a/src/ReadableResourceStream.php b/src/ReadableResourceStream.php index 231739b..461f6e4 100644 --- a/src/ReadableResourceStream.php +++ b/src/ReadableResourceStream.php @@ -144,7 +144,7 @@ public function handleData() if ($data !== '') { $this->emit('data', array($data)); - } else{ + } elseif (\feof($this->stream)) { // no data read => we reached the end and close the stream $this->emit('end'); $this->close(); diff --git a/tests/DuplexResourceStreamIntegrationTest.php b/tests/DuplexResourceStreamIntegrationTest.php index fb5f02a..7135e15 100644 --- a/tests/DuplexResourceStreamIntegrationTest.php +++ b/tests/DuplexResourceStreamIntegrationTest.php @@ -2,6 +2,7 @@ namespace React\Tests\Stream; +use Clue\StreamFilter as Filter; use React\Stream\DuplexResourceStream; use React\Stream\ReadableResourceStream; use React\EventLoop\ExtEventLoop; @@ -342,6 +343,43 @@ public function testReadsNothingFromProcessPipeWithNoOutput($condition, $loopFac $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) { diff --git a/tests/ReadableResourceStreamTest.php b/tests/ReadableResourceStreamTest.php index 20da96f..7566f92 100644 --- a/tests/ReadableResourceStreamTest.php +++ b/tests/ReadableResourceStreamTest.php @@ -365,6 +365,25 @@ public function testDataErrorShouldEmitErrorAndClose() $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(); From 50426855f7a77ddf43b9266c22320df5bf6c6ce6 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Tue, 1 Jan 2019 17:15:09 +0100 Subject: [PATCH 26/66] Prepare v1.1.0 release --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58aebca..c5a7dd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # 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/). From 07140bafc6a5a0c29bb07cf2b6985f6f80024028 Mon Sep 17 00:00:00 2001 From: Florian Engelhardt Date: Tue, 14 May 2019 13:51:34 +0200 Subject: [PATCH 27/66] fix phpstan error output --- src/CompositeStream.php | 3 ++- src/DuplexResourceStream.php | 2 +- src/ReadableResourceStream.php | 2 +- src/WritableResourceStream.php | 10 +++++++++- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/CompositeStream.php b/src/CompositeStream.php index 153f2a3..dde091d 100644 --- a/src/CompositeStream.php +++ b/src/CompositeStream.php @@ -16,7 +16,8 @@ public function __construct(ReadableStreamInterface $readable, WritableStreamInt $this->writable = $writable; if (!$readable->isReadable() || !$writable->isWritable()) { - return $this->close(); + $this->close(); + return; } Util::forwardEvents($this->readable, $this, array('data', 'end', 'error')); diff --git a/src/DuplexResourceStream.php b/src/DuplexResourceStream.php index 5f038c6..c8c1c50 100644 --- a/src/DuplexResourceStream.php +++ b/src/DuplexResourceStream.php @@ -49,7 +49,7 @@ public function __construct($stream, LoopInterface $loop, $readChunkSize = null, // 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) { + if (\stream_set_blocking($stream, false) !== true) { throw new \RuntimeException('Unable to set stream resource to non-blocking mode'); } diff --git a/src/ReadableResourceStream.php b/src/ReadableResourceStream.php index 461f6e4..9d9c006 100644 --- a/src/ReadableResourceStream.php +++ b/src/ReadableResourceStream.php @@ -52,7 +52,7 @@ public function __construct($stream, LoopInterface $loop, $readChunkSize = null) // 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) { + if (\stream_set_blocking($stream, false) !== true) { throw new \RuntimeException('Unable to set stream resource to non-blocking mode'); } diff --git a/src/WritableResourceStream.php b/src/WritableResourceStream.php index 57c09b2..ff726cc 100644 --- a/src/WritableResourceStream.php +++ b/src/WritableResourceStream.php @@ -9,7 +9,15 @@ final class WritableResourceStream extends EventEmitter implements WritableStrea { private $stream; private $loop; + + /** + * @var int + */ private $softLimit; + + /** + * @var int + */ private $writeChunkSize; private $listening = false; @@ -31,7 +39,7 @@ public function __construct($stream, LoopInterface $loop, $writeBufferSoftLimit // 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) { + if (\stream_set_blocking($stream, false) !== true) { throw new \RuntimeException('Unable to set stream resource to non-blocking mode'); } From 575e8872be76a7545729eb4b9b3aad1b0699a4c2 Mon Sep 17 00:00:00 2001 From: Reedy Date: Fri, 20 Dec 2019 15:14:13 +0000 Subject: [PATCH 28/66] Add .gitattributes to exclude dev files from exports --- .gitattributes | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..f2f51dd --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +/.gitattributes export-ignore +/.gitignore export-ignore +/.travis.yml export-ignore +/examples export-ignore +/phpunit.xml.dist export-ignore +/tests export-ignore From 860446f36333203aa0f97b59776cf558c6dba7bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Thu, 9 Jan 2020 13:28:59 +0100 Subject: [PATCH 29/66] Run tests on PHP 7.4 and simplify test matrix --- .travis.yml | 28 ++++++++++++---------------- composer.json | 2 +- phpunit.xml.dist | 1 - 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9ff354b..e840ca7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,18 +1,5 @@ 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 @@ -20,15 +7,24 @@ matrix: include: - php: 5.3 dist: precise - include: + - php: 5.4 + - php: 5.5 + - php: 5.6 + - php: 7.0 + - php: 7.1 + - php: 7.2 + - php: 7.3 + - php: 7.4 + - php: hhvm-3.18 + install: + - composer require phpunit/phpunit:^5 --dev --no-interaction # requires legacy phpunit - os: osx language: generic php: 7.0 # just to look right on travis env: - PACKAGE: php70 allow_failures: - - php: nightly - - php: hhvm + - php: hhvm-3.18 - os: osx install: diff --git a/composer.json b/composer.json index f6faa66..4bf0c4f 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "evenement/evenement": "^3.0 || ^2.0 || ^1.0" }, "require-dev": { - "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35", + "phpunit/phpunit": "^7.0 || ^6.4 || ^5.7 || ^4.8.35", "clue/stream-filter": "~1.2" }, "autoload": { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 13d3fab..04d426b 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -8,7 +8,6 @@ convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" - syntaxCheck="false" bootstrap="vendor/autoload.php" > From 63853824e54712f240333e127d04c1b6f30c2013 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Thu, 30 Apr 2020 12:24:42 +0200 Subject: [PATCH 30/66] Reproduce faulty write buffer behavior on Mac OS X --- tests/FunctionalInternetTest.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/FunctionalInternetTest.php b/tests/FunctionalInternetTest.php index 4d31e8e..f27c5f9 100644 --- a/tests/FunctionalInternetTest.php +++ b/tests/FunctionalInternetTest.php @@ -78,11 +78,20 @@ public function testUploadKilobyteSecure() $this->assertNotEquals('', $buffer); } - public function testUploadBiggerBlockSecureRequiresSmallerChunkSize() + public function testUploadBiggerBlockSecure() { - $size = 50 * 1000; + // A few dozen kilobytes should be enough to verify this works. + // Underlying buffer sizes are platform-specific, so let's increase this + // a bit to trigger different behavior on Linux vs Mac OS X. + $size = 136 * 1000; + $stream = stream_socket_client('tls://httpbin.org:443'); + // PHP < 7.1.4 (and PHP < 7.0.18) suffers from a bug when writing big + // chunks of data over TLS streams at once. + // We work around this by limiting the write chunk size to 8192 bytes + // here to also support older PHP versions. + // See https://github.com/reactphp/socket/issues/105 $loop = Factory::create(); $stream = new DuplexResourceStream( $stream, From 3eb342d87ca89e0c4c7c428505f36051c172f677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Thu, 30 Apr 2020 12:38:29 +0200 Subject: [PATCH 31/66] Only report write error when write fails with error and nothing written --- src/WritableResourceStream.php | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/src/WritableResourceStream.php b/src/WritableResourceStream.php index ff726cc..952f39c 100644 --- a/src/WritableResourceStream.php +++ b/src/WritableResourceStream.php @@ -113,13 +113,8 @@ public function close() 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 - ); + \set_error_handler(function ($_, $errstr) use (&$error) { + $error = $errstr; }); if ($this->writeChunkSize === -1) { @@ -130,25 +125,16 @@ public function handleWrite() \restore_error_handler(); - // Only report errors if *nothing* could be sent. + // Only report errors if *nothing* could be sent and an error has been raised. + // Ignore non-fatal warnings if *some* data 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))); + if (($sent === 0 || $sent === false) && $error !== null) { + $this->emit('error', array(new \RuntimeException('Unable to write to stream: ' . $error))); $this->close(); return; From 26d564035da98f314ca21bd3f3bb83a8e3a0705b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Thu, 30 Apr 2020 12:49:26 +0200 Subject: [PATCH 32/66] Simplify Travis CI test matrix for Mac OS X setup --- .travis.yml | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/.travis.yml b/.travis.yml index e840ca7..61f500c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,28 +18,17 @@ matrix: - php: hhvm-3.18 install: - composer require phpunit/phpunit:^5 --dev --no-interaction # requires legacy phpunit - - os: osx + - name: Mac OS X + os: osx language: generic - php: 7.0 # just to look right on travis - env: - - PACKAGE: php70 + before_install: + - curl -s http://getcomposer.org/installer | php + - mv composer.phar /usr/local/bin/composer allow_failures: - php: hhvm-3.18 - 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: From 7c02b510ee3f582c810aeccd3a197b9c2f52ff1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 4 May 2020 12:17:57 +0200 Subject: [PATCH 33/66] Prepare v1.1.1 release --- CHANGELOG.md | 22 ++++++++++++++++++---- README.md | 2 +- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5a7dd3..ac00244 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,26 @@ # Changelog -## 1.1.0 (2018-01-01) +## 1.1.1 (2020-05-04) -* Improvement: Increase performance by optimizing global function and constant look ups +* Fix: Fix faulty write buffer behavior when sending large data chunks over TLS (Mac OS X only). + (#150 by @clue) + +* Minor code style improvements to fix phpstan analysis warnings and + add `.gitattributes` to exclude dev files from exports. + (#140 by @flow-control and #144 by @reedy) + +* Improve test suite to run tests on PHP 7.4 and simplify test matrix. + (#147 by @clue) + +## 1.1.0 (2019-01-01) + +* Improvement: Increase performance by optimizing global function and constant look ups. (#137 by @WyriHaximus) -* Travis: Test against PHP 7.3 + +* Travis: Test against PHP 7.3. (#138 by @WyriHaximus) -* Fix: Ignore empty reads + +* Fix: Ignore empty reads. (#139 by @WyriHaximus) ## 1.0.0 (2018-07-11) diff --git a/README.md b/README.md index b5bc907..c79bacf 100644 --- a/README.md +++ b/README.md @@ -1179,7 +1179,7 @@ This project follows [SemVer](https://semver.org/). This will install the latest supported version: ```bash -$ composer require react/stream:^1.0 +$ composer require react/stream:^1.1.1 ``` See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. From d0db280b111afc40e5878886157fbe72ed379a6d Mon Sep 17 00:00:00 2001 From: Simon Frings Date: Fri, 17 Jul 2020 14:07:53 +0200 Subject: [PATCH 34/66] Run tests on PHPUnit 9 --- composer.json | 2 +- tests/CallableStub.php | 10 ------ tests/DuplexResourceStreamTest.php | 8 ++--- tests/ReadableResourceStreamTest.php | 8 ++--- tests/TestCase.php | 47 +++++++++++++++++++++++++++- tests/ThroughStreamTest.php | 2 +- tests/WritableStreamResourceTest.php | 12 +++---- 7 files changed, 62 insertions(+), 27 deletions(-) delete mode 100644 tests/CallableStub.php diff --git a/composer.json b/composer.json index 4bf0c4f..c2aa433 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "evenement/evenement": "^3.0 || ^2.0 || ^1.0" }, "require-dev": { - "phpunit/phpunit": "^7.0 || ^6.4 || ^5.7 || ^4.8.35", + "phpunit/phpunit": "^9.0 || ^5.7 || ^4.8.35", "clue/stream-filter": "~1.2" }, "autoload": { diff --git a/tests/CallableStub.php b/tests/CallableStub.php deleted file mode 100644 index 31cc834..0000000 --- a/tests/CallableStub.php +++ /dev/null @@ -1,10 +0,0 @@ -createLoopMock(); + $this->setExpectedException('InvalidArgumentException'); new DuplexResourceStream('breakme', $loop); } /** * @covers React\Stream\DuplexResourceStream::__construct - * @expectedException InvalidArgumentException */ public function testConstructorThrowsExceptionOnWriteOnlyStream() { @@ -59,12 +58,12 @@ public function testConstructorThrowsExceptionOnWriteOnlyStream() $loop = $this->createLoopMock(); + $this->setExpectedException('InvalidArgumentException'); new DuplexResourceStream(STDOUT, $loop); } /** * @covers React\Stream\DuplexResourceStream::__construct - * @expectedException InvalidArgumentException */ public function testConstructorThrowsExceptionOnWriteOnlyStreamWithExcessiveMode() { @@ -74,12 +73,12 @@ public function testConstructorThrowsExceptionOnWriteOnlyStreamWithExcessiveMode unlink($name); $loop = $this->createLoopMock(); + $this->setExpectedException('InvalidArgumentException'); new DuplexResourceStream($stream, $loop); } /** * @covers React\Stream\DuplexResourceStream::__construct - * @expectedException RunTimeException */ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking() { @@ -90,6 +89,7 @@ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking( $stream = fopen('blocking://test', 'r+'); $loop = $this->createLoopMock(); + $this->setExpectedException('RunTimeException'); new DuplexResourceStream($stream, $loop); } diff --git a/tests/ReadableResourceStreamTest.php b/tests/ReadableResourceStreamTest.php index 7566f92..30503a6 100644 --- a/tests/ReadableResourceStreamTest.php +++ b/tests/ReadableResourceStreamTest.php @@ -37,18 +37,17 @@ public function testConstructorWithExcessiveMode() /** * @covers React\Stream\ReadableResourceStream::__construct - * @expectedException InvalidArgumentException */ public function testConstructorThrowsExceptionOnInvalidStream() { $loop = $this->createLoopMock(); + $this->setExpectedException('InvalidArgumentException'); new ReadableResourceStream(false, $loop); } /** * @covers React\Stream\ReadableResourceStream::__construct - * @expectedException InvalidArgumentException */ public function testConstructorThrowsExceptionOnWriteOnlyStream() { @@ -58,12 +57,12 @@ public function testConstructorThrowsExceptionOnWriteOnlyStream() $loop = $this->createLoopMock(); + $this->setExpectedException('InvalidArgumentException'); new ReadableResourceStream(STDOUT, $loop); } /** * @covers React\Stream\ReadableResourceStream::__construct - * @expectedException InvalidArgumentException */ public function testConstructorThrowsExceptionOnWriteOnlyStreamWithExcessiveMode() { @@ -73,12 +72,12 @@ public function testConstructorThrowsExceptionOnWriteOnlyStreamWithExcessiveMode unlink($name); $loop = $this->createLoopMock(); + $this->setExpectedException('InvalidArgumentException'); new ReadableResourceStream($stream, $loop); } /** * @covers React\Stream\ReadableResourceStream::__construct - * @expectedException RuntimeException */ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking() { @@ -89,6 +88,7 @@ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking( $stream = fopen('blocking://test', 'r+'); $loop = $this->createLoopMock(); + $this->setExpectedException('RuntimeException'); new ReadableResourceStream($stream, $loop); } diff --git a/tests/TestCase.php b/tests/TestCase.php index c8fc1db..2b0928f 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -49,6 +49,51 @@ protected function expectCallableNever() protected function createCallableMock() { - return $this->getMockBuilder('React\Tests\Stream\CallableStub')->getMock(); + if (method_exists('PHPUnit\Framework\MockObject\MockBuilder', 'addMethods')) { + // PHPUnit 9+ + return $this->getMockBuilder('stdClass')->addMethods(array('__invoke'))->getMock(); + } else { + // legacy PHPUnit 4 - PHPUnit 9 + return $this->getMockBuilder('stdClass')->setMethods(array('__invoke'))->getMock(); + } + } + + public function setExpectedException($exception, $exceptionMessage = '', $exceptionCode = null) + { + if (method_exists($this, 'expectException')) { + // PHPUnit 5+ + $this->expectException($exception); + if ($exceptionMessage !== '') { + $this->expectExceptionMessage($exceptionMessage); + } + if ($exceptionCode !== null) { + $this->expectExceptionCode($exceptionCode); + } + } else { + // legacy PHPUnit 4 + parent::setExpectedException($exception, $exceptionMessage, $exceptionCode); + } + } + + public function assertContainsString($needle, $haystack) + { + if (method_exists($this, 'assertStringContainsString')) { + // PHPUnit 7.5+ + $this->assertStringContainsString($needle, $haystack); + } else { + // legacy PHPUnit 4 - PHPUnit 7.5 + $this->assertContains($needle, $haystack); + } + } + + public function assertContainsStringIgnoringCase($needle, $haystack) + { + if (method_exists($this, 'assertStringContainsStringIgnoringCase')) { + // PHPUnit 7.5+ + $this->assertStringContainsStringIgnoringCase($needle, $haystack); + } else { + // legacy PHPUnit 4 - PHPUnit 7.5 + $this->assertContains($needle, $haystack, '', true); + } } } diff --git a/tests/ThroughStreamTest.php b/tests/ThroughStreamTest.php index a98badf..444f3b1 100644 --- a/tests/ThroughStreamTest.php +++ b/tests/ThroughStreamTest.php @@ -11,10 +11,10 @@ class ThroughStreamTest extends TestCase { /** * @test - * @expectedException InvalidArgumentException */ public function itShouldRejectInvalidCallback() { + $this->setExpectedException('InvalidArgumentException'); new ThroughStream(123); } diff --git a/tests/WritableStreamResourceTest.php b/tests/WritableStreamResourceTest.php index 05bce9c..196a576 100644 --- a/tests/WritableStreamResourceTest.php +++ b/tests/WritableStreamResourceTest.php @@ -37,31 +37,30 @@ public function testConstructorWithExcessiveMode() /** * @covers React\Stream\WritableResourceStream::__construct - * @expectedException InvalidArgumentException */ public function testConstructorThrowsIfNotAValidStreamResource() { $stream = null; $loop = $this->createLoopMock(); + $this->setExpectedException('InvalidArgumentException'); new WritableResourceStream($stream, $loop); } /** * @covers React\Stream\WritableResourceStream::__construct - * @expectedException InvalidArgumentException */ public function testConstructorThrowsExceptionOnReadOnlyStream() { $stream = fopen('php://temp', 'r'); $loop = $this->createLoopMock(); + $this->setExpectedException('InvalidArgumentException'); new WritableResourceStream($stream, $loop); } /** * @covers React\Stream\WritableResourceStream::__construct - * @expectedException InvalidArgumentException */ public function testConstructorThrowsExceptionOnReadOnlyStreamWithExcessiveMode() { @@ -71,12 +70,12 @@ public function testConstructorThrowsExceptionOnReadOnlyStreamWithExcessiveMode( unlink($name); $loop = $this->createLoopMock(); + $this->setExpectedException('InvalidArgumentException'); new WritableResourceStream($stream, $loop); } /** * @covers React\Stream\WritableResourceStream::__construct - * @expectedException RuntimeException */ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking() { @@ -87,6 +86,7 @@ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking( $stream = fopen('blocking://test', 'r+'); $loop = $this->createLoopMock(); + $this->setExpectedException('RuntimeException'); new WritableResourceStream($stream, $loop); } @@ -480,8 +480,8 @@ public function testErrorWhenStreamResourceIsInvalid() $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); + $this->assertContainsString('Unable to write to stream: ', $error->getMessage()); + $this->assertContainsStringIgnoringCase(' Not a valid stream resource', $error->getMessage()); } public function testWritingToClosedStream() From 56eafea92fad3ced6fa64ab8aeba83d423bdf80f Mon Sep 17 00:00:00 2001 From: Simon Frings Date: Fri, 17 Jul 2020 14:09:48 +0200 Subject: [PATCH 35/66] Clean up test suite --- .travis.yml | 2 +- phpunit.xml.dist | 11 +---------- tests/WritableStreamResourceTest.php | 4 ++++ 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 61f500c..cc82aaf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ language: php # lock distro so new future defaults will not break the build dist: trusty -matrix: +jobs: include: - php: 5.3 dist: precise diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 04d426b..0e947b8 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,15 +1,6 @@ - + ./tests/ diff --git a/tests/WritableStreamResourceTest.php b/tests/WritableStreamResourceTest.php index 196a576..3192abd 100644 --- a/tests/WritableStreamResourceTest.php +++ b/tests/WritableStreamResourceTest.php @@ -325,6 +325,10 @@ public function testEndWithoutDataDoesNotCloseIfWritableResourceStreamIsFull() */ public function testEndWithDataClosesImmediatelyIfWritableResourceStreamFlushes() { + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('Not supported on HHVM'); + } + $stream = fopen('php://temp', 'r+'); $filterBuffer = ''; $loop = $this->createLoopMock(); From 6fb67145a66e3858a7a29945ea6841c8e2416961 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Mon, 24 Aug 2020 19:36:12 +0200 Subject: [PATCH 36/66] Add full core team to the license Added the full core team in order of joining the team --- LICENSE | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index a808108..d6f8901 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,6 @@ -Copyright (c) 2012 Igor Wiedler, Chris Boden +The MIT License (MIT) + +Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 77f4adee43e17751c2fcb7a783422147ebe32a70 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Mon, 24 Aug 2020 19:46:26 +0200 Subject: [PATCH 37/66] Add full core team to composer authors list --- composer.json | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/composer.json b/composer.json index c2aa433..d376084 100644 --- a/composer.json +++ b/composer.json @@ -3,6 +3,28 @@ "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", + "authors": [ + { + "name": "Christian Lück", + "homepage": "https://clue.engineering/", + "email": "christian@clue.engineering" + }, + { + "name": "Cees-Jan Kiewiet", + "homepage": "https://wyrihaximus.net/", + "email": "reactphp@ceesjankiewiet.nl" + }, + { + "name": "Jan Sorgalla", + "homepage": "https://sorgalla.com/", + "email": "jsorgalla@gmail.com" + }, + { + "name": "Chris Boden", + "homepage": "https://cboden.dev/", + "email": "cboden@gmail.com" + } + ], "require": { "php": ">=5.3.8", "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5", From 0e984d7f1c5326778b65aa697487570ea9b0ee28 Mon Sep 17 00:00:00 2001 From: Simon Frings Date: Wed, 23 Sep 2020 11:41:33 +0200 Subject: [PATCH 38/66] Update PHPUnit configuration schema for PHPUnit 9.3 and minor clean up --- .gitattributes | 1 + .travis.yml | 5 +++-- composer.json | 2 +- phpunit.xml.dist | 16 ++++++++++------ phpunit.xml.legacy | 18 ++++++++++++++++++ tests/TestCase.php | 14 ++------------ tests/UtilTest.php | 5 ----- ...Test.php => WritableResourceStreamTest.php} | 0 8 files changed, 35 insertions(+), 26 deletions(-) create mode 100644 phpunit.xml.legacy rename tests/{WritableStreamResourceTest.php => WritableResourceStreamTest.php} (100%) diff --git a/.gitattributes b/.gitattributes index f2f51dd..64ab6e0 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,4 +3,5 @@ /.travis.yml export-ignore /examples export-ignore /phpunit.xml.dist export-ignore +/phpunit.xml.legacy export-ignore /tests export-ignore diff --git a/.travis.yml b/.travis.yml index cc82aaf..a2498b1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,8 +29,9 @@ jobs: - os: osx install: - - composer install --no-interaction + - composer install script: - - vendor/bin/phpunit --coverage-text + - if [[ "$TRAVIS_PHP_VERSION" > "7.2" ]]; then vendor/bin/phpunit --coverage-text; fi + - if [[ "$TRAVIS_PHP_VERSION" < "7.3" ]]; then vendor/bin/phpunit --coverage-text -c phpunit.xml.legacy; fi - time php examples/91-benchmark-throughput.php diff --git a/composer.json b/composer.json index d376084..aada0be 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "evenement/evenement": "^3.0 || ^2.0 || ^1.0" }, "require-dev": { - "phpunit/phpunit": "^9.0 || ^5.7 || ^4.8.35", + "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35", "clue/stream-filter": "~1.2" }, "autoload": { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 0e947b8..fa88e7e 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,15 +1,19 @@ - + + ./tests/ - - - + + ./src/ - - + + diff --git a/phpunit.xml.legacy b/phpunit.xml.legacy new file mode 100644 index 0000000..fbb43e8 --- /dev/null +++ b/phpunit.xml.legacy @@ -0,0 +1,18 @@ + + + + + + + ./tests/ + + + + + ./src/ + + + diff --git a/tests/TestCase.php b/tests/TestCase.php index 2b0928f..7bef2f5 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -6,16 +6,6 @@ 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(); @@ -61,7 +51,7 @@ protected function createCallableMock() public function setExpectedException($exception, $exceptionMessage = '', $exceptionCode = null) { if (method_exists($this, 'expectException')) { - // PHPUnit 5+ + // PHPUnit 5.2+ $this->expectException($exception); if ($exceptionMessage !== '') { $this->expectExceptionMessage($exceptionMessage); @@ -70,7 +60,7 @@ public function setExpectedException($exception, $exceptionMessage = '', $except $this->expectExceptionCode($exceptionCode); } } else { - // legacy PHPUnit 4 + // legacy PHPUnit 4 - PHPUnit 5.1 parent::setExpectedException($exception, $exceptionMessage, $exceptionCode); } } diff --git a/tests/UtilTest.php b/tests/UtilTest.php index 3d113ab..f12baad 100644 --- a/tests/UtilTest.php +++ b/tests/UtilTest.php @@ -265,9 +265,4 @@ private function createLoopMock() { return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); } - - private function notEqualTo($value) - { - return new \PHPUnit_Framework_Constraint_Not($value); - } } diff --git a/tests/WritableStreamResourceTest.php b/tests/WritableResourceStreamTest.php similarity index 100% rename from tests/WritableStreamResourceTest.php rename to tests/WritableResourceStreamTest.php From cf710f5e8775ff865fe2bcbc8000795bc33eb022 Mon Sep 17 00:00:00 2001 From: Simon Frings Date: Tue, 24 Nov 2020 15:16:58 +0100 Subject: [PATCH 39/66] Remove test because case is already covered --- tests/WritableResourceStreamTest.php | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/tests/WritableResourceStreamTest.php b/tests/WritableResourceStreamTest.php index 3192abd..7b36418 100644 --- a/tests/WritableResourceStreamTest.php +++ b/tests/WritableResourceStreamTest.php @@ -461,33 +461,6 @@ public function testWritingToClosedWritableResourceStreamShouldNotWriteToStream( $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->assertContainsString('Unable to write to stream: ', $error->getMessage()); - $this->assertContainsStringIgnoringCase(' Not a valid stream resource', $error->getMessage()); - } - public function testWritingToClosedStream() { if ('Darwin' === PHP_OS) { From 2f5e3c9be55584cc2e4a0b620b973f963d6a1767 Mon Sep 17 00:00:00 2001 From: Simon Frings Date: Tue, 9 Feb 2021 12:04:45 +0100 Subject: [PATCH 40/66] Use GitHub actions for continuous integration (CI) Bye bye Travis CI, you've served us well. --- .gitattributes | 6 ++-- .github/workflows/ci.yml | 61 ++++++++++++++++++++++++++++++++ .gitignore | 4 +-- .travis.yml | 37 ------------------- README.md | 2 +- tests/FunctionalInternetTest.php | 4 +-- 6 files changed, 69 insertions(+), 45 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .travis.yml diff --git a/.gitattributes b/.gitattributes index 64ab6e0..fc0be87 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,7 +1,7 @@ /.gitattributes export-ignore +/.github/ export-ignore /.gitignore export-ignore -/.travis.yml export-ignore -/examples export-ignore +/examples/ export-ignore /phpunit.xml.dist export-ignore /phpunit.xml.legacy export-ignore -/tests export-ignore +/tests/ export-ignore diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..366d72a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,61 @@ +name: CI + +on: + push: + pull_request: + +jobs: + PHPUnit: + name: PHPUnit (PHP ${{ matrix.php }}) + runs-on: ubuntu-20.04 + strategy: + matrix: + php: + - 7.4 + - 7.3 + - 7.2 + - 7.1 + - 7.0 + - 5.6 + - 5.5 + - 5.4 + - 5.3 + steps: + - uses: actions/checkout@v2 + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: xdebug + - run: composer install + - run: vendor/bin/phpunit --coverage-text + if: ${{ matrix.php >= 7.3 }} + - run: vendor/bin/phpunit --coverage-text -c phpunit.xml.legacy + if: ${{ matrix.php < 7.3 }} + - run: time php examples/91-benchmark-throughput.php + + PHPUnit-macOS: + name: PHPUnit (macOS) + runs-on: macos-10.15 + continue-on-error: true + steps: + - uses: actions/checkout@v2 + - uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + coverage: xdebug + - run: composer install + - run: vendor/bin/phpunit --coverage-text + - run: time php examples/91-benchmark-throughput.php + + PHPUnit-hhvm: + name: PHPUnit (HHVM) + runs-on: ubuntu-18.04 + continue-on-error: true + steps: + - uses: actions/checkout@v2 + - uses: azjezz/setup-hhvm@v1 + with: + version: lts-3.30 + - run: hhvm $(which composer) install + - run: hhvm vendor/bin/phpunit + - run: time php examples/91-benchmark-throughput.php diff --git a/.gitignore b/.gitignore index 987e2a2..c8153b5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -composer.lock -vendor +/composer.lock +/vendor/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a2498b1..0000000 --- a/.travis.yml +++ /dev/null @@ -1,37 +0,0 @@ -language: php - -# lock distro so new future defaults will not break the build -dist: trusty - -jobs: - include: - - php: 5.3 - dist: precise - - php: 5.4 - - php: 5.5 - - php: 5.6 - - php: 7.0 - - php: 7.1 - - php: 7.2 - - php: 7.3 - - php: 7.4 - - php: hhvm-3.18 - install: - - composer require phpunit/phpunit:^5 --dev --no-interaction # requires legacy phpunit - - name: Mac OS X - os: osx - language: generic - before_install: - - curl -s http://getcomposer.org/installer | php - - mv composer.phar /usr/local/bin/composer - allow_failures: - - php: hhvm-3.18 - - os: osx - -install: - - composer install - -script: - - if [[ "$TRAVIS_PHP_VERSION" > "7.2" ]]; then vendor/bin/phpunit --coverage-text; fi - - if [[ "$TRAVIS_PHP_VERSION" < "7.3" ]]; then vendor/bin/phpunit --coverage-text -c phpunit.xml.legacy; fi - - time php examples/91-benchmark-throughput.php diff --git a/README.md b/README.md index c79bacf..2ec6219 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Stream -[![Build Status](https://travis-ci.org/reactphp/stream.svg?branch=master)](https://travis-ci.org/reactphp/stream) +[![CI status](https://github.com/reactphp/stream/workflows/CI/badge.svg)](https://github.com/reactphp/stream/actions) Event-driven readable and writable streams for non-blocking I/O in [ReactPHP](https://reactphp.org/). diff --git a/tests/FunctionalInternetTest.php b/tests/FunctionalInternetTest.php index f27c5f9..4f07537 100644 --- a/tests/FunctionalInternetTest.php +++ b/tests/FunctionalInternetTest.php @@ -59,7 +59,7 @@ public function testUploadBiggerBlockPlain() public function testUploadKilobyteSecure() { $size = 1000; - $stream = stream_socket_client('tls://httpbin.org:443'); + $stream = stream_socket_client('ssl://httpbin.org:443'); $loop = Factory::create(); $stream = new DuplexResourceStream($stream, $loop); @@ -85,7 +85,7 @@ public function testUploadBiggerBlockSecure() // a bit to trigger different behavior on Linux vs Mac OS X. $size = 136 * 1000; - $stream = stream_socket_client('tls://httpbin.org:443'); + $stream = stream_socket_client('ssl://httpbin.org:443'); // PHP < 7.1.4 (and PHP < 7.0.18) suffers from a bug when writing big // chunks of data over TLS streams at once. From fb4cf1167f54f55541e41b94b12a46133f837d6f Mon Sep 17 00:00:00 2001 From: Simon Frings Date: Tue, 9 Feb 2021 13:57:51 +0100 Subject: [PATCH 41/66] Support PHP 8 --- .github/workflows/ci.yml | 3 ++- README.md | 2 +- tests/TestCase.php | 11 +++++++++++ tests/WritableResourceStreamTest.php | 2 +- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 366d72a..dc9cf4b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,7 @@ jobs: strategy: matrix: php: + - 8.0 - 7.4 - 7.3 - 7.2 @@ -41,7 +42,7 @@ jobs: - uses: actions/checkout@v2 - uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: 8.0 coverage: xdebug - run: composer install - run: vendor/bin/phpunit --coverage-text diff --git a/README.md b/README.md index 2ec6219..a0f3ebb 100644 --- a/README.md +++ b/README.md @@ -1185,7 +1185,7 @@ $ composer require react/stream:^1.1.1 See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. This project aims to run on any platform and thus does not require any PHP -extensions and supports running on legacy PHP 5.3 through current PHP 7+ and HHVM. +extensions and supports running on legacy PHP 5.3 through current PHP 8+ and HHVM. It's *highly recommended to use PHP 7+* for this project due to its vast performance improvements. diff --git a/tests/TestCase.php b/tests/TestCase.php index 7bef2f5..af07e3e 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -86,4 +86,15 @@ public function assertContainsStringIgnoringCase($needle, $haystack) $this->assertContains($needle, $haystack, '', true); } } + + public function assertSameIgnoringCase($expected, $actual) + { + if (method_exists($this, 'assertEqualsIgnoringCase')) { + // PHPUnit 7.5+ + $this->assertEqualsIgnoringCase($expected, $actual); + } else { + // legacy PHPUnit 4 - PHPUnit 7.5 + $this->assertSame($expected, $actual); + } + } } diff --git a/tests/WritableResourceStreamTest.php b/tests/WritableResourceStreamTest.php index 7b36418..f0a3b42 100644 --- a/tests/WritableResourceStreamTest.php +++ b/tests/WritableResourceStreamTest.php @@ -485,7 +485,7 @@ public function testWritingToClosedStream() $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()); + $this->assertSameIgnoringCase('Unable to write to stream: fwrite(): send of 3 bytes failed with errno=32 Broken pipe', $error->getMessage()); } private function createWriteableLoopMock() From b7a85ad981e7c6d1e21bad8dc4b0f4fe4a682178 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 5 Jul 2021 10:36:41 +0200 Subject: [PATCH 42/66] Simplify usage by supporting new default loop --- README.md | 73 ++++++++++++++++++---------- composer.json | 2 +- examples/01-http.php | 6 +-- examples/02-https.php | 6 +-- examples/11-cat.php | 9 +--- examples/91-benchmark-throughput.php | 18 +++---- src/DuplexResourceStream.php | 7 ++- src/ReadableResourceStream.php | 6 ++- src/ReadableStreamInterface.php | 2 +- src/WritableResourceStream.php | 7 ++- src/WritableStreamInterface.php | 2 +- tests/DuplexResourceStreamTest.php | 13 +++++ tests/ReadableResourceStreamTest.php | 13 +++++ tests/WritableResourceStreamTest.php | 13 +++++ 14 files changed, 116 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index a0f3ebb..5284462 100644 --- a/README.md +++ b/README.md @@ -303,7 +303,7 @@ Re-attach the data source after a previous `pause()`. ```php $stream->pause(); -$loop->addTimer(1.0, function () use ($stream) { +Loop::addTimer(1.0, function () use ($stream) { $stream->resume(); }); ``` @@ -737,7 +737,7 @@ stream in order to stop waiting for the stream to flush its final data. ```php $stream->end(); -$loop->addTimer(1.0, function () use ($stream) { +Loop::addTimer(1.0, function () use ($stream) { $stream->close(); }); ``` @@ -821,7 +821,7 @@ This can be used to represent a read-only resource like a file stream opened in readable mode or a stream such as `STDIN`: ```php -$stream = new ReadableResourceStream(STDIN, $loop); +$stream = new ReadableResourceStream(STDIN); $stream->on('data', function ($chunk) { echo $chunk; }); @@ -838,7 +838,7 @@ Otherwise, it will throw an `InvalidArgumentException`: ```php // throws InvalidArgumentException -$stream = new ReadableResourceStream(false, $loop); +$stream = new ReadableResourceStream(false); ``` See also the [`DuplexResourceStream`](#readableresourcestream) for read-and-write @@ -851,7 +851,7 @@ If this fails, it will throw a `RuntimeException`: ```php // throws RuntimeException on Windows -$stream = new ReadableResourceStream(STDIN, $loop); +$stream = new ReadableResourceStream(STDIN); ``` Once the constructor is called with a valid stream resource, this class will @@ -859,6 +859,12 @@ take care of the underlying stream resource. You SHOULD only use its public API and SHOULD NOT interfere with the underlying stream resource manually. +This class takes an optional `LoopInterface|null $loop` parameter that can be used to +pass the event loop instance to use for this object. You can use a `null` value +here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). +This value SHOULD NOT be given unless you're sure you want to explicitly use a +given event loop instance. + This class takes an optional `int|null $readChunkSize` parameter that controls the maximum buffer size in bytes to read at once from the stream. You can use a `null` value here in order to apply its default value. @@ -874,7 +880,7 @@ This should read until the stream resource is not readable anymore mean it reached EOF. ```php -$stream = new ReadableResourceStream(STDIN, $loop, 8192); +$stream = new ReadableResourceStream(STDIN, null, 8192); ``` > PHP bug warning: If the PHP process has explicitly been started without a @@ -883,6 +889,9 @@ $stream = new ReadableResourceStream(STDIN, $loop, 8192); stream like `php test.php < /dev/null` instead of `php test.php <&-`. See [#81](https://github.com/reactphp/stream/issues/81) for more details. +> Changelog: As of v1.2.0 the `$loop` parameter can be omitted (or skipped with a + `null` value) to use the [default loop](https://github.com/reactphp/event-loop#loop). + ### WritableResourceStream The `WritableResourceStream` is a concrete implementation of the @@ -892,7 +901,7 @@ This can be used to represent a write-only resource like a file stream opened in writable mode or a stream such as `STDOUT` or `STDERR`: ```php -$stream = new WritableResourceStream(STDOUT, $loop); +$stream = new WritableResourceStream(STDOUT); $stream->write('hello!'); $stream->end(); ``` @@ -905,7 +914,7 @@ Otherwise, it will throw an `InvalidArgumentException`: ```php // throws InvalidArgumentException -$stream = new WritableResourceStream(false, $loop); +$stream = new WritableResourceStream(false); ``` See also the [`DuplexResourceStream`](#readableresourcestream) for read-and-write @@ -918,7 +927,7 @@ If this fails, it will throw a `RuntimeException`: ```php // throws RuntimeException on Windows -$stream = new WritableResourceStream(STDOUT, $loop); +$stream = new WritableResourceStream(STDOUT); ``` Once the constructor is called with a valid stream resource, this class will @@ -933,13 +942,19 @@ For this, it uses an in-memory buffer string to collect all outstanding writes. This buffer has a soft-limit applied which defines how much data it is willing to accept before the caller SHOULD stop sending further data. +This class takes an optional `LoopInterface|null $loop` parameter that can be used to +pass the event loop instance to use for this object. You can use a `null` value +here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). +This value SHOULD NOT be given unless you're sure you want to explicitly use a +given event loop instance. + This class takes an optional `int|null $writeBufferSoftLimit` parameter that controls this maximum buffer size in bytes. You can use a `null` value here in order to apply its default value. This value SHOULD NOT be changed unless you know what you're doing. ```php -$stream = new WritableResourceStream(STDOUT, $loop, 8192); +$stream = new WritableResourceStream(STDOUT, null, 8192); ``` This class takes an optional `int|null $writeChunkSize` parameter that controls @@ -954,11 +969,14 @@ This can be `-1` which means "write everything available" to the underlying stream resource. ```php -$stream = new WritableResourceStream(STDOUT, $loop, null, 8192); +$stream = new WritableResourceStream(STDOUT, null, null, 8192); ``` See also [`write()`](#write) for more details. +> Changelog: As of v1.2.0 the `$loop` parameter can be omitted (or skipped with a + `null` value) to use the [default loop](https://github.com/reactphp/event-loop#loop). + ### DuplexResourceStream The `DuplexResourceStream` is a concrete implementation of the @@ -969,7 +987,7 @@ in read and write mode mode or a stream such as a TCP/IP connection: ```php $conn = stream_socket_client('tcp://google.com:80'); -$stream = new DuplexResourceStream($conn, $loop); +$stream = new DuplexResourceStream($conn); $stream->write('hello!'); $stream->end(); ``` @@ -982,7 +1000,7 @@ Otherwise, it will throw an `InvalidArgumentException`: ```php // throws InvalidArgumentException -$stream = new DuplexResourceStream(false, $loop); +$stream = new DuplexResourceStream(false); ``` See also the [`ReadableResourceStream`](#readableresourcestream) for read-only @@ -996,7 +1014,7 @@ If this fails, it will throw a `RuntimeException`: ```php // throws RuntimeException on Windows -$stream = new DuplexResourceStream(STDOUT, $loop); +$stream = new DuplexResourceStream(STDOUT); ``` Once the constructor is called with a valid stream resource, this class will @@ -1004,6 +1022,12 @@ take care of the underlying stream resource. You SHOULD only use its public API and SHOULD NOT interfere with the underlying stream resource manually. +This class takes an optional `LoopInterface|null $loop` parameter that can be used to +pass the event loop instance to use for this object. You can use a `null` value +here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). +This value SHOULD NOT be given unless you're sure you want to explicitly use a +given event loop instance. + This class takes an optional `int|null $readChunkSize` parameter that controls the maximum buffer size in bytes to read at once from the stream. You can use a `null` value here in order to apply its default value. @@ -1020,7 +1044,7 @@ mean it reached EOF. ```php $conn = stream_socket_client('tcp://google.com:80'); -$stream = new DuplexResourceStream($conn, $loop, 8192); +$stream = new DuplexResourceStream($conn, null, 8192); ``` Any `write()` calls to this class will not be performed instantly, but will @@ -1040,12 +1064,15 @@ If you want to change the write buffer soft limit, you can pass an instance of ```php $conn = stream_socket_client('tcp://google.com:80'); -$buffer = new WritableResourceStream($conn, $loop, 8192); -$stream = new DuplexResourceStream($conn, $loop, null, $buffer); +$buffer = new WritableResourceStream($conn, null, 8192); +$stream = new DuplexResourceStream($conn, null, null, $buffer); ``` See also [`WritableResourceStream`](#writableresourcestream) for more details. +> Changelog: As of v1.2.0 the `$loop` parameter can be omitted (or skipped with a + `null` value) to use the [default loop](https://github.com/reactphp/event-loop#loop). + ### ThroughStream The `ThroughStream` implements the @@ -1123,8 +1150,8 @@ This is useful for some APIs which may require a single more convenient to work with a single stream instance like this: ```php -$stdin = new ReadableResourceStream(STDIN, $loop); -$stdout = new WritableResourceStream(STDOUT, $loop); +$stdin = new ReadableResourceStream(STDIN); +$stdout = new WritableResourceStream(STDOUT); $stdio = new CompositeStream($stdin, $stdout); @@ -1154,14 +1181,10 @@ The following example can be used to pipe the contents of a source file into a destination file without having to ever read the whole file into memory: ```php -$loop = new React\EventLoop\StreamSelectLoop; - -$source = new React\Stream\ReadableResourceStream(fopen('source.txt', 'r'), $loop); -$dest = new React\Stream\WritableResourceStream(fopen('destination.txt', 'w'), $loop); +$source = new React\Stream\ReadableResourceStream(fopen('source.txt', 'r')); +$dest = new React\Stream\WritableResourceStream(fopen('destination.txt', 'w')); $source->pipe($dest); - -$loop->run(); ``` > Note that this example uses `fopen()` for illustration purposes only. diff --git a/composer.json b/composer.json index aada0be..fd90e9f 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ ], "require": { "php": ">=5.3.8", - "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5", + "react/event-loop": "dev-master#78f7f43 as 1.2.0", "evenement/evenement": "^3.0 || ^2.0 || ^1.0" }, "require-dev": { diff --git a/examples/01-http.php b/examples/01-http.php index 3687f7c..e70691d 100644 --- a/examples/01-http.php +++ b/examples/01-http.php @@ -11,7 +11,6 @@ // $ 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'; @@ -25,8 +24,7 @@ exit(1); } -$loop = Factory::create(); -$stream = new DuplexResourceStream($resource, $loop); +$stream = new DuplexResourceStream($resource); $stream->on('data', function ($chunk) { echo $chunk; @@ -36,5 +34,3 @@ }); $stream->write("GET / HTTP/1.0\r\nHost: $host\r\n\r\n"); - -$loop->run(); diff --git a/examples/02-https.php b/examples/02-https.php index 163f7c8..1d212da 100644 --- a/examples/02-https.php +++ b/examples/02-https.php @@ -11,7 +11,6 @@ // $ 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'; @@ -25,8 +24,7 @@ exit(1); } -$loop = Factory::create(); -$stream = new DuplexResourceStream($resource, $loop); +$stream = new DuplexResourceStream($resource); $stream->on('data', function ($chunk) { echo $chunk; @@ -36,5 +34,3 @@ }); $stream->write("GET / HTTP/1.0\r\nHost: $host\r\n\r\n"); - -$loop->run(); diff --git a/examples/11-cat.php b/examples/11-cat.php index 90fadc0..818ec8e 100644 --- a/examples/11-cat.php +++ b/examples/11-cat.php @@ -8,7 +8,6 @@ // $ 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; @@ -19,10 +18,6 @@ exit(1); } -$loop = Factory::create(); - -$stdout = new WritableResourceStream(STDOUT, $loop); -$stdin = new ReadableResourceStream(STDIN, $loop); +$stdout = new WritableResourceStream(STDOUT); +$stdin = new ReadableResourceStream(STDIN); $stdin->pipe($stdout); - -$loop->run(); diff --git a/examples/91-benchmark-throughput.php b/examples/91-benchmark-throughput.php index ecf695c..4203950 100644 --- a/examples/91-benchmark-throughput.php +++ b/examples/91-benchmark-throughput.php @@ -11,6 +11,8 @@ // $ php examples/91-benchmark-throughput.php -t 10 -o zero.bin // $ php examples/91-benchmark-throughput.php -t 60 -i zero.bin +use React\EventLoop\Loop; + require __DIR__ . '/../vendor/autoload.php'; if (DIRECTORY_SEPARATOR === '\\') { @@ -27,10 +29,8 @@ $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); +$info = new React\Stream\WritableResourceStream(STDERR); if (extension_loaded('xdebug')) { $info->write('NOTICE: The "xdebug" extension is loaded, this has a major impact on performance.' . PHP_EOL); } @@ -38,25 +38,23 @@ // 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 = new React\Stream\ReadableResourceStream($fh); +$out = new React\Stream\WritableResourceStream(fopen($of, 'w')); $in->pipe($out); // stop input stream in $t seconds $start = microtime(true); -$timeout = $loop->addTimer($t, function () use ($in, &$bytes) { +$timeout = Loop::addTimer($t, function () use ($in) { $in->close(); }); // print stream position once stream closes -$in->on('close', function () use ($fh, $start, $loop, $timeout, $info) { +$in->on('close', function () use ($fh, $start, $timeout, $info) { $t = microtime(true) - $start; - $loop->cancelTimer($timeout); + 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(); diff --git a/src/DuplexResourceStream.php b/src/DuplexResourceStream.php index c8c1c50..c3163c6 100644 --- a/src/DuplexResourceStream.php +++ b/src/DuplexResourceStream.php @@ -3,12 +3,15 @@ namespace React\Stream; use Evenement\EventEmitter; +use React\EventLoop\Loop; use React\EventLoop\LoopInterface; use InvalidArgumentException; final class DuplexResourceStream extends EventEmitter implements DuplexStreamInterface { private $stream; + + /** @var LoopInterface */ private $loop; /** @@ -35,7 +38,7 @@ final class DuplexResourceStream extends EventEmitter implements DuplexStreamInt private $closing = false; private $listening = false; - public function __construct($stream, LoopInterface $loop, $readChunkSize = null, WritableStreamInterface $buffer = null) + public function __construct($stream, LoopInterface $loop = null, $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'); @@ -70,7 +73,7 @@ public function __construct($stream, LoopInterface $loop, $readChunkSize = null, } $this->stream = $stream; - $this->loop = $loop; + $this->loop = $loop ?: Loop::get(); $this->bufferSize = ($readChunkSize === null) ? 65536 : (int)$readChunkSize; $this->buffer = $buffer; diff --git a/src/ReadableResourceStream.php b/src/ReadableResourceStream.php index 9d9c006..1b0b08c 100644 --- a/src/ReadableResourceStream.php +++ b/src/ReadableResourceStream.php @@ -3,6 +3,7 @@ namespace React\Stream; use Evenement\EventEmitter; +use React\EventLoop\Loop; use React\EventLoop\LoopInterface; use InvalidArgumentException; @@ -13,6 +14,7 @@ final class ReadableResourceStream extends EventEmitter implements ReadableStrea */ private $stream; + /** @var LoopInterface */ private $loop; /** @@ -38,7 +40,7 @@ final class ReadableResourceStream extends EventEmitter implements ReadableStrea private $closed = false; private $listening = false; - public function __construct($stream, LoopInterface $loop, $readChunkSize = null) + public function __construct($stream, LoopInterface $loop = null, $readChunkSize = null) { if (!\is_resource($stream) || \get_resource_type($stream) !== "stream") { throw new InvalidArgumentException('First parameter must be a valid stream resource'); @@ -69,7 +71,7 @@ public function __construct($stream, LoopInterface $loop, $readChunkSize = null) } $this->stream = $stream; - $this->loop = $loop; + $this->loop = $loop ?: Loop::get(); $this->bufferSize = ($readChunkSize === null) ? 65536 : (int)$readChunkSize; $this->resume(); diff --git a/src/ReadableStreamInterface.php b/src/ReadableStreamInterface.php index 2b4c3d0..fa3d59c 100644 --- a/src/ReadableStreamInterface.php +++ b/src/ReadableStreamInterface.php @@ -236,7 +236,7 @@ public function pause(); * ```php * $stream->pause(); * - * $loop->addTimer(1.0, function () use ($stream) { + * Loop::addTimer(1.0, function () use ($stream) { * $stream->resume(); * }); * ``` diff --git a/src/WritableResourceStream.php b/src/WritableResourceStream.php index 952f39c..1af16b1 100644 --- a/src/WritableResourceStream.php +++ b/src/WritableResourceStream.php @@ -3,11 +3,14 @@ namespace React\Stream; use Evenement\EventEmitter; +use React\EventLoop\Loop; use React\EventLoop\LoopInterface; final class WritableResourceStream extends EventEmitter implements WritableStreamInterface { private $stream; + + /** @var LoopInterface */ private $loop; /** @@ -25,7 +28,7 @@ final class WritableResourceStream extends EventEmitter implements WritableStrea private $closed = false; private $data = ''; - public function __construct($stream, LoopInterface $loop, $writeBufferSoftLimit = null, $writeChunkSize = null) + public function __construct($stream, LoopInterface $loop = null, $writeBufferSoftLimit = null, $writeChunkSize = null) { if (!\is_resource($stream) || \get_resource_type($stream) !== "stream") { throw new \InvalidArgumentException('First parameter must be a valid stream resource'); @@ -44,7 +47,7 @@ public function __construct($stream, LoopInterface $loop, $writeBufferSoftLimit } $this->stream = $stream; - $this->loop = $loop; + $this->loop = $loop ?: Loop::get(); $this->softLimit = ($writeBufferSoftLimit === null) ? 65536 : (int)$writeBufferSoftLimit; $this->writeChunkSize = ($writeChunkSize === null) ? -1 : (int)$writeChunkSize; } diff --git a/src/WritableStreamInterface.php b/src/WritableStreamInterface.php index 3bc932e..9b54680 100644 --- a/src/WritableStreamInterface.php +++ b/src/WritableStreamInterface.php @@ -330,7 +330,7 @@ public function end($data = null); * * ```php * $stream->end(); - * $loop->addTimer(1.0, function () use ($stream) { + * Loop::addTimer(1.0, function () use ($stream) { * $stream->close(); * }); * ``` diff --git a/tests/DuplexResourceStreamTest.php b/tests/DuplexResourceStreamTest.php index 2432efe..2edc07e 100644 --- a/tests/DuplexResourceStreamTest.php +++ b/tests/DuplexResourceStreamTest.php @@ -20,6 +20,19 @@ public function testConstructor() new DuplexResourceStream($stream, $loop); } + public function testConstructWithoutLoopAssignsLoopAutomatically() + { + $resource = fopen('php://temp', 'r+'); + + $stream = new DuplexResourceStream($resource); + + $ref = new \ReflectionProperty($stream, 'loop'); + $ref->setAccessible(true); + $loop = $ref->getValue($stream); + + $this->assertInstanceOf('React\EventLoop\LoopInterface', $loop); + } + /** * @covers React\Stream\DuplexResourceStream::__construct * @doesNotPerformAssertions diff --git a/tests/ReadableResourceStreamTest.php b/tests/ReadableResourceStreamTest.php index 30503a6..f534488 100644 --- a/tests/ReadableResourceStreamTest.php +++ b/tests/ReadableResourceStreamTest.php @@ -19,6 +19,19 @@ public function testConstructor() new ReadableResourceStream($stream, $loop); } + public function testConstructWithoutLoopAssignsLoopAutomatically() + { + $resource = fopen('php://temp', 'r+'); + + $stream = new ReadableResourceStream($resource); + + $ref = new \ReflectionProperty($stream, 'loop'); + $ref->setAccessible(true); + $loop = $ref->getValue($stream); + + $this->assertInstanceOf('React\EventLoop\LoopInterface', $loop); + } + /** * @covers React\Stream\ReadableResourceStream::__construct * @doesNotPerformAssertions diff --git a/tests/WritableResourceStreamTest.php b/tests/WritableResourceStreamTest.php index f0a3b42..38d4259 100644 --- a/tests/WritableResourceStreamTest.php +++ b/tests/WritableResourceStreamTest.php @@ -19,6 +19,19 @@ public function testConstructor() new WritableResourceStream($stream, $loop); } + public function testConstructWithoutLoopAssignsLoopAutomatically() + { + $resource = fopen('php://temp', 'r+'); + + $stream = new WritableResourceStream($resource); + + $ref = new \ReflectionProperty($stream, 'loop'); + $ref->setAccessible(true); + $loop = $ref->getValue($stream); + + $this->assertInstanceOf('React\EventLoop\LoopInterface', $loop); + } + /** * @covers React\Stream\WritableResourceStream::__construct * @doesNotPerformAssertions From e617d632943bc8022950bcd53b8ed23c4edf4b1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 5 Jul 2021 10:42:17 +0200 Subject: [PATCH 43/66] Update to stable reactphp/event-loop v1.2.0 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index fd90e9f..b235f5a 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ ], "require": { "php": ">=5.3.8", - "react/event-loop": "dev-master#78f7f43 as 1.2.0", + "react/event-loop": "^1.2", "evenement/evenement": "^3.0 || ^2.0 || ^1.0" }, "require-dev": { From 7a423506ee1903e89f1e08ec5f0ed430ff784ae9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sun, 11 Jul 2021 14:37:55 +0200 Subject: [PATCH 44/66] Prepare v1.2.0 release --- CHANGELOG.md | 23 +++++++++++++++++++++++ README.md | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac00244..9bafba7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # Changelog +## 1.2.0 (2021-07-11) + +A major new feature release, see [**release announcement**](https://clue.engineering/2021/announcing-reactphp-default-loop). + +* Feature: Simplify usage by supporting new [default loop](https://reactphp.org/event-loop/#loop). + (#159 by @clue) + + ```php + // old (still supported) + $stream = new ReadableResourceStream($resource, $loop); + $stream = new WritabeResourceStream($resource, $loop); + $stream = new DuplexResourceStream($resource, $loop); + + // new (using default loop) + $stream = new ReadableResourceStream($resource); + $stream = new WritabeResourceStream($resource); + $stream = new DuplexResourceStream($resource); + ``` + +* Improve test suite, use GitHub actions for continuous integration (CI), + update PHPUnit config, run tests on PHP 8 and add full core team to the license. + (#153, #156 and #157 by @SimonFrings and #154 by @WyriHaximus) + ## 1.1.1 (2020-05-04) * Fix: Fix faulty write buffer behavior when sending large data chunks over TLS (Mac OS X only). diff --git a/README.md b/README.md index 5284462..460f51a 100644 --- a/README.md +++ b/README.md @@ -1202,7 +1202,7 @@ This project follows [SemVer](https://semver.org/). This will install the latest supported version: ```bash -$ composer require react/stream:^1.1.1 +$ composer require react/stream:^1.2 ``` See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. From 526d6ad096cd60524f06294fc2f59bb15609e2d8 Mon Sep 17 00:00:00 2001 From: Simon Frings Date: Mon, 15 Nov 2021 16:08:39 +0100 Subject: [PATCH 45/66] Support PHP 8.1 --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc9cf4b..ca7e8f0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,7 @@ jobs: strategy: matrix: php: + - 8.1 - 8.0 - 7.4 - 7.3 From 99508eec9b52c8cfdfcb2205a95ddcc0173531f4 Mon Sep 17 00:00:00 2001 From: Simon Bennett Date: Wed, 16 Mar 2022 06:38:15 +0000 Subject: [PATCH 46/66] Update typo's in docs --- README.md | 2 +- src/WritableStreamInterface.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 460f51a..7aacfed 100644 --- a/README.md +++ b/README.md @@ -609,7 +609,7 @@ 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) +Similarly, if 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. diff --git a/src/WritableStreamInterface.php b/src/WritableStreamInterface.php index 9b54680..e262592 100644 --- a/src/WritableStreamInterface.php +++ b/src/WritableStreamInterface.php @@ -196,7 +196,7 @@ public function isWritable(); * 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) + * Similarly, if 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. * From b8fc28d71addc187ae19aa5cdc67fce95b9c8426 Mon Sep 17 00:00:00 2001 From: Simon Frings Date: Wed, 16 Mar 2022 13:38:26 +0100 Subject: [PATCH 47/66] Add badge to show number of project installations --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 460f51a..cc435fe 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Stream [![CI status](https://github.com/reactphp/stream/workflows/CI/badge.svg)](https://github.com/reactphp/stream/actions) +[![installs on Packagist](https://img.shields.io/packagist/dt/react/stream?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/react/stream) Event-driven readable and writable streams for non-blocking I/O in [ReactPHP](https://reactphp.org/). From 85cd3386df6fbb90880b2a337b7330f491d5fb98 Mon Sep 17 00:00:00 2001 From: Simon Frings Date: Tue, 12 Apr 2022 10:00:43 +0200 Subject: [PATCH 48/66] Fix legacy HHVM build by downgrading Composer --- .github/workflows/ci.yml | 1 + README.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ca7e8f0..3ee70fd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,6 +58,7 @@ jobs: - uses: azjezz/setup-hhvm@v1 with: version: lts-3.30 + - run: composer self-update --2.2 # downgrade Composer for HHVM - run: hhvm $(which composer) install - run: hhvm vendor/bin/phpunit - run: time php examples/91-benchmark-throughput.php diff --git a/README.md b/README.md index a0e5ce1..f55c9c6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Stream -[![CI status](https://github.com/reactphp/stream/workflows/CI/badge.svg)](https://github.com/reactphp/stream/actions) +[![CI status](https://github.com/reactphp/stream/actions/workflows/ci.yml/badge.svg)](https://github.com/reactphp/stream/actions) [![installs on Packagist](https://img.shields.io/packagist/dt/react/stream?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/react/stream) Event-driven readable and writable streams for non-blocking I/O in [ReactPHP](https://reactphp.org/). From 2c436bb477ceabfb705fe773b123da23f077241d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 20 May 2022 10:18:27 +0200 Subject: [PATCH 49/66] Avoid unneeded syscall when creating non-blocking `DuplexResourceStream` --- src/DuplexResourceStream.php | 2 +- tests/DuplexResourceStreamTest.php | 20 +++++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/DuplexResourceStream.php b/src/DuplexResourceStream.php index c3163c6..4da2139 100644 --- a/src/DuplexResourceStream.php +++ b/src/DuplexResourceStream.php @@ -52,7 +52,7 @@ public function __construct($stream, LoopInterface $loop = null, $readChunkSize // 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, false) !== true) { + if ($buffer !== null && !$buffer instanceof WritableResourceStream && \stream_set_blocking($stream, false) !== true) { throw new \RuntimeException('Unable to set stream resource to non-blocking mode'); } diff --git a/tests/DuplexResourceStreamTest.php b/tests/DuplexResourceStreamTest.php index 2edc07e..e61f14b 100644 --- a/tests/DuplexResourceStreamTest.php +++ b/tests/DuplexResourceStreamTest.php @@ -117,7 +117,25 @@ public function testConstructorAcceptsBuffer() $buffer = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); - $conn = new DuplexResourceStream($stream, $loop, null, $buffer); + new DuplexResourceStream($stream, $loop, null, $buffer); + } + + /** + * @covers React\Stream\DuplexResourceStream::__construct + */ + public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlockingWithBufferGiven() + { + if (!in_array('blocking', stream_get_wrappers())) { + stream_wrapper_register('blocking', 'React\Tests\Stream\EnforceBlockingWrapper'); + } + + $stream = fopen('blocking://test', 'r+'); + $loop = $this->createLoopMock(); + + $buffer = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + + $this->setExpectedException('RunTimeException'); + new DuplexResourceStream($stream, $loop, null, $buffer); } public function testCloseShouldEmitCloseEvent() From f96bfb2a211df35283ded6b5d2f09961d2bccecd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 17 Jun 2022 11:43:25 +0200 Subject: [PATCH 50/66] Make tests compatible with PHP 8.2 by avoiding dynamic properties --- tests/EnforceBlockingWrapper.php | 4 +++- tests/WritableResourceStreamTest.php | 22 ++++++++++++++-------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/tests/EnforceBlockingWrapper.php b/tests/EnforceBlockingWrapper.php index 39c0487..a171b41 100644 --- a/tests/EnforceBlockingWrapper.php +++ b/tests/EnforceBlockingWrapper.php @@ -5,10 +5,12 @@ /** * Used to test dummy stream resources that do not support setting non-blocking mode * - * @link http://php.net/manual/de/class.streamwrapper.php + * @link https://www.php.net/manual/en/class.streamwrapper.php */ class EnforceBlockingWrapper { + public $context; + public function stream_open($path, $mode, $options, &$opened_path) { return true; diff --git a/tests/WritableResourceStreamTest.php b/tests/WritableResourceStreamTest.php index 38d4259..678db98 100644 --- a/tests/WritableResourceStreamTest.php +++ b/tests/WritableResourceStreamTest.php @@ -157,14 +157,23 @@ public function testEmptyWriteDoesNotAddToLoop() public function testWriteReturnsFalseWhenWritableResourceStreamIsFull() { $stream = fopen('php://temp', 'r+'); - $loop = $this->createWriteableLoopMock(); - $loop->preventWrites = true; + + $preventWrites = true; + $loop = $this->createLoopMock(); + $loop + ->expects($this->any()) + ->method('addWriteStream') + ->will($this->returnCallback(function ($stream, $listener) use (&$preventWrites) { + if (!$preventWrites) { + call_user_func($listener, $stream); + } + })); $buffer = new WritableResourceStream($stream, $loop, 4); $buffer->on('error', $this->expectCallableNever()); $this->assertTrue($buffer->write("foo")); - $loop->preventWrites = false; + $preventWrites = false; $this->assertFalse($buffer->write("bar\n")); } @@ -504,14 +513,11 @@ public function testWritingToClosedStream() 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); - } + ->will($this->returnCallback(function ($stream, $listener) { + call_user_func($listener, $stream); })); return $loop; From 2128f2c29de2bfe8cd368c7b6da59b217881e614 Mon Sep 17 00:00:00 2001 From: Nicolas Hedger Date: Mon, 20 Jun 2022 17:02:04 +0200 Subject: [PATCH 51/66] chore(docs): remove leading dollar sign --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f55c9c6..eb6962d 100644 --- a/README.md +++ b/README.md @@ -1203,7 +1203,7 @@ This project follows [SemVer](https://semver.org/). This will install the latest supported version: ```bash -$ composer require react/stream:^1.2 +composer require react/stream:^1.2 ``` See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. @@ -1219,13 +1219,13 @@ To run the test suite, you first need to clone this repo and then install all dependencies [through Composer](https://getcomposer.org): ```bash -$ composer install +composer install ``` To run the test suite, go to the project root and run: ```bash -$ php vendor/bin/phpunit +vendor/bin/phpunit ``` The test suite also contains a number of functional integration tests that rely @@ -1233,7 +1233,7 @@ on a stable internet connection. If you do not want to run these, they can simply be skipped like this: ```bash -$ php vendor/bin/phpunit --exclude-group internet +vendor/bin/phpunit --exclude-group internet ``` ## License From d14450bc1f8926feb7c19501f6c510befccb7bdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sat, 6 Aug 2022 09:24:32 +0200 Subject: [PATCH 52/66] Fix failing test suite by updating macOS --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3ee70fd..6d143b7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,7 +37,7 @@ jobs: PHPUnit-macOS: name: PHPUnit (macOS) - runs-on: macos-10.15 + runs-on: macos-12 continue-on-error: true steps: - uses: actions/checkout@v2 From a0764a6d023f54cba8d9a467284bec6f500a8e4d Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Sun, 14 Aug 2022 00:12:43 +0200 Subject: [PATCH 53/66] Test on PHP 8.2 With PHP 8.2 coming out later this year, we should be reading for it's release to ensure all out code works on it. Refs: https://github.com/reactphp/event-loop/pull/258 --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6d143b7..1250e9b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,7 @@ jobs: strategy: matrix: php: + - 8.2 - 8.1 - 8.0 - 7.4 From 9b365777a99cf86803dcdfdae81e9a092a078394 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sun, 13 Nov 2022 22:47:10 +0100 Subject: [PATCH 54/66] Update test suite and report failed assertions --- .github/workflows/ci.yml | 32 ++++++++++++++++++++------------ composer.json | 6 +++--- phpunit.xml.dist | 17 +++++++++++++---- phpunit.xml.legacy | 10 +++++++++- 4 files changed, 45 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1250e9b..bc41ae6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ on: jobs: PHPUnit: name: PHPUnit (PHP ${{ matrix.php }}) - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: matrix: php: @@ -24,11 +24,12 @@ jobs: - 5.4 - 5.3 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} coverage: xdebug + ini-file: development - run: composer install - run: vendor/bin/phpunit --coverage-text if: ${{ matrix.php >= 7.3 }} @@ -41,25 +42,32 @@ jobs: runs-on: macos-12 continue-on-error: true steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: shivammathur/setup-php@v2 with: - php-version: 8.0 + php-version: 8.1 coverage: xdebug + ini-file: development - run: composer install - run: vendor/bin/phpunit --coverage-text - run: time php examples/91-benchmark-throughput.php PHPUnit-hhvm: name: PHPUnit (HHVM) - runs-on: ubuntu-18.04 + runs-on: ubuntu-22.04 continue-on-error: true steps: - - uses: actions/checkout@v2 - - uses: azjezz/setup-hhvm@v1 + - uses: actions/checkout@v3 + - run: cp "$(which composer)" composer.phar && ./composer.phar self-update --2.2 # downgrade Composer for HHVM + - name: Run hhvm composer.phar install + uses: docker://hhvm/hhvm:3.30-lts-latest with: - version: lts-3.30 - - run: composer self-update --2.2 # downgrade Composer for HHVM - - run: hhvm $(which composer) install - - run: hhvm vendor/bin/phpunit - - run: time php examples/91-benchmark-throughput.php + args: hhvm composer.phar install + - name: Run hhvm vendor/bin/phpunit + uses: docker://hhvm/hhvm:3.30-lts-latest + with: + args: hhvm vendor/bin/phpunit + - name: Run time hhvm examples/91-benchmark-throughput.php + uses: docker://hhvm/hhvm:3.30-lts-latest + with: + args: bash -c "time hhvm examples/91-benchmark-throughput.php" diff --git a/composer.json b/composer.json index b235f5a..e139434 100644 --- a/composer.json +++ b/composer.json @@ -31,17 +31,17 @@ "evenement/evenement": "^3.0 || ^2.0 || ^1.0" }, "require-dev": { - "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35", + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", "clue/stream-filter": "~1.2" }, "autoload": { "psr-4": { - "React\\Stream\\": "src" + "React\\Stream\\": "src/" } }, "autoload-dev": { "psr-4": { - "React\\Tests\\Stream\\": "tests" + "React\\Tests\\Stream\\": "tests/" } } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index fa88e7e..7a9577e 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,11 +1,12 @@ - - + + convertDeprecationsToExceptions="true"> ./tests/ @@ -16,4 +17,12 @@ ./src/ + + + + + + + + diff --git a/phpunit.xml.legacy b/phpunit.xml.legacy index fbb43e8..ac5600a 100644 --- a/phpunit.xml.legacy +++ b/phpunit.xml.legacy @@ -1,6 +1,6 @@ - + ./src/ + + + + + + + + From 6fbc9672905c7d5a885f2da2fc696f65840f4a66 Mon Sep 17 00:00:00 2001 From: Simon Frings Date: Fri, 16 Jun 2023 12:51:30 +0200 Subject: [PATCH 55/66] Prepare v1.3.0 release --- CHANGELOG.md | 14 ++++++++++++++ README.md | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bafba7..b4e2307 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## 1.3.0 (2023-06-16) + +* Feature: Full PHP 8.1 and PHP 8.2 compatibility. + (#160 by @SimonFrings, #165 by @clue and #169 by @WyriHaximus) + +* Feature: Avoid unneeded syscall when creating non-blocking `DuplexResourceStream`. + (#164 by @clue) + +* Minor documentation improvements. + (#161 by @mrsimonbennett, #162 by @SimonFrings and #166 by @nhedger) + +* Improve test suite and project setup and report failed assertions. + (#168 and #170 by @clue and #163 by @SimonFrings) + ## 1.2.0 (2021-07-11) A major new feature release, see [**release announcement**](https://clue.engineering/2021/announcing-reactphp-default-loop). diff --git a/README.md b/README.md index eb6962d..2bfcbbe 100644 --- a/README.md +++ b/README.md @@ -1203,7 +1203,7 @@ This project follows [SemVer](https://semver.org/). This will install the latest supported version: ```bash -composer require react/stream:^1.2 +composer require react/stream:^1.3 ``` See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. From 4e4e6388f9406e5902dbc066060a4066610ad38c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sun, 17 Sep 2023 13:19:15 +0200 Subject: [PATCH 56/66] Test on PHP 8.3 and update test environment --- .github/workflows/ci.yml | 9 +++++---- composer.json | 2 +- phpunit.xml.dist | 6 +++--- phpunit.xml.legacy | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bc41ae6..bc36a4d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,7 @@ jobs: strategy: matrix: php: + - 8.3 - 8.2 - 8.1 - 8.0 @@ -24,7 +25,7 @@ jobs: - 5.4 - 5.3 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} @@ -42,10 +43,10 @@ jobs: runs-on: macos-12 continue-on-error: true steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: - php-version: 8.1 + php-version: 8.2 coverage: xdebug ini-file: development - run: composer install @@ -57,7 +58,7 @@ jobs: runs-on: ubuntu-22.04 continue-on-error: true steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: cp "$(which composer)" composer.phar && ./composer.phar self-update --2.2 # downgrade Composer for HHVM - name: Run hhvm composer.phar install uses: docker://hhvm/hhvm:3.30-lts-latest diff --git a/composer.json b/composer.json index e139434..09d8b71 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "evenement/evenement": "^3.0 || ^2.0 || ^1.0" }, "require-dev": { - "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", "clue/stream-filter": "~1.2" }, "autoload": { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 7a9577e..ac542e7 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,8 +1,8 @@ - + - + diff --git a/phpunit.xml.legacy b/phpunit.xml.legacy index ac5600a..8916116 100644 --- a/phpunit.xml.legacy +++ b/phpunit.xml.legacy @@ -18,7 +18,7 @@ - + From 6273e35967202d32389b118e9ffba274163e3cd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 22 Sep 2023 17:43:17 +0200 Subject: [PATCH 57/66] Fix `drain` event of `ThroughStream` to handle potential race condition --- src/ThroughStream.php | 19 +++++++----- tests/ThroughStreamTest.php | 58 +++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 7 deletions(-) diff --git a/src/ThroughStream.php b/src/ThroughStream.php index 6f73fb8..3b4fbb7 100644 --- a/src/ThroughStream.php +++ b/src/ThroughStream.php @@ -93,16 +93,19 @@ public function __construct($callback = null) public function pause() { - $this->paused = true; + // only allow pause if still readable, false otherwise + $this->paused = $this->readable; } public function resume() { + $this->paused = false; + + // emit drain event if previous write was paused (throttled) if ($this->drain) { $this->drain = false; $this->emit('drain'); } - $this->paused = false; } public function pipe(WritableStreamInterface $dest, array $options = array()) @@ -139,12 +142,13 @@ public function write($data) $this->emit('data', array($data)); + // emit drain event on next resume if currently paused (throttled) if ($this->paused) { $this->drain = true; - return false; } - return true; + // continue writing if still writable and not paused (throttled), false otherwise + return $this->writable && !$this->paused; } public function end($data = null) @@ -164,7 +168,7 @@ public function end($data = null) $this->readable = false; $this->writable = false; - $this->paused = true; + $this->paused = false; $this->drain = false; $this->emit('end'); @@ -179,9 +183,10 @@ public function close() $this->readable = false; $this->writable = false; - $this->closed = true; - $this->paused = true; + $this->paused = false; $this->drain = false; + + $this->closed = true; $this->callback = null; $this->emit('close'); diff --git a/tests/ThroughStreamTest.php b/tests/ThroughStreamTest.php index 444f3b1..42c251a 100644 --- a/tests/ThroughStreamTest.php +++ b/tests/ThroughStreamTest.php @@ -95,6 +95,30 @@ public function itShouldReturnFalseForAnyDataWrittenToItWhenPaused() $this->assertFalse($ret); } + /** @test */ + public function itShouldReturnFalseForAnyDataWrittenToItWhenDataEventEndsStream() + { + $through = new ThroughStream(); + $through->on('data', function () use ($through) { + $through->end(); + }); + $ret = $through->write('foo'); + + $this->assertFalse($ret); + } + + /** @test */ + public function itShouldReturnFalseForAnyDataWrittenToItWhenDataEventClosesStream() + { + $through = new ThroughStream(); + $through->on('data', function () use ($through) { + $through->close(); + }); + $ret = $through->write('foo'); + + $this->assertFalse($ret); + } + /** @test */ public function itShouldEmitDrainOnResumeAfterReturnFalseForAnyDataWrittenToItWhenPaused() { @@ -106,6 +130,40 @@ public function itShouldEmitDrainOnResumeAfterReturnFalseForAnyDataWrittenToItWh $through->resume(); } + /** @test */ + public function itShouldNotEmitDrainOnResumeAfterClose() + { + $through = new ThroughStream(); + $through->close(); + + $through->on('drain', $this->expectCallableNever()); + $through->resume(); + } + + /** @test */ + public function itShouldNotEmitDrainOnResumeAfterReturnFalseForAnyDataWrittenThatCausesStreamToClose() + { + $through = new ThroughStream(); + $through->on('data', function () use ($through) { $through->close(); }); + $through->write('foo'); + + $through->on('drain', $this->expectCallableNever()); + $through->resume(); + } + + /** @test */ + public function itShouldReturnFalseForAnyDataWrittenToItAfterPausingFromDrainEvent() + { + $through = new ThroughStream(); + $through->pause(); + $through->write('foo'); + + $through->on('drain', function () use ($through) { $through->pause(); }); + $through->resume(); + + $this->assertFalse($through->write('bar')); + } + /** @test */ public function itShouldReturnTrueForAnyDataWrittenToItWhenResumedAfterPause() { From abdd922d3f5a9eafb255a9bc8b8d2f205c4770cf Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Sat, 27 Jan 2024 10:17:21 +0100 Subject: [PATCH 58/66] Hello `3.x` development branch Once this PR is merged, we can start working on the new [v3.0.0 milestone](https://github.com/reactphp/stream/milestone/22). The default branch will be `3.x` and the old `1.x` branch still stay in place at least until `3.0.0` is released. Refs: Road map ticket for stream: #173 Plans for ReactPHP v3: https://github.com/orgs/reactphp/discussions/481 PR templated from: https://github.com/friends-of-reactphp/mysql/pull/185 --- README.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2bfcbbe..3920a75 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,14 @@ Event-driven readable and writable streams for non-blocking I/O in [ReactPHP](https://reactphp.org/). +> **Development version:** This branch contains the code for the upcoming v3 +> release. For the code of the current stable v1 release, check out the +> [`1.x` branch](https://github.com/reactphp/stream/tree/1.x). +> +> The upcoming v3 release will be the way forward for this package. However, +> we will still actively support v1 for those not yet on the latest version. +> See also [installation instructions](#install) for more details. + In order to make the [EventLoop](https://github.com/reactphp/event-loop) easier to use, this component introduces the powerful concept of "streams". Streams allow you to efficiently process huge amounts of data (such as a multi @@ -1199,11 +1207,11 @@ $source->pipe($dest); The recommended way to install this library is [through Composer](https://getcomposer.org). [New to Composer?](https://getcomposer.org/doc/00-intro.md) -This project follows [SemVer](https://semver.org/). -This will install the latest supported version: +Once released, this project will follow [SemVer](https://semver.org/). +At the moment, this will install the latest development version: ```bash -composer require react/stream:^1.3 +composer require react/stream:^3@dev ``` See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. From 9bde4fdbed057edacfa063a922c2b7f6f9e29ab6 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Fri, 23 Feb 2024 22:06:08 +0100 Subject: [PATCH 59/66] Update to require PHP 7.1+ --- .github/workflows/ci.yml | 25 ------------- README.md | 5 +-- composer.json | 4 +- phpunit.xml.legacy | 2 +- src/DuplexResourceStream.php | 37 ++----------------- src/ReadableResourceStream.php | 37 ++----------------- src/WritableResourceStream.php | 2 +- tests/DuplexResourceStreamIntegrationTest.php | 20 ---------- tests/DuplexResourceStreamTest.php | 4 -- tests/FunctionalInternetTest.php | 4 +- tests/ReadableResourceStreamTest.php | 4 -- tests/WritableResourceStreamTest.php | 4 -- 12 files changed, 16 insertions(+), 132 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bc36a4d..f654fb0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,11 +19,6 @@ jobs: - 7.3 - 7.2 - 7.1 - - 7.0 - - 5.6 - - 5.5 - - 5.4 - - 5.3 steps: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 @@ -52,23 +47,3 @@ jobs: - run: composer install - run: vendor/bin/phpunit --coverage-text - run: time php examples/91-benchmark-throughput.php - - PHPUnit-hhvm: - name: PHPUnit (HHVM) - runs-on: ubuntu-22.04 - continue-on-error: true - steps: - - uses: actions/checkout@v4 - - run: cp "$(which composer)" composer.phar && ./composer.phar self-update --2.2 # downgrade Composer for HHVM - - name: Run hhvm composer.phar install - uses: docker://hhvm/hhvm:3.30-lts-latest - with: - args: hhvm composer.phar install - - name: Run hhvm vendor/bin/phpunit - uses: docker://hhvm/hhvm:3.30-lts-latest - with: - args: hhvm vendor/bin/phpunit - - name: Run time hhvm examples/91-benchmark-throughput.php - uses: docker://hhvm/hhvm:3.30-lts-latest - with: - args: bash -c "time hhvm examples/91-benchmark-throughput.php" diff --git a/README.md b/README.md index 3920a75..2ab41a1 100644 --- a/README.md +++ b/README.md @@ -1217,9 +1217,8 @@ composer require react/stream:^3@dev See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. This project aims to run on any platform and thus does not require any PHP -extensions and supports running on legacy PHP 5.3 through current PHP 8+ and HHVM. -It's *highly recommended to use PHP 7+* for this project due to its vast -performance improvements. +extensions and supports running on PHP 7.1 through current PHP 8+. +It's *highly recommended to use the latest supported PHP version* for this project. ## Tests diff --git a/composer.json b/composer.json index 09d8b71..44e750c 100644 --- a/composer.json +++ b/composer.json @@ -26,12 +26,12 @@ } ], "require": { - "php": ">=5.3.8", + "php": ">=7.1", "react/event-loop": "^1.2", "evenement/evenement": "^3.0 || ^2.0 || ^1.0" }, "require-dev": { - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "phpunit/phpunit": "^9.6 || ^5.7", "clue/stream-filter": "~1.2" }, "autoload": { diff --git a/phpunit.xml.legacy b/phpunit.xml.legacy index 8916116..a018d7a 100644 --- a/phpunit.xml.legacy +++ b/phpunit.xml.legacy @@ -2,7 +2,7 @@ diff --git a/src/DuplexResourceStream.php b/src/DuplexResourceStream.php index 4da2139..1bbd698 100644 --- a/src/DuplexResourceStream.php +++ b/src/DuplexResourceStream.php @@ -46,7 +46,7 @@ public function __construct($stream, LoopInterface $loop = null, $readChunkSize // 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) { + if (\strpos($meta['mode'], '+') === false) { throw new InvalidArgumentException('Given stream resource is not opened in read and write mode'); } @@ -59,14 +59,9 @@ public function __construct($stream, LoopInterface $loop = null, $readChunkSize // 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); - } + // trigger events on (edge triggered). This does not affect the default + // event loop implementation (level triggered). + \stream_set_read_buffer($stream, 0); if ($buffer === null) { $buffer = new WritableResourceStream($stream, $loop); @@ -200,28 +195,4 @@ public function handleData($stream) $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; - } } diff --git a/src/ReadableResourceStream.php b/src/ReadableResourceStream.php index 1b0b08c..3ef5f72 100644 --- a/src/ReadableResourceStream.php +++ b/src/ReadableResourceStream.php @@ -48,7 +48,7 @@ public function __construct($stream, LoopInterface $loop = null, $readChunkSize // 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'], '+')) { + if (\strpos($meta['mode'], 'r') === \strpos($meta['mode'], '+')) { throw new InvalidArgumentException('Given stream resource is not opened in read mode'); } @@ -61,14 +61,9 @@ public function __construct($stream, LoopInterface $loop = null, $readChunkSize // 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); - } + // trigger events on (edge triggered). This does not affect the default + // event loop implementation (level triggered). + \stream_set_read_buffer($stream, 0); $this->stream = $stream; $this->loop = $loop ?: Loop::get(); @@ -152,28 +147,4 @@ public function handleData() $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; - } } diff --git a/src/WritableResourceStream.php b/src/WritableResourceStream.php index 1af16b1..b78ec1d 100644 --- a/src/WritableResourceStream.php +++ b/src/WritableResourceStream.php @@ -36,7 +36,7 @@ public function __construct($stream, LoopInterface $loop = null, $writeBufferSof // 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']) { + if (\strtr($meta['mode'], 'waxc+', '.....') === $meta['mode']) { throw new \InvalidArgumentException('Given stream resource is not opened in write mode'); } diff --git a/tests/DuplexResourceStreamIntegrationTest.php b/tests/DuplexResourceStreamIntegrationTest.php index 7135e15..6bdf0eb 100644 --- a/tests/DuplexResourceStreamIntegrationTest.php +++ b/tests/DuplexResourceStreamIntegrationTest.php @@ -6,11 +6,7 @@ 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 @@ -26,22 +22,6 @@ 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'); diff --git a/tests/DuplexResourceStreamTest.php b/tests/DuplexResourceStreamTest.php index e61f14b..f2bbb34 100644 --- a/tests/DuplexResourceStreamTest.php +++ b/tests/DuplexResourceStreamTest.php @@ -65,10 +65,6 @@ public function testConstructorThrowsExceptionOnInvalidStream() */ public function testConstructorThrowsExceptionOnWriteOnlyStream() { - if (defined('HHVM_VERSION')) { - $this->markTestSkipped('HHVM does not report fopen mode for STDOUT'); - } - $loop = $this->createLoopMock(); $this->setExpectedException('InvalidArgumentException'); diff --git a/tests/FunctionalInternetTest.php b/tests/FunctionalInternetTest.php index 4f07537..fd062fe 100644 --- a/tests/FunctionalInternetTest.php +++ b/tests/FunctionalInternetTest.php @@ -87,8 +87,8 @@ public function testUploadBiggerBlockSecure() $stream = stream_socket_client('ssl://httpbin.org:443'); - // PHP < 7.1.4 (and PHP < 7.0.18) suffers from a bug when writing big - // chunks of data over TLS streams at once. + // PHP < 7.1.4 suffers from a bug when writing big chunks of data over + // TLS streams at once. // We work around this by limiting the write chunk size to 8192 bytes // here to also support older PHP versions. // See https://github.com/reactphp/socket/issues/105 diff --git a/tests/ReadableResourceStreamTest.php b/tests/ReadableResourceStreamTest.php index f534488..7390267 100644 --- a/tests/ReadableResourceStreamTest.php +++ b/tests/ReadableResourceStreamTest.php @@ -64,10 +64,6 @@ public function testConstructorThrowsExceptionOnInvalidStream() */ public function testConstructorThrowsExceptionOnWriteOnlyStream() { - if (defined('HHVM_VERSION')) { - $this->markTestSkipped('HHVM does not report fopen mode for STDOUT'); - } - $loop = $this->createLoopMock(); $this->setExpectedException('InvalidArgumentException'); diff --git a/tests/WritableResourceStreamTest.php b/tests/WritableResourceStreamTest.php index 678db98..e3710ac 100644 --- a/tests/WritableResourceStreamTest.php +++ b/tests/WritableResourceStreamTest.php @@ -347,10 +347,6 @@ public function testEndWithoutDataDoesNotCloseIfWritableResourceStreamIsFull() */ public function testEndWithDataClosesImmediatelyIfWritableResourceStreamFlushes() { - if (defined('HHVM_VERSION')) { - $this->markTestSkipped('Not supported on HHVM'); - } - $stream = fopen('php://temp', 'r+'); $filterBuffer = ''; $loop = $this->createLoopMock(); From a06b91490fd1fdaf87ac2670e1b59579fcc8f754 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Wed, 28 Feb 2024 06:52:59 +0100 Subject: [PATCH 60/66] Update PHP language syntax and remove legacy workarounds --- README.md | 4 ++-- examples/01-http.php | 2 +- examples/02-https.php | 2 +- examples/91-benchmark-throughput.php | 6 +++--- src/CompositeStream.php | 10 +++++----- src/DuplexResourceStream.php | 20 +++++++++---------- src/ReadableResourceStream.php | 8 ++++---- src/ReadableStreamInterface.php | 4 ++-- src/ThroughStream.php | 8 ++++---- src/Util.php | 4 ++-- src/WritableResourceStream.php | 4 ++-- tests/CompositeStreamTest.php | 6 +++--- tests/DuplexResourceStreamIntegrationTest.php | 4 ++-- tests/DuplexResourceStreamTest.php | 8 ++++---- tests/FunctionalInternetTest.php | 5 ++--- tests/ReadableResourceStreamTest.php | 6 +++--- tests/Stub/ReadableStreamStub.php | 8 ++++---- tests/TestCase.php | 4 ++-- tests/ThroughStreamTest.php | 4 ++-- tests/UtilTest.php | 8 ++++---- tests/WritableResourceStreamTest.php | 8 ++++---- 21 files changed, 65 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index 2ab41a1..cce1408 100644 --- a/README.md +++ b/README.md @@ -353,7 +353,7 @@ 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)); +$source->pipe($dest, ['end' => false]); ``` Note that this only applies to the `end` event. @@ -1126,7 +1126,7 @@ $through = new ThroughStream(function ($data) { }); $through->on('data', $this->expectCallableOnceWith("[2, true]\n")); -$through->write(array(2, true)); +$through->write([2, true]); ``` The callback function is allowed to throw an `Exception`. In this case, diff --git a/examples/01-http.php b/examples/01-http.php index e70691d..786aefc 100644 --- a/examples/01-http.php +++ b/examples/01-http.php @@ -15,7 +15,7 @@ require __DIR__ . '/../vendor/autoload.php'; -$host = isset($argv[1]) ? $argv[1] : 'www.google.com'; +$host = $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! diff --git a/examples/02-https.php b/examples/02-https.php index 1d212da..e629791 100644 --- a/examples/02-https.php +++ b/examples/02-https.php @@ -15,7 +15,7 @@ require __DIR__ . '/../vendor/autoload.php'; -$host = isset($argv[1]) ? $argv[1] : 'www.google.com'; +$host = $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! diff --git a/examples/91-benchmark-throughput.php b/examples/91-benchmark-throughput.php index 4203950..c396606 100644 --- a/examples/91-benchmark-throughput.php +++ b/examples/91-benchmark-throughput.php @@ -21,9 +21,9 @@ } $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; +$if = $args['i'] ?? '/dev/zero'; +$of = $args['o'] ?? '/dev/null'; +$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); diff --git a/src/CompositeStream.php b/src/CompositeStream.php index dde091d..d0b934b 100644 --- a/src/CompositeStream.php +++ b/src/CompositeStream.php @@ -20,11 +20,11 @@ public function __construct(ReadableStreamInterface $readable, WritableStreamInt return; } - Util::forwardEvents($this->readable, $this, array('data', 'end', 'error')); - Util::forwardEvents($this->writable, $this, array('drain', 'error', 'pipe')); + Util::forwardEvents($this->readable, $this, ['data', 'end', 'error']); + Util::forwardEvents($this->writable, $this, ['drain', 'error', 'pipe']); - $this->readable->on('close', array($this, 'close')); - $this->writable->on('close', array($this, 'close')); + $this->readable->on('close', [$this, 'close']); + $this->writable->on('close', [$this, 'close']); } public function isReadable() @@ -46,7 +46,7 @@ public function resume() $this->readable->resume(); } - public function pipe(WritableStreamInterface $dest, array $options = array()) + public function pipe(WritableStreamInterface $dest, array $options = []) { return Util::pipe($this, $dest, $options); } diff --git a/src/DuplexResourceStream.php b/src/DuplexResourceStream.php index 1bbd698..0b52c9b 100644 --- a/src/DuplexResourceStream.php +++ b/src/DuplexResourceStream.php @@ -72,16 +72,14 @@ public function __construct($stream, LoopInterface $loop = null, $readChunkSize $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('error', function ($error) { + $this->emit('error', [$error]); }); - $this->buffer->on('close', array($this, 'close')); + $this->buffer->on('close', [$this, 'close']); - $this->buffer->on('drain', function () use ($that) { - $that->emit('drain'); + $this->buffer->on('drain', function () { + $this->emit('drain'); }); $this->resume(); @@ -108,7 +106,7 @@ public function pause() public function resume() { if (!$this->listening && $this->readable) { - $this->loop->addReadStream($this->stream, array($this, 'handleData')); + $this->loop->addReadStream($this->stream, [$this, 'handleData']); $this->listening = true; } } @@ -158,7 +156,7 @@ public function end($data = null) $this->buffer->end($data); } - public function pipe(WritableStreamInterface $dest, array $options = array()) + public function pipe(WritableStreamInterface $dest, array $options = []) { return Util::pipe($this, $dest, $options); } @@ -182,13 +180,13 @@ public function handleData($stream) \restore_error_handler(); if ($error !== null) { - $this->emit('error', array(new \RuntimeException('Unable to read from stream: ' . $error->getMessage(), 0, $error))); + $this->emit('error', [new \RuntimeException('Unable to read from stream: ' . $error->getMessage(), 0, $error)]); $this->close(); return; } if ($data !== '') { - $this->emit('data', array($data)); + $this->emit('data', [$data]); } elseif (\feof($this->stream)) { // no data read => we reached the end and close the stream $this->emit('end'); diff --git a/src/ReadableResourceStream.php b/src/ReadableResourceStream.php index 3ef5f72..41314fe 100644 --- a/src/ReadableResourceStream.php +++ b/src/ReadableResourceStream.php @@ -88,12 +88,12 @@ public function pause() public function resume() { if (!$this->listening && !$this->closed) { - $this->loop->addReadStream($this->stream, array($this, 'handleData')); + $this->loop->addReadStream($this->stream, [$this, 'handleData']); $this->listening = true; } } - public function pipe(WritableStreamInterface $dest, array $options = array()) + public function pipe(WritableStreamInterface $dest, array $options = []) { return Util::pipe($this, $dest, $options); } @@ -134,13 +134,13 @@ public function handleData() \restore_error_handler(); if ($error !== null) { - $this->emit('error', array(new \RuntimeException('Unable to read from stream: ' . $error->getMessage(), 0, $error))); + $this->emit('error', [new \RuntimeException('Unable to read from stream: ' . $error->getMessage(), 0, $error)]); $this->close(); return; } if ($data !== '') { - $this->emit('data', array($data)); + $this->emit('data', [$data]); } elseif (\feof($this->stream)) { // no data read => we reached the end and close the stream $this->emit('end'); diff --git a/src/ReadableStreamInterface.php b/src/ReadableStreamInterface.php index fa3d59c..ecc5267 100644 --- a/src/ReadableStreamInterface.php +++ b/src/ReadableStreamInterface.php @@ -278,7 +278,7 @@ public function resume(); * source stream emits an `end` event. This can be disabled like this: * * ```php - * $source->pipe($dest, array('end' => false)); + * $source->pipe($dest, ['end' => false]); * ``` * * Note that this only applies to the `end` event. @@ -322,7 +322,7 @@ public function resume(); * @param array $options * @return WritableStreamInterface $dest stream as-is */ - public function pipe(WritableStreamInterface $dest, array $options = array()); + public function pipe(WritableStreamInterface $dest, array $options = []); /** * Closes the stream (forcefully). diff --git a/src/ThroughStream.php b/src/ThroughStream.php index 3b4fbb7..c49ebfc 100644 --- a/src/ThroughStream.php +++ b/src/ThroughStream.php @@ -48,7 +48,7 @@ * }); * $through->on('data', $this->expectCallableOnceWith("[2, true]\n")); * - * $through->write(array(2, true)); + * $through->write([2, true]); * ``` * * The callback function is allowed to throw an `Exception`. In this case, @@ -108,7 +108,7 @@ public function resume() } } - public function pipe(WritableStreamInterface $dest, array $options = array()) + public function pipe(WritableStreamInterface $dest, array $options = []) { return Util::pipe($this, $dest, $options); } @@ -133,14 +133,14 @@ public function write($data) try { $data = \call_user_func($this->callback, $data); } catch (\Exception $e) { - $this->emit('error', array($e)); + $this->emit('error', [$e]); $this->close(); return false; } } - $this->emit('data', array($data)); + $this->emit('data', [$data]); // emit drain event on next resume if currently paused (throttled) if ($this->paused) { diff --git a/src/Util.php b/src/Util.php index 056b037..114ccb8 100644 --- a/src/Util.php +++ b/src/Util.php @@ -13,7 +13,7 @@ final class Util * @return WritableStreamInterface $dest stream as-is * @see ReadableStreamInterface::pipe() for more details */ - public static function pipe(ReadableStreamInterface $source, WritableStreamInterface $dest, array $options = array()) + public static function pipe(ReadableStreamInterface $source, WritableStreamInterface $dest, array $options = []) { // source not readable => NO-OP if (!$source->isReadable()) { @@ -27,7 +27,7 @@ public static function pipe(ReadableStreamInterface $source, WritableStreamInter return $dest; } - $dest->emit('pipe', array($source)); + $dest->emit('pipe', [$source]); // forward all source data events as $dest->write() $source->on('data', $dataer = function ($data) use ($source, $dest) { diff --git a/src/WritableResourceStream.php b/src/WritableResourceStream.php index b78ec1d..9665721 100644 --- a/src/WritableResourceStream.php +++ b/src/WritableResourceStream.php @@ -68,7 +68,7 @@ public function write($data) if (!$this->listening && $this->data !== '') { $this->listening = true; - $this->loop->addWriteStream($this->stream, array($this, 'handleWrite')); + $this->loop->addWriteStream($this->stream, [$this, 'handleWrite']); } return !isset($this->data[$this->softLimit - 1]); @@ -137,7 +137,7 @@ public function handleWrite() // 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) && $error !== null) { - $this->emit('error', array(new \RuntimeException('Unable to write to stream: ' . $error))); + $this->emit('error', [new \RuntimeException('Unable to write to stream: ' . $error)]); $this->close(); return; diff --git a/tests/CompositeStreamTest.php b/tests/CompositeStreamTest.php index df89c3e..c858e6b 100644 --- a/tests/CompositeStreamTest.php +++ b/tests/CompositeStreamTest.php @@ -217,7 +217,7 @@ public function itShouldReceiveForwardedEvents() $composite->on('data', $this->expectCallableOnce()); $composite->on('drain', $this->expectCallableOnce()); - $readable->emit('data', array('foo')); + $readable->emit('data', ['foo']); $writable->emit('drain'); } @@ -241,7 +241,7 @@ public function itShouldHandlePipingCorrectly() $input = new ThroughStream(); $input->pipe($composite); - $input->emit('data', array('foo')); + $input->emit('data', ['foo']); } /** @test */ @@ -262,6 +262,6 @@ public function itShouldForwardPipeCallsToReadableStream() ->with('foo'); $composite->pipe($output); - $readable->emit('data', array('foo')); + $readable->emit('data', ['foo']); } } diff --git a/tests/DuplexResourceStreamIntegrationTest.php b/tests/DuplexResourceStreamIntegrationTest.php index 6bdf0eb..196a319 100644 --- a/tests/DuplexResourceStreamIntegrationTest.php +++ b/tests/DuplexResourceStreamIntegrationTest.php @@ -2,12 +2,12 @@ namespace React\Tests\Stream; -use Clue\StreamFilter as Filter; use React\Stream\DuplexResourceStream; use React\Stream\ReadableResourceStream; use React\EventLoop\ExtEventLoop; use React\EventLoop\LoopInterface; use React\EventLoop\StreamSelectLoop; +use function Clue\StreamFilter\append as filter_append; class DuplexResourceStreamIntegrationTest extends TestCase { @@ -340,7 +340,7 @@ public function testEmptyReadShouldntFcloseStream($condition, $loopFactory) // add a filter which returns an error when encountering an 'a' when reading - Filter\append($stream, function ($chunk) { + filter_append($stream, function ($chunk) { return ''; }, STREAM_FILTER_READ); diff --git a/tests/DuplexResourceStreamTest.php b/tests/DuplexResourceStreamTest.php index f2bbb34..f81fa90 100644 --- a/tests/DuplexResourceStreamTest.php +++ b/tests/DuplexResourceStreamTest.php @@ -3,8 +3,8 @@ namespace React\Tests\Stream; use React\Stream\DuplexResourceStream; -use Clue\StreamFilter as Filter; use React\Stream\WritableResourceStream; +use function Clue\StreamFilter\append as filter_append; class DuplexResourceStreamTest extends TestCase { @@ -423,7 +423,7 @@ public function testBufferEventsShouldBubbleUp() $conn->on('error', $this->expectCallableOnce()); $buffer->emit('drain'); - $buffer->emit('error', array(new \RuntimeException('Whoops'))); + $buffer->emit('error', [new \RuntimeException('Whoops')]); } /** @@ -454,7 +454,7 @@ public function testDataFiltered() $stream = fopen('php://temp', 'r+'); // add a filter which removes every 'a' when reading - Filter\append($stream, function ($chunk) { + filter_append($stream, function ($chunk) { return str_replace('a', '', $chunk); }, STREAM_FILTER_READ); @@ -482,7 +482,7 @@ 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) { + filter_append($stream, function ($chunk) { if (strpos($chunk, 'a') !== false) { throw new \Exception('Invalid'); } diff --git a/tests/FunctionalInternetTest.php b/tests/FunctionalInternetTest.php index fd062fe..5113e7b 100644 --- a/tests/FunctionalInternetTest.php +++ b/tests/FunctionalInternetTest.php @@ -120,10 +120,9 @@ private function awaitStreamClose(DuplexResourceStream $stream, LoopInterface $l $loop->stop(); }); - $that = $this; - $loop->addTimer($timeout, function () use ($loop, $that) { + $loop->addTimer($timeout, function () use ($loop) { $loop->stop(); - $that->fail('Timed out while waiting for stream to close'); + $this->fail('Timed out while waiting for stream to close'); }); $loop->run(); diff --git a/tests/ReadableResourceStreamTest.php b/tests/ReadableResourceStreamTest.php index 7390267..5d1f802 100644 --- a/tests/ReadableResourceStreamTest.php +++ b/tests/ReadableResourceStreamTest.php @@ -3,7 +3,7 @@ namespace React\Tests\Stream; use React\Stream\ReadableResourceStream; -use Clue\StreamFilter as Filter; +use function Clue\StreamFilter\append as filter_append; class ReadableResourceStreamTest extends TestCase { @@ -326,7 +326,7 @@ public function testDataFiltered() $stream = fopen('php://temp', 'r+'); // add a filter which removes every 'a' when reading - Filter\append($stream, function ($chunk) { + filter_append($stream, function ($chunk) { return str_replace('a', '', $chunk); }, STREAM_FILTER_READ); @@ -354,7 +354,7 @@ 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) { + filter_append($stream, function ($chunk) { if (strpos($chunk, 'a') !== false) { throw new \Exception('Invalid'); } diff --git a/tests/Stub/ReadableStreamStub.php b/tests/Stub/ReadableStreamStub.php index 6984f24..3fa56a8 100644 --- a/tests/Stub/ReadableStreamStub.php +++ b/tests/Stub/ReadableStreamStub.php @@ -20,19 +20,19 @@ public function isReadable() // trigger data event public function write($data) { - $this->emit('data', array($data)); + $this->emit('data', [$data]); } // trigger error event public function error($error) { - $this->emit('error', array($error)); + $this->emit('error', [$error]); } // trigger end event public function end() { - $this->emit('end', array()); + $this->emit('end', []); } public function pause() @@ -52,7 +52,7 @@ public function close() $this->emit('close'); } - public function pipe(WritableStreamInterface $dest, array $options = array()) + public function pipe(WritableStreamInterface $dest, array $options = []) { Util::pipe($this, $dest, $options); diff --git a/tests/TestCase.php b/tests/TestCase.php index af07e3e..4bca070 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -41,10 +41,10 @@ protected function createCallableMock() { if (method_exists('PHPUnit\Framework\MockObject\MockBuilder', 'addMethods')) { // PHPUnit 9+ - return $this->getMockBuilder('stdClass')->addMethods(array('__invoke'))->getMock(); + return $this->getMockBuilder('stdClass')->addMethods(['__invoke'])->getMock(); } else { // legacy PHPUnit 4 - PHPUnit 9 - return $this->getMockBuilder('stdClass')->setMethods(array('__invoke'))->getMock(); + return $this->getMockBuilder('stdClass')->setMethods(['__invoke'])->getMock(); } } diff --git a/tests/ThroughStreamTest.php b/tests/ThroughStreamTest.php index 42c251a..3093136 100644 --- a/tests/ThroughStreamTest.php +++ b/tests/ThroughStreamTest.php @@ -185,7 +185,7 @@ public function pipingStuffIntoItShouldWork() $through->on('data', $this->expectCallableOnceWith('foo')); $readable->pipe($through); - $readable->emit('data', array('foo')); + $readable->emit('data', ['foo']); } /** @test */ @@ -243,7 +243,7 @@ public function writeAfterEndShouldReturnFalse() public function writeDataWillCloseStreamShouldReturnFalse() { $through = new ThroughStream(); - $through->on('data', array($through, 'close')); + $through->on('data', [$through, 'close']); $this->assertFalse($through->write('foo')); } diff --git a/tests/UtilTest.php b/tests/UtilTest.php index f12baad..47d3646 100644 --- a/tests/UtilTest.php +++ b/tests/UtilTest.php @@ -114,7 +114,7 @@ public function testPipeWithoutEnd() ->expects($this->never()) ->method('end'); - Util::pipe($readable, $writable, array('end' => false)); + Util::pipe($readable, $writable, ['end' => false]); $readable->end(); } @@ -253,12 +253,12 @@ public function forwardEventsShouldSetupForwards() $source = new ThroughStream(); $target = new ThroughStream(); - Util::forwardEvents($source, $target, array('data')); + Util::forwardEvents($source, $target, ['data']); $target->on('data', $this->expectCallableOnce()); $target->on('foo', $this->expectCallableNever()); - $source->emit('data', array('hello')); - $source->emit('foo', array('bar')); + $source->emit('data', ['hello']); + $source->emit('foo', ['bar']); } private function createLoopMock() diff --git a/tests/WritableResourceStreamTest.php b/tests/WritableResourceStreamTest.php index e3710ac..a05adc8 100644 --- a/tests/WritableResourceStreamTest.php +++ b/tests/WritableResourceStreamTest.php @@ -2,8 +2,8 @@ namespace React\Tests\Stream; -use Clue\StreamFilter as Filter; use React\Stream\WritableResourceStream; +use function Clue\StreamFilter\append as filter_append; class WritableResourceStreamTest extends TestCase { @@ -355,7 +355,7 @@ public function testEndWithDataClosesImmediatelyIfWritableResourceStreamFlushes( $buffer->on('error', $this->expectCallableNever()); $buffer->on('close', $this->expectCallableOnce()); - Filter\append($stream, function ($chunk) use (&$filterBuffer) { + filter_append($stream, function ($chunk) use (&$filterBuffer) { $filterBuffer .= $chunk; return $chunk; }); @@ -407,7 +407,7 @@ public function testClose() $buffer->close(); $this->assertFalse($buffer->isWritable()); - $this->assertEquals(array(), $buffer->listeners('close')); + $this->assertEquals([], $buffer->listeners('close')); } /** @@ -466,7 +466,7 @@ public function testWritingToClosedWritableResourceStreamShouldNotWriteToStream( $buffer = new WritableResourceStream($stream, $loop); - Filter\append($stream, function ($chunk) use (&$filterBuffer) { + filter_append($stream, function ($chunk) use (&$filterBuffer) { $filterBuffer .= $chunk; return $chunk; }); From 1bb959597975c7f6c879d3a3a66b7b95ba25f65f Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Wed, 28 Feb 2024 07:09:20 +0100 Subject: [PATCH 61/66] Update test suite and remove legacy PHPUnit workarounds --- composer.json | 2 +- phpunit.xml.legacy | 2 +- tests/CompositeStreamTest.php | 38 ++++++------ tests/DuplexResourceStreamIntegrationTest.php | 34 +++++------ tests/DuplexResourceStreamTest.php | 22 +++---- tests/ReadableResourceStreamTest.php | 14 +++-- tests/TestCase.php | 59 ++----------------- tests/ThroughStreamTest.php | 5 +- tests/UtilTest.php | 40 ++++++------- tests/WritableResourceStreamTest.php | 15 ++--- 10 files changed, 93 insertions(+), 138 deletions(-) diff --git a/composer.json b/composer.json index 44e750c..b8d34d4 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "evenement/evenement": "^3.0 || ^2.0 || ^1.0" }, "require-dev": { - "phpunit/phpunit": "^9.6 || ^5.7", + "phpunit/phpunit": "^9.6 || ^7.5", "clue/stream-filter": "~1.2" }, "autoload": { diff --git a/phpunit.xml.legacy b/phpunit.xml.legacy index a018d7a..0086860 100644 --- a/phpunit.xml.legacy +++ b/phpunit.xml.legacy @@ -2,7 +2,7 @@ diff --git a/tests/CompositeStreamTest.php b/tests/CompositeStreamTest.php index c858e6b..75a0426 100644 --- a/tests/CompositeStreamTest.php +++ b/tests/CompositeStreamTest.php @@ -3,7 +3,9 @@ namespace React\Tests\Stream; use React\Stream\CompositeStream; +use React\Stream\ReadableStreamInterface; use React\Stream\ThroughStream; +use React\Stream\WritableStreamInterface; /** * @covers React\Stream\CompositeStream @@ -13,7 +15,7 @@ class CompositeStreamTest extends TestCase /** @test */ public function itShouldCloseReadableIfNotWritable() { - $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable = $this->createMock(ReadableStreamInterface::class); $readable ->expects($this->once()) ->method('isReadable') @@ -22,7 +24,7 @@ public function itShouldCloseReadableIfNotWritable() ->expects($this->once()) ->method('close'); - $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable = $this->createMock(WritableStreamInterface::class); $writable ->expects($this->once()) ->method('isWritable') @@ -37,13 +39,13 @@ public function itShouldCloseReadableIfNotWritable() /** @test */ public function itShouldCloseWritableIfNotReadable() { - $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable = $this->createMock(ReadableStreamInterface::class); $readable ->expects($this->once()) ->method('isReadable') ->willReturn(false); - $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable = $this->createMock(WritableStreamInterface::class); $writable ->expects($this->once()) ->method('close'); @@ -57,13 +59,13 @@ public function itShouldCloseWritableIfNotReadable() /** @test */ public function itShouldForwardWritableCallsToWritableStream() { - $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable = $this->createMock(ReadableStreamInterface::class); $readable ->expects($this->once()) ->method('isReadable') ->willReturn(true); - $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable = $this->createMock(WritableStreamInterface::class); $writable ->expects($this->once()) ->method('write') @@ -81,7 +83,7 @@ public function itShouldForwardWritableCallsToWritableStream() /** @test */ public function itShouldForwardReadableCallsToReadableStream() { - $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable = $this->createMock(ReadableStreamInterface::class); $readable ->expects($this->exactly(2)) ->method('isReadable') @@ -93,7 +95,7 @@ public function itShouldForwardReadableCallsToReadableStream() ->expects($this->once()) ->method('resume'); - $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable = $this->createMock(WritableStreamInterface::class); $writable ->expects($this->any()) ->method('isWritable') @@ -108,7 +110,7 @@ public function itShouldForwardReadableCallsToReadableStream() /** @test */ public function itShouldNotForwardResumeIfStreamIsNotWritable() { - $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable = $this->createMock(ReadableStreamInterface::class); $readable ->expects($this->once()) ->method('isReadable') @@ -117,7 +119,7 @@ public function itShouldNotForwardResumeIfStreamIsNotWritable() ->expects($this->never()) ->method('resume'); - $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable = $this->createMock(WritableStreamInterface::class); $writable ->expects($this->exactly(2)) ->method('isWritable') @@ -130,13 +132,13 @@ public function itShouldNotForwardResumeIfStreamIsNotWritable() /** @test */ public function endShouldDelegateToWritableWithData() { - $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable = $this->createMock(ReadableStreamInterface::class); $readable ->expects($this->once()) ->method('isReadable') ->willReturn(true); - $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable = $this->createMock(WritableStreamInterface::class); $writable ->expects($this->once()) ->method('isWritable') @@ -153,7 +155,7 @@ public function endShouldDelegateToWritableWithData() /** @test */ public function closeShouldCloseBothStreams() { - $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable = $this->createMock(ReadableStreamInterface::class); $readable ->expects($this->once()) ->method('isReadable') @@ -162,7 +164,7 @@ public function closeShouldCloseBothStreams() ->expects($this->once()) ->method('close'); - $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable = $this->createMock(WritableStreamInterface::class); $writable ->expects($this->once()) ->method('isWritable') @@ -224,13 +226,13 @@ public function itShouldReceiveForwardedEvents() /** @test */ public function itShouldHandlePipingCorrectly() { - $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable = $this->createMock(ReadableStreamInterface::class); $readable ->expects($this->once()) ->method('isReadable') ->willReturn(true); - $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable = $this->createMock(WritableStreamInterface::class); $writable->expects($this->any())->method('isWritable')->willReturn(True); $writable ->expects($this->once()) @@ -249,12 +251,12 @@ public function itShouldForwardPipeCallsToReadableStream() { $readable = new ThroughStream(); - $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable = $this->createMock(WritableStreamInterface::class); $writable->expects($this->any())->method('isWritable')->willReturn(True); $composite = new CompositeStream($readable, $writable); - $output = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $output = $this->createMock(WritableStreamInterface::class); $output->expects($this->any())->method('isWritable')->willReturn(True); $output ->expects($this->once()) diff --git a/tests/DuplexResourceStreamIntegrationTest.php b/tests/DuplexResourceStreamIntegrationTest.php index 196a319..02da5d2 100644 --- a/tests/DuplexResourceStreamIntegrationTest.php +++ b/tests/DuplexResourceStreamIntegrationTest.php @@ -13,24 +13,22 @@ class DuplexResourceStreamIntegrationTest extends TestCase { public function loopProvider() { - return array( - array( - function() { - return true; - }, - function () { - return new StreamSelectLoop(); - } - ), - array( - function () { - return class_exists('EventBase') && class_exists('React\EventLoop\ExtEventLoop'); - }, - function () { - return new ExtEventLoop(); - } - ) - ); + yield [ + function() { + return true; + }, + function () { + return new StreamSelectLoop(); + } + ]; + yield [ + function () { + return class_exists('EventBase'); + }, + function () { + return new ExtEventLoop(); + } + ]; } /** diff --git a/tests/DuplexResourceStreamTest.php b/tests/DuplexResourceStreamTest.php index f81fa90..52477fe 100644 --- a/tests/DuplexResourceStreamTest.php +++ b/tests/DuplexResourceStreamTest.php @@ -2,8 +2,10 @@ namespace React\Tests\Stream; +use React\EventLoop\LoopInterface; use React\Stream\DuplexResourceStream; use React\Stream\WritableResourceStream; +use React\Stream\WritableStreamInterface; use function Clue\StreamFilter\append as filter_append; class DuplexResourceStreamTest extends TestCase @@ -56,7 +58,7 @@ public function testConstructorThrowsExceptionOnInvalidStream() { $loop = $this->createLoopMock(); - $this->setExpectedException('InvalidArgumentException'); + $this->expectException(\InvalidArgumentException::class); new DuplexResourceStream('breakme', $loop); } @@ -67,7 +69,7 @@ public function testConstructorThrowsExceptionOnWriteOnlyStream() { $loop = $this->createLoopMock(); - $this->setExpectedException('InvalidArgumentException'); + $this->expectException(\InvalidArgumentException::class); new DuplexResourceStream(STDOUT, $loop); } @@ -82,7 +84,7 @@ public function testConstructorThrowsExceptionOnWriteOnlyStreamWithExcessiveMode unlink($name); $loop = $this->createLoopMock(); - $this->setExpectedException('InvalidArgumentException'); + $this->expectException(\InvalidArgumentException::class); new DuplexResourceStream($stream, $loop); } @@ -98,7 +100,7 @@ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking( $stream = fopen('blocking://test', 'r+'); $loop = $this->createLoopMock(); - $this->setExpectedException('RunTimeException'); + $this->expectException(\RuntimeException::class); new DuplexResourceStream($stream, $loop); } @@ -111,7 +113,7 @@ public function testConstructorAcceptsBuffer() $stream = fopen('php://temp', 'r+'); $loop = $this->createLoopMock(); - $buffer = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $buffer = $this->createMock(WritableStreamInterface::class); new DuplexResourceStream($stream, $loop, null, $buffer); } @@ -130,7 +132,7 @@ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlockingW $buffer = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); - $this->setExpectedException('RunTimeException'); + $this->expectException(\RuntimeException::class); new DuplexResourceStream($stream, $loop, null, $buffer); } @@ -153,7 +155,7 @@ public function testEndShouldEndBuffer() $stream = fopen('php://temp', 'r+'); $loop = $this->createLoopMock(); - $buffer = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $buffer = $this->createMock(WritableStreamInterface::class); $buffer->expects($this->once())->method('end')->with('foo'); $conn = new DuplexResourceStream($stream, $loop, null, $buffer); @@ -166,7 +168,7 @@ public function testEndAfterCloseIsNoOp() $stream = fopen('php://temp', 'r+'); $loop = $this->createLoopMock(); - $buffer = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $buffer = $this->createMock(WritableStreamInterface::class); $buffer->expects($this->never())->method('end'); $conn = new DuplexResourceStream($stream, $loop); @@ -406,7 +408,7 @@ public function testPipeShouldReturnDestination() $loop = $this->createLoopMock(); $conn = new DuplexResourceStream($stream, $loop); - $dest = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $dest = $this->createMock(WritableStreamInterface::class); $this->assertSame($dest, $conn->pipe($dest)); } @@ -517,6 +519,6 @@ private function createWriteableLoopMock() private function createLoopMock() { - return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + return $this->createMock(LoopInterface::class); } } diff --git a/tests/ReadableResourceStreamTest.php b/tests/ReadableResourceStreamTest.php index 5d1f802..4cebb5a 100644 --- a/tests/ReadableResourceStreamTest.php +++ b/tests/ReadableResourceStreamTest.php @@ -2,7 +2,9 @@ namespace React\Tests\Stream; +use React\EventLoop\LoopInterface; use React\Stream\ReadableResourceStream; +use React\Stream\WritableStreamInterface; use function Clue\StreamFilter\append as filter_append; class ReadableResourceStreamTest extends TestCase @@ -55,7 +57,7 @@ public function testConstructorThrowsExceptionOnInvalidStream() { $loop = $this->createLoopMock(); - $this->setExpectedException('InvalidArgumentException'); + $this->expectException(\InvalidArgumentException::class); new ReadableResourceStream(false, $loop); } @@ -66,7 +68,7 @@ public function testConstructorThrowsExceptionOnWriteOnlyStream() { $loop = $this->createLoopMock(); - $this->setExpectedException('InvalidArgumentException'); + $this->expectException(\InvalidArgumentException::class); new ReadableResourceStream(STDOUT, $loop); } @@ -81,7 +83,7 @@ public function testConstructorThrowsExceptionOnWriteOnlyStreamWithExcessiveMode unlink($name); $loop = $this->createLoopMock(); - $this->setExpectedException('InvalidArgumentException'); + $this->expectException(\InvalidArgumentException::class); new ReadableResourceStream($stream, $loop); } @@ -97,7 +99,7 @@ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking( $stream = fopen('blocking://test', 'r+'); $loop = $this->createLoopMock(); - $this->setExpectedException('RuntimeException'); + $this->expectException(\RuntimeException::class); new ReadableResourceStream($stream, $loop); } @@ -221,7 +223,7 @@ public function testPipeShouldReturnDestination() $loop = $this->createLoopMock(); $conn = new ReadableResourceStream($stream, $loop); - $dest = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $dest = $this->createMock(WritableStreamInterface::class); $this->assertSame($dest, $conn->pipe($dest)); } @@ -395,6 +397,6 @@ public function testEmptyReadShouldntFcloseStream() private function createLoopMock() { - return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + return $this->createMock(LoopInterface::class); } } diff --git a/tests/TestCase.php b/tests/TestCase.php index 4bca070..b161c46 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -39,62 +39,13 @@ protected function expectCallableNever() protected function createCallableMock() { - if (method_exists('PHPUnit\Framework\MockObject\MockBuilder', 'addMethods')) { + $builder = $this->getMockBuilder(\stdClass::class); + if (method_exists($builder, 'addMethods')) { // PHPUnit 9+ - return $this->getMockBuilder('stdClass')->addMethods(['__invoke'])->getMock(); + return $builder->addMethods(['__invoke'])->getMock(); } else { - // legacy PHPUnit 4 - PHPUnit 9 - return $this->getMockBuilder('stdClass')->setMethods(['__invoke'])->getMock(); - } - } - - public function setExpectedException($exception, $exceptionMessage = '', $exceptionCode = null) - { - if (method_exists($this, 'expectException')) { - // PHPUnit 5.2+ - $this->expectException($exception); - if ($exceptionMessage !== '') { - $this->expectExceptionMessage($exceptionMessage); - } - if ($exceptionCode !== null) { - $this->expectExceptionCode($exceptionCode); - } - } else { - // legacy PHPUnit 4 - PHPUnit 5.1 - parent::setExpectedException($exception, $exceptionMessage, $exceptionCode); - } - } - - public function assertContainsString($needle, $haystack) - { - if (method_exists($this, 'assertStringContainsString')) { - // PHPUnit 7.5+ - $this->assertStringContainsString($needle, $haystack); - } else { - // legacy PHPUnit 4 - PHPUnit 7.5 - $this->assertContains($needle, $haystack); - } - } - - public function assertContainsStringIgnoringCase($needle, $haystack) - { - if (method_exists($this, 'assertStringContainsStringIgnoringCase')) { - // PHPUnit 7.5+ - $this->assertStringContainsStringIgnoringCase($needle, $haystack); - } else { - // legacy PHPUnit 4 - PHPUnit 7.5 - $this->assertContains($needle, $haystack, '', true); - } - } - - public function assertSameIgnoringCase($expected, $actual) - { - if (method_exists($this, 'assertEqualsIgnoringCase')) { - // PHPUnit 7.5+ - $this->assertEqualsIgnoringCase($expected, $actual); - } else { - // legacy PHPUnit 4 - PHPUnit 7.5 - $this->assertSame($expected, $actual); + // legacy PHPUnit + return $builder->setMethods(['__invoke'])->getMock(); } } } diff --git a/tests/ThroughStreamTest.php b/tests/ThroughStreamTest.php index 3093136..04a49d0 100644 --- a/tests/ThroughStreamTest.php +++ b/tests/ThroughStreamTest.php @@ -3,6 +3,7 @@ namespace React\Tests\Stream; use React\Stream\ThroughStream; +use React\Stream\WritableStreamInterface; /** * @covers React\Stream\ThroughStream @@ -14,7 +15,7 @@ class ThroughStreamTest extends TestCase */ public function itShouldRejectInvalidCallback() { - $this->setExpectedException('InvalidArgumentException'); + $this->expectException(\InvalidArgumentException::class); new ThroughStream(123); } @@ -311,7 +312,7 @@ public function doubleCloseShouldCloseOnce() /** @test */ public function pipeShouldPipeCorrectly() { - $output = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $output = $this->createMock(WritableStreamInterface::class); $output->expects($this->any())->method('isWritable')->willReturn(True); $output ->expects($this->once()) diff --git a/tests/UtilTest.php b/tests/UtilTest.php index 47d3646..50b2e75 100644 --- a/tests/UtilTest.php +++ b/tests/UtilTest.php @@ -2,10 +2,13 @@ namespace React\Tests\Stream; -use React\Stream\WritableResourceStream; -use React\Stream\Util; +use React\EventLoop\LoopInterface; use React\Stream\CompositeStream; +use React\Stream\ReadableStreamInterface; use React\Stream\ThroughStream; +use React\Stream\Util; +use React\Stream\WritableResourceStream; +use React\Stream\WritableStreamInterface; /** * @covers React\Stream\Util @@ -14,9 +17,9 @@ class UtilTest extends TestCase { public function testPipeReturnsDestinationStream() { - $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable = $this->createMock(ReadableStreamInterface::class); - $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable = $this->createMock(WritableStreamInterface::class); $ret = Util::pipe($readable, $writable); @@ -25,13 +28,13 @@ public function testPipeReturnsDestinationStream() public function testPipeNonReadableSourceShouldDoNothing() { - $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable = $this->createMock(ReadableStreamInterface::class); $readable ->expects($this->any()) ->method('isReadable') ->willReturn(false); - $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable = $this->createMock(WritableStreamInterface::class); $writable ->expects($this->never()) ->method('isWritable'); @@ -44,7 +47,7 @@ public function testPipeNonReadableSourceShouldDoNothing() public function testPipeIntoNonWritableDestinationShouldPauseSource() { - $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable = $this->createMock(ReadableStreamInterface::class); $readable ->expects($this->any()) ->method('isReadable') @@ -53,7 +56,7 @@ public function testPipeIntoNonWritableDestinationShouldPauseSource() ->expects($this->once()) ->method('pause'); - $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable = $this->createMock(WritableStreamInterface::class); $writable ->expects($this->any()) ->method('isWritable') @@ -67,7 +70,7 @@ public function testPipeIntoNonWritableDestinationShouldPauseSource() public function testPipeClosingDestPausesSource() { - $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable = $this->createMock(ReadableStreamInterface::class); $readable ->expects($this->any()) ->method('isReadable') @@ -87,7 +90,7 @@ public function testPipeWithEnd() { $readable = new Stub\ReadableStreamStub(); - $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable = $this->createMock(WritableStreamInterface::class); $writable ->expects($this->any()) ->method('isWritable') @@ -105,7 +108,7 @@ public function testPipeWithoutEnd() { $readable = new Stub\ReadableStreamStub(); - $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable = $this->createMock(WritableStreamInterface::class); $writable ->expects($this->any()) ->method('isWritable') @@ -123,7 +126,7 @@ public function testPipeWithTooSlowWritableShouldPauseReadable() { $readable = new Stub\ReadableStreamStub(); - $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable = $this->createMock(WritableStreamInterface::class); $writable ->expects($this->any()) ->method('isWritable') @@ -147,7 +150,7 @@ public function testPipeWithTooSlowWritableShouldResumeOnDrain() $onDrain = null; - $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable = $this->createMock(WritableStreamInterface::class); $writable ->expects($this->any()) ->method('isWritable') @@ -175,7 +178,7 @@ public function testPipeWithWritableResourceStream() $readable = new Stub\ReadableStreamStub(); $stream = fopen('php://temp', 'r+'); - $loop = $this->createLoopMock(); + $loop = $this->createMock(LoopInterface::class); $buffer = new WritableResourceStream($stream, $loop); $readable->pipe($buffer); @@ -234,9 +237,9 @@ public function testPipeClosingDestRemovesListeners() public function testPipeDuplexIntoSelfEndsOnEnd() { - $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable = $this->createMock(ReadableStreamInterface::class); $readable->expects($this->any())->method('isReadable')->willReturn(true); - $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable = $this->createMock(WritableStreamInterface::class); $writable->expects($this->any())->method('isWritable')->willReturn(true); $duplex = new CompositeStream($readable, $writable); @@ -260,9 +263,4 @@ public function forwardEventsShouldSetupForwards() $source->emit('data', ['hello']); $source->emit('foo', ['bar']); } - - private function createLoopMock() - { - return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); - } } diff --git a/tests/WritableResourceStreamTest.php b/tests/WritableResourceStreamTest.php index a05adc8..02f2367 100644 --- a/tests/WritableResourceStreamTest.php +++ b/tests/WritableResourceStreamTest.php @@ -2,6 +2,7 @@ namespace React\Tests\Stream; +use React\EventLoop\LoopInterface; use React\Stream\WritableResourceStream; use function Clue\StreamFilter\append as filter_append; @@ -56,7 +57,7 @@ public function testConstructorThrowsIfNotAValidStreamResource() $stream = null; $loop = $this->createLoopMock(); - $this->setExpectedException('InvalidArgumentException'); + $this->expectException(\InvalidArgumentException::class); new WritableResourceStream($stream, $loop); } @@ -68,7 +69,7 @@ public function testConstructorThrowsExceptionOnReadOnlyStream() $stream = fopen('php://temp', 'r'); $loop = $this->createLoopMock(); - $this->setExpectedException('InvalidArgumentException'); + $this->expectException(\InvalidArgumentException::class); new WritableResourceStream($stream, $loop); } @@ -83,7 +84,7 @@ public function testConstructorThrowsExceptionOnReadOnlyStreamWithExcessiveMode( unlink($name); $loop = $this->createLoopMock(); - $this->setExpectedException('InvalidArgumentException'); + $this->expectException(\InvalidArgumentException::class); new WritableResourceStream($stream, $loop); } @@ -99,7 +100,7 @@ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking( $stream = fopen('blocking://test', 'r+'); $loop = $this->createLoopMock(); - $this->setExpectedException('RuntimeException'); + $this->expectException(\RuntimeException::class); new WritableResourceStream($stream, $loop); } @@ -502,8 +503,8 @@ public function testWritingToClosedStream() $buffer->write('bar'); $buffer->handleWrite(); - $this->assertInstanceOf('Exception', $error); - $this->assertSameIgnoringCase('Unable to write to stream: fwrite(): send of 3 bytes failed with errno=32 Broken pipe', $error->getMessage()); + $this->assertInstanceOf(\Exception::class, $error); + $this->assertEqualsIgnoringCase('Unable to write to stream: fwrite(): send of 3 bytes failed with errno=32 Broken pipe', $error->getMessage()); } private function createWriteableLoopMock() @@ -521,6 +522,6 @@ private function createWriteableLoopMock() private function createLoopMock() { - return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + return $this->createMock(LoopInterface::class); } } From 74d748d2d98f5d46eeb30a53ea827f857f2f41d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Tue, 14 May 2024 12:23:03 +0200 Subject: [PATCH 62/66] Improve PHP 8.4+ support by avoiding implicitly nullable types --- src/DuplexResourceStream.php | 8 +++++++- src/ReadableResourceStream.php | 7 ++++++- src/WritableResourceStream.php | 8 +++++++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/DuplexResourceStream.php b/src/DuplexResourceStream.php index 0b52c9b..32a668d 100644 --- a/src/DuplexResourceStream.php +++ b/src/DuplexResourceStream.php @@ -38,7 +38,13 @@ final class DuplexResourceStream extends EventEmitter implements DuplexStreamInt private $closing = false; private $listening = false; - public function __construct($stream, LoopInterface $loop = null, $readChunkSize = null, WritableStreamInterface $buffer = null) + /** + * @param resource $stream + * @param ?LoopInterface $loop + * @param ?int $readChunkSize + * @param ?WritableStreamInterface $buffer + */ + public function __construct($stream, ?LoopInterface $loop = null, $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'); diff --git a/src/ReadableResourceStream.php b/src/ReadableResourceStream.php index 41314fe..627fbf9 100644 --- a/src/ReadableResourceStream.php +++ b/src/ReadableResourceStream.php @@ -40,7 +40,12 @@ final class ReadableResourceStream extends EventEmitter implements ReadableStrea private $closed = false; private $listening = false; - public function __construct($stream, LoopInterface $loop = null, $readChunkSize = null) + /** + * @param resource $stream + * @param ?LoopInterface $loop + * @param ?int $readChunkSize + */ + public function __construct($stream, ?LoopInterface $loop = null, $readChunkSize = null) { if (!\is_resource($stream) || \get_resource_type($stream) !== "stream") { throw new InvalidArgumentException('First parameter must be a valid stream resource'); diff --git a/src/WritableResourceStream.php b/src/WritableResourceStream.php index 9665721..9792a5f 100644 --- a/src/WritableResourceStream.php +++ b/src/WritableResourceStream.php @@ -28,7 +28,13 @@ final class WritableResourceStream extends EventEmitter implements WritableStrea private $closed = false; private $data = ''; - public function __construct($stream, LoopInterface $loop = null, $writeBufferSoftLimit = null, $writeChunkSize = null) + /** + * @param resource $stream + * @param ?LoopInterface $loop + * @param ?int $writeBufferSoftLimit + * @param ?int $writeChunkSize + */ + public function __construct($stream, ?LoopInterface $loop = null, $writeBufferSoftLimit = null, $writeChunkSize = null) { if (!\is_resource($stream) || \get_resource_type($stream) !== "stream") { throw new \InvalidArgumentException('First parameter must be a valid stream resource'); From 74dd76353dde1eec7bba9a844273938cd099ecf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 15 May 2024 20:18:59 +0200 Subject: [PATCH 63/66] Add native types to public API --- README.md | 2 +- src/CompositeStream.php | 16 ++++++++-------- src/DuplexResourceStream.php | 20 ++++++++++---------- src/ReadableResourceStream.php | 14 +++++++------- src/ReadableStreamInterface.php | 10 +++++----- src/ThroughStream.php | 22 +++++++++------------- src/Util.php | 10 ++++++++-- src/WritableResourceStream.php | 14 +++++++------- src/WritableStreamInterface.php | 8 ++++---- tests/Stub/ReadableStreamStub.php | 10 +++++----- tests/ThroughStreamTest.php | 9 --------- 11 files changed, 64 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index cce1408..fee7f67 100644 --- a/README.md +++ b/README.md @@ -324,7 +324,7 @@ See also `pause()`. #### pipe() -The `pipe(WritableStreamInterface $dest, array $options = [])` method can be used to +The `pipe(WritableStreamInterface $dest, array $options = []): WritableStreamInterface` method can be used to pipe all the data from this readable source into the given writable destination. Automatically sends all incoming data to the destination. diff --git a/src/CompositeStream.php b/src/CompositeStream.php index d0b934b..10cef07 100644 --- a/src/CompositeStream.php +++ b/src/CompositeStream.php @@ -27,17 +27,17 @@ public function __construct(ReadableStreamInterface $readable, WritableStreamInt $this->writable->on('close', [$this, 'close']); } - public function isReadable() + public function isReadable(): bool { return $this->readable->isReadable(); } - public function pause() + public function pause(): void { $this->readable->pause(); } - public function resume() + public function resume(): void { if (!$this->writable->isWritable()) { return; @@ -46,28 +46,28 @@ public function resume() $this->readable->resume(); } - public function pipe(WritableStreamInterface $dest, array $options = []) + public function pipe(WritableStreamInterface $dest, array $options = []): WritableStreamInterface { return Util::pipe($this, $dest, $options); } - public function isWritable() + public function isWritable(): bool { return $this->writable->isWritable(); } - public function write($data) + public function write($data): bool { return $this->writable->write($data); } - public function end($data = null) + public function end($data = null): void { $this->readable->pause(); $this->writable->end($data); } - public function close() + public function close(): void { if ($this->closed) { return; diff --git a/src/DuplexResourceStream.php b/src/DuplexResourceStream.php index 32a668d..bd05013 100644 --- a/src/DuplexResourceStream.php +++ b/src/DuplexResourceStream.php @@ -44,7 +44,7 @@ final class DuplexResourceStream extends EventEmitter implements DuplexStreamInt * @param ?int $readChunkSize * @param ?WritableStreamInterface $buffer */ - public function __construct($stream, ?LoopInterface $loop = null, $readChunkSize = null, ?WritableStreamInterface $buffer = null) + public function __construct($stream, ?LoopInterface $loop = null, ?int $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'); @@ -75,7 +75,7 @@ public function __construct($stream, ?LoopInterface $loop = null, $readChunkSize $this->stream = $stream; $this->loop = $loop ?: Loop::get(); - $this->bufferSize = ($readChunkSize === null) ? 65536 : (int)$readChunkSize; + $this->bufferSize = $readChunkSize ?? 65536; $this->buffer = $buffer; $this->buffer->on('error', function ($error) { @@ -91,17 +91,17 @@ public function __construct($stream, ?LoopInterface $loop = null, $readChunkSize $this->resume(); } - public function isReadable() + public function isReadable(): bool { return $this->readable; } - public function isWritable() + public function isWritable(): bool { return $this->writable; } - public function pause() + public function pause(): void { if ($this->listening) { $this->loop->removeReadStream($this->stream); @@ -109,7 +109,7 @@ public function pause() } } - public function resume() + public function resume(): void { if (!$this->listening && $this->readable) { $this->loop->addReadStream($this->stream, [$this, 'handleData']); @@ -117,7 +117,7 @@ public function resume() } } - public function write($data) + public function write($data): bool { if (!$this->writable) { return false; @@ -126,7 +126,7 @@ public function write($data) return $this->buffer->write($data); } - public function close() + public function close(): void { if (!$this->writable && !$this->closing) { return; @@ -147,7 +147,7 @@ public function close() } } - public function end($data = null) + public function end($data = null): void { if (!$this->writable) { return; @@ -162,7 +162,7 @@ public function end($data = null) $this->buffer->end($data); } - public function pipe(WritableStreamInterface $dest, array $options = []) + public function pipe(WritableStreamInterface $dest, array $options = []): WritableStreamInterface { return Util::pipe($this, $dest, $options); } diff --git a/src/ReadableResourceStream.php b/src/ReadableResourceStream.php index 627fbf9..592e35c 100644 --- a/src/ReadableResourceStream.php +++ b/src/ReadableResourceStream.php @@ -45,7 +45,7 @@ final class ReadableResourceStream extends EventEmitter implements ReadableStrea * @param ?LoopInterface $loop * @param ?int $readChunkSize */ - public function __construct($stream, ?LoopInterface $loop = null, $readChunkSize = null) + public function __construct($stream, ?LoopInterface $loop = null, ?int $readChunkSize = null) { if (!\is_resource($stream) || \get_resource_type($stream) !== "stream") { throw new InvalidArgumentException('First parameter must be a valid stream resource'); @@ -72,17 +72,17 @@ public function __construct($stream, ?LoopInterface $loop = null, $readChunkSize $this->stream = $stream; $this->loop = $loop ?: Loop::get(); - $this->bufferSize = ($readChunkSize === null) ? 65536 : (int)$readChunkSize; + $this->bufferSize = $readChunkSize ?? 65536; $this->resume(); } - public function isReadable() + public function isReadable(): bool { return !$this->closed; } - public function pause() + public function pause(): void { if ($this->listening) { $this->loop->removeReadStream($this->stream); @@ -90,7 +90,7 @@ public function pause() } } - public function resume() + public function resume(): void { if (!$this->listening && !$this->closed) { $this->loop->addReadStream($this->stream, [$this, 'handleData']); @@ -98,12 +98,12 @@ public function resume() } } - public function pipe(WritableStreamInterface $dest, array $options = []) + public function pipe(WritableStreamInterface $dest, array $options = []): WritableStreamInterface { return Util::pipe($this, $dest, $options); } - public function close() + public function close(): void { if ($this->closed) { return; diff --git a/src/ReadableStreamInterface.php b/src/ReadableStreamInterface.php index ecc5267..e4ddf32 100644 --- a/src/ReadableStreamInterface.php +++ b/src/ReadableStreamInterface.php @@ -194,7 +194,7 @@ interface ReadableStreamInterface extends EventEmitterInterface * * @return bool */ - public function isReadable(); + public function isReadable(): bool; /** * Pauses reading incoming data events. @@ -226,7 +226,7 @@ public function isReadable(); * @see self::resume() * @return void */ - public function pause(); + public function pause(): void; /** * Resumes reading incoming data events. @@ -247,7 +247,7 @@ public function pause(); * @see self::pause() * @return void */ - public function resume(); + public function resume(): void; /** * Pipes all the data from this readable source into the given writable destination. @@ -322,7 +322,7 @@ public function resume(); * @param array $options * @return WritableStreamInterface $dest stream as-is */ - public function pipe(WritableStreamInterface $dest, array $options = []); + public function pipe(WritableStreamInterface $dest, array $options = []): WritableStreamInterface; /** * Closes the stream (forcefully). @@ -358,5 +358,5 @@ public function pipe(WritableStreamInterface $dest, array $options = []); * @return void * @see WritableStreamInterface::close() */ - public function close(); + public function close(): void; } diff --git a/src/ThroughStream.php b/src/ThroughStream.php index c49ebfc..4c1f4a2 100644 --- a/src/ThroughStream.php +++ b/src/ThroughStream.php @@ -82,22 +82,18 @@ final class ThroughStream extends EventEmitter implements DuplexStreamInterface private $drain = false; private $callback; - public function __construct($callback = null) + public function __construct(?callable $callback = null) { - if ($callback !== null && !\is_callable($callback)) { - throw new InvalidArgumentException('Invalid transformation callback given'); - } - $this->callback = $callback; } - public function pause() + public function pause(): void { // only allow pause if still readable, false otherwise $this->paused = $this->readable; } - public function resume() + public function resume(): void { $this->paused = false; @@ -108,22 +104,22 @@ public function resume() } } - public function pipe(WritableStreamInterface $dest, array $options = []) + public function pipe(WritableStreamInterface $dest, array $options = []): WritableStreamInterface { return Util::pipe($this, $dest, $options); } - public function isReadable() + public function isReadable(): bool { return $this->readable; } - public function isWritable() + public function isWritable(): bool { return $this->writable; } - public function write($data) + public function write($data): bool { if (!$this->writable) { return false; @@ -151,7 +147,7 @@ public function write($data) return $this->writable && !$this->paused; } - public function end($data = null) + public function end($data = null): void { if (!$this->writable) { return; @@ -175,7 +171,7 @@ public function end($data = null) $this->close(); } - public function close() + public function close(): void { if ($this->closed) { return; diff --git a/src/Util.php b/src/Util.php index 114ccb8..23dd252 100644 --- a/src/Util.php +++ b/src/Util.php @@ -13,7 +13,7 @@ final class Util * @return WritableStreamInterface $dest stream as-is * @see ReadableStreamInterface::pipe() for more details */ - public static function pipe(ReadableStreamInterface $source, WritableStreamInterface $dest, array $options = []) + public static function pipe(ReadableStreamInterface $source, WritableStreamInterface $dest, array $options = []): WritableStreamInterface { // source not readable => NO-OP if (!$source->isReadable()) { @@ -64,7 +64,13 @@ public static function pipe(ReadableStreamInterface $source, WritableStreamInter return $dest; } - public static function forwardEvents($source, $target, array $events) + /** + * @param ReadableStreamInterface|WritableStreamInterface $source + * @param ReadableStreamInterface|WritableStreamInterface $target + * @param string[] $events + * @return void + */ + public static function forwardEvents($source, $target, array $events): void { foreach ($events as $event) { $source->on($event, function () use ($event, $target) { diff --git a/src/WritableResourceStream.php b/src/WritableResourceStream.php index 9792a5f..84ec4f6 100644 --- a/src/WritableResourceStream.php +++ b/src/WritableResourceStream.php @@ -34,7 +34,7 @@ final class WritableResourceStream extends EventEmitter implements WritableStrea * @param ?int $writeBufferSoftLimit * @param ?int $writeChunkSize */ - public function __construct($stream, ?LoopInterface $loop = null, $writeBufferSoftLimit = null, $writeChunkSize = null) + public function __construct($stream, ?LoopInterface $loop = null, ?int $writeBufferSoftLimit = null, ?int $writeChunkSize = null) { if (!\is_resource($stream) || \get_resource_type($stream) !== "stream") { throw new \InvalidArgumentException('First parameter must be a valid stream resource'); @@ -54,16 +54,16 @@ public function __construct($stream, ?LoopInterface $loop = null, $writeBufferSo $this->stream = $stream; $this->loop = $loop ?: Loop::get(); - $this->softLimit = ($writeBufferSoftLimit === null) ? 65536 : (int)$writeBufferSoftLimit; - $this->writeChunkSize = ($writeChunkSize === null) ? -1 : (int)$writeChunkSize; + $this->softLimit = $writeBufferSoftLimit ?? 65536; + $this->writeChunkSize = $writeChunkSize ?? -1; } - public function isWritable() + public function isWritable(): bool { return $this->writable; } - public function write($data) + public function write($data): bool { if (!$this->writable) { return false; @@ -80,7 +80,7 @@ public function write($data) return !isset($this->data[$this->softLimit - 1]); } - public function end($data = null) + public function end($data = null): void { if (null !== $data) { $this->write($data); @@ -95,7 +95,7 @@ public function end($data = null) } } - public function close() + public function close(): void { if ($this->closed) { return; diff --git a/src/WritableStreamInterface.php b/src/WritableStreamInterface.php index e262592..77baaad 100644 --- a/src/WritableStreamInterface.php +++ b/src/WritableStreamInterface.php @@ -170,7 +170,7 @@ interface WritableStreamInterface extends EventEmitterInterface * * @return bool */ - public function isWritable(); + public function isWritable(): bool; /** * Write some data into the stream. @@ -219,7 +219,7 @@ public function isWritable(); * @param mixed|string $data * @return bool */ - public function write($data); + public function write($data): bool; /** * Successfully ends the stream (after optionally sending some final data). @@ -292,7 +292,7 @@ public function write($data); * @param mixed|string|null $data * @return void */ - public function end($data = null); + public function end($data = null): void; /** * Closes the stream (forcefully). @@ -343,5 +343,5 @@ public function end($data = null); * @return void * @see ReadableStreamInterface::close() */ - public function close(); + public function close(): void; } diff --git a/tests/Stub/ReadableStreamStub.php b/tests/Stub/ReadableStreamStub.php index 3fa56a8..669059c 100644 --- a/tests/Stub/ReadableStreamStub.php +++ b/tests/Stub/ReadableStreamStub.php @@ -12,7 +12,7 @@ class ReadableStreamStub extends EventEmitter implements ReadableStreamInterface public $readable = true; public $paused = false; - public function isReadable() + public function isReadable(): bool { return true; } @@ -35,24 +35,24 @@ public function end() $this->emit('end', []); } - public function pause() + public function pause(): void { $this->paused = true; } - public function resume() + public function resume(): void { $this->paused = false; } - public function close() + public function close(): void { $this->readable = false; $this->emit('close'); } - public function pipe(WritableStreamInterface $dest, array $options = []) + public function pipe(WritableStreamInterface $dest, array $options = []): WritableStreamInterface { Util::pipe($this, $dest, $options); diff --git a/tests/ThroughStreamTest.php b/tests/ThroughStreamTest.php index 04a49d0..85c22de 100644 --- a/tests/ThroughStreamTest.php +++ b/tests/ThroughStreamTest.php @@ -10,15 +10,6 @@ */ class ThroughStreamTest extends TestCase { - /** - * @test - */ - public function itShouldRejectInvalidCallback() - { - $this->expectException(\InvalidArgumentException::class); - new ThroughStream(123); - } - /** @test */ public function itShouldReturnTrueForAnyDataWrittenToIt() { From 9234da07cb99728b5530c0534176d0dc4c847d5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 15 May 2024 20:35:55 +0200 Subject: [PATCH 64/66] Add PHPStan to test environment --- .gitattributes | 1 + .github/workflows/ci.yml | 23 +++++++++++++++++++ README.md | 6 +++++ composer.json | 5 ++-- phpstan.neon.dist | 7 ++++++ src/DuplexResourceStream.php | 3 ++- src/ReadableResourceStream.php | 3 ++- src/ThroughStream.php | 2 ++ src/WritableResourceStream.php | 3 ++- tests/CompositeStreamTest.php | 18 +++++++++++++++ tests/DuplexResourceStreamIntegrationTest.php | 22 +++++++++--------- tests/DuplexResourceStreamTest.php | 7 +++++- tests/ReadableResourceStreamTest.php | 17 +++++++------- tests/ThroughStreamTest.php | 3 ++- tests/UtilTest.php | 16 +++++++++++++ tests/WritableResourceStreamTest.php | 3 ++- 16 files changed, 112 insertions(+), 27 deletions(-) create mode 100644 phpstan.neon.dist diff --git a/.gitattributes b/.gitattributes index fc0be87..e3b263e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,6 +2,7 @@ /.github/ export-ignore /.gitignore export-ignore /examples/ export-ignore +/phpstan.neon.dist export-ignore /phpunit.xml.dist export-ignore /phpunit.xml.legacy export-ignore /tests/ export-ignore diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f654fb0..1bb8701 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,3 +47,26 @@ jobs: - run: composer install - run: vendor/bin/phpunit --coverage-text - run: time php examples/91-benchmark-throughput.php + + PHPStan: + name: PHPStan (PHP ${{ matrix.php }}) + runs-on: ubuntu-22.04 + strategy: + matrix: + php: + - 8.3 + - 8.2 + - 8.1 + - 8.0 + - 7.4 + - 7.3 + - 7.2 + - 7.1 + steps: + - uses: actions/checkout@v4 + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: none + - run: composer install + - run: vendor/bin/phpstan diff --git a/README.md b/README.md index fee7f67..e27cbec 100644 --- a/README.md +++ b/README.md @@ -1243,6 +1243,12 @@ If you do not want to run these, they can simply be skipped like this: vendor/bin/phpunit --exclude-group internet ``` +On top of this, we use PHPStan on level 5 to ensure type safety across the project: + +```bash +vendor/bin/phpstan +``` + ## License MIT, see [LICENSE file](LICENSE). diff --git a/composer.json b/composer.json index b8d34d4..beb0a56 100644 --- a/composer.json +++ b/composer.json @@ -31,8 +31,9 @@ "evenement/evenement": "^3.0 || ^2.0 || ^1.0" }, "require-dev": { - "phpunit/phpunit": "^9.6 || ^7.5", - "clue/stream-filter": "~1.2" + "clue/stream-filter": "^1.2", + "phpstan/phpstan": "1.11.1 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" }, "autoload": { "psr-4": { diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..d631a6d --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,7 @@ +parameters: + level: 5 + + paths: + - examples/ + - src/ + - tests/ diff --git a/src/DuplexResourceStream.php b/src/DuplexResourceStream.php index bd05013..94d013c 100644 --- a/src/DuplexResourceStream.php +++ b/src/DuplexResourceStream.php @@ -171,7 +171,7 @@ public function pipe(WritableStreamInterface $dest, array $options = []): Writab public function handleData($stream) { $error = null; - \set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) { + \set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error): bool { $error = new \ErrorException( $errstr, 0, @@ -179,6 +179,7 @@ public function handleData($stream) $errfile, $errline ); + return true; }); $data = \stream_get_contents($stream, $this->bufferSize); diff --git a/src/ReadableResourceStream.php b/src/ReadableResourceStream.php index 592e35c..a0993bb 100644 --- a/src/ReadableResourceStream.php +++ b/src/ReadableResourceStream.php @@ -124,7 +124,7 @@ public function close(): void public function handleData() { $error = null; - \set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) { + \set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error): bool { $error = new \ErrorException( $errstr, 0, @@ -132,6 +132,7 @@ public function handleData() $errfile, $errline ); + return true; }); $data = \stream_get_contents($this->stream, $this->bufferSize); diff --git a/src/ThroughStream.php b/src/ThroughStream.php index 4c1f4a2..a209982 100644 --- a/src/ThroughStream.php +++ b/src/ThroughStream.php @@ -144,6 +144,7 @@ public function write($data): bool } // continue writing if still writable and not paused (throttled), false otherwise + // @phpstan-ignore-next-line (may be false when write() causes stream to close) return $this->writable && !$this->paused; } @@ -157,6 +158,7 @@ public function end($data = null): void $this->write($data); // return if write() already caused the stream to close + // @phpstan-ignore-next-line (may be false when write() causes stream to close) if (!$this->writable) { return; } diff --git a/src/WritableResourceStream.php b/src/WritableResourceStream.php index 84ec4f6..6140246 100644 --- a/src/WritableResourceStream.php +++ b/src/WritableResourceStream.php @@ -122,8 +122,9 @@ public function close(): void public function handleWrite() { $error = null; - \set_error_handler(function ($_, $errstr) use (&$error) { + \set_error_handler(function ($_, $errstr) use (&$error): bool { $error = $errstr; + return true; }); if ($this->writeChunkSize === -1) { diff --git a/tests/CompositeStreamTest.php b/tests/CompositeStreamTest.php index 75a0426..cc9ef28 100644 --- a/tests/CompositeStreamTest.php +++ b/tests/CompositeStreamTest.php @@ -23,12 +23,14 @@ public function itShouldCloseReadableIfNotWritable() $readable ->expects($this->once()) ->method('close'); + assert($readable instanceof ReadableStreamInterface); $writable = $this->createMock(WritableStreamInterface::class); $writable ->expects($this->once()) ->method('isWritable') ->willReturn(false); + assert($writable instanceof WritableStreamInterface); $composite = new CompositeStream($readable, $writable); @@ -44,11 +46,13 @@ public function itShouldCloseWritableIfNotReadable() ->expects($this->once()) ->method('isReadable') ->willReturn(false); + assert($readable instanceof ReadableStreamInterface); $writable = $this->createMock(WritableStreamInterface::class); $writable ->expects($this->once()) ->method('close'); + assert($writable instanceof WritableStreamInterface); $composite = new CompositeStream($readable, $writable); @@ -64,6 +68,7 @@ public function itShouldForwardWritableCallsToWritableStream() ->expects($this->once()) ->method('isReadable') ->willReturn(true); + assert($readable instanceof ReadableStreamInterface); $writable = $this->createMock(WritableStreamInterface::class); $writable @@ -74,6 +79,7 @@ public function itShouldForwardWritableCallsToWritableStream() ->expects($this->exactly(2)) ->method('isWritable') ->willReturn(true); + assert($writable instanceof WritableStreamInterface); $composite = new CompositeStream($readable, $writable); $composite->write('foo'); @@ -94,12 +100,14 @@ public function itShouldForwardReadableCallsToReadableStream() $readable ->expects($this->once()) ->method('resume'); + assert($readable instanceof ReadableStreamInterface); $writable = $this->createMock(WritableStreamInterface::class); $writable ->expects($this->any()) ->method('isWritable') ->willReturn(true); + assert($writable instanceof WritableStreamInterface); $composite = new CompositeStream($readable, $writable); $composite->isReadable(); @@ -118,12 +126,14 @@ public function itShouldNotForwardResumeIfStreamIsNotWritable() $readable ->expects($this->never()) ->method('resume'); + assert($readable instanceof ReadableStreamInterface); $writable = $this->createMock(WritableStreamInterface::class); $writable ->expects($this->exactly(2)) ->method('isWritable') ->willReturnOnConsecutiveCalls(true, false); + assert($writable instanceof WritableStreamInterface); $composite = new CompositeStream($readable, $writable); $composite->resume(); @@ -137,6 +147,7 @@ public function endShouldDelegateToWritableWithData() ->expects($this->once()) ->method('isReadable') ->willReturn(true); + assert($readable instanceof ReadableStreamInterface); $writable = $this->createMock(WritableStreamInterface::class); $writable @@ -147,6 +158,7 @@ public function endShouldDelegateToWritableWithData() ->expects($this->once()) ->method('end') ->with('foo'); + assert($writable instanceof WritableStreamInterface); $composite = new CompositeStream($readable, $writable); $composite->end('foo'); @@ -163,6 +175,7 @@ public function closeShouldCloseBothStreams() $readable ->expects($this->once()) ->method('close'); + assert($readable instanceof ReadableStreamInterface); $writable = $this->createMock(WritableStreamInterface::class); $writable @@ -172,6 +185,7 @@ public function closeShouldCloseBothStreams() $writable ->expects($this->once()) ->method('close'); + assert($writable instanceof WritableStreamInterface); $composite = new CompositeStream($readable, $writable); $composite->close(); @@ -231,6 +245,7 @@ public function itShouldHandlePipingCorrectly() ->expects($this->once()) ->method('isReadable') ->willReturn(true); + assert($readable instanceof ReadableStreamInterface); $writable = $this->createMock(WritableStreamInterface::class); $writable->expects($this->any())->method('isWritable')->willReturn(True); @@ -238,6 +253,7 @@ public function itShouldHandlePipingCorrectly() ->expects($this->once()) ->method('write') ->with('foo'); + assert($writable instanceof WritableStreamInterface); $composite = new CompositeStream($readable, $writable); @@ -253,6 +269,7 @@ public function itShouldForwardPipeCallsToReadableStream() $writable = $this->createMock(WritableStreamInterface::class); $writable->expects($this->any())->method('isWritable')->willReturn(True); + assert($writable instanceof WritableStreamInterface); $composite = new CompositeStream($readable, $writable); @@ -262,6 +279,7 @@ public function itShouldForwardPipeCallsToReadableStream() ->expects($this->once()) ->method('write') ->with('foo'); + assert($output instanceof WritableStreamInterface); $composite->pipe($output); $readable->emit('data', ['foo']); diff --git a/tests/DuplexResourceStreamIntegrationTest.php b/tests/DuplexResourceStreamIntegrationTest.php index 02da5d2..a468aef 100644 --- a/tests/DuplexResourceStreamIntegrationTest.php +++ b/tests/DuplexResourceStreamIntegrationTest.php @@ -37,7 +37,7 @@ function () { public function testBufferReadsLargeChunks($condition, $loopFactory) { if (true !== $condition()) { - return $this->markTestSkipped('Loop implementation not available'); + $this->markTestSkipped('Loop implementation not available'); } $loop = $loopFactory(); @@ -73,7 +73,7 @@ public function testBufferReadsLargeChunks($condition, $loopFactory) public function testWriteLargeChunk($condition, $loopFactory) { if (true !== $condition()) { - return $this->markTestSkipped('Loop implementation not available'); + $this->markTestSkipped('Loop implementation not available'); } $loop = $loopFactory(); @@ -113,7 +113,7 @@ public function testWriteLargeChunk($condition, $loopFactory) public function testDoesNotEmitDataIfNothingHasBeenWritten($condition, $loopFactory) { if (true !== $condition()) { - return $this->markTestSkipped('Loop implementation not available'); + $this->markTestSkipped('Loop implementation not available'); } $loop = $loopFactory(); @@ -141,7 +141,7 @@ public function testDoesNotEmitDataIfNothingHasBeenWritten($condition, $loopFact public function testDoesNotWriteDataIfRemoteSideFromPairHasBeenClosed($condition, $loopFactory) { if (true !== $condition()) { - return $this->markTestSkipped('Loop implementation not available'); + $this->markTestSkipped('Loop implementation not available'); } $loop = $loopFactory(); @@ -171,7 +171,7 @@ public function testDoesNotWriteDataIfRemoteSideFromPairHasBeenClosed($condition public function testDoesNotWriteDataIfServerSideHasBeenClosed($condition, $loopFactory) { if (true !== $condition()) { - return $this->markTestSkipped('Loop implementation not available'); + $this->markTestSkipped('Loop implementation not available'); } $loop = $loopFactory(); @@ -204,7 +204,7 @@ public function testDoesNotWriteDataIfServerSideHasBeenClosed($condition, $loopF public function testDoesNotWriteDataIfClientSideHasBeenClosed($condition, $loopFactory) { if (true !== $condition()) { - return $this->markTestSkipped('Loop implementation not available'); + $this->markTestSkipped('Loop implementation not available'); } $loop = $loopFactory(); @@ -237,7 +237,7 @@ public function testDoesNotWriteDataIfClientSideHasBeenClosed($condition, $loopF public function testReadsSingleChunkFromProcessPipe($condition, $loopFactory) { if (true !== $condition()) { - return $this->markTestSkipped('Loop implementation not available'); + $this->markTestSkipped('Loop implementation not available'); } $loop = $loopFactory(); @@ -256,7 +256,7 @@ public function testReadsSingleChunkFromProcessPipe($condition, $loopFactory) public function testReadsMultipleChunksFromProcessPipe($condition, $loopFactory) { if (true !== $condition()) { - return $this->markTestSkipped('Loop implementation not available'); + $this->markTestSkipped('Loop implementation not available'); } $loop = $loopFactory(); @@ -282,7 +282,7 @@ public function testReadsMultipleChunksFromProcessPipe($condition, $loopFactory) public function testReadsLongChunksFromProcessPipe($condition, $loopFactory) { if (true !== $condition()) { - return $this->markTestSkipped('Loop implementation not available'); + $this->markTestSkipped('Loop implementation not available'); } $loop = $loopFactory(); @@ -308,7 +308,7 @@ public function testReadsLongChunksFromProcessPipe($condition, $loopFactory) public function testReadsNothingFromProcessPipeWithNoOutput($condition, $loopFactory) { if (true !== $condition()) { - return $this->markTestSkipped('Loop implementation not available'); + $this->markTestSkipped('Loop implementation not available'); } $loop = $loopFactory(); @@ -328,7 +328,7 @@ public function testReadsNothingFromProcessPipeWithNoOutput($condition, $loopFac public function testEmptyReadShouldntFcloseStream($condition, $loopFactory) { if (true !== $condition()) { - return $this->markTestSkipped('Loop implementation not available'); + $this->markTestSkipped('Loop implementation not available'); } $server = stream_socket_server('tcp://127.0.0.1:0'); diff --git a/tests/DuplexResourceStreamTest.php b/tests/DuplexResourceStreamTest.php index 52477fe..5b18553 100644 --- a/tests/DuplexResourceStreamTest.php +++ b/tests/DuplexResourceStreamTest.php @@ -59,7 +59,7 @@ public function testConstructorThrowsExceptionOnInvalidStream() $loop = $this->createLoopMock(); $this->expectException(\InvalidArgumentException::class); - new DuplexResourceStream('breakme', $loop); + new DuplexResourceStream('breakme', $loop); // @phpstan-ignore-line } /** @@ -114,6 +114,7 @@ public function testConstructorAcceptsBuffer() $loop = $this->createLoopMock(); $buffer = $this->createMock(WritableStreamInterface::class); + assert($buffer instanceof WritableStreamInterface); new DuplexResourceStream($stream, $loop, null, $buffer); } @@ -131,6 +132,7 @@ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlockingW $loop = $this->createLoopMock(); $buffer = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + assert($buffer instanceof WritableStreamInterface); $this->expectException(\RuntimeException::class); new DuplexResourceStream($stream, $loop, null, $buffer); @@ -157,6 +159,7 @@ public function testEndShouldEndBuffer() $buffer = $this->createMock(WritableStreamInterface::class); $buffer->expects($this->once())->method('end')->with('foo'); + assert($buffer instanceof WritableStreamInterface); $conn = new DuplexResourceStream($stream, $loop, null, $buffer); $conn->end('foo'); @@ -170,6 +173,7 @@ public function testEndAfterCloseIsNoOp() $buffer = $this->createMock(WritableStreamInterface::class); $buffer->expects($this->never())->method('end'); + assert($buffer instanceof WritableStreamInterface); $conn = new DuplexResourceStream($stream, $loop); $conn->close(); @@ -409,6 +413,7 @@ public function testPipeShouldReturnDestination() $conn = new DuplexResourceStream($stream, $loop); $dest = $this->createMock(WritableStreamInterface::class); + assert($dest instanceof WritableStreamInterface); $this->assertSame($dest, $conn->pipe($dest)); } diff --git a/tests/ReadableResourceStreamTest.php b/tests/ReadableResourceStreamTest.php index 4cebb5a..365ac0d 100644 --- a/tests/ReadableResourceStreamTest.php +++ b/tests/ReadableResourceStreamTest.php @@ -58,7 +58,7 @@ public function testConstructorThrowsExceptionOnInvalidStream() $loop = $this->createLoopMock(); $this->expectException(\InvalidArgumentException::class); - new ReadableResourceStream(false, $loop); + new ReadableResourceStream(false, $loop); // @phpstan-ignore-line } /** @@ -148,7 +148,7 @@ public function testDataEvent() fwrite($stream, "foobar\n"); rewind($stream); - $conn->handleData($stream); + $conn->handleData(); $this->assertSame("foobar\n", $capturedData); } @@ -171,7 +171,7 @@ public function testDataEventDoesEmitOneChunkMatchingBufferSize() fwrite($stream, str_repeat("a", 100000)); rewind($stream); - $conn->handleData($stream); + $conn->handleData(); $this->assertTrue($conn->isReadable()); $this->assertEquals(4321, strlen($capturedData)); @@ -197,7 +197,7 @@ public function testDataEventDoesEmitOneChunkUntilStreamEndsWhenBufferSizeIsInfi fwrite($stream, str_repeat("a", 100000)); rewind($stream); - $conn->handleData($stream); + $conn->handleData(); $this->assertTrue($conn->isReadable()); $this->assertEquals(100000, strlen($capturedData)); @@ -214,7 +214,7 @@ public function testEmptyStreamShouldNotEmitData() $conn = new ReadableResourceStream($stream, $loop); $conn->on('data', $this->expectCallableNever()); - $conn->handleData($stream); + $conn->handleData(); } public function testPipeShouldReturnDestination() @@ -224,6 +224,7 @@ public function testPipeShouldReturnDestination() $conn = new ReadableResourceStream($stream, $loop); $dest = $this->createMock(WritableStreamInterface::class); + assert($dest instanceof WritableStreamInterface); $this->assertSame($dest, $conn->pipe($dest)); } @@ -245,7 +246,7 @@ public function testClosingStreamInDataEventShouldNotTriggerError() fwrite($stream, "foobar\n"); rewind($stream); - $conn->handleData($stream); + $conn->handleData(); } /** @@ -344,7 +345,7 @@ public function testDataFiltered() fwrite($stream, "foobar\n"); rewind($stream); - $conn->handleData($stream); + $conn->handleData(); $this->assertSame("foobr\n", $capturedData); } @@ -373,7 +374,7 @@ public function testDataErrorShouldEmitErrorAndClose() fwrite($stream, "foobar\n"); rewind($stream); - $conn->handleData($stream); + $conn->handleData(); } /** diff --git a/tests/ThroughStreamTest.php b/tests/ThroughStreamTest.php index 85c22de..ddd570f 100644 --- a/tests/ThroughStreamTest.php +++ b/tests/ThroughStreamTest.php @@ -216,7 +216,7 @@ public function endShouldWriteDataBeforeClosing() public function endTwiceShouldOnlyEmitOnce() { $through = new ThroughStream(); - $through->on('data', $this->expectCallableOnce('first')); + $through->on('data', $this->expectCallableOnceWith('first')); $through->end('first'); $through->end('ignored'); } @@ -309,6 +309,7 @@ public function pipeShouldPipeCorrectly() ->expects($this->once()) ->method('write') ->with('foo'); + assert($output instanceof WritableStreamInterface); $through = new ThroughStream(); $through->pipe($output); diff --git a/tests/UtilTest.php b/tests/UtilTest.php index 50b2e75..cd00c8b 100644 --- a/tests/UtilTest.php +++ b/tests/UtilTest.php @@ -18,8 +18,10 @@ class UtilTest extends TestCase public function testPipeReturnsDestinationStream() { $readable = $this->createMock(ReadableStreamInterface::class); + assert($readable instanceof ReadableStreamInterface); $writable = $this->createMock(WritableStreamInterface::class); + assert($writable instanceof WritableStreamInterface); $ret = Util::pipe($readable, $writable); @@ -33,6 +35,7 @@ public function testPipeNonReadableSourceShouldDoNothing() ->expects($this->any()) ->method('isReadable') ->willReturn(false); + assert($readable instanceof ReadableStreamInterface); $writable = $this->createMock(WritableStreamInterface::class); $writable @@ -41,6 +44,7 @@ public function testPipeNonReadableSourceShouldDoNothing() $writable ->expects($this->never()) ->method('end'); + assert($writable instanceof WritableStreamInterface); Util::pipe($readable, $writable); } @@ -55,6 +59,7 @@ public function testPipeIntoNonWritableDestinationShouldPauseSource() $readable ->expects($this->once()) ->method('pause'); + assert($readable instanceof ReadableStreamInterface); $writable = $this->createMock(WritableStreamInterface::class); $writable @@ -64,6 +69,7 @@ public function testPipeIntoNonWritableDestinationShouldPauseSource() $writable ->expects($this->never()) ->method('end'); + assert($writable instanceof WritableStreamInterface); Util::pipe($readable, $writable); } @@ -78,6 +84,7 @@ public function testPipeClosingDestPausesSource() $readable ->expects($this->once()) ->method('pause'); + assert($readable instanceof ReadableStreamInterface); $writable = new ThroughStream(); @@ -98,6 +105,7 @@ public function testPipeWithEnd() $writable ->expects($this->once()) ->method('end'); + assert($writable instanceof WritableStreamInterface); Util::pipe($readable, $writable); @@ -116,6 +124,7 @@ public function testPipeWithoutEnd() $writable ->expects($this->never()) ->method('end'); + assert($writable instanceof WritableStreamInterface); Util::pipe($readable, $writable, ['end' => false]); @@ -136,6 +145,7 @@ public function testPipeWithTooSlowWritableShouldPauseReadable() ->method('write') ->with('some data') ->will($this->returnValue(false)); + assert($writable instanceof WritableStreamInterface); $readable->pipe($writable); @@ -163,6 +173,7 @@ public function testPipeWithTooSlowWritableShouldResumeOnDrain() $onDrain = $callback; } })); + assert($writable instanceof WritableStreamInterface); $readable->pipe($writable); $readable->pause(); @@ -179,6 +190,7 @@ public function testPipeWithWritableResourceStream() $stream = fopen('php://temp', 'r+'); $loop = $this->createMock(LoopInterface::class); + assert($loop instanceof LoopInterface); $buffer = new WritableResourceStream($stream, $loop); $readable->pipe($buffer); @@ -239,8 +251,12 @@ public function testPipeDuplexIntoSelfEndsOnEnd() { $readable = $this->createMock(ReadableStreamInterface::class); $readable->expects($this->any())->method('isReadable')->willReturn(true); + assert($readable instanceof ReadableStreamInterface); + $writable = $this->createMock(WritableStreamInterface::class); $writable->expects($this->any())->method('isWritable')->willReturn(true); + assert($writable instanceof WritableStreamInterface); + $duplex = new CompositeStream($readable, $writable); Util::pipe($duplex, $duplex); diff --git a/tests/WritableResourceStreamTest.php b/tests/WritableResourceStreamTest.php index 02f2367..df3e815 100644 --- a/tests/WritableResourceStreamTest.php +++ b/tests/WritableResourceStreamTest.php @@ -58,7 +58,7 @@ public function testConstructorThrowsIfNotAValidStreamResource() $loop = $this->createLoopMock(); $this->expectException(\InvalidArgumentException::class); - new WritableResourceStream($stream, $loop); + new WritableResourceStream($stream, $loop); // @phpstan-ignore-line } /** @@ -165,6 +165,7 @@ public function testWriteReturnsFalseWhenWritableResourceStreamIsFull() ->expects($this->any()) ->method('addWriteStream') ->will($this->returnCallback(function ($stream, $listener) use (&$preventWrites) { + /** @var bool $preventWrites */ if (!$preventWrites) { call_user_func($listener, $stream); } From 5047943c375e568f52ae67bb3a5abfb60af65025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 15 May 2024 21:15:56 +0200 Subject: [PATCH 65/66] Improve type definitions and update to PHPStan level `max` --- README.md | 36 ++--- examples/01-http.php | 4 +- examples/02-https.php | 4 +- examples/91-benchmark-throughput.php | 12 +- phpstan.neon.dist | 2 +- src/CompositeStream.php | 5 + src/DuplexResourceStream.php | 23 +++- src/ReadableResourceStream.php | 7 +- src/ReadableStreamInterface.php | 14 +- src/ThroughStream.php | 19 ++- src/Util.php | 16 +-- src/WritableResourceStream.php | 23 ++-- src/WritableStreamInterface.php | 12 +- tests/CompositeStreamTest.php | 24 ++-- tests/DuplexResourceStreamIntegrationTest.php | 80 +++++++---- tests/DuplexResourceStreamTest.php | 128 +++++++++++++----- tests/EnforceBlockingWrapper.php | 9 +- tests/FunctionalInternetTest.php | 14 +- tests/ReadableResourceStreamTest.php | 98 ++++++++++---- tests/Stub/ReadableStreamStub.php | 16 ++- tests/TestCase.php | 18 ++- tests/ThroughStreamTest.php | 56 ++++---- tests/UtilTest.php | 30 ++-- tests/WritableResourceStreamTest.php | 124 ++++++++++++----- 24 files changed, 511 insertions(+), 263 deletions(-) diff --git a/README.md b/README.md index e27cbec..870a907 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ from this source stream. The event receives a single mixed argument for incoming data. ```php -$stream->on('data', function ($data) { +$stream->on('data', function (mixed $data): void { echo $data; }); ``` @@ -142,7 +142,7 @@ The `end` event will be emitted once the source stream has successfully reached the end of the stream (EOF). ```php -$stream->on('end', function () { +$stream->on('end', function (): void { echo 'END'; }); ``` @@ -180,7 +180,7 @@ trying to read from this stream. The event receives a single `Exception` argument for the error instance. ```php -$server->on('error', function (Exception $e) { +$server->on('error', function (Exception $e): void { echo 'Error: ' . $e->getMessage() . PHP_EOL; }); ``` @@ -213,7 +213,7 @@ stream which should result in the same error processing. The `close` event will be emitted once the stream closes (terminates). ```php -$stream->on('close', function () { +$stream->on('close', function (): void { echo 'CLOSED'; }); ``` @@ -312,7 +312,7 @@ Re-attach the data source after a previous `pause()`. ```php $stream->pause(); -Loop::addTimer(1.0, function () use ($stream) { +Loop::addTimer(1.0, function () use ($stream): void { $stream->resume(); }); ``` @@ -362,7 +362,7 @@ you'll have to manually close the destination stream: ```php $source->pipe($dest); -$source->on('close', function () use ($dest) { +$source->on('close', function () use ($dest): void { $dest->end('BYE!'); }); ``` @@ -456,7 +456,7 @@ 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) { +$stream->on('drain', function () use ($stream): void { echo 'Stream is now ready to accept more data'; }); ``` @@ -478,11 +478,11 @@ The event receives a single `ReadableStreamInterface` argument for the source stream. ```php -$stream->on('pipe', function (ReadableStreamInterface $source) use ($stream) { +$stream->on('pipe', function (ReadableStreamInterface $source) use ($stream): void { echo 'Now receiving piped data'; // explicitly close target if source emits an error - $source->on('error', function () use ($stream) { + $source->on('error', function () use ($stream): void { $stream->close(); }); }); @@ -506,7 +506,7 @@ trying to write to this stream. The event receives a single `Exception` argument for the error instance. ```php -$stream->on('error', function (Exception $e) { +$stream->on('error', function (Exception $e): void { echo 'Error: ' . $e->getMessage() . PHP_EOL; }); ``` @@ -536,7 +536,7 @@ stream which should result in the same error processing. The `close` event will be emitted once the stream closes (terminates). ```php -$stream->on('close', function () { +$stream->on('close', function (): void { echo 'CLOSED'; }); ``` @@ -746,7 +746,7 @@ stream in order to stop waiting for the stream to flush its final data. ```php $stream->end(); -Loop::addTimer(1.0, function () use ($stream) { +Loop::addTimer(1.0, function () use ($stream): void { $stream->close(); }); ``` @@ -831,10 +831,10 @@ readable mode or a stream such as `STDIN`: ```php $stream = new ReadableResourceStream(STDIN); -$stream->on('data', function ($chunk) { +$stream->on('data', function (string $chunk): void { echo $chunk; }); -$stream->on('end', function () { +$stream->on('end', function (): void { echo 'END'; }); ``` @@ -1121,7 +1121,7 @@ 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) { +$through = new ThroughStream(function (mixed $data): string { return json_encode($data) . PHP_EOL; }); $through->on('data', $this->expectCallableOnceWith("[2, true]\n")); @@ -1133,7 +1133,7 @@ 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) { +$through = new ThroughStream(function (mixed $data): string { if (!is_string($data)) { throw new \UnexpectedValueException('Only strings allowed'); } @@ -1164,7 +1164,7 @@ $stdout = new WritableResourceStream(STDOUT); $stdio = new CompositeStream($stdin, $stdout); -$stdio->on('data', function ($chunk) use ($stdio) { +$stdio->on('data', function (string $chunk) use ($stdio): void { $stdio->write('You said: ' . $chunk); }); ``` @@ -1243,7 +1243,7 @@ If you do not want to run these, they can simply be skipped like this: vendor/bin/phpunit --exclude-group internet ``` -On top of this, we use PHPStan on level 5 to ensure type safety across the project: +On top of this, we use PHPStan on max level to ensure type safety across the project: ```bash vendor/bin/phpstan diff --git a/examples/01-http.php b/examples/01-http.php index 786aefc..316e7e3 100644 --- a/examples/01-http.php +++ b/examples/01-http.php @@ -26,10 +26,10 @@ $stream = new DuplexResourceStream($resource); -$stream->on('data', function ($chunk) { +$stream->on('data', function (string $chunk): void { echo $chunk; }); -$stream->on('close', function () { +$stream->on('close', function (): void { echo '[CLOSED]' . PHP_EOL; }); diff --git a/examples/02-https.php b/examples/02-https.php index e629791..8b3ad79 100644 --- a/examples/02-https.php +++ b/examples/02-https.php @@ -26,10 +26,10 @@ $stream = new DuplexResourceStream($resource); -$stream->on('data', function ($chunk) { +$stream->on('data', function (string $chunk): void { echo $chunk; }); -$stream->on('close', function () { +$stream->on('close', function (): void { echo '[CLOSED]' . PHP_EOL; }); diff --git a/examples/91-benchmark-throughput.php b/examples/91-benchmark-throughput.php index c396606..3d0213c 100644 --- a/examples/91-benchmark-throughput.php +++ b/examples/91-benchmark-throughput.php @@ -22,8 +22,11 @@ $args = getopt('i:o:t:'); $if = $args['i'] ?? '/dev/zero'; +assert(is_string($if)); $of = $args['o'] ?? '/dev/null'; +assert(is_string($of)); $t = $args['t'] ?? 1; +assert(is_numeric($t)); // passing file descriptors requires mapping paths (https://bugs.php.net/bug.php?id=53465) $if = str_replace('/dev/fd/', 'php://fd/', $if); @@ -38,18 +41,21 @@ // setup input and output streams and pipe inbetween $fh = fopen($if, 'r'); +assert(is_resource($fh)); +$fo = fopen($of, 'w'); +assert(is_resource($fo)); $in = new React\Stream\ReadableResourceStream($fh); -$out = new React\Stream\WritableResourceStream(fopen($of, 'w')); +$out = new React\Stream\WritableResourceStream($fo); $in->pipe($out); // stop input stream in $t seconds $start = microtime(true); -$timeout = Loop::addTimer($t, function () use ($in) { +$timeout = Loop::addTimer((float) $t, function () use ($in): void { $in->close(); }); // print stream position once stream closes -$in->on('close', function () use ($fh, $start, $timeout, $info) { +$in->on('close', function () use ($fh, $start, $timeout, $info): void { $t = microtime(true) - $start; Loop::cancelTimer($timeout); diff --git a/phpstan.neon.dist b/phpstan.neon.dist index d631a6d..0fe275f 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,5 +1,5 @@ parameters: - level: 5 + level: max paths: - examples/ diff --git a/src/CompositeStream.php b/src/CompositeStream.php index 10cef07..db6db78 100644 --- a/src/CompositeStream.php +++ b/src/CompositeStream.php @@ -6,8 +6,13 @@ final class CompositeStream extends EventEmitter implements DuplexStreamInterface { + /** @var ReadableStreamInterface */ private $readable; + + /** @var WritableStreamInterface */ private $writable; + + /** @var bool */ private $closed = false; public function __construct(ReadableStreamInterface $readable, WritableStreamInterface $writable) diff --git a/src/DuplexResourceStream.php b/src/DuplexResourceStream.php index 94d013c..5990906 100644 --- a/src/DuplexResourceStream.php +++ b/src/DuplexResourceStream.php @@ -9,6 +9,7 @@ final class DuplexResourceStream extends EventEmitter implements DuplexStreamInterface { + /** @var resource */ private $stream; /** @var LoopInterface */ @@ -31,11 +32,20 @@ final class DuplexResourceStream extends EventEmitter implements DuplexStreamInt * @var int */ private $bufferSize; + + /** @var WritableStreamInterface */ private $buffer; + /** @var bool */ private $readable = true; + + /** @var bool */ private $writable = true; + + /** @var bool */ private $closing = false; + + /** @var bool */ private $listening = false; /** @@ -78,13 +88,13 @@ public function __construct($stream, ?LoopInterface $loop = null, ?int $readChun $this->bufferSize = $readChunkSize ?? 65536; $this->buffer = $buffer; - $this->buffer->on('error', function ($error) { + $this->buffer->on('error', function (\Exception $error): void { $this->emit('error', [$error]); }); $this->buffer->on('close', [$this, 'close']); - $this->buffer->on('drain', function () { + $this->buffer->on('drain', function (): void { $this->emit('drain'); }); @@ -167,11 +177,14 @@ public function pipe(WritableStreamInterface $dest, array $options = []): Writab return Util::pipe($this, $dest, $options); } - /** @internal */ - public function handleData($stream) + /** + * @internal + * @param resource $stream + */ + public function handleData($stream): void { $error = null; - \set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error): bool { + \set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline) use (&$error): bool { $error = new \ErrorException( $errstr, 0, diff --git a/src/ReadableResourceStream.php b/src/ReadableResourceStream.php index a0993bb..cf1161a 100644 --- a/src/ReadableResourceStream.php +++ b/src/ReadableResourceStream.php @@ -37,7 +37,10 @@ final class ReadableResourceStream extends EventEmitter implements ReadableStrea */ private $bufferSize; + /** @var bool */ private $closed = false; + + /** @var bool */ private $listening = false; /** @@ -121,10 +124,10 @@ public function close(): void } /** @internal */ - public function handleData() + public function handleData(): void { $error = null; - \set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error): bool { + \set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline) use (&$error): bool { $error = new \ErrorException( $errstr, 0, diff --git a/src/ReadableStreamInterface.php b/src/ReadableStreamInterface.php index e4ddf32..94e5915 100644 --- a/src/ReadableStreamInterface.php +++ b/src/ReadableStreamInterface.php @@ -17,7 +17,7 @@ * The event receives a single mixed argument for incoming data. * * ```php - * $stream->on('data', function ($data) { + * $stream->on('data', function (mixed $data): void { * echo $data; * }); * ``` @@ -47,7 +47,7 @@ * reached the end of the stream (EOF). * * ```php - * $stream->on('end', function () { + * $stream->on('end', function (): void { * echo 'END'; * }); * ``` @@ -84,7 +84,7 @@ * The event receives a single `Exception` argument for the error instance. * * ```php - * $stream->on('error', function (Exception $e) { + * $stream->on('error', function (Exception $e): void { * echo 'Error: ' . $e->getMessage() . PHP_EOL; * }); * ``` @@ -116,7 +116,7 @@ * The `close` event will be emitted once the stream closes (terminates). * * ```php - * $stream->on('close', function () { + * $stream->on('close', function (): void { * echo 'CLOSED'; * }); * ``` @@ -236,7 +236,7 @@ public function pause(): void; * ```php * $stream->pause(); * - * Loop::addTimer(1.0, function () use ($stream) { + * Loop::addTimer(1.0, function () use ($stream): void { * $stream->resume(); * }); * ``` @@ -287,7 +287,7 @@ public function resume(): void; * * ```php * $source->pipe($dest); - * $source->on('close', function () use ($dest) { + * $source->on('close', function () use ($dest): void { * $dest->end('BYE!'); * }); * ``` @@ -319,7 +319,7 @@ public function resume(): void; * a `pipe` event with this source stream an event argument. * * @param WritableStreamInterface $dest - * @param array $options + * @param array{end?:bool} $options * @return WritableStreamInterface $dest stream as-is */ public function pipe(WritableStreamInterface $dest, array $options = []): WritableStreamInterface; diff --git a/src/ThroughStream.php b/src/ThroughStream.php index a209982..a8e4837 100644 --- a/src/ThroughStream.php +++ b/src/ThroughStream.php @@ -3,7 +3,6 @@ namespace React\Stream; use Evenement\EventEmitter; -use InvalidArgumentException; /** * The `ThroughStream` implements the @@ -43,7 +42,7 @@ * a newline-delimited JSON (NDJSON) stream like this: * * ```php - * $through = new ThroughStream(function ($data) { + * $through = new ThroughStream(function (mixed $data): string { * return json_encode($data) . PHP_EOL; * }); * $through->on('data', $this->expectCallableOnceWith("[2, true]\n")); @@ -55,7 +54,7 @@ * the stream will emit an `error` event and then [`close()`](#close-1) the stream. * * ```php - * $through = new ThroughStream(function ($data) { + * $through = new ThroughStream(function (mixed $data): string { * if (!is_string($data)) { * throw new \UnexpectedValueException('Only strings allowed'); * } @@ -75,13 +74,27 @@ */ final class ThroughStream extends EventEmitter implements DuplexStreamInterface { + /** @var bool */ private $readable = true; + + /** @var bool */ private $writable = true; + + /** @var bool */ private $closed = false; + + /** @var bool */ private $paused = false; + + /** @var bool */ private $drain = false; + + /** @var ?callable */ private $callback; + /** + * @param ?callable $callback + */ public function __construct(?callable $callback = null) { $this->callback = $callback; diff --git a/src/Util.php b/src/Util.php index 23dd252..c6c038d 100644 --- a/src/Util.php +++ b/src/Util.php @@ -9,7 +9,7 @@ final class Util * * @param ReadableStreamInterface $source * @param WritableStreamInterface $dest - * @param array $options + * @param array{end?:bool} $options * @return WritableStreamInterface $dest stream as-is * @see ReadableStreamInterface::pipe() for more details */ @@ -30,33 +30,33 @@ public static function pipe(ReadableStreamInterface $source, WritableStreamInter $dest->emit('pipe', [$source]); // forward all source data events as $dest->write() - $source->on('data', $dataer = function ($data) use ($source, $dest) { + $source->on('data', $dataer = function ($data) use ($source, $dest): void { $feedMore = $dest->write($data); if (false === $feedMore) { $source->pause(); } }); - $dest->on('close', function () use ($source, $dataer) { + $dest->on('close', function () use ($source, $dataer): void { $source->removeListener('data', $dataer); $source->pause(); }); // forward destination drain as $source->resume() - $dest->on('drain', $drainer = function () use ($source) { + $dest->on('drain', $drainer = function () use ($source): void { $source->resume(); }); - $source->on('close', function () use ($dest, $drainer) { + $source->on('close', function () use ($dest, $drainer): void { $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) { + $source->on('end', $ender = function () use ($dest): void { $dest->end(); }); - $dest->on('close', function () use ($source, $ender) { + $dest->on('close', function () use ($source, $ender): void { $source->removeListener('end', $ender); }); } @@ -73,7 +73,7 @@ public static function pipe(ReadableStreamInterface $source, WritableStreamInter public static function forwardEvents($source, $target, array $events): void { foreach ($events as $event) { - $source->on($event, function () use ($event, $target) { + $source->on($event, function () use ($event, $target): void { $target->emit($event, \func_get_args()); }); } diff --git a/src/WritableResourceStream.php b/src/WritableResourceStream.php index 6140246..68b2721 100644 --- a/src/WritableResourceStream.php +++ b/src/WritableResourceStream.php @@ -8,24 +8,28 @@ final class WritableResourceStream extends EventEmitter implements WritableStreamInterface { + /** @var resource */ private $stream; /** @var LoopInterface */ private $loop; - /** - * @var int - */ + /** @var int */ private $softLimit; - /** - * @var int - */ + /** @var int */ private $writeChunkSize; + /** @var bool */ private $listening = false; + + /** @var bool */ private $writable = true; + + /** @var bool */ private $closed = false; + + /** @var string */ private $data = ''; /** @@ -119,10 +123,10 @@ public function close(): void } /** @internal */ - public function handleWrite() + public function handleWrite(): void { $error = null; - \set_error_handler(function ($_, $errstr) use (&$error): bool { + \set_error_handler(function (int $_, string $errstr) use (&$error): bool { $error = $errstr; return true; }); @@ -130,6 +134,7 @@ public function handleWrite() if ($this->writeChunkSize === -1) { $sent = \fwrite($this->stream, $this->data); } else { + \assert($this->writeChunkSize >= -1); $sent = \fwrite($this->stream, $this->data, $this->writeChunkSize); } @@ -151,7 +156,7 @@ public function handleWrite() } $exceeded = isset($this->data[$this->softLimit - 1]); - $this->data = (string) \substr($this->data, $sent); + $this->data = (string) \substr($this->data, (int) $sent); // buffer has been above limit and is now below limit if ($exceeded && !isset($this->data[$this->softLimit - 1])) { diff --git a/src/WritableStreamInterface.php b/src/WritableStreamInterface.php index 77baaad..d48536d 100644 --- a/src/WritableStreamInterface.php +++ b/src/WritableStreamInterface.php @@ -16,7 +16,7 @@ * previously and is now ready to accept more data. * * ```php - * $stream->on('drain', function () use ($stream) { + * $stream->on('drain', function () use ($stream): void { * echo 'Stream is now ready to accept more data'; * }); * ``` @@ -37,11 +37,11 @@ * source stream. * * ```php - * $stream->on('pipe', function (ReadableStreamInterface $source) use ($stream) { + * $stream->on('pipe', function (ReadableStreamInterface $source) use ($stream): void { * echo 'Now receiving piped data'; * * // explicitly close target if source emits an error - * $source->on('error', function () use ($stream) { + * $source->on('error', function () use ($stream): void { * $stream->close(); * }); * }); @@ -64,7 +64,7 @@ * The event receives a single `Exception` argument for the error instance. * * ```php - * $stream->on('error', function (Exception $e) { + * $stream->on('error', function (Exception $e): void { * echo 'Error: ' . $e->getMessage() . PHP_EOL; * }); * ``` @@ -93,7 +93,7 @@ * The `close` event will be emitted once the stream closes (terminates). * * ```php - * $stream->on('close', function () { + * $stream->on('close', function (): void { * echo 'CLOSED'; * }); * ``` @@ -330,7 +330,7 @@ public function end($data = null): void; * * ```php * $stream->end(); - * Loop::addTimer(1.0, function () use ($stream) { + * Loop::addTimer(1.0, function () use ($stream): void { * $stream->close(); * }); * ``` diff --git a/tests/CompositeStreamTest.php b/tests/CompositeStreamTest.php index cc9ef28..6e43ad7 100644 --- a/tests/CompositeStreamTest.php +++ b/tests/CompositeStreamTest.php @@ -13,7 +13,7 @@ class CompositeStreamTest extends TestCase { /** @test */ - public function itShouldCloseReadableIfNotWritable() + public function itShouldCloseReadableIfNotWritable(): void { $readable = $this->createMock(ReadableStreamInterface::class); $readable @@ -39,7 +39,7 @@ public function itShouldCloseReadableIfNotWritable() } /** @test */ - public function itShouldCloseWritableIfNotReadable() + public function itShouldCloseWritableIfNotReadable(): void { $readable = $this->createMock(ReadableStreamInterface::class); $readable @@ -61,7 +61,7 @@ public function itShouldCloseWritableIfNotReadable() } /** @test */ - public function itShouldForwardWritableCallsToWritableStream() + public function itShouldForwardWritableCallsToWritableStream(): void { $readable = $this->createMock(ReadableStreamInterface::class); $readable @@ -87,7 +87,7 @@ public function itShouldForwardWritableCallsToWritableStream() } /** @test */ - public function itShouldForwardReadableCallsToReadableStream() + public function itShouldForwardReadableCallsToReadableStream(): void { $readable = $this->createMock(ReadableStreamInterface::class); $readable @@ -116,7 +116,7 @@ public function itShouldForwardReadableCallsToReadableStream() } /** @test */ - public function itShouldNotForwardResumeIfStreamIsNotWritable() + public function itShouldNotForwardResumeIfStreamIsNotWritable(): void { $readable = $this->createMock(ReadableStreamInterface::class); $readable @@ -140,7 +140,7 @@ public function itShouldNotForwardResumeIfStreamIsNotWritable() } /** @test */ - public function endShouldDelegateToWritableWithData() + public function endShouldDelegateToWritableWithData(): void { $readable = $this->createMock(ReadableStreamInterface::class); $readable @@ -165,7 +165,7 @@ public function endShouldDelegateToWritableWithData() } /** @test */ - public function closeShouldCloseBothStreams() + public function closeShouldCloseBothStreams(): void { $readable = $this->createMock(ReadableStreamInterface::class); $readable @@ -192,7 +192,7 @@ public function closeShouldCloseBothStreams() } /** @test */ - public function itShouldForwardCloseOnlyOnce() + public function itShouldForwardCloseOnlyOnce(): void { $readable = new ThroughStream(); $writable = new ThroughStream(); @@ -205,7 +205,7 @@ public function itShouldForwardCloseOnlyOnce() } /** @test */ - public function itShouldForwardCloseAndRemoveAllListeners() + public function itShouldForwardCloseAndRemoveAllListeners(): void { $in = new ThroughStream(); @@ -224,7 +224,7 @@ public function itShouldForwardCloseAndRemoveAllListeners() } /** @test */ - public function itShouldReceiveForwardedEvents() + public function itShouldReceiveForwardedEvents(): void { $readable = new ThroughStream(); $writable = new ThroughStream(); @@ -238,7 +238,7 @@ public function itShouldReceiveForwardedEvents() } /** @test */ - public function itShouldHandlePipingCorrectly() + public function itShouldHandlePipingCorrectly(): void { $readable = $this->createMock(ReadableStreamInterface::class); $readable @@ -263,7 +263,7 @@ public function itShouldHandlePipingCorrectly() } /** @test */ - public function itShouldForwardPipeCallsToReadableStream() + public function itShouldForwardPipeCallsToReadableStream(): void { $readable = new ThroughStream(); diff --git a/tests/DuplexResourceStreamIntegrationTest.php b/tests/DuplexResourceStreamIntegrationTest.php index a468aef..2c3d2ed 100644 --- a/tests/DuplexResourceStreamIntegrationTest.php +++ b/tests/DuplexResourceStreamIntegrationTest.php @@ -11,7 +11,7 @@ class DuplexResourceStreamIntegrationTest extends TestCase { - public function loopProvider() + public function loopProvider(): \Generator { yield [ function() { @@ -34,7 +34,7 @@ function () { /** * @dataProvider loopProvider */ - public function testBufferReadsLargeChunks($condition, $loopFactory) + public function testBufferReadsLargeChunks(callable $condition, callable $loopFactory): void { if (true !== $condition()) { $this->markTestSkipped('Loop implementation not available'); @@ -42,7 +42,9 @@ public function testBufferReadsLargeChunks($condition, $loopFactory) $loop = $loopFactory(); - list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0); + $pair = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0); + assert(is_array($pair)); + [$sockA, $sockB] = $pair; $bufferSize = 4096; $streamA = new DuplexResourceStream($sockA, $loop, $bufferSize); @@ -70,7 +72,7 @@ public function testBufferReadsLargeChunks($condition, $loopFactory) /** * @dataProvider loopProvider */ - public function testWriteLargeChunk($condition, $loopFactory) + public function testWriteLargeChunk(callable $condition, callable $loopFactory): void { if (true !== $condition()) { $this->markTestSkipped('Loop implementation not available'); @@ -78,7 +80,9 @@ public function testWriteLargeChunk($condition, $loopFactory) $loop = $loopFactory(); - list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0); + $pair = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0); + assert(is_array($pair)); + [$sockA, $sockB] = $pair; $streamA = new DuplexResourceStream($sockA, $loop); $streamB = new DuplexResourceStream($sockB, $loop); @@ -110,7 +114,7 @@ public function testWriteLargeChunk($condition, $loopFactory) /** * @dataProvider loopProvider */ - public function testDoesNotEmitDataIfNothingHasBeenWritten($condition, $loopFactory) + public function testDoesNotEmitDataIfNothingHasBeenWritten(callable $condition, callable $loopFactory): void { if (true !== $condition()) { $this->markTestSkipped('Loop implementation not available'); @@ -118,7 +122,9 @@ public function testDoesNotEmitDataIfNothingHasBeenWritten($condition, $loopFact $loop = $loopFactory(); - list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0); + $pair = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0); + assert(is_array($pair)); + [$sockA, $sockB] = $pair; $streamA = new DuplexResourceStream($sockA, $loop); $streamB = new DuplexResourceStream($sockB, $loop); @@ -138,7 +144,7 @@ public function testDoesNotEmitDataIfNothingHasBeenWritten($condition, $loopFact /** * @dataProvider loopProvider */ - public function testDoesNotWriteDataIfRemoteSideFromPairHasBeenClosed($condition, $loopFactory) + public function testDoesNotWriteDataIfRemoteSideFromPairHasBeenClosed(callable $condition, callable $loopFactory): void { if (true !== $condition()) { $this->markTestSkipped('Loop implementation not available'); @@ -146,7 +152,9 @@ public function testDoesNotWriteDataIfRemoteSideFromPairHasBeenClosed($condition $loop = $loopFactory(); - list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0); + $pair = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0); + assert(is_array($pair)); + [$sockA, $sockB] = $pair; $streamA = new DuplexResourceStream($sockA, $loop); $streamB = new DuplexResourceStream($sockB, $loop); @@ -168,7 +176,7 @@ public function testDoesNotWriteDataIfRemoteSideFromPairHasBeenClosed($condition /** * @dataProvider loopProvider */ - public function testDoesNotWriteDataIfServerSideHasBeenClosed($condition, $loopFactory) + public function testDoesNotWriteDataIfServerSideHasBeenClosed(callable $condition, callable $loopFactory): void { if (true !== $condition()) { $this->markTestSkipped('Loop implementation not available'); @@ -177,9 +185,13 @@ public function testDoesNotWriteDataIfServerSideHasBeenClosed($condition, $loopF $loop = $loopFactory(); $server = stream_socket_server('tcp://127.0.0.1:0'); + assert(is_resource($server)); + + $client = stream_socket_client((string) stream_socket_get_name($server, false)); + assert(is_resource($client)); - $client = stream_socket_client(stream_socket_get_name($server, false)); $peer = stream_socket_accept($server); + assert(is_resource($peer)); $streamA = new DuplexResourceStream($client, $loop); $streamB = new DuplexResourceStream($peer, $loop); @@ -201,7 +213,7 @@ public function testDoesNotWriteDataIfServerSideHasBeenClosed($condition, $loopF /** * @dataProvider loopProvider */ - public function testDoesNotWriteDataIfClientSideHasBeenClosed($condition, $loopFactory) + public function testDoesNotWriteDataIfClientSideHasBeenClosed(callable $condition, callable $loopFactory): void { if (true !== $condition()) { $this->markTestSkipped('Loop implementation not available'); @@ -210,9 +222,13 @@ public function testDoesNotWriteDataIfClientSideHasBeenClosed($condition, $loopF $loop = $loopFactory(); $server = stream_socket_server('tcp://127.0.0.1:0'); + assert(is_resource($server)); + + $client = stream_socket_client((string) stream_socket_get_name($server, false)); + assert(is_resource($client)); - $client = stream_socket_client(stream_socket_get_name($server, false)); $peer = stream_socket_accept($server); + assert(is_resource($peer)); $streamA = new DuplexResourceStream($peer, $loop); $streamB = new DuplexResourceStream($client, $loop); @@ -234,7 +250,7 @@ public function testDoesNotWriteDataIfClientSideHasBeenClosed($condition, $loopF /** * @dataProvider loopProvider */ - public function testReadsSingleChunkFromProcessPipe($condition, $loopFactory) + public function testReadsSingleChunkFromProcessPipe(callable $condition, callable $loopFactory): void { if (true !== $condition()) { $this->markTestSkipped('Loop implementation not available'); @@ -242,7 +258,10 @@ public function testReadsSingleChunkFromProcessPipe($condition, $loopFactory) $loop = $loopFactory(); - $stream = new ReadableResourceStream(popen('echo test', 'r'), $loop); + $fh = popen('echo test', 'r'); + assert(is_resource($fh)); + + $stream = new ReadableResourceStream($fh, $loop); $stream->on('data', $this->expectCallableOnceWith("test\n")); $stream->on('end', $this->expectCallableOnce()); $stream->on('error', $this->expectCallableNever()); @@ -253,7 +272,7 @@ public function testReadsSingleChunkFromProcessPipe($condition, $loopFactory) /** * @dataProvider loopProvider */ - public function testReadsMultipleChunksFromProcessPipe($condition, $loopFactory) + public function testReadsMultipleChunksFromProcessPipe(callable $condition, callable $loopFactory): void { if (true !== $condition()) { $this->markTestSkipped('Loop implementation not available'); @@ -261,7 +280,10 @@ public function testReadsMultipleChunksFromProcessPipe($condition, $loopFactory) $loop = $loopFactory(); - $stream = new ReadableResourceStream(popen('echo a;sleep 0.1;echo b;sleep 0.1;echo c', 'r'), $loop); + $fh = popen('echo a;sleep 0.1;echo b;sleep 0.1;echo c', 'r'); + assert(is_resource($fh)); + + $stream = new ReadableResourceStream($fh, $loop); $buffer = ''; $stream->on('data', function ($chunk) use (&$buffer) { @@ -279,7 +301,7 @@ public function testReadsMultipleChunksFromProcessPipe($condition, $loopFactory) /** * @dataProvider loopProvider */ - public function testReadsLongChunksFromProcessPipe($condition, $loopFactory) + public function testReadsLongChunksFromProcessPipe(callable $condition, callable $loopFactory): void { if (true !== $condition()) { $this->markTestSkipped('Loop implementation not available'); @@ -287,7 +309,10 @@ public function testReadsLongChunksFromProcessPipe($condition, $loopFactory) $loop = $loopFactory(); - $stream = new ReadableResourceStream(popen('dd if=/dev/zero bs=12345 count=1234 2>&-', 'r'), $loop); + $fh = popen('dd if=/dev/zero bs=12345 count=1234 2>&-', 'r'); + assert(is_resource($fh)); + + $stream = new ReadableResourceStream($fh, $loop); $bytes = 0; $stream->on('data', function ($chunk) use (&$bytes) { @@ -305,7 +330,7 @@ public function testReadsLongChunksFromProcessPipe($condition, $loopFactory) /** * @dataProvider loopProvider */ - public function testReadsNothingFromProcessPipeWithNoOutput($condition, $loopFactory) + public function testReadsNothingFromProcessPipeWithNoOutput(callable $condition, callable $loopFactory): void { if (true !== $condition()) { $this->markTestSkipped('Loop implementation not available'); @@ -313,7 +338,10 @@ public function testReadsNothingFromProcessPipeWithNoOutput($condition, $loopFac $loop = $loopFactory(); - $stream = new ReadableResourceStream(popen('true', 'r'), $loop); + $fh = popen('true', 'r'); + assert(is_resource($fh)); + + $stream = new ReadableResourceStream($fh, $loop); $stream->on('data', $this->expectCallableNever()); $stream->on('end', $this->expectCallableOnce()); $stream->on('error', $this->expectCallableNever()); @@ -325,16 +353,20 @@ public function testReadsNothingFromProcessPipeWithNoOutput($condition, $loopFac * @covers React\Stream\ReadableResourceStream::handleData * @dataProvider loopProvider */ - public function testEmptyReadShouldntFcloseStream($condition, $loopFactory) + public function testEmptyReadShouldntFcloseStream(callable $condition, callable $loopFactory): void { if (true !== $condition()) { $this->markTestSkipped('Loop implementation not available'); } $server = stream_socket_server('tcp://127.0.0.1:0'); + assert(is_resource($server)); + + $client = stream_socket_client((string) stream_socket_get_name($server, false)); + assert(is_resource($client)); - $client = stream_socket_client(stream_socket_get_name($server, false)); $stream = stream_socket_accept($server); + assert(is_resource($stream)); // add a filter which returns an error when encountering an 'a' when reading @@ -358,7 +390,7 @@ public function testEmptyReadShouldntFcloseStream($condition, $loopFactory) fclose($server); } - private function loopTick(LoopInterface $loop) + private function loopTick(LoopInterface $loop): void { $loop->addTimer(0, function () use ($loop) { $loop->stop(); diff --git a/tests/DuplexResourceStreamTest.php b/tests/DuplexResourceStreamTest.php index 5b18553..c1076a6 100644 --- a/tests/DuplexResourceStreamTest.php +++ b/tests/DuplexResourceStreamTest.php @@ -2,6 +2,7 @@ namespace React\Tests\Stream; +use PHPUnit\Framework\MockObject\MockObject; use React\EventLoop\LoopInterface; use React\Stream\DuplexResourceStream; use React\Stream\WritableResourceStream; @@ -14,17 +15,20 @@ class DuplexResourceStreamTest extends TestCase * @covers React\Stream\DuplexResourceStream::__construct * @doesNotPerformAssertions */ - public function testConstructor() + public function testConstructor(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); new DuplexResourceStream($stream, $loop); } - public function testConstructWithoutLoopAssignsLoopAutomatically() + public function testConstructWithoutLoopAssignsLoopAutomatically(): void { $resource = fopen('php://temp', 'r+'); + assert(is_resource($resource)); $stream = new DuplexResourceStream($resource); @@ -39,11 +43,12 @@ public function testConstructWithoutLoopAssignsLoopAutomatically() * @covers React\Stream\DuplexResourceStream::__construct * @doesNotPerformAssertions */ - public function testConstructorWithExcessiveMode() + public function testConstructorWithExcessiveMode(): void { // 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'); + $name = (string) tempnam(sys_get_temp_dir(), 'test'); + $stream = fopen($name, 'r+eANYTHING'); + assert(is_resource($stream)); unlink($name); $loop = $this->createLoopMock(); @@ -54,7 +59,7 @@ public function testConstructorWithExcessiveMode() /** * @covers React\Stream\DuplexResourceStream::__construct */ - public function testConstructorThrowsExceptionOnInvalidStream() + public function testConstructorThrowsExceptionOnInvalidStream(): void { $loop = $this->createLoopMock(); @@ -65,7 +70,7 @@ public function testConstructorThrowsExceptionOnInvalidStream() /** * @covers React\Stream\DuplexResourceStream::__construct */ - public function testConstructorThrowsExceptionOnWriteOnlyStream() + public function testConstructorThrowsExceptionOnWriteOnlyStream(): void { $loop = $this->createLoopMock(); @@ -76,11 +81,12 @@ public function testConstructorThrowsExceptionOnWriteOnlyStream() /** * @covers React\Stream\DuplexResourceStream::__construct */ - public function testConstructorThrowsExceptionOnWriteOnlyStreamWithExcessiveMode() + public function testConstructorThrowsExceptionOnWriteOnlyStreamWithExcessiveMode(): void { // excessive flags are ignored for temp streams, so we have to use a file stream - $name = tempnam(sys_get_temp_dir(), 'test'); + $name = (string) tempnam(sys_get_temp_dir(), 'test'); $stream = fopen($name, 'weANYTHING'); + assert(is_resource($stream)); unlink($name); $loop = $this->createLoopMock(); @@ -91,13 +97,15 @@ public function testConstructorThrowsExceptionOnWriteOnlyStreamWithExcessiveMode /** * @covers React\Stream\DuplexResourceStream::__construct */ - public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking() + public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking(): void { if (!in_array('blocking', stream_get_wrappers())) { stream_wrapper_register('blocking', 'React\Tests\Stream\EnforceBlockingWrapper'); } $stream = fopen('blocking://test', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $this->expectException(\RuntimeException::class); @@ -108,9 +116,11 @@ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking( * @covers React\Stream\DuplexResourceStream::__construct * @doesNotPerformAssertions */ - public function testConstructorAcceptsBuffer() + public function testConstructorAcceptsBuffer(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $buffer = $this->createMock(WritableStreamInterface::class); @@ -122,13 +132,15 @@ public function testConstructorAcceptsBuffer() /** * @covers React\Stream\DuplexResourceStream::__construct */ - public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlockingWithBufferGiven() + public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlockingWithBufferGiven(): void { if (!in_array('blocking', stream_get_wrappers())) { stream_wrapper_register('blocking', 'React\Tests\Stream\EnforceBlockingWrapper'); } $stream = fopen('blocking://test', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $buffer = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); @@ -138,9 +150,11 @@ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlockingW new DuplexResourceStream($stream, $loop, null, $buffer); } - public function testCloseShouldEmitCloseEvent() + public function testCloseShouldEmitCloseEvent(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $conn = new DuplexResourceStream($stream, $loop); @@ -152,9 +166,11 @@ public function testCloseShouldEmitCloseEvent() $this->assertFalse($conn->isReadable()); } - public function testEndShouldEndBuffer() + public function testEndShouldEndBuffer(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $buffer = $this->createMock(WritableStreamInterface::class); @@ -166,9 +182,11 @@ public function testEndShouldEndBuffer() } - public function testEndAfterCloseIsNoOp() + public function testEndAfterCloseIsNoOp(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $buffer = $this->createMock(WritableStreamInterface::class); @@ -184,9 +202,11 @@ public function testEndAfterCloseIsNoOp() * @covers React\Stream\DuplexResourceStream::__construct * @covers React\Stream\DuplexResourceStream::handleData */ - public function testDataEvent() + public function testDataEvent(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $capturedData = null; @@ -207,9 +227,11 @@ public function testDataEvent() * @covers React\Stream\DuplexResourceStream::__construct * @covers React\Stream\DuplexResourceStream::handleData */ - public function testDataEventDoesEmitOneChunkMatchingBufferSize() + public function testDataEventDoesEmitOneChunkMatchingBufferSize(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $capturedData = null; @@ -232,9 +254,11 @@ public function testDataEventDoesEmitOneChunkMatchingBufferSize() * @covers React\Stream\DuplexResourceStream::__construct * @covers React\Stream\DuplexResourceStream::handleData */ - public function testDataEventDoesEmitOneChunkUntilStreamEndsWhenBufferSizeIsInfinite() + public function testDataEventDoesEmitOneChunkUntilStreamEndsWhenBufferSizeIsInfinite(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $capturedData = null; @@ -257,9 +281,11 @@ public function testDataEventDoesEmitOneChunkUntilStreamEndsWhenBufferSizeIsInfi /** * @covers React\Stream\DuplexResourceStream::handleData */ - public function testEmptyStreamShouldNotEmitData() + public function testEmptyStreamShouldNotEmitData(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $conn = new DuplexResourceStream($stream, $loop); @@ -271,9 +297,11 @@ public function testEmptyStreamShouldNotEmitData() /** * @covers React\Stream\DuplexResourceStream::write */ - public function testWrite() + public function testWrite(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createWriteableLoopMock(); $conn = new DuplexResourceStream($stream, $loop); @@ -288,9 +316,11 @@ public function testWrite() * @covers React\Stream\DuplexResourceStream::isReadable * @covers React\Stream\DuplexResourceStream::isWritable */ - public function testEnd() + public function testEnd(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $conn = new DuplexResourceStream($stream, $loop); @@ -304,9 +334,11 @@ public function testEnd() /** * @covers React\Stream\DuplexResourceStream::end */ - public function testEndRemovesReadStreamFromLoop() + public function testEndRemovesReadStreamFromLoop(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $loop->expects($this->once())->method('addReadStream')->with($stream); $loop->expects($this->once())->method('removeReadStream')->with($stream); @@ -318,9 +350,11 @@ public function testEndRemovesReadStreamFromLoop() /** * @covers React\Stream\DuplexResourceStream::pause */ - public function testPauseRemovesReadStreamFromLoop() + public function testPauseRemovesReadStreamFromLoop(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $loop->expects($this->once())->method('addReadStream')->with($stream); $loop->expects($this->once())->method('removeReadStream')->with($stream); @@ -333,9 +367,11 @@ public function testPauseRemovesReadStreamFromLoop() /** * @covers React\Stream\DuplexResourceStream::pause */ - public function testResumeDoesAddStreamToLoopOnlyOnce() + public function testResumeDoesAddStreamToLoopOnlyOnce(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $loop->expects($this->once())->method('addReadStream')->with($stream); @@ -347,9 +383,11 @@ public function testResumeDoesAddStreamToLoopOnlyOnce() /** * @covers React\Stream\DuplexResourceStream::close */ - public function testCloseRemovesReadStreamFromLoop() + public function testCloseRemovesReadStreamFromLoop(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $loop->expects($this->once())->method('addReadStream')->with($stream); $loop->expects($this->once())->method('removeReadStream')->with($stream); @@ -361,9 +399,11 @@ public function testCloseRemovesReadStreamFromLoop() /** * @covers React\Stream\DuplexResourceStream::close */ - public function testCloseAfterPauseRemovesReadStreamFromLoopOnlyOnce() + public function testCloseAfterPauseRemovesReadStreamFromLoopOnlyOnce(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $loop->expects($this->once())->method('addReadStream')->with($stream); $loop->expects($this->once())->method('removeReadStream')->with($stream); @@ -376,9 +416,11 @@ public function testCloseAfterPauseRemovesReadStreamFromLoopOnlyOnce() /** * @covers React\Stream\DuplexResourceStream::close */ - public function testResumeAfterCloseDoesAddReadStreamToLoopOnlyOnce() + public function testResumeAfterCloseDoesAddReadStreamToLoopOnlyOnce(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $loop->expects($this->once())->method('addReadStream')->with($stream); @@ -387,10 +429,12 @@ public function testResumeAfterCloseDoesAddReadStreamToLoopOnlyOnce() $conn->resume(); } - public function testEndedStreamsShouldNotWrite() + public function testEndedStreamsShouldNotWrite(): void { - $file = tempnam(sys_get_temp_dir(), 'reactphptest_'); + $file = (string) tempnam(sys_get_temp_dir(), 'reactphptest_'); $stream = fopen($file, 'r+'); + assert(is_resource($stream)); + $loop = $this->createWriteableLoopMock(); $conn = new DuplexResourceStream($stream, $loop); @@ -398,7 +442,9 @@ public function testEndedStreamsShouldNotWrite() $conn->end(); $res = $conn->write("bar\n"); + $stream = fopen($file, 'r'); + assert(is_resource($stream)); $this->assertSame("foo\n", fgets($stream)); $this->assertFalse($res); @@ -406,9 +452,11 @@ public function testEndedStreamsShouldNotWrite() unlink($file); } - public function testPipeShouldReturnDestination() + public function testPipeShouldReturnDestination(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $conn = new DuplexResourceStream($stream, $loop); @@ -418,9 +466,11 @@ public function testPipeShouldReturnDestination() $this->assertSame($dest, $conn->pipe($dest)); } - public function testBufferEventsShouldBubbleUp() + public function testBufferEventsShouldBubbleUp(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $buffer = new WritableResourceStream($stream, $loop); @@ -436,9 +486,11 @@ public function testBufferEventsShouldBubbleUp() /** * @covers React\Stream\DuplexResourceStream::handleData */ - public function testClosingStreamInDataEventShouldNotTriggerError() + public function testClosingStreamInDataEventShouldNotTriggerError(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $conn = new DuplexResourceStream($stream, $loop); @@ -456,9 +508,10 @@ public function testClosingStreamInDataEventShouldNotTriggerError() /** * @covers React\Stream\DuplexResourceStream::handleData */ - public function testDataFiltered() + public function testDataFiltered(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); // add a filter which removes every 'a' when reading filter_append($stream, function ($chunk) { @@ -484,9 +537,10 @@ public function testDataFiltered() /** * @covers React\Stream\DuplexResourceStream::handleData */ - public function testDataErrorShouldEmitErrorAndClose() + public function testDataErrorShouldEmitErrorAndClose(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); // add a filter which returns an error when encountering an 'a' when reading filter_append($stream, function ($chunk) { @@ -509,7 +563,7 @@ public function testDataErrorShouldEmitErrorAndClose() $conn->handleData($stream); } - private function createWriteableLoopMock() + private function createWriteableLoopMock(): LoopInterface { $loop = $this->createLoopMock(); $loop @@ -522,8 +576,10 @@ private function createWriteableLoopMock() return $loop; } - private function createLoopMock() + /** @return LoopInterface&MockObject */ + private function createLoopMock(): MockObject { + /** @var MockObject&LoopInterface */ return $this->createMock(LoopInterface::class); } } diff --git a/tests/EnforceBlockingWrapper.php b/tests/EnforceBlockingWrapper.php index a171b41..cab433b 100644 --- a/tests/EnforceBlockingWrapper.php +++ b/tests/EnforceBlockingWrapper.php @@ -9,24 +9,25 @@ */ class EnforceBlockingWrapper { + /** @var resource */ public $context; - public function stream_open($path, $mode, $options, &$opened_path) + public function stream_open(string $path, string $mode, int $options, ?string &$opened_path): bool { return true; } - public function stream_cast($cast_as) + public function stream_cast(int $cast_as): bool { return false; } - public function stream_eof() + public function stream_eof(): bool { return false; } - public function stream_set_option($option, $arg1, $arg2) + public function stream_set_option(int $option, int $arg1, ?int $arg2): bool { if ($option === STREAM_OPTION_BLOCKING) { return false; diff --git a/tests/FunctionalInternetTest.php b/tests/FunctionalInternetTest.php index 5113e7b..f56eefc 100644 --- a/tests/FunctionalInternetTest.php +++ b/tests/FunctionalInternetTest.php @@ -12,10 +12,11 @@ */ class FunctionalInternetTest extends TestCase { - public function testUploadKilobytePlain() + public function testUploadKilobytePlain(): void { $size = 1000; $stream = stream_socket_client('tcp://httpbin.org:80'); + assert(is_resource($stream)); $loop = Factory::create(); $stream = new DuplexResourceStream($stream, $loop); @@ -34,10 +35,11 @@ public function testUploadKilobytePlain() $this->assertNotEquals('', $buffer); } - public function testUploadBiggerBlockPlain() + public function testUploadBiggerBlockPlain(): void { $size = 50 * 1000; $stream = stream_socket_client('tcp://httpbin.org:80'); + assert(is_resource($stream)); $loop = Factory::create(); $stream = new DuplexResourceStream($stream, $loop); @@ -56,10 +58,11 @@ public function testUploadBiggerBlockPlain() $this->assertNotEquals('', $buffer); } - public function testUploadKilobyteSecure() + public function testUploadKilobyteSecure(): void { $size = 1000; $stream = stream_socket_client('ssl://httpbin.org:443'); + assert(is_resource($stream)); $loop = Factory::create(); $stream = new DuplexResourceStream($stream, $loop); @@ -78,7 +81,7 @@ public function testUploadKilobyteSecure() $this->assertNotEquals('', $buffer); } - public function testUploadBiggerBlockSecure() + public function testUploadBiggerBlockSecure(): void { // A few dozen kilobytes should be enough to verify this works. // Underlying buffer sizes are platform-specific, so let's increase this @@ -86,6 +89,7 @@ public function testUploadBiggerBlockSecure() $size = 136 * 1000; $stream = stream_socket_client('ssl://httpbin.org:443'); + assert(is_resource($stream)); // PHP < 7.1.4 suffers from a bug when writing big chunks of data over // TLS streams at once. @@ -114,7 +118,7 @@ public function testUploadBiggerBlockSecure() $this->assertNotEquals('', $buffer); } - private function awaitStreamClose(DuplexResourceStream $stream, LoopInterface $loop, $timeout = 10.0) + private function awaitStreamClose(DuplexResourceStream $stream, LoopInterface $loop, float $timeout = 10.0): void { $stream->on('close', function () use ($loop) { $loop->stop(); diff --git a/tests/ReadableResourceStreamTest.php b/tests/ReadableResourceStreamTest.php index 365ac0d..3f7c144 100644 --- a/tests/ReadableResourceStreamTest.php +++ b/tests/ReadableResourceStreamTest.php @@ -2,6 +2,7 @@ namespace React\Tests\Stream; +use PHPUnit\Framework\MockObject\MockObject; use React\EventLoop\LoopInterface; use React\Stream\ReadableResourceStream; use React\Stream\WritableStreamInterface; @@ -13,17 +14,20 @@ class ReadableResourceStreamTest extends TestCase * @covers React\Stream\ReadableResourceStream::__construct * @doesNotPerformAssertions */ - public function testConstructor() + public function testConstructor(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); new ReadableResourceStream($stream, $loop); } - public function testConstructWithoutLoopAssignsLoopAutomatically() + public function testConstructWithoutLoopAssignsLoopAutomatically(): void { $resource = fopen('php://temp', 'r+'); + assert(is_resource($resource)); $stream = new ReadableResourceStream($resource); @@ -38,11 +42,12 @@ public function testConstructWithoutLoopAssignsLoopAutomatically() * @covers React\Stream\ReadableResourceStream::__construct * @doesNotPerformAssertions */ - public function testConstructorWithExcessiveMode() + public function testConstructorWithExcessiveMode(): void { // 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'); + $name = (string) tempnam(sys_get_temp_dir(), 'test'); + $stream = fopen($name, 'r+eANYTHING'); + assert(is_resource($stream)); unlink($name); $loop = $this->createLoopMock(); @@ -53,7 +58,7 @@ public function testConstructorWithExcessiveMode() /** * @covers React\Stream\ReadableResourceStream::__construct */ - public function testConstructorThrowsExceptionOnInvalidStream() + public function testConstructorThrowsExceptionOnInvalidStream(): void { $loop = $this->createLoopMock(); @@ -64,7 +69,7 @@ public function testConstructorThrowsExceptionOnInvalidStream() /** * @covers React\Stream\ReadableResourceStream::__construct */ - public function testConstructorThrowsExceptionOnWriteOnlyStream() + public function testConstructorThrowsExceptionOnWriteOnlyStream(): void { $loop = $this->createLoopMock(); @@ -75,11 +80,12 @@ public function testConstructorThrowsExceptionOnWriteOnlyStream() /** * @covers React\Stream\ReadableResourceStream::__construct */ - public function testConstructorThrowsExceptionOnWriteOnlyStreamWithExcessiveMode() + public function testConstructorThrowsExceptionOnWriteOnlyStreamWithExcessiveMode(): void { // excessive flags are ignored for temp streams, so we have to use a file stream - $name = tempnam(sys_get_temp_dir(), 'test'); + $name = (string) tempnam(sys_get_temp_dir(), 'test'); $stream = fopen($name, 'weANYTHING'); + assert(is_resource($stream)); unlink($name); $loop = $this->createLoopMock(); @@ -90,23 +96,26 @@ public function testConstructorThrowsExceptionOnWriteOnlyStreamWithExcessiveMode /** * @covers React\Stream\ReadableResourceStream::__construct */ - public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking() + public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking(): void { if (!in_array('blocking', stream_get_wrappers())) { stream_wrapper_register('blocking', 'React\Tests\Stream\EnforceBlockingWrapper'); } $stream = fopen('blocking://test', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $this->expectException(\RuntimeException::class); new ReadableResourceStream($stream, $loop); } - - public function testCloseShouldEmitCloseEvent() + public function testCloseShouldEmitCloseEvent(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $conn = new ReadableResourceStream($stream, $loop); @@ -117,9 +126,11 @@ public function testCloseShouldEmitCloseEvent() $this->assertFalse($conn->isReadable()); } - public function testCloseTwiceShouldEmitCloseEventOnce() + public function testCloseTwiceShouldEmitCloseEventOnce(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $conn = new ReadableResourceStream($stream, $loop); @@ -133,9 +144,11 @@ public function testCloseTwiceShouldEmitCloseEventOnce() * @covers React\Stream\ReadableResourceStream::__construct * @covers React\Stream\ReadableResourceStream::handleData */ - public function testDataEvent() + public function testDataEvent(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $capturedData = null; @@ -156,9 +169,11 @@ public function testDataEvent() * @covers React\Stream\ReadableResourceStream::__construct * @covers React\Stream\ReadableResourceStream::handleData */ - public function testDataEventDoesEmitOneChunkMatchingBufferSize() + public function testDataEventDoesEmitOneChunkMatchingBufferSize(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $capturedData = null; @@ -181,9 +196,11 @@ public function testDataEventDoesEmitOneChunkMatchingBufferSize() * @covers React\Stream\ReadableResourceStream::__construct * @covers React\Stream\ReadableResourceStream::handleData */ - public function testDataEventDoesEmitOneChunkUntilStreamEndsWhenBufferSizeIsInfinite() + public function testDataEventDoesEmitOneChunkUntilStreamEndsWhenBufferSizeIsInfinite(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $capturedData = null; @@ -206,9 +223,11 @@ public function testDataEventDoesEmitOneChunkUntilStreamEndsWhenBufferSizeIsInfi /** * @covers React\Stream\ReadableResourceStream::handleData */ - public function testEmptyStreamShouldNotEmitData() + public function testEmptyStreamShouldNotEmitData(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $conn = new ReadableResourceStream($stream, $loop); @@ -217,9 +236,11 @@ public function testEmptyStreamShouldNotEmitData() $conn->handleData(); } - public function testPipeShouldReturnDestination() + public function testPipeShouldReturnDestination(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $conn = new ReadableResourceStream($stream, $loop); @@ -232,9 +253,11 @@ public function testPipeShouldReturnDestination() /** * @covers React\Stream\ReadableResourceStream::handleData */ - public function testClosingStreamInDataEventShouldNotTriggerError() + public function testClosingStreamInDataEventShouldNotTriggerError(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $conn = new ReadableResourceStream($stream, $loop); @@ -252,9 +275,11 @@ public function testClosingStreamInDataEventShouldNotTriggerError() /** * @covers React\Stream\ReadableResourceStream::pause */ - public function testPauseRemovesReadStreamFromLoop() + public function testPauseRemovesReadStreamFromLoop(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $loop->expects($this->once())->method('addReadStream')->with($stream); $loop->expects($this->once())->method('removeReadStream')->with($stream); @@ -267,9 +292,11 @@ public function testPauseRemovesReadStreamFromLoop() /** * @covers React\Stream\ReadableResourceStream::pause */ - public function testResumeDoesAddStreamToLoopOnlyOnce() + public function testResumeDoesAddStreamToLoopOnlyOnce(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $loop->expects($this->once())->method('addReadStream')->with($stream); @@ -281,9 +308,11 @@ public function testResumeDoesAddStreamToLoopOnlyOnce() /** * @covers React\Stream\ReadableResourceStream::close */ - public function testCloseRemovesReadStreamFromLoop() + public function testCloseRemovesReadStreamFromLoop(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $loop->expects($this->once())->method('addReadStream')->with($stream); $loop->expects($this->once())->method('removeReadStream')->with($stream); @@ -295,9 +324,11 @@ public function testCloseRemovesReadStreamFromLoop() /** * @covers React\Stream\ReadableResourceStream::close */ - public function testCloseAfterPauseRemovesReadStreamFromLoopOnce() + public function testCloseAfterPauseRemovesReadStreamFromLoopOnce(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $loop->expects($this->once())->method('addReadStream')->with($stream); $loop->expects($this->once())->method('removeReadStream')->with($stream); @@ -310,9 +341,11 @@ public function testCloseAfterPauseRemovesReadStreamFromLoopOnce() /** * @covers React\Stream\ReadableResourceStream::close */ - public function testResumeAfterCloseDoesAddReadStreamToLoopOnlyOnce() + public function testResumeAfterCloseDoesAddReadStreamToLoopOnlyOnce(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $loop->expects($this->once())->method('addReadStream')->with($stream); @@ -324,9 +357,10 @@ public function testResumeAfterCloseDoesAddReadStreamToLoopOnlyOnce() /** * @covers React\Stream\ReadableResourceStream::handleData */ - public function testDataFiltered() + public function testDataFiltered(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); // add a filter which removes every 'a' when reading filter_append($stream, function ($chunk) { @@ -352,9 +386,10 @@ public function testDataFiltered() /** * @covers React\Stream\ReadableResourceStream::handleData */ - public function testDataErrorShouldEmitErrorAndClose() + public function testDataErrorShouldEmitErrorAndClose(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); // add a filter which returns an error when encountering an 'a' when reading filter_append($stream, function ($chunk) { @@ -380,9 +415,12 @@ public function testDataErrorShouldEmitErrorAndClose() /** * @covers React\Stream\ReadableResourceStream::handleData */ - public function testEmptyReadShouldntFcloseStream() + public function testEmptyReadShouldntFcloseStream(): void { - list($stream, $_) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0); + $pair = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0); + assert(is_array($pair)); + [$stream, $_] = $pair; + $loop = $this->createLoopMock(); $conn = new ReadableResourceStream($stream, $loop); @@ -396,8 +434,10 @@ public function testEmptyReadShouldntFcloseStream() fclose($_); } - private function createLoopMock() + /** @return MockObject&LoopInterface */ + private function createLoopMock(): MockObject { + /** @var MockObject&LoopInterface */ return $this->createMock(LoopInterface::class); } } diff --git a/tests/Stub/ReadableStreamStub.php b/tests/Stub/ReadableStreamStub.php index 669059c..98d0240 100644 --- a/tests/Stub/ReadableStreamStub.php +++ b/tests/Stub/ReadableStreamStub.php @@ -9,7 +9,10 @@ class ReadableStreamStub extends EventEmitter implements ReadableStreamInterface { + /** @var bool */ public $readable = true; + + /** @var bool */ public $paused = false; public function isReadable(): bool @@ -17,20 +20,25 @@ public function isReadable(): bool return true; } - // trigger data event - public function write($data) + /** + * trigger data event + * + * @param mixed $data + * @return void + */ + public function write($data): void { $this->emit('data', [$data]); } // trigger error event - public function error($error) + public function error(\Exception $error): void { $this->emit('error', [$error]); } // trigger end event - public function end() + public function end(): void { $this->emit('end', []); } diff --git a/tests/TestCase.php b/tests/TestCase.php index b161c46..95bc3f1 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,11 +2,12 @@ namespace React\Tests\Stream; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase as BaseTestCase; class TestCase extends BaseTestCase { - protected function expectCallableOnce() + protected function expectCallableOnce(): callable { $mock = $this->createCallableMock(); $mock @@ -16,7 +17,8 @@ protected function expectCallableOnce() return $mock; } - protected function expectCallableOnceWith($value) + /** @param mixed $value */ + protected function expectCallableOnceWith($value): callable { $callback = $this->createCallableMock(); $callback @@ -27,7 +29,7 @@ protected function expectCallableOnceWith($value) return $callback; } - protected function expectCallableNever() + protected function expectCallableNever(): callable { $mock = $this->createCallableMock(); $mock @@ -37,15 +39,19 @@ protected function expectCallableNever() return $mock; } - protected function createCallableMock() + /** @return MockObject&callable */ + protected function createCallableMock(): MockObject { $builder = $this->getMockBuilder(\stdClass::class); if (method_exists($builder, 'addMethods')) { // PHPUnit 9+ - return $builder->addMethods(['__invoke'])->getMock(); + $mock = $builder->addMethods(['__invoke'])->getMock(); } else { // legacy PHPUnit - return $builder->setMethods(['__invoke'])->getMock(); + $mock = $builder->setMethods(['__invoke'])->getMock(); } + assert($mock instanceof MockObject && is_callable($mock)); + + return $mock; } } diff --git a/tests/ThroughStreamTest.php b/tests/ThroughStreamTest.php index ddd570f..a4b49b0 100644 --- a/tests/ThroughStreamTest.php +++ b/tests/ThroughStreamTest.php @@ -11,7 +11,7 @@ class ThroughStreamTest extends TestCase { /** @test */ - public function itShouldReturnTrueForAnyDataWrittenToIt() + public function itShouldReturnTrueForAnyDataWrittenToIt(): void { $through = new ThroughStream(); $ret = $through->write('foo'); @@ -20,7 +20,7 @@ public function itShouldReturnTrueForAnyDataWrittenToIt() } /** @test */ - public function itShouldEmitAnyDataWrittenToIt() + public function itShouldEmitAnyDataWrittenToIt(): void { $through = new ThroughStream(); $through->on('data', $this->expectCallableOnceWith('foo')); @@ -28,7 +28,7 @@ public function itShouldEmitAnyDataWrittenToIt() } /** @test */ - public function itShouldEmitAnyDataWrittenToItPassedThruFunction() + public function itShouldEmitAnyDataWrittenToItPassedThruFunction(): void { $through = new ThroughStream('strtoupper'); $through->on('data', $this->expectCallableOnceWith('FOO')); @@ -36,7 +36,7 @@ public function itShouldEmitAnyDataWrittenToItPassedThruFunction() } /** @test */ - public function itShouldEmitAnyDataWrittenToItPassedThruCallback() + public function itShouldEmitAnyDataWrittenToItPassedThruCallback(): void { $through = new ThroughStream('strtoupper'); $through->on('data', $this->expectCallableOnceWith('FOO')); @@ -44,7 +44,7 @@ public function itShouldEmitAnyDataWrittenToItPassedThruCallback() } /** @test */ - public function itShouldEmitErrorAndCloseIfCallbackThrowsException() + public function itShouldEmitErrorAndCloseIfCallbackThrowsException(): void { $through = new ThroughStream(function () { throw new \RuntimeException(); @@ -61,7 +61,7 @@ public function itShouldEmitErrorAndCloseIfCallbackThrowsException() } /** @test */ - public function itShouldEmitErrorAndCloseIfCallbackThrowsExceptionOnEnd() + public function itShouldEmitErrorAndCloseIfCallbackThrowsExceptionOnEnd(): void { $through = new ThroughStream(function () { throw new \RuntimeException(); @@ -78,7 +78,7 @@ public function itShouldEmitErrorAndCloseIfCallbackThrowsExceptionOnEnd() } /** @test */ - public function itShouldReturnFalseForAnyDataWrittenToItWhenPaused() + public function itShouldReturnFalseForAnyDataWrittenToItWhenPaused(): void { $through = new ThroughStream(); $through->pause(); @@ -88,7 +88,7 @@ public function itShouldReturnFalseForAnyDataWrittenToItWhenPaused() } /** @test */ - public function itShouldReturnFalseForAnyDataWrittenToItWhenDataEventEndsStream() + public function itShouldReturnFalseForAnyDataWrittenToItWhenDataEventEndsStream(): void { $through = new ThroughStream(); $through->on('data', function () use ($through) { @@ -100,7 +100,7 @@ public function itShouldReturnFalseForAnyDataWrittenToItWhenDataEventEndsStream( } /** @test */ - public function itShouldReturnFalseForAnyDataWrittenToItWhenDataEventClosesStream() + public function itShouldReturnFalseForAnyDataWrittenToItWhenDataEventClosesStream(): void { $through = new ThroughStream(); $through->on('data', function () use ($through) { @@ -112,7 +112,7 @@ public function itShouldReturnFalseForAnyDataWrittenToItWhenDataEventClosesStrea } /** @test */ - public function itShouldEmitDrainOnResumeAfterReturnFalseForAnyDataWrittenToItWhenPaused() + public function itShouldEmitDrainOnResumeAfterReturnFalseForAnyDataWrittenToItWhenPaused(): void { $through = new ThroughStream(); $through->pause(); @@ -123,7 +123,7 @@ public function itShouldEmitDrainOnResumeAfterReturnFalseForAnyDataWrittenToItWh } /** @test */ - public function itShouldNotEmitDrainOnResumeAfterClose() + public function itShouldNotEmitDrainOnResumeAfterClose(): void { $through = new ThroughStream(); $through->close(); @@ -133,7 +133,7 @@ public function itShouldNotEmitDrainOnResumeAfterClose() } /** @test */ - public function itShouldNotEmitDrainOnResumeAfterReturnFalseForAnyDataWrittenThatCausesStreamToClose() + public function itShouldNotEmitDrainOnResumeAfterReturnFalseForAnyDataWrittenThatCausesStreamToClose(): void { $through = new ThroughStream(); $through->on('data', function () use ($through) { $through->close(); }); @@ -144,7 +144,7 @@ public function itShouldNotEmitDrainOnResumeAfterReturnFalseForAnyDataWrittenTha } /** @test */ - public function itShouldReturnFalseForAnyDataWrittenToItAfterPausingFromDrainEvent() + public function itShouldReturnFalseForAnyDataWrittenToItAfterPausingFromDrainEvent(): void { $through = new ThroughStream(); $through->pause(); @@ -157,7 +157,7 @@ public function itShouldReturnFalseForAnyDataWrittenToItAfterPausingFromDrainEve } /** @test */ - public function itShouldReturnTrueForAnyDataWrittenToItWhenResumedAfterPause() + public function itShouldReturnTrueForAnyDataWrittenToItWhenResumedAfterPause(): void { $through = new ThroughStream(); $through->on('drain', $this->expectCallableNever()); @@ -169,7 +169,7 @@ public function itShouldReturnTrueForAnyDataWrittenToItWhenResumedAfterPause() } /** @test */ - public function pipingStuffIntoItShouldWork() + public function pipingStuffIntoItShouldWork(): void { $readable = new ThroughStream(); @@ -181,7 +181,7 @@ public function pipingStuffIntoItShouldWork() } /** @test */ - public function endShouldEmitEndAndClose() + public function endShouldEmitEndAndClose(): void { $through = new ThroughStream(); $through->on('data', $this->expectCallableNever()); @@ -191,7 +191,7 @@ public function endShouldEmitEndAndClose() } /** @test */ - public function endShouldCloseTheStream() + public function endShouldCloseTheStream(): void { $through = new ThroughStream(); $through->on('data', $this->expectCallableNever()); @@ -202,7 +202,7 @@ public function endShouldCloseTheStream() } /** @test */ - public function endShouldWriteDataBeforeClosing() + public function endShouldWriteDataBeforeClosing(): void { $through = new ThroughStream(); $through->on('data', $this->expectCallableOnceWith('foo')); @@ -213,7 +213,7 @@ public function endShouldWriteDataBeforeClosing() } /** @test */ - public function endTwiceShouldOnlyEmitOnce() + public function endTwiceShouldOnlyEmitOnce(): void { $through = new ThroughStream(); $through->on('data', $this->expectCallableOnceWith('first')); @@ -222,7 +222,7 @@ public function endTwiceShouldOnlyEmitOnce() } /** @test */ - public function writeAfterEndShouldReturnFalse() + public function writeAfterEndShouldReturnFalse(): void { $through = new ThroughStream(); $through->on('data', $this->expectCallableNever()); @@ -232,7 +232,7 @@ public function writeAfterEndShouldReturnFalse() } /** @test */ - public function writeDataWillCloseStreamShouldReturnFalse() + public function writeDataWillCloseStreamShouldReturnFalse(): void { $through = new ThroughStream(); $through->on('data', [$through, 'close']); @@ -241,7 +241,7 @@ public function writeDataWillCloseStreamShouldReturnFalse() } /** @test */ - public function writeDataToPausedShouldReturnFalse() + public function writeDataToPausedShouldReturnFalse(): void { $through = new ThroughStream(); $through->pause(); @@ -250,7 +250,7 @@ public function writeDataToPausedShouldReturnFalse() } /** @test */ - public function writeDataToResumedShouldReturnTrue() + public function writeDataToResumedShouldReturnTrue(): void { $through = new ThroughStream(); $through->pause(); @@ -260,21 +260,21 @@ public function writeDataToResumedShouldReturnTrue() } /** @test */ - public function itShouldBeReadableByDefault() + public function itShouldBeReadableByDefault(): void { $through = new ThroughStream(); $this->assertTrue($through->isReadable()); } /** @test */ - public function itShouldBeWritableByDefault() + public function itShouldBeWritableByDefault(): void { $through = new ThroughStream(); $this->assertTrue($through->isWritable()); } /** @test */ - public function closeShouldCloseOnce() + public function closeShouldCloseOnce(): void { $through = new ThroughStream(); @@ -287,7 +287,7 @@ public function closeShouldCloseOnce() } /** @test */ - public function doubleCloseShouldCloseOnce() + public function doubleCloseShouldCloseOnce(): void { $through = new ThroughStream(); @@ -301,7 +301,7 @@ public function doubleCloseShouldCloseOnce() } /** @test */ - public function pipeShouldPipeCorrectly() + public function pipeShouldPipeCorrectly(): void { $output = $this->createMock(WritableStreamInterface::class); $output->expects($this->any())->method('isWritable')->willReturn(True); diff --git a/tests/UtilTest.php b/tests/UtilTest.php index cd00c8b..6bac04c 100644 --- a/tests/UtilTest.php +++ b/tests/UtilTest.php @@ -15,7 +15,7 @@ */ class UtilTest extends TestCase { - public function testPipeReturnsDestinationStream() + public function testPipeReturnsDestinationStream(): void { $readable = $this->createMock(ReadableStreamInterface::class); assert($readable instanceof ReadableStreamInterface); @@ -28,7 +28,7 @@ public function testPipeReturnsDestinationStream() $this->assertSame($writable, $ret); } - public function testPipeNonReadableSourceShouldDoNothing() + public function testPipeNonReadableSourceShouldDoNothing(): void { $readable = $this->createMock(ReadableStreamInterface::class); $readable @@ -49,7 +49,7 @@ public function testPipeNonReadableSourceShouldDoNothing() Util::pipe($readable, $writable); } - public function testPipeIntoNonWritableDestinationShouldPauseSource() + public function testPipeIntoNonWritableDestinationShouldPauseSource(): void { $readable = $this->createMock(ReadableStreamInterface::class); $readable @@ -74,7 +74,7 @@ public function testPipeIntoNonWritableDestinationShouldPauseSource() Util::pipe($readable, $writable); } - public function testPipeClosingDestPausesSource() + public function testPipeClosingDestPausesSource(): void { $readable = $this->createMock(ReadableStreamInterface::class); $readable @@ -93,7 +93,7 @@ public function testPipeClosingDestPausesSource() $writable->close(); } - public function testPipeWithEnd() + public function testPipeWithEnd(): void { $readable = new Stub\ReadableStreamStub(); @@ -112,7 +112,7 @@ public function testPipeWithEnd() $readable->end(); } - public function testPipeWithoutEnd() + public function testPipeWithoutEnd(): void { $readable = new Stub\ReadableStreamStub(); @@ -131,7 +131,7 @@ public function testPipeWithoutEnd() $readable->end(); } - public function testPipeWithTooSlowWritableShouldPauseReadable() + public function testPipeWithTooSlowWritableShouldPauseReadable(): void { $readable = new Stub\ReadableStreamStub(); @@ -154,7 +154,7 @@ public function testPipeWithTooSlowWritableShouldPauseReadable() $this->assertTrue($readable->paused); } - public function testPipeWithTooSlowWritableShouldResumeOnDrain() + public function testPipeWithTooSlowWritableShouldResumeOnDrain(): void { $readable = new Stub\ReadableStreamStub(); @@ -184,11 +184,13 @@ public function testPipeWithTooSlowWritableShouldResumeOnDrain() $this->assertFalse($readable->paused); } - public function testPipeWithWritableResourceStream() + public function testPipeWithWritableResourceStream(): void { $readable = new Stub\ReadableStreamStub(); $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createMock(LoopInterface::class); assert($loop instanceof LoopInterface); $buffer = new WritableResourceStream($stream, $loop); @@ -203,7 +205,7 @@ public function testPipeWithWritableResourceStream() $this->assertSame('hello, I am some random data', stream_get_contents($stream)); } - public function testPipeSetsUpListeners() + public function testPipeSetsUpListeners(): void { $source = new ThroughStream(); $dest = new ThroughStream(); @@ -219,7 +221,7 @@ public function testPipeSetsUpListeners() $this->assertCount(1, $dest->listeners('drain')); } - public function testPipeClosingSourceRemovesListeners() + public function testPipeClosingSourceRemovesListeners(): void { $source = new ThroughStream(); $dest = new ThroughStream(); @@ -233,7 +235,7 @@ public function testPipeClosingSourceRemovesListeners() $this->assertCount(0, $dest->listeners('drain')); } - public function testPipeClosingDestRemovesListeners() + public function testPipeClosingDestRemovesListeners(): void { $source = new ThroughStream(); $dest = new ThroughStream(); @@ -247,7 +249,7 @@ public function testPipeClosingDestRemovesListeners() $this->assertCount(0, $dest->listeners('drain')); } - public function testPipeDuplexIntoSelfEndsOnEnd() + public function testPipeDuplexIntoSelfEndsOnEnd(): void { $readable = $this->createMock(ReadableStreamInterface::class); $readable->expects($this->any())->method('isReadable')->willReturn(true); @@ -267,7 +269,7 @@ public function testPipeDuplexIntoSelfEndsOnEnd() } /** @test */ - public function forwardEventsShouldSetupForwards() + public function forwardEventsShouldSetupForwards(): void { $source = new ThroughStream(); $target = new ThroughStream(); diff --git a/tests/WritableResourceStreamTest.php b/tests/WritableResourceStreamTest.php index df3e815..bc4fc42 100644 --- a/tests/WritableResourceStreamTest.php +++ b/tests/WritableResourceStreamTest.php @@ -2,6 +2,7 @@ namespace React\Tests\Stream; +use PHPUnit\Framework\MockObject\MockObject; use React\EventLoop\LoopInterface; use React\Stream\WritableResourceStream; use function Clue\StreamFilter\append as filter_append; @@ -12,17 +13,20 @@ class WritableResourceStreamTest extends TestCase * @covers React\Stream\WritableResourceStream::__construct * @doesNotPerformAssertions */ - public function testConstructor() + public function testConstructor(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); new WritableResourceStream($stream, $loop); } - public function testConstructWithoutLoopAssignsLoopAutomatically() + public function testConstructWithoutLoopAssignsLoopAutomatically(): void { $resource = fopen('php://temp', 'r+'); + assert(is_resource($resource)); $stream = new WritableResourceStream($resource); @@ -37,11 +41,12 @@ public function testConstructWithoutLoopAssignsLoopAutomatically() * @covers React\Stream\WritableResourceStream::__construct * @doesNotPerformAssertions */ - public function testConstructorWithExcessiveMode() + public function testConstructorWithExcessiveMode(): void { // 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'); + $name = (string) tempnam(sys_get_temp_dir(), 'test'); + $stream = fopen($name, 'w+eANYTHING'); + assert(is_resource($stream)); unlink($name); $loop = $this->createLoopMock(); @@ -52,7 +57,7 @@ public function testConstructorWithExcessiveMode() /** * @covers React\Stream\WritableResourceStream::__construct */ - public function testConstructorThrowsIfNotAValidStreamResource() + public function testConstructorThrowsIfNotAValidStreamResource(): void { $stream = null; $loop = $this->createLoopMock(); @@ -64,9 +69,11 @@ public function testConstructorThrowsIfNotAValidStreamResource() /** * @covers React\Stream\WritableResourceStream::__construct */ - public function testConstructorThrowsExceptionOnReadOnlyStream() + public function testConstructorThrowsExceptionOnReadOnlyStream(): void { $stream = fopen('php://temp', 'r'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $this->expectException(\InvalidArgumentException::class); @@ -76,11 +83,12 @@ public function testConstructorThrowsExceptionOnReadOnlyStream() /** * @covers React\Stream\WritableResourceStream::__construct */ - public function testConstructorThrowsExceptionOnReadOnlyStreamWithExcessiveMode() + public function testConstructorThrowsExceptionOnReadOnlyStreamWithExcessiveMode(): void { // excessive flags are ignored for temp streams, so we have to use a file stream - $name = tempnam(sys_get_temp_dir(), 'test'); + $name = (string) tempnam(sys_get_temp_dir(), 'test'); $stream = fopen($name, 'reANYTHING'); + assert(is_resource($stream)); unlink($name); $loop = $this->createLoopMock(); @@ -91,13 +99,15 @@ public function testConstructorThrowsExceptionOnReadOnlyStreamWithExcessiveMode( /** * @covers React\Stream\WritableResourceStream::__construct */ - public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking() + public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking(): void { if (!in_array('blocking', stream_get_wrappers())) { stream_wrapper_register('blocking', 'React\Tests\Stream\EnforceBlockingWrapper'); } $stream = fopen('blocking://test', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $this->expectException(\RuntimeException::class); @@ -108,9 +118,11 @@ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking( * @covers React\Stream\WritableResourceStream::write * @covers React\Stream\WritableResourceStream::handleWrite */ - public function testWrite() + public function testWrite(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createWriteableLoopMock(); $buffer = new WritableResourceStream($stream, $loop); @@ -124,9 +136,11 @@ public function testWrite() /** * @covers React\Stream\WritableResourceStream::write */ - public function testWriteWithDataDoesAddResourceToLoop() + public function testWriteWithDataDoesAddResourceToLoop(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $loop->expects($this->once())->method('addWriteStream')->with($this->equalTo($stream)); @@ -139,9 +153,11 @@ public function testWriteWithDataDoesAddResourceToLoop() * @covers React\Stream\WritableResourceStream::write * @covers React\Stream\WritableResourceStream::handleWrite */ - public function testEmptyWriteDoesNotAddToLoop() + public function testEmptyWriteDoesNotAddToLoop(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $loop->expects($this->never())->method('addWriteStream'); @@ -155,9 +171,10 @@ public function testEmptyWriteDoesNotAddToLoop() * @covers React\Stream\WritableResourceStream::write * @covers React\Stream\WritableResourceStream::handleWrite */ - public function testWriteReturnsFalseWhenWritableResourceStreamIsFull() + public function testWriteReturnsFalseWhenWritableResourceStreamIsFull(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); $preventWrites = true; $loop = $this->createLoopMock(); @@ -182,9 +199,11 @@ public function testWriteReturnsFalseWhenWritableResourceStreamIsFull() /** * @covers React\Stream\WritableResourceStream::write */ - public function testWriteReturnsFalseWhenWritableResourceStreamIsExactlyFull() + public function testWriteReturnsFalseWhenWritableResourceStreamIsExactlyFull(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $buffer = new WritableResourceStream($stream, $loop, 3); @@ -196,9 +215,11 @@ public function testWriteReturnsFalseWhenWritableResourceStreamIsExactlyFull() * @covers React\Stream\WritableResourceStream::write * @covers React\Stream\WritableResourceStream::handleWrite */ - public function testWriteDetectsWhenOtherSideIsClosed() + public function testWriteDetectsWhenOtherSideIsClosed(): void { - list($a, $b) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); + $pair = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); + assert(is_array($pair)); + [$a, $b] = $pair; $loop = $this->createWriteableLoopMock(); @@ -214,9 +235,11 @@ public function testWriteDetectsWhenOtherSideIsClosed() * @covers React\Stream\WritableResourceStream::write * @covers React\Stream\WritableResourceStream::handleWrite */ - public function testEmitsDrainAfterWriteWhichExceedsBuffer() + public function testEmitsDrainAfterWriteWhichExceedsBuffer(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $buffer = new WritableResourceStream($stream, $loop, 2); @@ -231,9 +254,11 @@ public function testEmitsDrainAfterWriteWhichExceedsBuffer() * @covers React\Stream\WritableResourceStream::write * @covers React\Stream\WritableResourceStream::handleWrite */ - public function testWriteInDrain() + public function testWriteInDrain(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $buffer = new WritableResourceStream($stream, $loop, 2); @@ -255,9 +280,11 @@ public function testWriteInDrain() * @covers React\Stream\WritableResourceStream::write * @covers React\Stream\WritableResourceStream::handleWrite */ - public function testDrainAfterWrite() + public function testDrainAfterWrite(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $buffer = new WritableResourceStream($stream, $loop, 2); @@ -271,9 +298,11 @@ public function testDrainAfterWrite() /** * @covers React\Stream\WritableResourceStream::handleWrite */ - public function testDrainAfterWriteWillRemoveResourceFromLoopWithoutClosing() + public function testDrainAfterWriteWillRemoveResourceFromLoopWithoutClosing(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $loop->expects($this->once())->method('removeWriteStream')->with($stream); @@ -290,9 +319,11 @@ public function testDrainAfterWriteWillRemoveResourceFromLoopWithoutClosing() /** * @covers React\Stream\WritableResourceStream::handleWrite */ - public function testClosingDuringDrainAfterWriteWillRemoveResourceFromLoopOnceAndClose() + public function testClosingDuringDrainAfterWriteWillRemoveResourceFromLoopOnceAndClose(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $loop->expects($this->once())->method('removeWriteStream')->with($stream); @@ -311,9 +342,11 @@ public function testClosingDuringDrainAfterWriteWillRemoveResourceFromLoopOnceAn /** * @covers React\Stream\WritableResourceStream::end */ - public function testEndWithoutDataClosesImmediatelyIfWritableResourceStreamIsEmpty() + public function testEndWithoutDataClosesImmediatelyIfWritableResourceStreamIsEmpty(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $buffer = new WritableResourceStream($stream, $loop); @@ -328,9 +361,11 @@ public function testEndWithoutDataClosesImmediatelyIfWritableResourceStreamIsEmp /** * @covers React\Stream\WritableResourceStream::end */ - public function testEndWithoutDataDoesNotCloseIfWritableResourceStreamIsFull() + public function testEndWithoutDataDoesNotCloseIfWritableResourceStreamIsFull(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $buffer = new WritableResourceStream($stream, $loop); @@ -347,9 +382,11 @@ public function testEndWithoutDataDoesNotCloseIfWritableResourceStreamIsFull() /** * @covers React\Stream\WritableResourceStream::end */ - public function testEndWithDataClosesImmediatelyIfWritableResourceStreamFlushes() + public function testEndWithDataClosesImmediatelyIfWritableResourceStreamFlushes(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $filterBuffer = ''; $loop = $this->createLoopMock(); @@ -373,9 +410,11 @@ public function testEndWithDataClosesImmediatelyIfWritableResourceStreamFlushes( /** * @covers React\Stream\WritableResourceStream::end */ - public function testEndWithDataDoesNotCloseImmediatelyIfWritableResourceStreamIsFull() + public function testEndWithDataDoesNotCloseImmediatelyIfWritableResourceStreamIsFull(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $buffer = new WritableResourceStream($stream, $loop); @@ -396,9 +435,11 @@ public function testEndWithDataDoesNotCloseImmediatelyIfWritableResourceStreamIs * @covers React\Stream\WritableResourceStream::isWritable * @covers React\Stream\WritableResourceStream::close */ - public function testClose() + public function testClose(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $buffer = new WritableResourceStream($stream, $loop); @@ -415,9 +456,11 @@ public function testClose() /** * @covers React\Stream\WritableResourceStream::close */ - public function testClosingAfterWriteRemovesStreamFromLoop() + public function testClosingAfterWriteRemovesStreamFromLoop(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $buffer = new WritableResourceStream($stream, $loop); @@ -430,9 +473,11 @@ public function testClosingAfterWriteRemovesStreamFromLoop() /** * @covers React\Stream\WritableResourceStream::close */ - public function testClosingWithoutWritingDoesNotRemoveStreamFromLoop() + public function testClosingWithoutWritingDoesNotRemoveStreamFromLoop(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $buffer = new WritableResourceStream($stream, $loop); @@ -444,9 +489,11 @@ public function testClosingWithoutWritingDoesNotRemoveStreamFromLoop() /** * @covers React\Stream\WritableResourceStream::close */ - public function testDoubleCloseWillEmitOnlyOnce() + public function testDoubleCloseWillEmitOnlyOnce(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $loop = $this->createLoopMock(); $buffer = new WritableResourceStream($stream, $loop); @@ -460,9 +507,11 @@ public function testDoubleCloseWillEmitOnlyOnce() * @covers React\Stream\WritableResourceStream::write * @covers React\Stream\WritableResourceStream::close */ - public function testWritingToClosedWritableResourceStreamShouldNotWriteToStream() + public function testWritingToClosedWritableResourceStreamShouldNotWriteToStream(): void { $stream = fopen('php://temp', 'r+'); + assert(is_resource($stream)); + $filterBuffer = ''; $loop = $this->createLoopMock(); @@ -481,13 +530,16 @@ public function testWritingToClosedWritableResourceStreamShouldNotWriteToStream( $this->assertSame('', $filterBuffer); } - public function testWritingToClosedStream() + public function testWritingToClosedStream(): void { 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); + $pair = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); + assert(is_array($pair)); + [$a, $b] = $pair; + $loop = $this->createLoopMock(); $error = null; @@ -508,7 +560,7 @@ public function testWritingToClosedStream() $this->assertEqualsIgnoringCase('Unable to write to stream: fwrite(): send of 3 bytes failed with errno=32 Broken pipe', $error->getMessage()); } - private function createWriteableLoopMock() + private function createWriteableLoopMock(): LoopInterface { $loop = $this->createLoopMock(); $loop @@ -521,8 +573,10 @@ private function createWriteableLoopMock() return $loop; } - private function createLoopMock() + /** @return MockObject&LoopInterface */ + private function createLoopMock(): MockObject { + /** @var MockObject&LoopInterface */ return $this->createMock(LoopInterface::class); } } From 58b6951113ac5e8102639b666327f902493a06e6 Mon Sep 17 00:00:00 2001 From: Paul Rotmann Date: Thu, 27 Feb 2025 08:56:19 +0100 Subject: [PATCH 66/66] Run tests on PHP 8.4 and update test environment --- .github/workflows/ci.yml | 8 +++++--- composer.json | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1bb8701..b2645b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,10 +7,11 @@ on: jobs: PHPUnit: name: PHPUnit (PHP ${{ matrix.php }}) - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: matrix: php: + - 8.4 - 8.3 - 8.2 - 8.1 @@ -35,7 +36,7 @@ jobs: PHPUnit-macOS: name: PHPUnit (macOS) - runs-on: macos-12 + runs-on: macos-14 continue-on-error: true steps: - uses: actions/checkout@v4 @@ -50,10 +51,11 @@ jobs: PHPStan: name: PHPStan (PHP ${{ matrix.php }}) - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: matrix: php: + - 8.4 - 8.3 - 8.2 - 8.1 diff --git a/composer.json b/composer.json index beb0a56..00c2378 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ }, "require-dev": { "clue/stream-filter": "^1.2", - "phpstan/phpstan": "1.11.1 || 1.4.10", + "phpstan/phpstan": "1.12.19 || 1.4.10", "phpunit/phpunit": "^9.6 || ^7.5" }, "autoload": {