1<?php 2namespace GuzzleHttp\Tests\Stream; 3 4use GuzzleHttp\Stream\Stream; 5use GuzzleHttp\Stream\CachingStream; 6use GuzzleHttp\Stream\Utils; 7use PHPUnit\Framework\TestCase; 8use ReflectionProperty; 9use RuntimeException; 10 11/** 12 * @covers GuzzleHttp\Stream\CachingStream 13 */ 14class CachingStreamTest extends TestCase 15{ 16 /** @var CachingStream */ 17 protected $body; 18 19 /** @var Stream */ 20 protected $decorated; 21 22 public function setUp(): void 23 { 24 $this->decorated = Stream::factory('testing'); 25 $this->body = new CachingStream($this->decorated); 26 } 27 28 public function tearDown(): void 29 { 30 $this->decorated->close(); 31 $this->body->close(); 32 } 33 34 public function testUsesRemoteSizeIfPossible() 35 { 36 $body = Stream::factory('test'); 37 $caching = new CachingStream($body); 38 $this->assertEquals(4, $caching->getSize()); 39 } 40 41 public function testCannotSeekPastWhatHasBeenRead() 42 { 43 $this->expectException(RuntimeException::class); 44 $this->expectErrorMessage('Cannot seek to byte 10'); 45 $this->body->seek(10); 46 } 47 48 public function testCannotUseSeekEnd() 49 { 50 $this->assertFalse($this->body->seek(2, SEEK_END)); 51 } 52 53 public function testRewindUsesSeek() 54 { 55 $a = Stream::factory('foo'); 56 $d = $this->getMockBuilder('GuzzleHttp\Stream\CachingStream') 57 ->setConstructorArgs(array($a)) 58 ->getMock(); 59 $d->expects($this->once()) 60 ->method('seek') 61 ->with(0) 62 ->will($this->returnValue(true)); 63 $d->seek(0); 64 } 65 66 public function testCanSeekToReadBytes() 67 { 68 $this->assertEquals('te', $this->body->read(2)); 69 $this->body->seek(0); 70 $this->assertEquals('test', $this->body->read(4)); 71 $this->assertEquals(4, $this->body->tell()); 72 $this->body->seek(2); 73 $this->assertEquals(2, $this->body->tell()); 74 $this->body->seek(2, SEEK_CUR); 75 $this->assertEquals(4, $this->body->tell()); 76 $this->assertEquals('ing', $this->body->read(3)); 77 } 78 79 public function testWritesToBufferStream() 80 { 81 $this->body->read(2); 82 $this->body->write('hi'); 83 $this->body->seek(0); 84 $this->assertEquals('tehiing', (string) $this->body); 85 } 86 87 public function testSkipsOverwrittenBytes() 88 { 89 $decorated = Stream::factory( 90 implode("\n", array_map(function ($n) { 91 return str_pad($n, 4, '0', STR_PAD_LEFT); 92 }, range(0, 25))) 93 ); 94 95 $body = new CachingStream($decorated); 96 97 $skipReadBytes = new ReflectionProperty(CachingStream::class, 'skipReadBytes'); 98 $skipReadBytes->setAccessible(true); 99 100 $this->assertEquals("0000\n", Utils::readline($body)); 101 $this->assertEquals("0001\n", Utils::readline($body)); 102 // Write over part of the body yet to be read, so skip some bytes 103 $this->assertEquals(5, $body->write("TEST\n")); 104 $this->assertEquals(5, $skipReadBytes->getValue($body)); 105 // Read, which skips bytes, then reads 106 $this->assertEquals("0003\n", Utils::readline($body)); 107 $this->assertEquals(0, $skipReadBytes->getValue($body)); 108 $this->assertEquals("0004\n", Utils::readline($body)); 109 $this->assertEquals("0005\n", Utils::readline($body)); 110 111 // Overwrite part of the cached body (so don't skip any bytes) 112 $body->seek(5); 113 $this->assertEquals(5, $body->write("ABCD\n")); 114 $this->assertEquals(0, $skipReadBytes->getValue($body)); 115 $this->assertEquals("TEST\n", Utils::readline($body)); 116 $this->assertEquals("0003\n", Utils::readline($body)); 117 $this->assertEquals("0004\n", Utils::readline($body)); 118 $this->assertEquals("0005\n", Utils::readline($body)); 119 $this->assertEquals("0006\n", Utils::readline($body)); 120 $this->assertEquals(5, $body->write("1234\n")); 121 $this->assertEquals(5, $skipReadBytes->getValue($body)); 122 123 // Seek to 0 and ensure the overwritten bit is replaced 124 $body->seek(0); 125 $this->assertEquals("0000\nABCD\nTEST\n0003\n0004\n0005\n0006\n1234\n0008\n0009\n", $body->read(50)); 126 127 // Ensure that casting it to a string does not include the bit that was overwritten 128 $this->assertStringContainsString("0000\nABCD\nTEST\n0003\n0004\n0005\n0006\n1234\n0008\n0009\n", (string) $body); 129 } 130 131 public function testClosesBothStreams() 132 { 133 $s = fopen('php://temp', 'r'); 134 $a = Stream::factory($s); 135 $d = new CachingStream($a); 136 $d->close(); 137 $this->assertFalse(is_resource($s)); 138 } 139} 140