You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
447 lines
12 KiB
447 lines
12 KiB
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace GuzzleHttp\Tests\Psr7;
|
|
|
|
use GuzzleHttp\Psr7\FnStream;
|
|
use GuzzleHttp\Psr7\Stream;
|
|
use GuzzleHttp\Psr7\StreamWrapper;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
/**
|
|
* @covers \GuzzleHttp\Psr7\Stream
|
|
*/
|
|
class StreamTest extends TestCase
|
|
{
|
|
public static $isFReadError = false;
|
|
|
|
public function testConstructorThrowsExceptionOnInvalidArgument(): void
|
|
{
|
|
$this->expectException(\InvalidArgumentException::class);
|
|
new Stream(true);
|
|
}
|
|
|
|
public function testConstructorInitializesProperties(): void
|
|
{
|
|
$handle = fopen('php://temp', 'r+');
|
|
fwrite($handle, 'data');
|
|
$stream = new Stream($handle);
|
|
self::assertTrue($stream->isReadable());
|
|
self::assertTrue($stream->isWritable());
|
|
self::assertTrue($stream->isSeekable());
|
|
self::assertSame('php://temp', $stream->getMetadata('uri'));
|
|
self::assertIsArray($stream->getMetadata());
|
|
self::assertSame(4, $stream->getSize());
|
|
self::assertFalse($stream->eof());
|
|
$stream->close();
|
|
}
|
|
|
|
public function testConstructorInitializesPropertiesWithRbPlus(): void
|
|
{
|
|
$handle = fopen('php://temp', 'rb+');
|
|
fwrite($handle, 'data');
|
|
$stream = new Stream($handle);
|
|
self::assertTrue($stream->isReadable());
|
|
self::assertTrue($stream->isWritable());
|
|
self::assertTrue($stream->isSeekable());
|
|
self::assertSame('php://temp', $stream->getMetadata('uri'));
|
|
self::assertIsArray($stream->getMetadata());
|
|
self::assertSame(4, $stream->getSize());
|
|
self::assertFalse($stream->eof());
|
|
$stream->close();
|
|
}
|
|
|
|
public function testStreamClosesHandleOnDestruct(): void
|
|
{
|
|
$handle = fopen('php://temp', 'r');
|
|
$stream = new Stream($handle);
|
|
unset($stream);
|
|
self::assertFalse(is_resource($handle));
|
|
}
|
|
|
|
public function testConvertsToString(): void
|
|
{
|
|
$handle = fopen('php://temp', 'w+');
|
|
fwrite($handle, 'data');
|
|
$stream = new Stream($handle);
|
|
self::assertSame('data', (string) $stream);
|
|
self::assertSame('data', (string) $stream);
|
|
$stream->close();
|
|
}
|
|
|
|
public function testConvertsToStringNonSeekableStream(): void
|
|
{
|
|
$handle = popen('echo foo', 'r');
|
|
$stream = new Stream($handle);
|
|
self::assertFalse($stream->isSeekable());
|
|
self::assertSame('foo', trim((string) $stream));
|
|
}
|
|
|
|
public function testConvertsToStringNonSeekablePartiallyReadStream(): void
|
|
{
|
|
$handle = popen('echo bar', 'r');
|
|
$stream = new Stream($handle);
|
|
$firstLetter = $stream->read(1);
|
|
self::assertFalse($stream->isSeekable());
|
|
self::assertSame('b', $firstLetter);
|
|
self::assertSame('ar', trim((string) $stream));
|
|
}
|
|
|
|
public function testGetsContents(): void
|
|
{
|
|
$handle = fopen('php://temp', 'w+');
|
|
fwrite($handle, 'data');
|
|
$stream = new Stream($handle);
|
|
self::assertSame('', $stream->getContents());
|
|
$stream->seek(0);
|
|
self::assertSame('data', $stream->getContents());
|
|
self::assertSame('', $stream->getContents());
|
|
$stream->close();
|
|
}
|
|
|
|
public function testChecksEof(): void
|
|
{
|
|
$handle = fopen('php://temp', 'w+');
|
|
fwrite($handle, 'data');
|
|
$stream = new Stream($handle);
|
|
self::assertSame(4, $stream->tell(), 'Stream cursor already at the end');
|
|
self::assertFalse($stream->eof(), 'Stream still not eof');
|
|
self::assertSame('', $stream->read(1), 'Need to read one more byte to reach eof');
|
|
self::assertTrue($stream->eof());
|
|
$stream->close();
|
|
}
|
|
|
|
public function testGetSize(): void
|
|
{
|
|
$size = filesize(__FILE__);
|
|
$handle = fopen(__FILE__, 'r');
|
|
$stream = new Stream($handle);
|
|
self::assertSame($size, $stream->getSize());
|
|
// Load from cache
|
|
self::assertSame($size, $stream->getSize());
|
|
$stream->close();
|
|
}
|
|
|
|
public function testEnsuresSizeIsConsistent(): void
|
|
{
|
|
$h = fopen('php://temp', 'w+');
|
|
self::assertSame(3, fwrite($h, 'foo'));
|
|
$stream = new Stream($h);
|
|
self::assertSame(3, $stream->getSize());
|
|
self::assertSame(4, $stream->write('test'));
|
|
self::assertSame(7, $stream->getSize());
|
|
self::assertSame(7, $stream->getSize());
|
|
$stream->close();
|
|
}
|
|
|
|
public function testProvidesStreamPosition(): void
|
|
{
|
|
$handle = fopen('php://temp', 'w+');
|
|
$stream = new Stream($handle);
|
|
self::assertSame(0, $stream->tell());
|
|
$stream->write('foo');
|
|
self::assertSame(3, $stream->tell());
|
|
$stream->seek(1);
|
|
self::assertSame(1, $stream->tell());
|
|
self::assertSame(ftell($handle), $stream->tell());
|
|
$stream->close();
|
|
}
|
|
|
|
public function testDetachStreamAndClearProperties(): void
|
|
{
|
|
$handle = fopen('php://temp', 'r');
|
|
$stream = new Stream($handle);
|
|
self::assertSame($handle, $stream->detach());
|
|
self::assertIsResource($handle, 'Stream is not closed');
|
|
self::assertNull($stream->detach());
|
|
|
|
$this->assertStreamStateAfterClosedOrDetached($stream);
|
|
|
|
$stream->close();
|
|
}
|
|
|
|
public function testCloseResourceAndClearProperties(): void
|
|
{
|
|
$handle = fopen('php://temp', 'r');
|
|
$stream = new Stream($handle);
|
|
$stream->close();
|
|
|
|
self::assertFalse(is_resource($handle));
|
|
|
|
$this->assertStreamStateAfterClosedOrDetached($stream);
|
|
}
|
|
|
|
private function assertStreamStateAfterClosedOrDetached(Stream $stream): void
|
|
{
|
|
self::assertFalse($stream->isReadable());
|
|
self::assertFalse($stream->isWritable());
|
|
self::assertFalse($stream->isSeekable());
|
|
self::assertNull($stream->getSize());
|
|
self::assertSame([], $stream->getMetadata());
|
|
self::assertNull($stream->getMetadata('foo'));
|
|
|
|
$throws = function (callable $fn): void {
|
|
try {
|
|
$fn();
|
|
} catch (\Exception $e) {
|
|
$this->assertStringContainsString('Stream is detached', $e->getMessage());
|
|
|
|
return;
|
|
}
|
|
|
|
$this->fail('Exception should be thrown after the stream is detached.');
|
|
};
|
|
|
|
$throws(function () use ($stream): void {
|
|
$stream->read(10);
|
|
});
|
|
$throws(function () use ($stream): void {
|
|
$stream->write('bar');
|
|
});
|
|
$throws(function () use ($stream): void {
|
|
$stream->seek(10);
|
|
});
|
|
$throws(function () use ($stream): void {
|
|
$stream->tell();
|
|
});
|
|
$throws(function () use ($stream): void {
|
|
$stream->eof();
|
|
});
|
|
$throws(function () use ($stream): void {
|
|
$stream->getContents();
|
|
});
|
|
|
|
if (\PHP_VERSION_ID >= 70400) {
|
|
$throws(function () use ($stream): void {
|
|
(string) $stream;
|
|
});
|
|
} else {
|
|
$errors = [];
|
|
set_error_handler(function (int $errorNumber, string $errorMessage) use (&$errors): void {
|
|
$errors[] = ['message' => $errorMessage, 'number' => $errorNumber];
|
|
});
|
|
self::assertSame('', (string) $stream);
|
|
restore_error_handler();
|
|
|
|
self::assertCount(1, $errors);
|
|
self::assertStringStartsWith('GuzzleHttp\Psr7\Stream::__toString exception', $errors[0]['message']);
|
|
self::assertSame(E_USER_ERROR, $errors[0]['number']);
|
|
}
|
|
}
|
|
|
|
public function testStreamReadingWithZeroLength(): void
|
|
{
|
|
$r = fopen('php://temp', 'r');
|
|
$stream = new Stream($r);
|
|
|
|
self::assertSame('', $stream->read(0));
|
|
|
|
$stream->close();
|
|
}
|
|
|
|
public function testStreamReadingWithNegativeLength(): void
|
|
{
|
|
$r = fopen('php://temp', 'r');
|
|
$stream = new Stream($r);
|
|
$this->expectException(\RuntimeException::class);
|
|
$this->expectExceptionMessage('Length parameter cannot be negative');
|
|
|
|
try {
|
|
$stream->read(-1);
|
|
} catch (\Exception $e) {
|
|
$stream->close();
|
|
throw $e;
|
|
}
|
|
|
|
$stream->close();
|
|
}
|
|
|
|
public function testStreamReadingFreadFalse(): void
|
|
{
|
|
self::$isFReadError = true;
|
|
$r = fopen('php://temp', 'r');
|
|
$stream = new Stream($r);
|
|
$this->expectException(\RuntimeException::class);
|
|
$this->expectExceptionMessage('Unable to read from stream');
|
|
|
|
try {
|
|
$stream->read(1);
|
|
} catch (\Exception $e) {
|
|
self::$isFReadError = false;
|
|
$stream->close();
|
|
throw $e;
|
|
}
|
|
|
|
self::$isFReadError = false;
|
|
$stream->close();
|
|
}
|
|
|
|
public function testStreamReadingFreadException(): void
|
|
{
|
|
$this->expectException(\RuntimeException::class);
|
|
$this->expectExceptionMessage('Unable to read from stream');
|
|
|
|
$r = StreamWrapper::getResource(new FnStream([
|
|
'read' => function ($len): string {
|
|
throw new \ErrorException('Some error');
|
|
},
|
|
'isReadable' => function (): bool {
|
|
return true;
|
|
},
|
|
'isWritable' => function (): bool {
|
|
return false;
|
|
},
|
|
'eof' => function (): bool {
|
|
return false;
|
|
},
|
|
]));
|
|
|
|
$stream = new Stream($r);
|
|
$stream->read(1);
|
|
}
|
|
|
|
/**
|
|
* @requires extension zlib
|
|
*
|
|
* @dataProvider gzipModeProvider
|
|
*/
|
|
public function testGzipStreamModes(string $mode, bool $readable, bool $writable): void
|
|
{
|
|
$r = gzopen('php://temp', $mode);
|
|
$stream = new Stream($r);
|
|
|
|
self::assertSame($readable, $stream->isReadable());
|
|
self::assertSame($writable, $stream->isWritable());
|
|
|
|
$stream->close();
|
|
}
|
|
|
|
public function gzipModeProvider(): iterable
|
|
{
|
|
return [
|
|
['mode' => 'rb9', 'readable' => true, 'writable' => false],
|
|
['mode' => 'wb2', 'readable' => false, 'writable' => true],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider readableModeProvider
|
|
*/
|
|
public function testReadableStream(string $mode): void
|
|
{
|
|
$r = fopen('php://temp', $mode);
|
|
$stream = new Stream($r);
|
|
|
|
self::assertTrue($stream->isReadable());
|
|
|
|
$stream->close();
|
|
}
|
|
|
|
public function readableModeProvider(): iterable
|
|
{
|
|
return [
|
|
['r'],
|
|
['w+'],
|
|
['r+'],
|
|
['x+'],
|
|
['c+'],
|
|
['rb'],
|
|
['w+b'],
|
|
['r+b'],
|
|
['x+b'],
|
|
['c+b'],
|
|
['rt'],
|
|
['w+t'],
|
|
['r+t'],
|
|
['x+t'],
|
|
['c+t'],
|
|
['a+'],
|
|
['rb+'],
|
|
];
|
|
}
|
|
|
|
public function testWriteOnlyStreamIsNotReadable(): void
|
|
{
|
|
$r = fopen('php://output', 'w');
|
|
$stream = new Stream($r);
|
|
|
|
self::assertFalse($stream->isReadable());
|
|
|
|
$stream->close();
|
|
}
|
|
|
|
/**
|
|
* @dataProvider writableModeProvider
|
|
*/
|
|
public function testWritableStream(string $mode): void
|
|
{
|
|
$r = fopen('php://temp', $mode);
|
|
$stream = new Stream($r);
|
|
|
|
self::assertTrue($stream->isWritable());
|
|
|
|
$stream->close();
|
|
}
|
|
|
|
public function writableModeProvider(): iterable
|
|
{
|
|
return [
|
|
['w'],
|
|
['w+'],
|
|
['rw'],
|
|
['r+'],
|
|
['x+'],
|
|
['c+'],
|
|
['wb'],
|
|
['w+b'],
|
|
['r+b'],
|
|
['rb+'],
|
|
['x+b'],
|
|
['c+b'],
|
|
['w+t'],
|
|
['r+t'],
|
|
['x+t'],
|
|
['c+t'],
|
|
['a'],
|
|
['a+'],
|
|
];
|
|
}
|
|
|
|
public function testReadOnlyStreamIsNotWritable(): void
|
|
{
|
|
$r = fopen('php://input', 'r');
|
|
$stream = new Stream($r);
|
|
|
|
self::assertFalse($stream->isWritable());
|
|
|
|
$stream->close();
|
|
}
|
|
|
|
public function testCannotReadUnreadableStream(): void
|
|
{
|
|
$r = fopen(tempnam(sys_get_temp_dir(), 'guzzle-psr7-'), 'w');
|
|
$stream = new Stream($r);
|
|
|
|
$stream->write('Hello world!!');
|
|
|
|
$stream->seek(0);
|
|
|
|
$this->expectException(\RuntimeException::class);
|
|
|
|
try {
|
|
$stream->getContents();
|
|
} finally {
|
|
$stream->close();
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace GuzzleHttp\Psr7;
|
|
|
|
use GuzzleHttp\Tests\Psr7\StreamTest;
|
|
|
|
function fread($handle, $length)
|
|
{
|
|
return StreamTest::$isFReadError ? false : \fread($handle, $length);
|
|
}
|
|
|