123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447 |
- <?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);
- }
|