~ancarda/psr7-string-stream

ref: 613485cfa5ed04fe26f6b60807209e098a97ca87 psr7-string-stream/tests/StringStreamTest.php -rw-r--r-- 8.3 KiB
613485cfMark Dain Throw StreamUnusableException after close/detach 1 year, 11 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
<?php

declare(strict_types=1);

namespace Tests;

use Ancarda\Psr7\StringStream\StreamAlreadyClosedException;
use Ancarda\Psr7\StringStream\StreamUnusableException;
use Ancarda\Psr7\StringStream\StringStream;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\StreamInterface;

class StringStreamTest extends TestCase
{
    public function testRead(): void
    {
        $stringStream = new StringStream('hello world');

        static::assertTrue($stringStream->isReadable());

        // getContents will read until the end, so the next time round it should be empty
        static::assertSame('hello world', $stringStream->getContents());
        static::assertSame('', $stringStream->getContents());

        // Simple functions
        static::assertSame(11, $stringStream->getSize());

        // __toString always returns everything
        static::assertSame('hello world', (string) $stringStream);
        static::assertSame('hello world', (string) $stringStream);
    }

    public function testSeeking(): void
    {
        $stringStream = new StringStream('hello world');

        static::assertTrue($stringStream->isSeekable());

        // We should start at 0
        static::assertSame(0, $stringStream->tell());
        static::assertFalse($stringStream->eof());

        // Read the first 5 bytes.
        static::assertSame('hello', $stringStream->read(5));
        static::assertSame(5, $stringStream->tell());
        static::assertFalse($stringStream->eof());

        // Read till the rest of the file with getContents
        static::assertSame(' world', $stringStream->getContents());
        static::assertTrue($stringStream->eof());
        static::assertSame($stringStream->getSize(), $stringStream->tell());

        // Now rewind and do it all again
        $stringStream->rewind();
        static::assertSame(0, $stringStream->tell());

        // Finally, test more esoteric seeking
        $stringStream->seek(2, SEEK_SET);
        static::assertSame(2, $stringStream->tell());
        $stringStream->seek(3, SEEK_CUR);
        static::assertSame(5, $stringStream->tell());
        $stringStream->seek(2, SEEK_END);
        static::assertSame(13, $stringStream->tell());

        // @TODO(ancarda): The specification doesn't say what to do for an unknown $whence value.
        // Given an invalid SEEK flag (oh how I wish PHP had enums...) what should the implementation do?
        // It seems it could either do nothing or do the default behavior (SEEK_SET).
        // This implementation does nothing as that appears to be the correct behavior (going by what fseek() does).
        $stringStream->rewind();
        $stringStream->seek(2);
        $stringStream->seek(5, -1);
        static::assertSame(2, $stringStream->tell()); // Do nothing when whence is invalid
    }

    public function testWriting(): void
    {
        $stringStream = new StringStream('hello world');
        static::assertTrue($stringStream->isWritable());

        // Can we write at the end of a string?
        $stringStream->seek(0, SEEK_END);
        $bytesWritten = $stringStream->write(', isn\'t it a lovely day');
        $fullString = 'hello world, isn\'t it a lovely day';
        static::assertSame(strlen($fullString), $stringStream->getSize());
        static::assertSame($fullString, (string) $stringStream);
        static::assertSame(strlen(', isn\'t it a lovely day'), $bytesWritten);

        // Can we overwrite at the start of the string to fix the capitalization?
        $stringStream->seek(0);
        $bytesWritten = $stringStream->write('H');
        $fullString = 'Hello world, isn\'t it a lovely day';
        static::assertSame(strlen($fullString), $stringStream->getSize());
        static::assertSame($fullString, (string) $stringStream);
        static::assertSame(1, $bytesWritten);

        // Can we make a multi-word replacement? We'll replace 2 bytes with 0x7F (DEL) which in a
        // real world application could be filtered out as deleted bytes.
        $stringStream->seek(0);
        $bytesWritten = $stringStream->write('Hey' . chr(127) . chr(127));
        $fullString = 'Hey' . chr(127) . chr(127) . ' world, isn\'t it a lovely day';
        static::assertSame(strlen($fullString), $stringStream->getSize());
        static::assertSame($fullString, (string) $stringStream);
        static::assertSame(5, $bytesWritten);

        // Finally, can we replace and append?
        $stringStream->seek(31);
        $bytesWritten = $stringStream->write('evening?');
        $fullString = 'Hey' . chr(127) . chr(127) . ' world, isn\'t it a lovely evening?';
        static::assertSame(strlen($fullString), $stringStream->getSize());
        static::assertSame($fullString, (string) $stringStream);
        static::assertSame(8, $bytesWritten);
    }

