diff --git a/tests/phpunit/functional/BaseFunctionalTestCase.php b/tests/phpunit/functional/BaseFunctionalTestCase.php new file mode 100644 index 00000000..641b9189 --- /dev/null +++ b/tests/phpunit/functional/BaseFunctionalTestCase.php @@ -0,0 +1,69 @@ +realFileName = tempnam(sys_get_temp_dir(), 'vfsstream_test_'); + } + else { + $root = vfsStream::setup(); + $this->vfsFile = vfsStream::newFile('test'); + $this->vfsFile->at($root); + } + } + + protected function getMockFileName(): string + { + if ( isset($_ENV['TEST_THE_TESTS_WITH_REAL_FILES']) ) { + return $this->realFileName; + } + else { + return $this->vfsFile->url(); + } + } + + protected function setMockFileContent(string $content): void + { + if ( isset($_ENV['TEST_THE_TESTS_WITH_REAL_FILES']) ) { + file_put_contents($this->realFileName, $content); + } + else { + $this->vfsFile->setContent($content); + } + } + + /** + * The virtual file system will clean itself up, but real files need real deletion + */ + protected function tearDown(): void + { + if ( isset($_ENV['TEST_THE_TESTS_WITH_REAL_FILES']) ) { + unlink($this->realFileName); + } + } +} \ No newline at end of file diff --git a/tests/phpunit/functional/FeofTestCase.php b/tests/phpunit/functional/FeofTestCase.php new file mode 100644 index 00000000..ee228a1a --- /dev/null +++ b/tests/phpunit/functional/FeofTestCase.php @@ -0,0 +1,221 @@ +setMockFileContent("Line 1\n"); + + $stream = fopen($this->getMockFileName(), 'rb'); + + assertFalse(feof($stream)); + } + + /** + * @test + */ + public function feofIsFalseWhenEmptyFileOpened(): void + { + $this->setMockFileContent(''); + + $stream = fopen($this->getMockFileName(), 'rb'); + + assertFalse(feof($stream)); + } + + /** + * @test + */ + public function feofIsTrueAfterEmptyFileRead(): void + { + $this->setMockFileContent(''); + + $stream = fopen($this->getMockFileName(), 'rb'); + + fgets($stream); + + assertTrue(feof($stream)); + } + + /** + * @test + */ + public function feofIsFalseWhenStreamRewound(): void + { + $this->setMockFileContent("Line 1\n"); + + $stream = fopen($this->getMockFileName(), 'rb'); + + fgets($stream); + rewind($stream); + assertFalse(feof($stream)); + } + + /** + * @test + */ + public function feofIsFalseWhenSeekingToStart(): void + { + $fp = fopen($this->getMockFileName(), 'rb'); + fseek($fp, 0, SEEK_SET); + assertFalse(feof($fp)); + fclose($fp); + } + + /** + * @test + */ + public function feofIsFalseWhenSeekingToEnd(): void + { + $fp = fopen($this->getMockFileName(), 'rb'); + fseek($fp, 0, SEEK_END); + assertFalse(feof($fp)); + fclose($fp); + } + + /** + * @test + */ + public function feofIsFalseWhenSeekingPastEnd(): void + { + $fp = fopen($this->getMockFileName(), 'rb'); + fseek($fp, 10, SEEK_END); + assertFalse(feof($fp)); + fclose($fp); + } + + /** + * @test + */ + public function feofIsFalseWhenEmptyStreamRewound(): void + { + $this->setMockFileContent(''); + + $stream = fopen($this->getMockFileName(), 'rb'); + + fgets($stream); + rewind($stream); + assertFalse(feof($stream)); + } + + /** + * @test + */ + public function feofIsFalseAfterReadingLastLine(): void + { + $this->setMockFileContent("Line 1\n"); + + $stream = fopen($this->getMockFileName(), 'rb'); + + assertThat(fgets($stream), equals("Line 1\n")); + assertFalse(feof($stream)); + } + + /** + * @test + */ + public function feofIsTrueAfterReadingBeyondLastLine(): void + { + $this->setMockFileContent("Line 1\n"); + + $stream = fopen($this->getMockFileName(), 'rb'); + + fgets($stream); + fgets($stream); + + assertTrue(feof($stream)); + } + + /** + * @test + */ + public function readLessThanSizeDoesNotReachEof(): void + { + $this->setMockFileContent('123456789'); + $stream = fopen($this->getMockFileName(), 'rb'); + fread($stream, 3); + assertFalse(feof($stream)); + } + + /** + * @test + */ + public function readSizeDoesNotReachEof(): void + { + $this->setMockFileContent('123456789'); + $stream = fopen($this->getMockFileName(), 'rb'); + fread($stream, 9); + assertFalse(feof($stream)); + } + + /** + * @test + */ + public function readSizeReachesEofOnNextRead(): void + { + $this->setMockFileContent('123456789'); + $stream = fopen($this->getMockFileName(), 'rb'); + fread($stream, 9); + fread($stream, 1); + assertTrue(feof($stream)); + } + + /** + * @test + */ + public function readMoreThanSizeReachesEof(): void + { + $this->setMockFileContent('123456789'); + $stream = fopen($this->getMockFileName(), 'rb'); + fread($stream, 10); + assertTrue(feof($stream)); + } + + /** + * @test + */ + public function readMoreThanSizeReachesEofOnNextRead(): void + { + $this->setMockFileContent('123456789'); + $stream = fopen($this->getMockFileName(), 'rb'); + fread($stream, 10); + fread($stream, 1); + assertTrue(feof($stream)); + } + + /** + * @test + */ + public function streamIsNotEofAfterTruncate(): void + { + $this->setMockFileContent('test'); + $stream = fopen($this->getMockFileName(), 'rb+'); + fread($stream, 4); + ftruncate($stream, 0); + assertFalse(feof($stream)); + } +} \ No newline at end of file diff --git a/tests/phpunit/functional/FtellTestCase.php b/tests/phpunit/functional/FtellTestCase.php new file mode 100644 index 00000000..91c92448 --- /dev/null +++ b/tests/phpunit/functional/FtellTestCase.php @@ -0,0 +1,123 @@ +setMockFileContent(''); + $stream = fopen($this->getMockFileName(), 'rb'); + assertThat(ftell($stream), equals(0)); + } + + /** + * @test + */ + public function nonEmptyFileStartsAtZero(): void + { + $this->setMockFileContent('abcdef'); + $stream = fopen($this->getMockFileName(), 'rb'); + assertThat(ftell($stream), equals(0)); + } + + /** + * @test + */ + public function fileStartsAtZeroIfOpenedWithTruncation(): void + { + $this->setMockFileContent('abcdef'); + $stream = fopen($this->getMockFileName(), 'wb'); + assertThat(ftell($stream), equals(0)); + } + + public function filePointsAtZeroAfterTruncation(): void + { + $this->setMockFileContent('abcdef'); + $stream = fopen($this->getMockFileName(), 'rb+'); + ftruncate($stream, 0); + assertThat(ftell($stream), equals(0)); + } + + public function filePointsAtEndAfterPartialTruncation(): void + { + $this->setMockFileContent('abc'); + $stream = fopen($this->getMockFileName(), 'ab+'); + ftruncate($stream, 3); + assertThat(ftell($stream), equals(3)); + } + + public function readSingleByteProgressesPointer(): void + { + $this->setMockFileContent('abc'); + $stream = fopen($this->getMockFileName(), 'rb'); + fgetc($stream); + assertThat(ftell($stream), equals(1)); + } + + public function readSingleByteTwiceProgressesPointerTwice(): void + { + $this->setMockFileContent('abc'); + $stream = fopen($this->getMockFileName(), 'rb'); + fgetc($stream); + fgetc($stream); + assertThat(ftell($stream), equals(2)); + } + + public function readAllBytesDoesntPointPastEndOfFile(): void + { + $this->setMockFileContent('abc'); + $stream = fopen($this->getMockFileName(), 'rb'); + fgetc($stream); + fgetc($stream); + fgetc($stream); + fgetc($stream); + assertThat(ftell($stream), equals(3)); + } + + public function readSeveralBytesProgressesPointer(): void + { + $this->setMockFileContent('abcdef'); + $stream = fopen($this->getMockFileName(), 'rb'); + fread($stream, 3); + assertThat(ftell($stream), equals(3)); + } + + /** + * @test + */ + public function readMoreThanSizeDoesntPointPastEndOfFile(): void + { + $this->setMockFileContent('123456789'); + $stream = fopen($this->getMockFileName(), 'rb'); + fread($stream, 10); + assertThat(ftell($stream), equals(9)); + } + + public function readLineProgressesPointer(): void + { + $this->setMockFileContent("123\n456"); + $stream = fopen($this->getMockFileName(), 'rb'); + fgets($stream); + assertThat(ftell($stream), equals(3)); + } +} \ No newline at end of file diff --git a/tests/phpunit/functional/README.md b/tests/phpunit/functional/README.md new file mode 100644 index 00000000..056fb791 --- /dev/null +++ b/tests/phpunit/functional/README.md @@ -0,0 +1,23 @@ +## Purpose + +The tests in this directory are intended to test that the behaviour of the stream wrapper +matches the behaviour of normal PHP file streams. + +The individual tests should remain completely independent of the implementation details of +the wrapper. The only library-specific logic should be in the `BaseFunctionalTestCase` class. + +## Testing the Tests + +In order to verify that the tests match the behaviour of real PHP file streams, an additional +test mode is included which runs the assertions against a real file created in the system temp +directory. + +To run these, point PHPUnit at the config file in this directory: + + vendor/bin/phpunit -c tests/phpunit/functional/testthetests.phpunit.xml + +## Adding New Types of Test + +In order for the above to work, all the setup and fixture manipulation should be maintained in +`BaseFunctionalTestCase`. Each method includes two implementations: one using `vfsStream` APIs, +and one using the real file system. \ No newline at end of file diff --git a/tests/phpunit/functional/SimultaneousStreamsTestCase.php b/tests/phpunit/functional/SimultaneousStreamsTestCase.php new file mode 100644 index 00000000..6f589070 --- /dev/null +++ b/tests/phpunit/functional/SimultaneousStreamsTestCase.php @@ -0,0 +1,156 @@ +setMockFileContent($content); + + $fp1 = fopen($this->getMockFileName(), 'rb'); + $fp2 = fopen($this->getMockFileName(), 'rb'); + + assertThat(fread($fp1, 4096), equals($content)); + assertThat(fread($fp2, 4096), equals($content)); + + fclose($fp1); + fclose($fp2); + } + + /** + * @test + */ + public function canReadTwoStreamsAlternately(): void + { + $this->setMockFileContent("Line 1\nLine 2\n"); + + $stream1 = fopen($this->getMockFileName(), 'rb'); + $stream2 = fopen($this->getMockFileName(), 'rb'); + + assertThat(fgets($stream1), equals("Line 1\n"), 'First line from first stream'); + assertThat(fgets($stream2), equals("Line 1\n"), 'First line from second stream'); + + assertThat(fgets($stream1), equals("Line 2\n"), 'Second line from first stream'); + assertThat(fgets($stream2), equals("Line 2\n"), 'Second line from second stream'); + } + + /** + * @test + */ + public function canReadTwoStreamsSequentially(): void + { + $this->setMockFileContent("Line 1\nLine 2\n"); + + $stream1 = fopen($this->getMockFileName(), 'rb'); + $stream2 = fopen($this->getMockFileName(), 'rb'); + + assertThat(fgets($stream1), equals("Line 1\n"), 'First line from first stream'); + assertThat(fgets($stream1), equals("Line 2\n"), 'Second line from first stream'); + + assertThat(fgets($stream2), equals("Line 1\n"), 'First line from second stream'); + assertThat(fgets($stream2), equals("Line 2\n"), 'Second line from second stream'); + } + + /** + * @test + */ + public function multipleWritesOnSameFileHaveDifferentPointers(): void + { + $contentA = uniqid('a'); + $contentB = uniqid('b'); + $url = $this->getMockFileName(); + + $fp1 = fopen($url, 'wb'); + $fp2 = fopen($url, 'wb'); + + fwrite($fp1, $contentA . $contentA); + fwrite($fp2, $contentB); + + fclose($fp1); + fclose($fp2); + + assertThat(file_get_contents($url), equals($contentB . $contentA)); + } + + /** + * @test + */ + public function readsAndWritesOnSameFileHaveDifferentPointers(): void + { + $contentA = uniqid('a'); + $contentB = uniqid('b'); + $url = $this->getMockFileName(); + + $fp1 = fopen($url, 'wb'); + $fp2 = fopen($url, 'rb'); + + fwrite($fp1, $contentA); + $contentBeforeWrite = fread($fp2, strlen($contentA)); + + fwrite($fp1, $contentB); + $contentAfterWrite = fread($fp2, strlen($contentB)); + + fclose($fp1); + fclose($fp2); + + assertThat($contentBeforeWrite, equals($contentA)); + assertThat($contentAfterWrite, equals($contentB)); + } + + /** + * @test + */ + public function canReadLinesWrittenBeforeReading(): void + { + $this->setMockFileContent("Line 1\nLine 2\n"); + + $writeStream = fopen($this->getMockFileName(), 'wb'); + $readStream = fopen($this->getMockFileName(), 'rb'); + + fputs($writeStream, "Line 1\n"); + fputs($writeStream, "Line 2\n"); + + assertThat(fgets($readStream), equals("Line 1\n"), 'Should be able to read first line'); + assertThat(fgets($readStream), equals("Line 2\n"), 'Should be able to read second line'); + } + + /** + * @test + */ + public function canReadLinesWrittenAfterFirstRead(): void + { + $this->setMockFileContent("Line 1\nLine 2\n"); + + $writeStream = fopen($this->getMockFileName(), 'wb'); + $readStream = fopen($this->getMockFileName(), 'rb'); + + fputs($writeStream, "Line 1\n"); + fputs($writeStream, "Line 2\n"); + assertThat(fgets($readStream), equals("Line 1\n"), 'Should be able to read first line written before first read'); + + fputs($writeStream, "Line 3\n"); + assertThat(fgets($readStream), equals("Line 2\n"), 'Should be able to read second line written before first read'); + assertThat(fgets($readStream), equals("Line 3\n"), 'Should be able to read line written after first read'); + } +} diff --git a/tests/phpunit/functional/testthetests.phpunit.xml b/tests/phpunit/functional/testthetests.phpunit.xml new file mode 100644 index 00000000..a0295541 --- /dev/null +++ b/tests/phpunit/functional/testthetests.phpunit.xml @@ -0,0 +1,42 @@ + + + + + . + + + + + + ../../../src + + + + + + + + + + + + + + + +