This commit is contained in:
2022-10-23 01:39:27 +02:00
parent 8c17aab483
commit 1929b84685
4130 changed files with 479334 additions and 0 deletions

10
vendor/react/stream/tests/CallableStub.php vendored Executable file
View File

@@ -0,0 +1,10 @@
<?php
namespace React\Tests\Stream;
class CallableStub
{
public function __invoke()
{
}
}

View File

@@ -0,0 +1,267 @@
<?php
namespace React\Tests\Stream;
use React\Stream\CompositeStream;
use React\Stream\ThroughStream;
/**
* @covers React\Stream\CompositeStream
*/
class CompositeStreamTest extends TestCase
{
/** @test */
public function itShouldCloseReadableIfNotWritable()
{
$readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
$readable
->expects($this->once())
->method('isReadable')
->willReturn(true);
$readable
->expects($this->once())
->method('close');
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
$writable
->expects($this->once())
->method('isWritable')
->willReturn(false);
$composite = new CompositeStream($readable, $writable);
$composite->on('close', $this->expectCallableNever());
$composite->close();
}
/** @test */
public function itShouldCloseWritableIfNotReadable()
{
$readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
$readable
->expects($this->once())
->method('isReadable')
->willReturn(false);
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
$writable
->expects($this->once())
->method('close');
$composite = new CompositeStream($readable, $writable);
$composite->on('close', $this->expectCallableNever());
$composite->close();
}
/** @test */
public function itShouldForwardWritableCallsToWritableStream()
{
$readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
$readable
->expects($this->once())
->method('isReadable')
->willReturn(true);
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
$writable
->expects($this->once())
->method('write')
->with('foo');
$writable
->expects($this->exactly(2))
->method('isWritable')
->willReturn(true);
$composite = new CompositeStream($readable, $writable);
$composite->write('foo');
$composite->isWritable();
}
/** @test */
public function itShouldForwardReadableCallsToReadableStream()
{
$readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
$readable
->expects($this->exactly(2))
->method('isReadable')
->willReturn(true);
$readable
->expects($this->once())
->method('pause');
$readable
->expects($this->once())
->method('resume');
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
$writable
->expects($this->any())
->method('isWritable')
->willReturn(true);
$composite = new CompositeStream($readable, $writable);
$composite->isReadable();
$composite->pause();
$composite->resume();
}
/** @test */
public function itShouldNotForwardResumeIfStreamIsNotWritable()
{
$readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
$readable
->expects($this->once())
->method('isReadable')
->willReturn(true);
$readable
->expects($this->never())
->method('resume');
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
$writable
->expects($this->exactly(2))
->method('isWritable')
->willReturnOnConsecutiveCalls(true, false);
$composite = new CompositeStream($readable, $writable);
$composite->resume();
}
/** @test */
public function endShouldDelegateToWritableWithData()
{
$readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
$readable
->expects($this->once())
->method('isReadable')
->willReturn(true);
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
$writable
->expects($this->once())
->method('isWritable')
->willReturn(true);
$writable
->expects($this->once())
->method('end')
->with('foo');
$composite = new CompositeStream($readable, $writable);
$composite->end('foo');
}
/** @test */
public function closeShouldCloseBothStreams()
{
$readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
$readable
->expects($this->once())
->method('isReadable')
->willReturn(true);
$readable
->expects($this->once())
->method('close');
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
$writable
->expects($this->once())
->method('isWritable')
->willReturn(true);
$writable
->expects($this->once())
->method('close');
$composite = new CompositeStream($readable, $writable);
$composite->close();
}
/** @test */
public function itShouldForwardCloseOnlyOnce()
{
$readable = new ThroughStream();
$writable = new ThroughStream();
$composite = new CompositeStream($readable, $writable);
$composite->on('close', $this->expectCallableOnce());
$readable->close();
$writable->close();
}
/** @test */
public function itShouldForwardCloseAndRemoveAllListeners()
{
$in = new ThroughStream();
$composite = new CompositeStream($in, $in);
$composite->on('close', $this->expectCallableOnce());
$this->assertTrue($composite->isReadable());
$this->assertTrue($composite->isWritable());
$this->assertCount(1, $composite->listeners('close'));
$composite->close();
$this->assertFalse($composite->isReadable());
$this->assertFalse($composite->isWritable());
$this->assertCount(0, $composite->listeners('close'));
}
/** @test */
public function itShouldReceiveForwardedEvents()
{
$readable = new ThroughStream();
$writable = new ThroughStream();
$composite = new CompositeStream($readable, $writable);
$composite->on('data', $this->expectCallableOnce());
$composite->on('drain', $this->expectCallableOnce());
$readable->emit('data', array('foo'));
$writable->emit('drain');
}
/** @test */
public function itShouldHandlePipingCorrectly()
{
$readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
$readable
->expects($this->once())
->method('isReadable')
->willReturn(true);
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
$writable->expects($this->any())->method('isWritable')->willReturn(True);
$writable
->expects($this->once())
->method('write')
->with('foo');
$composite = new CompositeStream($readable, $writable);
$input = new ThroughStream();
$input->pipe($composite);
$input->emit('data', array('foo'));
}
/** @test */
public function itShouldForwardPipeCallsToReadableStream()
{
$readable = new ThroughStream();
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
$writable->expects($this->any())->method('isWritable')->willReturn(True);
$composite = new CompositeStream($readable, $writable);
$output = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
$output->expects($this->any())->method('isWritable')->willReturn(True);
$output
->expects($this->once())
->method('write')
->with('foo');
$composite->pipe($output);
$readable->emit('data', array('foo'));
}
}

View File