    public function testOverwriteWorksCorrectly(): void
    {
        $stream = new StringStream('');
        $stream->write('abc');
        $stream->write('def');
        $stream->rewind();
        $stream->write('XXX');
        static::assertSame('XXXdef', (string) $stream);
    }

    public function testMiscFunctions(): void
    {
        $stringStream = new StringStream('hello world');

        // These functions do nothing as we don't use streams.
        static::assertNull($stringStream->detach());
        static::assertNull($stringStream->getMetadata());
    }

    private function checkStreamIsDead(StreamInterface $stream): void
    {
        static::assertSame(0, $stream->getSize(), 'Closed/detached streams have no data');
        static::assertFalse($stream->isReadable(), 'Closed/detached streams cannot be read.');
        static::assertFalse($stream->isWritable(), 'Closed/detached streams cannot be written to.');
        static::assertFalse($stream->isSeekable(), 'Closed/detached streams cannot be seeked.');
        static::assertTrue($stream->eof());
        static::assertSame('', (string) $stream);
    }

    public function testDetach(): void
    {
        $stringStream = new StringStream('hello world');
        $stringStream->detach();
        $this->checkStreamIsDead($stringStream);
    }

    public function testClose(): void
    {
        $stringStream = new StringStream('hello world');
        $stringStream->close();
        $this->checkStreamIsDead($stringStream);
    }

    public function testTellThrowsExceptionAfterClose(): void
    {
        $stringStream = new StringStream('hello world');
        $stringStream->close();
        $this->expectException(StreamUnusableException::class);
        $this->expectExceptionMessage("You cannot call `tell' on this stream because it's closed.");
        $stringStream->tell();
    }

    public function testSeekThrowsExceptionAfterClose(): void
    {
        $stringStream = new StringStream('hello world');
        $stringStream->close();
        $this->expectException(StreamUnusableException::class);
        $this->expectExceptionMessage("You cannot call `seek' on this stream because it's closed.");
        $stringStream->seek(0);
    }

    public function testRewindThrowsExceptionAfterClose(): void
    {
        $stringStream = new StringStream('hello world');
        $stringStream->close();
        $this->expectException(StreamUnusableException::class);
        $this->expectExceptionMessage("You cannot call `rewind' on this stream because it's closed.");
        $stringStream->rewind();
    }

    public function testWriteThrowsExceptionAfterClose(): void
    {
        $stringStream = new StringStream('hello world');
        $stringStream->close();
        $this->expectException(StreamUnusableException::class);
        $this->expectExceptionMessage("You cannot call `write' on this stream because it's closed.");
        $stringStream->write('!');
    }

    public function testReadThrowsExceptionAfterClose(): void
    {
        $stringStream = new StringStream('hello world');
        $stringStream->close();
        $this->expectException(StreamUnusableException::class);
        $this->expectExceptionMessage("You cannot call `read' on this stream because it's closed.");
        $stringStream->read(1);
    }

    public function testGetContentsThrowsExceptionAfterClose(): void
    {
        $stringStream = new StringStream('hello world');
        $stringStream->close();
        $this->expectException(StreamUnusableException::class);
        $this->expectExceptionMessage("You cannot call `getContents' on this stream because it's closed.");
        $stringStream->getContents();
    }
}