diff --git a/src/CodeCoverage.php b/src/CodeCoverage.php index c95d70dd5..80a8e9c80 100644 --- a/src/CodeCoverage.php +++ b/src/CodeCoverage.php @@ -9,6 +9,7 @@ */ namespace SebastianBergmann\CodeCoverage; +use OutOfBoundsException; use PHPUnit\Framework\TestCase; use PHPUnit\Runner\PhptTestCase; use PHPUnit\Util\Test; @@ -611,7 +612,17 @@ private function getLinesToBeIgnored(string $fileName): array if (isset($this->ignoredLines[$fileName])) { return $this->ignoredLines[$fileName]; } + try { + return $this->getLinesToBeIgnoredInner($fileName); + } catch (OutOfBoundsException $e) { + // This can happen with PHP_Token_Stream if the file is syntactically invalid, + // and probably affects a file that wasn't executed. + return []; + } + } + private function getLinesToBeIgnoredInner(string $fileName): array + { $this->ignoredLines[$fileName] = []; $lines = \file($fileName); diff --git a/src/Node/File.php b/src/Node/File.php index 7c62b2eda..bcb64e1a9 100644 --- a/src/Node/File.php +++ b/src/Node/File.php @@ -9,6 +9,8 @@ */ namespace SebastianBergmann\CodeCoverage\Node; +use OutOfBoundsException; + /** * Represents a file in the code coverage information tree. */ @@ -341,9 +343,14 @@ private function calculateStatistics(): void $this->codeUnitsByLine[$lineNumber] = []; } - $this->processClasses($tokens); - $this->processTraits($tokens); - $this->processFunctions($tokens); + try { + $this->processClasses($tokens); + $this->processTraits($tokens); + $this->processFunctions($tokens); + } catch (OutOfBoundsException $e) { + // This can happen with PHP_Token_Stream if the file is syntactically invalid, + // and probably affects a file that wasn't executed. + } unset($tokens); foreach (\range(1, $this->linesOfCode['loc']) as $lineNumber) { diff --git a/tests/TestCase.php b/tests/TestCase.php index 871ee473d..e93bb4e34 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -374,4 +374,27 @@ protected function setUpXdebugStubForClassWithAnonymousFunction() return $stub; } + + protected function getCoverageForCrashParsing() + { + $filter = new Filter; + $filter->addFileToWhitelist(TEST_FILES_PATH . 'Crash.php'); + + // This is a file with invalid syntax, so it isn't executed. + return new CodeCoverage( + $this->setUpXdebugStubForCrashParsing(), + $filter + ); + } + + protected function setUpXdebugStubForCrashParsing() + { + $stub = $this->createMock(Driver::class); + + $stub->expects($this->any()) + ->method('stop') + ->will($this->returnValue([])); + return $stub; + } + } diff --git a/tests/_files/Crash.php b/tests/_files/Crash.php new file mode 100644 index 000000000..0e91b42b0 --- /dev/null +++ b/tests/_files/Crash.php @@ -0,0 +1,2 @@ +assertEquals([], $root->getFunctions()); } + public function testNotCrashParsing() + { + $coverage = $this->getCoverageForCrashParsing(); + $root = $coverage->getReport(); + + $expectedPath = rtrim(TEST_FILES_PATH, DIRECTORY_SEPARATOR); + $this->assertEquals($expectedPath, $root->getName()); + $this->assertEquals($expectedPath, $root->getPath()); + $this->assertEquals(2, $root->getNumExecutableLines()); + $this->assertEquals(0, $root->getNumExecutedLines()); + $data = $coverage->getData(); + $expectedFile = $expectedPath . DIRECTORY_SEPARATOR . 'Crash.php'; + $this->assertSame([$expectedFile => [1 => [], 2 => []]], $data); + } + public function testBuildDirectoryStructure() { $method = new \ReflectionMethod( diff --git a/tests/tests/FilterTest.php b/tests/tests/FilterTest.php index 5663a05e7..4c90fca65 100644 --- a/tests/tests/FilterTest.php +++ b/tests/tests/FilterTest.php @@ -52,6 +52,7 @@ protected function setUp() TEST_FILES_PATH . 'CoverageTwoDefaultClassAnnotations.php', TEST_FILES_PATH . 'CoveredClass.php', TEST_FILES_PATH . 'CoveredFunction.php', + TEST_FILES_PATH . 'Crash.php', TEST_FILES_PATH . 'NamespaceCoverageClassExtendedTest.php', TEST_FILES_PATH . 'NamespaceCoverageClassTest.php', TEST_FILES_PATH . 'NamespaceCoverageCoversClassPublicTest.php',