@@ -0,0 +1,390 @@
<?php
namespace React\Tests\Stream;
use Clue\StreamFilter as Filter;
use React\Stream\DuplexResourceStream;
use React\Stream\ReadableResourceStream;
use React\EventLoop\ExtEventLoop;
use React\EventLoop\ExtLibeventLoop;
use React\EventLoop\ExtLibevLoop;
use React\EventLoop\LoopInterface;
use React\EventLoop\LibEventLoop;
use React\EventLoop\LibEvLoop;
use React\EventLoop\StreamSelectLoop;
class DuplexResourceStreamIntegrationTest extends TestCase
{
public function loopProvider()
{
return array(
array(
function() {
return true;
},
function () {
return new StreamSelectLoop();
}
),
array(
function () {
return function_exists('event_base_new');
},
function () {
return class_exists('React\EventLoop\ExtLibeventLoop') ? new ExtLibeventLoop() : new LibEventLoop();
}
),
array(
function () {
return class_exists('libev\EventLoop');
},
function () {
return class_exists('React\EventLoop\ExtLibevLoop') ? new ExtLibevLoop() : new LibEvLoop();
}
),
array(
function () {
return class_exists('EventBase') && class_exists('React\EventLoop\ExtEventLoop');
},
function () {
return new ExtEventLoop();
}
)
);
}
/**
* @dataProvider loopProvider
*/
public function testBufferReadsLargeChunks($condition, $loopFactory)
{
if (true !== $condition()) {
return $this->markTestSkipped('Loop implementation not available');
}
$loop = $loopFactory();
list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
$bufferSize = 4096;
$streamA = new DuplexResourceStream($sockA, $loop, $bufferSize);
$streamB = new DuplexResourceStream($sockB, $loop, $bufferSize);
$testString = str_repeat("*", $bufferSize + 1);
$buffer = "";
$streamB->on('data', function ($data) use (&$buffer) {
$buffer .= $data;
});
$streamA->write($testString);
$this->loopTick($loop);
$this->loopTick($loop);
$this->loopTick($loop);
$streamA->close();
$streamB->close();
$this->assertEquals($testString, $buffer);
}
/**
* @dataProvider loopProvider
*/
public function testWriteLargeChunk($condition, $loopFactory)
{
if (true !== $condition()) {
return $this->markTestSkipped('Loop implementation not available');
}
$loop = $loopFactory();
list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
$streamA = new DuplexResourceStream($sockA, $loop);
$streamB = new DuplexResourceStream($sockB, $loop);
// limit seems to be 192 KiB
$size = 256 * 1024;
// sending side sends and expects clean close with no errors
$streamA->end(str_repeat('*', $size));
$streamA->on('close', $this->expectCallableOnce());
$streamA->on('error', $this->expectCallableNever());
// receiving side counts bytes and expects clean close with no errors
$received = 0;
$streamB->on('data', function ($chunk) use (&$received) {
$received += strlen($chunk);
});
$streamB->on('close', $this->expectCallableOnce());
$streamB->on('error', $this->expectCallableNever());
$loop->run();
$streamA->close();
$streamB->close();
$this->assertEquals($size, $received);
}
/**
* @dataProvider loopProvider
*/
public function testDoesNotEmitDataIfNothingHasBeenWritten($condition, $loopFactory)
{
if (true !== $condition()) {
return $this->markTestSkipped('Loop implementation not available');
}
$loop = $loopFactory();
list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
$streamA = new DuplexResourceStream($sockA, $loop);
$streamB = new DuplexResourceStream($sockB, $loop);
// end streamA without writing any data
$streamA->end();
// streamB should not emit any data
$streamB->on('data', $this->expectCallableNever());
$loop->run();
$streamA->close();
$streamB->close();
}
/**
* @dataProvider loopProvider
*/
public function testDoesNotWriteDataIfRemoteSideFromPairHasBeenClosed($condition, $loopFactory)
{
if (true !== $condition()) {
return $this->markTestSkipped('Loop implementation not available');
}
$loop = $loopFactory();
list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
$streamA = new DuplexResourceStream($sockA, $loop);
$streamB = new DuplexResourceStream($sockB, $loop);
// end streamA without writing any data
$streamA->pause();
$streamA->write('hello');
$streamA->on('close', $this->expectCallableOnce());
$streamB->on('data', $this->expectCallableNever());
$streamB->close();
$loop->run();
$streamA->close();
$streamB->close();
}
/**
* @dataProvider loopProvider
*/
public function testDoesNotWriteDataIfServerSideHasBeenClosed($condition, $loopFactory)
{
if (true !== $condition()) {
return $this->markTestSkipped('Loop implementation not available');
}
$loop = $loopFactory();
$server = stream_socket_server('tcp://127.0.0.1:0');
$client = stream_socket_client(stream_socket_get_name($server, false));
$peer = stream_socket_accept($server);
$streamA = new DuplexResourceStream($client, $loop);
$streamB = new DuplexResourceStream($peer, $loop);
// end streamA without writing any data
$streamA->pause();
$streamA->write('hello');
$streamA->on('close', $this->expectCallableOnce());
$streamB->on('data', $this->expectCallableNever());
$streamB->close();
$loop->run();
$streamA->close();
$streamB->close();
}
/**
* @dataProvider loopProvider
*/
public function testDoesNotWriteDataIfClientSideHasBeenClosed($condition, $loopFactory)
{
if (true !== $condition()) {
return $this->markTestSkipped('Loop implementation not available');
}
$loop = $loopFactory();
$server = stream_socket_server('tcp://127.0.0.1:0');
$client = stream_socket_client(stream_socket_get_name($server, false));
$peer = stream_socket_accept($server);
$streamA = new DuplexResourceStream($peer, $loop);
$streamB = new DuplexResourceStream($client, $loop);
// end streamA without writing any data
$streamA->pause();
$streamA->write('hello');
$streamA->on('close', $this->expectCallableOnce());
$streamB->on('data', $this->expectCallableNever());
$streamB->close();
$loop->run();
$streamA->close();
$streamB->close();
}
/**
* @dataProvider loopProvider
*/
public function testReadsSingleChunkFromProcessPipe($condition, $loopFactory)
{
if (true !== $condition()) {
return $this->markTestSkipped('Loop implementation not available');
}
$loop = $loopFactory();
$stream = new ReadableResourceStream(popen('echo test', 'r'), $loop);
$stream->on('data', $this->expectCallableOnceWith("test\n"));
$stream->on('end', $this->expectCallableOnce());
$stream->on('error', $this->expectCallableNever());
$loop->run();
}
/**
* @dataProvider loopProvider
*/
public function testReadsMultipleChunksFromProcessPipe($condition, $loopFactory)
{
if (true !== $condition()) {
return $this->markTestSkipped('Loop implementation not available');
}
$loop = $loopFactory();
$stream = new ReadableResourceStream(popen('echo a;sleep 0.1;echo b;sleep 0.1;echo c', 'r'), $loop);
$buffer = '';
$stream->on('data', function ($chunk) use (&$buffer) {
$buffer .= $chunk;
});
$stream->on('end', $this->expectCallableOnce());
$stream->on('error', $this->expectCallableNever());
$loop->run();
$this->assertEquals("a\n" . "b\n" . "c\n", $buffer);
}
/**
* @dataProvider loopProvider
*/
public function testReadsLongChunksFromProcessPipe($condition, $loopFactory)
{
if (true !== $condition()) {
return $this->markTestSkipped('Loop implementation not available');
}
$loop = $loopFactory();
$stream = new ReadableResourceStream(popen('dd if=/dev/zero bs=12345 count=1234 2>&-', 'r'), $loop);
$bytes = 0;
$stream->on('data', function ($chunk) use (&$bytes) {
$bytes += strlen($chunk);
});
$stream->on('end', $this->expectCallableOnce());
$stream->on('error', $this->expectCallableNever());
$loop->run();
$this->assertEquals(12345 * 1234, $bytes);
}
/**
* @dataProvider loopProvider
*/
public function testReadsNothingFromProcessPipeWithNoOutput($condition, $loopFactory)
{
if (true !== $condition()) {
return $this->markTestSkipped('Loop implementation not available');
}
$loop = $loopFactory();
$stream = new ReadableResourceStream(popen('true', 'r'), $loop);
$stream->on('data', $this->expectCallableNever());
$stream->on('end', $this->expectCallableOnce());
$stream->on('error', $this->expectCallableNever());
$loop->run();
}
/**
* @covers React\Stream\ReadableResourceStream::handleData
* @dataProvider loopProvider
*/
public function testEmptyReadShouldntFcloseStream($condition, $loopFactory)
{
if (true !== $condition()) {
return $this->markTestSkipped('Loop implementation not available');
}
$server = stream_socket_server('tcp://127.0.0.1:0');
$client = stream_socket_client(stream_socket_get_name($server, false));
$stream = stream_socket_accept($server);
// add a filter which returns an error when encountering an 'a' when reading
Filter\append($stream, function ($chunk) {
return '';
}, STREAM_FILTER_READ);
$loop = $loopFactory();
$conn = new DuplexResourceStream($stream, $loop);
$conn->on('error', $this->expectCallableNever());
$conn->on('data', $this->expectCallableNever());
$conn->on('end', $this->expectCallableNever());
fwrite($client, "foobar\n");
$conn->handleData($stream);
fclose($stream);
fclose($client);
fclose($server);
}
private function loopTick(LoopInterface $loop)
{
$loop->addTimer(0, function () use ($loop) {
$loop->stop();
});
$loop->run();
}
}

View File

