diff options
author | marvin-borner@live.com | 2018-04-10 21:50:16 +0200 |
---|---|---|
committer | marvin-borner@live.com | 2018-04-10 21:54:48 +0200 |
commit | fc9401f04a3aca5abb22f87ebc210de8afe11d32 (patch) | |
tree | b0b310f3581764ec3955f4e496a05137a32951c3 /assets/php/vendor/react/stream/tests | |
parent | 286d643180672f20526f3dc3bd19d7b751e2fa97 (diff) |
Initial Commit
Diffstat (limited to 'assets/php/vendor/react/stream/tests')
12 files changed, 2842 insertions, 0 deletions
diff --git a/assets/php/vendor/react/stream/tests/CallableStub.php b/assets/php/vendor/react/stream/tests/CallableStub.php new file mode 100644 index 0000000..31cc834 --- /dev/null +++ b/assets/php/vendor/react/stream/tests/CallableStub.php @@ -0,0 +1,10 @@ +<?php + +namespace React\Tests\Stream; + +class CallableStub +{ + public function __invoke() + { + } +} diff --git a/assets/php/vendor/react/stream/tests/CompositeStreamTest.php b/assets/php/vendor/react/stream/tests/CompositeStreamTest.php new file mode 100644 index 0000000..df89c3e --- /dev/null +++ b/assets/php/vendor/react/stream/tests/CompositeStreamTest.php @@ -0,0 +1,267 @@ +<?php + +namespace React\Tests\Stream; + +use React\Stream\CompositeStream; +use React\Stream\ThroughStream; + +/** + * @covers React\Stream\CompositeStream + */ +class CompositeStreamTest extends TestCase +{ + /** @test */ + public function itShouldCloseReadableIfNotWritable() + { + $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable + ->expects($this->once()) + ->method('isReadable') + ->willReturn(true); + $readable + ->expects($this->once()) + ->method('close'); + + $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable + ->expects($this->once()) + ->method('isWritable') + ->willReturn(false); + + $composite = new CompositeStream($readable, $writable); + + $composite->on('close', $this->expectCallableNever()); + $composite->close(); + } + + /** @test */ + public function itShouldCloseWritableIfNotReadable() + { + $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable + ->expects($this->once()) + ->method('isReadable') + ->willReturn(false); + + $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable + ->expects($this->once()) + ->method('close'); + + $composite = new CompositeStream($readable, $writable); + + $composite->on('close', $this->expectCallableNever()); + $composite->close(); + } + + /** @test */ + public function itShouldForwardWritableCallsToWritableStream() + { + $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable + ->expects($this->once()) + ->method('isReadable') + ->willReturn(true); + + $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable + ->expects($this->once()) + ->method('write') + ->with('foo'); + $writable + ->expects($this->exactly(2)) + ->method('isWritable') + ->willReturn(true); + + $composite = new CompositeStream($readable, $writable); + $composite->write('foo'); + $composite->isWritable(); + } + + /** @test */ + public function itShouldForwardReadableCallsToReadableStream() + { + $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable + ->expects($this->exactly(2)) + ->method('isReadable') + ->willReturn(true); + $readable + ->expects($this->once()) + ->method('pause'); + $readable + ->expects($this->once()) + ->method('resume'); + + $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable + ->expects($this->any()) + ->method('isWritable') + ->willReturn(true); + + $composite = new CompositeStream($readable, $writable); + $composite->isReadable(); + $composite->pause(); + $composite->resume(); + } + + /** @test */ + public function itShouldNotForwardResumeIfStreamIsNotWritable() + { + $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable + ->expects($this->once()) + ->method('isReadable') + ->willReturn(true); + $readable + ->expects($this->never()) + ->method('resume'); + + $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable + ->expects($this->exactly(2)) + ->method('isWritable') + ->willReturnOnConsecutiveCalls(true, false); + + $composite = new CompositeStream($readable, $writable); + $composite->resume(); + } + + /** @test */ + public function endShouldDelegateToWritableWithData() + { + $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable + ->expects($this->once()) + ->method('isReadable') + ->willReturn(true); + + $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable + ->expects($this->once()) + ->method('isWritable') + ->willReturn(true); + $writable + ->expects($this->once()) + ->method('end') + ->with('foo'); + + $composite = new CompositeStream($readable, $writable); + $composite->end('foo'); + } + + /** @test */ + public function closeShouldCloseBothStreams() + { + $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable + ->expects($this->once()) + ->method('isReadable') + ->willReturn(true); + $readable + ->expects($this->once()) + ->method('close'); + + $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable + ->expects($this->once()) + ->method('isWritable') + ->willReturn(true); + $writable + ->expects($this->once()) + ->method('close'); + + $composite = new CompositeStream($readable, $writable); + $composite->close(); + } + + /** @test */ + public function itShouldForwardCloseOnlyOnce() + { + $readable = new ThroughStream(); + $writable = new ThroughStream(); + + $composite = new CompositeStream($readable, $writable); + $composite->on('close', $this->expectCallableOnce()); + + $readable->close(); + $writable->close(); + } + + /** @test */ + public function itShouldForwardCloseAndRemoveAllListeners() + { + $in = new ThroughStream(); + + $composite = new CompositeStream($in, $in); + $composite->on('close', $this->expectCallableOnce()); + + $this->assertTrue($composite->isReadable()); + $this->assertTrue($composite->isWritable()); + $this->assertCount(1, $composite->listeners('close')); + + $composite->close(); + + $this->assertFalse($composite->isReadable()); + $this->assertFalse($composite->isWritable()); + $this->assertCount(0, $composite->listeners('close')); + } + + /** @test */ + public function itShouldReceiveForwardedEvents() + { + $readable = new ThroughStream(); + $writable = new ThroughStream(); + + $composite = new CompositeStream($readable, $writable); + $composite->on('data', $this->expectCallableOnce()); + $composite->on('drain', $this->expectCallableOnce()); + + $readable->emit('data', array('foo')); + $writable->emit('drain'); + } + + /** @test */ + public function itShouldHandlePipingCorrectly() + { + $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable + ->expects($this->once()) + ->method('isReadable') + ->willReturn(true); + + $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable->expects($this->any())->method('isWritable')->willReturn(True); + $writable + ->expects($this->once()) + ->method('write') + ->with('foo'); + + $composite = new CompositeStream($readable, $writable); + + $input = new ThroughStream(); + $input->pipe($composite); + $input->emit('data', array('foo')); + } + + /** @test */ + public function itShouldForwardPipeCallsToReadableStream() + { + $readable = new ThroughStream(); + + $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable->expects($this->any())->method('isWritable')->willReturn(True); + + $composite = new CompositeStream($readable, $writable); + + $output = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $output->expects($this->any())->method('isWritable')->willReturn(True); + $output + ->expects($this->once()) + ->method('write') + ->with('foo'); + + $composite->pipe($output); + $readable->emit('data', array('foo')); + } +} diff --git a/assets/php/vendor/react/stream/tests/DuplexResourceStreamIntegrationTest.php b/assets/php/vendor/react/stream/tests/DuplexResourceStreamIntegrationTest.php new file mode 100644 index 0000000..fb5f02a --- /dev/null +++ b/assets/php/vendor/react/stream/tests/DuplexResourceStreamIntegrationTest.php @@ -0,0 +1,352 @@ +<?php + +namespace React\Tests\Stream; + +use React\Stream\DuplexResourceStream; +use React\Stream\ReadableResourceStream; +use React\EventLoop\ExtEventLoop; +use React\EventLoop\ExtLibeventLoop; +use React\EventLoop\ExtLibevLoop; +use React\EventLoop\LoopInterface; +use React\EventLoop\LibEventLoop; +use React\EventLoop\LibEvLoop; +use React\EventLoop\StreamSelectLoop; + +class DuplexResourceStreamIntegrationTest extends TestCase +{ + public function loopProvider() + { + return array( + array( + function() { + return true; + }, + function () { + return new StreamSelectLoop(); + } + ), + array( + function () { + return function_exists('event_base_new'); + }, + function () { + return class_exists('React\EventLoop\ExtLibeventLoop') ? new ExtLibeventLoop() : new LibEventLoop(); + } + ), + array( + function () { + return class_exists('libev\EventLoop'); + }, + function () { + return class_exists('React\EventLoop\ExtLibevLoop') ? new ExtLibevLoop() : new LibEvLoop(); + } + ), + array( + function () { + return class_exists('EventBase') && class_exists('React\EventLoop\ExtEventLoop'); + }, + function () { + return new ExtEventLoop(); + } + ) + ); + } + + /** + * @dataProvider loopProvider + */ + public function testBufferReadsLargeChunks($condition, $loopFactory) + { + if (true !== $condition()) { + return $this->markTestSkipped('Loop implementation not available'); + } + + $loop = $loopFactory(); + + list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0); + + $bufferSize = 4096; + $streamA = new DuplexResourceStream($sockA, $loop, $bufferSize); + $streamB = new DuplexResourceStream($sockB, $loop, $bufferSize); + + $testString = str_repeat("*", $bufferSize + 1); + + $buffer = ""; + $streamB->on('data', function ($data) use (&$buffer) { + $buffer .= $data; + }); + + $streamA->write($testString); + + $this->loopTick($loop); + $this->loopTick($loop); + $this->loopTick($loop); + + $streamA->close(); + $streamB->close(); + + $this->assertEquals($testString, $buffer); + } + + /** + * @dataProvider loopProvider + */ + public function testWriteLargeChunk($condition, $loopFactory) + { + if (true !== $condition()) { + return $this->markTestSkipped('Loop implementation not available'); + } + + $loop = $loopFactory(); + + list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0); + + $streamA = new DuplexResourceStream($sockA, $loop); + $streamB = new DuplexResourceStream($sockB, $loop); + + // limit seems to be 192 KiB + $size = 256 * 1024; + + // sending side sends and expects clean close with no errors + $streamA->end(str_repeat('*', $size)); + $streamA->on('close', $this->expectCallableOnce()); + $streamA->on('error', $this->expectCallableNever()); + + // receiving side counts bytes and expects clean close with no errors + $received = 0; + $streamB->on('data', function ($chunk) use (&$received) { + $received += strlen($chunk); + }); + $streamB->on('close', $this->expectCallableOnce()); + $streamB->on('error', $this->expectCallableNever()); + + $loop->run(); + + $streamA->close(); + $streamB->close(); + + $this->assertEquals($size, $received); + } + + /** + * @dataProvider loopProvider + */ + public function testDoesNotEmitDataIfNothingHasBeenWritten($condition, $loopFactory) + { + if (true !== $condition()) { + return $this->markTestSkipped('Loop implementation not available'); + } + + $loop = $loopFactory(); + + list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0); + + $streamA = new DuplexResourceStream($sockA, $loop); + $streamB = new DuplexResourceStream($sockB, $loop); + + // end streamA without writing any data + $streamA->end(); + + // streamB should not emit any data + $streamB->on('data', $this->expectCallableNever()); + + $loop->run(); + + $streamA->close(); + $streamB->close(); + } + + /** + * @dataProvider loopProvider + */ + public function testDoesNotWriteDataIfRemoteSideFromPairHasBeenClosed($condition, $loopFactory) + { + if (true !== $condition()) { + return $this->markTestSkipped('Loop implementation not available'); + } + + $loop = $loopFactory(); + + list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0); + + $streamA = new DuplexResourceStream($sockA, $loop); + $streamB = new DuplexResourceStream($sockB, $loop); + + // end streamA without writing any data + $streamA->pause(); + $streamA->write('hello'); + $streamA->on('close', $this->expectCallableOnce()); + + $streamB->on('data', $this->expectCallableNever()); + $streamB->close(); + + $loop->run(); + + $streamA->close(); + $streamB->close(); + } + + /** + * @dataProvider loopProvider + */ + public function testDoesNotWriteDataIfServerSideHasBeenClosed($condition, $loopFactory) + { + if (true !== $condition()) { + return $this->markTestSkipped('Loop implementation not available'); + } + + $loop = $loopFactory(); + + $server = stream_socket_server('tcp://127.0.0.1:0'); + + $client = stream_socket_client(stream_socket_get_name($server, false)); + $peer = stream_socket_accept($server); + + $streamA = new DuplexResourceStream($client, $loop); + $streamB = new DuplexResourceStream($peer, $loop); + + // end streamA without writing any data + $streamA->pause(); + $streamA->write('hello'); + $streamA->on('close', $this->expectCallableOnce()); + + $streamB->on('data', $this->expectCallableNever()); + $streamB->close(); + + $loop->run(); + + $streamA->close(); + $streamB->close(); + } + + /** + * @dataProvider loopProvider + */ + public function testDoesNotWriteDataIfClientSideHasBeenClosed($condition, $loopFactory) + { + if (true !== $condition()) { + return $this->markTestSkipped('Loop implementation not available'); + } + + $loop = $loopFactory(); + + $server = stream_socket_server('tcp://127.0.0.1:0'); + + $client = stream_socket_client(stream_socket_get_name($server, false)); + $peer = stream_socket_accept($server); + + $streamA = new DuplexResourceStream($peer, $loop); + $streamB = new DuplexResourceStream($client, $loop); + + // end streamA without writing any data + $streamA->pause(); + $streamA->write('hello'); + $streamA->on('close', $this->expectCallableOnce()); + + $streamB->on('data', $this->expectCallableNever()); + $streamB->close(); + + $loop->run(); + + $streamA->close(); + $streamB->close(); + } + + /** + * @dataProvider loopProvider + */ + public function testReadsSingleChunkFromProcessPipe($condition, $loopFactory) + { + if (true !== $condition()) { + return $this->markTestSkipped('Loop implementation not available'); + } + + $loop = $loopFactory(); + + $stream = new ReadableResourceStream(popen('echo test', 'r'), $loop); + $stream->on('data', $this->expectCallableOnceWith("test\n")); + $stream->on('end', $this->expectCallableOnce()); + $stream->on('error', $this->expectCallableNever()); + + $loop->run(); + } + + /** + * @dataProvider loopProvider + */ + public function testReadsMultipleChunksFromProcessPipe($condition, $loopFactory) + { + if (true !== $condition()) { + return $this->markTestSkipped('Loop implementation not available'); + } + + $loop = $loopFactory(); + + $stream = new ReadableResourceStream(popen('echo a;sleep 0.1;echo b;sleep 0.1;echo c', 'r'), $loop); + + $buffer = ''; + $stream->on('data', function ($chunk) use (&$buffer) { + $buffer .= $chunk; + }); + + $stream->on('end', $this->expectCallableOnce()); + $stream->on('error', $this->expectCallableNever()); + + $loop->run(); + + $this->assertEquals("a\n" . "b\n" . "c\n", $buffer); + } + + /** + * @dataProvider loopProvider + */ + public function testReadsLongChunksFromProcessPipe($condition, $loopFactory) + { + if (true !== $condition()) { + return $this->markTestSkipped('Loop implementation not available'); + } + + $loop = $loopFactory(); + + $stream = new ReadableResourceStream(popen('dd if=/dev/zero bs=12345 count=1234 2>&-', 'r'), $loop); + + $bytes = 0; + $stream->on('data', function ($chunk) use (&$bytes) { + $bytes += strlen($chunk); + }); + + $stream->on('end', $this->expectCallableOnce()); + $stream->on('error', $this->expectCallableNever()); + + $loop->run(); + + $this->assertEquals(12345 * 1234, $bytes); + } + + /** + * @dataProvider loopProvider + */ + public function testReadsNothingFromProcessPipeWithNoOutput($condition, $loopFactory) + { + if (true !== $condition()) { + return $this->markTestSkipped('Loop implementation not available'); + } + + $loop = $loopFactory(); + + $stream = new ReadableResourceStream(popen('true', 'r'), $loop); + $stream->on('data', $this->expectCallableNever()); + $stream->on('end', $this->expectCallableOnce()); + $stream->on('error', $this->expectCallableNever()); + + $loop->run(); + } + + private function loopTick(LoopInterface $loop) + { + $loop->addTimer(0, function () use ($loop) { + $loop->stop(); + }); + $loop->run(); + } +} diff --git a/assets/php/vendor/react/stream/tests/DuplexResourceStreamTest.php b/assets/php/vendor/react/stream/tests/DuplexResourceStreamTest.php new file mode 100644 index 0000000..3212ae8 --- /dev/null +++ b/assets/php/vendor/react/stream/tests/DuplexResourceStreamTest.php @@ -0,0 +1,495 @@ +<?php + +namespace React\Tests\Stream; + +use React\Stream\DuplexResourceStream; +use Clue\StreamFilter as Filter; +use React\Stream\WritableResourceStream; + +class DuplexResourceStreamTest extends TestCase +{ + /** + * @covers React\Stream\DuplexResourceStream::__construct + * @doesNotPerformAssertions + */ + public function testConstructor() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + new DuplexResourceStream($stream, $loop); + } + + /** + * @covers React\Stream\DuplexResourceStream::__construct + * @doesNotPerformAssertions + */ + public function testConstructorWithExcessiveMode() + { + // excessive flags are ignored for temp streams, so we have to use a file stream + $name = tempnam(sys_get_temp_dir(), 'test'); + $stream = @fopen($name, 'r+eANYTHING'); + unlink($name); + + $loop = $this->createLoopMock(); + $buffer = new DuplexResourceStream($stream, $loop); + $buffer->close(); + } + + /** + * @covers React\Stream\DuplexResourceStream::__construct + * @expectedException InvalidArgumentException + */ + public function testConstructorThrowsExceptionOnInvalidStream() + { + $loop = $this->createLoopMock(); + + new DuplexResourceStream('breakme', $loop); + } + + /** + * @covers React\Stream\DuplexResourceStream::__construct + * @expectedException InvalidArgumentException + */ + public function testConstructorThrowsExceptionOnWriteOnlyStream() + { + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('HHVM does not report fopen mode for STDOUT'); + } + + $loop = $this->createLoopMock(); + + new DuplexResourceStream(STDOUT, $loop); + } + + /** + * @covers React\Stream\DuplexResourceStream::__construct + * @expectedException InvalidArgumentException + */ + public function testConstructorThrowsExceptionOnWriteOnlyStreamWithExcessiveMode() + { + // excessive flags are ignored for temp streams, so we have to use a file stream + $name = tempnam(sys_get_temp_dir(), 'test'); + $stream = fopen($name, 'weANYTHING'); + unlink($name); + + $loop = $this->createLoopMock(); + new DuplexResourceStream($stream, $loop); + } + + /** + * @covers React\Stream\DuplexResourceStream::__construct + * @expectedException RunTimeException + */ + public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking() + { + if (!in_array('blocking', stream_get_wrappers())) { + stream_wrapper_register('blocking', 'React\Tests\Stream\EnforceBlockingWrapper'); + } + + $stream = fopen('blocking://test', 'r+'); + $loop = $this->createLoopMock(); + + new DuplexResourceStream($stream, $loop); + } + + /** + * @covers React\Stream\DuplexResourceStream::__construct + * @doesNotPerformAssertions + */ + public function testConstructorAcceptsBuffer() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $buffer = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + + $conn = new DuplexResourceStream($stream, $loop, null, $buffer); + } + + public function testCloseShouldEmitCloseEvent() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $conn = new DuplexResourceStream($stream, $loop); + $conn->on('close', $this->expectCallableOnce()); + $conn->on('end', $this->expectCallableNever()); + + $conn->close(); + + $this->assertFalse($conn->isReadable()); + } + + public function testEndShouldEndBuffer() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $buffer = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $buffer->expects($this->once())->method('end')->with('foo'); + + $conn = new DuplexResourceStream($stream, $loop, null, $buffer); + $conn->end('foo'); + } + + + public function testEndAfterCloseIsNoOp() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $buffer = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $buffer->expects($this->never())->method('end'); + + $conn = new DuplexResourceStream($stream, $loop); + $conn->close(); + $conn->end(); + } + + /** + * @covers React\Stream\DuplexResourceStream::__construct + * @covers React\Stream\DuplexResourceStream::handleData + */ + public function testDataEvent() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $capturedData = null; + + $conn = new DuplexResourceStream($stream, $loop); + $conn->on('data', function ($data) use (&$capturedData) { + $capturedData = $data; + }); + + fwrite($stream, "foobar\n"); + rewind($stream); + + $conn->handleData($stream); + $this->assertSame("foobar\n", $capturedData); + } + + /** + * @covers React\Stream\DuplexResourceStream::__construct + * @covers React\Stream\DuplexResourceStream::handleData + */ + public function testDataEventDoesEmitOneChunkMatchingBufferSize() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $capturedData = null; + + $conn = new DuplexResourceStream($stream, $loop, 4321); + $conn->on('data', function ($data) use (&$capturedData) { + $capturedData = $data; + }); + + fwrite($stream, str_repeat("a", 100000)); + rewind($stream); + + $conn->handleData($stream); + + $this->assertTrue($conn->isReadable()); + $this->assertEquals(4321, strlen($capturedData)); + } + + /** + * @covers React\Stream\DuplexResourceStream::__construct + * @covers React\Stream\DuplexResourceStream::handleData + */ + public function testDataEventDoesEmitOneChunkUntilStreamEndsWhenBufferSizeIsInfinite() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $capturedData = null; + + $conn = new DuplexResourceStream($stream, $loop, -1); + + $conn->on('data', function ($data) use (&$capturedData) { + $capturedData = $data; + }); + + fwrite($stream, str_repeat("a", 100000)); + rewind($stream); + + $conn->handleData($stream); + + $this->assertTrue($conn->isReadable()); + $this->assertEquals(100000, strlen($capturedData)); + } + + /** + * @covers React\Stream\DuplexResourceStream::handleData + */ + public function testEmptyStreamShouldNotEmitData() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $conn = new DuplexResourceStream($stream, $loop); + $conn->on('data', $this->expectCallableNever()); + + $conn->handleData($stream); + } + + /** + * @covers React\Stream\DuplexResourceStream::write + */ + public function testWrite() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createWriteableLoopMock(); + + $conn = new DuplexResourceStream($stream, $loop); + $conn->write("foo\n"); + + rewind($stream); + $this->assertSame("foo\n", fgets($stream)); + } + + /** + * @covers React\Stream\DuplexResourceStream::end + * @covers React\Stream\DuplexResourceStream::isReadable + * @covers React\Stream\DuplexResourceStream::isWritable + */ + public function testEnd() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $conn = new DuplexResourceStream($stream, $loop); + $conn->end(); + + $this->assertFalse(is_resource($stream)); + $this->assertFalse($conn->isReadable()); + $this->assertFalse($conn->isWritable()); + } + + /** + * @covers React\Stream\DuplexResourceStream::end + */ + public function testEndRemovesReadStreamFromLoop() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $loop->expects($this->once())->method('addReadStream')->with($stream); + $loop->expects($this->once())->method('removeReadStream')->with($stream); + + $conn = new DuplexResourceStream($stream, $loop); + $conn->end('bye'); + } + + /** + * @covers React\Stream\DuplexResourceStream::pause + */ + public function testPauseRemovesReadStreamFromLoop() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $loop->expects($this->once())->method('addReadStream')->with($stream); + $loop->expects($this->once())->method('removeReadStream')->with($stream); + + $conn = new DuplexResourceStream($stream, $loop); + $conn->pause(); + $conn->pause(); + } + + /** + * @covers React\Stream\DuplexResourceStream::pause + */ + public function testResumeDoesAddStreamToLoopOnlyOnce() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $loop->expects($this->once())->method('addReadStream')->with($stream); + + $conn = new DuplexResourceStream($stream, $loop); + $conn->resume(); + $conn->resume(); + } + + /** + * @covers React\Stream\DuplexResourceStream::close + */ + public function testCloseRemovesReadStreamFromLoop() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $loop->expects($this->once())->method('addReadStream')->with($stream); + $loop->expects($this->once())->method('removeReadStream')->with($stream); + + $conn = new DuplexResourceStream($stream, $loop); + $conn->close(); + } + + /** + * @covers React\Stream\DuplexResourceStream::close + */ + public function testCloseAfterPauseRemovesReadStreamFromLoopOnlyOnce() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $loop->expects($this->once())->method('addReadStream')->with($stream); + $loop->expects($this->once())->method('removeReadStream')->with($stream); + + $conn = new DuplexResourceStream($stream, $loop); + $conn->pause(); + $conn->close(); + } + + /** + * @covers React\Stream\DuplexResourceStream::close + */ + public function testResumeAfterCloseDoesAddReadStreamToLoopOnlyOnce() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $loop->expects($this->once())->method('addReadStream')->with($stream); + + $conn = new DuplexResourceStream($stream, $loop); + $conn->close(); + $conn->resume(); + } + + public function testEndedStreamsShouldNotWrite() + { + $file = tempnam(sys_get_temp_dir(), 'reactphptest_'); + $stream = fopen($file, 'r+'); + $loop = $this->createWriteableLoopMock(); + + $conn = new DuplexResourceStream($stream, $loop); + $conn->write("foo\n"); + $conn->end(); + + $res = $conn->write("bar\n"); + $stream = fopen($file, 'r'); + + $this->assertSame("foo\n", fgets($stream)); + $this->assertFalse($res); + + unlink($file); + } + + public function testPipeShouldReturnDestination() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $conn = new DuplexResourceStream($stream, $loop); + $dest = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + + $this->assertSame($dest, $conn->pipe($dest)); + } + + public function testBufferEventsShouldBubbleUp() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $buffer = new WritableResourceStream($stream, $loop); + $conn = new DuplexResourceStream($stream, $loop, null, $buffer); + + $conn->on('drain', $this->expectCallableOnce()); + $conn->on('error', $this->expectCallableOnce()); + + $buffer->emit('drain'); + $buffer->emit('error', array(new \RuntimeException('Whoops'))); + } + + /** + * @covers React\Stream\DuplexResourceStream::handleData + */ + public function testClosingStreamInDataEventShouldNotTriggerError() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $conn = new DuplexResourceStream($stream, $loop); + $conn->on('error', $this->expectCallableNever()); + $conn->on('data', function ($data) use ($conn) { + $conn->close(); + }); + + fwrite($stream, "foobar\n"); + rewind($stream); + + $conn->handleData($stream); + } + + /** + * @covers React\Stream\DuplexResourceStream::handleData + */ + public function testDataFiltered() + { + $stream = fopen('php://temp', 'r+'); + + // add a filter which removes every 'a' when reading + Filter\append($stream, function ($chunk) { + return str_replace('a', '', $chunk); + }, STREAM_FILTER_READ); + + $loop = $this->createLoopMock(); + + $capturedData = null; + + $conn = new DuplexResourceStream($stream, $loop); + $conn->on('data', function ($data) use (&$capturedData) { + $capturedData = $data; + }); + + fwrite($stream, "foobar\n"); + rewind($stream); + + $conn->handleData($stream); + $this->assertSame("foobr\n", $capturedData); + } + + /** + * @covers React\Stream\DuplexResourceStream::handleData + */ + public function testDataErrorShouldEmitErrorAndClose() + { + $stream = fopen('php://temp', 'r+'); + + // add a filter which returns an error when encountering an 'a' when reading + Filter\append($stream, function ($chunk) { + if (strpos($chunk, 'a') !== false) { + throw new \Exception('Invalid'); + } + return $chunk; + }, STREAM_FILTER_READ); + + $loop = $this->createLoopMock(); + + $conn = new DuplexResourceStream($stream, $loop); + $conn->on('data', $this->expectCallableNever()); + $conn->on('error', $this->expectCallableOnce()); + $conn->on('close', $this->expectCallableOnce()); + + fwrite($stream, "foobar\n"); + rewind($stream); + + $conn->handleData($stream); + } + + private function createWriteableLoopMock() + { + $loop = $this->createLoopMock(); + $loop + ->expects($this->once()) + ->method('addWriteStream') + ->will($this->returnCallback(function ($stream, $listener) { + call_user_func($listener, $stream); + })); + + return $loop; + } + + private function createLoopMock() + { + return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + } +} diff --git a/assets/php/vendor/react/stream/tests/EnforceBlockingWrapper.php b/assets/php/vendor/react/stream/tests/EnforceBlockingWrapper.php new file mode 100644 index 0000000..39c0487 --- /dev/null +++ b/assets/php/vendor/react/stream/tests/EnforceBlockingWrapper.php @@ -0,0 +1,35 @@ +<?php + +namespace React\Tests\Stream; + +/** + * Used to test dummy stream resources that do not support setting non-blocking mode + * + * @link http://php.net/manual/de/class.streamwrapper.php + */ +class EnforceBlockingWrapper +{ + public function stream_open($path, $mode, $options, &$opened_path) + { + return true; + } + + public function stream_cast($cast_as) + { + return false; + } + + public function stream_eof() + { + return false; + } + + public function stream_set_option($option, $arg1, $arg2) + { + if ($option === STREAM_OPTION_BLOCKING) { + return false; + } + + return true; + } +} diff --git a/assets/php/vendor/react/stream/tests/FunctionalInternetTest.php b/assets/php/vendor/react/stream/tests/FunctionalInternetTest.php new file mode 100644 index 0000000..4d31e8e --- /dev/null +++ b/assets/php/vendor/react/stream/tests/FunctionalInternetTest.php @@ -0,0 +1,122 @@ +<?php + +namespace React\Tests\Stream; + +use React\EventLoop\Factory; +use React\EventLoop\LoopInterface; +use React\Stream\DuplexResourceStream; +use React\Stream\WritableResourceStream; + +/** + * @group internet + */ +class FunctionalInternetTest extends TestCase +{ + public function testUploadKilobytePlain() + { + $size = 1000; + $stream = stream_socket_client('tcp://httpbin.org:80'); + + $loop = Factory::create(); + $stream = new DuplexResourceStream($stream, $loop); + + $buffer = ''; + $stream->on('data', function ($chunk) use (&$buffer) { + $buffer .= $chunk; + }); + + $stream->on('error', $this->expectCallableNever()); + + $stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size)); + + $this->awaitStreamClose($stream, $loop); + + $this->assertNotEquals('', $buffer); + } + + public function testUploadBiggerBlockPlain() + { + $size = 50 * 1000; + $stream = stream_socket_client('tcp://httpbin.org:80'); + + $loop = Factory::create(); + $stream = new DuplexResourceStream($stream, $loop); + + $buffer = ''; + $stream->on('data', function ($chunk) use (&$buffer) { + $buffer .= $chunk; + }); + + $stream->on('error', $this->expectCallableNever()); + + $stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size)); + + $this->awaitStreamClose($stream, $loop); + + $this->assertNotEquals('', $buffer); + } + + public function testUploadKilobyteSecure() + { + $size = 1000; + $stream = stream_socket_client('tls://httpbin.org:443'); + + $loop = Factory::create(); + $stream = new DuplexResourceStream($stream, $loop); + + $buffer = ''; + $stream->on('data', function ($chunk) use (&$buffer) { + $buffer .= $chunk; + }); + + $stream->on('error', $this->expectCallableNever()); + + $stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size)); + + $this->awaitStreamClose($stream, $loop); + + $this->assertNotEquals('', $buffer); + } + + public function testUploadBiggerBlockSecureRequiresSmallerChunkSize() + { + $size = 50 * 1000; + $stream = stream_socket_client('tls://httpbin.org:443'); + + $loop = Factory::create(); + $stream = new DuplexResourceStream( + $stream, + $loop, + null, + new WritableResourceStream($stream, $loop, null, 8192) + ); + + $buffer = ''; + $stream->on('data', function ($chunk) use (&$buffer) { + $buffer .= $chunk; + }); + + $stream->on('error', $this->expectCallableNever()); + + $stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size)); + + $this->awaitStreamClose($stream, $loop); + + $this->assertNotEquals('', $buffer); + } + + private function awaitStreamClose(DuplexResourceStream $stream, LoopInterface $loop, $timeout = 10.0) + { + $stream->on('close', function () use ($loop) { + $loop->stop(); + }); + + $that = $this; + $loop->addTimer($timeout, function () use ($loop, $that) { + $loop->stop(); + $that->fail('Timed out while waiting for stream to close'); + }); + + $loop->run(); + } +} diff --git a/assets/php/vendor/react/stream/tests/ReadableResourceStreamTest.php b/assets/php/vendor/react/stream/tests/ReadableResourceStreamTest.php new file mode 100644 index 0000000..20da96f --- /dev/null +++ b/assets/php/vendor/react/stream/tests/ReadableResourceStreamTest.php @@ -0,0 +1,372 @@ +<?php + +namespace React\Tests\Stream; + +use React\Stream\ReadableResourceStream; +use Clue\StreamFilter as Filter; + +class ReadableResourceStreamTest extends TestCase +{ + /** + * @covers React\Stream\ReadableResourceStream::__construct + * @doesNotPerformAssertions + */ + public function testConstructor() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + new ReadableResourceStream($stream, $loop); + } + + /** + * @covers React\Stream\ReadableResourceStream::__construct + * @doesNotPerformAssertions + */ + public function testConstructorWithExcessiveMode() + { + // excessive flags are ignored for temp streams, so we have to use a file stream + $name = tempnam(sys_get_temp_dir(), 'test'); + $stream = @fopen($name, 'r+eANYTHING'); + unlink($name); + + $loop = $this->createLoopMock(); + $buffer = new ReadableResourceStream($stream, $loop); + $buffer->close(); + } + + /** + * @covers React\Stream\ReadableResourceStream::__construct + * @expectedException InvalidArgumentException + */ + public function testConstructorThrowsExceptionOnInvalidStream() + { + $loop = $this->createLoopMock(); + + new ReadableResourceStream(false, $loop); + } + + /** + * @covers React\Stream\ReadableResourceStream::__construct + * @expectedException InvalidArgumentException + */ + public function testConstructorThrowsExceptionOnWriteOnlyStream() + { + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('HHVM does not report fopen mode for STDOUT'); + } + + $loop = $this->createLoopMock(); + + new ReadableResourceStream(STDOUT, $loop); + } + + /** + * @covers React\Stream\ReadableResourceStream::__construct + * @expectedException InvalidArgumentException + */ + public function testConstructorThrowsExceptionOnWriteOnlyStreamWithExcessiveMode() + { + // excessive flags are ignored for temp streams, so we have to use a file stream + $name = tempnam(sys_get_temp_dir(), 'test'); + $stream = fopen($name, 'weANYTHING'); + unlink($name); + + $loop = $this->createLoopMock(); + new ReadableResourceStream($stream, $loop); + } + + /** + * @covers React\Stream\ReadableResourceStream::__construct + * @expectedException RuntimeException + */ + public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking() + { + if (!in_array('blocking', stream_get_wrappers())) { + stream_wrapper_register('blocking', 'React\Tests\Stream\EnforceBlockingWrapper'); + } + + $stream = fopen('blocking://test', 'r+'); + $loop = $this->createLoopMock(); + + new ReadableResourceStream($stream, $loop); + } + + + public function testCloseShouldEmitCloseEvent() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $conn = new ReadableResourceStream($stream, $loop); + $conn->on('close', $this->expectCallableOnce()); + + $conn->close(); + + $this->assertFalse($conn->isReadable()); + } + + public function testCloseTwiceShouldEmitCloseEventOnce() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $conn = new ReadableResourceStream($stream, $loop); + $conn->on('close', $this->expectCallableOnce()); + + $conn->close(); + $conn->close(); + } + + /** + * @covers React\Stream\ReadableResourceStream::__construct + * @covers React\Stream\ReadableResourceStream::handleData + */ + public function testDataEvent() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $capturedData = null; + + $conn = new ReadableResourceStream($stream, $loop); + $conn->on('data', function ($data) use (&$capturedData) { + $capturedData = $data; + }); + + fwrite($stream, "foobar\n"); + rewind($stream); + + $conn->handleData($stream); + $this->assertSame("foobar\n", $capturedData); + } + + /** + * @covers React\Stream\ReadableResourceStream::__construct + * @covers React\Stream\ReadableResourceStream::handleData + */ + public function testDataEventDoesEmitOneChunkMatchingBufferSize() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $capturedData = null; + + $conn = new ReadableResourceStream($stream, $loop, 4321); + $conn->on('data', function ($data) use (&$capturedData) { + $capturedData = $data; + }); + + fwrite($stream, str_repeat("a", 100000)); + rewind($stream); + + $conn->handleData($stream); + + $this->assertTrue($conn->isReadable()); + $this->assertEquals(4321, strlen($capturedData)); + } + + /** + * @covers React\Stream\ReadableResourceStream::__construct + * @covers React\Stream\ReadableResourceStream::handleData + */ + public function testDataEventDoesEmitOneChunkUntilStreamEndsWhenBufferSizeIsInfinite() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $capturedData = null; + + $conn = new ReadableResourceStream($stream, $loop, -1); + + $conn->on('data', function ($data) use (&$capturedData) { + $capturedData = $data; + }); + + fwrite($stream, str_repeat("a", 100000)); + rewind($stream); + + $conn->handleData($stream); + + $this->assertTrue($conn->isReadable()); + $this->assertEquals(100000, strlen($capturedData)); + } + + /** + * @covers React\Stream\ReadableResourceStream::handleData + */ + public function testEmptyStreamShouldNotEmitData() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $conn = new ReadableResourceStream($stream, $loop); + $conn->on('data', $this->expectCallableNever()); + + $conn->handleData($stream); + } + + public function testPipeShouldReturnDestination() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $conn = new ReadableResourceStream($stream, $loop); + $dest = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + + $this->assertSame($dest, $conn->pipe($dest)); + } + + /** + * @covers React\Stream\ReadableResourceStream::handleData + */ + public function testClosingStreamInDataEventShouldNotTriggerError() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $conn = new ReadableResourceStream($stream, $loop); + $conn->on('error', $this->expectCallableNever()); + $conn->on('data', function ($data) use ($conn) { + $conn->close(); + }); + + fwrite($stream, "foobar\n"); + rewind($stream); + + $conn->handleData($stream); + } + + /** + * @covers React\Stream\ReadableResourceStream::pause + */ + public function testPauseRemovesReadStreamFromLoop() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $loop->expects($this->once())->method('addReadStream')->with($stream); + $loop->expects($this->once())->method('removeReadStream')->with($stream); + + $conn = new ReadableResourceStream($stream, $loop); + $conn->pause(); + $conn->pause(); + } + + /** + * @covers React\Stream\ReadableResourceStream::pause + */ + public function testResumeDoesAddStreamToLoopOnlyOnce() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $loop->expects($this->once())->method('addReadStream')->with($stream); + + $conn = new ReadableResourceStream($stream, $loop); + $conn->resume(); + $conn->resume(); + } + + /** + * @covers React\Stream\ReadableResourceStream::close + */ + public function testCloseRemovesReadStreamFromLoop() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $loop->expects($this->once())->method('addReadStream')->with($stream); + $loop->expects($this->once())->method('removeReadStream')->with($stream); + + $conn = new ReadableResourceStream($stream, $loop); + $conn->close(); + } + + /** + * @covers React\Stream\ReadableResourceStream::close + */ + public function testCloseAfterPauseRemovesReadStreamFromLoopOnce() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $loop->expects($this->once())->method('addReadStream')->with($stream); + $loop->expects($this->once())->method('removeReadStream')->with($stream); + + $conn = new ReadableResourceStream($stream, $loop); + $conn->pause(); + $conn->close(); + } + + /** + * @covers React\Stream\ReadableResourceStream::close + */ + public function testResumeAfterCloseDoesAddReadStreamToLoopOnlyOnce() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $loop->expects($this->once())->method('addReadStream')->with($stream); + + $conn = new ReadableResourceStream($stream, $loop); + $conn->close(); + $conn->resume(); + } + + /** + * @covers React\Stream\ReadableResourceStream::handleData + */ + public function testDataFiltered() + { + $stream = fopen('php://temp', 'r+'); + + // add a filter which removes every 'a' when reading + Filter\append($stream, function ($chunk) { + return str_replace('a', '', $chunk); + }, STREAM_FILTER_READ); + + $loop = $this->createLoopMock(); + + $capturedData = null; + + $conn = new ReadableResourceStream($stream, $loop); + $conn->on('data', function ($data) use (&$capturedData) { + $capturedData = $data; + }); + + fwrite($stream, "foobar\n"); + rewind($stream); + + $conn->handleData($stream); + $this->assertSame("foobr\n", $capturedData); + } + + /** + * @covers React\Stream\ReadableResourceStream::handleData + */ + public function testDataErrorShouldEmitErrorAndClose() + { + $stream = fopen('php://temp', 'r+'); + + // add a filter which returns an error when encountering an 'a' when reading + Filter\append($stream, function ($chunk) { + if (strpos($chunk, 'a') !== false) { + throw new \Exception('Invalid'); + } + return $chunk; + }, STREAM_FILTER_READ); + + $loop = $this->createLoopMock(); + + $conn = new ReadableResourceStream($stream, $loop); + $conn->on('data', $this->expectCallableNever()); + $conn->on('error', $this->expectCallableOnce()); + $conn->on('close', $this->expectCallableOnce()); + + fwrite($stream, "foobar\n"); + rewind($stream); + + $conn->handleData($stream); + } + + private function createLoopMock() + { + return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + } +} diff --git a/assets/php/vendor/react/stream/tests/Stub/ReadableStreamStub.php b/assets/php/vendor/react/stream/tests/Stub/ReadableStreamStub.php new file mode 100644 index 0000000..6984f24 --- /dev/null +++ b/assets/php/vendor/react/stream/tests/Stub/ReadableStreamStub.php @@ -0,0 +1,61 @@ +<?php + +namespace React\Tests\Stream\Stub; + +use Evenement\EventEmitter; +use React\Stream\ReadableStreamInterface; +use React\Stream\WritableStreamInterface; +use React\Stream\Util; + +class ReadableStreamStub extends EventEmitter implements ReadableStreamInterface +{ + public $readable = true; + public $paused = false; + + public function isReadable() + { + return true; + } + + // trigger data event + public function write($data) + { + $this->emit('data', array($data)); + } + + // trigger error event + public function error($error) + { + $this->emit('error', array($error)); + } + + // trigger end event + public function end() + { + $this->emit('end', array()); + } + + public function pause() + { + $this->paused = true; + } + + public function resume() + { + $this->paused = false; + } + + public function close() + { + $this->readable = false; + + $this->emit('close'); + } + + public function pipe(WritableStreamInterface $dest, array $options = array()) + { + Util::pipe($this, $dest, $options); + + return $dest; + } +} diff --git a/assets/php/vendor/react/stream/tests/TestCase.php b/assets/php/vendor/react/stream/tests/TestCase.php new file mode 100644 index 0000000..c8fc1db --- /dev/null +++ b/assets/php/vendor/react/stream/tests/TestCase.php @@ -0,0 +1,54 @@ +<?php + +namespace React\Tests\Stream; + +use PHPUnit\Framework\TestCase as BaseTestCase; + +class TestCase extends BaseTestCase +{ + protected function expectCallableExactly($amount) + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->exactly($amount)) + ->method('__invoke'); + + return $mock; + } + + protected function expectCallableOnce() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke'); + + return $mock; + } + + protected function expectCallableOnceWith($value) + { + $callback = $this->createCallableMock(); + $callback + ->expects($this->once()) + ->method('__invoke') + ->with($value); + + return $callback; + } + + protected function expectCallableNever() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->never()) + ->method('__invoke'); + + return $mock; + } + + protected function createCallableMock() + { + return $this->getMockBuilder('React\Tests\Stream\CallableStub')->getMock(); + } +} diff --git a/assets/php/vendor/react/stream/tests/ThroughStreamTest.php b/assets/php/vendor/react/stream/tests/ThroughStreamTest.php new file mode 100644 index 0000000..a98badf --- /dev/null +++ b/assets/php/vendor/react/stream/tests/ThroughStreamTest.php @@ -0,0 +1,267 @@ +<?php + +namespace React\Tests\Stream; + +use React\Stream\ThroughStream; + +/** + * @covers React\Stream\ThroughStream + */ +class ThroughStreamTest extends TestCase +{ + /** + * @test + * @expectedException InvalidArgumentException + */ + public function itShouldRejectInvalidCallback() + { + new ThroughStream(123); + } + + /** @test */ + public function itShouldReturnTrueForAnyDataWrittenToIt() + { + $through = new ThroughStream(); + $ret = $through->write('foo'); + + $this->assertTrue($ret); + } + + /** @test */ + public function itShouldEmitAnyDataWrittenToIt() + { + $through = new ThroughStream(); + $through->on('data', $this->expectCallableOnceWith('foo')); + $through->write('foo'); + } + + /** @test */ + public function itShouldEmitAnyDataWrittenToItPassedThruFunction() + { + $through = new ThroughStream('strtoupper'); + $through->on('data', $this->expectCallableOnceWith('FOO')); + $through->write('foo'); + } + + /** @test */ + public function itShouldEmitAnyDataWrittenToItPassedThruCallback() + { + $through = new ThroughStream('strtoupper'); + $through->on('data', $this->expectCallableOnceWith('FOO')); + $through->write('foo'); + } + + /** @test */ + public function itShouldEmitErrorAndCloseIfCallbackThrowsException() + { + $through = new ThroughStream(function () { + throw new \RuntimeException(); + }); + $through->on('error', $this->expectCallableOnce()); + $through->on('close', $this->expectCallableOnce()); + $through->on('data', $this->expectCallableNever()); + $through->on('end', $this->expectCallableNever()); + + $through->write('foo'); + + $this->assertFalse($through->isReadable()); + $this->assertFalse($through->isWritable()); + } + + /** @test */ + public function itShouldEmitErrorAndCloseIfCallbackThrowsExceptionOnEnd() + { + $through = new ThroughStream(function () { + throw new \RuntimeException(); + }); + $through->on('error', $this->expectCallableOnce()); + $through->on('close', $this->expectCallableOnce()); + $through->on('data', $this->expectCallableNever()); + $through->on('end', $this->expectCallableNever()); + + $through->end('foo'); + + $this->assertFalse($through->isReadable()); + $this->assertFalse($through->isWritable()); + } + + /** @test */ + public function itShouldReturnFalseForAnyDataWrittenToItWhenPaused() + { + $through = new ThroughStream(); + $through->pause(); + $ret = $through->write('foo'); + + $this->assertFalse($ret); + } + + /** @test */ + public function itShouldEmitDrainOnResumeAfterReturnFalseForAnyDataWrittenToItWhenPaused() + { + $through = new ThroughStream(); + $through->pause(); + $through->write('foo'); + + $through->on('drain', $this->expectCallableOnce()); + $through->resume(); + } + + /** @test */ + public function itShouldReturnTrueForAnyDataWrittenToItWhenResumedAfterPause() + { + $through = new ThroughStream(); + $through->on('drain', $this->expectCallableNever()); + $through->pause(); + $through->resume(); + $ret = $through->write('foo'); + + $this->assertTrue($ret); + } + + /** @test */ + public function pipingStuffIntoItShouldWork() + { + $readable = new ThroughStream(); + + $through = new ThroughStream(); + $through->on('data', $this->expectCallableOnceWith('foo')); + + $readable->pipe($through); + $readable->emit('data', array('foo')); + } + + /** @test */ + public function endShouldEmitEndAndClose() + { + $through = new ThroughStream(); + $through->on('data', $this->expectCallableNever()); + $through->on('end', $this->expectCallableOnce()); + $through->on('close', $this->expectCallableOnce()); + $through->end(); + } + + /** @test */ + public function endShouldCloseTheStream() + { + $through = new ThroughStream(); + $through->on('data', $this->expectCallableNever()); + $through->end(); + + $this->assertFalse($through->isReadable()); + $this->assertFalse($through->isWritable()); + } + + /** @test */ + public function endShouldWriteDataBeforeClosing() + { + $through = new ThroughStream(); + $through->on('data', $this->expectCallableOnceWith('foo')); + $through->end('foo'); + + $this->assertFalse($through->isReadable()); + $this->assertFalse($through->isWritable()); + } + + /** @test */ + public function endTwiceShouldOnlyEmitOnce() + { + $through = new ThroughStream(); + $through->on('data', $this->expectCallableOnce('first')); + $through->end('first'); + $through->end('ignored'); + } + + /** @test */ + public function writeAfterEndShouldReturnFalse() + { + $through = new ThroughStream(); + $through->on('data', $this->expectCallableNever()); + $through->end(); + + $this->assertFalse($through->write('foo')); + } + + /** @test */ + public function writeDataWillCloseStreamShouldReturnFalse() + { + $through = new ThroughStream(); + $through->on('data', array($through, 'close')); + + $this->assertFalse($through->write('foo')); + } + + /** @test */ + public function writeDataToPausedShouldReturnFalse() + { + $through = new ThroughStream(); + $through->pause(); + + $this->assertFalse($through->write('foo')); + } + + /** @test */ + public function writeDataToResumedShouldReturnTrue() + { + $through = new ThroughStream(); + $through->pause(); + $through->resume(); + + $this->assertTrue($through->write('foo')); + } + + /** @test */ + public function itShouldBeReadableByDefault() + { + $through = new ThroughStream(); + $this->assertTrue($through->isReadable()); + } + + /** @test */ + public function itShouldBeWritableByDefault() + { + $through = new ThroughStream(); + $this->assertTrue($through->isWritable()); + } + + /** @test */ + public function closeShouldCloseOnce() + { + $through = new ThroughStream(); + + $through->on('close', $this->expectCallableOnce()); + + $through->close(); + + $this->assertFalse($through->isReadable()); + $this->assertFalse($through->isWritable()); + } + + /** @test */ + public function doubleCloseShouldCloseOnce() + { + $through = new ThroughStream(); + + $through->on('close', $this->expectCallableOnce()); + + $through->close(); + $through->close(); + + $this->assertFalse($through->isReadable()); + $this->assertFalse($through->isWritable()); + } + + /** @test */ + public function pipeShouldPipeCorrectly() + { + $output = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $output->expects($this->any())->method('isWritable')->willReturn(True); + $output + ->expects($this->once()) + ->method('write') + ->with('foo'); + + $through = new ThroughStream(); + $through->pipe($output); + $through->write('foo'); + } +} diff --git a/assets/php/vendor/react/stream/tests/UtilTest.php b/assets/php/vendor/react/stream/tests/UtilTest.php new file mode 100644 index 0000000..3d113ab --- /dev/null +++ b/assets/php/vendor/react/stream/tests/UtilTest.php @@ -0,0 +1,273 @@ +<?php + +namespace React\Tests\Stream; + +use React\Stream\WritableResourceStream; +use React\Stream\Util; +use React\Stream\CompositeStream; +use React\Stream\ThroughStream; + +/** + * @covers React\Stream\Util + */ +class UtilTest extends TestCase +{ + public function testPipeReturnsDestinationStream() + { + $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + + $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + + $ret = Util::pipe($readable, $writable); + + $this->assertSame($writable, $ret); + } + + public function testPipeNonReadableSourceShouldDoNothing() + { + $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable + ->expects($this->any()) + ->method('isReadable') + ->willReturn(false); + + $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable + ->expects($this->never()) + ->method('isWritable'); + $writable + ->expects($this->never()) + ->method('end'); + + Util::pipe($readable, $writable); + } + + public function testPipeIntoNonWritableDestinationShouldPauseSource() + { + $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable + ->expects($this->any()) + ->method('isReadable') + ->willReturn(true); + $readable + ->expects($this->once()) + ->method('pause'); + + $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable + ->expects($this->any()) + ->method('isWritable') + ->willReturn(false); + $writable + ->expects($this->never()) + ->method('end'); + + Util::pipe($readable, $writable); + } + + public function testPipeClosingDestPausesSource() + { + $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable + ->expects($this->any()) + ->method('isReadable') + ->willReturn(true); + $readable + ->expects($this->once()) + ->method('pause'); + + $writable = new ThroughStream(); + + Util::pipe($readable, $writable); + + $writable->close(); + } + + public function testPipeWithEnd() + { + $readable = new Stub\ReadableStreamStub(); + + $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable + ->expects($this->any()) + ->method('isWritable') + ->willReturn(true); + $writable + ->expects($this->once()) + ->method('end'); + + Util::pipe($readable, $writable); + + $readable->end(); + } + + public function testPipeWithoutEnd() + { + $readable = new Stub\ReadableStreamStub(); + + $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable + ->expects($this->any()) + ->method('isWritable') + ->willReturn(true); + $writable + ->expects($this->never()) + ->method('end'); + + Util::pipe($readable, $writable, array('end' => false)); + + $readable->end(); + } + + public function testPipeWithTooSlowWritableShouldPauseReadable() + { + $readable = new Stub\ReadableStreamStub(); + + $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable + ->expects($this->any()) + ->method('isWritable') + ->willReturn(true); + $writable + ->expects($this->once()) + ->method('write') + ->with('some data') + ->will($this->returnValue(false)); + + $readable->pipe($writable); + + $this->assertFalse($readable->paused); + $readable->write('some data'); + $this->assertTrue($readable->paused); + } + + public function testPipeWithTooSlowWritableShouldResumeOnDrain() + { + $readable = new Stub\ReadableStreamStub(); + + $onDrain = null; + + $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable + ->expects($this->any()) + ->method('isWritable') + ->willReturn(true); + $writable + ->expects($this->any()) + ->method('on') + ->will($this->returnCallback(function ($name, $callback) use (&$onDrain) { + if ($name === 'drain') { + $onDrain = $callback; + } + })); + + $readable->pipe($writable); + $readable->pause(); + + $this->assertTrue($readable->paused); + $this->assertNotNull($onDrain); + $onDrain(); + $this->assertFalse($readable->paused); + } + + public function testPipeWithWritableResourceStream() + { + $readable = new Stub\ReadableStreamStub(); + + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $buffer = new WritableResourceStream($stream, $loop); + + $readable->pipe($buffer); + + $readable->write('hello, I am some '); + $readable->write('random data'); + + $buffer->handleWrite(); + rewind($stream); + $this->assertSame('hello, I am some random data', stream_get_contents($stream)); + } + + public function testPipeSetsUpListeners() + { + $source = new ThroughStream(); + $dest = new ThroughStream(); + + $this->assertCount(0, $source->listeners('data')); + $this->assertCount(0, $source->listeners('end')); + $this->assertCount(0, $dest->listeners('drain')); + + Util::pipe($source, $dest); + + $this->assertCount(1, $source->listeners('data')); + $this->assertCount(1, $source->listeners('end')); + $this->assertCount(1, $dest->listeners('drain')); + } + + public function testPipeClosingSourceRemovesListeners() + { + $source = new ThroughStream(); + $dest = new ThroughStream(); + + Util::pipe($source, $dest); + + $source->close(); + + $this->assertCount(0, $source->listeners('data')); + $this->assertCount(0, $source->listeners('end')); + $this->assertCount(0, $dest->listeners('drain')); + } + + public function testPipeClosingDestRemovesListeners() + { + $source = new ThroughStream(); + $dest = new ThroughStream(); + + Util::pipe($source, $dest); + + $dest->close(); + + $this->assertCount(0, $source->listeners('data')); + $this->assertCount(0, $source->listeners('end')); + $this->assertCount(0, $dest->listeners('drain')); + } + + public function testPipeDuplexIntoSelfEndsOnEnd() + { + $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable->expects($this->any())->method('isReadable')->willReturn(true); + $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable->expects($this->any())->method('isWritable')->willReturn(true); + $duplex = new CompositeStream($readable, $writable); + + Util::pipe($duplex, $duplex); + + $writable->expects($this->once())->method('end'); + + $duplex->emit('end'); + } + + /** @test */ + public function forwardEventsShouldSetupForwards() + { + $source = new ThroughStream(); + $target = new ThroughStream(); + + Util::forwardEvents($source, $target, array('data')); + $target->on('data', $this->expectCallableOnce()); + $target->on('foo', $this->expectCallableNever()); + + $source->emit('data', array('hello')); + $source->emit('foo', array('bar')); + } + + private function createLoopMock() + { + return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + } + + private function notEqualTo($value) + { + return new \PHPUnit_Framework_Constraint_Not($value); + } +} diff --git a/assets/php/vendor/react/stream/tests/WritableStreamResourceTest.php b/assets/php/vendor/react/stream/tests/WritableStreamResourceTest.php new file mode 100644 index 0000000..05bce9c --- /dev/null +++ b/assets/php/vendor/react/stream/tests/WritableStreamResourceTest.php @@ -0,0 +1,534 @@ +<?php + +namespace React\Tests\Stream; + +use Clue\StreamFilter as Filter; +use React\Stream\WritableResourceStream; + +class WritableResourceStreamTest extends TestCase +{ + /** + * @covers React\Stream\WritableResourceStream::__construct + * @doesNotPerformAssertions + */ + public function testConstructor() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + new WritableResourceStream($stream, $loop); + } + + /** + * @covers React\Stream\WritableResourceStream::__construct + * @doesNotPerformAssertions + */ + public function testConstructorWithExcessiveMode() + { + // excessive flags are ignored for temp streams, so we have to use a file stream + $name = tempnam(sys_get_temp_dir(), 'test'); + $stream = @fopen($name, 'w+eANYTHING'); + unlink($name); + + $loop = $this->createLoopMock(); + $buffer = new WritableResourceStream($stream, $loop); + $buffer->close(); + } + + /** + * @covers React\Stream\WritableResourceStream::__construct + * @expectedException InvalidArgumentException + */ + public function testConstructorThrowsIfNotAValidStreamResource() + { + $stream = null; + $loop = $this->createLoopMock(); + + new WritableResourceStream($stream, $loop); + } + + /** + * @covers React\Stream\WritableResourceStream::__construct + * @expectedException InvalidArgumentException + */ + public function testConstructorThrowsExceptionOnReadOnlyStream() + { + $stream = fopen('php://temp', 'r'); + $loop = $this->createLoopMock(); + + new WritableResourceStream($stream, $loop); + } + + /** + * @covers React\Stream\WritableResourceStream::__construct + * @expectedException InvalidArgumentException + */ + public function testConstructorThrowsExceptionOnReadOnlyStreamWithExcessiveMode() + { + // excessive flags are ignored for temp streams, so we have to use a file stream + $name = tempnam(sys_get_temp_dir(), 'test'); + $stream = fopen($name, 'reANYTHING'); + unlink($name); + + $loop = $this->createLoopMock(); + new WritableResourceStream($stream, $loop); + } + + /** + * @covers React\Stream\WritableResourceStream::__construct + * @expectedException RuntimeException + */ + public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking() + { + if (!in_array('blocking', stream_get_wrappers())) { + stream_wrapper_register('blocking', 'React\Tests\Stream\EnforceBlockingWrapper'); + } + + $stream = fopen('blocking://test', 'r+'); + $loop = $this->createLoopMock(); + + new WritableResourceStream($stream, $loop); + } + + /** + * @covers React\Stream\WritableResourceStream::write + * @covers React\Stream\WritableResourceStream::handleWrite + */ + public function testWrite() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createWriteableLoopMock(); + + $buffer = new WritableResourceStream($stream, $loop); + $buffer->on('error', $this->expectCallableNever()); + + $buffer->write("foobar\n"); + rewind($stream); + $this->assertSame("foobar\n", fread($stream, 1024)); + } + + /** + * @covers React\Stream\WritableResourceStream::write + */ + public function testWriteWithDataDoesAddResourceToLoop() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $loop->expects($this->once())->method('addWriteStream')->with($this->equalTo($stream)); + + $buffer = new WritableResourceStream($stream, $loop); + + $buffer->write("foobar\n"); + } + + /** + * @covers React\Stream\WritableResourceStream::write + * @covers React\Stream\WritableResourceStream::handleWrite + */ + public function testEmptyWriteDoesNotAddToLoop() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $loop->expects($this->never())->method('addWriteStream'); + + $buffer = new WritableResourceStream($stream, $loop); + + $buffer->write(""); + $buffer->write(null); + } + + /** + * @covers React\Stream\WritableResourceStream::write + * @covers React\Stream\WritableResourceStream::handleWrite + */ + public function testWriteReturnsFalseWhenWritableResourceStreamIsFull() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createWriteableLoopMock(); + $loop->preventWrites = true; + + $buffer = new WritableResourceStream($stream, $loop, 4); + $buffer->on('error', $this->expectCallableNever()); + + $this->assertTrue($buffer->write("foo")); + $loop->preventWrites = false; + $this->assertFalse($buffer->write("bar\n")); + } + + /** + * @covers React\Stream\WritableResourceStream::write + */ + public function testWriteReturnsFalseWhenWritableResourceStreamIsExactlyFull() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $buffer = new WritableResourceStream($stream, $loop, 3); + + $this->assertFalse($buffer->write("foo")); + } + + /** + * @covers React\Stream\WritableResourceStream::write + * @covers React\Stream\WritableResourceStream::handleWrite + */ + public function testWriteDetectsWhenOtherSideIsClosed() + { + list($a, $b) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); + + $loop = $this->createWriteableLoopMock(); + + $buffer = new WritableResourceStream($a, $loop, 4); + $buffer->on('error', $this->expectCallableOnce()); + + fclose($b); + + $buffer->write("foo"); + } + + /** + * @covers React\Stream\WritableResourceStream::write + * @covers React\Stream\WritableResourceStream::handleWrite + */ + public function testEmitsDrainAfterWriteWhichExceedsBuffer() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $buffer = new WritableResourceStream($stream, $loop, 2); + $buffer->on('error', $this->expectCallableNever()); + $buffer->on('drain', $this->expectCallableOnce()); + + $buffer->write("foo"); + $buffer->handleWrite(); + } + + /** + * @covers React\Stream\WritableResourceStream::write + * @covers React\Stream\WritableResourceStream::handleWrite + */ + public function testWriteInDrain() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $buffer = new WritableResourceStream($stream, $loop, 2); + $buffer->on('error', $this->expectCallableNever()); + + $buffer->once('drain', function () use ($buffer) { + $buffer->write("bar\n"); + $buffer->handleWrite(); + }); + + $this->assertFalse($buffer->write("foo\n")); + $buffer->handleWrite(); + + fseek($stream, 0); + $this->assertSame("foo\nbar\n", stream_get_contents($stream)); + } + + /** + * @covers React\Stream\WritableResourceStream::write + * @covers React\Stream\WritableResourceStream::handleWrite + */ + public function testDrainAfterWrite() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $buffer = new WritableResourceStream($stream, $loop, 2); + + $buffer->on('drain', $this->expectCallableOnce()); + + $buffer->write("foo"); + $buffer->handleWrite(); + } + + /** + * @covers React\Stream\WritableResourceStream::handleWrite + */ + public function testDrainAfterWriteWillRemoveResourceFromLoopWithoutClosing() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $loop->expects($this->once())->method('removeWriteStream')->with($stream); + + $buffer = new WritableResourceStream($stream, $loop, 2); + + $buffer->on('drain', $this->expectCallableOnce()); + + $buffer->on('close', $this->expectCallableNever()); + + $buffer->write("foo"); + $buffer->handleWrite(); + } + + /** + * @covers React\Stream\WritableResourceStream::handleWrite + */ + public function testClosingDuringDrainAfterWriteWillRemoveResourceFromLoopOnceAndClose() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $loop->expects($this->once())->method('removeWriteStream')->with($stream); + + $buffer = new WritableResourceStream($stream, $loop, 2); + + $buffer->on('drain', function () use ($buffer) { + $buffer->close(); + }); + + $buffer->on('close', $this->expectCallableOnce()); + + $buffer->write("foo"); + $buffer->handleWrite(); + } + + /** + * @covers React\Stream\WritableResourceStream::end + */ + public function testEndWithoutDataClosesImmediatelyIfWritableResourceStreamIsEmpty() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $buffer = new WritableResourceStream($stream, $loop); + $buffer->on('error', $this->expectCallableNever()); + $buffer->on('close', $this->expectCallableOnce()); + + $this->assertTrue($buffer->isWritable()); + $buffer->end(); + $this->assertFalse($buffer->isWritable()); + } + + /** + * @covers React\Stream\WritableResourceStream::end + */ + public function testEndWithoutDataDoesNotCloseIfWritableResourceStreamIsFull() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $buffer = new WritableResourceStream($stream, $loop); + $buffer->on('error', $this->expectCallableNever()); + $buffer->on('close', $this->expectCallableNever()); + + $buffer->write('foo'); + + $this->assertTrue($buffer->isWritable()); + $buffer->end(); + $this->assertFalse($buffer->isWritable()); + } + + /** + * @covers React\Stream\WritableResourceStream::end + */ + public function testEndWithDataClosesImmediatelyIfWritableResourceStreamFlushes() + { + $stream = fopen('php://temp', 'r+'); + $filterBuffer = ''; + $loop = $this->createLoopMock(); + + $buffer = new WritableResourceStream($stream, $loop); + $buffer->on('error', $this->expectCallableNever()); + $buffer->on('close', $this->expectCallableOnce()); + + Filter\append($stream, function ($chunk) use (&$filterBuffer) { + $filterBuffer .= $chunk; + return $chunk; + }); + + $this->assertTrue($buffer->isWritable()); + $buffer->end('final words'); + $this->assertFalse($buffer->isWritable()); + + $buffer->handleWrite(); + $this->assertSame('final words', $filterBuffer); + } + + /** + * @covers React\Stream\WritableResourceStream::end + */ + public function testEndWithDataDoesNotCloseImmediatelyIfWritableResourceStreamIsFull() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $buffer = new WritableResourceStream($stream, $loop); + $buffer->on('error', $this->expectCallableNever()); + $buffer->on('close', $this->expectCallableNever()); + + $buffer->write('foo'); + + $this->assertTrue($buffer->isWritable()); + $buffer->end('final words'); + $this->assertFalse($buffer->isWritable()); + + rewind($stream); + $this->assertSame('', stream_get_contents($stream)); + } + + /** + * @covers React\Stream\WritableResourceStream::isWritable + * @covers React\Stream\WritableResourceStream::close + */ + public function testClose() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $buffer = new WritableResourceStream($stream, $loop); + $buffer->on('error', $this->expectCallableNever()); + $buffer->on('close', $this->expectCallableOnce()); + + $this->assertTrue($buffer->isWritable()); + $buffer->close(); + $this->assertFalse($buffer->isWritable()); + + $this->assertEquals(array(), $buffer->listeners('close')); + } + + /** + * @covers React\Stream\WritableResourceStream::close + */ + public function testClosingAfterWriteRemovesStreamFromLoop() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $buffer = new WritableResourceStream($stream, $loop); + + $loop->expects($this->once())->method('removeWriteStream')->with($stream); + + $buffer->write('foo'); + $buffer->close(); + } + + /** + * @covers React\Stream\WritableResourceStream::close + */ + public function testClosingWithoutWritingDoesNotRemoveStreamFromLoop() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $buffer = new WritableResourceStream($stream, $loop); + + $loop->expects($this->never())->method('removeWriteStream'); + + $buffer->close(); + } + + /** + * @covers React\Stream\WritableResourceStream::close + */ + public function testDoubleCloseWillEmitOnlyOnce() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $buffer = new WritableResourceStream($stream, $loop); + $buffer->on('close', $this->expectCallableOnce()); + + $buffer->close(); + $buffer->close(); + } + + /** + * @covers React\Stream\WritableResourceStream::write + * @covers React\Stream\WritableResourceStream::close + */ + public function testWritingToClosedWritableResourceStreamShouldNotWriteToStream() + { + $stream = fopen('php://temp', 'r+'); + $filterBuffer = ''; + $loop = $this->createLoopMock(); + + $buffer = new WritableResourceStream($stream, $loop); + + Filter\append($stream, function ($chunk) use (&$filterBuffer) { + $filterBuffer .= $chunk; + return $chunk; + }); + + $buffer->close(); + + $buffer->write('foo'); + + $buffer->handleWrite(); + $this->assertSame('', $filterBuffer); + } + + /** + * @covers React\Stream\WritableResourceStream::handleWrite + */ + public function testErrorWhenStreamResourceIsInvalid() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createWriteableLoopMock(); + + $error = null; + + $buffer = new WritableResourceStream($stream, $loop); + $buffer->on('error', function ($message) use (&$error) { + $error = $message; + }); + + // invalidate stream resource + fclose($stream); + + $buffer->write('Attempting to write to bad stream'); + + $this->assertInstanceOf('Exception', $error); + + // the error messages differ between PHP versions, let's just check substrings + $this->assertContains('Unable to write to stream: ', $error->getMessage()); + $this->assertContains(' not a valid stream resource', $error->getMessage(), '', true); + } + + public function testWritingToClosedStream() + { + if ('Darwin' === PHP_OS) { + $this->markTestSkipped('OS X issue with shutting down pair for writing'); + } + + list($a, $b) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); + $loop = $this->createLoopMock(); + + $error = null; + + $buffer = new WritableResourceStream($a, $loop); + $buffer->on('error', function($message) use (&$error) { + $error = $message; + }); + + $buffer->write('foo'); + $buffer->handleWrite(); + stream_socket_shutdown($b, STREAM_SHUT_RD); + stream_socket_shutdown($a, STREAM_SHUT_RD); + $buffer->write('bar'); + $buffer->handleWrite(); + + $this->assertInstanceOf('Exception', $error); + $this->assertSame('Unable to write to stream: fwrite(): send of 3 bytes failed with errno=32 Broken pipe', $error->getMessage()); + } + + private function createWriteableLoopMock() + { + $loop = $this->createLoopMock(); + $loop->preventWrites = false; + $loop + ->expects($this->any()) + ->method('addWriteStream') + ->will($this->returnCallback(function ($stream, $listener) use ($loop) { + if (!$loop->preventWrites) { + call_user_func($listener, $stream); + } + })); + + return $loop; + } + + private function createLoopMock() + { + return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + } +} |