diff --git a/composer.json b/composer.json index c2151fb854..14c245db7f 100644 --- a/composer.json +++ b/composer.json @@ -90,6 +90,8 @@ "tests/data/register_command/examples", "tests/data/DummyClass.php", "tests/data/DummyOverloadableClass.php", + "tests/data/InvalidClass.php", + "tests/data/InvalidChildClass.php", "tests/data/services/UserModel.php", "tests/data/services/UserService.php", "tests/unit/Codeception/Command/BaseCommandRunner.php", diff --git a/src/Codeception/Exception/TestParseException.php b/src/Codeception/Exception/TestParseException.php index 4e2df4cb13..dc6a3014f2 100644 --- a/src/Codeception/Exception/TestParseException.php +++ b/src/Codeception/Exception/TestParseException.php @@ -8,14 +8,23 @@ class TestParseException extends Exception { - public function __construct(string $fileName, string $errors = null, int $line = null) + public function __construct(string $fileName, string $errors = null, int $line = null, string $testFile = null) { + $fileName = self::normalizePathSeparators($fileName); $this->message = "Couldn't parse test '{$fileName}'"; if ($line !== null) { $this->message .= " on line {$line}"; } if ($errors) { - $this->message .= PHP_EOL . $errors; + $this->message .= ":" . PHP_EOL . $errors; } + if (($testFile = self::normalizePathSeparators($testFile)) && realpath($fileName) !== realpath($testFile)) { + $this->message .= PHP_EOL . "(Error occurred while parsing Test '{$testFile}')"; + } + } + + public static function normalizePathSeparators(?string $testFile): ?string + { + return str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $testFile); } } diff --git a/src/Codeception/Lib/Parser.php b/src/Codeception/Lib/Parser.php index 4c91a59d7f..447baced19 100644 --- a/src/Codeception/Lib/Parser.php +++ b/src/Codeception/Lib/Parser.php @@ -100,7 +100,7 @@ public static function load(string $file): void try { self::includeFile($file); } catch (ParseError $e) { - throw new TestParseException($file, $e->getMessage(), $e->getLine()); + throw new TestParseException($e->getFile(), $e->getMessage(), $e->getLine(), $file); } catch (Exception) { // file is valid otherwise } diff --git a/tests/data/InvalidChildClass.php b/tests/data/InvalidChildClass.php new file mode 100755 index 0000000000..6557c271fb --- /dev/null +++ b/tests/data/InvalidChildClass.php @@ -0,0 +1,5 @@ +assertSame([], $classes); } + #[Group('core')] + public function testParseExceptionWithFileNameOnly() + { + $this->expectException(TestParseException::class); + $this->expectExceptionMessage("Couldn't parse test 'test.file'"); + throw new TestParseException('test.file'); + } + + #[Group('core')] + public function testParseExceptionWithErrors() + { + $this->expectException(TestParseException::class); + $this->expectExceptionMessage("Couldn't parse test 'test.file':" . PHP_EOL . "Funny error"); + throw new TestParseException('test.file', 'Funny error'); + } + + #[Group('core')] + public function testParseExceptionWithLineNumber() + { + $this->expectException(TestParseException::class); + $this->expectExceptionMessage("Couldn't parse test 'test.file' on line 27:" . PHP_EOL . "Funny error"); + throw new TestParseException('test.file', 'Funny error', 27); + } + + #[Group('core')] + public function testParseExceptionWithTestFile() + { + $this->expectException(TestParseException::class); + $this->expectExceptionMessage("Couldn't parse test 'test.file' on line 27:" . PHP_EOL . "Funny error"); + throw new TestParseException('test.file', 'Funny error', 27, 'test.file'); + } + + #[Group('core')] + public function testParseExceptionWithDifferentTestFile() + { + $this->expectException(TestParseException::class); + $this->expectExceptionMessage(sprintf( + "Couldn't parse test '%s' on line %d:" . PHP_EOL . "%s" . PHP_EOL . "(Error occurred while parsing Test '%s')", + 'parent.file', + 27, + 'Funny error', + 'test.file' + )); + throw new TestParseException('parent.file', 'Funny error', 27, 'test.file'); + } + #[Group('core')] public function testModernValidation() { - $this->expectException(\Codeception\Exception\TestParseException::class); + $this->expectException(TestParseException::class); Parser::load(codecept_data_dir('Invalid.php')); } + #[Group('core')] + public function testModernClassValidation() + { + $this->expectException(TestParseException::class); + $this->expectExceptionMessage(sprintf( + "Couldn't parse test '%s' on line %d:" . PHP_EOL . "%s", + TestParseException::normalizePathSeparators(codecept_data_dir('InvalidClass.php')), + 5, + 'syntax error, unexpected identifier "foo", expecting "function" or "const"' + )); + Parser::load(codecept_data_dir('InvalidClass.php')); + } + + #[Group('core')] + public function testModernChildClassValidation() + { + $this->expectException(TestParseException::class); + $this->expectExceptionMessage(sprintf( + "Couldn't parse test '%s' on line %d:" . PHP_EOL . "%s" . PHP_EOL . "(Error occurred while parsing Test '%s')", + TestParseException::normalizePathSeparators(codecept_data_dir('InvalidClass.php')), + 5, + 'syntax error, unexpected identifier "foo", expecting "function" or "const"', + TestParseException::normalizePathSeparators(codecept_data_dir('InvalidChildClass.php')) + )); + Parser::load(codecept_data_dir('InvalidChildClass.php')); + } + #[Group('core')] public function testClassesFromFile() {