@@ -0,0 +1,495 @@
<?php
namespace React\Tests\Stream;
use React\Stream\DuplexResourceStream;
use Clue\StreamFilter as Filter;
use React\Stream\WritableResourceStream;
class DuplexResourceStreamTest extends TestCase
{
/**
* @covers React\Stream\DuplexResourceStream::__construct
* @doesNotPerformAssertions
*/
public function testConstructor()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
new DuplexResourceStream($stream, $loop);
}
/**
* @covers React\Stream\DuplexResourceStream::__construct
* @doesNotPerformAssertions
*/
public function testConstructorWithExcessiveMode()
{
// excessive flags are ignored for temp streams, so we have to use a file stream
$name = tempnam(sys_get_temp_dir(), 'test');
$stream = @fopen($name, 'r+eANYTHING');
unlink($name);
$loop = $this->createLoopMock();
$buffer = new DuplexResourceStream($stream, $loop);
$buffer->close();
}
/**
* @covers React\Stream\DuplexResourceStream::__construct
* @expectedException InvalidArgumentException
*/
public function testConstructorThrowsExceptionOnInvalidStream()
{
$loop = $this->createLoopMock();
new DuplexResourceStream('breakme', $loop);
}
/**
* @covers React\Stream\DuplexResourceStream::__construct
* @expectedException InvalidArgumentException
*/
public function testConstructorThrowsExceptionOnWriteOnlyStream()
{
if (defined('HHVM_VERSION')) {
$this->markTestSkipped('HHVM does not report fopen mode for STDOUT');
}
$loop = $this->createLoopMock();
new DuplexResourceStream(STDOUT, $loop);
}
/**
* @covers React\Stream\DuplexResourceStream::__construct
* @expectedException InvalidArgumentException
*/
public function testConstructorThrowsExceptionOnWriteOnlyStreamWithExcessiveMode()
{
// excessive flags are ignored for temp streams, so we have to use a file stream
$name = tempnam(sys_get_temp_dir(), 'test');
$stream = fopen($name, 'weANYTHING');
unlink($name);
$loop = $this->createLoopMock();
new DuplexResourceStream($stream, $loop);
}
/**
* @covers React\Stream\DuplexResourceStream::__construct
* @expectedException RunTimeException
*/
public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking()
{
if (!in_array('blocking', stream_get_wrappers())) {
stream_wrapper_register('blocking', 'React\Tests\Stream\EnforceBlockingWrapper');
}
$stream = fopen('blocking://test', 'r+');
$loop = $this->createLoopMock();
new DuplexResourceStream($stream, $loop);
}
/**
* @covers React\Stream\DuplexResourceStream::__construct
* @doesNotPerformAssertions
*/
public function testConstructorAcceptsBuffer()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$buffer = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
$conn = new DuplexResourceStream($stream, $loop, null, $buffer);
}
public function testCloseShouldEmitCloseEvent()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$conn = new DuplexResourceStream($stream, $loop);
$conn->on('close', $this->expectCallableOnce());
$conn->on('end', $this->expectCallableNever());
$conn->close();
$this->assertFalse($conn->isReadable());
}
public function testEndShouldEndBuffer()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$buffer = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
$buffer->expects($this->once())->method('end')->with('foo');
$conn = new DuplexResourceStream($stream, $loop, null, $buffer);
$conn->end('foo');
}
public function testEndAfterCloseIsNoOp()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$buffer = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
$buffer->expects($this->never())->method('end');
$conn = new DuplexResourceStream($stream, $loop);
$conn->close();
$conn->end();
}
/**
* @covers React\Stream\DuplexResourceStream::__construct
* @covers React\Stream\DuplexResourceStream::handleData
*/
public function testDataEvent()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$capturedData = null;
$conn = new DuplexResourceStream($stream, $loop);
$conn->on('data', function ($data) use (&$capturedData) {
$capturedData = $data;
});
fwrite($stream, "foobar\n");
rewind($stream);
$conn->handleData($stream);
$this->assertSame("foobar\n", $capturedData);
}
/**
* @covers React\Stream\DuplexResourceStream::__construct
* @covers React\Stream\DuplexResourceStream::handleData
*/
public function testDataEventDoesEmitOneChunkMatchingBufferSize()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$capturedData = null;
$conn = new DuplexResourceStream($stream, $loop, 4321);
$conn->on('data', function ($data) use (&$capturedData) {
$capturedData = $data;
});
fwrite($stream, str_repeat("a", 100000));
rewind($stream);
$conn->handleData($stream);
$this->assertTrue($conn->isReadable());
$this->assertEquals(4321, strlen($capturedData));
}
/**
* @covers React\Stream\DuplexResourceStream::__construct
* @covers React\Stream\DuplexResourceStream::handleData
*/
public function testDataEventDoesEmitOneChunkUntilStreamEndsWhenBufferSizeIsInfinite()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$capturedData = null;
$conn = new DuplexResourceStream($stream, $loop, -1);
$conn->on('data', function ($data) use (&$capturedData) {
$capturedData = $data;
});
fwrite($stream, str_repeat("a", 100000));
rewind($stream);
$conn->handleData($stream);
$this->assertTrue($conn->isReadable());
$this->assertEquals(100000, strlen($capturedData));
}
/**
* @covers React\Stream\DuplexResourceStream::handleData
*/
public function testEmptyStreamShouldNotEmitData()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$conn = new DuplexResourceStream($stream, $loop);
$conn->on('data', $this->expectCallableNever());
$conn->handleData($stream);
}
/**
* @covers React\Stream\DuplexResourceStream::write
*/
public function testWrite()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createWriteableLoopMock();
$conn = new DuplexResourceStream($stream, $loop);
$conn->write("foo\n");
rewind($stream);
$this->assertSame("foo\n", fgets($stream));
}
/**
* @covers React\Stream\DuplexResourceStream::end
* @covers React\Stream\DuplexResourceStream::isReadable
* @covers React\Stream\DuplexResourceStream::isWritable
*/
public function testEnd()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$conn = new DuplexResourceStream($stream, $loop);
$conn->end();
$this->assertFalse(is_resource($stream));
$this->assertFalse($conn->isReadable());
$this->assertFalse($conn->isWritable());
}
/**
* @covers React\Stream\DuplexResourceStream::end
*/
public function testEndRemovesReadStreamFromLoop()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$loop->expects($this->once())->method('addReadStream')->with($stream);
$loop->expects($this->once())->method('removeReadStream')->with($stream);
$conn = new DuplexResourceStream($stream, $loop);
$conn->end('bye');
}
/**
* @covers React\Stream\DuplexResourceStream::pause
*/
public function testPauseRemovesReadStreamFromLoop()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$loop->expects($this->once())->method('addReadStream')->with($stream);
$loop->expects($this->once())->method('removeReadStream')->with($stream);
$conn = new DuplexResourceStream($stream, $loop);
$conn->pause();
$conn->pause();
}
/**
* @covers React\Stream\DuplexResourceStream::pause
*/
public function testResumeDoesAddStreamToLoopOnlyOnce()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$loop->expects($this->once())->method('addReadStream')->with($stream);
$conn = new DuplexResourceStream($stream, $loop);
$conn->resume();
$conn->resume();
}
/**
* @covers React\Stream\DuplexResourceStream::close
*/
public function testCloseRemovesReadStreamFromLoop()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$loop->expects($this->once())->method('addReadStream')->with($stream);
$loop->expects($this->once())->method('removeReadStream')->with($stream);
$conn = new DuplexResourceStream($stream, $loop);
$conn->close();
}
/**
* @covers React\Stream\DuplexResourceStream::close
*/
public function testCloseAfterPauseRemovesReadStreamFromLoopOnlyOnce()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$loop->expects($this->once())->method('addReadStream')->with($stream);
$loop->expects($this->once())->method('removeReadStream')->with($stream);
$conn = new DuplexResourceStream($stream, $loop);
$conn->pause();
$conn->close();
}
/**
* @covers React\Stream\DuplexResourceStream::close
*/
public function testResumeAfterCloseDoesAddReadStreamToLoopOnlyOnce()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$loop->expects($this->once())->method('addReadStream')->with($stream);
$conn = new DuplexResourceStream($stream, $loop);
$conn->close();
$conn->resume();
}
public function testEndedStreamsShouldNotWrite()
{
$file = tempnam(sys_get_temp_dir(), 'reactphptest_');
$stream = fopen($file, 'r+');
$loop = $this->createWriteableLoopMock();
$conn = new DuplexResourceStream($stream, $loop);
$conn->write("foo\n");
$conn->end();
$res = $conn->write("bar\n");
$stream = fopen($file, 'r');
$this->assertSame("foo\n", fgets($stream));
$this->assertFalse($res);
unlink($file);
}
public function testPipeShouldReturnDestination()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$conn = new DuplexResourceStream($stream, $loop);
$dest = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
$this->assertSame($dest, $conn->pipe($dest));
}
public function testBufferEventsShouldBubbleUp()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$buffer = new WritableResourceStream($stream, $loop);
$conn = new DuplexResourceStream($stream, $loop, null, $buffer);
$conn->on('drain', $this->expectCallableOnce());
$conn->on('error', $this->expectCallableOnce());
$buffer->emit('drain');
$buffer->emit('error', array(new \RuntimeException('Whoops')));
}
/**
* @covers React\Stream\DuplexResourceStream::handleData
*/
public function testClosingStreamInDataEventShouldNotTriggerError()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$conn = new DuplexResourceStream($stream, $loop);
$conn->on('error', $this->expectCallableNever());
$conn->on('data', function ($data) use ($conn) {
$conn->close();
});
fwrite($stream, "foobar\n");
rewind($stream);
$conn->handleData($stream);
}
/**
* @covers React\Stream\DuplexResourceStream::handleData
*/
public function testDataFiltered()
{
$stream = fopen('php://temp', 'r+');
// add a filter which removes every 'a' when reading
Filter\append($stream, function ($chunk) {
return str_replace('a', '', $chunk);
}, STREAM_FILTER_READ);
$loop = $this->createLoopMock();
$capturedData = null;
$conn = new DuplexResourceStream($stream, $loop);
$conn->on('data', function ($data) use (&$capturedData) {
$capturedData = $data;
});
fwrite($stream, "foobar\n");
rewind($stream);
$conn->handleData($stream);
$this->assertSame("foobr\n", $capturedData);
}
/**
* @covers React\Stream\DuplexResourceStream::handleData
*/
public function testDataErrorShouldEmitErrorAndClose()
{
$stream = fopen('php://temp', 'r+');
// add a filter which returns an error when encountering an 'a' when reading
Filter\append($stream, function ($chunk) {
if (strpos($chunk, 'a') !== false) {
throw new \Exception('Invalid');
}
return $chunk;
}, STREAM_FILTER_READ);
$loop = $this->createLoopMock();
$conn = new DuplexResourceStream($stream, $loop);
$conn->on('data', $this->expectCallableNever());
$conn->on('error', $this->expectCallableOnce());
$conn->on('close', $this->expectCallableOnce());
fwrite($stream, "foobar\n");
rewind($stream);
$conn->handleData($stream);
}
private function createWriteableLoopMock()
{
$loop = $this->createLoopMock();
$loop
->expects($this->once())
->method('addWriteStream')
->will($this->returnCallback(function ($stream, $listener) {
call_user_func($listener, $stream);
}));
return $loop;
}
private function createLoopMock()
{
return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace React\Tests\Stream;
/**
* Used to test dummy stream resources that do not support setting non-blocking mode
*
* @link http://php.net/manual/de/class.streamwrapper.php
*/
class EnforceBlockingWrapper
{
public function stream_open($path, $mode, $options, &$opened_path)
{
return true;
}
public function stream_cast($cast_as)
{
return false;
}
public function stream_eof()
{
return false;
}
public function stream_set_option($option, $arg1, $arg2)
{
if ($option === STREAM_OPTION_BLOCKING) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,122 @@
<?php
namespace React\Tests\Stream;
use React\EventLoop\Factory;
use React\EventLoop\LoopInterface;
use React\Stream\DuplexResourceStream;
use React\Stream\WritableResourceStream;
/**
* @group internet
*/
class FunctionalInternetTest extends TestCase
{
public function testUploadKilobytePlain()
{
$size = 1000;
$stream = stream_socket_client('tcp://httpbin.org:80');
$loop = Factory::create();
$stream = new DuplexResourceStream($stream, $loop);
$buffer = '';
$stream->on('data', function ($chunk) use (&$buffer) {
$buffer .= $chunk;
});
$stream->on('error', $this->expectCallableNever());
$stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size));
$this->awaitStreamClose($stream, $loop);
$this->assertNotEquals('', $buffer);
}
public function testUploadBiggerBlockPlain()
{
$size = 50 * 1000;
$stream = stream_socket_client('tcp://httpbin.org:80');
$loop = Factory::create();
$stream = new DuplexResourceStream($stream, $loop);
$buffer = '';
$stream->on('data', function ($chunk) use (&$buffer) {
$buffer .= $chunk;
});
$stream->on('error', $this->expectCallableNever());
$stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size));
$this->awaitStreamClose($stream, $loop);
$this->assertNotEquals('', $buffer);
}
public function testUploadKilobyteSecure()
{
$size = 1000;
$stream = stream_socket_client('tls://httpbin.org:443');
$loop = Factory::create();
$stream = new DuplexResourceStream($stream, $loop);
$buffer = '';
$stream->on('data', function ($chunk) use (&$buffer) {
$buffer .= $chunk;
});
$stream->on('error', $this->expectCallableNever());
$stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size));
$this->awaitStreamClose($stream, $loop);
$this->assertNotEquals('', $buffer);
}
public function testUploadBiggerBlockSecureRequiresSmallerChunkSize()
{
$size = 50 * 1000;
$stream = stream_socket_client('tls://httpbin.org:443');
$loop = Factory::create();
$stream = new DuplexResourceStream(
$stream,
$loop,
null,
new WritableResourceStream($stream, $loop, null, 8192)
);
$buffer = '';
$stream->on('data', function ($chunk) use (&$buffer) {
$buffer .= $chunk;
});
$stream->on('error', $this->expectCallableNever());
$stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size));
$this->awaitStreamClose($stream, $loop);
$this->assertNotEquals('', $buffer);
}
private function awaitStreamClose(DuplexResourceStream $stream, LoopInterface $loop, $timeout = 10.0)
{
$stream->on('close', function () use ($loop) {
$loop->stop();
});
$that = $this;
$loop->addTimer($timeout, function () use ($loop, $that) {
$loop->stop();
$that->fail('Timed out while waiting for stream to close');
});
$loop->run();
}
}

