From 7de92ce6fefd64c53e9671bcfb0102b76fd0c97b Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Mon, 17 Aug 2020 12:40:46 +0200 Subject: [PATCH 01/19] CS Fix: explicit variables in string --- bin/phpunit-wrapper.php | 6 +++--- src/Coverage/CoverageMerger.php | 2 +- src/Logging/JUnit/Reader.php | 4 ++-- src/Logging/MetaProvider.php | 2 +- src/Parser/ParsedObject.php | 2 +- src/Runners/PHPUnit/ExecutableTest.php | 2 +- src/Runners/PHPUnit/Options.php | 4 ++-- src/Runners/PHPUnit/Runner.php | 2 +- src/Runners/PHPUnit/SuiteLoader.php | 2 +- src/Runners/PHPUnit/Worker/BaseWorker.php | 2 +- src/Runners/PHPUnit/Worker/WrapperWorker.php | 2 +- src/Runners/PHPUnit/WrapperRunner.php | 4 ++-- test/Functional/FunctionalTestBase.php | 4 ++-- test/Functional/SkippedOrIncompleteTest.php | 2 +- test/Functional/TestGenerator.php | 2 +- test/TestBase.php | 2 +- test/Unit/Parser/ParsedObjectTest.php | 2 +- test/Unit/Runners/PHPUnit/ResultPrinterTest.php | 2 +- test/Unit/Runners/PHPUnit/SuiteLoaderTest.php | 2 +- 19 files changed, 25 insertions(+), 25 deletions(-) diff --git a/bin/phpunit-wrapper.php b/bin/phpunit-wrapper.php index 799aa718..895d3907 100644 --- a/bin/phpunit-wrapper.php +++ b/bin/phpunit-wrapper.php @@ -54,12 +54,12 @@ $arguments = unserialize($command); $command = implode(' ', $arguments); - echo "Executing: $command\n"; + echo "Executing: {$command}\n"; $info = []; $info[] = 'Time: ' . (new DateTime())->format(DateTime::RFC3339); - $info[] = "Iteration: $i"; - $info[] = "Command: $command"; + $info[] = "Iteration: {$i}"; + $info[] = "Command: {$command}"; $info[] = PHP_EOL; $infoText = implode(PHP_EOL, $info) . PHP_EOL; $logInfo($infoText); diff --git a/src/Coverage/CoverageMerger.php b/src/Coverage/CoverageMerger.php index b21ff411..dc44b106 100644 --- a/src/Coverage/CoverageMerger.php +++ b/src/Coverage/CoverageMerger.php @@ -69,7 +69,7 @@ public function addCoverageFromFile(?string $coverageFile): void } throw new RuntimeException( - "Coverage file $coverageFile is empty. " . $extra + "Coverage file {$coverageFile} is empty. " . $extra ); } diff --git a/src/Logging/JUnit/Reader.php b/src/Logging/JUnit/Reader.php index ce458a58..2434c638 100644 --- a/src/Logging/JUnit/Reader.php +++ b/src/Logging/JUnit/Reader.php @@ -47,13 +47,13 @@ final class Reader extends MetaProvider public function __construct(string $logFile) { if (! file_exists($logFile)) { - throw new InvalidArgumentException("Log file $logFile does not exist"); + throw new InvalidArgumentException("Log file {$logFile} does not exist"); } $this->logFile = $logFile; if (filesize($logFile) === 0) { throw new InvalidArgumentException( - "Log file $logFile is empty. This means a PHPUnit process has crashed." + "Log file {$logFile} is empty. This means a PHPUnit process has crashed." ); } diff --git a/src/Logging/MetaProvider.php b/src/Logging/MetaProvider.php index 9d5e21ef..50165080 100644 --- a/src/Logging/MetaProvider.php +++ b/src/Logging/MetaProvider.php @@ -58,7 +58,7 @@ final public function __call(string $method, array $args) return $this->getMessages($type); } - throw new RuntimeException("Method $method uknown"); + throw new RuntimeException("Method {$method} uknown"); } /** diff --git a/src/Parser/ParsedObject.php b/src/Parser/ParsedObject.php index 728c65fa..bac1df8f 100644 --- a/src/Parser/ParsedObject.php +++ b/src/Parser/ParsedObject.php @@ -47,7 +47,7 @@ final public function hasAnnotation(string $annotation, ?string $value = null): $pattern = sprintf( '/@%s%s/', $annotation, - $value !== null ? "[\s]+$value" : '\b' + $value !== null ? "[\\s]+{$value}" : '\b' ); return preg_match($pattern, $this->docBlock) === 1; diff --git a/src/Runners/PHPUnit/ExecutableTest.php b/src/Runners/PHPUnit/ExecutableTest.php index d6f55f1b..6862ad86 100644 --- a/src/Runners/PHPUnit/ExecutableTest.php +++ b/src/Runners/PHPUnit/ExecutableTest.php @@ -131,7 +131,7 @@ final public function commandArguments(string $binary, array $options = [], ?arr } foreach ($options as $key => $value) { - $arguments[] = "--$key"; + $arguments[] = "--{$key}"; if ($value === null) { continue; } diff --git a/src/Runners/PHPUnit/Options.php b/src/Runners/PHPUnit/Options.php index 81ea5a48..a5a4e346 100644 --- a/src/Runners/PHPUnit/Options.php +++ b/src/Runners/PHPUnit/Options.php @@ -597,9 +597,9 @@ private static function getPhpunitBinary(): string */ private static function vendorDir(): string { - $vendor = dirname(dirname(dirname(__DIR__))) . DIRECTORY_SEPARATOR . 'vendor'; + $vendor = dirname(__DIR__, 3) . DIRECTORY_SEPARATOR . 'vendor'; if (! file_exists($vendor)) { - $vendor = dirname(dirname(dirname(dirname(dirname(__DIR__))))); + $vendor = dirname(__DIR__, 5); } return $vendor; diff --git a/src/Runners/PHPUnit/Runner.php b/src/Runners/PHPUnit/Runner.php index a0f98beb..ed7133d6 100644 --- a/src/Runners/PHPUnit/Runner.php +++ b/src/Runners/PHPUnit/Runner.php @@ -62,7 +62,7 @@ public function run(): void } } catch (Throwable $e) { if ($this->options->verbose() > 0) { - $this->output->writeln("An error for $key: {$e->getMessage()}"); + $this->output->writeln("An error for {$key}: {$e->getMessage()}"); $this->output->writeln("Command: {$test->getExecutableTest()->getLastCommand()}"); $this->output->writeln('StdErr: ' . $test->getStderr()); $this->output->writeln('StdOut: ' . $test->getStdout()); diff --git a/src/Runners/PHPUnit/SuiteLoader.php b/src/Runners/PHPUnit/SuiteLoader.php index eba6722a..ceb44f76 100644 --- a/src/Runners/PHPUnit/SuiteLoader.php +++ b/src/Runners/PHPUnit/SuiteLoader.php @@ -136,7 +136,7 @@ public function load(?string $path = null): void }, $testSuiteCollection); foreach ($this->options->testsuite() as $testSuiteName) { if (! in_array($testSuiteName, $suitesName, true)) { - throw new RuntimeException("Suite path $testSuiteName could not be found"); + throw new RuntimeException("Suite path {$testSuiteName} could not be found"); } } diff --git a/src/Runners/PHPUnit/Worker/BaseWorker.php b/src/Runners/PHPUnit/Worker/BaseWorker.php index 6482a21d..5e725eef 100644 --- a/src/Runners/PHPUnit/Worker/BaseWorker.php +++ b/src/Runners/PHPUnit/Worker/BaseWorker.php @@ -101,7 +101,7 @@ final public function start( $pipes = []; if ($options !== null && $options->verbose() > 0) { - $this->output->writeln("Starting WrapperWorker via: $bin"); + $this->output->writeln("Starting WrapperWorker via: {$bin}"); } // Taken from \Symfony\Component\Process\Process::prepareWindowsCommandLine diff --git a/src/Runners/PHPUnit/Worker/WrapperWorker.php b/src/Runners/PHPUnit/Worker/WrapperWorker.php index 1f19a584..ad037e02 100644 --- a/src/Runners/PHPUnit/Worker/WrapperWorker.php +++ b/src/Runners/PHPUnit/Worker/WrapperWorker.php @@ -62,7 +62,7 @@ public function assign(ExecutableTest $test, string $phpunit, array $phpunitOpti $commandArguments = $test->commandArguments($phpunit, $phpunitOptions, $options->passthru()); $command = implode(' ', $commandArguments); if ($options->verbose() > 0) { - $this->output->write("\nExecuting test via: $command\n"); + $this->output->write("\nExecuting test via: {$command}\n"); } $test->setLastCommand($command); diff --git a/src/Runners/PHPUnit/WrapperRunner.php b/src/Runners/PHPUnit/WrapperRunner.php index 0f2cf8f1..bc0a39e0 100644 --- a/src/Runners/PHPUnit/WrapperRunner.php +++ b/src/Runners/PHPUnit/WrapperRunner.php @@ -89,7 +89,7 @@ private function assignAllPendingTests(): void if ($this->options->verbose() > 0) { $worker->stop(); $this->output->writeln( - "Error while assigning pending tests for worker $key: {$e->getMessage()}" + "Error while assigning pending tests for worker {$key}: {$e->getMessage()}" ); $this->output->write($worker->getCrashReport()); } @@ -182,7 +182,7 @@ private function waitForAllToFinish(): void if ($this->options->verbose() > 0) { $worker->stop(); unset($toStop[$index]); - $this->output->writeln("Error while waiting to finish for worker $index: {$e->getMessage()}"); + $this->output->writeln("Error while waiting to finish for worker {$index}: {$e->getMessage()}"); $this->output->write($worker->getCrashReport()); } diff --git a/test/Functional/FunctionalTestBase.php b/test/Functional/FunctionalTestBase.php index 995d286d..2d1affd8 100644 --- a/test/Functional/FunctionalTestBase.php +++ b/test/Functional/FunctionalTestBase.php @@ -17,7 +17,7 @@ final protected function fixture(string $fixture): string { $fixture = FIXTURES . DS . $fixture; if (! file_exists($fixture)) { - throw new InvalidArgumentException("Fixture $fixture not found"); + throw new InvalidArgumentException("Fixture {$fixture} not found"); } return $fixture; @@ -66,6 +66,6 @@ final protected function guardSqliteExtensionLoaded(): void return; } - static::markTestSkipped("Skipping test: Extension '$sqliteExtension' not found."); + static::markTestSkipped("Skipping test: Extension '{$sqliteExtension}' not found."); } } diff --git a/test/Functional/SkippedOrIncompleteTest.php b/test/Functional/SkippedOrIncompleteTest.php index 09b7b7fd..4f8aab2b 100644 --- a/test/Functional/SkippedOrIncompleteTest.php +++ b/test/Functional/SkippedOrIncompleteTest.php @@ -118,7 +118,7 @@ private function assertContainsNSkippedTests(int $n, string $output): void static::assertEquals( $n, $numberOfS, - "The test should have skipped $n tests, instead it skipped $numberOfS, $matches[1]" + "The test should have skipped {$n} tests, instead it skipped {$numberOfS}, {$matches[1]}" ); } } diff --git a/test/Functional/TestGenerator.php b/test/Functional/TestGenerator.php index 6cd37607..b5d9ca44 100644 --- a/test/Functional/TestGenerator.php +++ b/test/Functional/TestGenerator.php @@ -43,7 +43,7 @@ private function generateTestString(string $testName, int $methods = 1): string { $namespace = sprintf('Generated%s', basename($this->path)); $php = '<' - . "?php\n\nnamespace $namespace;\n\nclass $testName extends \\PHPUnit\\Framework\\TestCase\n{\n"; + . "?php\n\nnamespace {$namespace};\n\nclass {$testName} extends \\PHPUnit\\Framework\\TestCase\n{\n"; for ($i = 0; $i < $methods; ++$i) { $php .= "\tpublic function testMethod{$i}(): void{"; diff --git a/test/TestBase.php b/test/TestBase.php index b0f9d802..38f0befe 100644 --- a/test/TestBase.php +++ b/test/TestBase.php @@ -56,7 +56,7 @@ final protected function fixture(string $fixture): string { $fixture = FIXTURES . DS . $fixture; if (! file_exists($fixture)) { - throw new InvalidArgumentException("Fixture $fixture not found"); + throw new InvalidArgumentException("Fixture {$fixture} not found"); } return $fixture; diff --git a/test/Unit/Parser/ParsedObjectTest.php b/test/Unit/Parser/ParsedObjectTest.php index 4e14533e..60efdc2d 100644 --- a/test/Unit/Parser/ParsedObjectTest.php +++ b/test/Unit/Parser/ParsedObjectTest.php @@ -14,7 +14,7 @@ final class ParsedObjectTest extends TestBase public function setUp(): void { - $this->parsedClass = new ParsedClass("/**\n * @test\n @group group1\n*\/", 'MyClass', 'My\\Name\\Space'); + $this->parsedClass = new ParsedClass("/**\n * @test\n @group group1\n*\\/", 'MyClass', 'My\\Name\\Space'); } public function testHasAnnotationReturnsTrueWhenAnnotationPresent(): void diff --git a/test/Unit/Runners/PHPUnit/ResultPrinterTest.php b/test/Unit/Runners/PHPUnit/ResultPrinterTest.php index 8f1a9cb5..85aab0f6 100644 --- a/test/Unit/Runners/PHPUnit/ResultPrinterTest.php +++ b/test/Unit/Runners/PHPUnit/ResultPrinterTest.php @@ -176,7 +176,7 @@ public function testGetHeader(): void static::assertMatchesRegularExpression( "/\n\nTime: ([.:]?[0-9]{1,3})+ ?" . '(minute|minutes|second|seconds|ms|)?,' . - " Memory:[\s][0-9]+([.][0-9]{1,2})? ?M[Bb]\n\n/", + " Memory:[\\s][0-9]+([.][0-9]{1,2})? ?M[Bb]\n\n/", $header ); } diff --git a/test/Unit/Runners/PHPUnit/SuiteLoaderTest.php b/test/Unit/Runners/PHPUnit/SuiteLoaderTest.php index 731d12f0..177d68d4 100644 --- a/test/Unit/Runners/PHPUnit/SuiteLoaderTest.php +++ b/test/Unit/Runners/PHPUnit/SuiteLoaderTest.php @@ -276,7 +276,7 @@ private function suiteByPath(string $path, array $paraSuites): Suite } } - throw new RuntimeException("Suite $path not found."); + throw new RuntimeException("Suite {$path} not found."); } /** From 7427350355e73406f70760376558a68e2ec923a3 Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Mon, 17 Aug 2020 14:37:23 +0200 Subject: [PATCH 02/19] CoverageMerger ok --- composer.json | 1 + src/Coverage/CoverageMerger.php | 57 ++---- src/Coverage/CoverageReporter.php | 2 +- src/Coverage/CoverageReporterInterface.php | 48 ----- .../Coverage/CoverageMergerTest.php | 172 ------------------ .../Coverage/CoverageReporterTest.php | 7 +- test/Functional/DataProviderTest.php | 3 + test/Functional/GroupTest.php | 3 + test/Functional/OutputTest.php | 3 + test/Functional/PHPUnitOtherWarningsTest.php | 2 + test/Functional/PHPUnitTest.php | 3 + test/Functional/PHPUnitWarningsTest.php | 2 + .../Runners/PHPUnit/RunnerIntegrationTest.php | 5 +- .../Functional/Runners/PHPUnit/WorkerTest.php | 6 +- test/Functional/SkippedOrIncompleteTest.php | 1 + test/Functional/SqliteRunnerTest.php | 3 + test/Functional/WrapperRunnerTest.php | 1 + test/TestBase.php | 49 ++--- .../Console/Commands/ParaTestCommandTest.php | 5 +- test/Unit/Coverage/CoverageMergerTest.php | 101 +++++----- test/Unit/Logging/JUnit/ReaderTest.php | 5 +- test/Unit/Logging/JUnit/WriterTest.php | 5 +- test/Unit/Logging/LogInterpreterTest.php | 3 + test/Unit/Parser/GetClassTest.php | 3 + test/Unit/Parser/ParsedClassTest.php | 5 +- test/Unit/Parser/ParsedObjectTest.php | 5 +- test/Unit/Parser/ParserTest.php | 3 + test/Unit/ResultTester.php | 2 +- .../Runners/PHPUnit/ExecutableTestChild.php | 3 + .../Runners/PHPUnit/ExecutableTestTest.php | 5 +- test/Unit/Runners/PHPUnit/OptionsTest.php | 5 +- .../Runners/PHPUnit/ResultPrinterTest.php | 3 + test/Unit/Runners/PHPUnit/RunnerTest.php | 5 +- test/Unit/Runners/PHPUnit/SuiteLoaderTest.php | 3 + test/Unit/Runners/PHPUnit/TestMethodTest.php | 3 + .../Runners/PHPUnit/WrapperRunnerTest.php | 3 + test/Unit/Util/StrTest.php | 3 + test/bootstrap.php | 1 - test/constants.php | 1 + test/tmp/.gitignore | 2 + 40 files changed, 172 insertions(+), 370 deletions(-) delete mode 100644 src/Coverage/CoverageReporterInterface.php delete mode 100644 test/Functional/Coverage/CoverageMergerTest.php create mode 100644 test/tmp/.gitignore diff --git a/composer.json b/composer.json index 8c8a222a..41db9129 100644 --- a/composer.json +++ b/composer.json @@ -42,6 +42,7 @@ "phpstan/phpstan-phpunit": "^0.12.16", "phpstan/phpstan-strict-rules": "^0.12.4", "squizlabs/php_codesniffer": "^3.5.6", + "symfony/filesystem": "^5.1.3", "thecodingmachine/phpstan-strict-rules": "^0.12.0", "vimeo/psalm": "^3.12.2" }, diff --git a/src/Coverage/CoverageMerger.php b/src/Coverage/CoverageMerger.php index dc44b106..24b93870 100644 --- a/src/Coverage/CoverageMerger.php +++ b/src/Coverage/CoverageMerger.php @@ -7,30 +7,24 @@ use RuntimeException; use SebastianBergmann\CodeCoverage\CodeCoverage; use SebastianBergmann\CodeCoverage\ProcessedCodeCoverageData; +use SebastianBergmann\Environment\Runtime; use function array_map; use function array_slice; -use function assert; -use function extension_loaded; use function filesize; -use function function_exists; -use function ini_get; -use function is_array; use function is_file; use function unlink; -use const PHP_SAPI; - final class CoverageMerger { /** @var CodeCoverage|null */ private $coverage; /** @var int */ - private $test_limit; + private $testLimit; - public function __construct(int $test_limit = 0) + public function __construct(int $testLimit) { - $this->test_limit = $test_limit; + $this->testLimit = $testLimit; } private function addCoverage(CodeCoverage $coverage): void @@ -51,26 +45,15 @@ private function addCoverage(CodeCoverage $coverage): void * * @throws RuntimeException When coverage file is empty. */ - public function addCoverageFromFile(?string $coverageFile): void + public function addCoverageFromFile(string $coverageFile): void { - if ($coverageFile === null || ! is_file($coverageFile)) { - return; - } - - if (filesize($coverageFile) === 0) { + if (! is_file($coverageFile) || filesize($coverageFile) === 0) { $extra = 'This means a PHPUnit process has crashed.'; - - $xdebug = function_exists('xdebug_get_code_coverage'); - $phpdbg = PHP_SAPI === 'phpdbg'; - $pcov = extension_loaded('pcov') && (bool) ini_get('pcov.enabled'); - - if (! $xdebug && ! $phpdbg && ! $pcov) { + if (! (new Runtime())->canCollectCodeCoverage()) { $extra = 'No coverage driver found! Enable one of Xdebug, PHPDBG or PCOV for coverage.'; } - throw new RuntimeException( - "Coverage file {$coverageFile} is empty. " . $extra - ); + throw new RuntimeException("Coverage file {$coverageFile} is empty. " . $extra); } /** @psalm-suppress UnresolvableInclude **/ @@ -79,19 +62,6 @@ public function addCoverageFromFile(?string $coverageFile): void unlink($coverageFile); } - /** - * Get coverage report generator. - */ - public function getReporter(): CoverageReporterInterface - { - assert($this->coverage !== null); - - return new CoverageReporter($this->coverage); - } - - /** - * Get CodeCoverage object. - */ public function getCodeCoverageObject(): ?CodeCoverage { return $this->coverage; @@ -99,20 +69,15 @@ public function getCodeCoverageObject(): ?CodeCoverage private function limitCoverageTests(CodeCoverage $coverage): void { - if ($this->test_limit === 0) { + if ($this->testLimit === 0) { return; } - $testLimit = $this->test_limit; + $testLimit = $this->testLimit; $data = $coverage->getData(true); $newData = array_map( static function (array $lines) use ($testLimit): array { - /** @psalm-suppress MissingClosureReturnType **/ - return array_map(static function ($value) use ($testLimit) { - if (! is_array($value)) { - return $value; - } - + return array_map(static function (array $value) use ($testLimit): array { return array_slice($value, 0, $testLimit); }, $lines); }, diff --git a/src/Coverage/CoverageReporter.php b/src/Coverage/CoverageReporter.php index 13ed5f2f..faac0a15 100644 --- a/src/Coverage/CoverageReporter.php +++ b/src/Coverage/CoverageReporter.php @@ -13,7 +13,7 @@ use SebastianBergmann\CodeCoverage\Report\Xml\Facade as XmlReport; use SebastianBergmann\CodeCoverage\Version; -final class CoverageReporter implements CoverageReporterInterface +final class CoverageReporter { /** @var CodeCoverage */ private $coverage; diff --git a/src/Coverage/CoverageReporterInterface.php b/src/Coverage/CoverageReporterInterface.php deleted file mode 100644 index 63dc8ef9..00000000 --- a/src/Coverage/CoverageReporterInterface.php +++ /dev/null @@ -1,48 +0,0 @@ -targetDir = str_replace('.', '_', sys_get_temp_dir() . DS . uniqid('paratest-', true)); - $this->removeDirectory($this->targetDir); - mkdir($this->targetDir); - } - - protected function tearDown(): void - { - $this->removeDirectory($this->targetDir); - - parent::tearDown(); - } - - /** - * @param string[] $coverageFiles - * - * @dataProvider getCoverageFileProvider - */ - public function testCoverageFromFileIsDeletedAfterAdd(array $coverageFiles): void - { - $filename = $this->copyCoverageFile($coverageFiles[0], $this->targetDir); - - $coverageMerger = new CoverageMerger(); - $coverageMerger->addCoverageFromFile($filename); - - static::assertFileDoesNotExist($filename); - } - - /** - * @param string[] $coverageFiles - * @param class-string $expectedClass - * - * @dataProvider getCoverageFileProvider - */ - public function testCodeCoverageObjectIsCreatedFromCoverageFile(array $coverageFiles, string $expectedClass): void - { - $filename = $this->copyCoverageFile($coverageFiles[0], $this->targetDir); - - $coverageMerger = new CoverageMerger(); - $coverageMerger->addCoverageFromFile($filename); - - $coverage = $this->getCoverage($coverageMerger); - - static::assertInstanceOf($expectedClass, $coverage); - static::assertArrayHasKey( - 'ParaTest\\Runners\\PHPUnit\\RunnerTest::testConstructor', - $coverage->getTests(), - 'Code coverage was not added from file' - ); - } - - /** - * @param string[] $coverageFiles - * - * @dataProvider getCoverageFileProvider - */ - public function testCoverageIsMergedOnSecondAddCoverageFromFile(array $coverageFiles): void - { - $filename1 = $this->copyCoverageFile($coverageFiles[0], $this->targetDir); - $filename2 = $this->copyCoverageFile($coverageFiles[1], $this->targetDir); - - $coverageMerger = new CoverageMerger(); - $coverageMerger->addCoverageFromFile($filename1); - - $coverage = $this->getCoverage($coverageMerger); - - static::assertArrayHasKey( - 'ParaTest\\Runners\\PHPUnit\\RunnerTest::testConstructor', - $coverage->getTests(), - 'Code coverage was not added from first file' - ); - static::assertArrayNotHasKey( - 'ParaTest\\Runners\\PHPUnit\\ResultPrinterTest::testConstructor', - $coverage->getTests() - ); - - $coverageMerger->addCoverageFromFile($filename2); - - static::assertArrayHasKey( - 'ParaTest\\Runners\\PHPUnit\\RunnerTest::testConstructor', - $coverage->getTests(), - 'Code coverage from first file was removed' - ); - static::assertArrayHasKey( - 'ParaTest\\Runners\\PHPUnit\\ResultPrinterTest::testConstructor', - $coverage->getTests(), - 'Code coverage was not added from second file' - ); - } - - public function testCoverageFileIsEmpty(): void - { - $this->expectException(RuntimeException::class); - $regex = '/Coverage file .*? is empty. This means a PHPUnit process has crashed./'; - $this->expectExceptionMessageMatches($regex); - $filename = $this->copyCoverageFile('coverage-tests' . DS . 'empty_test.cov', $this->targetDir); - - $coverageMerger = new CoverageMerger(); - $coverageMerger->addCoverageFromFile($filename); - } - - public function testCoverageFileIsNull(): void - { - $coverageMerger = new CoverageMerger(); - $coverageMerger->addCoverageFromFile(null); - - static::assertNull($coverageMerger->getCodeCoverageObject()); - } - - public function testCoverageFileDoesNotExist(): void - { - $coverageMerger = new CoverageMerger(); - $coverageMerger->addCoverageFromFile('no-such-file.cov'); - - static::assertNull($coverageMerger->getCodeCoverageObject()); - } - - /** - * @return array> - */ - public static function getCoverageFileProvider(): array - { - $version = 'CodeCoverage >4.0'; - $filenames = [ - 'coverage-tests/runner_test.cov', - 'coverage-tests/result_printer_test.cov', - ]; - $coverageClass = CodeCoverage::class; - - return [ - $version => [ - 'filenames' => $filenames, - 'expected coverage class' => $coverageClass, - ], - ]; - } - - private function getCoverage(CoverageMerger $coverageMerger): CodeCoverage - { - return $this->getObjectValue($coverageMerger, 'coverage'); - } -} diff --git a/test/Functional/Coverage/CoverageReporterTest.php b/test/Functional/Coverage/CoverageReporterTest.php index 92770fcc..22ae02dd 100644 --- a/test/Functional/Coverage/CoverageReporterTest.php +++ b/test/Functional/Coverage/CoverageReporterTest.php @@ -15,6 +15,9 @@ use function sys_get_temp_dir; use function uniqid; +/** + * @coversNothing + */ final class CoverageReporterTest extends TestBase { /** @@ -24,10 +27,8 @@ final class CoverageReporterTest extends TestBase */ private $targetDir; - protected function setUp(): void + protected function setUpTest(): void { - parent::setUp(); - static::skipIfCodeCoverageNotEnabled(); $this->targetDir = str_replace('.', '_', sys_get_temp_dir() . DS . uniqid('paratest-', true)); diff --git a/test/Functional/DataProviderTest.php b/test/Functional/DataProviderTest.php index 1deddbe2..d7f9e5df 100644 --- a/test/Functional/DataProviderTest.php +++ b/test/Functional/DataProviderTest.php @@ -4,6 +4,9 @@ namespace ParaTest\Tests\Functional; +/** + * @coversNothing + */ final class DataProviderTest extends FunctionalTestBase { /** @var ParaTestInvoker */ diff --git a/test/Functional/GroupTest.php b/test/Functional/GroupTest.php index df6f7d95..05b4f74e 100644 --- a/test/Functional/GroupTest.php +++ b/test/Functional/GroupTest.php @@ -4,6 +4,9 @@ namespace ParaTest\Tests\Functional; +/** + * @coversNothing + */ final class GroupTest extends FunctionalTestBase { /** @var ParaTestInvoker */ diff --git a/test/Functional/OutputTest.php b/test/Functional/OutputTest.php index 5a15d0bf..f1e65871 100644 --- a/test/Functional/OutputTest.php +++ b/test/Functional/OutputTest.php @@ -6,6 +6,9 @@ use function getcwd; +/** + * @coversNothing + */ final class OutputTest extends FunctionalTestBase { /** @var ParaTestInvoker */ diff --git a/test/Functional/PHPUnitOtherWarningsTest.php b/test/Functional/PHPUnitOtherWarningsTest.php index c61e4c84..a8972c2e 100644 --- a/test/Functional/PHPUnitOtherWarningsTest.php +++ b/test/Functional/PHPUnitOtherWarningsTest.php @@ -10,6 +10,8 @@ * PHPUnit deprecated method calls, mocking non-existent methods and some other cases produce warnings that are output * slightly different. Now the paratest doesn't parse the output directly but relies on the JUnit XML logs. * This test checks whether the parates recognizes those warnings. + * + * @coversNothing */ final class PHPUnitOtherWarningsTest extends FunctionalTestBase { diff --git a/test/Functional/PHPUnitTest.php b/test/Functional/PHPUnitTest.php index 34276db3..9cd4c046 100644 --- a/test/Functional/PHPUnitTest.php +++ b/test/Functional/PHPUnitTest.php @@ -19,6 +19,9 @@ use function sprintf; use function unlink; +/** + * @coversNothing + */ final class PHPUnitTest extends FunctionalTestBase { public function testWithJustBootstrap(): void diff --git a/test/Functional/PHPUnitWarningsTest.php b/test/Functional/PHPUnitWarningsTest.php index cd4748bf..d3fc9c3b 100644 --- a/test/Functional/PHPUnitWarningsTest.php +++ b/test/Functional/PHPUnitWarningsTest.php @@ -6,6 +6,8 @@ /** * Specifically tests warnings in PHPUnit. + * + * @coversNothing */ final class PHPUnitWarningsTest extends FunctionalTestBase { diff --git a/test/Functional/Runners/PHPUnit/RunnerIntegrationTest.php b/test/Functional/Runners/PHPUnit/RunnerIntegrationTest.php index 7fe9791a..73440faf 100644 --- a/test/Functional/Runners/PHPUnit/RunnerIntegrationTest.php +++ b/test/Functional/Runners/PHPUnit/RunnerIntegrationTest.php @@ -16,6 +16,9 @@ use function sys_get_temp_dir; use function unlink; +/** + * @coversNothing + */ final class RunnerIntegrationTest extends TestBase { /** @var Runner $runner */ @@ -27,7 +30,7 @@ final class RunnerIntegrationTest extends TestBase /** @var Options */ private $options; - protected function setUp(): void + protected function setUpTest(): void { static::skipIfCodeCoverageNotEnabled(); diff --git a/test/Functional/Runners/PHPUnit/WorkerTest.php b/test/Functional/Runners/PHPUnit/WorkerTest.php index 8215be9b..acab4112 100644 --- a/test/Functional/Runners/PHPUnit/WorkerTest.php +++ b/test/Functional/Runners/PHPUnit/WorkerTest.php @@ -19,6 +19,9 @@ use function sys_get_temp_dir; use function unlink; +/** + * @coversNothing + */ final class WorkerTest extends TestBase { /** @var string[][] */ @@ -34,9 +37,8 @@ final class WorkerTest extends TestBase /** @var BufferedOutput */ private $output; - public function setUp(): void + public function setUpTest(): void { - parent::setUp(); $this->bootstrap = PARATEST_ROOT . DS . 'test' . DS . 'bootstrap.php'; $this->phpunitWrapper = PARATEST_ROOT . DS . 'bin' . DS . 'phpunit-wrapper.php'; $this->output = new BufferedOutput(); diff --git a/test/Functional/SkippedOrIncompleteTest.php b/test/Functional/SkippedOrIncompleteTest.php index 4f8aab2b..b76434a0 100644 --- a/test/Functional/SkippedOrIncompleteTest.php +++ b/test/Functional/SkippedOrIncompleteTest.php @@ -10,6 +10,7 @@ /** * @todo SkippedOrIncompleteTest can't be used in default mode with group filter * (not implemented yet) so we have to split tests per file. + * @coversNothing */ final class SkippedOrIncompleteTest extends FunctionalTestBase { diff --git a/test/Functional/SqliteRunnerTest.php b/test/Functional/SqliteRunnerTest.php index adb8df7a..662ab53c 100644 --- a/test/Functional/SqliteRunnerTest.php +++ b/test/Functional/SqliteRunnerTest.php @@ -6,6 +6,9 @@ use ParaTest\Runners\PHPUnit\SqliteRunner; +/** + * @coversNothing + */ final class SqliteRunnerTest extends FunctionalTestBase { private const TEST_METHODS_PER_CLASS = 5; diff --git a/test/Functional/WrapperRunnerTest.php b/test/Functional/WrapperRunnerTest.php index 8163939f..ce0a915a 100644 --- a/test/Functional/WrapperRunnerTest.php +++ b/test/Functional/WrapperRunnerTest.php @@ -14,6 +14,7 @@ /** * @requires OSFAMILY Linux + * @coversNothing */ final class WrapperRunnerTest extends FunctionalTestBase { diff --git a/test/TestBase.php b/test/TestBase.php index 38f0befe..cf3c3866 100644 --- a/test/TestBase.php +++ b/test/TestBase.php @@ -15,22 +15,34 @@ use ReflectionObject; use ReflectionProperty; use SebastianBergmann\Environment\Runtime; -use SplFileObject; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Filesystem\Filesystem; use function copy; use function file_exists; use function get_class; -use function is_dir; +use function glob; use function preg_match; -use function rmdir; use function str_replace; use function uniqid; -use function unlink; abstract class TestBase extends PHPUnit\Framework\TestCase { + final protected function setUp(): void + { + $glob = glob(TMP_DIR . DS . '*'); + static::assertNotFalse($glob); + + (new Filesystem())->remove($glob); + + $this->setUpTest(); + } + + protected function setUpTest(): void + { + } + /** * @param array $argv */ @@ -159,35 +171,6 @@ final protected static function skipIfCodeCoverageNotEnabled(): void static::markTestSkipped('No code coverage driver available'); } - /** - * Remove dir and its files. - */ - final protected function removeDirectory(string $dirname): void - { - if (! file_exists($dirname) || ! is_dir($dirname)) { - return; - } - - $directory = new RecursiveDirectoryIterator( - $dirname, - RecursiveDirectoryIterator::SKIP_DOTS - ); - /** @var SplFileObject[] $iterator */ - $iterator = new RecursiveIteratorIterator( - $directory, - RecursiveIteratorIterator::CHILD_FIRST - ); - foreach ($iterator as $file) { - if ($file->isDir()) { - rmdir($file->getPathname()); - } else { - unlink($file->getPathname()); - } - } - - rmdir($dirname); - } - /** * Copy fixture file to tmp folder, cause coverage file will be deleted by merger. * diff --git a/test/Unit/Console/Commands/ParaTestCommandTest.php b/test/Unit/Console/Commands/ParaTestCommandTest.php index b83947e2..d4d5b04b 100644 --- a/test/Unit/Console/Commands/ParaTestCommandTest.php +++ b/test/Unit/Console/Commands/ParaTestCommandTest.php @@ -14,12 +14,15 @@ use function sprintf; +/** + * @covers \ParaTest\Console\Commands\ParaTestCommand + */ final class ParaTestCommandTest extends TestBase { /** @var CommandTester */ private $commandTester; - public function setUp(): void + public function setUpTest(): void { $application = ParaTestCommand::applicationFactory(PARATEST_ROOT); $application->add(new HelpCommand()); diff --git a/test/Unit/Coverage/CoverageMergerTest.php b/test/Unit/Coverage/CoverageMergerTest.php index 80ceefb4..bf0e13b5 100644 --- a/test/Unit/Coverage/CoverageMergerTest.php +++ b/test/Unit/Coverage/CoverageMergerTest.php @@ -6,24 +6,29 @@ use ParaTest\Coverage\CoverageMerger; use ParaTest\Tests\TestBase; +use RuntimeException; use SebastianBergmann\CodeCoverage\CodeCoverage; use SebastianBergmann\CodeCoverage\Driver\Driver; use SebastianBergmann\CodeCoverage\Filter; use SebastianBergmann\CodeCoverage\RawCodeCoverageData; +use SebastianBergmann\CodeCoverage\Report\PHP; +use function touch; + +/** + * @covers \ParaTest\Coverage\CoverageMerger + */ final class CoverageMergerTest extends TestBase { - protected function setUp(): void + protected function setUpTest(): void { static::skipIfCodeCoverageNotEnabled(); } /** - * Test merge for code coverage library 4 version. - * - * @requires function \SebastianBergmann\CodeCoverage\CodeCoverage::merge + * @dataProvider provideTestLimit */ - public function testSimpleMerge(): void + public function testMerge(int $testLimit): void { $firstFile = PARATEST_ROOT . DS . 'src' . DS . 'Logging' . DS . 'LogInterpreter.php'; $secondFile = PARATEST_ROOT . DS . 'src' . DS . 'Logging' . DS . 'MetaProvider.php'; @@ -55,69 +60,57 @@ public function testSimpleMerge(): void 'Test2' ); - $merger = new CoverageMerger(); - $this->call($merger, 'addCoverage', $coverage1); - $this->call($merger, 'addCoverage', $coverage2); + $target1 = TMP_DIR . DS . 'coverage1.php'; + $target2 = TMP_DIR . DS . 'coverage2.php'; + $phpReport = new PHP(); + $phpReport->process($coverage1, $target1); + $phpReport->process($coverage2, $target2); + + $merger = new CoverageMerger($testLimit); + $merger->addCoverageFromFile($target1); + $merger->addCoverageFromFile($target2); - $coverage = $this->getObjectValue($merger, 'coverage'); - static::assertInstanceOf(CodeCoverage::class, $coverage); + static::assertFileDoesNotExist($target1); + static::assertFileDoesNotExist($target2); + $coverage = $merger->getCodeCoverageObject(); + static::assertNotNull($coverage); $data = $coverage->getData()->lineCoverage(); - static::assertCount(2, $data[$firstFile][$firstFileFirstLine]); - static::assertEquals('Test1', $data[$firstFile][$firstFileFirstLine][0]); - static::assertEquals('Test2', $data[$firstFile][$firstFileFirstLine][1]); + if ($testLimit === 0) { + static::assertCount(2, $data[$firstFile][$firstFileFirstLine]); + static::assertEquals('Test1', $data[$firstFile][$firstFileFirstLine][0]); + static::assertEquals('Test2', $data[$firstFile][$firstFileFirstLine][1]); + } else { + static::assertCount(1, $data[$firstFile][$firstFileFirstLine]); + static::assertEquals('Test1', $data[$firstFile][$firstFileFirstLine][0]); + } static::assertCount(1, $data[$secondFile][$secondFileFirstLine]); static::assertEquals('Test1', $data[$secondFile][$secondFileFirstLine][0]); } /** - * Test merge with limits - * - * @requires function \SebastianBergmann\CodeCoverage\CodeCoverage::merge + * @return array */ - public function testSimpleMergeLimited(): void + public function provideTestLimit(): array { - $firstFile = PARATEST_ROOT . DS . 'src' . DS . 'Logging' . DS . 'LogInterpreter.php'; - $secondFile = PARATEST_ROOT . DS . 'src' . DS . 'Logging' . DS . 'MetaProvider.php'; - - // Every time the two above files are changed, the line numbers - // may change, and so these two numbers may need adjustments - $firstFileFirstLine = 45; - $secondFileFirstLine = 53; - - $filter = new Filter(); - $filter->includeFiles([$firstFile, $secondFile]); - - $data = RawCodeCoverageData::fromXdebugWithoutPathCoverage([ - $firstFile => [$firstFileFirstLine => 1], - $secondFile => [$secondFileFirstLine => 1], - ]); - $coverage1 = new CodeCoverage(Driver::forLineCoverage($filter), $filter); - $coverage1->append( - $data, - 'Test1' - ); - - $data = RawCodeCoverageData::fromXdebugWithoutPathCoverage([ - $firstFile => [$firstFileFirstLine => 1, 1 + $firstFileFirstLine => 1], - ]); - $coverage2 = new CodeCoverage(Driver::forLineCoverage($filter), $filter); - $coverage2->append( - $data, - 'Test2' - ); + return [ + 'unlimited' => [0], + 'limited' => [1], + ]; + } - $merger = new CoverageMerger($test_limit = 1); - $this->call($merger, 'addCoverage', $coverage1); - $this->call($merger, 'addCoverage', $coverage2); + public function testCoverageFileIsEmpty(): void + { + $filename = TMP_DIR . DS . 'coverage.php'; + touch($filename); + $coverageMerger = new CoverageMerger(0); - $coverage = $this->getObjectValue($merger, 'coverage'); - static::assertInstanceOf(CodeCoverage::class, $coverage); - $data = $coverage->getData()->lineCoverage(); + $this->expectException(RuntimeException::class); + $regex = '/Coverage file .*? is empty. This means a PHPUnit process has crashed./'; + $this->expectExceptionMessageMatches($regex); - static::assertCount(1, $data[$firstFile][$firstFileFirstLine]); - static::assertCount(1, $data[$secondFile][$secondFileFirstLine]); + $coverageMerger->addCoverageFromFile($filename); } } diff --git a/test/Unit/Logging/JUnit/ReaderTest.php b/test/Unit/Logging/JUnit/ReaderTest.php index def48d78..78cdefae 100644 --- a/test/Unit/Logging/JUnit/ReaderTest.php +++ b/test/Unit/Logging/JUnit/ReaderTest.php @@ -14,6 +14,9 @@ use function file_get_contents; use function file_put_contents; +/** + * @coversNothing + */ final class ReaderTest extends TestBase { /** @var string */ @@ -27,7 +30,7 @@ final class ReaderTest extends TestBase /** @var Reader */ private $multi_errors; - public function setUp(): void + public function setUpTest(): void { $this->mixedPath = FIXTURES . DS . 'results' . DS . 'mixed-results.xml'; $this->mixed = new Reader($this->mixedPath); diff --git a/test/Unit/Logging/JUnit/WriterTest.php b/test/Unit/Logging/JUnit/WriterTest.php index a9ed9f09..b32cdf4e 100644 --- a/test/Unit/Logging/JUnit/WriterTest.php +++ b/test/Unit/Logging/JUnit/WriterTest.php @@ -13,6 +13,9 @@ use function file_get_contents; use function unlink; +/** + * @coversNothing + */ final class WriterTest extends TestBase { /** @var Writer */ @@ -22,7 +25,7 @@ final class WriterTest extends TestBase /** @var string */ protected $passing; - public function setUp(): void + public function setUpTest(): void { $this->interpreter = new LogInterpreter(); $this->writer = new Writer($this->interpreter, 'test/fixtures/tests/'); diff --git a/test/Unit/Logging/LogInterpreterTest.php b/test/Unit/Logging/LogInterpreterTest.php index f8847419..c652cd4b 100644 --- a/test/Unit/Logging/LogInterpreterTest.php +++ b/test/Unit/Logging/LogInterpreterTest.php @@ -11,6 +11,9 @@ use function array_pop; +/** + * @coversNothing + */ final class LogInterpreterTest extends ResultTester { /** @var LogInterpreter */ diff --git a/test/Unit/Parser/GetClassTest.php b/test/Unit/Parser/GetClassTest.php index c0341a68..7722818b 100644 --- a/test/Unit/Parser/GetClassTest.php +++ b/test/Unit/Parser/GetClassTest.php @@ -8,6 +8,9 @@ use ParaTest\Parser\Parser; use ParaTest\Tests\TestBase; +/** + * @coversNothing + */ final class GetClassTest extends TestBase { public function testPreviouslyLoadedTestClassCanBeParsed(): void diff --git a/test/Unit/Parser/ParsedClassTest.php b/test/Unit/Parser/ParsedClassTest.php index ac5dcfc3..087071a3 100644 --- a/test/Unit/Parser/ParsedClassTest.php +++ b/test/Unit/Parser/ParsedClassTest.php @@ -8,6 +8,9 @@ use ParaTest\Parser\ParsedFunction; use ParaTest\Tests\TestBase; +/** + * @coversNothing + */ final class ParsedClassTest extends TestBase { /** @var ParsedClass */ @@ -15,7 +18,7 @@ final class ParsedClassTest extends TestBase /** @var ParsedFunction[] */ protected $methods; - public function setUp(): void + public function setUpTest(): void { $this->methods = [ new ParsedFunction( diff --git a/test/Unit/Parser/ParsedObjectTest.php b/test/Unit/Parser/ParsedObjectTest.php index 60efdc2d..4ad92c43 100644 --- a/test/Unit/Parser/ParsedObjectTest.php +++ b/test/Unit/Parser/ParsedObjectTest.php @@ -7,12 +7,15 @@ use ParaTest\Parser\ParsedClass; use ParaTest\Tests\TestBase; +/** + * @coversNothing + */ final class ParsedObjectTest extends TestBase { /** @var ParsedClass */ protected $parsedClass; - public function setUp(): void + public function setUpTest(): void { $this->parsedClass = new ParsedClass("/**\n * @test\n @group group1\n*\\/", 'MyClass', 'My\\Name\\Space'); } diff --git a/test/Unit/Parser/ParserTest.php b/test/Unit/Parser/ParserTest.php index dc500d1f..a6875c58 100644 --- a/test/Unit/Parser/ParserTest.php +++ b/test/Unit/Parser/ParserTest.php @@ -9,6 +9,9 @@ use ParaTest\Parser\Parser; use ParaTest\Tests\TestBase; +/** + * @coversNothing + */ final class ParserTest extends TestBase { public function testConstructorThrowsExceptionIfFileNotFound(): void diff --git a/test/Unit/ResultTester.php b/test/Unit/ResultTester.php index d5a87ee4..c082f23b 100644 --- a/test/Unit/ResultTester.php +++ b/test/Unit/ResultTester.php @@ -23,7 +23,7 @@ abstract class ResultTester extends TestBase /** @var Suite */ protected $errorSuite; - final public function setUp(): void + final public function setUpTest(): void { $this->errorSuite = $this->getSuiteWithResult('single-werror.xml', 1); $this->otherErrorSuite = $this->getSuiteWithResult('single-werror2.xml', 1); diff --git a/test/Unit/Runners/PHPUnit/ExecutableTestChild.php b/test/Unit/Runners/PHPUnit/ExecutableTestChild.php index eb441edb..0ef1508f 100644 --- a/test/Unit/Runners/PHPUnit/ExecutableTestChild.php +++ b/test/Unit/Runners/PHPUnit/ExecutableTestChild.php @@ -6,6 +6,9 @@ use ParaTest\Runners\PHPUnit\ExecutableTest; +/** + * @coversNothing + */ final class ExecutableTestChild extends ExecutableTest { /** diff --git a/test/Unit/Runners/PHPUnit/ExecutableTestTest.php b/test/Unit/Runners/PHPUnit/ExecutableTestTest.php index 15e542de..de48cb49 100644 --- a/test/Unit/Runners/PHPUnit/ExecutableTestTest.php +++ b/test/Unit/Runners/PHPUnit/ExecutableTestTest.php @@ -9,12 +9,15 @@ use function defined; use function unlink; +/** + * @coversNothing + */ final class ExecutableTestTest extends TestBase { /** @var ExecutableTestChild */ protected $executableTestChild; - public function setUp(): void + public function setUpTest(): void { $this->executableTestChild = new ExecutableTestChild('pathToFile', true); } diff --git a/test/Unit/Runners/PHPUnit/OptionsTest.php b/test/Unit/Runners/PHPUnit/OptionsTest.php index 028008e1..235a44b0 100644 --- a/test/Unit/Runners/PHPUnit/OptionsTest.php +++ b/test/Unit/Runners/PHPUnit/OptionsTest.php @@ -17,6 +17,9 @@ use function sort; use function unlink; +/** + * @coversNothing + */ final class OptionsTest extends TestBase { /** @var Options */ @@ -26,7 +29,7 @@ final class OptionsTest extends TestBase /** @var string */ private $testCwd; - public function setUp(): void + public function setUpTest(): void { $this->unfiltered = [ '--processes' => 5, diff --git a/test/Unit/Runners/PHPUnit/ResultPrinterTest.php b/test/Unit/Runners/PHPUnit/ResultPrinterTest.php index 85aab0f6..b23af20d 100644 --- a/test/Unit/Runners/PHPUnit/ResultPrinterTest.php +++ b/test/Unit/Runners/PHPUnit/ResultPrinterTest.php @@ -18,6 +18,9 @@ use function sprintf; use function unlink; +/** + * @coversNothing + */ final class ResultPrinterTest extends ResultTester { /** @var ResultPrinter */ diff --git a/test/Unit/Runners/PHPUnit/RunnerTest.php b/test/Unit/Runners/PHPUnit/RunnerTest.php index 05ce0e54..40e3a45c 100644 --- a/test/Unit/Runners/PHPUnit/RunnerTest.php +++ b/test/Unit/Runners/PHPUnit/RunnerTest.php @@ -14,6 +14,9 @@ use function getcwd; use function uniqid; +/** + * @coversNothing + */ final class RunnerTest extends TestBase { /** @var Runner */ @@ -21,7 +24,7 @@ final class RunnerTest extends TestBase /** @var BufferedOutput */ private $output; - public function setUp(): void + public function setUpTest(): void { $this->output = new BufferedOutput(); $this->runner = new Runner($this->createOptionsFromArgv([]), $this->output); diff --git a/test/Unit/Runners/PHPUnit/SuiteLoaderTest.php b/test/Unit/Runners/PHPUnit/SuiteLoaderTest.php index 177d68d4..ea671e1e 100644 --- a/test/Unit/Runners/PHPUnit/SuiteLoaderTest.php +++ b/test/Unit/Runners/PHPUnit/SuiteLoaderTest.php @@ -16,6 +16,9 @@ use function strstr; use function uniqid; +/** + * @coversNothing + */ final class SuiteLoaderTest extends TestBase { public function testConstructor(): void diff --git a/test/Unit/Runners/PHPUnit/TestMethodTest.php b/test/Unit/Runners/PHPUnit/TestMethodTest.php index 3e1e5fb6..5dab2765 100644 --- a/test/Unit/Runners/PHPUnit/TestMethodTest.php +++ b/test/Unit/Runners/PHPUnit/TestMethodTest.php @@ -7,6 +7,9 @@ use ParaTest\Runners\PHPUnit\TestMethod; use ParaTest\Tests\TestBase; +/** + * @coversNothing + */ final class TestMethodTest extends TestBase { public function testConstructor(): void diff --git a/test/Unit/Runners/PHPUnit/WrapperRunnerTest.php b/test/Unit/Runners/PHPUnit/WrapperRunnerTest.php index cdd35951..f50d7a44 100644 --- a/test/Unit/Runners/PHPUnit/WrapperRunnerTest.php +++ b/test/Unit/Runners/PHPUnit/WrapperRunnerTest.php @@ -9,6 +9,9 @@ use RuntimeException; use Symfony\Component\Console\Output\BufferedOutput; +/** + * @coversNothing + */ final class WrapperRunnerTest extends TestBase { /** diff --git a/test/Unit/Util/StrTest.php b/test/Unit/Util/StrTest.php index 2957b4c2..51c96f6f 100644 --- a/test/Unit/Util/StrTest.php +++ b/test/Unit/Util/StrTest.php @@ -9,6 +9,9 @@ use function array_values; +/** + * @coversNothing + */ final class StrTest extends TestCase { /** diff --git a/test/bootstrap.php b/test/bootstrap.php index 1ee133d2..2b2295ea 100644 --- a/test/bootstrap.php +++ b/test/bootstrap.php @@ -4,4 +4,3 @@ require __DIR__ . DIRECTORY_SEPARATOR . 'constants.php'; require PARATEST_ROOT . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php'; -require __DIR__ . DIRECTORY_SEPARATOR . 'TestBase.php'; diff --git a/test/constants.php b/test/constants.php index 917bfd2f..c654ab59 100644 --- a/test/constants.php +++ b/test/constants.php @@ -8,6 +8,7 @@ //TEST CONSTANTS define('FIXTURES', __DIR__ . DS . 'fixtures'); +define('TMP_DIR', __DIR__ . DS . 'tmp'); define('PARATEST_ROOT', dirname(__DIR__)); define('PARA_BINARY', PARATEST_ROOT . DS . 'bin' . DS . 'paratest'); define('PHPUNIT', PARATEST_ROOT . DS . 'vendor' . DS . 'phpunit' . DS . 'phpunit' . DS . 'phpunit'); diff --git a/test/tmp/.gitignore b/test/tmp/.gitignore new file mode 100644 index 00000000..c96a04f0 --- /dev/null +++ b/test/tmp/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file From 2a141d21c0da7e7f29fce9bfbb23b7f2fd9fb231 Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Mon, 17 Aug 2020 14:51:21 +0200 Subject: [PATCH 03/19] CoverageReporter ok --- .../Coverage/CoverageReporterTest.php | 234 ------------------ test/Unit/Coverage/CoverageReporterTest.php | 104 ++++++++ 2 files changed, 104 insertions(+), 234 deletions(-) delete mode 100644 test/Functional/Coverage/CoverageReporterTest.php create mode 100644 test/Unit/Coverage/CoverageReporterTest.php diff --git a/test/Functional/Coverage/CoverageReporterTest.php b/test/Functional/Coverage/CoverageReporterTest.php deleted file mode 100644 index 22ae02dd..00000000 --- a/test/Functional/Coverage/CoverageReporterTest.php +++ /dev/null @@ -1,234 +0,0 @@ -targetDir = str_replace('.', '_', sys_get_temp_dir() . DS . uniqid('paratest-', true)); - $this->removeDirectory($this->targetDir); - mkdir($this->targetDir); - } - - protected function tearDown(): void - { - $this->removeDirectory($this->targetDir); - - parent::tearDown(); - } - - /** - * @param string[] $coverageFiles - * @param class-string $expectedReportClass - * - * @dataProvider getReporterProvider - */ - public function testGetReporter(array $coverageFiles, string $expectedReportClass): void - { - $filename1 = $this->copyCoverageFile($coverageFiles[0], $this->targetDir); - $filename2 = $this->copyCoverageFile($coverageFiles[1], $this->targetDir); - - $coverageMerger = new CoverageMerger(); - $coverageMerger->addCoverageFromFile($filename1); - $coverageMerger->addCoverageFromFile($filename2); - - $reporter = $coverageMerger->getReporter(); - - static::assertInstanceOf($expectedReportClass, $reporter); - } - - /** - * @param string[] $coverageFiles - * - * @dataProvider getReporterProvider - */ - public function testGeneratePhp(array $coverageFiles): void - { - $filename1 = $this->copyCoverageFile($coverageFiles[0], $this->targetDir); - $filename2 = $this->copyCoverageFile($coverageFiles[1], $this->targetDir); - - $coverageMerger = new CoverageMerger(); - $coverageMerger->addCoverageFromFile($filename1); - $coverageMerger->addCoverageFromFile($filename2); - - $target = $this->targetDir . DS . 'coverage.php'; - - static::assertFileDoesNotExist($target); - - $coverageMerger->getReporter()->php($target); - - static::assertFileExists($target); - } - - /** - * @param string[] $coverageFiles - * - * @dataProvider getReporterProvider - */ - public function testGenerateClover(array $coverageFiles): void - { - $filename1 = $this->copyCoverageFile($coverageFiles[0], $this->targetDir); - $filename2 = $this->copyCoverageFile($coverageFiles[1], $this->targetDir); - - $coverageMerger = new CoverageMerger(); - $coverageMerger->addCoverageFromFile($filename1); - $coverageMerger->addCoverageFromFile($filename2); - - $target = $this->targetDir . DS . 'coverage.xml'; - - static::assertFileDoesNotExist($target); - - $coverageMerger->getReporter()->clover($target); - - static::assertFileExists($target); - - $reportXml = (new Xml\Loader())->loadFile($target); - static::assertTrue($reportXml->hasChildNodes(), 'Incorrect clover report xml was generated'); - } - - /** - * @param string[] $coverageFiles - * - * @dataProvider getReporterProvider - */ - public function testGenerateCrap4J(array $coverageFiles): void - { - $filename1 = $this->copyCoverageFile($coverageFiles[0], $this->targetDir); - $filename2 = $this->copyCoverageFile($coverageFiles[1], $this->targetDir); - - $coverageMerger = new CoverageMerger(); - $coverageMerger->addCoverageFromFile($filename1); - $coverageMerger->addCoverageFromFile($filename2); - - $target = $this->targetDir . DS . 'coverage.xml'; - - static::assertFileDoesNotExist($target); - - $coverageMerger->getReporter()->crap4j($target); - - static::assertFileExists($target); - - $reportXml = (new Xml\Loader())->loadFile($target); - static::assertTrue($reportXml->hasChildNodes(), 'Incorrect crap4j report xml was generated'); - $documentElement = $reportXml->documentElement; - static::assertNotNull($documentElement); - static::assertEquals('crap_result', $documentElement->tagName); - } - - /** - * @param string[] $coverageFiles - * - * @dataProvider getReporterProvider - */ - public function testGenerateHtml(array $coverageFiles): void - { - $filename1 = $this->copyCoverageFile($coverageFiles[0], $this->targetDir); - $filename2 = $this->copyCoverageFile($coverageFiles[1], $this->targetDir); - - $coverageMerger = new CoverageMerger(); - $coverageMerger->addCoverageFromFile($filename1); - $coverageMerger->addCoverageFromFile($filename2); - - $target = $this->targetDir . DS . 'coverage'; - - static::assertFileDoesNotExist($target); - - $coverageMerger->getReporter()->html($target); - - static::assertFileExists($target); - static::assertFileExists($target . DS . 'index.html', 'Index html file was not generated'); - } - - /** - * @param string[] $coverageFiles - * - * @dataProvider getReporterProvider - */ - public function testGenerateText(array $coverageFiles): void - { - $filename1 = $this->copyCoverageFile($coverageFiles[0], $this->targetDir); - $filename2 = $this->copyCoverageFile($coverageFiles[1], $this->targetDir); - - $coverageMerger = new CoverageMerger(); - $coverageMerger->addCoverageFromFile($filename1); - $coverageMerger->addCoverageFromFile($filename2); - - $output = $coverageMerger->getReporter()->text(); - - static::assertStringContainsString('Code Coverage Report:', $output); - } - - /** - * @param string[] $coverageFiles - * - * @dataProvider getReporterProvider - */ - public function testGenerateXml(array $coverageFiles): void - { - $filename1 = $this->copyCoverageFile($coverageFiles[0], $this->targetDir); - $filename2 = $this->copyCoverageFile($coverageFiles[1], $this->targetDir); - - $coverageMerger = new CoverageMerger(); - $coverageMerger->addCoverageFromFile($filename1); - $coverageMerger->addCoverageFromFile($filename2); - - $target = $this->targetDir . DS . 'coverage.xml'; - - static::assertFileDoesNotExist($target); - - $coverageMerger->getReporter()->xml($target); - - static::assertFileExists($target); - static::assertFileExists($target . DS . 'index.xml', 'Index xml file was not generated'); - - $reportXml = (new Xml\Loader())->loadFile($target . DS . 'index.xml'); - static::assertTrue($reportXml->hasChildNodes(), 'Incorrect xml was generated'); - } - - /** - * @return array> - */ - public static function getReporterProvider(): array - { - $version = 'CodeCoverage >4.0'; - $windowsExt = defined('PHP_WINDOWS_VERSION_BUILD') ? '-windows' : ''; - $filenames = [ - 'coverage-tests' . DS . 'runner_test' . $windowsExt . '.cov', - 'coverage-tests' . DS . 'result_printer_test' . $windowsExt . '.cov', - ]; - $reporterClass = CoverageReporter::class; - - return [ - $version => [ - 'filenames' => $filenames, - 'expected reporter class' => $reporterClass, - ], - ]; - } -} diff --git a/test/Unit/Coverage/CoverageReporterTest.php b/test/Unit/Coverage/CoverageReporterTest.php new file mode 100644 index 00000000..273e33b0 --- /dev/null +++ b/test/Unit/Coverage/CoverageReporterTest.php @@ -0,0 +1,104 @@ +includeFile(__FILE__); + $codeCoverage = new CodeCoverage(Driver::forLineCoverage($filter), $filter); + $codeCoverage->append(RawCodeCoverageData::fromXdebugWithoutPathCoverage([ + __FILE__ => [__LINE__ => 1], + ]), uniqid()); + + $this->coverageReporter = new CoverageReporter($codeCoverage); + } + + public function testGenerateClover(): void + { + $target = TMP_DIR . DS . 'clover'; + + $this->coverageReporter->clover($target); + + static::assertFileExists($target); + + $reportXml = (new Xml\Loader())->loadFile($target); + static::assertTrue($reportXml->hasChildNodes(), 'Incorrect clover report xml was generated'); + } + + public function testGenerateCrap4j(): void + { + $target = TMP_DIR . DS . 'crap4j'; + + $this->coverageReporter->crap4j($target); + + static::assertFileExists($target); + + $reportXml = (new Xml\Loader())->loadFile($target); + static::assertTrue($reportXml->hasChildNodes(), 'Incorrect crap4j report xml was generated'); + $documentElement = $reportXml->documentElement; + static::assertNotNull($documentElement); + static::assertEquals('crap_result', $documentElement->tagName); + } + + public function testGenerateHtml(): void + { + $target = TMP_DIR . DS . 'html'; + + $this->coverageReporter->html($target); + + static::assertFileExists($target); + static::assertFileExists($target . DS . 'index.html', 'Index html file was not generated'); + } + + public function testGeneratePhp(): void + { + $target = TMP_DIR . DS . 'php'; + + $this->coverageReporter->php($target); + + static::assertFileExists($target); + } + + public function testGenerateText(): void + { + $output = $this->coverageReporter->text(); + + static::assertStringContainsString('Code Coverage Report:', $output); + } + + public function testGenerateXml(): void + { + $target = TMP_DIR . DS . 'xml'; + + $this->coverageReporter->xml($target); + + static::assertFileExists($target); + static::assertFileExists($target . DS . 'index.xml', 'Index xml file was not generated'); + + $reportXml = (new Xml\Loader())->loadFile($target . DS . 'index.xml'); + static::assertTrue($reportXml->hasChildNodes(), 'Incorrect xml was generated'); + } +} From ef73d51bff967db33309c1aad50ac7b6e53bee7a Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Mon, 17 Aug 2020 15:59:07 +0200 Subject: [PATCH 04/19] TestCase / TestSuite refactor --- src/Logging/JUnit/Reader.php | 79 +++++++++++-------------- src/Logging/JUnit/TestCase.php | 19 ++---- src/Logging/JUnit/TestSuite.php | 53 +++++------------ src/Logging/LogInterpreter.php | 5 +- test/Unit/Logging/JUnit/ReaderTest.php | 6 +- test/fixtures/results/mixed-results.xml | 6 ++ 6 files changed, 68 insertions(+), 100 deletions(-) diff --git a/src/Logging/JUnit/Reader.php b/src/Logging/JUnit/Reader.php index 2434c638..45617703 100644 --- a/src/Logging/JUnit/Reader.php +++ b/src/Logging/JUnit/Reader.php @@ -32,18 +32,6 @@ final class Reader extends MetaProvider /** @var string */ protected $logFile; - /** @var array{name: string, file: string, assertions: int, tests: int, failures: int, errors: int, skipped: int, time: float} */ - private static $defaultSuite = [ - 'name' => '', - 'file' => '', - 'tests' => 0, - 'assertions' => 0, - 'failures' => 0, - 'errors' => 0, - 'skipped' => 0, - 'time' => 0.0, - ]; - public function __construct(string $logFile) { if (! file_exists($logFile)) { @@ -144,10 +132,10 @@ private function init(): void */ private function initSuiteFromCases(array $nodeArray): void { - $testCases = []; - $properties = $this->caseNodesToSuiteProperties($nodeArray, $testCases); + $testCases = []; + $testSuite = $this->caseNodesToSuite($nodeArray, $testCases); if (! $this->isSingle) { - $this->addSuite($properties, $testCases); + $this->addSuite($testSuite, $testCases); } else { $suite = $this->suites[0]; $suite->cases = array_merge($suite->cases, $testCases); @@ -158,12 +146,10 @@ private function initSuiteFromCases(array $nodeArray): void * Creates and adds a TestSuite based on the given * suite properties and collection of test cases. * - * @param array{name: string, file: string, assertions: int, tests: int, failures: int, errors: int, skipped: int, time: float} $properties - * @param TestCase[] $testCases + * @param TestCase[] $testCases */ - private function addSuite(array $properties, array $testCases): void + private function addSuite(TestSuite $suite, array $testCases): void { - $suite = TestSuite::suiteFromArray($properties); $suite->cases = $testCases; $this->suites[0]->suites[] = $suite; } @@ -173,31 +159,26 @@ private function addSuite(array $properties, array $testCases): void * * @param SimpleXMLElement[] $nodeArray an array of testcase nodes * @param TestCase[] $testCases an array reference. Individual testcases will be placed here. - * - * @return array{name: string, file: string, assertions: int, tests: int, failures: int, errors: int, skipped: int, time: float} */ - private function caseNodesToSuiteProperties(array $nodeArray, array &$testCases = []): array + private function caseNodesToSuite(array $nodeArray, array &$testCases = []): TestSuite { - /** @var array{name: string, file: string, assertions: int, tests: int, failures: int, errors: int, skipped: int, time: float} $result */ - $result = array_reduce( - $nodeArray, - static function (array $result, SimpleXMLElement $xmlElement) use (&$testCases): array { - $testCases[] = TestCase::caseFromNode($xmlElement); - $result['name'] = (string) $xmlElement['class']; - $result['file'] = (string) $xmlElement['file']; - ++$result['tests']; - $result['assertions'] += (int) $xmlElement['assertions']; - $result['failures'] += ($failues = $xmlElement->xpath('failure')) !== false ? count($failues) : 0; - $result['errors'] += ($error = $xmlElement->xpath('error')) !== false ? count($error) : 0; - $result['skipped'] += ($skipped = $xmlElement->xpath('skipped')) !== false ? count($skipped) : 0; - $result['time'] += (float) $xmlElement['time']; - - return $result; - }, - static::$defaultSuite - ); - - return $result; + $testSuite = TestSuite::empty(); + foreach ($nodeArray as $simpleXMLElement) { + $testCase = TestCase::caseFromNode($simpleXMLElement); + $testCases[] = $testCase; + + $testSuite->name = $testCase->class; + $testSuite->file = $testCase->file; + ++$testSuite->tests; + $testSuite->assertions += $testCase->assertions; + $testSuite->failures += count($testCase->failures); + $testSuite->errors += count($testCase->errors); + $testSuite->warnings += count($testCase->warnings); + $testSuite->skipped += count($testCase->skipped); + $testSuite->time += $testCase->time; + } + + return $testSuite; } /** @@ -239,9 +220,19 @@ private function initSuite(): void $node = current($node); if ($node !== false) { - $this->suites[] = TestSuite::suiteFromNode($node); + $this->suites[] = new TestSuite( + (string) $node['name'], + (int) $node['tests'], + (int) $node['assertions'], + (int) $node['failures'], + (int) $node['errors'], + (int) $node['warnings'], + (int) $node['skipped'], + (float) $node['time'], + (string) $node['file'] + ); } else { - $this->suites[] = TestSuite::suiteFromArray(self::$defaultSuite); + $this->suites[] = TestSuite::empty(); } } diff --git a/src/Logging/JUnit/TestCase.php b/src/Logging/JUnit/TestCase.php index 3420bc82..291c77d6 100644 --- a/src/Logging/JUnit/TestCase.php +++ b/src/Logging/JUnit/TestCase.php @@ -62,19 +62,6 @@ public function __construct( $this->time = $time; } - /** - * Add a defect type (error or failure). - * - * @param string $collName the name of the collection to add to - */ - private function addDefect(string $collName, string $type, string $text): void - { - $this->{$collName}[] = [ - 'type' => $type, - 'text' => trim($text), - ]; - } - /** * Factory method that creates a TestCase object * from a SimpleXMLElement. @@ -106,7 +93,11 @@ public static function caseFromNode(SimpleXMLElement $node): self $message = (string) $defect; $message .= (string) $system_output; - $case->addDefect($group, (string) $defect['type'], $message); + + $case->{$group}[] = [ + 'type' => (string) $defect['type'], + 'text' => trim($message), + ]; } } diff --git a/src/Logging/JUnit/TestSuite.php b/src/Logging/JUnit/TestSuite.php index 15279814..b7fb0bd9 100644 --- a/src/Logging/JUnit/TestSuite.php +++ b/src/Logging/JUnit/TestSuite.php @@ -4,8 +4,6 @@ namespace ParaTest\Logging\JUnit; -use SimpleXMLElement; - /** * A simple data structure for tracking * data associated with a testsuite node @@ -28,6 +26,9 @@ final class TestSuite /** @var int */ public $errors; + /** @var int */ + public $warnings; + /** @var int */ public $skipped; @@ -57,6 +58,7 @@ public function __construct( int $assertions, int $failures, int $errors, + int $warnings, int $skipped, float $time, string $file @@ -67,48 +69,23 @@ public function __construct( $this->failures = $failures; $this->skipped = $skipped; $this->errors = $errors; + $this->warnings = $warnings; $this->time = $time; $this->file = $file; } - /** - * Create a TestSuite from an associative - * array. - * - * @param array{name: string, file: string, assertions: int, tests: int, failures: int, errors: int, skipped: int, time: float} $arr - * - * @return TestSuite - */ - public static function suiteFromArray(array $arr): self - { - return new self( - $arr['name'], - $arr['tests'], - $arr['assertions'], - $arr['failures'], - $arr['errors'], - $arr['skipped'], - $arr['time'], - $arr['file'] - ); - } - - /** - * Create a TestSuite from a SimpleXMLElement. - * - * @return TestSuite - */ - public static function suiteFromNode(SimpleXMLElement $node): self + public static function empty(): self { return new self( - (string) $node['name'], - (int) $node['tests'], - (int) $node['assertions'], - (int) $node['failures'], - (int) $node['errors'], - (int) $node['skipped'], - (float) $node['time'], - (string) $node['file'] + '', + 0, + 0, + 0, + 0, + 0, + 0, + 0.0, + '', ); } } diff --git a/src/Logging/LogInterpreter.php b/src/Logging/LogInterpreter.php index aca8a561..b1059791 100644 --- a/src/Logging/LogInterpreter.php +++ b/src/Logging/LogInterpreter.php @@ -126,9 +126,11 @@ public function flattenCases(): array $dict = []; foreach ($this->getCases() as $case) { if (! isset($dict[$case->file])) { - $dict[$case->file] = new TestSuite($case->class, 0, 0, 0, 0, 0, 0, $case->file); + $dict[$case->file] = TestSuite::empty(); } + $dict[$case->file]->name = $case->class; + $dict[$case->file]->file = $case->file; $dict[$case->file]->cases[] = $case; ++$dict[$case->file]->tests; $dict[$case->file]->assertions += $case->assertions; @@ -136,7 +138,6 @@ public function flattenCases(): array $dict[$case->file]->errors += count($case->errors); $dict[$case->file]->skipped += count($case->skipped); $dict[$case->file]->time += $case->time; - $dict[$case->file]->file = $case->file; } return array_values($dict); diff --git a/test/Unit/Logging/JUnit/ReaderTest.php b/test/Unit/Logging/JUnit/ReaderTest.php index 78cdefae..a708d201 100644 --- a/test/Unit/Logging/JUnit/ReaderTest.php +++ b/test/Unit/Logging/JUnit/ReaderTest.php @@ -15,7 +15,9 @@ use function file_put_contents; /** - * @coversNothing + * @covers \ParaTest\Logging\JUnit\Reader + * @covers \ParaTest\Logging\JUnit\TestCase + * @covers \ParaTest\Logging\JUnit\TestSuite */ final class ReaderTest extends TestBase { @@ -307,7 +309,7 @@ public function testGetMultiErrorsMessages(): void public function testMixedGetFeedback(): void { $feedback = $this->mixed->getFeedback(); - static::assertEquals(['.', 'F', '.', 'E', '.', 'F', '.'], $feedback); + static::assertSame(['.', 'F', '.', 'E', '.', 'F', '.', 'W', 'S'], $feedback); } public function testRemoveLog(): void diff --git a/test/fixtures/results/mixed-results.xml b/test/fixtures/results/mixed-results.xml index b70af33c..6665ce51 100644 --- a/test/fixtures/results/mixed-results.xml +++ b/test/fixtures/results/mixed-results.xml @@ -31,6 +31,12 @@ Failed asserting that true is false. + + WARNING + + + SKIPPED + From 85e3eba341ae8596450431bfc6bebe1112542768 Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Tue, 18 Aug 2020 08:57:45 +0200 Subject: [PATCH 05/19] Writer ok --- Makefile | 2 +- phpstan.neon.dist | 5 +- src/Logging/JUnit/Reader.php | 92 +++++++--- src/Logging/JUnit/Writer.php | 78 ++++---- src/Logging/LogInterpreter.php | 98 ++++++---- src/Logging/MetaProvider.php | 77 ++------ src/Runners/PHPUnit/BaseRunner.php | 5 +- src/Runners/PHPUnit/WrapperRunner.php | 4 +- .../Runners/PHPUnit/RunnerIntegrationTest.php | 10 +- test/Unit/Logging/JUnit/ReaderTest.php | 170 ++++++++++-------- test/Unit/Logging/JUnit/WriterTest.php | 2 +- test/Unit/Logging/LogInterpreterTest.php | 58 +++--- test/Unit/Parser/GetClassTest.php | 2 +- .../Runners/PHPUnit/ResultPrinterTest.php | 43 ++--- .../UnitTestWithMethodAnnotationsTest.php | 32 ++++ .../data-provider-with-special-chars.xml | 4 +- test/fixtures/results/mixed-results.xml | 72 ++++++-- test/fixtures/results/single-passing.xml | 8 +- 18 files changed, 452 insertions(+), 310 deletions(-) diff --git a/Makefile b/Makefile index d371ad6b..1cc44080 100644 --- a/Makefile +++ b/Makefile @@ -17,4 +17,4 @@ static-analysis: vendor .PHONY: test test: vendor - php -d zend.assertions=1 vendor/bin/phpunit + php -d zend.assertions=1 vendor/bin/phpunit ${arg} diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 55e5640b..b94ed6e5 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -70,8 +70,11 @@ parameters: # Known errors - "#^Variable property access on .+$#" - - "#^Variable method call on .+$#" - "#^.+ has no value type specified in iterable type Symfony\\\\Component\\\\Process\\\\Process\\.$#" + - + message: "#^Method ParaTest\\\\Tests\\\\TestBase\\:\\:setUpTest\\(\\) is not final, but since the containing class is abstract, it should be\\.$#" + count: 1 + path: test/TestBase.php - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" count: 2 diff --git a/src/Logging/JUnit/Reader.php b/src/Logging/JUnit/Reader.php index 45617703..d659bc1b 100644 --- a/src/Logging/JUnit/Reader.php +++ b/src/Logging/JUnit/Reader.php @@ -9,7 +9,6 @@ use SimpleXMLElement; use function array_merge; -use function array_reduce; use function assert; use function count; use function current; @@ -18,7 +17,7 @@ use function filesize; use function unlink; -final class Reader extends MetaProvider +final class Reader implements MetaProvider { /** @var SimpleXMLElement */ private $xml; @@ -236,38 +235,85 @@ private function initSuite(): void } } + public function getTotalTests(): int + { + return $this->suites[0]->tests; + } + + public function getTotalAssertions(): int + { + return $this->suites[0]->assertions; + } + + public function getTotalFailures(): int + { + return $this->suites[0]->failures; + } + + public function getTotalErrors(): int + { + return $this->suites[0]->errors; + } + + public function getTotalWarning(): int + { + return $this->suites[0]->warnings; + } + + public function getTotalTime(): float + { + return $this->suites[0]->time; + } + /** - * Return a value as a float or integer. - * - * @return float|int + * {@inheritDoc} + */ + public function getErrors(): array + { + $messages = []; + $suites = $this->isSingle ? $this->suites : $this->suites[0]->suites; + foreach ($suites as $suite) { + foreach ($suite->cases as $case) { + foreach ($case->errors as $msg) { + $messages[] = $msg['text']; + } + } + } + + return $messages; + } + + /** + * {@inheritDoc} */ - protected function getNumericValue(string $property) + public function getWarnings(): array { - return $property === 'time' - ? (float) $this->suites[0]->$property - : (int) $this->suites[0]->$property; + $messages = []; + $suites = $this->isSingle ? $this->suites : $this->suites[0]->suites; + foreach ($suites as $suite) { + foreach ($suite->cases as $case) { + foreach ($case->warnings as $msg) { + $messages[] = $msg['text']; + } + } + } + + return $messages; } /** - * Return messages for a given type. - * - * @return string[] + * {@inheritDoc} */ - protected function getMessages(string $type): array + public function getFailures(): array { $messages = []; $suites = $this->isSingle ? $this->suites : $this->suites[0]->suites; foreach ($suites as $suite) { - $messages = array_merge( - $messages, - array_reduce($suite->cases, static function (array $result, TestCase $case) use ($type): array { - return array_merge($result, array_reduce($case->$type, static function (array $msgs, array $msg): array { - $msgs[] = $msg['text']; - - return $msgs; - }, [])); - }, []) - ); + foreach ($suite->cases as $case) { + foreach ($case->failures as $msg) { + $messages[] = $msg['text']; + } + } } return $messages; diff --git a/src/Logging/JUnit/Writer.php b/src/Logging/JUnit/Writer.php index 5d672142..2494eeaa 100644 --- a/src/Logging/JUnit/Writer.php +++ b/src/Logging/JUnit/Writer.php @@ -8,14 +8,14 @@ use DOMElement; use ParaTest\Logging\LogInterpreter; -use function array_merge; -use function array_reduce; use function assert; use function count; use function file_put_contents; use function get_object_vars; use function htmlspecialchars; use function preg_match; +use function sprintf; +use function str_replace; use const ENT_XML1; @@ -40,7 +40,7 @@ final class Writer * * @var string */ - private static $suiteAttrs = '/name|(?:test|assertion|failure|error)s|time|file/'; + private static $suiteAttrs = '/name|(?:test|assertion|failure|error|warning)s|skipped|time|file/'; /** * A pattern for matching testcase attrs. @@ -49,21 +49,6 @@ final class Writer */ private static $caseAttrs = '/name|class|file|line|assertions|time/'; - /** - * A default suite to ease flattening of - * suite structures. - * - * @var array - */ - private static $defaultSuite = [ - 'tests' => 0, - 'assertions' => 0, - 'failures' => 0, - 'skipped' => 0, - 'errors' => 0, - 'time' => 0, - ]; - public function __construct(LogInterpreter $interpreter, string $name) { $this->name = $name; @@ -124,6 +109,10 @@ private function appendSuite(DOMElement $root, TestSuite $suite): DOMElement continue; } + if ($name === 'time') { + $value = sprintf('%F', $value); + } + $suiteNode->setAttribute($name, (string) $value); } @@ -141,9 +130,9 @@ private function appendCase(DOMElement $suiteNode, TestCase $case): DOMElement $caseNode = $this->document->createElement('testcase'); $vars = get_object_vars($case); foreach ($vars as $name => $value) { - $match = preg_match(static::$caseAttrs, $name); - assert($match !== false); - if ($match === 0) { + $matchCount = preg_match(static::$caseAttrs, $name); + assert($matchCount !== false); + if ($matchCount === 0) { continue; } @@ -151,12 +140,24 @@ private function appendCase(DOMElement $suiteNode, TestCase $case): DOMElement continue; } + if ($name === 'time') { + $value = sprintf('%F', $value); + } + $caseNode->setAttribute($name, (string) $value); + + if ($name !== 'class') { + continue; + } + + $caseNode->setAttribute('classname', str_replace('\\', '.', (string) $value)); } $suiteNode->appendChild($caseNode); $this->appendDefects($caseNode, $case->failures, 'failure'); $this->appendDefects($caseNode, $case->errors, 'error'); + $this->appendDefects($caseNode, $case->warnings, 'warning'); + $this->appendDefects($caseNode, $case->skipped, 'skipped'); return $caseNode; } @@ -169,8 +170,13 @@ private function appendCase(DOMElement $suiteNode, TestCase $case): DOMElement private function appendDefects(DOMElement $caseNode, array $defects, string $type): void { foreach ($defects as $defect) { - $defectNode = $this->document->createElement($type, htmlspecialchars($defect['text'], ENT_XML1) . "\n"); - $defectNode->setAttribute('type', $defect['type']); + if ($type === 'skipped') { + $defectNode = $this->document->createElement($type); + } else { + $defectNode = $this->document->createElement($type, htmlspecialchars($defect['text'], ENT_XML1) . "\n"); + $defectNode->setAttribute('type', $defect['type']); + } + $caseNode->appendChild($defectNode); } } @@ -203,22 +209,34 @@ private function getSuiteRoot(array $suites): DOMElement * Get the attributes used on the root testsuite * node. * - * @param array $suites + * @param TestSuite[] $suites * - * @return mixed + * @return array */ - private function getSuiteRootAttributes(array $suites) + private function getSuiteRootAttributes(array $suites): array { - return array_reduce($suites, static function (array $result, TestSuite $suite): array { + $result = [ + 'name' => $this->name, + 'tests' => 0, + 'assertions' => 0, + 'errors' => 0, + 'warnings' => 0, + 'failures' => 0, + 'skipped' => 0, + 'time' => 0, + ]; + + foreach ($suites as $suite) { $result['tests'] += $suite->tests; $result['assertions'] += $suite->assertions; + $result['errors'] += $suite->errors; + $result['warnings'] += $suite->warnings; $result['failures'] += $suite->failures; $result['skipped'] += $suite->skipped; - $result['errors'] += $suite->errors; $result['time'] += $suite->time; + } - return $result; - }, array_merge(['name' => $this->name], self::$defaultSuite)); + return $result; } /** diff --git a/src/Logging/LogInterpreter.php b/src/Logging/LogInterpreter.php index b1059791..54580932 100644 --- a/src/Logging/LogInterpreter.php +++ b/src/Logging/LogInterpreter.php @@ -13,9 +13,8 @@ use function array_values; use function count; use function reset; -use function ucfirst; -final class LogInterpreter extends MetaProvider +final class LogInterpreter implements MetaProvider { /** * A collection of Reader objects @@ -65,10 +64,7 @@ public function getReaders(): array */ public function isSuccessful(): bool { - $failures = $this->getNumericValue('failures'); - $errors = $this->getNumericValue('errors'); - - return $failures === 0 && $errors === 0; + return $this->getTotalFailures() === 0 && $this->getTotalErrors() === 0; } /** @@ -119,7 +115,7 @@ private function extendEmptyCasesFromSuites(array $cases, TestSuite $suite): voi /** * Flattens all cases into their respective suites. * - * @return TestSuite[] $suites a collection of suites and their cases + * @return TestSuite[] A collection of suites and their cases */ public function flattenCases(): array { @@ -136,6 +132,7 @@ public function flattenCases(): array $dict[$case->file]->assertions += $case->assertions; $dict[$case->file]->failures += count($case->failures); $dict[$case->file]->errors += count($case->errors); + $dict[$case->file]->warnings += count($case->warnings); $dict[$case->file]->skipped += count($case->skipped); $dict[$case->file]->time += $case->time; } @@ -143,57 +140,84 @@ public function flattenCases(): array return array_values($dict); } - /** - * Returns a value as either a float or int. - * - * @return float|int - */ - protected function getNumericValue(string $property) + public function getTotalTests(): int + { + return array_reduce($this->readers, static function (int $result, Reader $reader): int { + return $result + $reader->getTotalTests(); + }, 0); + } + + public function getTotalAssertions(): int { - return $property === 'time' - ? (float) $this->accumulate('getTotalTime') - : (int) $this->accumulate('getTotal' . ucfirst($property)); + return array_reduce($this->readers, static function (int $result, Reader $reader): int { + return $result + $reader->getTotalAssertions(); + }, 0); + } + + public function getTotalFailures(): int + { + return array_reduce($this->readers, static function (int $result, Reader $reader): int { + return $result + $reader->getTotalFailures(); + }, 0); + } + + public function getTotalErrors(): int + { + return array_reduce($this->readers, static function (int $result, Reader $reader): int { + return $result + $reader->getTotalErrors(); + }, 0); + } + + public function getTotalWarning(): int + { + return array_reduce($this->readers, static function (int $result, Reader $reader): int { + return $result + $reader->getTotalWarning(); + }, 0); + } + + public function getTotalTime(): float + { + return array_reduce($this->readers, static function (float $result, Reader $reader): float { + return $result + $reader->getTotalTime(); + }, 0.0); } /** - * Gets messages of a given type and - * merges them into a single collection. - * - * @return string[] + * {@inheritDoc} */ - protected function getMessages(string $type): array + public function getErrors(): array { - return $this->mergeMessages('get' . ucfirst($type)); + $messages = []; + foreach ($this->readers as $reader) { + $messages = array_merge($messages, $reader->getErrors()); + } + + return $messages; } /** - * Flatten messages into a single collection - * based on an accessor method. - * - * @return string[] + * {@inheritDoc} */ - private function mergeMessages(string $method): array + public function getWarnings(): array { $messages = []; foreach ($this->readers as $reader) { - $messages = array_merge($messages, $reader->{$method}()); + $messages = array_merge($messages, $reader->getWarnings()); } return $messages; } /** - * Reduces a collection of readers down to a single - * result based on an accessor. - * - * @return mixed + * {@inheritDoc} */ - private function accumulate(string $method) + public function getFailures(): array { - return array_reduce($this->readers, static function (int $result, Reader $reader) use ($method): int { - $result += $reader->$method(); + $messages = []; + foreach ($this->readers as $reader) { + $messages = array_merge($messages, $reader->getFailures()); + } - return $result; - }, 0); + return $messages; } } diff --git a/src/Logging/MetaProvider.php b/src/Logging/MetaProvider.php index 50165080..687d48f3 100644 --- a/src/Logging/MetaProvider.php +++ b/src/Logging/MetaProvider.php @@ -4,75 +4,26 @@ namespace ParaTest\Logging; -use RuntimeException; +interface MetaProvider +{ + public function getTotalTests(): int; -use function preg_match; -use function strtolower; + public function getTotalAssertions(): int; -/** - * Adds __call behavior to a logging object - * for aggregating totals and messages - * - * @method int getTotalTests() - * @method int getTotalAssertions() - * @method int getTotalFailures() - * @method int getTotalErrors() - * @method int getTotalWarning() - * @method int getTotalTime() - * @method string[] getFailures() - * @method string[] getErrors() - * @method string[] getWarnings() - */ -abstract class MetaProvider -{ - /** - * This pattern is used to see whether a missing - * method is a "total" method or not. - * - * @var string - */ - protected static $totalMethod = '/^getTotal([\w]+)$/'; + public function getTotalFailures(): int; - /** - * This pattern is used to add message retrieval for a given - * type - i.e getFailures() or getErrors(). - * - * @var string - */ - protected static $messageMethod = '/^get((Failure|Error|Warning)s)$/'; + public function getTotalErrors(): int; - /** - * Simplify aggregation of totals or messages. - * - * @param mixed[] $args - * - * @return float|int|string[] - */ - final public function __call(string $method, array $args) - { - if (preg_match(self::$totalMethod, $method, $matches) > 0 && ($property = strtolower($matches[1])) !== '') { - return $this->getNumericValue($property); - } + public function getTotalWarning(): int; - if (preg_match(self::$messageMethod, $method, $matches) > 0 && ($type = strtolower($matches[1])) !== '') { - return $this->getMessages($type); - } + public function getTotalTime(): float; - throw new RuntimeException("Method {$method} uknown"); - } + /** @return string[] */ + public function getErrors(): array; - /** - * Returns a value as either a float or int. - * - * @return float|int - */ - abstract protected function getNumericValue(string $property); + /** @return string[] */ + public function getWarnings(): array; - /** - * Gets messages of a given type and - * merges them into a single collection. - * - * @return string[] - */ - abstract protected function getMessages(string $type): array; + /** @return string[] */ + public function getFailures(): array; } diff --git a/src/Runners/PHPUnit/BaseRunner.php b/src/Runners/PHPUnit/BaseRunner.php index 1f869cce..51b31a1c 100644 --- a/src/Runners/PHPUnit/BaseRunner.php +++ b/src/Runners/PHPUnit/BaseRunner.php @@ -5,6 +5,7 @@ namespace ParaTest\Runners\PHPUnit; use ParaTest\Coverage\CoverageMerger; +use ParaTest\Coverage\CoverageReporter; use ParaTest\Logging\JUnit\Writer; use ParaTest\Logging\LogInterpreter; use Symfony\Component\Console\Output\OutputInterface; @@ -112,7 +113,9 @@ final protected function logCoverage(): void $coverageMerger = $this->getCoverage(); assert($coverageMerger !== null); - $reporter = $coverageMerger->getReporter(); + $codeCoverage = $coverageMerger->getCodeCoverageObject(); + assert($codeCoverage !== null); + $reporter = new CoverageReporter($codeCoverage); if (($coverageClover = $this->options->coverageClover()) !== null) { $reporter->clover($coverageClover); diff --git a/src/Runners/PHPUnit/WrapperRunner.php b/src/Runners/PHPUnit/WrapperRunner.php index bc0a39e0..7c2a23d5 100644 --- a/src/Runners/PHPUnit/WrapperRunner.php +++ b/src/Runners/PHPUnit/WrapperRunner.php @@ -151,8 +151,10 @@ private function flushWorker(WrapperWorker $worker): void if ($this->hasCoverage()) { $coverageMerger = $this->getCoverage(); assert($coverageMerger !== null); + $coverageFileName = $worker->getCoverageFileName(); + assert($coverageFileName !== null); - $coverageMerger->addCoverageFromFile($worker->getCoverageFileName()); + $coverageMerger->addCoverageFromFile($coverageFileName); } $worker->printFeedback($this->printer); diff --git a/test/Functional/Runners/PHPUnit/RunnerIntegrationTest.php b/test/Functional/Runners/PHPUnit/RunnerIntegrationTest.php index 73440faf..b9883453 100644 --- a/test/Functional/Runners/PHPUnit/RunnerIntegrationTest.php +++ b/test/Functional/Runners/PHPUnit/RunnerIntegrationTest.php @@ -123,6 +123,8 @@ public function assertJunitXmlIsCorrect(string $path): void $suites = $doc->xpath('//testsuite'); $cases = $doc->xpath('//testcase'); $failures = $doc->xpath('//failure'); + $warnings = $doc->xpath('//warning'); + $skipped = $doc->xpath('//skipped'); $errors = $doc->xpath('//error'); // these numbers represent the tests in fixtures/failing-tests @@ -130,10 +132,14 @@ public function assertJunitXmlIsCorrect(string $path): void static::assertNotFalse($suites); static::assertCount(6, $suites); static::assertNotFalse($cases); - static::assertCount(16, $cases); + static::assertCount(24, $cases); static::assertNotFalse($failures); static::assertCount(6, $failures); + static::assertNotFalse($warnings); + static::assertCount(2, $warnings); + static::assertNotFalse($skipped); + static::assertCount(4, $skipped); static::assertNotFalse($errors); - static::assertCount(1, $errors); + static::assertCount(3, $errors); } } diff --git a/test/Unit/Logging/JUnit/ReaderTest.php b/test/Unit/Logging/JUnit/ReaderTest.php index a708d201..8ea6523a 100644 --- a/test/Unit/Logging/JUnit/ReaderTest.php +++ b/test/Unit/Logging/JUnit/ReaderTest.php @@ -13,6 +13,7 @@ use function file_get_contents; use function file_put_contents; +use function implode; /** * @covers \ParaTest\Logging\JUnit\Reader @@ -72,12 +73,12 @@ public function testMixedSuiteShouldConstructRootSuite(): TestSuite { $suites = $this->mixed->getSuites(); static::assertCount(1, $suites); - static::assertEquals('test/fixtures/tests/', $suites[0]->name); - static::assertEquals('7', $suites[0]->tests); - static::assertEquals('6', $suites[0]->assertions); - static::assertEquals('2', $suites[0]->failures); - static::assertEquals('1', $suites[0]->errors); - static::assertEquals('0.007625', $suites[0]->time); + static::assertSame('test/fixtures/tests/', $suites[0]->name); + static::assertSame(19, $suites[0]->tests); + static::assertSame(10, $suites[0]->assertions); + static::assertSame(3, $suites[0]->failures); + static::assertSame(3, $suites[0]->errors); + static::assertSame(0.001489, $suites[0]->time); return $suites[0]; } @@ -89,16 +90,16 @@ public function testMixedSuiteConstructsChildSuites(TestSuite $suite): TestSuite { static::assertCount(3, $suite->suites); $first = $suite->suites[0]; - static::assertEquals('UnitTestWithClassAnnotationTest', $first->name); - static::assertEquals( - '/home/brian/Projects/parallel-phpunit/test/fixtures/tests/UnitTestWithClassAnnotationTest.php', + static::assertSame('Fixtures\\Tests\\UnitTestWithClassAnnotationTest', $first->name); + static::assertSame( + '/home/brian/Projects/parallel-phpunit/test/fixtures/failing-tests/UnitTestWithClassAnnotationTest.php', $first->file ); - static::assertEquals('3', $first->tests); - static::assertEquals('3', $first->assertions); - static::assertEquals('1', $first->failures); - static::assertEquals('0', $first->errors); - static::assertEquals('0.006109', $first->time); + static::assertSame(4, $first->tests); + static::assertSame(4, $first->assertions); + static::assertSame(1, $first->failures); + static::assertSame(0, $first->errors); + static::assertSame(0.000357, $first->time); return $first; } @@ -108,17 +109,17 @@ public function testMixedSuiteConstructsChildSuites(TestSuite $suite): TestSuite */ public function testMixedSuiteConstructsTestCases(TestSuite $suite): void { - static::assertCount(3, $suite->cases); + static::assertCount(4, $suite->cases); $first = $suite->cases[0]; - static::assertEquals('testTruth', $first->name); - static::assertEquals('UnitTestWithClassAnnotationTest', $first->class); - static::assertEquals( - '/home/brian/Projects/parallel-phpunit/test/fixtures/tests/UnitTestWithClassAnnotationTest.php', + static::assertSame('testTruth', $first->name); + static::assertSame('Fixtures\\Tests\\UnitTestWithClassAnnotationTest', $first->class); + static::assertSame( + '/home/brian/Projects/parallel-phpunit/test/fixtures/failing-tests/UnitTestWithClassAnnotationTest.php', $first->file ); - static::assertEquals('10', $first->line); - static::assertEquals('1', $first->assertions); - static::assertEquals('0.001760', $first->time); + static::assertSame(21, $first->line); + static::assertSame(1, $first->assertions); + static::assertSame(0.000042, $first->time); } public function testMixedSuiteCasesLoadFailures(): void @@ -127,10 +128,10 @@ public function testMixedSuiteCasesLoadFailures(): void $case = $suites[0]->suites[0]->cases[1]; static::assertCount(1, $case->failures); $failure = $case->failures[0]; - static::assertEquals(ExpectationFailedException::class, $failure['type']); - static::assertEquals( - "UnitTestWithClassAnnotationTest::testFalsehood\nFailed asserting that true is false.\n\n" . - '/home/brian/Projects/parallel-phpunit/test/fixtures/tests/UnitTestWithClassAnnotationTest.php:20', + static::assertSame(ExpectationFailedException::class, $failure['type']); + static::assertSame( + "Fixtures\\Tests\\UnitTestWithClassAnnotationTest::testFalsehood\nFailed asserting that true is false.\n\n" . + '/home/brian/Projects/parallel-phpunit/test/fixtures/failing-tests/UnitTestWithClassAnnotationTest.php:32', $failure['text'] ); } @@ -141,10 +142,10 @@ public function testMixedSuiteCasesLoadErrors(): void $case = $suites[0]->suites[1]->cases[0]; static::assertCount(1, $case->errors); $error = $case->errors[0]; - static::assertEquals('Exception', $error['type']); - static::assertEquals( + static::assertSame('Exception', $error['type']); + static::assertSame( "UnitTestWithErrorTest::testTruth\nException: Error!!!\n\n" . - '/home/brian/Projects/parallel-phpunit/test/fixtures/tests/UnitTestWithErrorTest.php:12', + '/home/brian/Projects/parallel-phpunit/test/fixtures/failing-tests/UnitTestWithErrorTest.php:17', $error['text'] ); } @@ -153,16 +154,16 @@ public function testSingleSuiteShouldConstructRootSuite(): TestSuite { $suites = $this->single->getSuites(); static::assertCount(1, $suites); - static::assertEquals('UnitTestWithMethodAnnotationsTest', $suites[0]->name); - static::assertEquals( + static::assertSame('UnitTestWithMethodAnnotationsTest', $suites[0]->name); + static::assertSame( '/home/brian/Projects/parallel-phpunit/test/fixtures/tests/UnitTestWithMethodAnnotationsTest.php', $suites[0]->file ); - static::assertEquals('3', $suites[0]->tests); - static::assertEquals('3', $suites[0]->assertions); - static::assertEquals('1', $suites[0]->failures); - static::assertEquals('0', $suites[0]->errors); - static::assertEquals('0.005895', $suites[0]->time); + static::assertSame(3, $suites[0]->tests); + static::assertSame(3, $suites[0]->assertions); + static::assertSame(1, $suites[0]->failures); + static::assertSame(0, $suites[0]->errors); + static::assertSame(0.005895, $suites[0]->time); return $suites[0]; } @@ -186,15 +187,15 @@ public function testSingleSuiteConstructsTestCases($suite): void { static::assertCount(3, $suite->cases); $first = $suite->cases[0]; - static::assertEquals('testTruth', $first->name); - static::assertEquals('UnitTestWithMethodAnnotationsTest', $first->class); - static::assertEquals( + static::assertSame('testTruth', $first->name); + static::assertSame('UnitTestWithMethodAnnotationsTest', $first->class); + static::assertSame( '/home/brian/Projects/parallel-phpunit/test/fixtures/tests/UnitTestWithMethodAnnotationsTest.php', $first->file ); - static::assertEquals('7', $first->line); - static::assertEquals('1', $first->assertions); - static::assertEquals('0.001632', $first->time); + static::assertSame(7, $first->line); + static::assertSame(1, $first->assertions); + static::assertSame(0.001632, $first->time); } public function testSingleSuiteCasesLoadFailures(): void @@ -203,8 +204,8 @@ public function testSingleSuiteCasesLoadFailures(): void $case = $suites[0]->cases[1]; static::assertCount(1, $case->failures); $failure = $case->failures[0]; - static::assertEquals(ExpectationFailedException::class, $failure['type']); - static::assertEquals( + static::assertSame(ExpectationFailedException::class, $failure['type']); + static::assertSame( "UnitTestWithMethodAnnotationsTest::testFalsehood\nFailed asserting that true is false.\n\n" . '/home/brian/Projects/parallel-phpunit/test/fixtures/tests/UnitTestWithMethodAnnotationsTest.php:18', $failure['text'] @@ -217,46 +218,48 @@ public function testEmptySuiteConstructsTestCase(): void static::assertCount(1, $suites); $suite = $suites[0]; - static::assertEquals('', $suite->name); - static::assertEquals('', $suite->file); - static::assertEquals(0, $suite->tests); - static::assertEquals(0, $suite->assertions); - static::assertEquals(0, $suite->failures); - static::assertEquals(0, $suite->errors); - static::assertEquals(0, $suite->time); + static::assertSame('', $suite->name); + static::assertSame('', $suite->file); + static::assertSame(0, $suite->tests); + static::assertSame(0, $suite->assertions); + static::assertSame(0, $suite->failures); + static::assertSame(0, $suite->errors); + static::assertSame(0.0, $suite->time); } public function testMixedGetTotals(): void { - static::assertEquals(7, $this->mixed->getTotalTests()); - static::assertEquals(6, $this->mixed->getTotalAssertions()); - static::assertEquals(2, $this->mixed->getTotalFailures()); - static::assertEquals(1, $this->mixed->getTotalErrors()); - static::assertEquals(0.007625, $this->mixed->getTotalTime()); + static::assertSame(19, $this->mixed->getTotalTests()); + static::assertSame(10, $this->mixed->getTotalAssertions()); + static::assertSame(3, $this->mixed->getTotalFailures()); + static::assertSame(2, $this->mixed->getTotalWarning()); + static::assertSame(3, $this->mixed->getTotalErrors()); + static::assertSame(0.001489, $this->mixed->getTotalTime()); } public function testSingleGetTotals(): void { - static::assertEquals(3, $this->single->getTotalTests()); - static::assertEquals(3, $this->single->getTotalAssertions()); - static::assertEquals(1, $this->single->getTotalFailures()); - static::assertEquals(0, $this->single->getTotalErrors()); - static::assertEquals(0.005895, $this->single->getTotalTime()); + static::assertSame(3, $this->single->getTotalTests()); + static::assertSame(3, $this->single->getTotalAssertions()); + static::assertSame(1, $this->single->getTotalFailures()); + static::assertSame(0, $this->single->getTotalWarning()); + static::assertSame(0, $this->single->getTotalErrors()); + static::assertSame(0.005895, $this->single->getTotalTime()); } public function testMixedGetFailureMessages(): void { $failures = $this->mixed->getFailures(); - static::assertCount(2, $failures); - static::assertEquals( - "UnitTestWithClassAnnotationTest::testFalsehood\nFailed asserting that true is false.\n\n" . - '/home/brian/Projects/parallel-phpunit/test/fixtures/tests/UnitTestWithClassAnnotationTest.php:20', + static::assertCount(3, $failures); + static::assertSame( + "Fixtures\\Tests\\UnitTestWithClassAnnotationTest::testFalsehood\nFailed asserting that true is false.\n\n" . + '/home/brian/Projects/parallel-phpunit/test/fixtures/failing-tests/UnitTestWithClassAnnotationTest.php:32', $failures[0] ); - static::assertEquals( - "UnitTestWithMethodAnnotationsTest::testFalsehood\nFailed asserting that true is false." . - "\n\n/home/brian/Projects/parallel-phpunit/test/fixtures/tests/UnitTestWithMethodAnnotationsTest." . - 'php:18', + static::assertSame( + "UnitTestWithErrorTest::testFalsehood\nFailed asserting that true is false." . + "\n\n/home/brian/Projects/parallel-phpunit/test/fixtures/failing-tests/UnitTestWithMethodAnnotationsTest." . + 'php:20', $failures[1] ); } @@ -264,19 +267,30 @@ public function testMixedGetFailureMessages(): void public function testMixedGetErrorMessages(): void { $errors = $this->mixed->getErrors(); - static::assertCount(1, $errors); - static::assertEquals( + static::assertCount(3, $errors); + static::assertSame( "UnitTestWithErrorTest::testTruth\nException: Error!!!\n\n" . - '/home/brian/Projects/parallel-phpunit/test/fixtures/tests/UnitTestWithErrorTest.php:12', + '/home/brian/Projects/parallel-phpunit/test/fixtures/failing-tests/UnitTestWithErrorTest.php:17', $errors[0] ); } + public function testMixedGetWarningMessages(): void + { + $warnings = $this->mixed->getWarnings(); + static::assertCount(2, $warnings); + static::assertSame( + "UnitTestWithErrorTest::testWarning\n" . + 'Function 1 deprecated', + $warnings[0] + ); + } + public function testSingleGetMessages(): void { $failures = $this->single->getFailures(); static::assertCount(1, $failures); - static::assertEquals( + static::assertSame( "UnitTestWithMethodAnnotationsTest::testFalsehood\nFailed asserting that true is false.\n\n" . '/home/brian/Projects/parallel-phpunit/test/fixtures/tests/UnitTestWithMethodAnnotationsTest.php:18', $failures[0] @@ -290,14 +304,14 @@ public function testGetMultiErrorsMessages(): void { $errors = $this->multi_errors->getErrors(); static::assertCount(2, $errors); - static::assertEquals( + static::assertSame( "Risky Test\n" . "/project/vendor/phpunit/phpunit/src/TextUI/Command.php:200\n" . "/project/vendor/phpunit/phpunit/src/TextUI/Command.php:159\n" . 'Custom error log on result test with multiple errors!', $errors[0] ); - static::assertEquals( + static::assertSame( "Risky Test\n" . "/project/vendor/phpunit/phpunit/src/TextUI/Command.php:200\n" . "/project/vendor/phpunit/phpunit/src/TextUI/Command.php:159\n" . @@ -309,7 +323,7 @@ public function testGetMultiErrorsMessages(): void public function testMixedGetFeedback(): void { $feedback = $this->mixed->getFeedback(); - static::assertSame(['.', 'F', '.', 'E', '.', 'F', '.', 'W', 'S'], $feedback); + static::assertSame('.F..E.F.WSSE.F.WSSE', implode('', $feedback)); } public function testRemoveLog(): void @@ -348,7 +362,7 @@ public function testResultWithSystemOut(): void $resultFail = $node->getSuites()[0]->suites[2]->cases[1]->failures[0]['text']; $resultError = $node->getSuites()[0]->suites[1]->cases[1]->errors[0]['text']; - static::assertEquals($failLog, $resultFail); - static::assertEquals($errorLog, $resultError); + static::assertSame($failLog, $resultFail); + static::assertSame($errorLog, $resultError); } } diff --git a/test/Unit/Logging/JUnit/WriterTest.php b/test/Unit/Logging/JUnit/WriterTest.php index b32cdf4e..da8eabc6 100644 --- a/test/Unit/Logging/JUnit/WriterTest.php +++ b/test/Unit/Logging/JUnit/WriterTest.php @@ -14,7 +14,7 @@ use function unlink; /** - * @coversNothing + * @covers \ParaTest\Logging\JUnit\Writer */ final class WriterTest extends TestBase { diff --git a/test/Unit/Logging/LogInterpreterTest.php b/test/Unit/Logging/LogInterpreterTest.php index c652cd4b..e4bf6c15 100644 --- a/test/Unit/Logging/LogInterpreterTest.php +++ b/test/Unit/Logging/LogInterpreterTest.php @@ -30,7 +30,7 @@ protected function setUpInterpreter(): void public function testConstructor(): void { $interpreter = new LogInterpreter(); - static::assertEquals([], $this->getObjectValue($interpreter, 'readers')); + static::assertSame([], $this->getObjectValue($interpreter, 'readers')); } public function testAddReaderIncrementsReaders(): void @@ -58,22 +58,22 @@ public function testGetReaders(): void public function testGetTotalTests(): void { - static::assertEquals(10, $this->interpreter->getTotalTests()); + static::assertSame(22, $this->interpreter->getTotalTests()); } public function testGetTotalAssertions(): void { - static::assertEquals(9, $this->interpreter->getTotalAssertions()); + static::assertSame(13, $this->interpreter->getTotalAssertions()); } public function testGetTotalFailures(): void { - static::assertEquals(2, $this->interpreter->getTotalFailures()); + static::assertSame(3, $this->interpreter->getTotalFailures()); } public function testGetTotalErrors(): void { - static::assertEquals(1, $this->interpreter->getTotalErrors()); + static::assertSame(3, $this->interpreter->getTotalErrors()); } public function testIsSuccessfulReturnsFalseIfFailuresPresentAndNoErrors(): void @@ -106,27 +106,31 @@ public function testGetErrorsReturnsArrayOfErrorMessages(): void { $errors = [ "UnitTestWithErrorTest::testTruth\nException: Error!!!\n\n/home/brian/Projects/parallel-phpunit/" . - 'test/fixtures/tests/UnitTestWithErrorTest.php:12', + 'test/fixtures/failing-tests/UnitTestWithErrorTest.php:17', + 'Risky Test', + 'Risky Test', ]; - static::assertEquals($errors, $this->interpreter->getErrors()); + static::assertSame($errors, $this->interpreter->getErrors()); } public function testGetFailuresReturnsArrayOfFailureMessages(): void { $failures = [ - "UnitTestWithClassAnnotationTest::testFalsehood\nFailed asserting that true is false.\n\n/" . - 'home/brian/Projects/parallel-phpunit/test/fixtures/tests/UnitTestWithClassAnnotationTest.php:20', + "Fixtures\\Tests\\UnitTestWithClassAnnotationTest::testFalsehood\nFailed asserting that true is false.\n\n/" . + 'home/brian/Projects/parallel-phpunit/test/fixtures/failing-tests/UnitTestWithClassAnnotationTest.php:32', + "UnitTestWithErrorTest::testFalsehood\nFailed asserting that true is false.\n\n" . + '/home/brian/Projects/parallel-phpunit/test/fixtures/failing-tests/UnitTestWithMethodAnnotationsTest.php:20', "UnitTestWithMethodAnnotationsTest::testFalsehood\nFailed asserting that true is false.\n\n" . - '/home/brian/Projects/parallel-phpunit/test/fixtures/tests/UnitTestWithMethodAnnotationsTest.php:18', + '/home/brian/Projects/parallel-phpunit/test/fixtures/failing-tests/UnitTestWithMethodAnnotationsTest.php:20', ]; - static::assertEquals($failures, $this->interpreter->getFailures()); + static::assertSame($failures, $this->interpreter->getFailures()); } public function testGetCasesReturnsAllCases(): void { $cases = $this->interpreter->getCases(); - static::assertCount(10, $cases); + static::assertCount(22, $cases); } public function testGetCasesExtendEmptyCasesFromSuites(): void @@ -138,25 +142,25 @@ public function testGetCasesExtendEmptyCasesFromSuites(): void static::assertCount(10, $cases); foreach ($cases as $name => $case) { if ($case->name === 'testNumericDataProvider5 with data set #3') { - static::assertEquals($case->class, 'DataProviderTest1'); + static::assertSame($case->class, 'DataProviderTest1'); } elseif ($case->name === 'testNamedDataProvider5 with data set #3') { - static::assertEquals($case->class, 'DataProviderTest2'); + static::assertSame($case->class, 'DataProviderTest2'); } else { - static::assertEquals($case->class, 'DataProviderTest'); + static::assertSame($case->class, 'DataProviderTest'); } if ($case->name === 'testNumericDataProvider5 with data set #4') { - static::assertEquals( + static::assertSame( $case->file, '/var/www/project/vendor/brianium/paratest/test/fixtures/dataprovider-tests/DataProviderTest1.php' ); } elseif ($case->name === 'testNamedDataProvider5 with data set #4') { - static::assertEquals( + static::assertSame( $case->file, '/var/www/project/vendor/brianium/paratest/test/fixtures/dataprovider-tests/DataProviderTest2.php' ); } else { - static::assertEquals( + static::assertSame( $case->file, '/var/www/project/vendor/brianium/paratest/test/fixtures/dataprovider-tests/DataProviderTest.php' ); @@ -183,15 +187,17 @@ public function testFlattenCasesReturnsCorrectNumberOfSuites(): array public function testFlattenedSuiteHasCorrectTotals(array $suites): void { $first = $suites[0]; - static::assertEquals('UnitTestWithClassAnnotationTest', $first->name); - static::assertEquals( - '/home/brian/Projects/parallel-phpunit/test/fixtures/tests/UnitTestWithClassAnnotationTest.php', + static::assertSame('Fixtures\\Tests\\UnitTestWithClassAnnotationTest', $first->name); + static::assertSame( + '/home/brian/Projects/parallel-phpunit/test/fixtures/failing-tests/UnitTestWithClassAnnotationTest.php', $first->file ); - static::assertEquals('3', $first->tests); - static::assertEquals('3', $first->assertions); - static::assertEquals('1', $first->failures); - static::assertEquals('0', $first->errors); - static::assertEquals('0.006109', $first->time); + static::assertSame(4, $first->tests); + static::assertSame(4, $first->assertions); + static::assertSame(1, $first->failures); + static::assertSame(0, $first->warnings); + static::assertSame(0, $first->skipped); + static::assertSame(0, $first->errors); + static::assertSame(0.000357, $first->time); } } diff --git a/test/Unit/Parser/GetClassTest.php b/test/Unit/Parser/GetClassTest.php index 7722818b..bf3f5127 100644 --- a/test/Unit/Parser/GetClassTest.php +++ b/test/Unit/Parser/GetClassTest.php @@ -58,7 +58,7 @@ public function testParsedClassHasCorrectNumberOfTestMethods(): void public function testParsedClassWithParentHasCorrectNumberOfTestMethods(): void { $class = $this->parseFile($this->fixture('failing-tests/UnitTestWithErrorTest.php')); - static::assertCount(4, $class->getMethods()); + static::assertCount(8, $class->getMethods()); } /** diff --git a/test/Unit/Runners/PHPUnit/ResultPrinterTest.php b/test/Unit/Runners/PHPUnit/ResultPrinterTest.php index b23af20d..e3f2236a 100644 --- a/test/Unit/Runners/PHPUnit/ResultPrinterTest.php +++ b/test/Unit/Runners/PHPUnit/ResultPrinterTest.php @@ -58,7 +58,7 @@ private function getPathToConfig(): string public function testConstructor(): void { - static::assertEquals([], $this->getObjectValue($this->printer, 'suites')); + static::assertSame([], $this->getObjectValue($this->printer, 'suites')); static::assertInstanceOf( LogInterpreter::class, $this->getObjectValue($this->printer, 'results') @@ -71,7 +71,7 @@ public function testAddTestShouldAddTest(): void $this->printer->addTest($suite); - static::assertEquals([$suite], $this->getObjectValue($this->printer, 'suites')); + static::assertSame([$suite], $this->getObjectValue($this->printer, 'suites')); } public function testAddTestReturnsSelf(): void @@ -105,14 +105,14 @@ public function testStartSetsWidthAndMaxColumn(): void $this->printer->addTest($suite); $this->getStartOutput(); $numTestsWidth = $this->getObjectValue($this->printer, 'numTestsWidth'); - static::assertEquals(3, $numTestsWidth); + static::assertSame(3, $numTestsWidth); $maxExpectedColumun = 63; if (defined('PHP_WINDOWS_VERSION_BUILD')) { $maxExpectedColumun -= 1; } $maxColumn = $this->getObjectValue($this->printer, 'maxColumn'); - static::assertEquals($maxExpectedColumun, $maxColumn); + static::assertSame($maxExpectedColumun, $maxColumn); } public function testStartPrintsOptionInfoAndConfigurationDetailsIfConfigFilePresent(): void @@ -157,14 +157,14 @@ public function testAddSuiteAddsFunctionCountToTotalTestCases(): void new TestMethod('funcTwo', [], false), ], false); $this->printer->addTest($suite); - static::assertEquals(2, $this->printer->getTotalCases()); + static::assertSame(2, $this->printer->getTotalCases()); } public function testAddTestMethodIncrementsCountByOne(): void { $method = new TestMethod('/path', ['testThisMethod'], false); $this->printer->addTest($method); - static::assertEquals(1, $this->printer->getTotalCases()); + static::assertSame(1, $this->printer->getTotalCases()); } public function testGetHeader(): void @@ -198,7 +198,7 @@ public function testGetErrorsSingleError(): void $eq .= "Exception: Error!!!\n\n"; $eq .= "/home/brian/Projects/parallel-phpunit/test/fixtures/tests/UnitTestWithErrorTest.php:12\n"; - static::assertEquals($eq, $errors); + static::assertSame($eq, $errors); } public function testGetErrorsMultipleErrors(): void @@ -218,7 +218,7 @@ public function testGetErrorsMultipleErrors(): void $eq .= "Exception: Another Error!!!\n\n"; $eq .= "/home/brian/Projects/parallel-phpunit/test/fixtures/tests/UnitTestWithOtherErrorTest.php:12\n"; - static::assertEquals($eq, $errors); + static::assertSame($eq, $errors); } public function testGetFailures(): void @@ -229,15 +229,18 @@ public function testGetFailures(): void $failures = $this->printer->getFailures(); - $eq = "There were 2 failures:\n\n"; - $eq .= "1) UnitTestWithClassAnnotationTest::testFalsehood\n"; + $eq = "There were 3 failures:\n\n"; + $eq .= "1) Fixtures\\Tests\\UnitTestWithClassAnnotationTest::testFalsehood\n"; $eq .= "Failed asserting that true is false.\n\n"; - $eq .= "/home/brian/Projects/parallel-phpunit/test/fixtures/tests/UnitTestWithClassAnnotationTest.php:20\n"; - $eq .= "\n2) UnitTestWithMethodAnnotationsTest::testFalsehood\n"; + $eq .= "/home/brian/Projects/parallel-phpunit/test/fixtures/failing-tests/UnitTestWithClassAnnotationTest.php:32\n"; + $eq .= "\n2) UnitTestWithErrorTest::testFalsehood\n"; $eq .= "Failed asserting that true is false.\n\n"; - $eq .= "/home/brian/Projects/parallel-phpunit/test/fixtures/tests/UnitTestWithMethodAnnotationsTest.php:18\n"; + $eq .= "/home/brian/Projects/parallel-phpunit/test/fixtures/failing-tests/UnitTestWithMethodAnnotationsTest.php:20\n"; + $eq .= "\n3) UnitTestWithMethodAnnotationsTest::testFalsehood\n"; + $eq .= "Failed asserting that true is false.\n\n"; + $eq .= "/home/brian/Projects/parallel-phpunit/test/fixtures/failing-tests/UnitTestWithMethodAnnotationsTest.php:20\n"; - static::assertEquals($eq, $failures); + static::assertSame($eq, $failures); } public function testGetFooterWithFailures(): void @@ -250,9 +253,9 @@ public function testGetFooterWithFailures(): void $footer = $this->printer->getFooter(); $eq = "\nFAILURES!\n"; - $eq .= "Tests: 8, Assertions: 6, Failures: 2, Errors: 2.\n"; + $eq .= "Tests: 20, Assertions: 10, Failures: 3, Errors: 4.\n"; - static::assertEquals($eq, $footer); + static::assertSame($eq, $footer); } public function testGetFooterWithSuccess(): void @@ -265,7 +268,7 @@ public function testGetFooterWithSuccess(): void $eq = "OK (3 tests, 3 assertions)\n"; - static::assertEquals($eq, $footer); + static::assertSame($eq, $footer); } public function testPrintFeedbackForMixed(): void @@ -273,7 +276,7 @@ public function testPrintFeedbackForMixed(): void $this->printer->addTest($this->mixedSuite); $this->printer->printFeedback($this->mixedSuite); $contents = $this->output->fetch(); - static::assertEquals('.F.E.F.', $contents); + static::assertSame('.F..E.F.WSSE.F.WSSE', $contents); } public function testPrintFeedbackForMoreThan100Suites(): void @@ -310,7 +313,7 @@ public function testPrintFeedbackForMoreThan100Suites(): void $expected .= '.'; } - static::assertEquals($expected, $feedback); + static::assertSame($expected, $feedback); } public function testResultPrinterAdjustsTotalCountForDataProviders(): void @@ -347,7 +350,7 @@ public function testResultPrinterAdjustsTotalCountForDataProviders(): void $expected .= '.'; } - static::assertEquals($expected, $feedback); + static::assertSame($expected, $feedback); } private function getStartOutput(): string diff --git a/test/fixtures/failing-tests/UnitTestWithMethodAnnotationsTest.php b/test/fixtures/failing-tests/UnitTestWithMethodAnnotationsTest.php index f7441318..fd0bee74 100644 --- a/test/fixtures/failing-tests/UnitTestWithMethodAnnotationsTest.php +++ b/test/fixtures/failing-tests/UnitTestWithMethodAnnotationsTest.php @@ -28,4 +28,36 @@ public function testArrayLength(): void $elems = [1, 2, 3, 4, 5]; $this->assertEquals(5, sizeof($elems)); } + + /** + * @group fixtures + */ + public function testWarning(): void + { + $this->addWarning(uniqid()); + } + + /** + * @group fixtures + */ + public function testSkipped(): void + { + $this->markTestSkipped(); + } + + /** + * @group fixtures + */ + public function testIncomplete(): void + { + $this->markTestIncomplete(); + } + + /** + * @group fixtures + */ + public function testRisky(): void + { + $this->markAsRisky(); + } } diff --git a/test/fixtures/results/data-provider-with-special-chars.xml b/test/fixtures/results/data-provider-with-special-chars.xml index c8cf6178..97e0e82c 100644 --- a/test/fixtures/results/data-provider-with-special-chars.xml +++ b/test/fixtures/results/data-provider-with-special-chars.xml @@ -1,7 +1,7 @@ - - + + UnitTestWithDataProviderSpecialCharsTest::testIsItFalse with data set #0 ('—') Failed asserting that '—' is false. diff --git a/test/fixtures/results/mixed-results.xml b/test/fixtures/results/mixed-results.xml index 6665ce51..a1c368eb 100644 --- a/test/fixtures/results/mixed-results.xml +++ b/test/fixtures/results/mixed-results.xml @@ -1,41 +1,75 @@ - - - - - UnitTestWithClassAnnotationTest::testFalsehood + + + + + Fixtures\Tests\UnitTestWithClassAnnotationTest::testFalsehood Failed asserting that true is false. -/home/brian/Projects/parallel-phpunit/test/fixtures/tests/UnitTestWithClassAnnotationTest.php:20 +/home/brian/Projects/parallel-phpunit/test/fixtures/failing-tests/UnitTestWithClassAnnotationTest.php:32 - + + - - + + UnitTestWithErrorTest::testTruth Exception: Error!!! -/home/brian/Projects/parallel-phpunit/test/fixtures/tests/UnitTestWithErrorTest.php:12 +/home/brian/Projects/parallel-phpunit/test/fixtures/failing-tests/UnitTestWithErrorTest.php:17 + + + + + UnitTestWithErrorTest::testFalsehood +Failed asserting that true is false. + +/home/brian/Projects/parallel-phpunit/test/fixtures/failing-tests/UnitTestWithMethodAnnotationsTest.php:20 + + + + + UnitTestWithErrorTest::testWarning +Function 1 deprecated + + + + + + + + + + Risky Test - - - + + + UnitTestWithMethodAnnotationsTest::testFalsehood Failed asserting that true is false. -/home/brian/Projects/parallel-phpunit/test/fixtures/tests/UnitTestWithMethodAnnotationsTest.php:18 +/home/brian/Projects/parallel-phpunit/test/fixtures/failing-tests/UnitTestWithMethodAnnotationsTest.php:20 - - - WARNING + + + UnitTestWithMethodAnnotationsTest::testWarning +Function 2 deprecated + - - SKIPPED + + + + + + + + Risky Test + diff --git a/test/fixtures/results/single-passing.xml b/test/fixtures/results/single-passing.xml index a1faf7ad..8c6446be 100644 --- a/test/fixtures/results/single-passing.xml +++ b/test/fixtures/results/single-passing.xml @@ -1,8 +1,8 @@ - - - - + + + + From b4c49650fd1fb5fe6a38843b6635a20ee47d81eb Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Tue, 18 Aug 2020 09:37:35 +0200 Subject: [PATCH 06/19] --tmp-dir option; LogInterpreter ok --- composer.json | 1 + src/Logging/JUnit/Reader.php | 2 +- src/Logging/LogInterpreter.php | 22 ++--------- src/Logging/MetaProvider.php | 2 +- src/Runners/PHPUnit/BaseWrapperRunner.php | 1 - src/Runners/PHPUnit/ExecutableTest.php | 10 +++-- src/Runners/PHPUnit/FullSuite.php | 4 +- src/Runners/PHPUnit/Options.php | 18 +++++++++ src/Runners/PHPUnit/Runner.php | 1 - src/Runners/PHPUnit/SqliteRunner.php | 3 +- src/Runners/PHPUnit/Suite.php | 4 +- src/Runners/PHPUnit/SuiteLoader.php | 36 +++++++++--------- src/Runners/PHPUnit/TestMethod.php | 4 +- .../Runners/PHPUnit/RunnerIntegrationTest.php | 15 ++++---- .../Functional/Runners/PHPUnit/WorkerTest.php | 15 ++++---- test/Functional/WrapperRunnerTest.php | 3 +- test/Unit/Logging/JUnit/ReaderTest.php | 4 +- test/Unit/Logging/LogInterpreterTest.php | 38 ++++++++----------- test/Unit/ResultTester.php | 4 +- .../Runners/PHPUnit/ExecutableTestTest.php | 2 +- test/Unit/Runners/PHPUnit/OptionsTest.php | 4 ++ .../Runners/PHPUnit/ResultPrinterTest.php | 16 ++++---- test/Unit/Runners/PHPUnit/SuiteLoaderTest.php | 20 ++++------ test/Unit/Runners/PHPUnit/TestMethodTest.php | 2 +- test/fixtures/parallel-suite/ParallelBase.php | 2 +- 25 files changed, 112 insertions(+), 121 deletions(-) diff --git a/composer.json b/composer.json index 41db9129..974da748 100644 --- a/composer.json +++ b/composer.json @@ -30,6 +30,7 @@ "phpunit/php-file-iterator": "^3.0", "phpunit/php-timer": "^5.0", "phpunit/phpunit": "^9.3.5", + "sebastian/environment": "^5.1", "symfony/console": "^4.4 || ^5.1", "symfony/process": "^4.4 || ^5.1" }, diff --git a/src/Logging/JUnit/Reader.php b/src/Logging/JUnit/Reader.php index d659bc1b..2982d488 100644 --- a/src/Logging/JUnit/Reader.php +++ b/src/Logging/JUnit/Reader.php @@ -255,7 +255,7 @@ public function getTotalErrors(): int return $this->suites[0]->errors; } - public function getTotalWarning(): int + public function getTotalWarnings(): int { return $this->suites[0]->warnings; } diff --git a/src/Logging/LogInterpreter.php b/src/Logging/LogInterpreter.php index 54580932..a2d2585f 100644 --- a/src/Logging/LogInterpreter.php +++ b/src/Logging/LogInterpreter.php @@ -12,7 +12,6 @@ use function array_reduce; use function array_values; use function count; -use function reset; final class LogInterpreter implements MetaProvider { @@ -22,28 +21,15 @@ final class LogInterpreter implements MetaProvider * * @var Reader[] */ - protected $readers = []; - - /** - * Reset the array pointer of the internal - * readers collection. - */ - public function rewind(): void - { - reset($this->readers); - } + private $readers = []; /** * Add a new Reader to be included * in the final results. - * - * @return $this */ - public function addReader(Reader $reader): self + public function addReader(Reader $reader): void { $this->readers[] = $reader; - - return $this; } /** @@ -168,10 +154,10 @@ public function getTotalErrors(): int }, 0); } - public function getTotalWarning(): int + public function getTotalWarnings(): int { return array_reduce($this->readers, static function (int $result, Reader $reader): int { - return $result + $reader->getTotalWarning(); + return $result + $reader->getTotalWarnings(); }, 0); } diff --git a/src/Logging/MetaProvider.php b/src/Logging/MetaProvider.php index 687d48f3..5c10985d 100644 --- a/src/Logging/MetaProvider.php +++ b/src/Logging/MetaProvider.php @@ -14,7 +14,7 @@ public function getTotalFailures(): int; public function getTotalErrors(): int; - public function getTotalWarning(): int; + public function getTotalWarnings(): int; public function getTotalTime(): float; diff --git a/src/Runners/PHPUnit/BaseWrapperRunner.php b/src/Runners/PHPUnit/BaseWrapperRunner.php index 1767ff09..959672ae 100644 --- a/src/Runners/PHPUnit/BaseWrapperRunner.php +++ b/src/Runners/PHPUnit/BaseWrapperRunner.php @@ -32,7 +32,6 @@ final protected function complete(): void { $this->setExitCode(); $this->printer->printResults(); - $this->interpreter->rewind(); $this->log(); $this->logCoverage(); $readers = $this->interpreter->getReaders(); diff --git a/src/Runners/PHPUnit/ExecutableTest.php b/src/Runners/PHPUnit/ExecutableTest.php index 6862ad86..cb02ec66 100644 --- a/src/Runners/PHPUnit/ExecutableTest.php +++ b/src/Runners/PHPUnit/ExecutableTest.php @@ -10,7 +10,6 @@ use function array_map; use function array_merge; use function assert; -use function sys_get_temp_dir; use function tempnam; use function unlink; @@ -47,11 +46,14 @@ abstract class ExecutableTest /** @var bool */ private $needsCoverage; + /** @var string */ + private $tmpDir; - public function __construct(string $path, bool $needsCoverage) + public function __construct(string $path, bool $needsCoverage, string $tmpDir) { $this->path = $path; $this->needsCoverage = $needsCoverage; + $this->tmpDir = $tmpDir; } /** @@ -75,7 +77,7 @@ final public function getPath(): string final public function getTempFile(): string { if ($this->temp === null) { - $temp = tempnam(sys_get_temp_dir(), 'PT_'); + $temp = tempnam($this->tmpDir, 'PT_'); assert($temp !== false); $this->temp = $temp; @@ -165,7 +167,7 @@ final public function command(string $binary, array $options = [], ?array $passt final public function getCoverageFileName(): string { if ($this->coverageFileName === null) { - $coverageFileName = tempnam(sys_get_temp_dir(), 'CV_'); + $coverageFileName = tempnam($this->tmpDir, 'CV_'); assert($coverageFileName !== false); $this->coverageFileName = $coverageFileName; diff --git a/src/Runners/PHPUnit/FullSuite.php b/src/Runners/PHPUnit/FullSuite.php index e2b58da8..84ce6248 100644 --- a/src/Runners/PHPUnit/FullSuite.php +++ b/src/Runners/PHPUnit/FullSuite.php @@ -14,9 +14,9 @@ final class FullSuite extends ExecutableTest /** @var string */ protected $configPath; - public function __construct(string $suiteName, string $configPath, bool $needsCoverage) + public function __construct(string $suiteName, string $configPath, bool $needsCoverage, string $tmpDir) { - parent::__construct('', $needsCoverage); + parent::__construct('', $needsCoverage, $tmpDir); $this->suiteName = $suiteName; $this->configPath = $configPath; diff --git a/src/Runners/PHPUnit/Options.php b/src/Runners/PHPUnit/Options.php index a5a4e346..091edbc6 100644 --- a/src/Runners/PHPUnit/Options.php +++ b/src/Runners/PHPUnit/Options.php @@ -36,6 +36,7 @@ use function realpath; use function sprintf; use function strlen; +use function sys_get_temp_dir; use function unserialize; use const DIRECTORY_SEPARATOR; @@ -190,6 +191,8 @@ final class Options private $logJunit; /** @var string|null */ private $whitelist; + /** @var string */ + private $tmpDir; /** * @param array $annotations @@ -229,6 +232,7 @@ private function __construct( string $runner, bool $stopOnFailure, array $testsuite, + string $tmpDir, int $verbose, ?string $whitelist ) { @@ -260,6 +264,7 @@ private function __construct( $this->runner = $runner; $this->stopOnFailure = $stopOnFailure; $this->testsuite = $testsuite; + $this->tmpDir = $tmpDir; $this->verbose = $verbose; $this->whitelist = $whitelist; } @@ -357,6 +362,7 @@ public static function fromConsoleInput(InputInterface $input, string $cwd): sel $options['runner'], $options['stop-on-failure'], $testsuite, + $options['tmp-dir'], (int) $options['verbose'], $options['whitelist'] ); @@ -552,6 +558,13 @@ public static function setInputDefinition(InputDefinition $inputDefinition): voi InputOption::VALUE_REQUIRED, 'Filter which testsuite to run' ), + new InputOption( + 'tmp-dir', + null, + InputOption::VALUE_REQUIRED, + 'Temporary directory for internal ParaTest files', + sys_get_temp_dir() + ), new InputOption( 'verbose', 'v', @@ -876,6 +889,11 @@ public function logJunit(): ?string return $this->logJunit; } + public function tmpDir(): string + { + return $this->tmpDir; + } + public function whitelist(): ?string { return $this->whitelist; diff --git a/src/Runners/PHPUnit/Runner.php b/src/Runners/PHPUnit/Runner.php index ed7133d6..627af9ba 100644 --- a/src/Runners/PHPUnit/Runner.php +++ b/src/Runners/PHPUnit/Runner.php @@ -88,7 +88,6 @@ public function run(): void private function complete(): void { $this->printer->printResults(); - $this->interpreter->rewind(); $this->log(); $this->logCoverage(); $readers = $this->interpreter->getReaders(); diff --git a/src/Runners/PHPUnit/SqliteRunner.php b/src/Runners/PHPUnit/SqliteRunner.php index c1be2d88..cecd04e3 100644 --- a/src/Runners/PHPUnit/SqliteRunner.php +++ b/src/Runners/PHPUnit/SqliteRunner.php @@ -15,7 +15,6 @@ use function implode; use function realpath; use function serialize; -use function sys_get_temp_dir; use function tempnam; use function uniqid; use function unlink; @@ -39,7 +38,7 @@ public function __construct(Options $opts, OutputInterface $output) { parent::__construct($opts, $output); - $this->dbFileName = (string) ($opts->filtered()['database'] ?? tempnam(sys_get_temp_dir(), 'paratest_db_')); + $this->dbFileName = (string) ($opts->filtered()['database'] ?? tempnam($opts->tmpDir(), 'paratest_db_')); $this->db = new PDO('sqlite:' . $this->dbFileName); } diff --git a/src/Runners/PHPUnit/Suite.php b/src/Runners/PHPUnit/Suite.php index 3d0119d8..12ec9cf9 100644 --- a/src/Runners/PHPUnit/Suite.php +++ b/src/Runners/PHPUnit/Suite.php @@ -23,9 +23,9 @@ final class Suite extends ExecutableTest /** * @param TestMethod[] $functions */ - public function __construct(string $path, array $functions, bool $needsCoverage) + public function __construct(string $path, array $functions, bool $needsCoverage, string $tmpDir) { - parent::__construct($path, $needsCoverage); + parent::__construct($path, $needsCoverage, $tmpDir); $this->functions = $functions; } diff --git a/src/Runners/PHPUnit/SuiteLoader.php b/src/Runners/PHPUnit/SuiteLoader.php index ceb44f76..0d0168db 100644 --- a/src/Runners/PHPUnit/SuiteLoader.php +++ b/src/Runners/PHPUnit/SuiteLoader.php @@ -60,16 +60,12 @@ final class SuiteLoader */ private $configuration; - /** @var Options|null */ - public $options; + /** @var Options */ + private $options; - public function __construct(?Options $options = null) + public function __construct(Options $options) { - $this->options = $options; - if ($options === null) { - return; - } - + $this->options = $options; $this->configuration = $options->configuration(); } @@ -117,8 +113,7 @@ public function load(?string $path = null): void (new Facade())->getFilesAsArray($path, ['Test.php']) ); } elseif ( - $this->options !== null - && $this->options->parallelSuite() + $this->options->parallelSuite() && $this->configuration !== null && ! $this->configuration->testSuite()->isEmpty() ) { @@ -130,7 +125,7 @@ public function load(?string $path = null): void && ! $this->configuration->testSuite()->isEmpty() ) { $testSuiteCollection = $this->configuration->testSuite()->asArray(); - if ($this->options !== null && count($this->options->testsuite()) > 0) { + if (count($this->options->testsuite()) > 0) { $suitesName = array_map(static function (TestSuite $testSuite): string { return $testSuite->name(); }, $testSuiteCollection); @@ -202,7 +197,8 @@ private function executableTests(string $path, ParsedClass $class): array $executableTests[] = new TestMethod( $path, $methodBatch, - $this->options !== null && $this->options->hasCoverage() + $this->options->hasCoverage(), + $this->options->tmpDir() ); } @@ -219,8 +215,8 @@ private function executableTests(string $path, ParsedClass $class): array */ private function getMethodBatches(ParsedClass $class): array { - $classMethods = $class->getMethods($this->options !== null ? $this->options->annotations() : []); - $maxBatchSize = $this->options !== null && $this->options->functional() ? $this->options->maxBatchSize() : 0; + $classMethods = $class->getMethods($this->options->annotations()); + $maxBatchSize = $this->options->functional() ? $this->options->maxBatchSize() : 0; assert($maxBatchSize !== null); $batches = []; @@ -328,7 +324,7 @@ private function getMethodTests(ParsedClass $class, ParsedFunction $method): arr */ private function testMatchGroupOptions(array $groups): bool { - if ($this->options === null || count($this->options->group()) === 0) { + if (count($this->options->group()) === 0) { return true; } @@ -342,7 +338,7 @@ private function testMatchGroupOptions(array $groups): bool private function testMatchFilterOptions(string $className, string $name): bool { - if ($this->options === null || ($filter = $this->options->filter()) === null) { + if (($filter = $this->options->filter()) === null) { return true; } @@ -362,7 +358,8 @@ private function createSuite(string $path, ParsedClass $class): Suite $path, $class ), - $this->options !== null && $this->options->hasCoverage() + $this->options->hasCoverage(), + $this->options->tmpDir() ); } @@ -371,7 +368,8 @@ private function createFullSuite(string $suiteName, string $configPath): FullSui return new FullSuite( $suiteName, $configPath, - $this->options !== null && $this->options->hasCoverage() + $this->options->hasCoverage(), + $this->options->tmpDir() ); } @@ -427,7 +425,7 @@ private function loadConfiguration(): void } $bootstrap = null; - if ($this->options !== null && $this->options->bootstrap() !== null) { + if ($this->options->bootstrap() !== null) { $bootstrap = $this->options->bootstrap(); } elseif ($this->configuration !== null && $this->configuration->phpunit()->hasBootstrap()) { $bootstrap = $this->configuration->phpunit()->bootstrap(); diff --git a/src/Runners/PHPUnit/TestMethod.php b/src/Runners/PHPUnit/TestMethod.php index f88eae57..c3ff3056 100644 --- a/src/Runners/PHPUnit/TestMethod.php +++ b/src/Runners/PHPUnit/TestMethod.php @@ -35,9 +35,9 @@ final class TestMethod extends ExecutableTest * @param string $testPath path to phpunit test case file * @param string[] $filters array of filters or single filter */ - public function __construct(string $testPath, array $filters, bool $needsCoverage) + public function __construct(string $testPath, array $filters, bool $needsCoverage, string $tmpDir) { - parent::__construct($testPath, $needsCoverage); + parent::__construct($testPath, $needsCoverage, $tmpDir); // for compatibility with other code (tests), which can pass string (one filter) // instead of array of filters $this->filters = $filters; diff --git a/test/Functional/Runners/PHPUnit/RunnerIntegrationTest.php b/test/Functional/Runners/PHPUnit/RunnerIntegrationTest.php index b9883453..b78f2a8f 100644 --- a/test/Functional/Runners/PHPUnit/RunnerIntegrationTest.php +++ b/test/Functional/Runners/PHPUnit/RunnerIntegrationTest.php @@ -13,7 +13,6 @@ use function count; use function glob; use function simplexml_load_file; -use function sys_get_temp_dir; use function unlink; /** @@ -34,7 +33,7 @@ protected function setUpTest(): void { static::skipIfCodeCoverageNotEnabled(); - $testcoverageFiles = sys_get_temp_dir() . DS . 'coverage-runner-integration*'; + $testcoverageFiles = TMP_DIR . DS . 'coverage-runner-integration*'; $glob = glob($testcoverageFiles); assert($glob !== false); foreach ($glob as $file) { @@ -44,9 +43,9 @@ protected function setUpTest(): void $this->bareOptions = [ '--path' => FIXTURES . DS . 'failing-tests', '--phpunit' => PHPUNIT, - '--coverage-clover' => sys_get_temp_dir() . DS . 'coverage-runner-integration.clover', - '--coverage-crap4j' => sys_get_temp_dir() . DS . 'coverage-runner-integration.crap4j', - '--coverage-php' => sys_get_temp_dir() . DS . 'coverage-runner-integration.php', + '--coverage-clover' => TMP_DIR . DS . 'coverage-runner-integration.clover', + '--coverage-crap4j' => TMP_DIR . DS . 'coverage-runner-integration.crap4j', + '--coverage-php' => TMP_DIR . DS . 'coverage-runner-integration.php', '--bootstrap' => BOOTSTRAP, '--whitelist' => FIXTURES . DS . 'failing-tests', ]; @@ -60,7 +59,7 @@ protected function setUpTest(): void */ private function globTempDir(string $pattern): array { - $glob = glob(sys_get_temp_dir() . DS . $pattern); + $glob = glob(TMP_DIR . DS . $pattern); assert($glob !== false); return $glob; @@ -92,12 +91,12 @@ public function testRunningTestsShouldLeaveNoTempFiles(): void static::assertEquals( $countAfter, $countBefore, - "Test Runner failed to clean up the 'PT_*' file in " . sys_get_temp_dir() + "Test Runner failed to clean up the 'PT_*' file in " . TMP_DIR ); static::assertEquals( $countCoverageAfter, $countCoverageBefore, - "Test Runner failed to clean up the 'CV_*' file in " . sys_get_temp_dir() + "Test Runner failed to clean up the 'CV_*' file in " . TMP_DIR ); } diff --git a/test/Functional/Runners/PHPUnit/WorkerTest.php b/test/Functional/Runners/PHPUnit/WorkerTest.php index acab4112..7908e762 100644 --- a/test/Functional/Runners/PHPUnit/WorkerTest.php +++ b/test/Functional/Runners/PHPUnit/WorkerTest.php @@ -16,7 +16,6 @@ use function get_class; use function proc_get_status; use function proc_open; -use function sys_get_temp_dir; use function unlink; /** @@ -46,8 +45,8 @@ public function setUpTest(): void public function tearDown(): void { - $this->deleteIfExists(sys_get_temp_dir() . DS . 'test.xml'); - $this->deleteIfExists(sys_get_temp_dir() . DS . 'test2.xml'); + $this->deleteIfExists(TMP_DIR . DS . 'test.xml'); + $this->deleteIfExists(TMP_DIR . DS . 'test2.xml'); } private function deleteIfExists(string $file): void @@ -64,7 +63,7 @@ private function deleteIfExists(string $file): void */ public function testReadsAPHPUnitCommandFromStdInAndExecutesItItsOwnProcess(): void { - $testLog = sys_get_temp_dir() . DS . 'test.xml'; + $testLog = TMP_DIR . DS . 'test.xml'; $testCmd = $this->getCommand('passing-tests' . DS . 'TestOfUnits.php', $testLog); $worker = new WrapperWorker($this->output); $worker->start($this->phpunitWrapper); @@ -81,7 +80,7 @@ public function testReadsAPHPUnitCommandFromStdInAndExecutesItItsOwnProcess(): v */ public function testKnowsWhenAJobIsFinished(): void { - $testLog = sys_get_temp_dir() . DS . 'test.xml'; + $testLog = TMP_DIR . DS . 'test.xml'; $testCmd = $this->getCommand('passing-tests' . DS . 'TestOfUnits.php', $testLog); $worker = new WrapperWorker($this->output); $worker->start($this->phpunitWrapper); @@ -96,7 +95,7 @@ public function testKnowsWhenAJobIsFinished(): void */ public function testTellsWhenItsFree(): void { - $testLog = sys_get_temp_dir() . DS . 'test.xml'; + $testLog = TMP_DIR . DS . 'test.xml'; $testCmd = $this->getCommand('passing-tests' . DS . 'TestOfUnits.php', $testLog); $worker = new WrapperWorker($this->output); $worker->start($this->phpunitWrapper); @@ -177,11 +176,11 @@ public function testCanExecuteMultiplePHPUnitCommands(): void $worker = new WrapperWorker($this->output); $worker->start($this->phpunitWrapper); - $testLog = sys_get_temp_dir() . DS . 'test.xml'; + $testLog = TMP_DIR . DS . 'test.xml'; $testCmd = $this->getCommand('passing-tests' . DS . 'TestOfUnits.php', $testLog); $worker->execute($testCmd); - $testLog2 = sys_get_temp_dir() . DS . 'test2.xml'; + $testLog2 = TMP_DIR . DS . 'test2.xml'; $testCmd2 = $this->getCommand('failing-tests' . DS . 'UnitTestWithErrorTest.php', $testLog2); $worker->execute($testCmd2); diff --git a/test/Functional/WrapperRunnerTest.php b/test/Functional/WrapperRunnerTest.php index ce0a915a..2ff95168 100644 --- a/test/Functional/WrapperRunnerTest.php +++ b/test/Functional/WrapperRunnerTest.php @@ -9,7 +9,6 @@ use function glob; use function is_dir; use function mkdir; -use function sys_get_temp_dir; use function unlink; /** @@ -91,7 +90,7 @@ public function testExitCodes(): void public function testParallelSuiteOption(): void { - $testDir = sys_get_temp_dir() . DS . 'parallel-suite'; + $testDir = TMP_DIR . DS . 'parallel-suite'; if (! is_dir($testDir)) { mkdir($testDir); } diff --git a/test/Unit/Logging/JUnit/ReaderTest.php b/test/Unit/Logging/JUnit/ReaderTest.php index 8ea6523a..ad270dd3 100644 --- a/test/Unit/Logging/JUnit/ReaderTest.php +++ b/test/Unit/Logging/JUnit/ReaderTest.php @@ -232,7 +232,7 @@ public function testMixedGetTotals(): void static::assertSame(19, $this->mixed->getTotalTests()); static::assertSame(10, $this->mixed->getTotalAssertions()); static::assertSame(3, $this->mixed->getTotalFailures()); - static::assertSame(2, $this->mixed->getTotalWarning()); + static::assertSame(2, $this->mixed->getTotalWarnings()); static::assertSame(3, $this->mixed->getTotalErrors()); static::assertSame(0.001489, $this->mixed->getTotalTime()); } @@ -242,7 +242,7 @@ public function testSingleGetTotals(): void static::assertSame(3, $this->single->getTotalTests()); static::assertSame(3, $this->single->getTotalAssertions()); static::assertSame(1, $this->single->getTotalFailures()); - static::assertSame(0, $this->single->getTotalWarning()); + static::assertSame(0, $this->single->getTotalWarnings()); static::assertSame(0, $this->single->getTotalErrors()); static::assertSame(0.005895, $this->single->getTotalTime()); } diff --git a/test/Unit/Logging/LogInterpreterTest.php b/test/Unit/Logging/LogInterpreterTest.php index e4bf6c15..b0ef64e0 100644 --- a/test/Unit/Logging/LogInterpreterTest.php +++ b/test/Unit/Logging/LogInterpreterTest.php @@ -12,7 +12,7 @@ use function array_pop; /** - * @coversNothing + * @covers \ParaTest\Logging\LogInterpreter */ final class LogInterpreterTest extends ResultTester { @@ -22,9 +22,8 @@ final class LogInterpreterTest extends ResultTester protected function setUpInterpreter(): void { $this->interpreter = new LogInterpreter(); - $this->interpreter - ->addReader(new Reader($this->mixedSuite->getTempFile())) - ->addReader(new Reader($this->passingSuite->getTempFile())); + $this->interpreter->addReader(new Reader($this->mixedSuite->getTempFile())); + $this->interpreter->addReader(new Reader($this->passingSuite->getTempFile())); } public function testConstructor(): void @@ -40,12 +39,6 @@ public function testAddReaderIncrementsReaders(): void static::assertCount(3, $this->getObjectValue($this->interpreter, 'readers')); } - public function testAddReaderReturnsSelf(): void - { - $self = $this->interpreter->addReader(new Reader($this->failureSuite->getTempFile())); - static::assertSame($self, $this->interpreter); - } - public function testGetReaders(): void { $reader = new Reader($this->failureSuite->getTempFile()); @@ -56,24 +49,14 @@ public function testGetReaders(): void static::assertSame($reader, $last); } - public function testGetTotalTests(): void + public function testGetTotals(): void { static::assertSame(22, $this->interpreter->getTotalTests()); - } - - public function testGetTotalAssertions(): void - { static::assertSame(13, $this->interpreter->getTotalAssertions()); - } - - public function testGetTotalFailures(): void - { static::assertSame(3, $this->interpreter->getTotalFailures()); - } - - public function testGetTotalErrors(): void - { + static::assertSame(2, $this->interpreter->getTotalWarnings()); static::assertSame(3, $this->interpreter->getTotalErrors()); + static::assertSame(0.006784, $this->interpreter->getTotalTime()); } public function testIsSuccessfulReturnsFalseIfFailuresPresentAndNoErrors(): void @@ -113,6 +96,15 @@ public function testGetErrorsReturnsArrayOfErrorMessages(): void static::assertSame($errors, $this->interpreter->getErrors()); } + public function testGetWarningsReturnsArrayOfErrorMessages(): void + { + $errors = [ + "UnitTestWithErrorTest::testWarning\nFunction 1 deprecated", + "UnitTestWithMethodAnnotationsTest::testWarning\nFunction 2 deprecated", + ]; + static::assertSame($errors, $this->interpreter->getWarnings()); + } + public function testGetFailuresReturnsArrayOfFailureMessages(): void { $failures = [ diff --git a/test/Unit/ResultTester.php b/test/Unit/ResultTester.php index c082f23b..e2496e2f 100644 --- a/test/Unit/ResultTester.php +++ b/test/Unit/ResultTester.php @@ -42,10 +42,10 @@ final protected function getSuiteWithResult(string $result, int $methodCount): S $result = FIXTURES . DS . 'results' . DS . $result; $functions = []; for ($i = 0; $i < $methodCount; ++$i) { - $functions[] = new TestMethod((string) $i, [], false); + $functions[] = new TestMethod((string) $i, [], false, TMP_DIR); } - $suite = new Suite('', $functions, false); + $suite = new Suite('', $functions, false, TMP_DIR); $suite->setTempFile($result); return $suite; diff --git a/test/Unit/Runners/PHPUnit/ExecutableTestTest.php b/test/Unit/Runners/PHPUnit/ExecutableTestTest.php index de48cb49..a2bc5185 100644 --- a/test/Unit/Runners/PHPUnit/ExecutableTestTest.php +++ b/test/Unit/Runners/PHPUnit/ExecutableTestTest.php @@ -19,7 +19,7 @@ final class ExecutableTestTest extends TestBase public function setUpTest(): void { - $this->executableTestChild = new ExecutableTestChild('pathToFile', true); + $this->executableTestChild = new ExecutableTestChild('pathToFile', true, TMP_DIR); } public function testConstructor(): void diff --git a/test/Unit/Runners/PHPUnit/OptionsTest.php b/test/Unit/Runners/PHPUnit/OptionsTest.php index 235a44b0..cf414197 100644 --- a/test/Unit/Runners/PHPUnit/OptionsTest.php +++ b/test/Unit/Runners/PHPUnit/OptionsTest.php @@ -15,6 +15,7 @@ use function is_dir; use function mkdir; use function sort; +use function sys_get_temp_dir; use function unlink; /** @@ -223,6 +224,7 @@ public function testDefaultOptions(): void static::assertStringContainsString('Runner', $options->runner()); static::assertFalse($options->stopOnFailure()); static::assertEmpty($options->testsuite()); + static::assertSame(sys_get_temp_dir(), $options->tmpDir()); static::assertNull($options->whitelist()); } @@ -255,6 +257,7 @@ public function testProvidedOptions(): void '--runner' => 'MYRUNNER', '--stop-on-failure' => true, '--testsuite' => 'TESTSUITE', + '--tmp-dir' => TMP_DIR, '--whitelist' => 'WHITELIST', ]; @@ -286,6 +289,7 @@ public function testProvidedOptions(): void static::assertSame('MYRUNNER', $options->runner()); static::assertTrue($options->stopOnFailure()); static::assertSame(['TESTSUITE'], $options->testsuite()); + static::assertSame(TMP_DIR, $options->tmpDir()); static::assertSame('WHITELIST', $options->whitelist()); } } diff --git a/test/Unit/Runners/PHPUnit/ResultPrinterTest.php b/test/Unit/Runners/PHPUnit/ResultPrinterTest.php index e3f2236a..4d791f84 100644 --- a/test/Unit/Runners/PHPUnit/ResultPrinterTest.php +++ b/test/Unit/Runners/PHPUnit/ResultPrinterTest.php @@ -67,7 +67,7 @@ public function testConstructor(): void public function testAddTestShouldAddTest(): void { - $suite = new Suite('/path/to/ResultSuite.php', [], false); + $suite = new Suite('/path/to/ResultSuite.php', [], false, TMP_DIR); $this->printer->addTest($suite); @@ -76,7 +76,7 @@ public function testAddTestShouldAddTest(): void public function testAddTestReturnsSelf(): void { - $suite = new Suite('/path/to/ResultSuite.php', [], false); + $suite = new Suite('/path/to/ResultSuite.php', [], false, TMP_DIR); $self = $this->printer->addTest($suite); @@ -98,10 +98,10 @@ public function testStartSetsWidthAndMaxColumn(): void { $funcs = []; for ($i = 0; $i < 120; ++$i) { - $funcs[] = new TestMethod((string) $i, [], false); + $funcs[] = new TestMethod((string) $i, [], false, TMP_DIR); } - $suite = new Suite('/path', $funcs, false); + $suite = new Suite('/path', $funcs, false, TMP_DIR); $this->printer->addTest($suite); $this->getStartOutput(); $numTestsWidth = $this->getObjectValue($this->printer, 'numTestsWidth'); @@ -153,16 +153,16 @@ public function testStartPrintsOptionInfoWithSingularForOneProcess(): void public function testAddSuiteAddsFunctionCountToTotalTestCases(): void { $suite = new Suite('/path', [ - new TestMethod('funcOne', [], false), - new TestMethod('funcTwo', [], false), - ], false); + new TestMethod('funcOne', [], false, TMP_DIR), + new TestMethod('funcTwo', [], false, TMP_DIR), + ], false, TMP_DIR); $this->printer->addTest($suite); static::assertSame(2, $this->printer->getTotalCases()); } public function testAddTestMethodIncrementsCountByOne(): void { - $method = new TestMethod('/path', ['testThisMethod'], false); + $method = new TestMethod('/path', ['testThisMethod'], false, TMP_DIR); $this->printer->addTest($method); static::assertSame(1, $this->printer->getTotalCases()); } diff --git a/test/Unit/Runners/PHPUnit/SuiteLoaderTest.php b/test/Unit/Runners/PHPUnit/SuiteLoaderTest.php index ea671e1e..2ebdcb10 100644 --- a/test/Unit/Runners/PHPUnit/SuiteLoaderTest.php +++ b/test/Unit/Runners/PHPUnit/SuiteLoaderTest.php @@ -28,26 +28,22 @@ public function testConstructor(): void static::assertEquals($options, $this->getObjectValue($loader, 'options')); } - public function testOptionsCanBeNull(): void - { - $loader = new SuiteLoader(); - static::assertNull($this->getObjectValue($loader, 'options')); - } - public function testLoadThrowsExceptionWithInvalidPath(): void { + $loader = new SuiteLoader($this->createOptionsFromArgv([])); + $this->expectException(RuntimeException::class); - $loader = new SuiteLoader(); $loader->load('/path/to/nowhere'); } public function testLoadBarePathWithNoPathAndNoConfiguration(): void { + $loader = new SuiteLoader($this->createOptionsFromArgv([], __DIR__)); + $this->expectException(RuntimeException::class); $this->expectExceptionMessage('No path or configuration provided (tests must end with Test.php)'); - $loader = new SuiteLoader(); $loader->load(); } @@ -217,7 +213,7 @@ public function testLoadFileGetsPathOfFile(): void */ private function getLoadedPaths(string $path, ?SuiteLoader $loader = null): array { - $loader = $loader ?? new SuiteLoader(); + $loader = $loader ?? new SuiteLoader($this->createOptionsFromArgv([])); $loader->load($path); $loaded = $this->getObjectValue($loader, 'loadedSuites'); @@ -239,7 +235,7 @@ public function testLoadDirGetsPathOfAllTestsWithKeys(): array $fixturePath = $this->fixture('passing-tests'); $files = $this->findTests($fixturePath); - $loader = new SuiteLoader(); + $loader = new SuiteLoader($this->createOptionsFromArgv([])); $loader->load($fixturePath); $loaded = $this->getObjectValue($loader, 'loadedSuites'); foreach ($loaded as $path => $test) { @@ -329,7 +325,7 @@ public function testGetSuitesForNonMatchingGroups(): void public function testLoadIgnoresFilesWithoutClasses(): void { - $loader = new SuiteLoader(); + $loader = new SuiteLoader($this->createOptionsFromArgv([])); $fileWithoutClass = $this->fixture('special-classes/FileWithoutClass.php'); $loader->load($fileWithoutClass); static::assertCount(0, $loader->getTestMethods()); @@ -338,7 +334,7 @@ public function testLoadIgnoresFilesWithoutClasses(): void public function testExecutableTestsForFunctionalModeUse(): void { $path = $this->fixture('passing-tests/DependsOnChain.php'); - $loader = new SuiteLoader(); + $loader = new SuiteLoader($this->createOptionsFromArgv([])); $loader->load($path); $tests = $loader->getTestMethods(); static::assertCount(2, $tests); diff --git a/test/Unit/Runners/PHPUnit/TestMethodTest.php b/test/Unit/Runners/PHPUnit/TestMethodTest.php index 5dab2765..baae5e02 100644 --- a/test/Unit/Runners/PHPUnit/TestMethodTest.php +++ b/test/Unit/Runners/PHPUnit/TestMethodTest.php @@ -14,7 +14,7 @@ final class TestMethodTest extends TestBase { public function testConstructor(): void { - $testMethod = new TestMethod('pathToFile', ['methodName'], false); + $testMethod = new TestMethod('pathToFile', ['methodName'], false, TMP_DIR); static::assertEquals('pathToFile', $this->getObjectValue($testMethod, 'path')); } } diff --git a/test/fixtures/parallel-suite/ParallelBase.php b/test/fixtures/parallel-suite/ParallelBase.php index fce110b9..288b8143 100644 --- a/test/fixtures/parallel-suite/ParallelBase.php +++ b/test/fixtures/parallel-suite/ParallelBase.php @@ -14,7 +14,7 @@ abstract class ParallelBase extends TestCase final public function testToken(): void { $refClass = new ReflectionClass(static::class); - $file = sys_get_temp_dir() . DS . 'parallel-suite' . DS . 'token_' . str_replace(['\\', '/'], '_', $refClass->getNamespaceName()); + $file = TMP_DIR . DS . 'parallel-suite' . DS . 'token_' . str_replace(['\\', '/'], '_', $refClass->getNamespaceName()); $token = getenv('TEST_TOKEN'); static::assertIsString($token); From e7704e9fe1dfede0bd0289178bcca6bdc292e44f Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Tue, 18 Aug 2020 10:29:19 +0200 Subject: [PATCH 07/19] Parser ok --- src/Coverage/CoverageMerger.php | 2 + src/Parser/ParsedClass.php | 30 ++++--- src/Parser/ParsedObject.php | 4 +- src/Parser/Parser.php | 20 +---- src/Runners/PHPUnit/ExecutableTest.php | 11 ++- src/Runners/PHPUnit/Options.php | 60 ++------------ src/Runners/PHPUnit/SqliteRunner.php | 5 +- src/Runners/PHPUnit/SuiteLoader.php | 2 +- src/Runners/PHPUnit/TestMethod.php | 6 +- src/Runners/PHPUnit/Worker/RunnerWorker.php | 16 ++-- src/Runners/PHPUnit/Worker/WrapperWorker.php | 2 +- test/Unit/Parser/GetClassTest.php | 77 ------------------ test/Unit/Parser/ParsedClassTest.php | 31 +++++--- test/Unit/Parser/ParsedObjectTest.php | 16 +++- test/Unit/Parser/ParserTest.php | 79 +++++++++++++++++-- test/Unit/Runners/PHPUnit/OptionsTest.php | 13 --- .../SomeNamespace/ParserTestClass.php | 8 +- 17 files changed, 167 insertions(+), 215 deletions(-) delete mode 100644 test/Unit/Parser/GetClassTest.php diff --git a/src/Coverage/CoverageMerger.php b/src/Coverage/CoverageMerger.php index 24b93870..26d695bb 100644 --- a/src/Coverage/CoverageMerger.php +++ b/src/Coverage/CoverageMerger.php @@ -50,7 +50,9 @@ public function addCoverageFromFile(string $coverageFile): void if (! is_file($coverageFile) || filesize($coverageFile) === 0) { $extra = 'This means a PHPUnit process has crashed.'; if (! (new Runtime())->canCollectCodeCoverage()) { + // @codeCoverageIgnoreStart $extra = 'No coverage driver found! Enable one of Xdebug, PHPDBG or PCOV for coverage.'; + // @codeCoverageIgnoreEnd } throw new RuntimeException("Coverage file {$coverageFile} is empty. " . $extra); diff --git a/src/Parser/ParsedClass.php b/src/Parser/ParsedClass.php index a8b0f284..76985c92 100644 --- a/src/Parser/ParsedClass.php +++ b/src/Parser/ParsedClass.php @@ -6,7 +6,6 @@ use function array_filter; use function count; -use function explode; /** * @method class-string getName() @@ -30,7 +29,7 @@ final class ParsedClass extends ParsedObject /** * @param ParsedFunction[] $methods */ - public function __construct(string $doc, string $name, string $namespace, array $methods = []) + public function __construct(string $doc, string $name, string $namespace, array $methods) { parent::__construct($doc, $name); $this->namespace = $namespace; @@ -42,25 +41,32 @@ public function __construct(string $doc, string $name, string $namespace, array * optionally filtering on annotations present * on a method. * - * @param array $annotations + * @param string[] $groups * * @return ParsedFunction[] */ - public function getMethods(array $annotations = []): array + public function getMethods(array $groups): array { - $methods = array_filter($this->methods, static function (ParsedFunction $method) use ($annotations): bool { - foreach ($annotations as $a => $v) { - foreach (explode(',', $v) as $subValue) { - if ($method->hasAnnotation($a, $subValue)) { - return true; - } + if (count($groups) === 0) { + return $this->methods; + } + + $groupAnnotation = 'group'; + foreach ($groups as $group) { + if ($this->hasAnnotation($groupAnnotation, $group)) { + return $this->methods; + } + } + + return array_filter($this->methods, static function (ParsedFunction $method) use ($groupAnnotation, $groups): bool { + foreach ($groups as $group) { + if ($method->hasAnnotation($groupAnnotation, $group)) { + return true; } } return false; }); - - return count($methods) > 0 ? $methods : $this->methods; } /** diff --git a/src/Parser/ParsedObject.php b/src/Parser/ParsedObject.php index bac1df8f..63e201bc 100644 --- a/src/Parser/ParsedObject.php +++ b/src/Parser/ParsedObject.php @@ -10,10 +10,10 @@ abstract class ParsedObject { /** @var string */ - protected $docBlock; + protected $name; /** @var string */ - protected $name; + private $docBlock; public function __construct(string $doc, string $name) { diff --git a/src/Parser/Parser.php b/src/Parser/Parser.php index 2e433860..365c7511 100644 --- a/src/Parser/Parser.php +++ b/src/Parser/Parser.php @@ -7,7 +7,6 @@ use InvalidArgumentException; use PHPUnit\Framework\TestCase; use ReflectionClass; -use ReflectionException; use ReflectionMethod; use function array_diff; @@ -67,15 +66,7 @@ public function __construct(string $srcPath) throw new NoClassInFileException(); } - try { - $this->refl = new ReflectionClass($class); - } catch (ReflectionException $reflectionException) { - throw new InvalidArgumentException( - 'Unable to instantiate ReflectionClass. ' . $class . ' not found in: ' . $srcPath, - 0, - $reflectionException - ); - } + $this->refl = new ReflectionClass($class); } /** @@ -140,16 +131,11 @@ private function getClassName(string $filename, array $previousDeclaredClasses): $newClasses = array_values(array_diff($classes, $previousDeclaredClasses)); $className = $this->searchForUnitTestClass($newClasses, $filename); - if (isset($className)) { - return $className; - } - - $className = $this->searchForUnitTestClass($classes, $filename); - if (isset($className)) { + if ($className !== null) { return $className; } - return null; + return $this->searchForUnitTestClass($classes, $filename); } /** diff --git a/src/Runners/PHPUnit/ExecutableTest.php b/src/Runners/PHPUnit/ExecutableTest.php index cb02ec66..40be3383 100644 --- a/src/Runners/PHPUnit/ExecutableTest.php +++ b/src/Runners/PHPUnit/ExecutableTest.php @@ -4,7 +4,6 @@ namespace ParaTest\Runners\PHPUnit; -use PHPUnit\TextUI\XmlConfiguration\Configuration; use Symfony\Component\Process\Process; use function array_map; @@ -114,9 +113,9 @@ final public function setLastCommand(string $command): void /** * Generate command line arguments with passed options suitable to handle through paratest. * - * @param string $binary executable binary name - * @param array $options command line options - * @param string[]|null $passthru + * @param string $binary executable binary name + * @param array $options command line options + * @param string[]|null $passthru * * @return string[] command line arguments */ @@ -187,9 +186,9 @@ final public function setTempFile(string $temp): void /** * A template method that can be overridden to add necessary options for a test. * - * @param array $options + * @param array $options * - * @return array + * @return array */ abstract protected function prepareOptions(array $options): array; } diff --git a/src/Runners/PHPUnit/Options.php b/src/Runners/PHPUnit/Options.php index 091edbc6..e3846420 100644 --- a/src/Runners/PHPUnit/Options.php +++ b/src/Runners/PHPUnit/Options.php @@ -24,7 +24,6 @@ use function file_exists; use function file_get_contents; use function implode; -use function in_array; use function intdiv; use function is_dir; use function is_file; @@ -96,7 +95,7 @@ final class Options * A collection of post-processed option values. This is the collection * containing ParaTest specific options. * - * @var array + * @var array */ private $filtered; @@ -128,14 +127,6 @@ final class Options /** @var string[] */ private $excludeGroup; - /** - * A collection of option values directly corresponding - * to certain annotations - i.e group. - * - * @var array - */ - private $annotations = []; - /** * Running the suite defined in the config in parallel. * @@ -195,16 +186,14 @@ final class Options private $tmpDir; /** - * @param array $annotations - * @param array $filtered - * @param string[] $testsuite - * @param string[] $group - * @param string[] $excludeGroup - * @param string[]|null $passthru - * @param string[]|null $passthruPhp + * @param array $filtered + * @param string[] $testsuite + * @param string[] $group + * @param string[] $excludeGroup + * @param string[]|null $passthru + * @param string[]|null $passthruPhp */ private function __construct( - array $annotations, ?string $bootstrap, bool $colors, ?Configuration $configuration, @@ -236,7 +225,6 @@ private function __construct( int $verbose, ?string $whitelist ) { - $this->annotations = $annotations; $this->bootstrap = $bootstrap; $this->colors = $colors; $this->configuration = $configuration; @@ -331,10 +319,7 @@ public static function fromConsoleInput(InputInterface $input, string $cwd): sel $filtered['exclude-group'] = implode(',', $excludeGroup); } - $annotations = self::initAnnotations($filtered); - return new self( - $annotations, $options['bootstrap'], $options['colors'], $configuration, @@ -653,29 +638,6 @@ private static function isAbsolutePath(string $path): bool return $path[0] === DIRECTORY_SEPARATOR || preg_match('~\A[A-Z]:(?![^/\\\\])~i', $path) > 0; } - /** - * Load options that are represented by annotations - * inside of tests i.e @group group1 = --group group1. - * - * @param array $filtered - * - * @return array - */ - private static function initAnnotations(array $filtered): array - { - $annotations = []; - $annotatedOptions = ['group']; - foreach ($filtered as $key => $value) { - if (! in_array($key, $annotatedOptions, true)) { - continue; - } - - $annotations[$key] = $value; - } - - return $annotations; - } - /** * Return number of (logical) CPU cores, use 2 as fallback. * @@ -767,7 +729,7 @@ public function stopOnFailure(): bool return $this->stopOnFailure; } - /** @return array */ + /** @return array */ public function filtered(): array { return $this->filtered; @@ -816,12 +778,6 @@ public function excludeGroup(): array return $this->excludeGroup; } - /** @return array */ - public function annotations(): array - { - return $this->annotations; - } - public function parallelSuite(): bool { return $this->parallelSuite; diff --git a/src/Runners/PHPUnit/SqliteRunner.php b/src/Runners/PHPUnit/SqliteRunner.php index cecd04e3..3ab70fe0 100644 --- a/src/Runners/PHPUnit/SqliteRunner.php +++ b/src/Runners/PHPUnit/SqliteRunner.php @@ -38,7 +38,10 @@ public function __construct(Options $opts, OutputInterface $output) { parent::__construct($opts, $output); - $this->dbFileName = (string) ($opts->filtered()['database'] ?? tempnam($opts->tmpDir(), 'paratest_db_')); + $dbFileName = tempnam($opts->tmpDir(), 'paratest_db_'); + assert($dbFileName !== false); + + $this->dbFileName = $dbFileName; $this->db = new PDO('sqlite:' . $this->dbFileName); } diff --git a/src/Runners/PHPUnit/SuiteLoader.php b/src/Runners/PHPUnit/SuiteLoader.php index 0d0168db..30c5983a 100644 --- a/src/Runners/PHPUnit/SuiteLoader.php +++ b/src/Runners/PHPUnit/SuiteLoader.php @@ -215,7 +215,7 @@ private function executableTests(string $path, ParsedClass $class): array */ private function getMethodBatches(ParsedClass $class): array { - $classMethods = $class->getMethods($this->options->annotations()); + $classMethods = $class->getMethods($this->options->group()); $maxBatchSize = $this->options->functional() ? $this->options->maxBatchSize() : 0; assert($maxBatchSize !== null); diff --git a/src/Runners/PHPUnit/TestMethod.php b/src/Runners/PHPUnit/TestMethod.php index c3ff3056..ad0042e8 100644 --- a/src/Runners/PHPUnit/TestMethod.php +++ b/src/Runners/PHPUnit/TestMethod.php @@ -4,8 +4,6 @@ namespace ParaTest\Runners\PHPUnit; -use PHPUnit\TextUI\XmlConfiguration\Configuration; - use function array_reduce; use function count; use function implode; @@ -69,9 +67,9 @@ public function getName(): string * This sets up the --filter switch used to run a single PHPUnit test method. * This method also provide escaping for method name to be used as filter regexp. * - * @param array $options + * @param array $options * - * @return array + * @return array */ protected function prepareOptions(array $options): array { diff --git a/src/Runners/PHPUnit/Worker/RunnerWorker.php b/src/Runners/PHPUnit/Worker/RunnerWorker.php index da10ad8a..87682274 100644 --- a/src/Runners/PHPUnit/Worker/RunnerWorker.php +++ b/src/Runners/PHPUnit/Worker/RunnerWorker.php @@ -76,10 +76,10 @@ public function getExitCode(): ?int /** * Executes the test by creating a separate process. * - * @param array $options - * @param array $environmentVariables - * @param string[]|null $passthru - * @param string[]|null $passthruPhp + * @param array $options + * @param array $environmentVariables + * @param string[]|null $passthru + * @param string[]|null $passthruPhp * * @return $this */ @@ -106,10 +106,10 @@ public function run( * Build the full executable as we would do on the command line, e.g. * php -d zend_extension=xdebug.so vendor/bin/phpunit --teststuite suite1 --prepend xdebug-filter.php. * - * @param array $options - * @param array $environmentVariables - * @param string[]|null $passthru - * @param string[]|null $passthruPhp + * @param array $options + * @param array $environmentVariables + * @param string[]|null $passthru + * @param string[]|null $passthruPhp */ private function getProcess( string $binary, diff --git a/src/Runners/PHPUnit/Worker/WrapperWorker.php b/src/Runners/PHPUnit/Worker/WrapperWorker.php index ad037e02..d75577ec 100644 --- a/src/Runners/PHPUnit/Worker/WrapperWorker.php +++ b/src/Runners/PHPUnit/Worker/WrapperWorker.php @@ -50,7 +50,7 @@ public function execute(array $testCmdArguments): void } /** - * @param array $phpunitOptions + * @param array $phpunitOptions */ public function assign(ExecutableTest $test, string $phpunit, array $phpunitOptions, Options $options): void { diff --git a/test/Unit/Parser/GetClassTest.php b/test/Unit/Parser/GetClassTest.php deleted file mode 100644 index bf3f5127..00000000 --- a/test/Unit/Parser/GetClassTest.php +++ /dev/null @@ -1,77 +0,0 @@ -fixture('passing-tests/PreviouslyLoadedTest.php'); - require_once $testFile; - - $class = $this->parseFile($testFile); - static::assertEquals('PreviouslyLoadedTest', $class->getName()); - } - - public function testParsedClassHasName(): void - { - $class = $this->parseFile($this->fixture('failing-tests/UnitTestWithClassAnnotationTest.php')); - static::assertEquals('Fixtures\\Tests\\UnitTestWithClassAnnotationTest', $class->getName()); - } - - public function testParsedAnonymousClassNameHasNoNullByte(): void - { - $class = $this->parseFile($this->fixture('failing-tests/AnonymousClass.inc')); - static::assertStringNotContainsString("\x00", $class->getName()); - } - - public function testParsedClassHasDocBlock(): void - { - $class = $this->parseFile($this->fixture('failing-tests/UnitTestWithClassAnnotationTest.php')); - static::assertEquals('/** - * @runParallel - * @pizzaBox - */', $class->getDocBlock()); - } - - public function testParsedClassHasNamespace(): void - { - $class = $this->parseFile($this->fixture('failing-tests/UnitTestWithClassAnnotationTest.php')); - static::assertEquals('Fixtures\\Tests', $class->getNamespace()); - } - - public function testParsedClassHasCorrectNumberOfTestMethods(): void - { - $class = $this->parseFile($this->fixture('failing-tests/UnitTestWithClassAnnotationTest.php')); - static::assertCount(4, $class->getMethods()); - } - - public function testParsedClassWithParentHasCorrectNumberOfTestMethods(): void - { - $class = $this->parseFile($this->fixture('failing-tests/UnitTestWithErrorTest.php')); - static::assertCount(8, $class->getMethods()); - } - - /** - * Parses a test case and returns the test class. - * - * @param mixed $path - */ - private function parseFile($path): ParsedClass - { - $parser = new Parser($path); - $parserClass = $parser->getClass(); - static::assertNotNull($parserClass); - - return $parserClass; - } -} diff --git a/test/Unit/Parser/ParsedClassTest.php b/test/Unit/Parser/ParsedClassTest.php index 087071a3..bfaa6c99 100644 --- a/test/Unit/Parser/ParsedClassTest.php +++ b/test/Unit/Parser/ParsedClassTest.php @@ -9,14 +9,14 @@ use ParaTest\Tests\TestBase; /** - * @coversNothing + * @covers \ParaTest\Parser\ParsedClass */ final class ParsedClassTest extends TestBase { /** @var ParsedClass */ - protected $class; + private $class; /** @var ParsedFunction[] */ - protected $methods; + private $methods; public function setUpTest(): void { @@ -35,12 +35,17 @@ public function setUpTest(): void ), new ParsedFunction('', 'testFunction3'), ]; - $this->class = new ParsedClass('', 'MyTestClass', '', $this->methods); + $this->class = new ParsedClass('', 'MyTestClass', 'MyNamespace', $this->methods); + } + + public function testGetNamespace(): void + { + static::assertSame('MyNamespace', $this->class->getNamespace()); } public function testGetMethodsReturnsMethods(): void { - static::assertEquals($this->methods, $this->class->getMethods()); + static::assertSame($this->methods, $this->class->getMethods([])); } public function testGetMethodsMultipleAnnotationsReturnsMethods(): void @@ -64,14 +69,22 @@ public function testGetMethodsMultipleAnnotationsReturnsMethods(): void 'testFunction2' ); $annotatedClass = new ParsedClass('', 'MyTestClass', '', [$goodMethod, $goodMethod2, $badMethod]); - $methods = $annotatedClass->getMethods(['group' => 'group1,group2']); - static::assertEquals([$goodMethod, $goodMethod2], $methods); + $methods = $annotatedClass->getMethods(['group1', 'group2']); + static::assertSame([$goodMethod, $goodMethod2], $methods); } public function testGetMethodsExceptsAdditionalAnnotationFilter(): void { - $group1 = $this->class->getMethods(['group' => 'group1']); + $group1 = $this->class->getMethods(['group1']); static::assertCount(1, $group1); - static::assertEquals($this->methods[0], $group1[0]); + static::assertSame($this->methods[0], $group1[0]); + } + + public function testGetAllClassMethodsIfClassBelongsToGroup(): void + { + $class = new ParsedClass('/** @group group9 */', 'MyTestClass', 'MyNamespace', $this->methods); + $group1 = $class->getMethods(['group9']); + static::assertCount(3, $group1); + static::assertSame($this->methods[0], $group1[0]); } } diff --git a/test/Unit/Parser/ParsedObjectTest.php b/test/Unit/Parser/ParsedObjectTest.php index 4ad92c43..7b6b340a 100644 --- a/test/Unit/Parser/ParsedObjectTest.php +++ b/test/Unit/Parser/ParsedObjectTest.php @@ -8,16 +8,26 @@ use ParaTest\Tests\TestBase; /** - * @coversNothing + * @covers \ParaTest\Parser\ParsedObject + * @covers \ParaTest\Parser\ParsedFunction */ final class ParsedObjectTest extends TestBase { /** @var ParsedClass */ - protected $parsedClass; + private $parsedClass; + /** @var string */ + private $docBlock; public function setUpTest(): void { - $this->parsedClass = new ParsedClass("/**\n * @test\n @group group1\n*\\/", 'MyClass', 'My\\Name\\Space'); + $this->docBlock = "/**\n * @test\n @group group1\n*\\/"; + $this->parsedClass = new ParsedClass($this->docBlock, self::class, 'My\\Name\\Space', []); + } + + public function testGetters(): void + { + static::assertSame(self::class, $this->parsedClass->getName()); + static::assertSame($this->docBlock, $this->parsedClass->getDocBlock()); } public function testHasAnnotationReturnsTrueWhenAnnotationPresent(): void diff --git a/test/Unit/Parser/ParserTest.php b/test/Unit/Parser/ParserTest.php index a6875c58..f194f9af 100644 --- a/test/Unit/Parser/ParserTest.php +++ b/test/Unit/Parser/ParserTest.php @@ -6,11 +6,14 @@ use InvalidArgumentException; use ParaTest\Parser\NoClassInFileException; +use ParaTest\Parser\ParsedClass; use ParaTest\Parser\Parser; use ParaTest\Tests\TestBase; +use function uniqid; + /** - * @coversNothing + * @covers \ParaTest\Parser\Parser */ final class ParserTest extends TestBase { @@ -18,24 +21,29 @@ public function testConstructorThrowsExceptionIfFileNotFound(): void { $this->expectException(InvalidArgumentException::class); - new Parser('/path/to/nowhere'); + new Parser(uniqid('/path/to/nowhere')); } public function testConstructorThrowsExceptionIfClassNotFoundInFile(): void { $fileWithoutAClass = FIXTURES . DS . 'fileWithoutClasses.php'; + $this->expectException(NoClassInFileException::class); new Parser($fileWithoutAClass); } + public function testExcludeAbstractClasses(): void + { + $parser = new Parser($this->fixture('warning-tests' . DS . 'AbstractTest.php')); + + static::assertNull($parser->getClass()); + } + public function testPrefersClassByFileName(): void { - $filename = FIXTURES . DS . 'special-classes' . DS . 'SomeNamespace' . DS . 'ParserTestClass.php'; - $parser = new Parser($filename); - $parserClass = $parser->getClass(); - static::assertNotNull($parserClass); - static::assertEquals('SomeNamespace\\ParserTestClass', $parserClass->getName()); + $class = $this->parseFile($this->fixture('special-classes' . DS . 'SomeNamespace' . DS . 'ParserTestClass.php')); + static::assertEquals('SomeNamespace\\ParserTestClass', $class->getName()); } public function testClassFallsBackOnExisting(): void @@ -46,4 +54,61 @@ public function testClassFallsBackOnExisting(): void static::assertNotNull($parserClass); static::assertEquals('ParserTestClassFallsBack', $parserClass->getName()); } + + public function testPreviouslyLoadedTestClassCanBeParsed(): void + { + $class = $this->parseFile($this->fixture('passing-tests' . DS . 'PreviouslyLoadedTest.php')); + static::assertEquals('PreviouslyLoadedTest', $class->getName()); + } + + public function testParsedClassHasName(): void + { + $class = $this->parseFile($this->fixture('failing-tests' . DS . 'UnitTestWithClassAnnotationTest.php')); + static::assertEquals('Fixtures\\Tests\\UnitTestWithClassAnnotationTest', $class->getName()); + } + + public function testParsedAnonymousClassNameHasNoNullByte(): void + { + $class = $this->parseFile($this->fixture('failing-tests' . DS . 'AnonymousClass.inc')); + static::assertStringNotContainsString("\x00", $class->getName()); + } + + public function testParsedClassHasDocBlock(): void + { + $class = $this->parseFile($this->fixture('failing-tests' . DS . 'UnitTestWithClassAnnotationTest.php')); + static::assertEquals('/** + * @runParallel + * @pizzaBox + */', $class->getDocBlock()); + } + + public function testParsedClassHasNamespace(): void + { + $class = $this->parseFile($this->fixture('failing-tests' . DS . 'UnitTestWithClassAnnotationTest.php')); + static::assertEquals('Fixtures\\Tests', $class->getNamespace()); + } + + public function testParsedClassHasCorrectNumberOfTestMethods(): void + { + $class = $this->parseFile($this->fixture('failing-tests' . DS . 'UnitTestWithClassAnnotationTest.php')); + static::assertCount(4, $class->getMethods([])); + } + + public function testParsedClassWithParentHasCorrectNumberOfTestMethods(): void + { + $class = $this->parseFile($this->fixture('failing-tests' . DS . 'UnitTestWithErrorTest.php')); + static::assertCount(8, $class->getMethods([])); + } + + /** + * Parses a test case and returns the test class. + */ + private function parseFile(string $path): ParsedClass + { + $parser = new Parser($path); + $parserClass = $parser->getClass(); + static::assertNotNull($parserClass); + + return $parserClass; + } } diff --git a/test/Unit/Runners/PHPUnit/OptionsTest.php b/test/Unit/Runners/PHPUnit/OptionsTest.php index cf414197..2fc1ae57 100644 --- a/test/Unit/Runners/PHPUnit/OptionsTest.php +++ b/test/Unit/Runners/PHPUnit/OptionsTest.php @@ -95,19 +95,6 @@ public function testFilteredOptionsIsSet(): void static::assertEquals([$this->unfiltered['--group']], $this->options->group()); } - public function testAnnotationsReturnsAnnotations(): void - { - static::assertCount(1, $this->options->annotations()); - static::assertEquals('group1', $this->options->annotations()['group']); - } - - public function testAnnotationsDefaultsToEmptyArray(): void - { - $options = $this->createOptionsFromArgv([]); - - static::assertEmpty($options->annotations()); - } - public function testHalfProcessesMode(): void { $options = $this->createOptionsFromArgv(['--processes' => 'half']); diff --git a/test/fixtures/special-classes/SomeNamespace/ParserTestClass.php b/test/fixtures/special-classes/SomeNamespace/ParserTestClass.php index 7533bc93..6f39c4bc 100644 --- a/test/fixtures/special-classes/SomeNamespace/ParserTestClass.php +++ b/test/fixtures/special-classes/SomeNamespace/ParserTestClass.php @@ -7,14 +7,18 @@ use PHPUnit\Framework\TestCase; // Test that it gives the class matching the file name priority. -final class SomeOtherClass extends TestCase +final class NonTestClass { } -final class ParserTestClass extends TestCase +final class SomeOtherClass extends TestCase { } final class AnotherClass extends TestCase { } + +final class ParserTestClass extends TestCase +{ +} From b0f5f2e75899bae16b47fb3558b793242456fa54 Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Tue, 18 Aug 2020 10:30:46 +0200 Subject: [PATCH 08/19] Util ok --- test/Unit/Util/StrTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Unit/Util/StrTest.php b/test/Unit/Util/StrTest.php index 51c96f6f..658510df 100644 --- a/test/Unit/Util/StrTest.php +++ b/test/Unit/Util/StrTest.php @@ -10,7 +10,7 @@ use function array_values; /** - * @coversNothing + * @covers \ParaTest\Util\Str */ final class StrTest extends TestCase { From fdb93379370fd2fca8542a1581ae0fae985712ed Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Tue, 18 Aug 2020 10:39:44 +0200 Subject: [PATCH 09/19] Options ok --- src/Runners/PHPUnit/Options.php | 11 ++-- test/Unit/Runners/PHPUnit/OptionsTest.php | 61 ++++++++++------------- 2 files changed, 33 insertions(+), 39 deletions(-) diff --git a/src/Runners/PHPUnit/Options.php b/src/Runners/PHPUnit/Options.php index e3846420..cea60349 100644 --- a/src/Runners/PHPUnit/Options.php +++ b/src/Runners/PHPUnit/Options.php @@ -4,10 +4,10 @@ namespace ParaTest\Runners\PHPUnit; +use InvalidArgumentException; use ParaTest\Util\Str; use PHPUnit\TextUI\XmlConfiguration\Configuration; use PHPUnit\TextUI\XmlConfiguration\Loader; -use RuntimeException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputInterface; @@ -293,7 +293,7 @@ public static function fromConsoleInput(InputInterface $input, string $cwd): sel : []; if (isset($options['filter']) && strlen($options['filter']) > 0 && ! $options['functional']) { - throw new RuntimeException('Option --filter is not implemented for non functional mode'); + throw new InvalidArgumentException('Option --filter is not implemented for non functional mode'); } $configuration = null; @@ -585,7 +585,7 @@ private static function getPhpunitBinary(): string return $phpunit; } - return 'phpunit'; + return 'phpunit'; // @codeCoverageIgnore } /** @@ -597,7 +597,7 @@ private static function vendorDir(): string { $vendor = dirname(__DIR__, 3) . DIRECTORY_SEPARATOR . 'vendor'; if (! file_exists($vendor)) { - $vendor = dirname(__DIR__, 5); + $vendor = dirname(__DIR__, 5); // @codeCoverageIgnore } return $vendor; @@ -654,6 +654,7 @@ public static function getNumberOfCPUCores(): int assert($cpuinfo !== false); preg_match_all('/^processor/m', $cpuinfo, $matches); $cores = count($matches[0]); + // @codeCoverageIgnoreStart } elseif (DIRECTORY_SEPARATOR === '\\') { // Windows if (($process = @popen('wmic cpu get NumberOfCores', 'rb')) !== false) { @@ -667,6 +668,8 @@ public static function getNumberOfCPUCores(): int pclose($process); } + // @codeCoverageIgnoreEnd + return $cores; } diff --git a/test/Unit/Runners/PHPUnit/OptionsTest.php b/test/Unit/Runners/PHPUnit/OptionsTest.php index 2fc1ae57..1debccf1 100644 --- a/test/Unit/Runners/PHPUnit/OptionsTest.php +++ b/test/Unit/Runners/PHPUnit/OptionsTest.php @@ -4,22 +4,19 @@ namespace ParaTest\Tests\Unit\Runners\PHPUnit; +use InvalidArgumentException; use ParaTest\Runners\PHPUnit\Options; use ParaTest\Tests\TestBase; use Symfony\Component\Console\Input\InputDefinition; use function defined; use function file_put_contents; -use function glob; use function intdiv; -use function is_dir; -use function mkdir; use function sort; use function sys_get_temp_dir; -use function unlink; /** - * @coversNothing + * @covers \ParaTest\Runners\PHPUnit\Options */ final class OptionsTest extends TestBase { @@ -27,8 +24,6 @@ final class OptionsTest extends TestBase private $options; /** @var array */ private $unfiltered; - /** @var string */ - private $testCwd; public function setUpTest(): void { @@ -41,27 +36,8 @@ public function setUpTest(): void '--exclude-group' => 'group2', '--bootstrap' => '/path/to/bootstrap', ]; - $this->options = $this->createOptionsFromArgv($this->unfiltered); - $this->testCwd = __DIR__ . DS . 'generated-configs'; - if (! is_dir($this->testCwd)) { - mkdir($this->testCwd, 0777, true); - } - - $this->cleanUpGeneratedFiles(); - } - protected function tearDown(): void - { - $this->cleanUpGeneratedFiles(); - } - - private function cleanUpGeneratedFiles(): void - { - $glob = glob($this->testCwd . DS . '*'); - self::assertNotFalse($glob); - foreach ($glob as $file) { - unlink($file); - } + $this->options = $this->createOptionsFromArgv($this->unfiltered); } public function testOptionsAreOrdered(): void @@ -95,6 +71,16 @@ public function testFilteredOptionsIsSet(): void static::assertEquals([$this->unfiltered['--group']], $this->options->group()); } + public function testFilterOptionRequiresFunctionalMode(): void + { + $this->expectException(InvalidArgumentException::class); + + $this->createOptionsFromArgv([ + '--functional' => false, + '--filter' => 'testMe', + ]); + } + public function testHalfProcessesMode(): void { $options = $this->createOptionsFromArgv(['--processes' => 'half']); @@ -104,17 +90,17 @@ public function testHalfProcessesMode(): void public function testConfigurationShouldReturnXmlIfConfigNotSpecifiedAndFileExistsInCwd(): void { - $this->assertConfigurationFileFiltered('phpunit.xml', $this->testCwd); + $this->assertConfigurationFileFiltered('phpunit.xml', TMP_DIR); } public function testConfigurationShouldReturnXmlDistIfConfigAndXmlNotSpecifiedAndFileExistsInCwd(): void { - $this->assertConfigurationFileFiltered('phpunit.xml.dist', $this->testCwd); + $this->assertConfigurationFileFiltered('phpunit.xml.dist', TMP_DIR); } public function testConfigurationShouldReturnSpecifiedConfigurationIfFileExists(): void { - $this->assertConfigurationFileFiltered('phpunit-myconfig.xml', $this->testCwd, 'phpunit-myconfig.xml'); + $this->assertConfigurationFileFiltered('phpunit-myconfig.xml', TMP_DIR, 'phpunit-myconfig.xml'); } public function testConfigurationKeyIsNotPresentIfNoConfigGiven(): void @@ -153,12 +139,12 @@ public function testPassthru(): void public function testConfigurationShouldReturnXmlIfConfigSpecifiedAsDirectoryAndFileExists(): void { - $this->assertConfigurationFileFiltered('phpunit.xml', $this->testCwd, $this->testCwd); + $this->assertConfigurationFileFiltered('phpunit.xml', TMP_DIR, TMP_DIR); } public function testConfigurationShouldReturnXmlDistIfConfigSpecifiedAsDirectoryAndFileExists(): void { - $this->assertConfigurationFileFiltered('phpunit.xml.dist', $this->testCwd, $this->testCwd); + $this->assertConfigurationFileFiltered('phpunit.xml.dist', TMP_DIR, TMP_DIR); } private function assertConfigurationFileFiltered( @@ -166,17 +152,17 @@ private function assertConfigurationFileFiltered( string $path, ?string $configurationParameter = null ): void { - file_put_contents($this->testCwd . DS . $configFileName, ''); + file_put_contents(TMP_DIR . DS . $configFileName, ''); $this->unfiltered['path'] = $path; if ($configurationParameter !== null) { $this->unfiltered['--configuration'] = $configurationParameter; } - $options = $this->createOptionsFromArgv($this->unfiltered, $this->testCwd); + $options = $this->createOptionsFromArgv($this->unfiltered, TMP_DIR); $configuration = $options->configuration(); static::assertNotNull($configuration); static::assertEquals( - $this->testCwd . DS . $configFileName, + TMP_DIR . DS . $configFileName, $configuration->filename() ); } @@ -212,6 +198,7 @@ public function testDefaultOptions(): void static::assertFalse($options->stopOnFailure()); static::assertEmpty($options->testsuite()); static::assertSame(sys_get_temp_dir(), $options->tmpDir()); + static::assertSame(0, $options->verbose()); static::assertNull($options->whitelist()); } @@ -245,6 +232,7 @@ public function testProvidedOptions(): void '--stop-on-failure' => true, '--testsuite' => 'TESTSUITE', '--tmp-dir' => TMP_DIR, + '--verbose' => 1, '--whitelist' => 'WHITELIST', ]; @@ -277,6 +265,9 @@ public function testProvidedOptions(): void static::assertTrue($options->stopOnFailure()); static::assertSame(['TESTSUITE'], $options->testsuite()); static::assertSame(TMP_DIR, $options->tmpDir()); + static::assertSame(1, $options->verbose()); static::assertSame('WHITELIST', $options->whitelist()); + + static::assertTrue($options->hasCoverage()); } } From f4b77f5e8d3048c9e7e9cfd220550c320b3be712 Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Tue, 18 Aug 2020 11:20:33 +0200 Subject: [PATCH 10/19] ExecutableTest ok --- src/Runners/PHPUnit/ExecutableTest.php | 32 ++--------- src/Runners/PHPUnit/FullSuite.php | 10 +--- src/Runners/PHPUnit/SuiteLoader.php | 6 +- src/Runners/PHPUnit/TestMethod.php | 12 +--- test/Functional/PHPUnitTest.php | 2 +- .../Console/Commands/ParaTestCommandTest.php | 2 +- test/Unit/ResultTester.php | 6 +- .../Unit}/Runners/PHPUnit/EmptyRunnerStub.php | 4 +- .../Runners/PHPUnit/ExecutableTestChild.php | 3 - .../Runners/PHPUnit/ExecutableTestTest.php | 57 ++++++++++++------- test/Unit/Runners/PHPUnit/FullSuiteTest.php | 28 +++++++++ test/Unit/Runners/PHPUnit/SuiteTest.php | 33 +++++++++++ test/Unit/Runners/PHPUnit/TestMethodTest.php | 16 +++++- 13 files changed, 128 insertions(+), 83 deletions(-) rename {src => test/Unit}/Runners/PHPUnit/EmptyRunnerStub.php (78%) create mode 100644 test/Unit/Runners/PHPUnit/FullSuiteTest.php create mode 100644 test/Unit/Runners/PHPUnit/SuiteTest.php diff --git a/src/Runners/PHPUnit/ExecutableTest.php b/src/Runners/PHPUnit/ExecutableTest.php index 40be3383..9696fcf7 100644 --- a/src/Runners/PHPUnit/ExecutableTest.php +++ b/src/Runners/PHPUnit/ExecutableTest.php @@ -4,8 +4,6 @@ namespace ParaTest\Runners\PHPUnit; -use Symfony\Component\Process\Process; - use function array_map; use function array_merge; use function assert; @@ -19,7 +17,7 @@ abstract class ExecutableTest * * @var string */ - protected $path; + private $path; /** * A path to the temp file created @@ -27,21 +25,21 @@ abstract class ExecutableTest * * @var string|null */ - protected $temp; + private $temp; /** * Path where the coveragereport is stored. * * @var string|null */ - protected $coverageFileName; + private $coverageFileName; /** * Last executed process command. * * @var string */ - protected $lastCommand = ''; + private $lastCommand = ''; /** @var bool */ private $needsCoverage; @@ -146,20 +144,6 @@ final public function commandArguments(string $binary, array $options = [], ?arr return $arguments; } - /** - * Generate command line with passed options suitable to handle through paratest. - * - * @param string $binary executable binary name - * @param array $options command line options - * @param string[]|null $passthru - * - * @return string command line - */ - final public function command(string $binary, array $options = [], ?array $passthru = null): string - { - return (new Process($this->commandArguments($binary, $options, $passthru)))->getCommandLine(); - } - /** * Get coverage filename. */ @@ -175,14 +159,6 @@ final public function getCoverageFileName(): string return $this->coverageFileName; } - /** - * Set process temporary filename. - */ - final public function setTempFile(string $temp): void - { - $this->temp = $temp; - } - /** * A template method that can be overridden to add necessary options for a test. * diff --git a/src/Runners/PHPUnit/FullSuite.php b/src/Runners/PHPUnit/FullSuite.php index 84ce6248..295b4ec4 100644 --- a/src/Runners/PHPUnit/FullSuite.php +++ b/src/Runners/PHPUnit/FullSuite.php @@ -9,17 +9,13 @@ final class FullSuite extends ExecutableTest { /** @var string */ - protected $suiteName; + private $suiteName; - /** @var string */ - protected $configPath; - - public function __construct(string $suiteName, string $configPath, bool $needsCoverage, string $tmpDir) + public function __construct(string $suiteName, bool $needsCoverage, string $tmpDir) { parent::__construct('', $needsCoverage, $tmpDir); - $this->suiteName = $suiteName; - $this->configPath = $configPath; + $this->suiteName = $suiteName; } /** diff --git a/src/Runners/PHPUnit/SuiteLoader.php b/src/Runners/PHPUnit/SuiteLoader.php index 30c5983a..50536a98 100644 --- a/src/Runners/PHPUnit/SuiteLoader.php +++ b/src/Runners/PHPUnit/SuiteLoader.php @@ -165,9 +165,8 @@ public function load(?string $path = null): void private function initSuites(): void { if (is_array($this->suitesName)) { - assert($this->configuration !== null); foreach ($this->suitesName as $suiteName) { - $this->loadedSuites[$suiteName] = $this->createFullSuite($suiteName, $this->configuration->filename()); + $this->loadedSuites[$suiteName] = $this->createFullSuite($suiteName); } } else { foreach ($this->files as $path) { @@ -363,11 +362,10 @@ private function createSuite(string $path, ParsedClass $class): Suite ); } - private function createFullSuite(string $suiteName, string $configPath): FullSuite + private function createFullSuite(string $suiteName): FullSuite { return new FullSuite( $suiteName, - $configPath, $this->options->hasCoverage(), $this->options->tmpDir() ); diff --git a/src/Runners/PHPUnit/TestMethod.php b/src/Runners/PHPUnit/TestMethod.php index ad0042e8..f8f09f4a 100644 --- a/src/Runners/PHPUnit/TestMethod.php +++ b/src/Runners/PHPUnit/TestMethod.php @@ -24,7 +24,7 @@ final class TestMethod extends ExecutableTest * * @var string[] */ - protected $filters; + private $filters; /** * Passed filters must be unescaped and must represent test name, optionally including @@ -41,16 +41,6 @@ public function __construct(string $testPath, array $filters, bool $needsCoverag $this->filters = $filters; } - /** - * Returns the test method's filters. - * - * @return string[] - */ - public function getFilters(): array - { - return $this->filters; - } - /** * Returns the test method's name. * diff --git a/test/Functional/PHPUnitTest.php b/test/Functional/PHPUnitTest.php index 9cd4c046..fcb1f15f 100644 --- a/test/Functional/PHPUnitTest.php +++ b/test/Functional/PHPUnitTest.php @@ -4,9 +4,9 @@ namespace ParaTest\Tests\Functional; -use ParaTest\Runners\PHPUnit\EmptyRunnerStub; use ParaTest\Runners\PHPUnit\SqliteRunner; use ParaTest\Runners\PHPUnit\WrapperRunner; +use ParaTest\Tests\Unit\Runners\PHPUnit\EmptyRunnerStub; use ParseError; use function array_merge; diff --git a/test/Unit/Console/Commands/ParaTestCommandTest.php b/test/Unit/Console/Commands/ParaTestCommandTest.php index d4d5b04b..56a43dfb 100644 --- a/test/Unit/Console/Commands/ParaTestCommandTest.php +++ b/test/Unit/Console/Commands/ParaTestCommandTest.php @@ -6,8 +6,8 @@ use InvalidArgumentException; use ParaTest\Console\Commands\ParaTestCommand; -use ParaTest\Runners\PHPUnit\EmptyRunnerStub; use ParaTest\Tests\TestBase; +use ParaTest\Tests\Unit\Runners\PHPUnit\EmptyRunnerStub; use PHPUnit\TextUI\XmlConfiguration\Exception; use Symfony\Component\Console\Command\HelpCommand; use Symfony\Component\Console\Tester\CommandTester; diff --git a/test/Unit/ResultTester.php b/test/Unit/ResultTester.php index e2496e2f..8a0dce76 100644 --- a/test/Unit/ResultTester.php +++ b/test/Unit/ResultTester.php @@ -8,6 +8,9 @@ use ParaTest\Runners\PHPUnit\TestMethod; use ParaTest\Tests\TestBase; +use function file_get_contents; +use function file_put_contents; + abstract class ResultTester extends TestBase { /** @var Suite */ @@ -39,14 +42,13 @@ abstract protected function setUpInterpreter(): void; final protected function getSuiteWithResult(string $result, int $methodCount): Suite { - $result = FIXTURES . DS . 'results' . DS . $result; $functions = []; for ($i = 0; $i < $methodCount; ++$i) { $functions[] = new TestMethod((string) $i, [], false, TMP_DIR); } $suite = new Suite('', $functions, false, TMP_DIR); - $suite->setTempFile($result); + file_put_contents($suite->getTempFile(), (string) file_get_contents(FIXTURES . DS . 'results' . DS . $result)); return $suite; } diff --git a/src/Runners/PHPUnit/EmptyRunnerStub.php b/test/Unit/Runners/PHPUnit/EmptyRunnerStub.php similarity index 78% rename from src/Runners/PHPUnit/EmptyRunnerStub.php rename to test/Unit/Runners/PHPUnit/EmptyRunnerStub.php index bd52654f..7ab100fe 100644 --- a/src/Runners/PHPUnit/EmptyRunnerStub.php +++ b/test/Unit/Runners/PHPUnit/EmptyRunnerStub.php @@ -2,7 +2,9 @@ declare(strict_types=1); -namespace ParaTest\Runners\PHPUnit; +namespace ParaTest\Tests\Unit\Runners\PHPUnit; + +use ParaTest\Runners\PHPUnit\BaseRunner; final class EmptyRunnerStub extends BaseRunner { diff --git a/test/Unit/Runners/PHPUnit/ExecutableTestChild.php b/test/Unit/Runners/PHPUnit/ExecutableTestChild.php index 0ef1508f..eb441edb 100644 --- a/test/Unit/Runners/PHPUnit/ExecutableTestChild.php +++ b/test/Unit/Runners/PHPUnit/ExecutableTestChild.php @@ -6,9 +6,6 @@ use ParaTest\Runners\PHPUnit\ExecutableTest; -/** - * @coversNothing - */ final class ExecutableTestChild extends ExecutableTest { /** diff --git a/test/Unit/Runners/PHPUnit/ExecutableTestTest.php b/test/Unit/Runners/PHPUnit/ExecutableTestTest.php index a2bc5185..1260c280 100644 --- a/test/Unit/Runners/PHPUnit/ExecutableTestTest.php +++ b/test/Unit/Runners/PHPUnit/ExecutableTestTest.php @@ -6,11 +6,10 @@ use ParaTest\Tests\TestBase; -use function defined; -use function unlink; +use function uniqid; /** - * @coversNothing + * @covers \ParaTest\Runners\PHPUnit\ExecutableTest */ final class ExecutableTestTest extends TestBase { @@ -24,34 +23,40 @@ public function setUpTest(): void public function testConstructor(): void { - static::assertEquals('pathToFile', $this->getObjectValue($this->executableTestChild, 'path')); + static::assertEquals('pathToFile', $this->executableTestChild->getPath()); } public function testCommandRedirectsCoverage(): void { - $options = ['a' => 'b']; - $binary = '/usr/bin/phpunit'; - - $command = $this->executableTestChild->command($binary, $options); - - if (defined('PHP_WINDOWS_VERSION_BUILD')) { - static::assertMatchesRegularExpression( - '#^"/usr/bin/phpunit" --a b .+#', - $command - ); - } else { - static::assertMatchesRegularExpression( - "#^'/usr/bin/phpunit' '--a' 'b' .+#", - $command - ); - } + $binary = uniqid('phpunit'); + $options = ['a' => 'b', 'no-coverage' => null]; + $passthru = ['--no-extensions']; + + $commandArguments = $this->executableTestChild->commandArguments($binary, $options, $passthru); + + $expected = [ + $binary, + '--no-extensions', + '--a', + 'b', + '--no-coverage', + '--log-junit', + $this->executableTestChild->getTempFile(), + '--coverage-php', + $this->executableTestChild->getCoverageFileName(), + 'pathToFile', + ]; + + static::assertSame($expected, $commandArguments); } public function testGetTempFileShouldCreateTempFile(): void { $file = $this->executableTestChild->getTempFile(); static::assertFileExists($file); - unlink($file); + + $this->executableTestChild->deleteFile(); + static::assertFileDoesNotExist($file); } public function testGetTempFileShouldReturnSameFileIfAlreadyCalled(): void @@ -59,6 +64,14 @@ public function testGetTempFileShouldReturnSameFileIfAlreadyCalled(): void $file = $this->executableTestChild->getTempFile(); $fileAgain = $this->executableTestChild->getTempFile(); static::assertEquals($file, $fileAgain); - unlink($file); + } + + public function testStoreLastCommand(): void + { + static::assertEmpty($this->executableTestChild->getLastCommand()); + + $this->executableTestChild->setLastCommand($lastCommand = uniqid()); + + static::assertSame($lastCommand, $this->executableTestChild->getLastCommand()); } } diff --git a/test/Unit/Runners/PHPUnit/FullSuiteTest.php b/test/Unit/Runners/PHPUnit/FullSuiteTest.php new file mode 100644 index 00000000..5a23e1a5 --- /dev/null +++ b/test/Unit/Runners/PHPUnit/FullSuiteTest.php @@ -0,0 +1,28 @@ +commandArguments(uniqid()); + + static::assertContains('--testsuite', $commandArguments); + static::assertContains($name, $commandArguments); + static::assertSame(1, $fullSuite->getTestCount()); + } +} diff --git a/test/Unit/Runners/PHPUnit/SuiteTest.php b/test/Unit/Runners/PHPUnit/SuiteTest.php new file mode 100644 index 00000000..1e4fd10d --- /dev/null +++ b/test/Unit/Runners/PHPUnit/SuiteTest.php @@ -0,0 +1,33 @@ +commandArguments(uniqid()); + + static::assertNotContains('--filter', $commandArguments); + static::assertContains($file, $commandArguments); + static::assertSame($testMethods, $suite->getFunctions()); + static::assertSame(2, $suite->getTestCount()); + } +} diff --git a/test/Unit/Runners/PHPUnit/TestMethodTest.php b/test/Unit/Runners/PHPUnit/TestMethodTest.php index baae5e02..5010a712 100644 --- a/test/Unit/Runners/PHPUnit/TestMethodTest.php +++ b/test/Unit/Runners/PHPUnit/TestMethodTest.php @@ -7,14 +7,24 @@ use ParaTest\Runners\PHPUnit\TestMethod; use ParaTest\Tests\TestBase; +use function uniqid; + /** - * @coversNothing + * @covers \ParaTest\Runners\PHPUnit\TestMethod */ final class TestMethodTest extends TestBase { public function testConstructor(): void { - $testMethod = new TestMethod('pathToFile', ['methodName'], false, TMP_DIR); - static::assertEquals('pathToFile', $this->getObjectValue($testMethod, 'path')); + $file = uniqid('pathToFile_'); + $testMethod = new TestMethod($file, ['method1', 'method2'], false, TMP_DIR); + + $commandArguments = $testMethod->commandArguments(uniqid()); + + static::assertContains('--filter', $commandArguments); + static::assertContains($file, $commandArguments); + static::assertStringContainsString('method1', $testMethod->getName()); + static::assertStringContainsString('method2', $testMethod->getName()); + static::assertSame(2, $testMethod->getTestCount()); } } From 739cbdf468a30754b08abe6d842d7ea3c6990df4 Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Tue, 18 Aug 2020 12:00:00 +0200 Subject: [PATCH 11/19] SuiteLoader ok --- src/Parser/ParsedClass.php | 28 +--- src/Parser/ParsedObject.php | 19 --- src/Runners/PHPUnit/BaseRunner.php | 2 +- src/Runners/PHPUnit/SuiteLoader.php | 15 +-- src/Runners/PHPUnit/SuitePath.php | 61 --------- test/Unit/Parser/ParsedClassTest.php | 48 +------ test/Unit/Parser/ParsedObjectTest.php | 24 ---- test/Unit/Parser/ParserTest.php | 4 +- test/Unit/Runners/PHPUnit/SuiteLoaderTest.php | 126 +++++++++++++----- test/fixtures/phpunit-multifile.xml | 1 + 10 files changed, 106 insertions(+), 222 deletions(-) delete mode 100644 src/Runners/PHPUnit/SuitePath.php diff --git a/src/Parser/ParsedClass.php b/src/Parser/ParsedClass.php index 76985c92..cf6dfe61 100644 --- a/src/Parser/ParsedClass.php +++ b/src/Parser/ParsedClass.php @@ -4,9 +4,6 @@ namespace ParaTest\Parser; -use function array_filter; -use function count; - /** * @method class-string getName() */ @@ -41,32 +38,11 @@ public function __construct(string $doc, string $name, string $namespace, array * optionally filtering on annotations present * on a method. * - * @param string[] $groups - * * @return ParsedFunction[] */ - public function getMethods(array $groups): array + public function getMethods(): array { - if (count($groups) === 0) { - return $this->methods; - } - - $groupAnnotation = 'group'; - foreach ($groups as $group) { - if ($this->hasAnnotation($groupAnnotation, $group)) { - return $this->methods; - } - } - - return array_filter($this->methods, static function (ParsedFunction $method) use ($groupAnnotation, $groups): bool { - foreach ($groups as $group) { - if ($method->hasAnnotation($groupAnnotation, $group)) { - return true; - } - } - - return false; - }); + return $this->methods; } /** diff --git a/src/Parser/ParsedObject.php b/src/Parser/ParsedObject.php index 63e201bc..740aa33d 100644 --- a/src/Parser/ParsedObject.php +++ b/src/Parser/ParsedObject.php @@ -4,9 +4,6 @@ namespace ParaTest\Parser; -use function preg_match; -use function sprintf; - abstract class ParsedObject { /** @var string */ @@ -36,20 +33,4 @@ final public function getDocBlock(): string { return $this->docBlock; } - - /** - * Returns whether or not the parsed object - * has an annotation matching the name and value - * if provided. - */ - final public function hasAnnotation(string $annotation, ?string $value = null): bool - { - $pattern = sprintf( - '/@%s%s/', - $annotation, - $value !== null ? "[\\s]+{$value}" : '\b' - ); - - return preg_match($pattern, $this->docBlock) === 1; - } } diff --git a/src/Runners/PHPUnit/BaseRunner.php b/src/Runners/PHPUnit/BaseRunner.php index 51b31a1c..2f7fae70 100644 --- a/src/Runners/PHPUnit/BaseRunner.php +++ b/src/Runners/PHPUnit/BaseRunner.php @@ -67,7 +67,7 @@ public function __construct(Options $options, OutputInterface $output) private function load(SuiteLoader $loader): void { $this->beforeLoadChecks(); - $loader->load($this->options->path()); + $loader->load(); $executables = $this->options->functional() ? $loader->getTestMethods() : $loader->getSuites(); $this->pending = array_merge($this->pending, $executables); foreach ($this->pending as $pending) { diff --git a/src/Runners/PHPUnit/SuiteLoader.php b/src/Runners/PHPUnit/SuiteLoader.php index 50536a98..36c9a845 100644 --- a/src/Runners/PHPUnit/SuiteLoader.php +++ b/src/Runners/PHPUnit/SuiteLoader.php @@ -30,6 +30,7 @@ use function sprintf; use function strrpos; use function substr; +use function trim; use function version_compare; use const PHP_VERSION; @@ -103,11 +104,11 @@ public function getTestMethods(): array * * @throws RuntimeException */ - public function load(?string $path = null): void + public function load(): void { $this->loadConfiguration(); - if ($path !== null) { + if (($path = $this->options->path()) !== null) { $this->files = array_merge( $this->files, (new Facade())->getFilesAsArray($path, ['Test.php']) @@ -214,7 +215,7 @@ private function executableTests(string $path, ParsedClass $class): array */ private function getMethodBatches(ParsedClass $class): array { - $classMethods = $class->getMethods($this->options->group()); + $classMethods = $class->getMethods(); $maxBatchSize = $this->options->functional() ? $this->options->maxBatchSize() : 0; assert($maxBatchSize !== null); @@ -341,9 +342,7 @@ private function testMatchFilterOptions(string $className, string $name): bool return true; } - $re = substr($filter, 0, 1) === '/' - ? $filter - : '/' . $filter . '/'; + $re = '/' . trim($filter, '/') . '/'; $fullName = $className . '::' . $name; return preg_match($re, $fullName) === 1; @@ -384,7 +383,7 @@ private function loadFilesFromTestSuite(TestSuite $testSuiteCollection): void $directory->phpVersionOperator()->asString() ) ) { - continue; + continue; // @codeCoverageIgnore } $exclude = []; @@ -409,7 +408,7 @@ private function loadFilesFromTestSuite(TestSuite $testSuiteCollection): void $file->phpVersionOperator()->asString() ) ) { - continue; + continue; // @codeCoverageIgnore } $this->files[] = $file->path(); diff --git a/src/Runners/PHPUnit/SuitePath.php b/src/Runners/PHPUnit/SuitePath.php deleted file mode 100644 index 8a4abad2..00000000 --- a/src/Runners/PHPUnit/SuitePath.php +++ /dev/null @@ -1,61 +0,0 @@ -path = $path; - $this->excludedPaths = $excludedPaths; - $this->suffix = $suffix; - } - - public function getPath(): string - { - return $this->path; - } - - /** - * @return string[] - */ - public function getExcludedPaths(): array - { - return $this->excludedPaths; - } - - public function getSuffix(): string - { - return $this->suffix; - } - - public function getPattern(): string - { - return '|' . preg_quote($this->getSuffix()) . '$|'; - } -} diff --git a/test/Unit/Parser/ParsedClassTest.php b/test/Unit/Parser/ParsedClassTest.php index bfaa6c99..a60ea21d 100644 --- a/test/Unit/Parser/ParsedClassTest.php +++ b/test/Unit/Parser/ParsedClassTest.php @@ -38,53 +38,9 @@ public function setUpTest(): void $this->class = new ParsedClass('', 'MyTestClass', 'MyNamespace', $this->methods); } - public function testGetNamespace(): void + public function testGetters(): void { static::assertSame('MyNamespace', $this->class->getNamespace()); - } - - public function testGetMethodsReturnsMethods(): void - { - static::assertSame($this->methods, $this->class->getMethods([])); - } - - public function testGetMethodsMultipleAnnotationsReturnsMethods(): void - { - $goodMethod = new ParsedFunction( - '/** - * @group group1 - */', - 'testFunction' - ); - $goodMethod2 = new ParsedFunction( - '/** - * @group group2 - */', - 'testFunction2' - ); - $badMethod = new ParsedFunction( - '/** - * @group group3 - */', - 'testFunction2' - ); - $annotatedClass = new ParsedClass('', 'MyTestClass', '', [$goodMethod, $goodMethod2, $badMethod]); - $methods = $annotatedClass->getMethods(['group1', 'group2']); - static::assertSame([$goodMethod, $goodMethod2], $methods); - } - - public function testGetMethodsExceptsAdditionalAnnotationFilter(): void - { - $group1 = $this->class->getMethods(['group1']); - static::assertCount(1, $group1); - static::assertSame($this->methods[0], $group1[0]); - } - - public function testGetAllClassMethodsIfClassBelongsToGroup(): void - { - $class = new ParsedClass('/** @group group9 */', 'MyTestClass', 'MyNamespace', $this->methods); - $group1 = $class->getMethods(['group9']); - static::assertCount(3, $group1); - static::assertSame($this->methods[0], $group1[0]); + static::assertSame($this->methods, $this->class->getMethods()); } } diff --git a/test/Unit/Parser/ParsedObjectTest.php b/test/Unit/Parser/ParsedObjectTest.php index 7b6b340a..b3434177 100644 --- a/test/Unit/Parser/ParsedObjectTest.php +++ b/test/Unit/Parser/ParsedObjectTest.php @@ -29,28 +29,4 @@ public function testGetters(): void static::assertSame(self::class, $this->parsedClass->getName()); static::assertSame($this->docBlock, $this->parsedClass->getDocBlock()); } - - public function testHasAnnotationReturnsTrueWhenAnnotationPresent(): void - { - $hasAnnotation = $this->parsedClass->hasAnnotation('test'); - static::assertTrue($hasAnnotation); - } - - public function testHasAnnotationReturnsFalseWhenAnnotationNotPresent(): void - { - $hasAnnotation = $this->parsedClass->hasAnnotation('pizza'); - static::assertFalse($hasAnnotation); - } - - public function testHasAnnotationReturnsTrueWhenAnnotationAndValueMatch(): void - { - $hasAnnotation = $this->parsedClass->hasAnnotation('group', 'group1'); - static::assertTrue($hasAnnotation); - } - - public function testHasAnnotationReturnsFalseWhenAnnotationAndValueDontMatch(): void - { - $hasAnnotation = $this->parsedClass->hasAnnotation('group', 'group2'); - static::assertFalse($hasAnnotation); - } } diff --git a/test/Unit/Parser/ParserTest.php b/test/Unit/Parser/ParserTest.php index f194f9af..679dddc4 100644 --- a/test/Unit/Parser/ParserTest.php +++ b/test/Unit/Parser/ParserTest.php @@ -91,13 +91,13 @@ public function testParsedClassHasNamespace(): void public function testParsedClassHasCorrectNumberOfTestMethods(): void { $class = $this->parseFile($this->fixture('failing-tests' . DS . 'UnitTestWithClassAnnotationTest.php')); - static::assertCount(4, $class->getMethods([])); + static::assertCount(4, $class->getMethods()); } public function testParsedClassWithParentHasCorrectNumberOfTestMethods(): void { $class = $this->parseFile($this->fixture('failing-tests' . DS . 'UnitTestWithErrorTest.php')); - static::assertCount(8, $class->getMethods([])); + static::assertCount(8, $class->getMethods()); } /** diff --git a/test/Unit/Runners/PHPUnit/SuiteLoaderTest.php b/test/Unit/Runners/PHPUnit/SuiteLoaderTest.php index 2ebdcb10..96755f42 100644 --- a/test/Unit/Runners/PHPUnit/SuiteLoaderTest.php +++ b/test/Unit/Runners/PHPUnit/SuiteLoaderTest.php @@ -5,6 +5,7 @@ namespace ParaTest\Tests\Unit\Runners\PHPUnit; use ParaTest\Runners\PHPUnit\ExecutableTest; +use ParaTest\Runners\PHPUnit\FullSuite; use ParaTest\Runners\PHPUnit\Suite; use ParaTest\Runners\PHPUnit\SuiteLoader; use ParaTest\Tests\TestBase; @@ -17,7 +18,7 @@ use function uniqid; /** - * @coversNothing + * @covers \ParaTest\Runners\PHPUnit\SuiteLoader */ final class SuiteLoaderTest extends TestBase { @@ -30,11 +31,11 @@ public function testConstructor(): void public function testLoadThrowsExceptionWithInvalidPath(): void { - $loader = new SuiteLoader($this->createOptionsFromArgv([])); + $loader = new SuiteLoader($this->createOptionsFromArgv(['--path' => '/path/to/nowhere'])); $this->expectException(RuntimeException::class); - $loader->load('/path/to/nowhere'); + $loader->load(); } public function testLoadBarePathWithNoPathAndNoConfiguration(): void @@ -51,7 +52,6 @@ public function testLoadTestsuiteFileFromConfig(): void { $options = $this->createOptionsFromArgv([ '--configuration' => $this->fixture('phpunit-file.xml'), - '--testsuite' => 'ParaTest Fixtures', ]); $loader = new SuiteLoader($options); $loader->load(); @@ -65,7 +65,6 @@ public function testLoadTestsuiteFilesFromConfigWhileIgnoringExcludeTag(): void { $options = $this->createOptionsFromArgv([ '--configuration' => $this->fixture('phpunit-excluded-including-file.xml'), - '--testsuite' => 'ParaTest Fixtures', ]); $loader = new SuiteLoader($options); $loader->load(); @@ -79,7 +78,6 @@ public function testLoadTestsuiteFilesFromDirFromConfigWhileRespectingExcludeTag { $options = $this->createOptionsFromArgv([ '--configuration' => $this->fixture('phpunit-excluded-including-dir.xml'), - '--testsuite' => 'ParaTest Fixtures', ]); $loader = new SuiteLoader($options); $loader->load(); @@ -93,21 +91,19 @@ public function testLoadTestsuiteFilesFromConfig(): void { $options = $this->createOptionsFromArgv([ '--configuration' => $this->fixture('phpunit-multifile.xml'), - '--testsuite' => 'ParaTest Fixtures', + '--group' => 'fixtures,group4', ]); $loader = new SuiteLoader($options); $loader->load(); $files = $this->getObjectValue($loader, 'files'); - $expected = 2; - static::assertCount($expected, $files); + static::assertCount(3, $files); } public function testLoadTestsuiteWithDirectory(): void { $options = $this->createOptionsFromArgv([ '--configuration' => $this->fixture('phpunit-passing.xml'), - '--testsuite' => 'ParaTest Fixtures', ]); $loader = new SuiteLoader($options); $loader->load(); @@ -121,7 +117,6 @@ public function testLoadTestsuiteWithDirectories(): void { $options = $this->createOptionsFromArgv([ '--configuration' => $this->fixture('phpunit-multidir.xml'), - '--testsuite' => 'ParaTest Fixtures', ]); $loader = new SuiteLoader($options); $loader->load(); @@ -136,7 +131,6 @@ public function testLoadTestsuiteWithFilesDirsMixed(): void { $options = $this->createOptionsFromArgv([ '--configuration' => $this->fixture('phpunit-files-dirs-mix.xml'), - '--testsuite' => 'ParaTest Fixtures', ]); $loader = new SuiteLoader($options); $loader->load(); @@ -150,7 +144,6 @@ public function testLoadTestsuiteWithDuplicateFilesDirMixed(): void { $options = $this->createOptionsFromArgv([ '--configuration' => $this->fixture('phpunit-files-dirs-mix-duplicates.xml'), - '--testsuite' => 'ParaTest Fixtures', ]); $loader = new SuiteLoader($options); $loader->load(); @@ -160,6 +153,20 @@ public function testLoadTestsuiteWithDuplicateFilesDirMixed(): void static::assertCount($expected, $files); } + public function testLoadSomeTestsuite(): void + { + $options = $this->createOptionsFromArgv([ + '--configuration' => $this->fixture('phpunit-parallel-suite.xml'), + '--testsuite' => 'Suite 1', + ]); + $loader = new SuiteLoader($options); + $loader->load(); + $files = $this->getObjectValue($loader, 'files'); + + $expected = count($this->findTests(FIXTURES . DS . 'parallel-suite' . DS . 'One')); + static::assertCount($expected, $files); + } + public function testLoadSuiteFromConfig(): void { $options = $this->createOptionsFromArgv([ @@ -213,8 +220,8 @@ public function testLoadFileGetsPathOfFile(): void */ private function getLoadedPaths(string $path, ?SuiteLoader $loader = null): array { - $loader = $loader ?? new SuiteLoader($this->createOptionsFromArgv([])); - $loader->load($path); + $loader = $loader ?? new SuiteLoader($this->createOptionsFromArgv(['--path' => $path])); + $loader->load(); $loaded = $this->getObjectValue($loader, 'loadedSuites'); return array_keys($loaded); @@ -235,8 +242,8 @@ public function testLoadDirGetsPathOfAllTestsWithKeys(): array $fixturePath = $this->fixture('passing-tests'); $files = $this->findTests($fixturePath); - $loader = new SuiteLoader($this->createOptionsFromArgv([])); - $loader->load($fixturePath); + $loader = new SuiteLoader($this->createOptionsFromArgv(['--path' => $fixturePath])); + $loader->load(); $loaded = $this->getObjectValue($loader, 'loadedSuites'); foreach ($loaded as $path => $test) { static::assertContains($path, $files); @@ -292,10 +299,12 @@ public function testSecondParallelSuiteHasCorrectFunctions(array $paraSuites): v public function testGetTestMethodsOnlyReturnsMethodsOfGroupIfOptionIsSpecified(): void { - $options = $this->createOptionsFromArgv(['--group' => 'group1']); - $loader = new SuiteLoader($options); - $groupsTest = $this->fixture('passing-tests/GroupsTest.php'); - $loader->load($groupsTest); + $options = $this->createOptionsFromArgv([ + '--group' => 'group1', + '--path' => $this->fixture('passing-tests/GroupsTest.php'), + ]); + $loader = new SuiteLoader($options); + $loader->load(); $methods = $loader->getTestMethods(); static::assertCount(2, $methods); static::assertEquals('testTruth', $methods[0]->getName()); @@ -304,10 +313,12 @@ public function testGetTestMethodsOnlyReturnsMethodsOfGroupIfOptionIsSpecified() public function testGetTestMethodsOnlyReturnsMethodsOfClassGroup(): void { - $options = $this->createOptionsFromArgv(['--group' => 'group4']); - $loader = new SuiteLoader($options); - $groupsTest = $this->fixture('passing-tests/GroupsTest.php'); - $loader->load($groupsTest); + $options = $this->createOptionsFromArgv([ + '--group' => 'group4', + '--path' => $this->fixture('passing-tests/GroupsTest.php'), + ]); + $loader = new SuiteLoader($options); + $loader->load(); $methods = $loader->getTestMethods(); static::assertCount(1, $loader->getSuites()); static::assertCount(5, $methods); @@ -315,27 +326,33 @@ public function testGetTestMethodsOnlyReturnsMethodsOfClassGroup(): void public function testGetSuitesForNonMatchingGroups(): void { - $options = $this->createOptionsFromArgv(['--group' => 'non-existent']); - $loader = new SuiteLoader($options); - $groupsTest = $this->fixture('passing-tests/GroupsTest.php'); - $loader->load($groupsTest); + $options = $this->createOptionsFromArgv([ + '--group' => 'non-existent', + '--path' => $this->fixture('passing-tests/GroupsTest.php'), + ]); + $loader = new SuiteLoader($options); + $loader->load(); static::assertCount(0, $loader->getSuites()); static::assertCount(0, $loader->getTestMethods()); } public function testLoadIgnoresFilesWithoutClasses(): void { - $loader = new SuiteLoader($this->createOptionsFromArgv([])); - $fileWithoutClass = $this->fixture('special-classes/FileWithoutClass.php'); - $loader->load($fileWithoutClass); + $options = $this->createOptionsFromArgv([ + '--group' => 'non-existent', + '--path' => $this->fixture('special-classes/FileWithoutClass.php'), + ]); + $loader = new SuiteLoader($options); + $loader->load(); static::assertCount(0, $loader->getTestMethods()); } public function testExecutableTestsForFunctionalModeUse(): void { - $path = $this->fixture('passing-tests/DependsOnChain.php'); - $loader = new SuiteLoader($this->createOptionsFromArgv([])); - $loader->load($path); + $loader = new SuiteLoader($this->createOptionsFromArgv([ + '--path' => $this->fixture('passing-tests/DependsOnChain.php'), + ])); + $loader->load(); $tests = $loader->getTestMethods(); static::assertCount(2, $tests); $testMethod = $tests[0]; @@ -343,4 +360,43 @@ public function testExecutableTestsForFunctionalModeUse(): void $testMethod = $tests[1]; static::assertEquals('testTwoA|testTwoBDependsOnA', $testMethod->getName()); } + + public function testParallelSuite(): void + { + $loader = new SuiteLoader($this->createOptionsFromArgv([ + '--configuration' => $this->fixture('phpunit-parallel-suite.xml'), + '--parallel-suite' => true, + '--processes' => 2, + ])); + $loader->load(); + + $suites = $loader->getSuites(); + + static::assertCount(2, $suites); + foreach ($suites as $suite) { + static::assertInstanceOf(FullSuite::class, $suite); + } + } + + public function testBatches(): void + { + $options = $this->createOptionsFromArgv([ + '--bootstrap' => BOOTSTRAP, + '--path' => $this->fixture('dataprovider-tests/DataProviderTest.php'), + '--filter' => 'testNumericDataProvider1000', + '--functional' => true, + '--max-batch-size' => 50, + ], __DIR__); + $loader = new SuiteLoader($options); + $loader->load(); + + $suites = $loader->getSuites(); + + static::assertCount(1, $suites); + + $suite = array_shift($suites); + + static::assertInstanceOf(Suite::class, $suite); + static::assertCount(20, $suite->getFunctions()); + } } diff --git a/test/fixtures/phpunit-multifile.xml b/test/fixtures/phpunit-multifile.xml index 5f21791a..75c7518f 100644 --- a/test/fixtures/phpunit-multifile.xml +++ b/test/fixtures/phpunit-multifile.xml @@ -4,6 +4,7 @@ ./passing-tests/TestOfUnits.php ./passing-tests/GroupsTest.php + ./failing-tests/FailingTest.php From 3447e5a2fc4a22370c09b969c3fa45978a0d7c24 Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Tue, 18 Aug 2020 12:48:54 +0200 Subject: [PATCH 12/19] ResultPrinter ok --- src/Runners/PHPUnit/ExecutableTest.php | 11 ++- src/Runners/PHPUnit/ResultPrinter.php | 20 ----- test/Unit/ResultTester.php | 3 + .../Runners/PHPUnit/ExecutableTestTest.php | 10 ++- .../Runners/PHPUnit/ResultPrinterTest.php | 86 ++++++++++++++++--- test/fixtures/results/single-skipped.xml | 8 ++ 6 files changed, 100 insertions(+), 38 deletions(-) create mode 100644 test/fixtures/results/single-skipped.xml diff --git a/src/Runners/PHPUnit/ExecutableTest.php b/src/Runners/PHPUnit/ExecutableTest.php index 9696fcf7..9d155f5e 100644 --- a/src/Runners/PHPUnit/ExecutableTest.php +++ b/src/Runners/PHPUnit/ExecutableTest.php @@ -88,8 +88,15 @@ final public function getTempFile(): string */ final public function deleteFile(): void { - $outputFile = $this->getTempFile(); - unlink($outputFile); + if ($this->temp !== null) { + unlink($this->temp); + } + + if ($this->coverageFileName === null) { + return; + } + + unlink($this->coverageFileName); } /** diff --git a/src/Runners/PHPUnit/ResultPrinter.php b/src/Runners/PHPUnit/ResultPrinter.php index b017d73a..3b260adb 100644 --- a/src/Runners/PHPUnit/ResultPrinter.php +++ b/src/Runners/PHPUnit/ResultPrinter.php @@ -152,15 +152,6 @@ public function println(string $string = ''): void * Prints all results and removes any log files * used for aggregating results. */ - public function flush(): void - { - $this->printResults(); - $this->clearLogs(); - } - - /** - * Print final results. - */ public function printResults(): void { $this->output->write($this->getHeader()); @@ -506,15 +497,4 @@ private function red(string $text): string return $text; } - - /** - * Deletes all the temporary log files for ExecutableTest objects - * being printed. - */ - private function clearLogs(): void - { - foreach ($this->suites as $suite) { - $suite->deleteFile(); - } - } } diff --git a/test/Unit/ResultTester.php b/test/Unit/ResultTester.php index 8a0dce76..cadec05e 100644 --- a/test/Unit/ResultTester.php +++ b/test/Unit/ResultTester.php @@ -25,6 +25,8 @@ abstract class ResultTester extends TestBase protected $dataProviderSuite; /** @var Suite */ protected $errorSuite; + /** @var Suite */ + protected $skipped; final public function setUpTest(): void { @@ -32,6 +34,7 @@ final public function setUpTest(): void $this->otherErrorSuite = $this->getSuiteWithResult('single-werror2.xml', 1); $this->failureSuite = $this->getSuiteWithResult('single-wfailure.xml', 3); $this->mixedSuite = $this->getSuiteWithResult('mixed-results.xml', 7); + $this->skipped = $this->getSuiteWithResult('single-skipped.xml', 1); $this->passingSuite = $this->getSuiteWithResult('single-passing.xml', 3); $this->dataProviderSuite = $this->getSuiteWithResult('data-provider-result.xml', 50); diff --git a/test/Unit/Runners/PHPUnit/ExecutableTestTest.php b/test/Unit/Runners/PHPUnit/ExecutableTestTest.php index 1260c280..c93487e5 100644 --- a/test/Unit/Runners/PHPUnit/ExecutableTestTest.php +++ b/test/Unit/Runners/PHPUnit/ExecutableTestTest.php @@ -52,11 +52,15 @@ public function testCommandRedirectsCoverage(): void public function testGetTempFileShouldCreateTempFile(): void { - $file = $this->executableTestChild->getTempFile(); - static::assertFileExists($file); + $logFile = $this->executableTestChild->getTempFile(); + $ccFile = $this->executableTestChild->getCoverageFileName(); + static::assertFileExists($logFile); + static::assertFileExists($ccFile); $this->executableTestChild->deleteFile(); - static::assertFileDoesNotExist($file); + + static::assertFileDoesNotExist($logFile); + static::assertFileDoesNotExist($ccFile); } public function testGetTempFileShouldReturnSameFileIfAlreadyCalled(): void diff --git a/test/Unit/Runners/PHPUnit/ResultPrinterTest.php b/test/Unit/Runners/PHPUnit/ResultPrinterTest.php index 4d791f84..977074fc 100644 --- a/test/Unit/Runners/PHPUnit/ResultPrinterTest.php +++ b/test/Unit/Runners/PHPUnit/ResultPrinterTest.php @@ -10,16 +10,16 @@ use ParaTest\Runners\PHPUnit\Suite; use ParaTest\Runners\PHPUnit\TestMethod; use ParaTest\Tests\Unit\ResultTester; +use RuntimeException; use Symfony\Component\Console\Output\BufferedOutput; use function defined; -use function file_exists; use function file_put_contents; use function sprintf; -use function unlink; +use function uniqid; /** - * @coversNothing + * @covers \ParaTest\Runners\PHPUnit\ResultPrinter */ final class ResultPrinterTest extends ResultTester { @@ -43,19 +43,10 @@ protected function setUpInterpreter(): void $this->output = new BufferedOutput(); $this->options = $this->createOptionsFromArgv([]); $this->printer = new ResultPrinter($this->interpreter, $this->output, $this->options); - $pathToConfig = $this->getPathToConfig(); - if (file_exists($pathToConfig)) { - unlink($pathToConfig); - } $this->passingSuiteWithWrongTestCountEstimation = $this->getSuiteWithResult('single-passing.xml', 1); } - private function getPathToConfig(): string - { - return __DIR__ . DS . 'phpunit-myconfig.xml'; - } - public function testConstructor(): void { static::assertSame([], $this->getObjectValue($this->printer, 'suites')); @@ -117,7 +108,8 @@ public function testStartSetsWidthAndMaxColumn(): void public function testStartPrintsOptionInfoAndConfigurationDetailsIfConfigFilePresent(): void { - $pathToConfig = $this->getPathToConfig(); + $pathToConfig = TMP_DIR . DS . 'phpunit-myconfig.xml'; + file_put_contents($pathToConfig, ''); $this->printer = new ResultPrinter($this->interpreter, $this->output, $this->createOptionsFromArgv(['--configuration' => $pathToConfig])); $contents = $this->getStartOutput(); @@ -353,6 +345,74 @@ public function testResultPrinterAdjustsTotalCountForDataProviders(): void static::assertSame($expected, $feedback); } + public function testColors(): void + { + $this->options = $this->createOptionsFromArgv(['--colors' => true]); + $this->printer = new ResultPrinter($this->interpreter, $this->output, $this->options); + $this->printer->addTest($this->mixedSuite); + + $this->printer->start(); + $this->printer->printFeedback($this->mixedSuite); + $this->printer->printResults(); + + static::assertStringContainsString('FAILURES', $this->output->fetch()); + } + + public function testColorsForSkipped(): void + { + $this->options = $this->createOptionsFromArgv(['--colors' => true]); + $this->printer = new ResultPrinter($this->interpreter, $this->output, $this->options); + $this->printer->addTest($this->skipped); + + $this->printer->start(); + $this->printer->printFeedback($this->skipped); + $this->printer->printResults(); + + static::assertStringContainsString('OK', $this->output->fetch()); + } + + public function testColorsForPassing(): void + { + $this->options = $this->createOptionsFromArgv(['--colors' => true]); + $this->printer = new ResultPrinter($this->interpreter, $this->output, $this->options); + $this->printer->addTest($this->passingSuite); + + $this->printer->start(); + $this->printer->printFeedback($this->passingSuite); + $this->printer->printResults(); + + static::assertStringContainsString('OK', $this->output->fetch()); + } + + /** + * This test ensure Code Coverage over printSkippedAndIncomplete + * but the real case for this test case is missing at the time of writing + * + * @see \ParaTest\Runners\PHPUnit\ResultPrinter::printSkippedAndIncomplete + */ + public function testParallelSuiteProgressOverhead(): void + { + $suite = $this->getSuiteWithResult('mixed-results.xml', 100); + $this->printer->addTest($suite); + + $this->printer->start(); + $this->printer->printFeedback($suite); + $this->printer->printResults(); + + static::assertStringContainsString('FAILURES', $this->output->fetch()); + } + + public function testEmptyLogFileRaiseExceptionWithLastCommand(): void + { + $test = new ExecutableTestChild(uniqid(), false, TMP_DIR); + $test->setLastCommand(uniqid()); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessageMatches(sprintf('/%s/', $test->getLastCommand())); + + $this->printer->printFeedback($test); + } + private function getStartOutput(): string { $this->printer->start(); diff --git a/test/fixtures/results/single-skipped.xml b/test/fixtures/results/single-skipped.xml new file mode 100644 index 00000000..4f6969a5 --- /dev/null +++ b/test/fixtures/results/single-skipped.xml @@ -0,0 +1,8 @@ + + + + + + + + From ca9bf6b9b35aebb7de1d13d58e8715371698e6c8 Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Tue, 18 Aug 2020 12:53:57 +0200 Subject: [PATCH 13/19] Minor CC workaround --- src/Runners/PHPUnit/ExecutableTest.php | 2 ++ test/Unit/Runners/PHPUnit/ExecutableTestTest.php | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Runners/PHPUnit/ExecutableTest.php b/src/Runners/PHPUnit/ExecutableTest.php index 9d155f5e..12b64f7e 100644 --- a/src/Runners/PHPUnit/ExecutableTest.php +++ b/src/Runners/PHPUnit/ExecutableTest.php @@ -90,6 +90,7 @@ final public function deleteFile(): void { if ($this->temp !== null) { unlink($this->temp); + $this->temp = null; } if ($this->coverageFileName === null) { @@ -97,6 +98,7 @@ final public function deleteFile(): void } unlink($this->coverageFileName); + $this->coverageFileName = null; } /** diff --git a/test/Unit/Runners/PHPUnit/ExecutableTestTest.php b/test/Unit/Runners/PHPUnit/ExecutableTestTest.php index c93487e5..afca3252 100644 --- a/test/Unit/Runners/PHPUnit/ExecutableTestTest.php +++ b/test/Unit/Runners/PHPUnit/ExecutableTestTest.php @@ -53,13 +53,13 @@ public function testCommandRedirectsCoverage(): void public function testGetTempFileShouldCreateTempFile(): void { $logFile = $this->executableTestChild->getTempFile(); - $ccFile = $this->executableTestChild->getCoverageFileName(); static::assertFileExists($logFile); - static::assertFileExists($ccFile); - $this->executableTestChild->deleteFile(); - static::assertFileDoesNotExist($logFile); + + $ccFile = $this->executableTestChild->getCoverageFileName(); + static::assertFileExists($ccFile); + $this->executableTestChild->deleteFile(); static::assertFileDoesNotExist($ccFile); } From 77bf67303831e6340fd5621a31ab763f465ce90c Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Wed, 19 Aug 2020 10:34:45 +0200 Subject: [PATCH 14/19] SQLiteRunner + SQLiteWorker ok --- bin/phpunit-wrapper.php | 7 +- phpstan.neon.dist | 12 +++ src/Runners/PHPUnit/BaseRunner.php | 61 +++++++---- src/Runners/PHPUnit/BaseWrapperRunner.php | 44 +------- src/Runners/PHPUnit/Options.php | 18 ++++ src/Runners/PHPUnit/ResultPrinter.php | 7 +- src/Runners/PHPUnit/Runner.php | 46 ++------ src/Runners/PHPUnit/SqliteRunner.php | 46 ++++---- src/Runners/PHPUnit/Worker/BaseWorker.php | 50 +++------ src/Runners/PHPUnit/Worker/RunnerWorker.php | 26 ++--- src/Runners/PHPUnit/Worker/SqliteWorker.php | 4 - src/Runners/PHPUnit/Worker/WrapperWorker.php | 11 +- src/Runners/PHPUnit/WrapperRunner.php | 29 +++-- .../Functional/Runners/PHPUnit/WorkerTest.php | 24 ++--- test/Functional/SqliteRunnerTest.php | 90 ---------------- test/TestBase.php | 18 ++++ test/Unit/Runners/PHPUnit/EmptyRunnerStub.php | 27 ++++- test/Unit/Runners/PHPUnit/OptionsTest.php | 25 +++++ .../Runners/PHPUnit/ResultPrinterTest.php | 25 ++--- test/Unit/Runners/PHPUnit/RunnerTest.php | 2 +- .../Unit/Runners/PHPUnit/SqliteRunnerTest.php | 100 ++++++++++++++++++ .../UnitTestThatExitsLoudlyTest.php | 11 ++ .../UnitTestThatExitsSilentlyTest.php | 11 ++ test/fixtures/phpunit-fatal.xml | 8 ++ 24 files changed, 370 insertions(+), 332 deletions(-) delete mode 100644 test/Functional/SqliteRunnerTest.php create mode 100644 test/Unit/Runners/PHPUnit/SqliteRunnerTest.php create mode 100644 test/fixtures/exit-tests/UnitTestThatExitsLoudlyTest.php create mode 100644 test/fixtures/exit-tests/UnitTestThatExitsSilentlyTest.php create mode 100644 test/fixtures/phpunit-fatal.xml diff --git a/bin/phpunit-wrapper.php b/bin/phpunit-wrapper.php index 895d3907..1bac7f63 100644 --- a/bin/phpunit-wrapper.php +++ b/bin/phpunit-wrapper.php @@ -46,8 +46,7 @@ exit($lastExitCode); } - $command = rtrim($command); - if ($command === 'EXIT') { + if ($command === \ParaTest\Runners\PHPUnit\Worker\WrapperWorker::COMMAND_EXIT) { echo "EXITED\n"; exit($lastExitCode); } @@ -57,7 +56,7 @@ echo "Executing: {$command}\n"; $info = []; - $info[] = 'Time: ' . (new DateTime())->format(DateTime::RFC3339); + $info[] = 'Time: ' . (new DateTimeImmutable())->format(DateTime::RFC3339); $info[] = "Iteration: {$i}"; $info[] = "Command: {$command}"; $info[] = PHP_EOL; @@ -73,5 +72,5 @@ $logInfo($infoText); - echo "FINISHED\n"; + echo \ParaTest\Runners\PHPUnit\Worker\WrapperWorker::COMMAND_FINISHED; } diff --git a/phpstan.neon.dist b/phpstan.neon.dist index b94ed6e5..77aad273 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -123,6 +123,18 @@ parameters: message: "#^Call to method PHPUnit\\\\Framework\\\\Assert\\:\\:assertTrue\\(\\) with false will always evaluate to false\\.$#" count: 1 path: test/fixtures/wrapper-runner-exit-code-tests/FailureTest.php + - + message: "#^Offset 'TEST_TOKEN' does not exist on array\\('PARATEST' \\=\\> 1, \\?'TEST_TOKEN' \\=\\> int, \\?'UNIQUE_TEST_TOKEN' \\=\\> string\\)\\.$#" + count: 1 + path: test/Unit/Runners/PHPUnit/OptionsTest.php + - + message: "#^Call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertIsString\\(\\) with string will always evaluate to true\\.$#" + count: 1 + path: test/Unit/Runners/PHPUnit/OptionsTest.php + - + message: "#^Offset 'UNIQUE_TEST_TOKEN' does not exist on array\\('PARATEST' \\=\\> 1, \\?'UNIQUE_TEST_TOKEN' \\=\\> string, 'TEST_TOKEN' \\=\\> int\\)\\.$#" + count: 1 + path: test/Unit/Runners/PHPUnit/OptionsTest.php # Level 7 - diff --git a/src/Runners/PHPUnit/BaseRunner.php b/src/Runners/PHPUnit/BaseRunner.php index 2f7fae70..754ce721 100644 --- a/src/Runners/PHPUnit/BaseRunner.php +++ b/src/Runners/PHPUnit/BaseRunner.php @@ -40,24 +40,42 @@ abstract class BaseRunner implements RunnerInterface */ protected $exitcode = -1; + /** @var OutputInterface */ + protected $output; + /** * CoverageMerger to hold track of the accumulated coverage. * * @var CoverageMerger|null */ - protected $coverage = null; - - /** @var OutputInterface */ - protected $output; + private $coverage = null; public function __construct(Options $options, OutputInterface $output) { $this->options = $options; + $this->output = $output; $this->interpreter = new LogInterpreter(); $this->printer = new ResultPrinter($this->interpreter, $output, $options); - $this->output = $output; + + if (! $this->options->hasCoverage()) { + return; + } + + $this->coverage = new CoverageMerger($this->options->coverageTestLimit()); + } + + final public function run(): void + { + $this->load(new SuiteLoader($this->options)); + $this->printer->start(); + + $this->doRun(); + + $this->complete(); } + abstract protected function doRun(): void; + /** * Builds the collection of pending ExecutableTest objects * to run. If functional mode is enabled $this->pending will @@ -77,6 +95,23 @@ private function load(SuiteLoader $loader): void abstract protected function beforeLoadChecks(): void; + /** + * Finalizes the run process. This method + * prints all results, rewinds the log interpreter, + * logs any results to JUnit, and cleans up temporary + * files. + */ + private function complete(): void + { + $this->printer->printResults(); + $this->log(); + $this->logCoverage(); + $readers = $this->interpreter->getReaders(); + foreach ($readers as $reader) { + $reader->removeLog(); + } + } + /** * Returns the highest exit code encountered * throughout the course of test execution. @@ -144,15 +179,6 @@ final protected function logCoverage(): void $reporter->php($coveragePhp); } - private function initCoverage(): void - { - if (! $this->options->hasCoverage()) { - return; - } - - $this->coverage = new CoverageMerger($this->options->coverageTestLimit()); - } - final protected function hasCoverage(): bool { return $this->options->hasCoverage(); @@ -162,11 +188,4 @@ final protected function getCoverage(): ?CoverageMerger { return $this->coverage; } - - final protected function initialize(): void - { - $this->initCoverage(); - $this->load(new SuiteLoader($this->options)); - $this->printer->start(); - } } diff --git a/src/Runners/PHPUnit/BaseWrapperRunner.php b/src/Runners/PHPUnit/BaseWrapperRunner.php index 959672ae..60ef66cc 100644 --- a/src/Runners/PHPUnit/BaseWrapperRunner.php +++ b/src/Runners/PHPUnit/BaseWrapperRunner.php @@ -4,20 +4,11 @@ namespace ParaTest\Runners\PHPUnit; +use PHPUnit\TextUI\TestRunner; use RuntimeException; abstract class BaseWrapperRunner extends BaseRunner { - private const PHPUNIT_FAILURES = 1; - - private const PHPUNIT_ERRORS = 2; - - /** @var resource[] */ - protected $streams = []; - - /** @var resource[] */ - protected $modified = []; - final protected function beforeLoadChecks(): void { if ($this->options->functional()) { @@ -28,38 +19,13 @@ final protected function beforeLoadChecks(): void } } - final protected function complete(): void - { - $this->setExitCode(); - $this->printer->printResults(); - $this->log(); - $this->logCoverage(); - $readers = $this->interpreter->getReaders(); - foreach ($readers as $reader) { - $reader->removeLog(); - } - } - - private function setExitCode(): void + final protected function setExitCode(): void { + $this->exitcode = TestRunner::SUCCESS_EXIT; if ($this->interpreter->getTotalErrors() > 0) { - $this->exitcode = self::PHPUNIT_ERRORS; + $this->exitcode = TestRunner::EXCEPTION_EXIT; } elseif ($this->interpreter->getTotalFailures() > 0) { - $this->exitcode = self::PHPUNIT_FAILURES; - } else { - $this->exitcode = 0; + $this->exitcode = TestRunner::FAILURE_EXIT; } } - - /* - private function testIsStillRunning($test) - { - if(!$test->isDoneRunning()) return true; - $this->setExitCode($test); - $test->stop(); - if (static::PHPUNIT_FATAL_ERROR === $test->getExitCode()) - throw new \Exception($test->getStderr(), $test->getExitCode()); - return false; - } - */ } diff --git a/src/Runners/PHPUnit/Options.php b/src/Runners/PHPUnit/Options.php index cea60349..48e230c8 100644 --- a/src/Runners/PHPUnit/Options.php +++ b/src/Runners/PHPUnit/Options.php @@ -36,6 +36,7 @@ use function sprintf; use function strlen; use function sys_get_temp_dir; +use function uniqid; use function unserialize; use const DIRECTORY_SEPARATOR; @@ -47,6 +48,9 @@ */ final class Options { + public const ENV_KEY_TOKEN = 'TEST_TOKEN'; + public const ENV_KEY_UNIQUE_TOKEN = 'UNIQUE_TEST_TOKEN'; + /** * @see \PHPUnit\Util\Configuration * @see https://github.com/sebastianbergmann/phpunit/commit/80754cf323fe96003a2567f5e57404fddecff3bf @@ -857,4 +861,18 @@ public function whitelist(): ?string { return $this->whitelist; } + + /** + * @return array{PARATEST: int, TEST_TOKEN?: int, UNIQUE_TEST_TOKEN?: string} + */ + public function fillEnvWithTokens(int $inc): array + { + $env = ['PARATEST' => 1]; + if (! $this->noTestTokens()) { + $env[self::ENV_KEY_TOKEN] = $inc; + $env[self::ENV_KEY_UNIQUE_TOKEN] = uniqid($inc . '_'); + } + + return $env; + } } diff --git a/src/Runners/PHPUnit/ResultPrinter.php b/src/Runners/PHPUnit/ResultPrinter.php index 3b260adb..15a16e8e 100644 --- a/src/Runners/PHPUnit/ResultPrinter.php +++ b/src/Runners/PHPUnit/ResultPrinter.php @@ -106,13 +106,10 @@ public function __construct(LogInterpreter $results, OutputInterface $output, Op /** * Adds an ExecutableTest to the tracked results. */ - public function addTest(ExecutableTest $suite): self + public function addTest(ExecutableTest $suite): void { $this->suites[] = $suite; - $increment = $suite->getTestCount(); - $this->totalCases += $increment; - - return $this; + $this->totalCases += $suite->getTestCount(); } /** diff --git a/src/Runners/PHPUnit/Runner.php b/src/Runners/PHPUnit/Runner.php index 627af9ba..256a7153 100644 --- a/src/Runners/PHPUnit/Runner.php +++ b/src/Runners/PHPUnit/Runner.php @@ -5,7 +5,6 @@ namespace ParaTest\Runners\PHPUnit; use Exception; -use Habitat\Habitat; use ParaTest\Runners\PHPUnit\Worker\RunnerWorker; use RuntimeException; use Symfony\Component\Console\Output\OutputInterface; @@ -13,11 +12,12 @@ use function array_filter; use function array_keys; +use function array_merge; use function array_shift; use function assert; use function count; +use function getenv; use function sprintf; -use function uniqid; use function usleep; final class Runner extends BaseRunner @@ -36,7 +36,7 @@ final class Runner extends BaseRunner * A collection of available tokens based on the number * of processes specified in $options. * - * @var array + * @var array */ private $tokens = []; @@ -49,10 +49,8 @@ public function __construct(Options $opts, OutputInterface $output) /** * The money maker. Runs all ExecutableTest objects in separate processes. */ - public function run(): void + protected function doRun(): void { - $this->initialize(); - while (count($this->running) > 0 || count($this->pending) > 0) { foreach ($this->running as $key => $test) { try { @@ -75,25 +73,6 @@ public function run(): void $this->fillRunQueue(); usleep(10000); } - - $this->complete(); - } - - /** - * Finalizes the run process. This method - * prints all results, rewinds the log interpreter, - * logs any results to JUnit, and cleans up temporary - * files. - */ - private function complete(): void - { - $this->printer->printResults(); - $this->log(); - $this->logCoverage(); - $readers = $this->interpreter->getReaders(); - foreach ($readers as $reader) { - $reader->removeLog(); - } } /** @@ -110,15 +89,7 @@ private function fillRunQueue(): void } $this->acquireToken($tokenData['token']); - $env = []; - if (! $this->options->noTestTokens()) { - $env = [ - 'TEST_TOKEN' => $tokenData['token'], - 'UNIQUE_TEST_TOKEN' => $tokenData['unique'], - ]; - } - - $env += Habitat::getAll(); + $env = array_merge(getenv(), $this->options->fillEnvWithTokens($tokenData['token'])); $executebleTest = array_shift($this->pending); /** @psalm-suppress RedundantConditionGivenDocblockType **/ @@ -203,7 +174,10 @@ private function initTokens(): void { $this->tokens = []; for ($i = 1; $i <= $this->options->processes(); ++$i) { - $this->tokens[$i] = ['token' => $i, 'unique' => uniqid(sprintf('%s_', $i)), 'available' => true]; + $this->tokens[$i] = [ + 'token' => $i, + 'available' => true, + ]; } } @@ -211,7 +185,7 @@ private function initTokens(): void * Gets the next token that is available to be acquired * from a finished process. * - * @return false|array{token: int, unique: string, available: bool} + * @return false|array{token: int, available: bool} */ private function getNextAvailableToken() { diff --git a/src/Runners/PHPUnit/SqliteRunner.php b/src/Runners/PHPUnit/SqliteRunner.php index 3ab70fe0..33ec10c1 100644 --- a/src/Runners/PHPUnit/SqliteRunner.php +++ b/src/Runners/PHPUnit/SqliteRunner.php @@ -9,6 +9,7 @@ use RuntimeException; use Symfony\Component\Console\Output\OutputInterface; +use function array_map; use function assert; use function count; use function dirname; @@ -16,8 +17,8 @@ use function realpath; use function serialize; use function tempnam; -use function uniqid; use function unlink; +use function unserialize; use function usleep; use const DIRECTORY_SEPARATOR; @@ -51,15 +52,14 @@ public function __destruct() unlink($this->dbFileName); } - public function run(): void + protected function doRun(): void { - $this->initialize(); $this->createTable(); $this->assignAllPendingTests(); $this->startWorkers(); $this->waitForAllToFinish(); - $this->complete(); $this->checkIfWorkersCrashed(); + $this->setExitCode(); } /** @@ -74,15 +74,8 @@ private function startWorkers(): void for ($i = 1; $i <= $this->options->processes(); ++$i) { $worker = new SqliteWorker($this->output, $this->dbFileName); - if ($this->options->noTestTokens()) { - $token = null; - $uniqueToken = null; - } else { - $token = $i; - $uniqueToken = uniqid(); - } - $worker->start($wrapper, $token, $uniqueToken); + $worker->start($wrapper, $this->options, $i); $this->workers[] = $worker; } } @@ -113,17 +106,18 @@ private function waitForAllToFinish(): void */ private function createTable(): void { - $statement = 'CREATE TABLE tests ( - id INTEGER PRIMARY KEY, - command TEXT NOT NULL UNIQUE, - file_name TEXT NOT NULL, - reserved_by_process_id INTEGER, - completed INTEGER DEFAULT 0 - )'; - - if ($this->db->exec($statement) === false) { - throw new RuntimeException('Error while creating sqlite database table: ' . $this->db->errorCode()); - } + $statement = ' + CREATE TABLE tests ( + id INTEGER PRIMARY KEY, + command TEXT NOT NULL UNIQUE, + file_name TEXT NOT NULL, + reserved_by_process_id INTEGER, + completed INTEGER DEFAULT 0 + ) + '; + + $tableCreationResult = $this->db->exec($statement); + assert($tableCreationResult !== false); } /** @@ -173,6 +167,10 @@ private function checkIfWorkersCrashed(): void $commandStmt = $this->db->query('SELECT command FROM tests'); assert($commandStmt !== false); + $commands = (array) $commandStmt->fetchAll(PDO::FETCH_COLUMN); + $commands = array_map(static function (string $serializedCommand): string { + return implode(' ', array_map('escapeshellarg', unserialize($serializedCommand))); + }, $commands); throw new RuntimeException( 'Some workers have crashed.' . PHP_EOL @@ -182,7 +180,7 @@ private function checkIfWorkersCrashed(): void . '----------------------' . PHP_EOL . 'Failed test command(s):' . PHP_EOL . '----------------------' . PHP_EOL - . implode(PHP_EOL, (array) $commandStmt->fetchAll(PDO::FETCH_COLUMN)) + . implode(PHP_EOL, $commands) ); } } diff --git a/src/Runners/PHPUnit/Worker/BaseWorker.php b/src/Runners/PHPUnit/Worker/BaseWorker.php index 5e725eef..ac17e98a 100644 --- a/src/Runners/PHPUnit/Worker/BaseWorker.php +++ b/src/Runners/PHPUnit/Worker/BaseWorker.php @@ -10,16 +10,15 @@ use Symfony\Component\Process\PhpExecutableFinder; use function array_map; +use function array_merge; use function assert; use function count; use function end; use function escapeshellarg; use function explode; -use function fclose; use function fread; use function getenv; use function implode; -use function is_numeric; use function is_resource; use function proc_get_status; use function proc_open; @@ -33,12 +32,6 @@ abstract class BaseWorker { - /** @var string[][] */ - protected static $descriptorspec = [ - 0 => ['pipe', 'r'], - 1 => ['pipe', 'w'], - 2 => ['pipe', 'w'], - ]; /** @var resource|null */ protected $proc; /** @var resource[] */ @@ -50,6 +43,12 @@ abstract class BaseWorker /** @var string[] */ protected $commands = []; + /** @var string[][] */ + private static $descriptorspec = [ + 0 => ['pipe', 'r'], + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], + ]; /** @var int|null */ private $exitCode = null; /** @var string */ @@ -62,45 +61,32 @@ public function __construct(OutputInterface $output) $this->output = $output; } - /** - * @param string[] $parameters - */ final public function start( string $wrapperBinary, - ?int $token = 1, - ?string $uniqueToken = null, - array $parameters = [], - ?Options $options = null + Options $options, + int $token ): void { - $env = getenv(); - $env['PARATEST'] = 1; - if (is_numeric($token)) { - $env['XDEBUG_CONFIG'] = 'true'; - $env['TEST_TOKEN'] = $token; - } - - if ($uniqueToken !== null) { - $env['UNIQUE_TEST_TOKEN'] = $uniqueToken; - } + $env = array_merge(getenv(), $options->fillEnvWithTokens($token)); $finder = new PhpExecutableFinder(); $phpExecutable = $finder->find(); assert($phpExecutable !== false); $bin = escapeshellarg($phpExecutable); - if ($options !== null && ($passthruPhp = $options->passthruPhp()) !== null) { - $bin .= ' ' . implode(' ', $passthruPhp) . ' '; + if (($passthruPhp = $options->passthruPhp()) !== null) { + $bin .= ' ' . implode(' ', $passthruPhp) . ' '; } $bin .= ' ' . escapeshellarg($wrapperBinary); + $parameters = []; $this->configureParameters($parameters); if (count($parameters) > 0) { $bin .= ' ' . implode(' ', array_map('escapeshellarg', $parameters)); } $pipes = []; - if ($options !== null && $options->verbose() > 0) { + if ($options->verbose() > 0) { $this->output->writeln("Starting WrapperWorker via: {$bin}"); } @@ -183,14 +169,6 @@ final public function getCrashReport(): string . $this->readAllStderr(); } - final public function stop(): void - { - $this->doStop(); - fclose($this->pipes[0]); - } - - abstract protected function doStop(): void; - final protected function setExitCode(bool $running, int $exitcode): void { if ($running) { diff --git a/src/Runners/PHPUnit/Worker/RunnerWorker.php b/src/Runners/PHPUnit/Worker/RunnerWorker.php index 87682274..35a7a115 100644 --- a/src/Runners/PHPUnit/Worker/RunnerWorker.php +++ b/src/Runners/PHPUnit/Worker/RunnerWorker.php @@ -20,7 +20,7 @@ final class RunnerWorker /** @var ExecutableTest */ private $executableTest; /** @var Process|null */ - private $process; + public $process; public function __construct(ExecutableTest $executableTest) { @@ -76,12 +76,10 @@ public function getExitCode(): ?int /** * Executes the test by creating a separate process. * - * @param array $options - * @param array $environmentVariables - * @param string[]|null $passthru - * @param string[]|null $passthruPhp - * - * @return $this + * @param array $options + * @param array $environmentVariables + * @param string[]|null $passthru + * @param string[]|null $passthruPhp */ public function run( string $binary, @@ -89,7 +87,7 @@ public function run( array $environmentVariables = [], ?array $passthru = null, ?array $passthruPhp = null - ) { + ): void { $process = $this->getProcess($binary, $options, $environmentVariables, $passthru, $passthruPhp); $cmd = $process->getCommandLine(); @@ -98,18 +96,16 @@ public function run( $this->process = $process; $this->process->start(); - - return $this; } /** * Build the full executable as we would do on the command line, e.g. * php -d zend_extension=xdebug.so vendor/bin/phpunit --teststuite suite1 --prepend xdebug-filter.php. * - * @param array $options - * @param array $environmentVariables - * @param string[]|null $passthru - * @param string[]|null $passthruPhp + * @param array $options + * @param array $environmentVariables + * @param string[]|null $passthru + * @param string[]|null $passthruPhp */ private function getProcess( string $binary, @@ -127,8 +123,6 @@ private function getProcess( $args = array_merge($args, $this->executableTest->commandArguments($binary, $options, $passthru)); - $environmentVariables['PARATEST'] = 1; - return new Process($args, null, $environmentVariables); } diff --git a/src/Runners/PHPUnit/Worker/SqliteWorker.php b/src/Runners/PHPUnit/Worker/SqliteWorker.php index 6bba71a3..faee26d9 100644 --- a/src/Runners/PHPUnit/Worker/SqliteWorker.php +++ b/src/Runners/PHPUnit/Worker/SqliteWorker.php @@ -24,8 +24,4 @@ protected function configureParameters(array &$parameters): void { $parameters[] = $this->dbFileName; } - - protected function doStop(): void - { - } } diff --git a/src/Runners/PHPUnit/Worker/WrapperWorker.php b/src/Runners/PHPUnit/Worker/WrapperWorker.php index d75577ec..ca4324c2 100644 --- a/src/Runners/PHPUnit/Worker/WrapperWorker.php +++ b/src/Runners/PHPUnit/Worker/WrapperWorker.php @@ -10,6 +10,7 @@ use RuntimeException; use function assert; +use function fclose; use function fgets; use function fwrite; use function implode; @@ -20,6 +21,9 @@ final class WrapperWorker extends BaseWorker { + public const COMMAND_EXIT = "EXIT\n"; + public const COMMAND_FINISHED = "FINISHED\n"; + /** @var ExecutableTest|null */ private $currentlyExecuting; @@ -90,9 +94,10 @@ private function checkStarted(): void } } - protected function doStop(): void + public function stop(): void { - fwrite($this->pipes[0], "EXIT\n"); + fwrite($this->pipes[0], self::COMMAND_EXIT); + fclose($this->pipes[0]); } /** @@ -108,7 +113,7 @@ public function waitForFinishedJob(): void $tellsUsItHasFinished = false; stream_set_blocking($this->pipes[1], true); while ($line = fgets($this->pipes[1])) { - if (strstr($line, "FINISHED\n") !== false) { + if (strstr($line, self::COMMAND_FINISHED) !== false) { $tellsUsItHasFinished = true; --$this->inExecution; break; diff --git a/src/Runners/PHPUnit/WrapperRunner.php b/src/Runners/PHPUnit/WrapperRunner.php index 7c2a23d5..4f7aa8d0 100644 --- a/src/Runners/PHPUnit/WrapperRunner.php +++ b/src/Runners/PHPUnit/WrapperRunner.php @@ -17,7 +17,6 @@ use function dirname; use function realpath; use function stream_select; -use function uniqid; use const DIRECTORY_SEPARATOR; @@ -26,6 +25,12 @@ final class WrapperRunner extends BaseWrapperRunner /** @var WrapperWorker[] */ private $workers = []; + /** @var resource[] */ + private $streams = []; + + /** @var resource[] */ + private $modified = []; + public function __construct(Options $opts, OutputInterface $output) { if (defined('PHP_WINDOWS_VERSION_BUILD')) { @@ -35,14 +40,13 @@ public function __construct(Options $opts, OutputInterface $output) parent::__construct($opts, $output); } - public function run(): void + protected function doRun(): void { - $this->initialize(); $this->startWorkers(); $this->assignAllPendingTests(); $this->sendStopMessages(); $this->waitForAllToFinish(); - $this->complete(); + $this->setExitCode(); } private function startWorkers(): void @@ -53,15 +57,8 @@ private function startWorkers(): void assert($wrapper !== false); for ($i = 1; $i <= $this->options->processes(); ++$i) { $worker = new WrapperWorker($this->output); - if ($this->options->noTestTokens()) { - $token = null; - $uniqueToken = null; - } else { - $token = $i; - $uniqueToken = uniqid(); - } - $worker->start($wrapper, $token, $uniqueToken, [], $this->options); + $worker->start($wrapper, $this->options, $i); $this->streams[] = $worker->stdout(); $this->workers[] = $worker; } @@ -71,7 +68,7 @@ private function assignAllPendingTests(): void { $phpunit = $this->options->phpunit(); $phpunitOptions = $this->options->filtered(); - // $phpunitOptions['no-globals-backup'] = null; // removed in phpunit 6.0 + while (count($this->pending)) { $this->waitForStreamsToChange($this->streams); foreach ($this->progressedWorkers() as $key => $worker) { @@ -105,7 +102,7 @@ private function assignAllPendingTests(): void * * @param resource[] $modified */ - private function waitForStreamsToChange(array $modified): int + private function waitForStreamsToChange(array $modified): void { $write = []; $except = []; @@ -115,8 +112,6 @@ private function waitForStreamsToChange(array $modified): int } $this->modified = $modified; - - return $result; } /** @@ -173,7 +168,7 @@ private function waitForAllToFinish(): void $toStop = $this->workers; while (count($toStop) > 0) { $toCheck = $this->streamsOf($toStop); - $new = $this->waitForStreamsToChange($toCheck); + $this->waitForStreamsToChange($toCheck); foreach ($this->progressedWorkers() as $index => $worker) { try { if (! $worker->isRunning()) { diff --git a/test/Functional/Runners/PHPUnit/WorkerTest.php b/test/Functional/Runners/PHPUnit/WorkerTest.php index 7908e762..21d9bd84 100644 --- a/test/Functional/Runners/PHPUnit/WorkerTest.php +++ b/test/Functional/Runners/PHPUnit/WorkerTest.php @@ -4,6 +4,7 @@ namespace ParaTest\Tests\Functional\Runners\PHPUnit; +use ParaTest\Runners\PHPUnit\Options; use ParaTest\Runners\PHPUnit\Worker\WrapperWorker; use ParaTest\Tests\TestBase; use ReflectionProperty; @@ -23,24 +24,21 @@ */ final class WorkerTest extends TestBase { - /** @var string[][] */ - protected static $descriptorspec = [ - 0 => ['pipe', 'r'], - 1 => ['pipe', 'w'], - 2 => ['pipe', 'w'], - ]; /** @var string */ - protected $bootstrap; + private $bootstrap; /** @var string */ private $phpunitWrapper; /** @var BufferedOutput */ private $output; + /** @var Options */ + private $options; public function setUpTest(): void { $this->bootstrap = PARATEST_ROOT . DS . 'test' . DS . 'bootstrap.php'; $this->phpunitWrapper = PARATEST_ROOT . DS . 'bin' . DS . 'phpunit-wrapper.php'; $this->output = new BufferedOutput(); + $this->options = $this->createOptionsFromArgv([]); } public function tearDown(): void @@ -66,7 +64,7 @@ public function testReadsAPHPUnitCommandFromStdInAndExecutesItItsOwnProcess(): v $testLog = TMP_DIR . DS . 'test.xml'; $testCmd = $this->getCommand('passing-tests' . DS . 'TestOfUnits.php', $testLog); $worker = new WrapperWorker($this->output); - $worker->start($this->phpunitWrapper); + $worker->start($this->phpunitWrapper, $this->options, 1); $worker->execute($testCmd); $worker->stop(); @@ -83,7 +81,7 @@ public function testKnowsWhenAJobIsFinished(): void $testLog = TMP_DIR . DS . 'test.xml'; $testCmd = $this->getCommand('passing-tests' . DS . 'TestOfUnits.php', $testLog); $worker = new WrapperWorker($this->output); - $worker->start($this->phpunitWrapper); + $worker->start($this->phpunitWrapper, $this->options, 1); $worker->execute($testCmd); $worker->waitForFinishedJob(); @@ -98,7 +96,7 @@ public function testTellsWhenItsFree(): void $testLog = TMP_DIR . DS . 'test.xml'; $testCmd = $this->getCommand('passing-tests' . DS . 'TestOfUnits.php', $testLog); $worker = new WrapperWorker($this->output); - $worker->start($this->phpunitWrapper); + $worker->start($this->phpunitWrapper, $this->options, 1); static::assertTrue($worker->isFree()); $worker->execute($testCmd); @@ -116,7 +114,7 @@ public function testTellsWhenItsStopped(): void $worker = new WrapperWorker($this->output); static::assertFalse($worker->isRunning()); - $worker->start($this->phpunitWrapper); + $worker->start($this->phpunitWrapper, $this->options, 1); static::assertTrue($worker->isRunning()); $worker->stop(); @@ -171,10 +169,8 @@ private function setPerReflection(object $instance, string $property, $value): v public function testCanExecuteMultiplePHPUnitCommands(): void { - $bin = 'bin/phpunit-wrapper.php'; - $worker = new WrapperWorker($this->output); - $worker->start($this->phpunitWrapper); + $worker->start($this->phpunitWrapper, $this->options, 1); $testLog = TMP_DIR . DS . 'test.xml'; $testCmd = $this->getCommand('passing-tests' . DS . 'TestOfUnits.php', $testLog); diff --git a/test/Functional/SqliteRunnerTest.php b/test/Functional/SqliteRunnerTest.php deleted file mode 100644 index 662ab53c..00000000 --- a/test/Functional/SqliteRunnerTest.php +++ /dev/null @@ -1,90 +0,0 @@ -guardSqliteExtensionLoaded(); - parent::setUp(); - } - - public function testResultsAreCorrect(): void - { - $generator = new TestGenerator(); - $generator->generate(self::TEST_CLASSES, self::TEST_METHODS_PER_CLASS); - - $proc = $this->invokeParatest($generator->path, ['--processes' => 3, '--runner' => SqliteRunner::class]); - - $expected = self::TEST_CLASSES * self::TEST_METHODS_PER_CLASS; - $this->assertTestsPassed($proc, (string) $expected, (string) $expected); - } - - public function testRunningFewerTestsThanTheWorkersIsPossible(): void - { - $generator = new TestGenerator(); - $generator->generate(1, 1); - - $proc = $this->invokeParatest($generator->path, ['--processes' => 2, '--runner' => SqliteRunner::class]); - - $this->assertTestsPassed($proc, '1', '1'); - } - - public function testExitCodes(): void - { - $options = ['--processes' => 1, '--runner' => SqliteRunner::class]; - $proc = $this->invokeParatest( - 'wrapper-runner-exit-code-tests/ErrorTest.php', - $options - ); - $output = $proc->getOutput(); - - static::assertStringContainsString('Tests: 1', $output); - static::assertStringContainsString('Failures: 0', $output); - static::assertStringContainsString('Errors: 1', $output); - static::assertEquals(2, $proc->getExitCode()); - - $proc = $this->invokeParatest( - 'wrapper-runner-exit-code-tests/FailureTest.php', - $options - ); - $output = $proc->getOutput(); - - static::assertStringContainsString('Tests: 1', $output); - static::assertStringContainsString('Failures: 1', $output); - static::assertStringContainsString('Errors: 0', $output); - static::assertEquals(1, $proc->getExitCode()); - - $proc = $this->invokeParatest( - 'wrapper-runner-exit-code-tests/SuccessTest.php', - $options - ); - $output = $proc->getOutput(); - - static::assertStringContainsString('OK (1 test, 1 assertion)', $output); - static::assertEquals(0, $proc->getExitCode()); - - $options['--processes'] = 3; - $proc = $this->invokeParatest( - 'wrapper-runner-exit-code-tests', - $options - ); - $output = $proc->getOutput(); - static::assertStringContainsString('Tests: 3', $output); - static::assertStringContainsString('Failures: 1', $output); - static::assertStringContainsString('Errors: 1', $output); - static::assertEquals(2, $proc->getExitCode()); // There is at least one error so the exit code must be 2 - } -} diff --git a/test/TestBase.php b/test/TestBase.php index cf3c3866..8623cb57 100644 --- a/test/TestBase.php +++ b/test/TestBase.php @@ -6,6 +6,7 @@ use InvalidArgumentException; use ParaTest\Runners\PHPUnit\Options; +use ParaTest\Tests\Functional\RunnerResult; use PHPUnit; use PHPUnit\Framework\SkippedTestError; use PHPUnit\Runner\Version; @@ -24,6 +25,7 @@ use function get_class; use function glob; use function preg_match; +use function sprintf; use function str_replace; use function uniqid; @@ -56,6 +58,22 @@ final protected function createOptionsFromArgv(array $argv, ?string $cwd = null) return Options::fromConsoleInput($input, $cwd ?? PARATEST_ROOT); } + final protected function assertTestsPassed( + RunnerResult $proc, + ?string $testPattern = null, + ?string $assertionPattern = null + ): void { + static::assertMatchesRegularExpression( + sprintf( + '/OK \(%s tests?, %s assertions?\)/', + $testPattern ?? '\d+', + $assertionPattern ?? '\d+' + ), + $proc->getOutput(), + ); + static::assertEquals(0, $proc->getExitCode()); + } + /** * Get PHPUnit version. */ diff --git a/test/Unit/Runners/PHPUnit/EmptyRunnerStub.php b/test/Unit/Runners/PHPUnit/EmptyRunnerStub.php index 7ab100fe..dbb9f9b2 100644 --- a/test/Unit/Runners/PHPUnit/EmptyRunnerStub.php +++ b/test/Unit/Runners/PHPUnit/EmptyRunnerStub.php @@ -4,19 +4,36 @@ namespace ParaTest\Tests\Unit\Runners\PHPUnit; -use ParaTest\Runners\PHPUnit\BaseRunner; +use ParaTest\Runners\PHPUnit\Options; +use ParaTest\Runners\PHPUnit\RunnerInterface; +use Symfony\Component\Console\Output\OutputInterface; -final class EmptyRunnerStub extends BaseRunner +final class EmptyRunnerStub implements RunnerInterface { public const OUTPUT = 'EmptyRunnerStub EXECUTED'; + /** @var Options */ + private $options; + /** @var OutputInterface */ + private $output; + + public function __construct(Options $options, OutputInterface $output) + { + $this->options = $options; + $this->output = $output; + } public function run(): void { - $this->printer->start(); - $this->output->write(self::OUTPUT); + $this->output->writeln('Path: ' . $this->options->path()); + $this->output->writeln('Configuration: ' . (($conf = $this->options->configuration()) !== null + ? $conf->filename() + : '' + )); + $this->output->writeln(self::OUTPUT); } - protected function beforeLoadChecks(): void + public function getExitCode(): int { + return 0; } } diff --git a/test/Unit/Runners/PHPUnit/OptionsTest.php b/test/Unit/Runners/PHPUnit/OptionsTest.php index 1debccf1..372d50f2 100644 --- a/test/Unit/Runners/PHPUnit/OptionsTest.php +++ b/test/Unit/Runners/PHPUnit/OptionsTest.php @@ -12,6 +12,7 @@ use function defined; use function file_put_contents; use function intdiv; +use function mt_rand; use function sort; use function sys_get_temp_dir; @@ -270,4 +271,28 @@ public function testProvidedOptions(): void static::assertTrue($options->hasCoverage()); } + + public function testFillEnvWithTokens(): void + { + $options = $this->createOptionsFromArgv(['--no-test-tokens' => false]); + + $inc = mt_rand(10, 99); + $env = $options->fillEnvWithTokens($inc); + + static::assertSame(1, $env['PARATEST']); + static::assertArrayHasKey(Options::ENV_KEY_TOKEN, $env); + static::assertSame($inc, $env[Options::ENV_KEY_TOKEN]); + static::assertArrayHasKey(Options::ENV_KEY_UNIQUE_TOKEN, $env); + static::assertIsString($env[Options::ENV_KEY_UNIQUE_TOKEN]); + static::assertStringContainsString($inc . '_', $env[Options::ENV_KEY_UNIQUE_TOKEN]); + + $options = $this->createOptionsFromArgv(['--no-test-tokens' => true]); + + $inc = mt_rand(10, 99); + $env = $options->fillEnvWithTokens($inc); + + static::assertSame(1, $env['PARATEST']); + static::assertArrayNotHasKey(Options::ENV_KEY_TOKEN, $env); + static::assertArrayNotHasKey(Options::ENV_KEY_UNIQUE_TOKEN, $env); + } } diff --git a/test/Unit/Runners/PHPUnit/ResultPrinterTest.php b/test/Unit/Runners/PHPUnit/ResultPrinterTest.php index 977074fc..361d644f 100644 --- a/test/Unit/Runners/PHPUnit/ResultPrinterTest.php +++ b/test/Unit/Runners/PHPUnit/ResultPrinterTest.php @@ -65,15 +65,6 @@ public function testAddTestShouldAddTest(): void static::assertSame([$suite], $this->getObjectValue($this->printer, 'suites')); } - public function testAddTestReturnsSelf(): void - { - $suite = new Suite('/path/to/ResultSuite.php', [], false, TMP_DIR); - - $self = $this->printer->addTest($suite); - - static::assertSame($this->printer, $self); - } - public function testStartPrintsOptionInfo(): void { $contents = $this->getStartOutput(); @@ -161,8 +152,8 @@ public function testAddTestMethodIncrementsCountByOne(): void public function testGetHeader(): void { - $this->printer->addTest($this->errorSuite) - ->addTest($this->failureSuite); + $this->printer->addTest($this->errorSuite); + $this->printer->addTest($this->failureSuite); $this->prepareReaders(); @@ -178,8 +169,8 @@ public function testGetHeader(): void public function testGetErrorsSingleError(): void { - $this->printer->addTest($this->errorSuite) - ->addTest($this->failureSuite); + $this->printer->addTest($this->errorSuite); + $this->printer->addTest($this->failureSuite); $this->prepareReaders(); @@ -195,8 +186,8 @@ public function testGetErrorsSingleError(): void public function testGetErrorsMultipleErrors(): void { - $this->printer->addTest($this->errorSuite) - ->addTest($this->otherErrorSuite); + $this->printer->addTest($this->errorSuite); + $this->printer->addTest($this->otherErrorSuite); $this->prepareReaders(); @@ -237,8 +228,8 @@ public function testGetFailures(): void public function testGetFooterWithFailures(): void { - $this->printer->addTest($this->errorSuite) - ->addTest($this->mixedSuite); + $this->printer->addTest($this->errorSuite); + $this->printer->addTest($this->mixedSuite); $this->prepareReaders(); diff --git a/test/Unit/Runners/PHPUnit/RunnerTest.php b/test/Unit/Runners/PHPUnit/RunnerTest.php index 40e3a45c..e803e5e9 100644 --- a/test/Unit/Runners/PHPUnit/RunnerTest.php +++ b/test/Unit/Runners/PHPUnit/RunnerTest.php @@ -15,7 +15,7 @@ use function uniqid; /** - * @coversNothing + * @covers \ParaTest\Runners\PHPUnit\Runner */ final class RunnerTest extends TestBase { diff --git a/test/Unit/Runners/PHPUnit/SqliteRunnerTest.php b/test/Unit/Runners/PHPUnit/SqliteRunnerTest.php new file mode 100644 index 00000000..7713c310 --- /dev/null +++ b/test/Unit/Runners/PHPUnit/SqliteRunnerTest.php @@ -0,0 +1,100 @@ + */ + private $bareOptions; + + protected function setUpTest(): void + { + if (! extension_loaded('pdo_sqlite')) { + static::markTestSkipped('Skipping test: Extension pdo_sqlite not found.'); + } + + $this->bareOptions = ['--tmp-dir' => TMP_DIR]; + } + + private function runSqliteRunner(): RunnerResult + { + $output = new BufferedOutput(); + $sqliteRunner = new SqliteRunner($this->createOptionsFromArgv($this->bareOptions), $output); + $sqliteRunner->run(); + + return new RunnerResult($sqliteRunner->getExitCode(), $output->fetch()); + } + + public function testResultsAreCorrect(): void + { + $this->bareOptions['--path'] = $this->fixture('passing-tests' . DS . 'GroupsTest.php'); + + $this->assertTestsPassed($this->runSqliteRunner()); + } + + public function testRunningFewerTestsThanTheWorkersIsPossible(): void + { + $this->bareOptions['--path'] = $this->fixture('passing-tests' . DS . 'GroupsTest.php'); + $this->bareOptions['--processes'] = 2; + + $this->assertTestsPassed($this->runSqliteRunner()); + } + + public function testExitCodes(): void + { + $this->bareOptions['--path'] = $this->fixture('wrapper-runner-exit-code-tests' . DS . 'ErrorTest.php'); + $runnerResult = $this->runSqliteRunner(); + + static::assertStringContainsString('Tests: 1', $runnerResult->getOutput()); + static::assertStringContainsString('Failures: 0', $runnerResult->getOutput()); + static::assertStringContainsString('Errors: 1', $runnerResult->getOutput()); + static::assertEquals(TestRunner::EXCEPTION_EXIT, $runnerResult->getExitCode()); + + $this->bareOptions['--path'] = $this->fixture('wrapper-runner-exit-code-tests' . DS . 'FailureTest.php'); + $runnerResult = $this->runSqliteRunner(); + + static::assertStringContainsString('Tests: 1', $runnerResult->getOutput()); + static::assertStringContainsString('Failures: 1', $runnerResult->getOutput()); + static::assertStringContainsString('Errors: 0', $runnerResult->getOutput()); + static::assertEquals(TestRunner::FAILURE_EXIT, $runnerResult->getExitCode()); + + $this->bareOptions['--path'] = $this->fixture('wrapper-runner-exit-code-tests' . DS . 'SuccessTest.php'); + $runnerResult = $this->runSqliteRunner(); + + static::assertStringContainsString('OK (1 test, 1 assertion)', $runnerResult->getOutput()); + static::assertEquals(TestRunner::SUCCESS_EXIT, $runnerResult->getExitCode()); + + $this->bareOptions['--path'] = $this->fixture('wrapper-runner-exit-code-tests'); + $runnerResult = $this->runSqliteRunner(); + + static::assertStringContainsString('Tests: 3', $runnerResult->getOutput()); + static::assertStringContainsString('Failures: 1', $runnerResult->getOutput()); + static::assertStringContainsString('Errors: 1', $runnerResult->getOutput()); + static::assertEquals(TestRunner::EXCEPTION_EXIT, $runnerResult->getExitCode()); + } + + public function testRaiseExceptionWhenATestCallsExit(): void + { + $this->bareOptions['--path'] = $this->fixture('exit-tests' . DS . 'UnitTestThatExitsSilentlyTest.php'); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessageMatches('/UnitTestThatExitsSilentlyTest/'); + + $this->runSqliteRunner(); + } +} diff --git a/test/fixtures/exit-tests/UnitTestThatExitsLoudlyTest.php b/test/fixtures/exit-tests/UnitTestThatExitsLoudlyTest.php new file mode 100644 index 00000000..931025f1 --- /dev/null +++ b/test/fixtures/exit-tests/UnitTestThatExitsLoudlyTest.php @@ -0,0 +1,11 @@ + + + + + ./fatal-tests/ + + + From 54a909d17a0374a637a48582e5d4c120b0e9f4ba Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Wed, 19 Aug 2020 12:19:26 +0200 Subject: [PATCH 15/19] WrapperRunner + WrapperWorker ok --- src/Coverage/CoverageMerger.php | 5 +- src/Coverage/EmptyCoverageFileException.php | 11 ++ src/Runners/PHPUnit/BaseWrapperRunner.php | 4 +- src/Runners/PHPUnit/Options.php | 4 + src/Runners/PHPUnit/Worker/BaseWorker.php | 13 +- src/Runners/PHPUnit/Worker/WrapperWorker.php | 22 ++- src/Runners/PHPUnit/WrapperRunner.php | 60 +++----- .../Functional/Runners/PHPUnit/WorkerTest.php | 19 +-- test/Functional/WrapperRunnerTest.php | 116 --------------- test/Unit/Runners/PHPUnit/OptionsTest.php | 9 ++ .../Unit/Runners/PHPUnit/SqliteRunnerTest.php | 2 + .../PHPUnit/WrapperRunnerOnWindowsTest.php | 27 ++++ .../Runners/PHPUnit/WrapperRunnerTest.php | 135 ++++++++++++++++-- test/fixtures/parallel-suite/ParallelBase.php | 2 +- 14 files changed, 220 insertions(+), 209 deletions(-) create mode 100644 src/Coverage/EmptyCoverageFileException.php delete mode 100644 test/Functional/WrapperRunnerTest.php create mode 100644 test/Unit/Runners/PHPUnit/WrapperRunnerOnWindowsTest.php diff --git a/src/Coverage/CoverageMerger.php b/src/Coverage/CoverageMerger.php index 26d695bb..e570362d 100644 --- a/src/Coverage/CoverageMerger.php +++ b/src/Coverage/CoverageMerger.php @@ -4,7 +4,6 @@ namespace ParaTest\Coverage; -use RuntimeException; use SebastianBergmann\CodeCoverage\CodeCoverage; use SebastianBergmann\CodeCoverage\ProcessedCodeCoverageData; use SebastianBergmann\Environment\Runtime; @@ -42,8 +41,6 @@ private function addCoverage(CodeCoverage $coverage): void * Adds the coverage contained in $coverageFile and deletes the file afterwards. * * @param string $coverageFile Code coverage file - * - * @throws RuntimeException When coverage file is empty. */ public function addCoverageFromFile(string $coverageFile): void { @@ -55,7 +52,7 @@ public function addCoverageFromFile(string $coverageFile): void // @codeCoverageIgnoreEnd } - throw new RuntimeException("Coverage file {$coverageFile} is empty. " . $extra); + throw new EmptyCoverageFileException("Coverage file {$coverageFile} is empty. " . $extra); } /** @psalm-suppress UnresolvableInclude **/ diff --git a/src/Coverage/EmptyCoverageFileException.php b/src/Coverage/EmptyCoverageFileException.php new file mode 100644 index 00000000..cbada723 --- /dev/null +++ b/src/Coverage/EmptyCoverageFileException.php @@ -0,0 +1,11 @@ +options->functional()) { - throw new RuntimeException( + throw new InvalidArgumentException( 'The `functional` option is not supported yet in the WrapperRunner. Only full classes can be run due ' . 'to the current PHPUnit commands causing classloading issues.' ); diff --git a/src/Runners/PHPUnit/Options.php b/src/Runners/PHPUnit/Options.php index 48e230c8..8afc9bd5 100644 --- a/src/Runners/PHPUnit/Options.php +++ b/src/Runners/PHPUnit/Options.php @@ -323,6 +323,10 @@ public static function fromConsoleInput(InputInterface $input, string $cwd): sel $filtered['exclude-group'] = implode(',', $excludeGroup); } + if ($options['whitelist'] !== null) { + $filtered['whitelist'] = $options['whitelist']; + } + return new self( $options['bootstrap'], $options['colors'], diff --git a/src/Runners/PHPUnit/Worker/BaseWorker.php b/src/Runners/PHPUnit/Worker/BaseWorker.php index ac17e98a..f24c4051 100644 --- a/src/Runners/PHPUnit/Worker/BaseWorker.php +++ b/src/Runners/PHPUnit/Worker/BaseWorker.php @@ -93,7 +93,7 @@ final public function start( // Taken from \Symfony\Component\Process\Process::prepareWindowsCommandLine // Needed to handle spaces in the binary path, boring to test in CI if (DIRECTORY_SEPARATOR === '\\') { - $bin = sprintf('cmd /V:ON /E:ON /D /C (%s)', $bin); + $bin = sprintf('cmd /V:ON /E:ON /D /C (%s)', $bin); // @codeCoverageIgnore } $process = proc_open($bin, self::$descriptorspec, $pipes, null, $env); @@ -159,9 +159,10 @@ final public function checkNotCrashed(): void final public function getCrashReport(): string { - $lastCommand = count($this->commands) !== 0 ? ' Last executed command: ' . end($this->commands) : ''; + $lastCommand = count($this->commands) !== 0 ? 'Last executed command: ' . end($this->commands) : ''; - return 'This worker has crashed.' . $lastCommand . PHP_EOL + return 'This worker has crashed.' . PHP_EOL + . $lastCommand . PHP_EOL . 'Output:' . PHP_EOL . '----------------------' . PHP_EOL . $this->alreadyReadOutput . PHP_EOL @@ -171,11 +172,7 @@ final public function getCrashReport(): string final protected function setExitCode(bool $running, int $exitcode): void { - if ($running) { - return; - } - - if ($this->exitCode !== null) { + if ($running || $this->exitCode !== null) { return; } diff --git a/src/Runners/PHPUnit/Worker/WrapperWorker.php b/src/Runners/PHPUnit/Worker/WrapperWorker.php index ca4324c2..a38239c9 100644 --- a/src/Runners/PHPUnit/Worker/WrapperWorker.php +++ b/src/Runners/PHPUnit/Worker/WrapperWorker.php @@ -9,6 +9,7 @@ use ParaTest\Runners\PHPUnit\ResultPrinter; use RuntimeException; +use function array_map; use function assert; use function fclose; use function fgets; @@ -47,8 +48,7 @@ public function stdout() */ public function execute(array $testCmdArguments): void { - $this->checkStarted(); - $this->commands[] = implode(' ', $testCmdArguments); + $this->commands[] = implode(' ', array_map('escapeshellarg', $testCmdArguments)); fwrite($this->pipes[0], serialize($testCmdArguments) . "\n"); ++$this->inExecution; } @@ -58,10 +58,7 @@ public function execute(array $testCmdArguments): void */ public function assign(ExecutableTest $test, string $phpunit, array $phpunitOptions, Options $options): void { - if ($this->currentlyExecuting !== null) { - throw new RuntimeException('Worker already has a test assigned - did you forget to call reset()?'); - } - + assert($this->currentlyExecuting === null); $this->currentlyExecuting = $test; $commandArguments = $test->commandArguments($phpunit, $phpunitOptions, $options->passthru()); $command = implode(' ', $commandArguments); @@ -87,13 +84,6 @@ public function reset(): void $this->currentlyExecuting = null; } - private function checkStarted(): void - { - if (! $this->isStarted()) { - throw new RuntimeException('You have to start the Worker first!'); - } - } - public function stop(): void { fwrite($this->pipes[0], self::COMMAND_EXIT); @@ -101,6 +91,10 @@ public function stop(): void } /** + * @internal + * + * @codeCoverageIgnore + * * This is an utility function for tests. * Refactor or write it only in the test case. */ @@ -128,6 +122,8 @@ public function waitForFinishedJob(): void /** * @internal * + * @codeCoverageIgnore + * * This function consumes a lot of CPU while waiting for * the worker to finish. Use it only in testing paratest * itself. diff --git a/src/Runners/PHPUnit/WrapperRunner.php b/src/Runners/PHPUnit/WrapperRunner.php index 4f7aa8d0..7f480a6c 100644 --- a/src/Runners/PHPUnit/WrapperRunner.php +++ b/src/Runners/PHPUnit/WrapperRunner.php @@ -4,10 +4,10 @@ namespace ParaTest\Runners\PHPUnit; +use ParaTest\Coverage\EmptyCoverageFileException; use ParaTest\Runners\PHPUnit\Worker\WrapperWorker; use RuntimeException; use Symfony\Component\Console\Output\OutputInterface; -use Throwable; use function array_keys; use function array_shift; @@ -34,7 +34,7 @@ final class WrapperRunner extends BaseWrapperRunner public function __construct(Options $opts, OutputInterface $output) { if (defined('PHP_WINDOWS_VERSION_BUILD')) { - throw new RuntimeException('WrapperRunner is not supported on Windows'); + throw new RuntimeException('WrapperRunner is not supported on Windows'); // @codeCoverageIgnore } parent::__construct($opts, $output); @@ -76,23 +76,13 @@ private function assignAllPendingTests(): void continue; } - try { - $this->flushWorker($worker); - $pending = array_shift($this->pending); - if ($pending !== null) { - $worker->assign($pending, $phpunit, $phpunitOptions, $this->options); - } - } catch (Throwable $e) { - if ($this->options->verbose() > 0) { - $worker->stop(); - $this->output->writeln( - "Error while assigning pending tests for worker {$key}: {$e->getMessage()}" - ); - $this->output->write($worker->getCrashReport()); - } - - throw $e; + $this->flushWorker($worker); + $pending = array_shift($this->pending); + if ($pending === null) { + continue; } + + $worker->assign($pending, $phpunit, $phpunitOptions, $this->options); } } } @@ -107,9 +97,7 @@ private function waitForStreamsToChange(array $modified): void $write = []; $except = []; $result = stream_select($modified, $write, $except, 1); - if ($result === false) { - throw new RuntimeException('stream_select() returned an error while waiting for all workers to finish.'); - } + assert($result !== false); $this->modified = $modified; } @@ -146,10 +134,13 @@ private function flushWorker(WrapperWorker $worker): void if ($this->hasCoverage()) { $coverageMerger = $this->getCoverage(); assert($coverageMerger !== null); - $coverageFileName = $worker->getCoverageFileName(); - assert($coverageFileName !== null); - - $coverageMerger->addCoverageFromFile($coverageFileName); + if (($coverageFileName = $worker->getCoverageFileName()) !== null) { + try { + $coverageMerger->addCoverageFromFile($coverageFileName); + } catch (EmptyCoverageFileException $emptyCoverageFileException) { + throw new RuntimeException($worker->getCrashReport(), 0, $emptyCoverageFileException); + } + } } $worker->printFeedback($this->printer); @@ -170,21 +161,12 @@ private function waitForAllToFinish(): void $toCheck = $this->streamsOf($toStop); $this->waitForStreamsToChange($toCheck); foreach ($this->progressedWorkers() as $index => $worker) { - try { - if (! $worker->isRunning()) { - $this->flushWorker($worker); - unset($toStop[$index]); - } - } catch (Throwable $e) { - if ($this->options->verbose() > 0) { - $worker->stop(); - unset($toStop[$index]); - $this->output->writeln("Error while waiting to finish for worker {$index}: {$e->getMessage()}"); - $this->output->write($worker->getCrashReport()); - } - - throw $e; + if ($worker->isRunning()) { + continue; } + + $this->flushWorker($worker); + unset($toStop[$index]); } } } diff --git a/test/Functional/Runners/PHPUnit/WorkerTest.php b/test/Functional/Runners/PHPUnit/WorkerTest.php index 21d9bd84..9edff9a8 100644 --- a/test/Functional/Runners/PHPUnit/WorkerTest.php +++ b/test/Functional/Runners/PHPUnit/WorkerTest.php @@ -12,15 +12,13 @@ use Symfony\Component\Console\Output\BufferedOutput; use function count; -use function file_exists; use function file_get_contents; use function get_class; use function proc_get_status; use function proc_open; -use function unlink; /** - * @coversNothing + * @covers \ParaTest\Runners\PHPUnit\Worker\BaseWorker */ final class WorkerTest extends TestBase { @@ -41,21 +39,6 @@ public function setUpTest(): void $this->options = $this->createOptionsFromArgv([]); } - public function tearDown(): void - { - $this->deleteIfExists(TMP_DIR . DS . 'test.xml'); - $this->deleteIfExists(TMP_DIR . DS . 'test2.xml'); - } - - private function deleteIfExists(string $file): void - { - if (! file_exists($file)) { - return; - } - - unlink($file); - } - /** * @requires OSFAMILY Linux */ diff --git a/test/Functional/WrapperRunnerTest.php b/test/Functional/WrapperRunnerTest.php deleted file mode 100644 index 2ff95168..00000000 --- a/test/Functional/WrapperRunnerTest.php +++ /dev/null @@ -1,116 +0,0 @@ -generate(self::TEST_CLASSES, self::TEST_METHODS_PER_CLASS); - - $proc = $this->invokeParatest($generator->path, ['--processes' => 3, '--runner' => WrapperRunner::class]); - - $expected = self::TEST_CLASSES * self::TEST_METHODS_PER_CLASS; - $this->assertTestsPassed($proc, (string) $expected, (string) $expected); - } - - public function testRunningFewerTestsThanTheWorkersIsPossible(): void - { - $generator = new TestGenerator(); - $generator->generate(1, 1); - - $proc = $this->invokeParatest($generator->path, ['--processes' => 2, '--runner' => WrapperRunner::class]); - - $this->assertTestsPassed($proc, '1', '1'); - } - - public function testExitCodes(): void - { - $options = ['--processes' => 1, '--runner' => WrapperRunner::class]; - $proc = $this->invokeParatest( - 'wrapper-runner-exit-code-tests/ErrorTest.php', - $options - ); - $output = $proc->getOutput(); - - static::assertStringContainsString('Tests: 1', $output); - static::assertStringContainsString('Failures: 0', $output); - static::assertStringContainsString('Errors: 1', $output); - static::assertEquals(2, $proc->getExitCode()); - - $proc = $this->invokeParatest( - 'wrapper-runner-exit-code-tests/FailureTest.php', - $options - ); - $output = $proc->getOutput(); - - static::assertStringContainsString('Tests: 1', $output); - static::assertStringContainsString('Failures: 1', $output); - static::assertStringContainsString('Errors: 0', $output); - static::assertEquals(1, $proc->getExitCode()); - - $proc = $this->invokeParatest( - 'wrapper-runner-exit-code-tests/SuccessTest.php', - $options - ); - $output = $proc->getOutput(); - - static::assertStringContainsString('OK (1 test, 1 assertion)', $output); - static::assertEquals(0, $proc->getExitCode()); - - $options['--processes'] = 3; - $proc = $this->invokeParatest( - 'wrapper-runner-exit-code-tests', - $options - ); - $output = $proc->getOutput(); - static::assertStringContainsString('Tests: 3', $output); - static::assertStringContainsString('Failures: 1', $output); - static::assertStringContainsString('Errors: 1', $output); - static::assertEquals(2, $proc->getExitCode()); // There is at least one error so the exit code must be 2 - } - - public function testParallelSuiteOption(): void - { - $testDir = TMP_DIR . DS . 'parallel-suite'; - if (! is_dir($testDir)) { - mkdir($testDir); - } - - $glob = glob($testDir . DS . '*'); - self::assertNotFalse($glob); - foreach ($glob as $file) { - unlink($file); - } - - $proc = $this->invokeParatest( - null, - [ - '--configuration' => $this->fixture('phpunit-parallel-suite.xml'), - '--parallel-suite' => true, - '--processes' => 2, - '--runner' => WrapperRunner::class, - ] - ); - - $this->assertTestsPassed($proc); - } -} diff --git a/test/Unit/Runners/PHPUnit/OptionsTest.php b/test/Unit/Runners/PHPUnit/OptionsTest.php index 372d50f2..b6b015e9 100644 --- a/test/Unit/Runners/PHPUnit/OptionsTest.php +++ b/test/Unit/Runners/PHPUnit/OptionsTest.php @@ -184,6 +184,7 @@ public function testDefaultOptions(): void static::assertNull($options->coverageXml()); static::assertEmpty($options->excludeGroup()); static::assertNull($options->filter()); + static::assertEmpty($options->filtered()); static::assertFalse($options->functional()); static::assertEmpty($options->group()); static::assertNull($options->logJunit()); @@ -269,6 +270,14 @@ public function testProvidedOptions(): void static::assertSame(1, $options->verbose()); static::assertSame('WHITELIST', $options->whitelist()); + static::assertSame([ + 'bootstrap' => 'BOOTSTRAP', + 'configuration' => $options->configuration()->filename(), + 'group' => 'GROUP', + 'exclude-group' => 'EXCLUDE-GROUP', + 'whitelist' => 'WHITELIST', + ], $options->filtered()); + static::assertTrue($options->hasCoverage()); } diff --git a/test/Unit/Runners/PHPUnit/SqliteRunnerTest.php b/test/Unit/Runners/PHPUnit/SqliteRunnerTest.php index 7713c310..2318b3d4 100644 --- a/test/Unit/Runners/PHPUnit/SqliteRunnerTest.php +++ b/test/Unit/Runners/PHPUnit/SqliteRunnerTest.php @@ -14,7 +14,9 @@ use function extension_loaded; /** + * @covers \ParaTest\Runners\PHPUnit\BaseWrapperRunner * @covers \ParaTest\Runners\PHPUnit\SqliteRunner + * @covers \ParaTest\Runners\PHPUnit\Worker\BaseWorker * @covers \ParaTest\Runners\PHPUnit\Worker\SqliteWorker */ final class SqliteRunnerTest extends TestBase diff --git a/test/Unit/Runners/PHPUnit/WrapperRunnerOnWindowsTest.php b/test/Unit/Runners/PHPUnit/WrapperRunnerOnWindowsTest.php new file mode 100644 index 00000000..fbcd6bc8 --- /dev/null +++ b/test/Unit/Runners/PHPUnit/WrapperRunnerOnWindowsTest.php @@ -0,0 +1,27 @@ +createOptionsFromArgv([]); + $output = new BufferedOutput(); + + $this->expectException(RuntimeException::class); + + new WrapperRunner($options, $output); + } +} diff --git a/test/Unit/Runners/PHPUnit/WrapperRunnerTest.php b/test/Unit/Runners/PHPUnit/WrapperRunnerTest.php index f50d7a44..84e0f4ea 100644 --- a/test/Unit/Runners/PHPUnit/WrapperRunnerTest.php +++ b/test/Unit/Runners/PHPUnit/WrapperRunnerTest.php @@ -4,26 +4,145 @@ namespace ParaTest\Tests\Unit\Runners\PHPUnit; +use InvalidArgumentException; use ParaTest\Runners\PHPUnit\WrapperRunner; +use ParaTest\Tests\Functional\RunnerResult; use ParaTest\Tests\TestBase; +use PHPUnit\TextUI\TestRunner; use RuntimeException; +use SebastianBergmann\CodeCoverage\CodeCoverage; use Symfony\Component\Console\Output\BufferedOutput; +use function array_merge; +use function is_file; +use function is_string; +use function uniqid; +use function unlink; + /** - * @coversNothing + * @requires OSFAMILY Linux + * @covers \ParaTest\Runners\PHPUnit\BaseWrapperRunner + * @covers \ParaTest\Runners\PHPUnit\WrapperRunner + * @covers \ParaTest\Runners\PHPUnit\Worker\BaseWorker + * @covers \ParaTest\Runners\PHPUnit\Worker\WrapperWorker */ final class WrapperRunnerTest extends TestBase { - /** - * @requires OSFAMILY Windows - */ - public function testWrapperRunnerCannotBeUsedOnWindows(): void + /** @var array */ + private $bareOptions; + + protected function setUpTest(): void + { + $this->bareOptions = [ + '--tmp-dir' => TMP_DIR, + '--coverage-php' => TMP_DIR . DS . uniqid('result_'), + ]; + } + + private function runWrapperRunner(): RunnerResult + { + $output = new BufferedOutput(); + $wrapperRunner = new WrapperRunner($this->createOptionsFromArgv($this->bareOptions), $output); + $wrapperRunner->run(); + + return new RunnerResult($wrapperRunner->getExitCode(), $output->fetch()); + } + + public function testWrapperRunnerNotAvailableInFunctionalMode(): void + { + $this->bareOptions['--path'] = $this->fixture('passing-tests' . DS . 'GroupsTest.php'); + $this->bareOptions['--functional'] = true; + + $this->expectException(InvalidArgumentException::class); + + $this->runWrapperRunner(); + } + + public function testResultsAreCorrect(): void + { + $this->bareOptions['--path'] = $this->fixture('passing-tests' . DS . 'GroupsTest.php'); + + $this->assertTestsPassed($this->runWrapperRunner()); + + $coveragePhp = include $this->bareOptions['--coverage-php']; + static::assertInstanceOf(CodeCoverage::class, $coveragePhp); + } + + public function testRunningFewerTestsThanTheWorkersIsPossible(): void + { + $this->bareOptions['--path'] = $this->fixture('passing-tests' . DS . 'GroupsTest.php'); + $this->bareOptions['--processes'] = 2; + + $this->assertTestsPassed($this->runWrapperRunner()); + + $coveragePhp = include $this->bareOptions['--coverage-php']; + static::assertInstanceOf(CodeCoverage::class, $coveragePhp); + } + + public function testExitCodes(): void + { + $this->bareOptions['--path'] = $this->fixture('wrapper-runner-exit-code-tests' . DS . 'ErrorTest.php'); + $runnerResult = $this->runWrapperRunner(); + + static::assertStringContainsString('Tests: 1', $runnerResult->getOutput()); + static::assertStringContainsString('Failures: 0', $runnerResult->getOutput()); + static::assertStringContainsString('Errors: 1', $runnerResult->getOutput()); + static::assertEquals(TestRunner::EXCEPTION_EXIT, $runnerResult->getExitCode()); + + $this->bareOptions['--path'] = $this->fixture('wrapper-runner-exit-code-tests' . DS . 'FailureTest.php'); + $runnerResult = $this->runWrapperRunner(); + + static::assertStringContainsString('Tests: 1', $runnerResult->getOutput()); + static::assertStringContainsString('Failures: 1', $runnerResult->getOutput()); + static::assertStringContainsString('Errors: 0', $runnerResult->getOutput()); + static::assertEquals(TestRunner::FAILURE_EXIT, $runnerResult->getExitCode()); + + $this->bareOptions['--path'] = $this->fixture('wrapper-runner-exit-code-tests' . DS . 'SuccessTest.php'); + $runnerResult = $this->runWrapperRunner(); + + static::assertStringContainsString('OK (1 test, 1 assertion)', $runnerResult->getOutput()); + static::assertEquals(TestRunner::SUCCESS_EXIT, $runnerResult->getExitCode()); + + $file = $this->bareOptions['--coverage-php']; + if (is_string($file) && is_file($file)) { + unlink($file); + } + + $this->bareOptions['--path'] = $this->fixture('wrapper-runner-exit-code-tests'); + $runnerResult = $this->runWrapperRunner(); + + static::assertStringContainsString('Tests: 3', $runnerResult->getOutput()); + static::assertStringContainsString('Failures: 1', $runnerResult->getOutput()); + static::assertStringContainsString('Errors: 1', $runnerResult->getOutput()); + static::assertEquals(TestRunner::EXCEPTION_EXIT, $runnerResult->getExitCode()); + + $coveragePhp = include $this->bareOptions['--coverage-php']; + static::assertInstanceOf(CodeCoverage::class, $coveragePhp); + } + + public function testParallelSuiteOption(): void + { + $this->bareOptions = array_merge($this->bareOptions, [ + '--configuration' => $this->fixture('phpunit-parallel-suite.xml'), + '--parallel-suite' => true, + '--processes' => 2, + '--verbose' => 1, + '--whitelist' => $this->fixture('parallel-suite'), + ]); + + $this->assertTestsPassed($this->runWrapperRunner()); + + $coveragePhp = include $this->bareOptions['--coverage-php']; + static::assertInstanceOf(CodeCoverage::class, $coveragePhp); + } + + public function testRaiseExceptionWhenATestCallsExit(): void { - $options = $this->createOptionsFromArgv([]); - $output = new BufferedOutput(); + $this->bareOptions['--path'] = $this->fixture('exit-tests' . DS . 'UnitTestThatExitsSilentlyTest.php'); $this->expectException(RuntimeException::class); + $this->expectExceptionMessageMatches('/UnitTestThatExitsSilentlyTest/'); - new WrapperRunner($options, $output); + $this->runWrapperRunner(); } } diff --git a/test/fixtures/parallel-suite/ParallelBase.php b/test/fixtures/parallel-suite/ParallelBase.php index 288b8143..7bdbb683 100644 --- a/test/fixtures/parallel-suite/ParallelBase.php +++ b/test/fixtures/parallel-suite/ParallelBase.php @@ -14,7 +14,7 @@ abstract class ParallelBase extends TestCase final public function testToken(): void { $refClass = new ReflectionClass(static::class); - $file = TMP_DIR . DS . 'parallel-suite' . DS . 'token_' . str_replace(['\\', '/'], '_', $refClass->getNamespaceName()); + $file = TMP_DIR . DS . 'token_' . str_replace(['\\', '/'], '_', $refClass->getNamespaceName()); $token = getenv('TEST_TOKEN'); static::assertIsString($token); From c59ae2027abde153cb4363b7365aa8b666096cd6 Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Wed, 19 Aug 2020 15:02:08 +0200 Subject: [PATCH 16/19] Runner + RunnerWorker ok --- src/Runners/PHPUnit/ExecutableTest.php | 2 +- src/Runners/PHPUnit/Runner.php | 69 ++++----- src/Runners/PHPUnit/SqliteRunner.php | 14 +- src/Runners/PHPUnit/Worker/RunnerWorker.php | 51 ++++--- .../PHPUnit/WorkerCrashedException.php | 11 ++ src/Runners/PHPUnit/WrapperRunner.php | 4 +- test/TestBase.php | 23 +++ .../Runners/PHPUnit/BaseRunnerTest.php} | 75 ++++------ test/Unit/Runners/PHPUnit/FullSuiteTest.php | 2 +- test/Unit/Runners/PHPUnit/RunnerTest.php | 134 ++---------------- test/Unit/Runners/PHPUnit/RunnerTestCase.php | 121 ++++++++++++++++ .../Unit/Runners/PHPUnit/SqliteRunnerTest.php | 90 +----------- test/Unit/Runners/PHPUnit/SuiteTest.php | 2 +- test/Unit/Runners/PHPUnit/TestMethodTest.php | 2 +- .../Runners/PHPUnit/WrapperRunnerTest.php | 125 +--------------- .../UnitTestThatExitsLoudlyTest.php | 2 +- test/fixtures/passthru-tests/PassthruTest.php | 14 ++ 17 files changed, 294 insertions(+), 447 deletions(-) create mode 100644 src/Runners/PHPUnit/WorkerCrashedException.php rename test/{Functional/Runners/PHPUnit/RunnerIntegrationTest.php => Unit/Runners/PHPUnit/BaseRunnerTest.php} (57%) create mode 100644 test/Unit/Runners/PHPUnit/RunnerTestCase.php create mode 100644 test/fixtures/passthru-tests/PassthruTest.php diff --git a/src/Runners/PHPUnit/ExecutableTest.php b/src/Runners/PHPUnit/ExecutableTest.php index 12b64f7e..a84aedae 100644 --- a/src/Runners/PHPUnit/ExecutableTest.php +++ b/src/Runners/PHPUnit/ExecutableTest.php @@ -126,7 +126,7 @@ final public function setLastCommand(string $command): void * * @return string[] command line arguments */ - final public function commandArguments(string $binary, array $options = [], ?array $passthru = null): array + final public function commandArguments(string $binary, array $options, ?array $passthru): array { $options = array_merge($this->prepareOptions($options), ['log-junit' => $this->getTempFile()]); if ($this->needsCoverage) { diff --git a/src/Runners/PHPUnit/Runner.php b/src/Runners/PHPUnit/Runner.php index 256a7153..761b9bbc 100644 --- a/src/Runners/PHPUnit/Runner.php +++ b/src/Runners/PHPUnit/Runner.php @@ -5,10 +5,10 @@ namespace ParaTest\Runners\PHPUnit; use Exception; +use ParaTest\Coverage\EmptyCoverageFileException; use ParaTest\Runners\PHPUnit\Worker\RunnerWorker; -use RuntimeException; +use PHPUnit\TextUI\TestRunner; use Symfony\Component\Console\Output\OutputInterface; -use Throwable; use function array_filter; use function array_keys; @@ -17,13 +17,10 @@ use function assert; use function count; use function getenv; -use function sprintf; use function usleep; final class Runner extends BaseRunner { - private const PHPUNIT_FATAL_ERROR = 255; - /** * A collection of ExecutableTest objects that have processes * currently running. @@ -53,21 +50,12 @@ protected function doRun(): void { while (count($this->running) > 0 || count($this->pending) > 0) { foreach ($this->running as $key => $test) { - try { - if (! $this->testIsStillRunning($test)) { - unset($this->running[$key]); - $this->releaseToken($key); - } - } catch (Throwable $e) { - if ($this->options->verbose() > 0) { - $this->output->writeln("An error for {$key}: {$e->getMessage()}"); - $this->output->writeln("Command: {$test->getExecutableTest()->getLastCommand()}"); - $this->output->writeln('StdErr: ' . $test->getStderr()); - $this->output->writeln('StdOut: ' . $test->getStdout()); - } - - throw $e; + if ($this->testIsStillRunning($test)) { + continue; } + + unset($this->running[$key]); + $this->releaseToken($key); } $this->fillRunQueue(); @@ -82,12 +70,11 @@ protected function doRun(): void */ private function fillRunQueue(): void { - while (count($this->pending) > 0 && count($this->running) < $this->options->processes()) { - $tokenData = $this->getNextAvailableToken(); - if ($tokenData === false) { - continue; - } - + while ( + count($this->pending) > 0 + && count($this->running) < $this->options->processes() + && ($tokenData = $this->getNextAvailableToken()) !== false + ) { $this->acquireToken($tokenData['token']); $env = array_merge(getenv(), $this->options->fillEnvWithTokens($tokenData['token'])); @@ -134,20 +121,26 @@ private function testIsStillRunning(RunnerWorker $worker): bool } $executableTest = $worker->getExecutableTest(); - if ($worker->getExitCode() === self::PHPUNIT_FATAL_ERROR) { - $errorOutput = $worker->getStderr(); - if ($errorOutput === '') { - $errorOutput = $worker->getStdout(); - } - - throw new RuntimeException(sprintf("Fatal error in %s:\n%s", $executableTest->getPath(), $errorOutput)); + if ( + $worker->getExitCode() > 0 + && $worker->getExitCode() !== TestRunner::FAILURE_EXIT + && $worker->getExitCode() !== TestRunner::EXCEPTION_EXIT + ) { + throw new WorkerCrashedException($worker->getCrashReport()); } - $this->printer->printFeedback($executableTest); if ($this->hasCoverage()) { - $this->addCoverage($executableTest); + $coverageMerger = $this->getCoverage(); + assert($coverageMerger !== null); + try { + $coverageMerger->addCoverageFromFile($executableTest->getCoverageFileName()); + } catch (EmptyCoverageFileException $emptyCoverageFileException) { + throw new WorkerCrashedException($worker->getCrashReport(), 0, $emptyCoverageFileException); + } } + $this->printer->printFeedback($executableTest); + return false; } @@ -226,14 +219,6 @@ private function acquireToken(int $tokenIdentifier): void $this->tokens[$keys[0]]['available'] = false; } - private function addCoverage(ExecutableTest $test): void - { - $coverageFile = $test->getCoverageFileName(); - $coverageMerger = $this->getCoverage(); - assert($coverageMerger !== null); - $coverageMerger->addCoverageFromFile($coverageFile); - } - protected function beforeLoadChecks(): void { } diff --git a/src/Runners/PHPUnit/SqliteRunner.php b/src/Runners/PHPUnit/SqliteRunner.php index 33ec10c1..ac0070d5 100644 --- a/src/Runners/PHPUnit/SqliteRunner.php +++ b/src/Runners/PHPUnit/SqliteRunner.php @@ -130,7 +130,8 @@ private function assignAllPendingTests(): void ->execute([ ':command' => serialize($test->commandArguments( $this->options->phpunit(), - $this->options->filtered() + $this->options->filtered(), + $this->options->passthru() )), ':fileName' => $fileName, ]); @@ -147,7 +148,14 @@ private function printOutput(): void $tests = $stmt->fetchAll(); assert($tests !== false); foreach ($tests as $test) { - $this->printer->printFeedback($this->pending[$test['file_name']]); + $executableTest = $this->pending[$test['file_name']]; + if ($this->hasCoverage()) { + $coverageMerger = $this->getCoverage(); + assert($coverageMerger !== null); + $coverageMerger->addCoverageFromFile($executableTest->getCoverageFileName()); + } + + $this->printer->printFeedback($executableTest); $this->db->prepare('DELETE FROM tests WHERE id = :id')->execute([ 'id' => $test['id'], ]); @@ -172,7 +180,7 @@ private function checkIfWorkersCrashed(): void return implode(' ', array_map('escapeshellarg', unserialize($serializedCommand))); }, $commands); - throw new RuntimeException( + throw new WorkerCrashedException( 'Some workers have crashed.' . PHP_EOL . '----------------------' . PHP_EOL . 'All workers have quit, but some tests are still to be executed.' . PHP_EOL diff --git a/src/Runners/PHPUnit/Worker/RunnerWorker.php b/src/Runners/PHPUnit/Worker/RunnerWorker.php index 35a7a115..49a80497 100644 --- a/src/Runners/PHPUnit/Worker/RunnerWorker.php +++ b/src/Runners/PHPUnit/Worker/RunnerWorker.php @@ -11,6 +11,7 @@ use function array_merge; use function assert; +use function sprintf; use function strlen; use const DIRECTORY_SEPARATOR; @@ -20,7 +21,7 @@ final class RunnerWorker /** @var ExecutableTest */ private $executableTest; /** @var Process|null */ - public $process; + private $process; public function __construct(ExecutableTest $executableTest) { @@ -32,16 +33,6 @@ public function getExecutableTest(): ExecutableTest return $this->executableTest; } - /** - * Return the test process' stderr contents. - */ - public function getStderr(): string - { - assert($this->process !== null); - - return $this->process->getErrorOutput(); - } - /** * Stop the process and return it's * exit code. @@ -126,16 +117,6 @@ private function getProcess( return new Process($args, null, $environmentVariables); } - /** - * Get process stdout content. - */ - public function getStdout(): string - { - assert($this->process !== null); - - return $this->process->getOutput(); - } - /** * Assert that command line length is valid. * @@ -149,12 +130,15 @@ public function getStdout(): string */ private function assertValidCommandLineLength(string $cmd): void { - if (DIRECTORY_SEPARATOR === '\\') { // windows + if (DIRECTORY_SEPARATOR === '\\') { + // @codeCoverageIgnoreStart // symfony's process wrapper $cmd = 'cmd /V:ON /E:ON /C "(' . $cmd . ')'; if (strlen($cmd) > 32767) { throw new RuntimeException('Command line is too long, try to decrease max batch size'); } + + // @codeCoverageIgnoreEnd } /* @@ -164,4 +148,27 @@ private function assertValidCommandLineLength(string $cmd): void * - osx/linux: getconf ARG_MAX */ } + + public function getCrashReport(): string + { + assert($this->process !== null); + + $error = sprintf( + 'The command "%s" failed.' . "\n\nExit Code: %s(%s)\n\nWorking directory: %s", + $this->process->getCommandLine(), + (string) $this->process->getExitCode(), + (string) $this->process->getExitCodeText(), + (string) $this->process->getWorkingDirectory() + ); + + if (! $this->process->isOutputDisabled()) { + $error .= sprintf( + "\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s", + $this->process->getOutput(), + $this->process->getErrorOutput() + ); + } + + return $error; + } } diff --git a/src/Runners/PHPUnit/WorkerCrashedException.php b/src/Runners/PHPUnit/WorkerCrashedException.php new file mode 100644 index 00000000..bef7cb46 --- /dev/null +++ b/src/Runners/PHPUnit/WorkerCrashedException.php @@ -0,0 +1,11 @@ +waitForStreamsToChange($this->streams); foreach ($this->progressedWorkers() as $key => $worker) { if (! $worker->isFree()) { - continue; + continue; // @codeCoverageIgnore } $this->flushWorker($worker); @@ -138,7 +138,7 @@ private function flushWorker(WrapperWorker $worker): void try { $coverageMerger->addCoverageFromFile($coverageFileName); } catch (EmptyCoverageFileException $emptyCoverageFileException) { - throw new RuntimeException($worker->getCrashReport(), 0, $emptyCoverageFileException); + throw new WorkerCrashedException($worker->getCrashReport(), 0, $emptyCoverageFileException); } } } diff --git a/test/TestBase.php b/test/TestBase.php index 8623cb57..d335d94a 100644 --- a/test/TestBase.php +++ b/test/TestBase.php @@ -6,6 +6,8 @@ use InvalidArgumentException; use ParaTest\Runners\PHPUnit\Options; +use ParaTest\Runners\PHPUnit\Runner; +use ParaTest\Runners\PHPUnit\RunnerInterface; use ParaTest\Tests\Functional\RunnerResult; use PHPUnit; use PHPUnit\Framework\SkippedTestError; @@ -18,6 +20,7 @@ use SebastianBergmann\Environment\Runtime; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Output\BufferedOutput; use Symfony\Component\Filesystem\Filesystem; use function copy; @@ -31,6 +34,11 @@ abstract class TestBase extends PHPUnit\Framework\TestCase { + /** @var class-string */ + protected $runnerClass = Runner::class; + /** @var array */ + protected $bareOptions = []; + final protected function setUp(): void { $glob = glob(TMP_DIR . DS . '*'); @@ -58,6 +66,21 @@ final protected function createOptionsFromArgv(array $argv, ?string $cwd = null) return Options::fromConsoleInput($input, $cwd ?? PARATEST_ROOT); } + final protected function runRunner(?string $runnerClass = null): RunnerResult + { + if ($runnerClass === null) { + $runnerClass = $this->runnerClass; + } + + $bareOptions = $this->bareOptions; + $bareOptions['--tmp-dir'] = TMP_DIR; + $output = new BufferedOutput(); + $wrapperRunner = new $runnerClass($this->createOptionsFromArgv($this->bareOptions), $output); + $wrapperRunner->run(); + + return new RunnerResult($wrapperRunner->getExitCode(), $output->fetch()); + } + final protected function assertTestsPassed( RunnerResult $proc, ?string $testPattern = null, diff --git a/test/Functional/Runners/PHPUnit/RunnerIntegrationTest.php b/test/Unit/Runners/PHPUnit/BaseRunnerTest.php similarity index 57% rename from test/Functional/Runners/PHPUnit/RunnerIntegrationTest.php rename to test/Unit/Runners/PHPUnit/BaseRunnerTest.php index b78f2a8f..eae75057 100644 --- a/test/Functional/Runners/PHPUnit/RunnerIntegrationTest.php +++ b/test/Unit/Runners/PHPUnit/BaseRunnerTest.php @@ -2,56 +2,35 @@ declare(strict_types=1); -namespace ParaTest\Tests\Functional\Runners\PHPUnit; +namespace ParaTest\Tests\Unit\Runners\PHPUnit; -use ParaTest\Runners\PHPUnit\Options; -use ParaTest\Runners\PHPUnit\Runner; use ParaTest\Tests\TestBase; -use Symfony\Component\Console\Output\BufferedOutput; use function assert; use function count; use function glob; use function simplexml_load_file; -use function unlink; /** - * @coversNothing + * @covers \ParaTest\Runners\PHPUnit\BaseRunner */ -final class RunnerIntegrationTest extends TestBase +final class BaseRunnerTest extends TestBase { - /** @var Runner $runner */ - private $runner; - /** @var BufferedOutput */ - private $output; - /** @var array */ - private $bareOptions; - /** @var Options */ - private $options; - protected function setUpTest(): void { static::skipIfCodeCoverageNotEnabled(); - $testcoverageFiles = TMP_DIR . DS . 'coverage-runner-integration*'; - $glob = glob($testcoverageFiles); - assert($glob !== false); - foreach ($glob as $file) { - unlink($file); - } - $this->bareOptions = [ '--path' => FIXTURES . DS . 'failing-tests', - '--phpunit' => PHPUNIT, - '--coverage-clover' => TMP_DIR . DS . 'coverage-runner-integration.clover', - '--coverage-crap4j' => TMP_DIR . DS . 'coverage-runner-integration.crap4j', - '--coverage-php' => TMP_DIR . DS . 'coverage-runner-integration.php', + '--coverage-clover' => TMP_DIR . DS . 'coverage.clover', + '--coverage-crap4j' => TMP_DIR . DS . 'coverage.crap4j', + '--coverage-html' => TMP_DIR . DS . 'coverage.html', + '--coverage-php' => TMP_DIR . DS . 'coverage.php', + '--coverage-text' => true, + '--coverage-xml' => TMP_DIR . DS . 'coverage.xml', '--bootstrap' => BOOTSTRAP, '--whitelist' => FIXTURES . DS . 'failing-tests', ]; - $this->options = $this->createOptionsFromArgv($this->bareOptions); - $this->output = new BufferedOutput(); - $this->runner = new Runner($this->options, $this->output); } /** @@ -67,33 +46,42 @@ private function globTempDir(string $pattern): array public function testGeneratesCoverageTypes(): void { - static::assertFileDoesNotExist($this->bareOptions['--coverage-clover']); - static::assertFileDoesNotExist($this->bareOptions['--coverage-crap4j']); - static::assertFileDoesNotExist($this->bareOptions['--coverage-php']); + static::assertFileDoesNotExist((string) $this->bareOptions['--coverage-clover']); + static::assertFileDoesNotExist((string) $this->bareOptions['--coverage-crap4j']); + static::assertFileDoesNotExist((string) $this->bareOptions['--coverage-html']); + static::assertFileDoesNotExist((string) $this->bareOptions['--coverage-php']); + static::assertFileDoesNotExist((string) $this->bareOptions['--coverage-xml']); - $this->runner->run(); + $runnerResult = $this->runRunner(); - static::assertFileExists($this->bareOptions['--coverage-clover']); - static::assertFileExists($this->bareOptions['--coverage-crap4j']); - static::assertFileExists($this->bareOptions['--coverage-php']); + static::assertFileExists((string) $this->bareOptions['--coverage-clover']); + static::assertFileExists((string) $this->bareOptions['--coverage-crap4j']); + static::assertFileExists((string) $this->bareOptions['--coverage-html']); + static::assertFileExists((string) $this->bareOptions['--coverage-php']); + static::assertFileExists((string) $this->bareOptions['--coverage-xml']); + + static::assertStringContainsString('Code Coverage Report:', $runnerResult->getOutput()); } public function testRunningTestsShouldLeaveNoTempFiles(): void { + // Needed for one line coverage on early exit CS Fix :\ + unset($this->bareOptions['--coverage-php']); + $countBefore = count($this->globTempDir('PT_*')); $countCoverageBefore = count($this->globTempDir('CV_*')); - $this->runner->run(); + $this->runRunner(); $countAfter = count($this->globTempDir('PT_*')); $countCoverageAfter = count($this->globTempDir('CV_*')); - static::assertEquals( + static::assertSame( $countAfter, $countBefore, "Test Runner failed to clean up the 'PT_*' file in " . TMP_DIR ); - static::assertEquals( + static::assertSame( $countCoverageAfter, $countCoverageBefore, "Test Runner failed to clean up the 'CV_*' file in " . TMP_DIR @@ -102,17 +90,14 @@ public function testRunningTestsShouldLeaveNoTempFiles(): void public function testLogJUnitCreatesXmlFile(): void { - $outputPath = FIXTURES . DS . 'logs' . DS . 'test-output.xml'; + $outputPath = TMP_DIR . DS . 'test-output.xml'; $this->bareOptions['--log-junit'] = $outputPath; - $runner = new Runner($this->createOptionsFromArgv($this->bareOptions), $this->output); - - $runner->run(); + $this->runRunner(); static::assertFileExists($outputPath); $this->assertJunitXmlIsCorrect($outputPath); - unlink($outputPath); } public function assertJunitXmlIsCorrect(string $path): void diff --git a/test/Unit/Runners/PHPUnit/FullSuiteTest.php b/test/Unit/Runners/PHPUnit/FullSuiteTest.php index 5a23e1a5..a3ef88a8 100644 --- a/test/Unit/Runners/PHPUnit/FullSuiteTest.php +++ b/test/Unit/Runners/PHPUnit/FullSuiteTest.php @@ -19,7 +19,7 @@ public function testPrepareTheFullSuiteAsArguments(): void $name = uniqid('Suite_'); $fullSuite = new FullSuite($name, false, TMP_DIR); - $commandArguments = $fullSuite->commandArguments(uniqid()); + $commandArguments = $fullSuite->commandArguments(uniqid(), [], null); static::assertContains('--testsuite', $commandArguments); static::assertContains($name, $commandArguments); diff --git a/test/Unit/Runners/PHPUnit/RunnerTest.php b/test/Unit/Runners/PHPUnit/RunnerTest.php index e803e5e9..c487519f 100644 --- a/test/Unit/Runners/PHPUnit/RunnerTest.php +++ b/test/Unit/Runners/PHPUnit/RunnerTest.php @@ -4,136 +4,28 @@ namespace ParaTest\Tests\Unit\Runners\PHPUnit; -use ParaTest\Logging\LogInterpreter; -use ParaTest\Runners\PHPUnit\ResultPrinter; -use ParaTest\Runners\PHPUnit\Runner; -use ParaTest\Tests\TestBase; -use PHPUnit\TextUI\XmlConfiguration\Loader; -use Symfony\Component\Console\Output\BufferedOutput; - -use function getcwd; -use function uniqid; +use function preg_match; /** + * @covers \ParaTest\Runners\PHPUnit\BaseRunner * @covers \ParaTest\Runners\PHPUnit\Runner + * @covers \ParaTest\Runners\PHPUnit\Worker\RunnerWorker */ -final class RunnerTest extends TestBase +final class RunnerTest extends RunnerTestCase { - /** @var Runner */ - private $runner; - /** @var BufferedOutput */ - private $output; - - public function setUpTest(): void - { - $this->output = new BufferedOutput(); - $this->runner = new Runner($this->createOptionsFromArgv([]), $this->output); - } - - public function testConstructor(): void - { - $opts = [ - '--processes' => 4, - '--path' => FIXTURES . DS . 'tests', - '--bootstrap' => 'hello', - '--functional' => true, - ]; - $runner = new Runner($this->createOptionsFromArgv($opts), $this->output); - $options = $this->getObjectValue($runner, 'options'); - - static::assertEquals(4, $options->processes()); - static::assertEquals(FIXTURES . DS . 'tests', $options->path()); - static::assertEquals([], $this->getObjectValue($runner, 'pending')); - static::assertEquals([], $this->getObjectValue($runner, 'running')); - static::assertEquals(-1, $this->getObjectValue($runner, 'exitcode')); - static::assertTrue($options->functional()); - //filter out processes and path and phpunit - $config = (new Loader())->load(getcwd() . DS . 'phpunit.xml.dist'); - static::assertEquals(['bootstrap' => 'hello', 'configuration' => $config->filename()], $options->filtered()); - static::assertInstanceOf(LogInterpreter::class, $this->getObjectValue($runner, 'interpreter')); - static::assertInstanceOf(ResultPrinter::class, $this->getObjectValue($runner, 'printer')); - } - - public function testGetExitCode(): void - { - static::assertEquals(-1, $this->runner->getExitCode()); - } - - public function testConstructorAssignsTokens(): void - { - $opts = [ - '--processes' => 4, - '--path' => FIXTURES . DS . 'tests', - '--bootstrap' => 'hello', - '--functional' => true, - ]; - $runner = new Runner($this->createOptionsFromArgv($opts), $this->output); - $tokens = $this->getObjectValue($runner, 'tokens'); - static::assertCount(4, $tokens); - } - - public function testGetsNextAvailableTokenReturnsTokenIdentifier(): void + public function testStopOnFailureEndsRunBeforeWholeTestSuite(): void { - $tokens = [ - 0 => ['token' => 0, 'unique' => uniqid(), 'available' => false], - 1 => ['token' => 1, 'unique' => uniqid(), 'available' => false], - 2 => ['token' => 2, 'unique' => uniqid(), 'available' => true], - 3 => ['token' => 3, 'unique' => uniqid(), 'available' => false], - ]; - $opts = [ - '--processes' => 4, - '--path' => FIXTURES . DS . 'tests', - '--bootstrap' => 'hello', - '--functional' => true, - ]; - $runner = new Runner($this->createOptionsFromArgv($opts), $this->output); - $this->setObjectValue($runner, 'tokens', $tokens); + $this->bareOptions['--path'] = $this->fixture('failing-tests'); + $runnerResult = $this->runRunner(); - $tokenData = $this->call($runner, 'getNextAvailableToken'); - static::assertEquals(2, $tokenData['token']); - } + $regexp = '/Tests: \d+, Assertions: \d+, Failures: \d+, Errors: \d+\./'; + static::assertSame(1, preg_match($regexp, $runnerResult->getOutput(), $matchesOnFullRun)); - public function testGetNextAvailableTokenReturnsFalseWhenNoTokensAreAvailable(): void - { - $tokens = [ - 0 => ['token' => 0, 'unique' => uniqid(), 'available' => false], - 1 => ['token' => 1, 'unique' => uniqid(), 'available' => false], - 2 => ['token' => 2, 'unique' => uniqid(), 'available' => false], - 3 => ['token' => 3, 'unique' => uniqid(), 'available' => false], - ]; - $opts = [ - '--processes' => 4, - '--path' => FIXTURES . DS . 'tests', - '--bootstrap' => 'hello', - '--functional' => true, - ]; - $runner = new Runner($this->createOptionsFromArgv($opts), $this->output); - $this->setObjectValue($runner, 'tokens', $tokens); + $this->bareOptions['--stop-on-failure'] = true; + $runnerResult = $this->runRunner(); - $tokenData = $this->call($runner, 'getNextAvailableToken'); - static::assertFalse($tokenData); - } - - public function testReleaseTokenMakesTokenAvailable(): void - { - $tokens = [ - 0 => ['token' => 0, 'unique' => uniqid(), 'available' => false], - 1 => ['token' => 1, 'unique' => uniqid(), 'available' => false], - 2 => ['token' => 2, 'unique' => uniqid(), 'available' => false], - 3 => ['token' => 3, 'unique' => uniqid(), 'available' => false], - ]; - $opts = [ - '--processes' => 4, - '--path' => FIXTURES . DS . 'tests', - '--bootstrap' => 'hello', - '--functional' => true, - ]; - $runner = new Runner($this->createOptionsFromArgv($opts), $this->output); - $this->setObjectValue($runner, 'tokens', $tokens); + static::assertSame(1, preg_match($regexp, $runnerResult->getOutput(), $matchesOnPartialRun)); - static::assertFalse($tokens[1]['available']); - $this->call($runner, 'releaseToken', 1); - $tokens = $this->getObjectValue($runner, 'tokens'); - static::assertTrue($tokens[1]['available']); + static::assertNotEquals($matchesOnFullRun[0], $matchesOnPartialRun[0]); } } diff --git a/test/Unit/Runners/PHPUnit/RunnerTestCase.php b/test/Unit/Runners/PHPUnit/RunnerTestCase.php new file mode 100644 index 00000000..93516bc2 --- /dev/null +++ b/test/Unit/Runners/PHPUnit/RunnerTestCase.php @@ -0,0 +1,121 @@ +bareOptions['--path'] = $this->fixture('passing-tests' . DS . 'GroupsTest.php'); + $this->bareOptions['--coverage-php'] = TMP_DIR . DS . uniqid('result_'); + + $this->assertTestsPassed($this->runRunner()); + + $coveragePhp = include $this->bareOptions['--coverage-php']; + static::assertInstanceOf(CodeCoverage::class, $coveragePhp); + } + + final public function testRunningFewerTestsThanTheWorkersIsPossible(): void + { + $this->bareOptions['--path'] = $this->fixture('passing-tests' . DS . 'GroupsTest.php'); + $this->bareOptions['--processes'] = 2; + + $this->assertTestsPassed($this->runRunner()); + } + + final public function testExitCodes(): void + { + $this->bareOptions['--path'] = $this->fixture('wrapper-runner-exit-code-tests' . DS . 'ErrorTest.php'); + $runnerResult = $this->runRunner(); + + static::assertStringContainsString('Tests: 1', $runnerResult->getOutput()); + static::assertStringContainsString('Failures: 0', $runnerResult->getOutput()); + static::assertStringContainsString('Errors: 1', $runnerResult->getOutput()); + static::assertEquals(TestRunner::EXCEPTION_EXIT, $runnerResult->getExitCode()); + + $this->bareOptions['--path'] = $this->fixture('wrapper-runner-exit-code-tests' . DS . 'FailureTest.php'); + $runnerResult = $this->runRunner(); + + static::assertStringContainsString('Tests: 1', $runnerResult->getOutput()); + static::assertStringContainsString('Failures: 1', $runnerResult->getOutput()); + static::assertStringContainsString('Errors: 0', $runnerResult->getOutput()); + static::assertEquals(TestRunner::FAILURE_EXIT, $runnerResult->getExitCode()); + + $this->bareOptions['--path'] = $this->fixture('wrapper-runner-exit-code-tests' . DS . 'SuccessTest.php'); + $runnerResult = $this->runRunner(); + + static::assertStringContainsString('OK (1 test, 1 assertion)', $runnerResult->getOutput()); + static::assertEquals(TestRunner::SUCCESS_EXIT, $runnerResult->getExitCode()); + + $this->bareOptions['--path'] = $this->fixture('wrapper-runner-exit-code-tests'); + $runnerResult = $this->runRunner(); + + static::assertStringContainsString('Tests: 3', $runnerResult->getOutput()); + static::assertStringContainsString('Failures: 1', $runnerResult->getOutput()); + static::assertStringContainsString('Errors: 1', $runnerResult->getOutput()); + static::assertEquals(TestRunner::EXCEPTION_EXIT, $runnerResult->getExitCode()); + } + + final public function testParallelSuiteOption(): void + { + $this->bareOptions = array_merge($this->bareOptions, [ + '--configuration' => $this->fixture('phpunit-parallel-suite.xml'), + '--parallel-suite' => true, + '--processes' => 2, + '--verbose' => 1, + '--whitelist' => $this->fixture('parallel-suite'), + ]); + + $this->assertTestsPassed($this->runRunner()); + } + + final public function testRaiseExceptionWhenATestCallsExitSilently(): void + { + $this->bareOptions['--path'] = $this->fixture('exit-tests' . DS . 'UnitTestThatExitsSilentlyTest.php'); + $this->bareOptions['--coverage-php'] = TMP_DIR . DS . uniqid('result_'); + + $this->expectException(WorkerCrashedException::class); + $this->expectExceptionMessageMatches('/UnitTestThatExitsSilentlyTest/'); + + $this->runRunner(); + } + + final public function testRaiseExceptionWhenATestCallsExitLoudly(): void + { + $this->bareOptions['--path'] = $this->fixture('exit-tests' . DS . 'UnitTestThatExitsLoudlyTest.php'); + $this->bareOptions['--coverage-php'] = TMP_DIR . DS . uniqid('result_'); + + $this->expectException(WorkerCrashedException::class); + $this->expectExceptionMessageMatches('/UnitTestThatExitsLoudlyTest/'); + + $this->runRunner(); + } + + final public function testPassthrus(): void + { + $this->bareOptions['--path'] = $this->fixture('passthru-tests' . DS . 'PassthruTest.php'); + + $runnerResult = $this->runRunner(); + static::assertSame(TestRunner::FAILURE_EXIT, $runnerResult->getExitCode()); + + $this->bareOptions['--passthru-php'] = sprintf("'-d' 'highlight.comment=%s'", self::PASSTHRU_PHP_CUSTOM); + $this->bareOptions['--passthru'] = sprintf("'-d' 'highlight.string=%s'", self::PASSTHRU_PHPUNIT_CUSTOM); + + $runnerResult = $this->runRunner(); + $this->assertTestsPassed($runnerResult); + } +} diff --git a/test/Unit/Runners/PHPUnit/SqliteRunnerTest.php b/test/Unit/Runners/PHPUnit/SqliteRunnerTest.php index 2318b3d4..3d7fe92b 100644 --- a/test/Unit/Runners/PHPUnit/SqliteRunnerTest.php +++ b/test/Unit/Runners/PHPUnit/SqliteRunnerTest.php @@ -5,98 +5,16 @@ namespace ParaTest\Tests\Unit\Runners\PHPUnit; use ParaTest\Runners\PHPUnit\SqliteRunner; -use ParaTest\Tests\Functional\RunnerResult; -use ParaTest\Tests\TestBase; -use PHPUnit\TextUI\TestRunner; -use RuntimeException; -use Symfony\Component\Console\Output\BufferedOutput; - -use function extension_loaded; /** + * @requires extension pdo_sqlite * @covers \ParaTest\Runners\PHPUnit\BaseWrapperRunner * @covers \ParaTest\Runners\PHPUnit\SqliteRunner * @covers \ParaTest\Runners\PHPUnit\Worker\BaseWorker * @covers \ParaTest\Runners\PHPUnit\Worker\SqliteWorker */ -final class SqliteRunnerTest extends TestBase +final class SqliteRunnerTest extends RunnerTestCase { - /** @var array */ - private $bareOptions; - - protected function setUpTest(): void - { - if (! extension_loaded('pdo_sqlite')) { - static::markTestSkipped('Skipping test: Extension pdo_sqlite not found.'); - } - - $this->bareOptions = ['--tmp-dir' => TMP_DIR]; - } - - private function runSqliteRunner(): RunnerResult - { - $output = new BufferedOutput(); - $sqliteRunner = new SqliteRunner($this->createOptionsFromArgv($this->bareOptions), $output); - $sqliteRunner->run(); - - return new RunnerResult($sqliteRunner->getExitCode(), $output->fetch()); - } - - public function testResultsAreCorrect(): void - { - $this->bareOptions['--path'] = $this->fixture('passing-tests' . DS . 'GroupsTest.php'); - - $this->assertTestsPassed($this->runSqliteRunner()); - } - - public function testRunningFewerTestsThanTheWorkersIsPossible(): void - { - $this->bareOptions['--path'] = $this->fixture('passing-tests' . DS . 'GroupsTest.php'); - $this->bareOptions['--processes'] = 2; - - $this->assertTestsPassed($this->runSqliteRunner()); - } - - public function testExitCodes(): void - { - $this->bareOptions['--path'] = $this->fixture('wrapper-runner-exit-code-tests' . DS . 'ErrorTest.php'); - $runnerResult = $this->runSqliteRunner(); - - static::assertStringContainsString('Tests: 1', $runnerResult->getOutput()); - static::assertStringContainsString('Failures: 0', $runnerResult->getOutput()); - static::assertStringContainsString('Errors: 1', $runnerResult->getOutput()); - static::assertEquals(TestRunner::EXCEPTION_EXIT, $runnerResult->getExitCode()); - - $this->bareOptions['--path'] = $this->fixture('wrapper-runner-exit-code-tests' . DS . 'FailureTest.php'); - $runnerResult = $this->runSqliteRunner(); - - static::assertStringContainsString('Tests: 1', $runnerResult->getOutput()); - static::assertStringContainsString('Failures: 1', $runnerResult->getOutput()); - static::assertStringContainsString('Errors: 0', $runnerResult->getOutput()); - static::assertEquals(TestRunner::FAILURE_EXIT, $runnerResult->getExitCode()); - - $this->bareOptions['--path'] = $this->fixture('wrapper-runner-exit-code-tests' . DS . 'SuccessTest.php'); - $runnerResult = $this->runSqliteRunner(); - - static::assertStringContainsString('OK (1 test, 1 assertion)', $runnerResult->getOutput()); - static::assertEquals(TestRunner::SUCCESS_EXIT, $runnerResult->getExitCode()); - - $this->bareOptions['--path'] = $this->fixture('wrapper-runner-exit-code-tests'); - $runnerResult = $this->runSqliteRunner(); - - static::assertStringContainsString('Tests: 3', $runnerResult->getOutput()); - static::assertStringContainsString('Failures: 1', $runnerResult->getOutput()); - static::assertStringContainsString('Errors: 1', $runnerResult->getOutput()); - static::assertEquals(TestRunner::EXCEPTION_EXIT, $runnerResult->getExitCode()); - } - - public function testRaiseExceptionWhenATestCallsExit(): void - { - $this->bareOptions['--path'] = $this->fixture('exit-tests' . DS . 'UnitTestThatExitsSilentlyTest.php'); - - $this->expectException(RuntimeException::class); - $this->expectExceptionMessageMatches('/UnitTestThatExitsSilentlyTest/'); - - $this->runSqliteRunner(); - } + /** {@inheritdoc } */ + protected $runnerClass = SqliteRunner::class; } diff --git a/test/Unit/Runners/PHPUnit/SuiteTest.php b/test/Unit/Runners/PHPUnit/SuiteTest.php index 1e4fd10d..cfbfcfc2 100644 --- a/test/Unit/Runners/PHPUnit/SuiteTest.php +++ b/test/Unit/Runners/PHPUnit/SuiteTest.php @@ -23,7 +23,7 @@ public function testConstructor(): void $testMethods = [$testMethod1, $testMethod2]; $suite = new Suite($file, $testMethods, false, TMP_DIR); - $commandArguments = $suite->commandArguments(uniqid()); + $commandArguments = $suite->commandArguments(uniqid(), [], null); static::assertNotContains('--filter', $commandArguments); static::assertContains($file, $commandArguments); diff --git a/test/Unit/Runners/PHPUnit/TestMethodTest.php b/test/Unit/Runners/PHPUnit/TestMethodTest.php index 5010a712..2a80023a 100644 --- a/test/Unit/Runners/PHPUnit/TestMethodTest.php +++ b/test/Unit/Runners/PHPUnit/TestMethodTest.php @@ -19,7 +19,7 @@ public function testConstructor(): void $file = uniqid('pathToFile_'); $testMethod = new TestMethod($file, ['method1', 'method2'], false, TMP_DIR); - $commandArguments = $testMethod->commandArguments(uniqid()); + $commandArguments = $testMethod->commandArguments(uniqid(), [], null); static::assertContains('--filter', $commandArguments); static::assertContains($file, $commandArguments); diff --git a/test/Unit/Runners/PHPUnit/WrapperRunnerTest.php b/test/Unit/Runners/PHPUnit/WrapperRunnerTest.php index 84e0f4ea..01d3ec9d 100644 --- a/test/Unit/Runners/PHPUnit/WrapperRunnerTest.php +++ b/test/Unit/Runners/PHPUnit/WrapperRunnerTest.php @@ -6,18 +6,6 @@ use InvalidArgumentException; use ParaTest\Runners\PHPUnit\WrapperRunner; -use ParaTest\Tests\Functional\RunnerResult; -use ParaTest\Tests\TestBase; -use PHPUnit\TextUI\TestRunner; -use RuntimeException; -use SebastianBergmann\CodeCoverage\CodeCoverage; -use Symfony\Component\Console\Output\BufferedOutput; - -use function array_merge; -use function is_file; -use function is_string; -use function uniqid; -use function unlink; /** * @requires OSFAMILY Linux @@ -26,27 +14,10 @@ * @covers \ParaTest\Runners\PHPUnit\Worker\BaseWorker * @covers \ParaTest\Runners\PHPUnit\Worker\WrapperWorker */ -final class WrapperRunnerTest extends TestBase +final class WrapperRunnerTest extends RunnerTestCase { - /** @var array */ - private $bareOptions; - - protected function setUpTest(): void - { - $this->bareOptions = [ - '--tmp-dir' => TMP_DIR, - '--coverage-php' => TMP_DIR . DS . uniqid('result_'), - ]; - } - - private function runWrapperRunner(): RunnerResult - { - $output = new BufferedOutput(); - $wrapperRunner = new WrapperRunner($this->createOptionsFromArgv($this->bareOptions), $output); - $wrapperRunner->run(); - - return new RunnerResult($wrapperRunner->getExitCode(), $output->fetch()); - } + /** {@inheritdoc } */ + protected $runnerClass = WrapperRunner::class; public function testWrapperRunnerNotAvailableInFunctionalMode(): void { @@ -55,94 +26,6 @@ public function testWrapperRunnerNotAvailableInFunctionalMode(): void $this->expectException(InvalidArgumentException::class); - $this->runWrapperRunner(); - } - - public function testResultsAreCorrect(): void - { - $this->bareOptions['--path'] = $this->fixture('passing-tests' . DS . 'GroupsTest.php'); - - $this->assertTestsPassed($this->runWrapperRunner()); - - $coveragePhp = include $this->bareOptions['--coverage-php']; - static::assertInstanceOf(CodeCoverage::class, $coveragePhp); - } - - public function testRunningFewerTestsThanTheWorkersIsPossible(): void - { - $this->bareOptions['--path'] = $this->fixture('passing-tests' . DS . 'GroupsTest.php'); - $this->bareOptions['--processes'] = 2; - - $this->assertTestsPassed($this->runWrapperRunner()); - - $coveragePhp = include $this->bareOptions['--coverage-php']; - static::assertInstanceOf(CodeCoverage::class, $coveragePhp); - } - - public function testExitCodes(): void - { - $this->bareOptions['--path'] = $this->fixture('wrapper-runner-exit-code-tests' . DS . 'ErrorTest.php'); - $runnerResult = $this->runWrapperRunner(); - - static::assertStringContainsString('Tests: 1', $runnerResult->getOutput()); - static::assertStringContainsString('Failures: 0', $runnerResult->getOutput()); - static::assertStringContainsString('Errors: 1', $runnerResult->getOutput()); - static::assertEquals(TestRunner::EXCEPTION_EXIT, $runnerResult->getExitCode()); - - $this->bareOptions['--path'] = $this->fixture('wrapper-runner-exit-code-tests' . DS . 'FailureTest.php'); - $runnerResult = $this->runWrapperRunner(); - - static::assertStringContainsString('Tests: 1', $runnerResult->getOutput()); - static::assertStringContainsString('Failures: 1', $runnerResult->getOutput()); - static::assertStringContainsString('Errors: 0', $runnerResult->getOutput()); - static::assertEquals(TestRunner::FAILURE_EXIT, $runnerResult->getExitCode()); - - $this->bareOptions['--path'] = $this->fixture('wrapper-runner-exit-code-tests' . DS . 'SuccessTest.php'); - $runnerResult = $this->runWrapperRunner(); - - static::assertStringContainsString('OK (1 test, 1 assertion)', $runnerResult->getOutput()); - static::assertEquals(TestRunner::SUCCESS_EXIT, $runnerResult->getExitCode()); - - $file = $this->bareOptions['--coverage-php']; - if (is_string($file) && is_file($file)) { - unlink($file); - } - - $this->bareOptions['--path'] = $this->fixture('wrapper-runner-exit-code-tests'); - $runnerResult = $this->runWrapperRunner(); - - static::assertStringContainsString('Tests: 3', $runnerResult->getOutput()); - static::assertStringContainsString('Failures: 1', $runnerResult->getOutput()); - static::assertStringContainsString('Errors: 1', $runnerResult->getOutput()); - static::assertEquals(TestRunner::EXCEPTION_EXIT, $runnerResult->getExitCode()); - - $coveragePhp = include $this->bareOptions['--coverage-php']; - static::assertInstanceOf(CodeCoverage::class, $coveragePhp); - } - - public function testParallelSuiteOption(): void - { - $this->bareOptions = array_merge($this->bareOptions, [ - '--configuration' => $this->fixture('phpunit-parallel-suite.xml'), - '--parallel-suite' => true, - '--processes' => 2, - '--verbose' => 1, - '--whitelist' => $this->fixture('parallel-suite'), - ]); - - $this->assertTestsPassed($this->runWrapperRunner()); - - $coveragePhp = include $this->bareOptions['--coverage-php']; - static::assertInstanceOf(CodeCoverage::class, $coveragePhp); - } - - public function testRaiseExceptionWhenATestCallsExit(): void - { - $this->bareOptions['--path'] = $this->fixture('exit-tests' . DS . 'UnitTestThatExitsSilentlyTest.php'); - - $this->expectException(RuntimeException::class); - $this->expectExceptionMessageMatches('/UnitTestThatExitsSilentlyTest/'); - - $this->runWrapperRunner(); + $this->runRunner(); } } diff --git a/test/fixtures/exit-tests/UnitTestThatExitsLoudlyTest.php b/test/fixtures/exit-tests/UnitTestThatExitsLoudlyTest.php index 931025f1..3b46f4dd 100644 --- a/test/fixtures/exit-tests/UnitTestThatExitsLoudlyTest.php +++ b/test/fixtures/exit-tests/UnitTestThatExitsLoudlyTest.php @@ -6,6 +6,6 @@ final class UnitTestThatExitsLoudlyTest extends \PHPUnit\Framework\TestCase { public function testExit(): void { - exit(1); + exit(255); } } diff --git a/test/fixtures/passthru-tests/PassthruTest.php b/test/fixtures/passthru-tests/PassthruTest.php new file mode 100644 index 00000000..12d9e6cf --- /dev/null +++ b/test/fixtures/passthru-tests/PassthruTest.php @@ -0,0 +1,14 @@ + Date: Wed, 19 Aug 2020 15:05:15 +0200 Subject: [PATCH 17/19] Fix tests for Windows --- test/Unit/Runners/PHPUnit/OptionsTest.php | 16 +++++++--------- test/Unit/Runners/PHPUnit/RunnerTestCase.php | 6 ++++++ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/test/Unit/Runners/PHPUnit/OptionsTest.php b/test/Unit/Runners/PHPUnit/OptionsTest.php index b6b015e9..aba078a3 100644 --- a/test/Unit/Runners/PHPUnit/OptionsTest.php +++ b/test/Unit/Runners/PHPUnit/OptionsTest.php @@ -14,6 +14,7 @@ use function intdiv; use function mt_rand; use function sort; +use function str_replace; use function sys_get_temp_dir; /** @@ -113,16 +114,13 @@ public function testConfigurationKeyIsNotPresentIfNoConfigGiven(): void public function testPassthru(): void { + $argv = [ + '--passthru' => "'--prepend' 'xdebug-filter.php'", + '--passthru-php' => "'-d' 'zend_extension=xdebug.so'", + ]; if (defined('PHP_WINDOWS_VERSION_BUILD')) { - $argv = [ - '--passthru' => '"--prepend" "xdebug-filter.php"', - '--passthru-php' => '"-d" "zend_extension=xdebug.so"', - ]; - } else { - $argv = [ - '--passthru' => "'--prepend' 'xdebug-filter.php'", - '--passthru-php' => "'-d' 'zend_extension=xdebug.so'", - ]; + $argv['--passthru'] = str_replace('\'', '"', $argv['--passthru']); + $argv['--passthru-php'] = str_replace('\'', '"', $argv['--passthru-php']); } $options = $this->createOptionsFromArgv($argv); diff --git a/test/Unit/Runners/PHPUnit/RunnerTestCase.php b/test/Unit/Runners/PHPUnit/RunnerTestCase.php index 93516bc2..36381811 100644 --- a/test/Unit/Runners/PHPUnit/RunnerTestCase.php +++ b/test/Unit/Runners/PHPUnit/RunnerTestCase.php @@ -10,7 +10,9 @@ use SebastianBergmann\CodeCoverage\CodeCoverage; use function array_merge; +use function defined; use function sprintf; +use function str_replace; use function uniqid; abstract class RunnerTestCase extends TestBase @@ -114,6 +116,10 @@ final public function testPassthrus(): void $this->bareOptions['--passthru-php'] = sprintf("'-d' 'highlight.comment=%s'", self::PASSTHRU_PHP_CUSTOM); $this->bareOptions['--passthru'] = sprintf("'-d' 'highlight.string=%s'", self::PASSTHRU_PHPUNIT_CUSTOM); + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->bareOptions['--passthru'] = str_replace('\'', '"', (string) $this->bareOptions['--passthru']); + $this->bareOptions['--passthru-php'] = str_replace('\'', '"', (string) $this->bareOptions['--passthru-php']); + } $runnerResult = $this->runRunner(); $this->assertTestsPassed($runnerResult); From dc7e1a1b93cdce56d3bee7b151fd445313df8944 Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Wed, 19 Aug 2020 15:16:24 +0200 Subject: [PATCH 18/19] Habitat is no longer necessary --- composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/composer.json b/composer.json index 974da748..055880e4 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,6 @@ "ext-pdo": "*", "ext-reflection": "*", "ext-simplexml": "*", - "brianium/habitat": "^1.0", "phpunit/php-code-coverage": "^9.1.2", "phpunit/php-file-iterator": "^3.0", "phpunit/php-timer": "^5.0", From e4faa4a45f187acdae5674a13dd6a8ff83c57795 Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Wed, 19 Aug 2020 15:16:49 +0200 Subject: [PATCH 19/19] Refactor test to increase code-coverage --- phpstan.neon.dist | 4 -- src/Runners/PHPUnit/Worker/BaseWorker.php | 26 +++-------- .../Functional/Runners/PHPUnit/WorkerTest.php | 45 +++---------------- 3 files changed, 12 insertions(+), 63 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 77aad273..96b428c2 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -63,10 +63,6 @@ parameters: message: "#^Should not use function \"proc_open\", please change the code\\.$#" count: 1 path: src/Runners/PHPUnit/Worker/BaseWorker.php - - - message: "#^Should not use function \"proc_open\", please change the code\\.$#" - count: 1 - path: test/Functional/Runners/PHPUnit/WorkerTest.php # Known errors - "#^Variable property access on .+$#" diff --git a/src/Runners/PHPUnit/Worker/BaseWorker.php b/src/Runners/PHPUnit/Worker/BaseWorker.php index f24c4051..bf2ced8a 100644 --- a/src/Runners/PHPUnit/Worker/BaseWorker.php +++ b/src/Runners/PHPUnit/Worker/BaseWorker.php @@ -5,7 +5,7 @@ namespace ParaTest\Runners\PHPUnit\Worker; use ParaTest\Runners\PHPUnit\Options; -use RuntimeException; +use ParaTest\Runners\PHPUnit\WorkerCrashedException; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Process\PhpExecutableFinder; @@ -125,17 +125,8 @@ final public function isRunning(): bool return $status !== false ? $status['running'] : false; } - final public function isStarted(): bool - { - return $this->proc !== null && $this->pipes !== []; - } - - final public function isCrashed(): bool + final public function checkNotCrashed(): void { - if (! $this->isStarted()) { - return false; - } - assert($this->proc !== null); $status = proc_get_status($this->proc); assert($status !== false); @@ -143,18 +134,11 @@ final public function isCrashed(): bool $this->updateStateFromAvailableOutput(); $this->setExitCode($status['running'], $status['exitcode']); - if ($this->exitCode === null) { - return false; + if ($this->exitCode === null || $this->exitCode === 0) { + return; } - return $this->exitCode !== 0; - } - - final public function checkNotCrashed(): void - { - if ($this->isCrashed()) { - throw new RuntimeException($this->getCrashReport()); - } + throw new WorkerCrashedException($this->getCrashReport()); } final public function getCrashReport(): string diff --git a/test/Functional/Runners/PHPUnit/WorkerTest.php b/test/Functional/Runners/PHPUnit/WorkerTest.php index 9edff9a8..0b8a3449 100644 --- a/test/Functional/Runners/PHPUnit/WorkerTest.php +++ b/test/Functional/Runners/PHPUnit/WorkerTest.php @@ -6,16 +6,14 @@ use ParaTest\Runners\PHPUnit\Options; use ParaTest\Runners\PHPUnit\Worker\WrapperWorker; +use ParaTest\Runners\PHPUnit\WorkerCrashedException; use ParaTest\Tests\TestBase; -use ReflectionProperty; use SimpleXMLElement; use Symfony\Component\Console\Output\BufferedOutput; use function count; use function file_get_contents; -use function get_class; -use function proc_get_status; -use function proc_open; +use function uniqid; /** * @covers \ParaTest\Runners\PHPUnit\Worker\BaseWorker @@ -112,42 +110,13 @@ public function testProcessIsMarkedAsCrashedWhenItFinishesWithNonZeroExitCode(): { // fake state: process has already exited (with non-zero exit code) but worker did not yet notice $worker = new WrapperWorker($this->output); - $this->setPerReflection($worker, 'proc', $this->createSomeClosedProcess()); - $this->setPerReflection($worker, 'pipes', [0 => true]); - static::assertTrue($worker->isCrashed()); - } - - /** - * @return resource - */ - private function createSomeClosedProcess() - { - $descriptorspec = [ - 0 => ['pipe', 'r'], - 1 => ['pipe', 'w'], - 2 => ['pipe', 'w'], - ]; - - $proc = proc_open('thisCommandHasAnExitcodeNotEqualZero', $descriptorspec, $pipes, '/tmp'); - static::assertIsResource($proc); - $running = true; - while ($running) { - $status = proc_get_status($proc); - static::assertNotFalse($status); - $running = $status['running']; - } + $worker->start(uniqid('thisCommandHasAnExitcodeNotEqualZero'), $this->createOptionsFromArgv([]), 1); + $worker->waitForStop(); - return $proc; - } + static::expectException(WorkerCrashedException::class); + static::expectExceptionMessageMatches('/thisCommandHasAnExitcodeNotEqualZero/'); - /** - * @param mixed $value - */ - private function setPerReflection(object $instance, string $property, $value): void - { - $reflectionProperty = new ReflectionProperty(get_class($instance), $property); - $reflectionProperty->setAccessible(true); - $reflectionProperty->setValue($instance, $value); + $worker->checkNotCrashed(); } public function testCanExecuteMultiplePHPUnitCommands(): void