StreamTest.php 16.7 KB
Newer Older
1
<?php
2

3
4
declare(strict_types=1);

5
6
7
8
9
10
11
12
13
14
15
16
17
/*
 * This file is part of the TYPO3 CMS project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

18
19
namespace TYPO3\CMS\Core\Tests\Unit\Http;

20
use TYPO3\CMS\Core\Core\Environment;
21
use TYPO3\CMS\Core\Http\Stream;
22
use TYPO3\CMS\Core\Utility\GeneralUtility;
23
use TYPO3\CMS\Core\Utility\StringUtility;
24
use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
25
26

/**
27
 * Test case
28
29
30
 *
 * Adapted from https://github.com/phly/http/
 */
31
class StreamTest extends UnitTestCase
32
{
33
    protected ?Stream $stream;
34

35
    protected function setUp(): void
36
    {
37
        parent::setUp();
38
39
40
        $this->stream = new Stream('php://memory', 'wb+');
    }

41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
    /**
     * Helper method to create a random directory and return the path.
     * The path will be registered for deletion upon test ending
     *
     * @param string $prefix
     * @return string
     */
    protected function getTestDirectory(string $prefix = 'root_'): string
    {
        $path = Environment::getVarPath() . '/tests/' . StringUtility::getUniqueId($prefix);
        $this->testFilesToDelete[] = $path;
        GeneralUtility::mkdir_deep($path);
        return $path;
    }

56
57
58
    /**
     * @test
     */
59
    public function canInstantiateWithStreamIdentifier(): void
60
    {
61
        self::assertInstanceOf(Stream::class, $this->stream);
62
63
64
65
66
    }

    /**
     * @test
     */
67
    public function canInstantiateWithStreamResource(): void
68
69
70
    {
        $resource = fopen('php://memory', 'wb+');
        $stream = new Stream($resource);
71
        self::assertInstanceOf(Stream::class, $stream);
72
73
74
75
76
    }

    /**
     * @test
     */
77
    public function isReadableReturnsFalseIfStreamIsNotReadable(): void
78
    {
79
        $fileName = $this->getTestDirectory() . '/' . StringUtility::getUniqueId('test_');
80
81
        touch($fileName);
        $stream = new Stream($fileName, 'w');
82
        self::assertFalse($stream->isReadable());
83
84
85
86
87
    }

    /**
     * @test
     */
88
    public function isWritableReturnsFalseIfStreamIsNotWritable(): void
89
90
    {
        $stream = new Stream('php://memory', 'r');
91
        self::assertFalse($stream->isWritable());
92
93
94
95
96
    }

    /**
     * @test
     */
97
    public function toStringRetrievesFullContentsOfStream(): void
98
99
100
    {
        $message = 'foo bar';
        $this->stream->write($message);
101
        self::assertEquals($message, (string)$this->stream);
102
103
104
105
106
    }

    /**
     * @test
     */
107
    public function detachReturnsResource(): void
108
109
110
    {
        $resource = fopen('php://memory', 'wb+');
        $stream = new Stream($resource);
111
        self::assertSame($resource, $stream->detach());
112
113
114
115
116
    }

    /**
     * @test
     */
117
    public function constructorRaisesExceptionWhenPassingInvalidStreamResource(): void
118
    {
119
        $this->expectException(\InvalidArgumentException::class);
120
        new Stream(['  THIS WILL NOT WORK  ']);
121
122
123
124
125
    }

    /**
     * @test
     */
126
    public function toStringSerializationReturnsEmptyStringWhenStreamIsNotReadable(): void
127
    {
128
        $fileName = $this->getTestDirectory() . '/' . StringUtility::getUniqueId('test_');
129
130
131
132
        touch($fileName);
        file_put_contents($fileName, 'FOO BAR');
        $stream = new Stream($fileName, 'w');

133
        self::assertEquals('', $stream->__toString());
134
135
136
137
138
    }

    /**
     * @test
     */
139
    public function closeClosesResource(): void
140
    {
141
        $fileName = $this->getTestDirectory() . '/' . StringUtility::getUniqueId('test_');
142
143
144
145
        touch($fileName);
        $resource = fopen($fileName, 'wb+');
        $stream = new Stream($resource);
        $stream->close();
146
147
148
149
150

        // Testing with a variable here, otherwise the suggested assertion would be assertIsNotResource
        // which fails.
        $isResource = is_resource($resource);
        self::assertFalse($isResource);
151
152
153
154
155
    }

    /**
     * @test
     */
156
    public function closeUnsetsResource(): void
157
    {
158
        $fileName = $this->getTestDirectory() . '/' . StringUtility::getUniqueId('test_');
159
160
161
162
163
        touch($fileName);
        $resource = fopen($fileName, 'wb+');
        $stream = new Stream($resource);
        $stream->close();

164
        self::assertNull($stream->detach());
165
166
167
168
169
    }

    /**
     * @test
     */
170
    public function closeDoesNothingAfterDetach(): void
171
    {
172
        $fileName = $this->getTestDirectory() . '/' . StringUtility::getUniqueId('test_');
173
174
175
176
177
178
        touch($fileName);
        $resource = fopen($fileName, 'wb+');
        $stream = new Stream($resource);
        $detached = $stream->detach();

        $stream->close();
179
        self::assertIsResource($detached);
180
        self::assertSame($resource, $detached);
181
182
183
184
185
    }

    /**
     * @test
     */
186
    public function getSizeReportsNullWhenNoResourcePresent(): void
187
188
    {
        $this->stream->detach();
189
        self::assertNull($this->stream->getSize());
190
191
192
193
194
    }

    /**
     * @test
     */
195
    public function tellReportsCurrentPositionInResource(): void
196
    {
197
        $fileName = $this->getTestDirectory() . '/' . StringUtility::getUniqueId('test_');
198
199
200
201
202
203
        file_put_contents($fileName, 'FOO BAR');
        $resource = fopen($fileName, 'wb+');
        $stream = new Stream($resource);

        fseek($resource, 2);

204
        self::assertEquals(2, $stream->tell());
205
206
207
208
209
    }

    /**
     * @test
     */
210
    public function tellRaisesExceptionIfResourceIsDetached(): void
211
    {
212
        $fileName = $this->getTestDirectory() . '/' . StringUtility::getUniqueId('test_');
213
214
215
216
217
218
        file_put_contents($fileName, 'FOO BAR');
        $resource = fopen($fileName, 'wb+');
        $stream = new Stream($resource);

        fseek($resource, 2);
        $stream->detach();
219
220
        $this->expectException(\RuntimeException::class);
        $this->expectExceptionCode(1436717285);
221
222
223
224
225
226
        $stream->tell();
    }

    /**
     * @test
     */
227
    public function eofReportsFalseWhenNotAtEndOfStream(): void
228
    {
229
        $fileName = $this->getTestDirectory() . '/' . StringUtility::getUniqueId('test_');
230
231
232
233
234
        file_put_contents($fileName, 'FOO BAR');
        $resource = fopen($fileName, 'wb+');
        $stream = new Stream($resource);

        fseek($resource, 2);
235
        self::assertFalse($stream->eof());
236
237
238
239
240
    }

    /**
     * @test
     */
241
    public function eofReportsTrueWhenAtEndOfStream(): void
242
    {
243
        $fileName = $this->getTestDirectory() . '/' . StringUtility::getUniqueId('test_');
244
245
246
247
248
249
250
        file_put_contents($fileName, 'FOO BAR');
        $resource = fopen($fileName, 'wb+');
        $stream = new Stream($resource);

        while (!feof($resource)) {
            fread($resource, 4096);
        }
251
        self::assertTrue($stream->eof());
252
253
254
255
256
    }

    /**
     * @test
     */
257
    public function eofReportsTrueWhenStreamIsDetached(): void
258
    {
259
        $fileName = $this->getTestDirectory() . '/' . StringUtility::getUniqueId('test_');
260
261
262
263
264
265
        file_put_contents($fileName, 'FOO BAR');
        $resource = fopen($fileName, 'wb+');
        $stream = new Stream($resource);

        fseek($resource, 2);
        $stream->detach();
266
        self::assertTrue($stream->eof());
267
268
269
270
271
    }

    /**
     * @test
     */
272
    public function isSeekableReturnsTrueForReadableStreams(): void
273
    {
274
        $fileName = $this->getTestDirectory() . '/' . StringUtility::getUniqueId('test_');
275
276
277
        file_put_contents($fileName, 'FOO BAR');
        $resource = fopen($fileName, 'wb+');
        $stream = new Stream($resource);
278
        self::assertTrue($stream->isSeekable());
279
280
281
282
283
    }

    /**
     * @test
     */
284
    public function isSeekableReturnsFalseForDetachedStreams(): void
285
    {
286
        $fileName = $this->getTestDirectory() . '/' . StringUtility::getUniqueId('test_');
287
288
289
290
        file_put_contents($fileName, 'FOO BAR');
        $resource = fopen($fileName, 'wb+');
        $stream = new Stream($resource);
        $stream->detach();
291
        self::assertFalse($stream->isSeekable());
292
293
294
295
296
    }

    /**
     * @test
     */
297
    public function seekAdvancesToGivenOffsetOfStream(): void
298
    {
299
        $fileName = $this->getTestDirectory() . '/' . StringUtility::getUniqueId('test_');
300
301
302
303
        file_put_contents($fileName, 'FOO BAR');
        $resource = fopen($fileName, 'wb+');
        $stream = new Stream($resource);
        $stream->seek(2);
304
        self::assertEquals(2, $stream->tell());
305
306
307
308
309
    }

    /**
     * @test
     */
310
    public function rewindResetsToStartOfStream(): void
311
    {
312
        $fileName = $this->getTestDirectory() . '/' . StringUtility::getUniqueId('test_');
313
314
315
316
317
        file_put_contents($fileName, 'FOO BAR');
        $resource = fopen($fileName, 'wb+');
        $stream = new Stream($resource);
        $stream->seek(2);
        $stream->rewind();
318
        self::assertEquals(0, $stream->tell());
319
320
321
322
323
    }

    /**
     * @test
     */
324
    public function seekRaisesExceptionWhenStreamIsDetached(): void
325
    {
326
        $fileName = $this->getTestDirectory() . '/' . StringUtility::getUniqueId('test_');
327
328
329
330
        file_put_contents($fileName, 'FOO BAR');
        $resource = fopen($fileName, 'wb+');
        $stream = new Stream($resource);
        $stream->detach();
331
332
        $this->expectException(\RuntimeException::class);
        $this->expectExceptionCode(1436717287);
333
334
335
336
337
338
        $stream->seek(2);
    }

    /**
     * @test
     */
339
    public function isWritableReturnsFalseWhenStreamIsDetached(): void
340
    {
341
        $fileName = $this->getTestDirectory() . '/' . StringUtility::getUniqueId('test_');
342
343
344
345
        file_put_contents($fileName, 'FOO BAR');
        $resource = fopen($fileName, 'wb+');
        $stream = new Stream($resource);
        $stream->detach();
346
        self::assertFalse($stream->isWritable());
347
348
349
350
351
    }

    /**
     * @test
     */
352
    public function writeRaisesExceptionWhenStreamIsDetached(): void
353
    {
354
        $fileName = $this->getTestDirectory() . '/' . StringUtility::getUniqueId('test_');
355
356
357
358
        file_put_contents($fileName, 'FOO BAR');
        $resource = fopen($fileName, 'wb+');
        $stream = new Stream($resource);
        $stream->detach();
359
360
        $this->expectException(\RuntimeException::class);
        $this->expectExceptionCode(1436717290);
361
362
363
364
365
366
        $stream->write('bar');
    }

    /**
     * @test
     */
367
    public function isReadableReturnsFalseWhenStreamIsDetached(): void
368
    {
369
        $fileName = $this->getTestDirectory() . '/' . StringUtility::getUniqueId('test_');
370
371
372
373
        file_put_contents($fileName, 'FOO BAR');
        $resource = fopen($fileName, 'wb+');
        $stream = new Stream($resource);
        $stream->detach();
374
        self::assertFalse($stream->isReadable());
375
376
377
378
379
    }

    /**
     * @test
     */
380
    public function readRaisesExceptionWhenStreamIsDetached(): void
381
    {
382
        $fileName = $this->getTestDirectory() . '/' . StringUtility::getUniqueId('test_');
383
384
385
386
        file_put_contents($fileName, 'FOO BAR');
        $resource = fopen($fileName, 'r');
        $stream = new Stream($resource);
        $stream->detach();
387
388
        $this->expectException(\RuntimeException::class);
        $this->expectExceptionCode(1436717292);
389
390
391
392
393
394
        $stream->read(4096);
    }

    /**
     * @test
     */
395
    public function readReturnsEmptyStringWhenAtEndOfFile(): void
396
    {
397
        $fileName = $this->getTestDirectory() . '/' . StringUtility::getUniqueId('test_');
398
399
400
401
402
403
        file_put_contents($fileName, 'FOO BAR');
        $resource = fopen($fileName, 'r');
        $stream = new Stream($resource);
        while (!feof($resource)) {
            fread($resource, 4096);
        }
404
        self::assertEquals('', $stream->read(4096));
405
406
407
408
409
    }

    /**
     * @test
     */
410
    public function getContentsReturnsEmptyStringIfStreamIsNotReadable(): void
411
    {
412
        $fileName = $this->getTestDirectory() . '/' . StringUtility::getUniqueId('test_');
413
414
415
        file_put_contents($fileName, 'FOO BAR');
        $resource = fopen($fileName, 'w');
        $stream = new Stream($resource);
416
        self::assertEquals('', $stream->getContents());
417
418
419
420
421
    }

    /**
     * @return array
     */
422
    public function invalidResourcesDataProvider(): array
423
424
425
426
427
428
429
430
431
432
433
    {
        $fileName = tempnam(sys_get_temp_dir(), 'PHLY');
        $this->testFilesToDelete[] = $fileName;

        return [
            'null'                => [null],
            'false'               => [false],
            'true'                => [true],
            'int'                 => [1],
            'float'               => [1.1],
            'array'               => [[fopen($fileName, 'r+')]],
434
            'object'              => [(object)['resource' => fopen($fileName, 'r+')]],
435
436
437
438
439
440
441
        ];
    }

    /**
     * @dataProvider invalidResourcesDataProvider
     * @test
     */
442
    public function attachWithNonStringNonResourceRaisesExceptionByType($resource): void
443
    {
444
445
        $this->expectException(\InvalidArgumentException::class);
        $this->expectExceptionCode(1436717297);
446
447
448
        $this->stream->attach($resource);
    }

449
450
451
    /**
     * @test
     */
452
    public function attachWithNonStringNonResourceRaisesExceptionByString(): void
453
454
455
456
457
458
    {
        $this->expectException(\InvalidArgumentException::class);
        $this->expectExceptionCode(1436717296);
        $this->stream->attach('foo-bar-baz');
    }

459
460
461
    /**
     * @test
     */
462
    public function attachWithResourceAttachesResource(): void
463
    {
464
        $fileName = $this->getTestDirectory() . '/' . StringUtility::getUniqueId('test_');
465
466
467
468
469
470
471
        touch($fileName);
        $resource = fopen($fileName, 'r+');
        $this->stream->attach($resource);

        $r = new \ReflectionProperty($this->stream, 'resource');
        $r->setAccessible(true);
        $test = $r->getValue($this->stream);
472
        self::assertSame($resource, $test);
473
474
475
476
477
    }

    /**
     * @test
     */
478
    public function attachWithStringRepresentingResourceCreatesAndAttachesResource(): void
479
    {
480
        $fileName = $this->getTestDirectory() . '/' . StringUtility::getUniqueId('test_');
481
482
483
484
485
486
487
        touch($fileName);
        $this->stream->attach($fileName);

        $resource = fopen($fileName, 'r+');
        fwrite($resource, 'FooBar');

        $this->stream->rewind();
488
        $test = (string)$this->stream;
489
        self::assertEquals('FooBar', $test);
490
491
492
493
494
    }

    /**
     * @test
     */
495
    public function getContentsShouldGetFullStreamContents(): void
496
    {
497
        $fileName = $this->getTestDirectory() . '/' . StringUtility::getUniqueId('test_');
498
499
500
501
502
503
504
505
506
        touch($fileName);
        $resource = fopen($fileName, 'r+');
        $this->stream->attach($resource);

        fwrite($resource, 'FooBar');

        // rewind, because current pointer is at end of stream!
        $this->stream->rewind();
        $test = $this->stream->getContents();
507
        self::assertEquals('FooBar', $test);
508
509
510
511
512
    }

    /**
     * @test
     */
513
    public function getContentsShouldReturnStreamContentsFromCurrentPointer(): void
514
    {
515
        $fileName = $this->getTestDirectory() . '/' . StringUtility::getUniqueId('test_');
516
517
518
519
520
521
522
523
524
        touch($fileName);
        $resource = fopen($fileName, 'r+');
        $this->stream->attach($resource);

        fwrite($resource, 'FooBar');

        // seek to position 3
        $this->stream->seek(3);
        $test = $this->stream->getContents();
525
        self::assertEquals('Bar', $test);
526
527
528
529
530
    }

    /**
     * @test
     */
531
    public function getMetadataReturnsAllMetadataWhenNoKeyPresent(): void
532
    {
533
        $fileName = $this->getTestDirectory() . '/' . StringUtility::getUniqueId('test_');
534
535
536
537
538
539
540
        touch($fileName);
        $resource = fopen($fileName, 'r+');
        $this->stream->attach($resource);

        $expected = stream_get_meta_data($resource);
        $test = $this->stream->getMetadata();

541
        self::assertEquals($expected, $test);
542
543
544
545
546
    }

    /**
     * @test
     */
547
    public function getMetadataReturnsDataForSpecifiedKey(): void
548
    {
549
        $fileName = $this->getTestDirectory() . '/' . StringUtility::getUniqueId('test_');
550
551
552
553
554
555
556
557
558
        touch($fileName);
        $resource = fopen($fileName, 'r+');
        $this->stream->attach($resource);

        $metadata = stream_get_meta_data($resource);
        $expected = $metadata['uri'];

        $test = $this->stream->getMetadata('uri');

559
        self::assertEquals($expected, $test);
560
561
562
563
564
    }

    /**
     * @test
     */
565
    public function getMetadataReturnsNullIfNoDataExistsForKey(): void
566
    {
567
        $fileName = $this->getTestDirectory() . '/' . StringUtility::getUniqueId('test_');
568
569
570
571
        touch($fileName);
        $resource = fopen($fileName, 'r+');
        $this->stream->attach($resource);

572
        self::assertNull($this->stream->getMetadata('TOTALLY_MADE_UP'));
573
574
575
576
577
    }

    /**
     * @test
     */
578
    public function getSizeReturnsStreamSize(): void
579
580
581
582
    {
        $resource = fopen(__FILE__, 'r');
        $expected = fstat($resource);
        $stream = new Stream($resource);
583
        self::assertEquals($expected['size'], $stream->getSize());
584
    }
585
}