View File

@@ -0,0 +1,391 @@
<?php
namespace React\Tests\Stream;
use React\Stream\ReadableResourceStream;
use Clue\StreamFilter as Filter;
class ReadableResourceStreamTest extends TestCase
{
/**
* @covers React\Stream\ReadableResourceStream::__construct
* @doesNotPerformAssertions
*/
public function testConstructor()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
new ReadableResourceStream($stream, $loop);
}
/**
* @covers React\Stream\ReadableResourceStream::__construct
* @doesNotPerformAssertions
*/
public function testConstructorWithExcessiveMode()
{
// excessive flags are ignored for temp streams, so we have to use a file stream
$name = tempnam(sys_get_temp_dir(), 'test');
$stream = @fopen($name, 'r+eANYTHING');
unlink($name);
$loop = $this->createLoopMock();
$buffer = new ReadableResourceStream($stream, $loop);
$buffer->close();
}
/**
* @covers React\Stream\ReadableResourceStream::__construct
* @expectedException InvalidArgumentException
*/
public function testConstructorThrowsExceptionOnInvalidStream()
{
$loop = $this->createLoopMock();
new ReadableResourceStream(false, $loop);
}
/**
* @covers React\Stream\ReadableResourceStream::__construct
* @expectedException InvalidArgumentException
*/
public function testConstructorThrowsExceptionOnWriteOnlyStream()
{
if (defined('HHVM_VERSION')) {
$this->markTestSkipped('HHVM does not report fopen mode for STDOUT');
}
$loop = $this->createLoopMock();
new ReadableResourceStream(STDOUT, $loop);
}
/**
* @covers React\Stream\ReadableResourceStream::__construct
* @expectedException InvalidArgumentException
*/
public function testConstructorThrowsExceptionOnWriteOnlyStreamWithExcessiveMode()
{
// excessive flags are ignored for temp streams, so we have to use a file stream
$name = tempnam(sys_get_temp_dir(), 'test');
$stream = fopen($name, 'weANYTHING');
unlink($name);
$loop = $this->createLoopMock();
new ReadableResourceStream($stream, $loop);
}
/**
* @covers React\Stream\ReadableResourceStream::__construct
* @expectedException RuntimeException
*/
public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking()
{
if (!in_array('blocking', stream_get_wrappers())) {
stream_wrapper_register('blocking', 'React\Tests\Stream\EnforceBlockingWrapper');
}
$stream = fopen('blocking://test', 'r+');
$loop = $this->createLoopMock();
new ReadableResourceStream($stream, $loop);
}
public function testCloseShouldEmitCloseEvent()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$conn = new ReadableResourceStream($stream, $loop);
$conn->on('close', $this->expectCallableOnce());
$conn->close();
$this->assertFalse($conn->isReadable());
}
public function testCloseTwiceShouldEmitCloseEventOnce()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$conn = new ReadableResourceStream($stream, $loop);
$conn->on('close', $this->expectCallableOnce());
$conn->close();
$conn->close();
}
/**
* @covers React\Stream\ReadableResourceStream::__construct
* @covers React\Stream\ReadableResourceStream::handleData
*/
public function testDataEvent()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$capturedData = null;
$conn = new ReadableResourceStream($stream, $loop);
$conn->on('data', function ($data) use (&$capturedData) {
$capturedData = $data;
});
fwrite($stream, "foobar\n");
rewind($stream);
$conn->handleData($stream);
$this->assertSame("foobar\n", $capturedData);
}
/**
* @covers React\Stream\ReadableResourceStream::__construct
* @covers React\Stream\ReadableResourceStream::handleData
*/
public function testDataEventDoesEmitOneChunkMatchingBufferSize()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$capturedData = null;
$conn = new ReadableResourceStream($stream, $loop, 4321);
$conn->on('data', function ($data) use (&$capturedData) {
$capturedData = $data;
});
fwrite($stream, str_repeat("a", 100000));
rewind($stream);
$conn->handleData($stream);
$this->assertTrue($conn->isReadable());
$this->assertEquals(4321, strlen($capturedData));
}
/**
* @covers React\Stream\ReadableResourceStream::__construct
* @covers React\Stream\ReadableResourceStream::handleData
*/
public function testDataEventDoesEmitOneChunkUntilStreamEndsWhenBufferSizeIsInfinite()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$capturedData = null;
$conn = new ReadableResourceStream($stream, $loop, -1);
$conn->on('data', function ($data) use (&$capturedData) {
$capturedData = $data;
});
fwrite($stream, str_repeat("a", 100000));
rewind($stream);
$conn->handleData($stream);
$this->assertTrue($conn->isReadable());
$this->assertEquals(100000, strlen($capturedData));
}
/**
* @covers React\Stream\ReadableResourceStream::handleData
*/
public function testEmptyStreamShouldNotEmitData()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$conn = new ReadableResourceStream($stream, $loop);
$conn->on('data', $this->expectCallableNever());
$conn->handleData($stream);
}
public function testPipeShouldReturnDestination()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$conn = new ReadableResourceStream($stream, $loop);
$dest = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
$this->assertSame($dest, $conn->pipe($dest));
}
/**
* @covers React\Stream\ReadableResourceStream::handleData
*/
public function testClosingStreamInDataEventShouldNotTriggerError()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$conn = new ReadableResourceStream($stream, $loop);
$conn->on('error', $this->expectCallableNever());
$conn->on('data', function ($data) use ($conn) {
$conn->close();
});
fwrite($stream, "foobar\n");
rewind($stream);
$conn->handleData($stream);
}
/**
* @covers React\Stream\ReadableResourceStream::pause
*/
public function testPauseRemovesReadStreamFromLoop()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$loop->expects($this->once())->method('addReadStream')->with($stream);
$loop->expects($this->once())->method('removeReadStream')->with($stream);
$conn = new ReadableResourceStream($stream, $loop);
$conn->pause();
$conn->pause();
}
/**
* @covers React\Stream\ReadableResourceStream::pause
*/
public function testResumeDoesAddStreamToLoopOnlyOnce()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$loop->expects($this->once())->method('addReadStream')->with($stream);
$conn = new ReadableResourceStream($stream, $loop);
$conn->resume();
$conn->resume();
}
/**
* @covers React\Stream\ReadableResourceStream::close
*/
public function testCloseRemovesReadStreamFromLoop()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$loop->expects($this->once())->method('addReadStream')->with($stream);
$loop->expects($this->once())->method('removeReadStream')->with($stream);
$conn = new ReadableResourceStream($stream, $loop);
$conn->close();
}
/**
* @covers React\Stream\ReadableResourceStream::close
*/
public function testCloseAfterPauseRemovesReadStreamFromLoopOnce()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$loop->expects($this->once())->method('addReadStream')->with($stream);
$loop->expects($this->once())->method('removeReadStream')->with($stream);
$conn = new ReadableResourceStream($stream, $loop);
$conn->pause();
$conn->close();
}
/**
* @covers React\Stream\ReadableResourceStream::close
*/
public function testResumeAfterCloseDoesAddReadStreamToLoopOnlyOnce()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$loop->expects($this->once())->method('addReadStream')->with($stream);
$conn = new ReadableResourceStream($stream, $loop);
$conn->close();
$conn->resume();
}
/**
* @covers React\Stream\ReadableResourceStream::handleData
*/
public function testDataFiltered()
{
$stream = fopen('php://temp', 'r+');
// add a filter which removes every 'a' when reading
Filter\append($stream, function ($chunk) {
return str_replace('a', '', $chunk);
}, STREAM_FILTER_READ);
$loop = $this->createLoopMock();
$capturedData = null;
$conn = new ReadableResourceStream($stream, $loop);
$conn->on('data', function ($data) use (&$capturedData) {
$capturedData = $data;
});
fwrite($stream, "foobar\n");
rewind($stream);
$conn->handleData($stream);
$this->assertSame("foobr\n", $capturedData);
}
/**
* @covers React\Stream\ReadableResourceStream::handleData
*/
public function testDataErrorShouldEmitErrorAndClose()
{
$stream = fopen('php://temp', 'r+');
// add a filter which returns an error when encountering an 'a' when reading
Filter\append($stream, function ($chunk) {
if (strpos($chunk, 'a') !== false) {
throw new \Exception('Invalid');
}
return $chunk;
}, STREAM_FILTER_READ);
$loop = $this->createLoopMock();
$conn = new ReadableResourceStream($stream, $loop);
$conn->on('data', $this->expectCallableNever());
$conn->on('error', $this->expectCallableOnce());
$conn->on('close', $this->expectCallableOnce());
fwrite($stream, "foobar\n");
rewind($stream);
$conn->handleData($stream);
}
/**
* @covers React\Stream\ReadableResourceStream::handleData
*/
public function testEmptyReadShouldntFcloseStream()
{
list($stream, $_) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
$loop = $this->createLoopMock();
$conn = new ReadableResourceStream($stream, $loop);
$conn->on('error', $this->expectCallableNever());
$conn->on('data', $this->expectCallableNever());
$conn->on('end', $this->expectCallableNever());
$conn->handleData();
fclose($stream);
fclose($_);
}
private function createLoopMock()
{
return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace React\Tests\Stream\Stub;
use Evenement\EventEmitter;
use React\Stream\ReadableStreamInterface;
use React\Stream\WritableStreamInterface;
use React\Stream\Util;
class ReadableStreamStub extends EventEmitter implements ReadableStreamInterface
{
public $readable = true;
public $paused = false;
public function isReadable()
{
return true;
}
// trigger data event
public function write($data)
{
$this->emit('data', array($data));
}
// trigger error event
public function error($error)
{
$this->emit('error', array($error));
}
// trigger end event
public function end()
{
$this->emit('end', array());
}
public function pause()
{
$this->paused = true;
}
public function resume()
{
$this->paused = false;
}
public function close()
{
$this->readable = false;
$this->emit('close');
}
public function pipe(WritableStreamInterface $dest, array $options = array())
{
Util::pipe($this, $dest, $options);
return $dest;
}
}

54
vendor/react/stream/tests/TestCase.php vendored Executable file
View File

@@ -0,0 +1,54 @@
<?php
namespace React\Tests\Stream;
use PHPUnit\Framework\TestCase as BaseTestCase;
class TestCase extends BaseTestCase
{
protected function expectCallableExactly($amount)
{
$mock = $this->createCallableMock();
$mock
->expects($this->exactly($amount))
->method('__invoke');
return $mock;
}
protected function expectCallableOnce()
{
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke');
return $mock;
}
protected function expectCallableOnceWith($value)
{
$callback = $this->createCallableMock();
$callback
->expects($this->once())
->method('__invoke')
->with($value);
return $callback;
}
protected function expectCallableNever()
{
$mock = $this->createCallableMock();
$mock
->expects($this->never())
->method('__invoke');
return $mock;
}
protected function createCallableMock()
{
return $this->getMockBuilder('React\Tests\Stream\CallableStub')->getMock();
}
}

View File

@@ -0,0 +1,267 @@
<?php
namespace React\Tests\Stream;
use React\Stream\ThroughStream;
/**
* @covers React\Stream\ThroughStream
*/
class ThroughStreamTest extends TestCase
{
/**
* @test
* @expectedException InvalidArgumentException
*/
public function itShouldRejectInvalidCallback()
{
new ThroughStream(123);
}
/** @test */
public function itShouldReturnTrueForAnyDataWrittenToIt()
{
$through = new ThroughStream();
$ret = $through->write('foo');
$this->assertTrue($ret);
}
/** @test */
public function itShouldEmitAnyDataWrittenToIt()
{
$through = new ThroughStream();
$through->on('data', $this->expectCallableOnceWith('foo'));
$through->write('foo');
}
/** @test */
public function itShouldEmitAnyDataWrittenToItPassedThruFunction()
{
$through = new ThroughStream('strtoupper');
$through->on('data', $this->expectCallableOnceWith('FOO'));
$through->write('foo');
}
/** @test */
public function itShouldEmitAnyDataWrittenToItPassedThruCallback()
{
$through = new ThroughStream('strtoupper');
$through->on('data', $this->expectCallableOnceWith('FOO'));
$through->write('foo');
}
/** @test */
public function itShouldEmitErrorAndCloseIfCallbackThrowsException()
{
$through = new ThroughStream(function () {
throw new \RuntimeException();
});
$through->on('error', $this->expectCallableOnce());
$through->on('close', $this->expectCallableOnce());
$through->on('data', $this->expectCallableNever());
$through->on('end', $this->expectCallableNever());
$through->write('foo');
$this->assertFalse($through->isReadable());
$this->assertFalse($through->isWritable());
}
/** @test */
public function itShouldEmitErrorAndCloseIfCallbackThrowsExceptionOnEnd()
{
$through = new ThroughStream(function () {
throw new \RuntimeException();
});
$through->on('error', $this->expectCallableOnce());
$through->on('close', $this->expectCallableOnce());
$through->on('data', $this->expectCallableNever());
$through->on('end', $this->expectCallableNever());
$through->end('foo');
$this->assertFalse($through->isReadable());
$this->assertFalse($through->isWritable());
}
/** @test */
public function itShouldReturnFalseForAnyDataWrittenToItWhenPaused()
{
$through = new ThroughStream();
$through->pause();
$ret = $through->write('foo');
$this->assertFalse($ret);
}
/** @test */
public function itShouldEmitDrainOnResumeAfterReturnFalseForAnyDataWrittenToItWhenPaused()
{
$through = new ThroughStream();
$through->pause();
$through->write('foo');
$through->on('drain', $this->expectCallableOnce());
$through->resume();
}
/** @test */
public function itShouldReturnTrueForAnyDataWrittenToItWhenResumedAfterPause()
{
$through = new ThroughStream();
$through->on('drain', $this->expectCallableNever());
$through->pause();
$through->resume();
$ret = $through->write('foo');
$this->assertTrue($ret);
}
/** @test */
public function pipingStuffIntoItShouldWork()
{
$readable = new ThroughStream();
$through = new ThroughStream();
$through->on('data', $this->expectCallableOnceWith('foo'));
$readable->pipe($through);
$readable->emit('data', array('foo'));
}
/** @test */
public function endShouldEmitEndAndClose()
{
$through = new ThroughStream();
$through->on('data', $this->expectCallableNever());
$through->on('end', $this->expectCallableOnce());
$through->on('close', $this->expectCallableOnce());
$through->end();
}
/** @test */
public function endShouldCloseTheStream()
{
$through = new ThroughStream();
$through->on('data', $this->expectCallableNever());
$through->end();
$this->assertFalse($through->isReadable());
$this->assertFalse($through->isWritable());
}
/** @test */
public function endShouldWriteDataBeforeClosing()
{
$through = new ThroughStream();
$through->on('data', $this->expectCallableOnceWith('foo'));
$through->end('foo');
$this->assertFalse($through->isReadable());
$this->assertFalse($through->isWritable());
}
/** @test */
public function endTwiceShouldOnlyEmitOnce()
{
$through = new ThroughStream();
$through->on('data', $this->expectCallableOnce('first'));
$through->end('first');
$through->end('ignored');
}
/** @test */
public function writeAfterEndShouldReturnFalse()
{
$through = new ThroughStream();
$through->on('data', $this->expectCallableNever());
$through->end();
$this->assertFalse($through->write('foo'));
}
/** @test */
public function writeDataWillCloseStreamShouldReturnFalse()
{
$through = new ThroughStream();
$through->on('data', array($through, 'close'));
$this->assertFalse($through->write('foo'));
}
/** @test */
public function writeDataToPausedShouldReturnFalse()
{
$through = new ThroughStream();
$through->pause();
$this->assertFalse($through->write('foo'));
}
/** @test */
public function writeDataToResumedShouldReturnTrue()
{
$through = new ThroughStream();
$through->pause();
$through->resume();
$this->assertTrue($through->write('foo'));
}
/** @test */
public function itShouldBeReadableByDefault()
{
$through = new ThroughStream();
$this->assertTrue($through->isReadable());
}
/** @test */
public function itShouldBeWritableByDefault()
{
$through = new ThroughStream();
$this->assertTrue($through->isWritable());
}
/** @test */
public function closeShouldCloseOnce()
{
$through = new ThroughStream();
$through->on('close', $this->expectCallableOnce());
$through->close();
$this->assertFalse($through->isReadable());
$this->assertFalse($through->isWritable());
}
/** @test */
public function doubleCloseShouldCloseOnce()
{
$through = new ThroughStream();
$through->on('close', $this->expectCallableOnce());
$through->close();
$through->close();
$this->assertFalse($through->isReadable());
$this->assertFalse($through->isWritable());
}
/** @test */
public function pipeShouldPipeCorrectly()
{
$output = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
$output->expects($this->any())->method('isWritable')->willReturn(True);
$output
->expects($this->once())
->method('write')
->with('foo');
$through = new ThroughStream();
$through->pipe($output);
$through->write('foo');
}
}

273
vendor/react/stream/tests/UtilTest.php vendored Executable file
View File

@@ -0,0 +1,273 @@
<?php
namespace React\Tests\Stream;
use React\Stream\WritableResourceStream;
use React\Stream\Util;
use React\Stream\CompositeStream;
use React\Stream\ThroughStream;
/**
* @covers React\Stream\Util
*/
class UtilTest extends TestCase
{
public function testPipeReturnsDestinationStream()
{
$readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
$ret = Util::pipe($readable, $writable);
$this->assertSame($writable, $ret);
}
public function testPipeNonReadableSourceShouldDoNothing()
{
$readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
$readable
->expects($this->any())
->method('isReadable')
->willReturn(false);
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
$writable
->expects($this->never())
->method('isWritable');
$writable
->expects($this->never())
->method('end');
Util::pipe($readable, $writable);
}
public function testPipeIntoNonWritableDestinationShouldPauseSource()
{
$readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
$readable
->expects($this->any())
->method('isReadable')
->willReturn(true);
$readable
->expects($this->once())
->method('pause');
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
$writable
->expects($this->any())
->method('isWritable')
->willReturn(false);
$writable
->expects($this->never())
->method('end');
Util::pipe($readable, $writable);
}
public function testPipeClosingDestPausesSource()
{
$readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
$readable
->expects($this->any())
->method('isReadable')
->willReturn(true);
$readable
->expects($this->once())
->method('pause');
$writable = new ThroughStream();
Util::pipe($readable, $writable);
$writable->close();
}
public function testPipeWithEnd()
{
$readable = new Stub\ReadableStreamStub();
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
$writable
->expects($this->any())
->method('isWritable')
->willReturn(true);
$writable
->expects($this->once())
->method('end');
Util::pipe($readable, $writable);
$readable->end();
}
public function testPipeWithoutEnd()
{
$readable = new Stub\ReadableStreamStub();
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
$writable
->expects($this->any())
->method('isWritable')
->willReturn(true);
$writable
->expects($this->never())
->method('end');
Util::pipe($readable, $writable, array('end' => false));
$readable->end();
}
public function testPipeWithTooSlowWritableShouldPauseReadable()
{
$readable = new Stub\ReadableStreamStub();
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
$writable
->expects($this->any())
->method('isWritable')
->willReturn(true);
$writable
->expects($this->once())
->method('write')
->with('some data')
->will($this->returnValue(false));
$readable->pipe($writable);
$this->assertFalse($readable->paused);
$readable->write('some data');
$this->assertTrue($readable->paused);
}
public function testPipeWithTooSlowWritableShouldResumeOnDrain()
{
$readable = new Stub\ReadableStreamStub();
$onDrain = null;
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
$writable
->expects($this->any())
->method('isWritable')
->willReturn(true);
$writable
->expects($this->any())
->method('on')
->will($this->returnCallback(function ($name, $callback) use (&$onDrain) {
if ($name === 'drain') {
$onDrain = $callback;
}
}));
$readable->pipe($writable);
$readable->pause();
$this->assertTrue($readable->paused);
$this->assertNotNull($onDrain);
$onDrain();
$this->assertFalse($readable->paused);
}
public function testPipeWithWritableResourceStream()
{
$readable = new Stub\ReadableStreamStub();
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$buffer = new WritableResourceStream($stream, $loop);
$readable->pipe($buffer);
$readable->write('hello, I am some ');
$readable->write('random data');
$buffer->handleWrite();
rewind($stream);
$this->assertSame('hello, I am some random data', stream_get_contents($stream));
}
public function testPipeSetsUpListeners()
{
$source = new ThroughStream();
$dest = new ThroughStream();
$this->assertCount(0, $source->listeners('data'));
$this->assertCount(0, $source->listeners('end'));
$this->assertCount(0, $dest->listeners('drain'));
Util::pipe($source, $dest);
$this->assertCount(1, $source->listeners('data'));
$this->assertCount(1, $source->listeners('end'));
$this->assertCount(1, $dest->listeners('drain'));
}
public function testPipeClosingSourceRemovesListeners()
{
$source = new ThroughStream();
$dest = new ThroughStream();
Util::pipe($source, $dest);
$source->close();
$this->assertCount(0, $source->listeners('data'));
$this->assertCount(0, $source->listeners('end'));
$this->assertCount(0, $dest->listeners('drain'));
}
public function testPipeClosingDestRemovesListeners()
{
$source = new ThroughStream();
$dest = new ThroughStream();
Util::pipe($source, $dest);
$dest->close();
$this->assertCount(0, $source->listeners('data'));
$this->assertCount(0, $source->listeners('end'));
$this->assertCount(0, $dest->listeners('drain'));
}
public function testPipeDuplexIntoSelfEndsOnEnd()
{
$readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
$readable->expects($this->any())->method('isReadable')->willReturn(true);
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
$writable->expects($this->any())->method('isWritable')->willReturn(true);
$duplex = new CompositeStream($readable, $writable);
Util::pipe($duplex, $duplex);
$writable->expects($this->once())->method('end');
$duplex->emit('end');
}
/** @test */
public function forwardEventsShouldSetupForwards()
{
$source = new ThroughStream();
$target = new ThroughStream();
Util::forwardEvents($source, $target, array('data'));
$target->on('data', $this->expectCallableOnce());
$target->on('foo', $this->expectCallableNever());
$source->emit('data', array('hello'));
$source->emit('foo', array('bar'));
}
private function createLoopMock()
{
return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
}
private function notEqualTo($value)
{
return new \PHPUnit_Framework_Constraint_Not($value);
}
}

View File

@@ -0,0 +1,534 @@
<?php
namespace React\Tests\Stream;
use Clue\StreamFilter as Filter;
use React\Stream\WritableResourceStream;
class WritableResourceStreamTest extends TestCase
{
/**
* @covers React\Stream\WritableResourceStream::__construct
* @doesNotPerformAssertions
*/
public function testConstructor()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
new WritableResourceStream($stream, $loop);
}
/**
* @covers React\Stream\WritableResourceStream::__construct
* @doesNotPerformAssertions
*/
public function testConstructorWithExcessiveMode()
{
// excessive flags are ignored for temp streams, so we have to use a file stream
$name = tempnam(sys_get_temp_dir(), 'test');
$stream = @fopen($name, 'w+eANYTHING');
unlink($name);
$loop = $this->createLoopMock();
$buffer = new WritableResourceStream($stream, $loop);
$buffer->close();
}
/**
* @covers React\Stream\WritableResourceStream::__construct
* @expectedException InvalidArgumentException
*/
public function testConstructorThrowsIfNotAValidStreamResource()
{
$stream = null;
$loop = $this->createLoopMock();
new WritableResourceStream($stream, $loop);
}
/**
* @covers React\Stream\WritableResourceStream::__construct
* @expectedException InvalidArgumentException
*/
public function testConstructorThrowsExceptionOnReadOnlyStream()
{
$stream = fopen('php://temp', 'r');
$loop = $this->createLoopMock();
new WritableResourceStream($stream, $loop);
}
/**
* @covers React\Stream\WritableResourceStream::__construct
* @expectedException InvalidArgumentException
*/
public function testConstructorThrowsExceptionOnReadOnlyStreamWithExcessiveMode()
{
// excessive flags are ignored for temp streams, so we have to use a file stream
$name = tempnam(sys_get_temp_dir(), 'test');
$stream = fopen($name, 'reANYTHING');
unlink($name);
$loop = $this->createLoopMock();
new WritableResourceStream($stream, $loop);
}
/**
* @covers React\Stream\WritableResourceStream::__construct
* @expectedException RuntimeException
*/
public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking()
{
if (!in_array('blocking', stream_get_wrappers())) {
stream_wrapper_register('blocking', 'React\Tests\Stream\EnforceBlockingWrapper');
}
$stream = fopen('blocking://test', 'r+');
$loop = $this->createLoopMock();
new WritableResourceStream($stream, $loop);
}
/**
* @covers React\Stream\WritableResourceStream::write
* @covers React\Stream\WritableResourceStream::handleWrite
*/
public function testWrite()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createWriteableLoopMock();
$buffer = new WritableResourceStream($stream, $loop);
$buffer->on('error', $this->expectCallableNever());
$buffer->write("foobar\n");
rewind($stream);
$this->assertSame("foobar\n", fread($stream, 1024));
}
/**
* @covers React\Stream\WritableResourceStream::write
*/
public function testWriteWithDataDoesAddResourceToLoop()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$loop->expects($this->once())->method('addWriteStream')->with($this->equalTo($stream));
$buffer = new WritableResourceStream($stream, $loop);
$buffer->write("foobar\n");
}
/**
* @covers React\Stream\WritableResourceStream::write
* @covers React\Stream\WritableResourceStream::handleWrite
*/
public function testEmptyWriteDoesNotAddToLoop()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$loop->expects($this->never())->method('addWriteStream');
$buffer = new WritableResourceStream($stream, $loop);
$buffer->write("");
$buffer->write(null);
}
/**
* @covers React\Stream\WritableResourceStream::write
* @covers React\Stream\WritableResourceStream::handleWrite
*/
public function testWriteReturnsFalseWhenWritableResourceStreamIsFull()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createWriteableLoopMock();
$loop->preventWrites = true;
$buffer = new WritableResourceStream($stream, $loop, 4);
$buffer->on('error', $this->expectCallableNever());
$this->assertTrue($buffer->write("foo"));
$loop->preventWrites = false;
$this->assertFalse($buffer->write("bar\n"));
}
/**
* @covers React\Stream\WritableResourceStream::write
*/
public function testWriteReturnsFalseWhenWritableResourceStreamIsExactlyFull()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$buffer = new WritableResourceStream($stream, $loop, 3);
$this->assertFalse($buffer->write("foo"));
}
/**
* @covers React\Stream\WritableResourceStream::write
* @covers React\Stream\WritableResourceStream::handleWrite
*/
public function testWriteDetectsWhenOtherSideIsClosed()
{
list($a, $b) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
$loop = $this->createWriteableLoopMock();
$buffer = new WritableResourceStream($a, $loop, 4);
$buffer->on('error', $this->expectCallableOnce());
fclose($b);
$buffer->write("foo");
}
/**
* @covers React\Stream\WritableResourceStream::write
* @covers React\Stream\WritableResourceStream::handleWrite
*/
public function testEmitsDrainAfterWriteWhichExceedsBuffer()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$buffer = new WritableResourceStream($stream, $loop, 2);
$buffer->on('error', $this->expectCallableNever());
$buffer->on('drain', $this->expectCallableOnce());
$buffer->write("foo");
$buffer->handleWrite();
}
/**
* @covers React\Stream\WritableResourceStream::write
* @covers React\Stream\WritableResourceStream::handleWrite
*/
public function testWriteInDrain()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$buffer = new WritableResourceStream($stream, $loop, 2);
$buffer->on('error', $this->expectCallableNever());
$buffer->once('drain', function () use ($buffer) {
$buffer->write("bar\n");
$buffer->handleWrite();
});
$this->assertFalse($buffer->write("foo\n"));
$buffer->handleWrite();
fseek($stream, 0);
$this->assertSame("foo\nbar\n", stream_get_contents($stream));
}
/**
* @covers React\Stream\WritableResourceStream::write
* @covers React\Stream\WritableResourceStream::handleWrite
*/
public function testDrainAfterWrite()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$buffer = new WritableResourceStream($stream, $loop, 2);
$buffer->on('drain', $this->expectCallableOnce());
$buffer->write("foo");
$buffer->handleWrite();
}
/**
* @covers React\Stream\WritableResourceStream::handleWrite
*/
public function testDrainAfterWriteWillRemoveResourceFromLoopWithoutClosing()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$loop->expects($this->once())->method('removeWriteStream')->with($stream);
$buffer = new WritableResourceStream($stream, $loop, 2);
$buffer->on('drain', $this->expectCallableOnce());
$buffer->on('close', $this->expectCallableNever());
$buffer->write("foo");
$buffer->handleWrite();
}
/**
* @covers React\Stream\WritableResourceStream::handleWrite
*/
public function testClosingDuringDrainAfterWriteWillRemoveResourceFromLoopOnceAndClose()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$loop->expects($this->once())->method('removeWriteStream')->with($stream);
$buffer = new WritableResourceStream($stream, $loop, 2);
$buffer->on('drain', function () use ($buffer) {
$buffer->close();
});
$buffer->on('close', $this->expectCallableOnce());
$buffer->write("foo");
$buffer->handleWrite();
}
/**
* @covers React\Stream\WritableResourceStream::end
*/
public function testEndWithoutDataClosesImmediatelyIfWritableResourceStreamIsEmpty()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$buffer = new WritableResourceStream($stream, $loop);
$buffer->on('error', $this->expectCallableNever());
$buffer->on('close', $this->expectCallableOnce());
$this->assertTrue($buffer->isWritable());
$buffer->end();
$this->assertFalse($buffer->isWritable());
}
/**
* @covers React\Stream\WritableResourceStream::end
*/
public function testEndWithoutDataDoesNotCloseIfWritableResourceStreamIsFull()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$buffer = new WritableResourceStream($stream, $loop);
$buffer->on('error', $this->expectCallableNever());
$buffer->on('close', $this->expectCallableNever());
$buffer->write('foo');
$this->assertTrue($buffer->isWritable());
$buffer->end();
$this->assertFalse($buffer->isWritable());
}
/**
* @covers React\Stream\WritableResourceStream::end
*/
public function testEndWithDataClosesImmediatelyIfWritableResourceStreamFlushes()
{
$stream = fopen('php://temp', 'r+');
$filterBuffer = '';
$loop = $this->createLoopMock();
$buffer = new WritableResourceStream($stream, $loop);
$buffer->on('error', $this->expectCallableNever());
$buffer->on('close', $this->expectCallableOnce());
Filter\append($stream, function ($chunk) use (&$filterBuffer) {
$filterBuffer .= $chunk;
return $chunk;
});
$this->assertTrue($buffer->isWritable());
$buffer->end('final words');
$this->assertFalse($buffer->isWritable());
$buffer->handleWrite();
$this->assertSame('final words', $filterBuffer);
}
/**
* @covers React\Stream\WritableResourceStream::end
*/
public function testEndWithDataDoesNotCloseImmediatelyIfWritableResourceStreamIsFull()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$buffer = new WritableResourceStream($stream, $loop);
$buffer->on('error', $this->expectCallableNever());
$buffer->on('close', $this->expectCallableNever());
$buffer->write('foo');
$this->assertTrue($buffer->isWritable());
$buffer->end('final words');
$this->assertFalse($buffer->isWritable());
rewind($stream);
$this->assertSame('', stream_get_contents($stream));
}
/**
* @covers React\Stream\WritableResourceStream::isWritable
* @covers React\Stream\WritableResourceStream::close
*/
public function testClose()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$buffer = new WritableResourceStream($stream, $loop);
$buffer->on('error', $this->expectCallableNever());
$buffer->on('close', $this->expectCallableOnce());
$this->assertTrue($buffer->isWritable());
$buffer->close();
$this->assertFalse($buffer->isWritable());
$this->assertEquals(array(), $buffer->listeners('close'));
}
/**
* @covers React\Stream\WritableResourceStream::close
*/
public function testClosingAfterWriteRemovesStreamFromLoop()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$buffer = new WritableResourceStream($stream, $loop);
$loop->expects($this->once())->method('removeWriteStream')->with($stream);
$buffer->write('foo');
$buffer->close();
}
/**
* @covers React\Stream\WritableResourceStream::close
*/
public function testClosingWithoutWritingDoesNotRemoveStreamFromLoop()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$buffer = new WritableResourceStream($stream, $loop);
$loop->expects($this->never())->method('removeWriteStream');
$buffer->close();
}
/**
* @covers React\Stream\WritableResourceStream::close
*/
public function testDoubleCloseWillEmitOnlyOnce()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createLoopMock();
$buffer = new WritableResourceStream($stream, $loop);
$buffer->on('close', $this->expectCallableOnce());
$buffer->close();
$buffer->close();
}
/**
* @covers React\Stream\WritableResourceStream::write
* @covers React\Stream\WritableResourceStream::close
*/
public function testWritingToClosedWritableResourceStreamShouldNotWriteToStream()
{
$stream = fopen('php://temp', 'r+');
$filterBuffer = '';
$loop = $this->createLoopMock();
$buffer = new WritableResourceStream($stream, $loop);
Filter\append($stream, function ($chunk) use (&$filterBuffer) {
$filterBuffer .= $chunk;
return $chunk;
});
$buffer->close();
$buffer->write('foo');
$buffer->handleWrite();
$this->assertSame('', $filterBuffer);
}
/**
* @covers React\Stream\WritableResourceStream::handleWrite
*/
public function testErrorWhenStreamResourceIsInvalid()
{
$stream = fopen('php://temp', 'r+');
$loop = $this->createWriteableLoopMock();
$error = null;
$buffer = new WritableResourceStream($stream, $loop);
$buffer->on('error', function ($message) use (&$error) {
$error = $message;
});
// invalidate stream resource
fclose($stream);
$buffer->write('Attempting to write to bad stream');
$this->assertInstanceOf('Exception', $error);
// the error messages differ between PHP versions, let's just check substrings
$this->assertContains('Unable to write to stream: ', $error->getMessage());
$this->assertContains(' not a valid stream resource', $error->getMessage(), '', true);
}
public function testWritingToClosedStream()
{
if ('Darwin' === PHP_OS) {
$this->markTestSkipped('OS X issue with shutting down pair for writing');
}
list($a, $b) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
$loop = $this->createLoopMock();
$error = null;
$buffer = new WritableResourceStream($a, $loop);
$buffer->on('error', function($message) use (&$error) {
$error = $message;
});
$buffer->write('foo');
$buffer->handleWrite();
stream_socket_shutdown($b, STREAM_SHUT_RD);
stream_socket_shutdown($a, STREAM_SHUT_RD);
$buffer->write('bar');
$buffer->handleWrite();
$this->assertInstanceOf('Exception', $error);
$this->assertSame('Unable to write to stream: fwrite(): send of 3 bytes failed with errno=32 Broken pipe', $error->getMessage());
}
private function createWriteableLoopMock()
{
$loop = $this->createLoopMock();
$loop->preventWrites = false;
$loop
->expects($this->any())
->method('addWriteStream')
->will($this->returnCallback(function ($stream, $listener) use ($loop) {
if (!$loop->preventWrites) {
call_user_func($listener, $stream);
}
}));
return $loop;
}
private function createLoopMock()
{
return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
}
}