From e67e9edded7c385f367ecb0313f6b57452ad5d79 Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Tue, 13 Jun 2023 15:15:42 +0200 Subject: [PATCH] Handle unexpected output (#769) --- .gitignore | 1 - Makefile | 4 + bin/phpunit-wrapper.php | 3 + phpstan-baseline.neon | 5 ++ .../ApplicationForWrapperWorker.php | 6 +- src/WrapperRunner/ProgressPrinterOutput.php | 42 ++++++++++ src/WrapperRunner/ResultPrinter.php | 18 +++-- src/WrapperRunner/WrapperRunner.php | 11 ++- src/WrapperRunner/WrapperWorker.php | 5 ++ test/MemoryPrinter.php | 37 +++++++++ .../ProgressPrinterOutputTest.php | 81 +++++++++++++++++++ test/Unit/WrapperRunner/ResultPrinterTest.php | 79 ++++++++---------- test/Unit/WrapperRunner/WrapperRunnerTest.php | 43 ++++++++++ .../UnexpectedOutputTest.php | 18 +++++ 14 files changed, 297 insertions(+), 56 deletions(-) create mode 100644 src/WrapperRunner/ProgressPrinterOutput.php create mode 100644 test/MemoryPrinter.php create mode 100644 test/Unit/WrapperRunner/ProgressPrinterOutputTest.php create mode 100644 test/fixtures/unexpected_output/UnexpectedOutputTest.php diff --git a/.gitignore b/.gitignore index 9fd409d4..7adfa86d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ /.idea /coverage -/test/fixtures/generated_tests /test/fixtures/wrapper_batchsize_suite/tmp /vendor /.phpunit.cache diff --git a/Makefile b/Makefile index 9e0e0414..2851025e 100644 --- a/Makefile +++ b/Makefile @@ -55,6 +55,10 @@ code-coverage: coverage/junit.xml --ignore-msi-with-no-mutations \ --min-msi=100 \ $(INFECTION_ARGS) + +.PHONY: clean +clean: + rm -r test/tmp/* .PHONY: regenerate-fixture-results regenerate-fixture-results: vendor diff --git a/bin/phpunit-wrapper.php b/bin/phpunit-wrapper.php index 4ad81509..6cd44e1f 100644 --- a/bin/phpunit-wrapper.php +++ b/bin/phpunit-wrapper.php @@ -9,6 +9,7 @@ $getopt = getopt('', [ 'status-file:', 'progress-file:', + 'unexpected-output-file:', 'testresult-file:', 'teamcity-file:', 'testdox-file:', @@ -36,6 +37,7 @@ assert(is_resource($statusFile)); assert(isset($getopt['progress-file']) && is_string($getopt['progress-file'])); + assert(isset($getopt['unexpected-output-file']) && is_string($getopt['unexpected-output-file'])); assert(isset($getopt['testresult-file']) && is_string($getopt['testresult-file'])); assert(!isset($getopt['teamcity-file']) || is_string($getopt['teamcity-file'])); assert(!isset($getopt['testdox-file']) || is_string($getopt['testdox-file'])); @@ -47,6 +49,7 @@ $application = new ApplicationForWrapperWorker( $phpunitArgv, $getopt['progress-file'], + $getopt['unexpected-output-file'], $getopt['testresult-file'], $getopt['teamcity-file'] ?? null, $getopt['testdox-file'] ?? null, diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index b9a37d56..506942d9 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -15,6 +15,11 @@ parameters: count: 1 path: src/Options.php + - + message: "#^Match expression does not handle remaining value\\: string$#" + count: 1 + path: src/WrapperRunner/ResultPrinter.php + - message: "#^Property ParaTest\\\\WrapperRunner\\\\SuiteLoader\\:\\:\\$files \\(array\\\\) does not accept array\\\\.$#" count: 1 diff --git a/src/WrapperRunner/ApplicationForWrapperWorker.php b/src/WrapperRunner/ApplicationForWrapperWorker.php index eb4f4738..1ce46ce2 100644 --- a/src/WrapperRunner/ApplicationForWrapperWorker.php +++ b/src/WrapperRunner/ApplicationForWrapperWorker.php @@ -50,6 +50,7 @@ final class ApplicationForWrapperWorker public function __construct( private readonly array $argv, private readonly string $progressFile, + private readonly string $unexpectedOutputFile, private readonly string $testresultFile, private readonly ?string $teamcityFile, private readonly ?string $testdoxFile, @@ -140,7 +141,10 @@ private function bootstrap(): void } new ProgressPrinter( - DefaultPrinter::from($this->progressFile), + new ProgressPrinterOutput( + DefaultPrinter::from($this->progressFile), + DefaultPrinter::from($this->unexpectedOutputFile), + ), EventFacade::instance(), false, 120, diff --git a/src/WrapperRunner/ProgressPrinterOutput.php b/src/WrapperRunner/ProgressPrinterOutput.php new file mode 100644 index 00000000..da54fd8c --- /dev/null +++ b/src/WrapperRunner/ProgressPrinterOutput.php @@ -0,0 +1,42 @@ + $this->progressPrinter->print($buffer), + default => $this->outputPrinter->print($buffer), + }; + } + + public function flush(): void + { + $this->progressPrinter->flush(); + $this->outputPrinter->flush(); + } +} diff --git a/src/WrapperRunner/ResultPrinter.php b/src/WrapperRunner/ResultPrinter.php index 3bc3ca1a..3f755d09 100644 --- a/src/WrapperRunner/ResultPrinter.php +++ b/src/WrapperRunner/ResultPrinter.php @@ -27,7 +27,6 @@ use function fseek; use function ftell; use function fwrite; -use function preg_replace; use function sprintf; use function str_repeat; use function strlen; @@ -129,8 +128,11 @@ public function start(): void } /** @param list $teamcityFiles */ - public function printFeedback(SplFileInfo $progressFile, array $teamcityFiles): void - { + public function printFeedback( + SplFileInfo $progressFile, + SplFileInfo $outputFile, + array $teamcityFiles + ): void { if ($this->options->needsTeamcity) { $teamcityProgress = $this->tailMultiple($teamcityFiles); @@ -150,14 +152,16 @@ public function printFeedback(SplFileInfo $progressFile, array $teamcityFiles): return; } + $unexpectedOutput = $this->tail($outputFile); + if ($unexpectedOutput !== '') { + $this->output->write($unexpectedOutput); + } + $feedbackItems = $this->tail($progressFile); if ($feedbackItems === '') { return; } - $feedbackItems = preg_replace('/ +\\d+ \\/ \\d+ \\( *\\d+%\\)\\s*/', '', $feedbackItems); - assert($feedbackItems !== null); - $actualTestCount = strlen($feedbackItems); for ($index = 0; $index < $actualTestCount; ++$index) { $this->printFeedbackItem($feedbackItems[$index]); @@ -249,7 +253,7 @@ private function printFeedbackItemColor(string $item): void 'F' => $this->colorizeTextBox('bg-red, fg-white', $item), 'I', 'N', 'D', 'R', 'W' => $this->colorizeTextBox('fg-yellow, bold', $item), 'S' => $this->colorizeTextBox('fg-cyan, bold', $item), - default => $item, + '.' => $item, }; $this->output->write($buffer); } diff --git a/src/WrapperRunner/WrapperRunner.php b/src/WrapperRunner/WrapperRunner.php index 5492e668..37dea913 100644 --- a/src/WrapperRunner/WrapperRunner.php +++ b/src/WrapperRunner/WrapperRunner.php @@ -54,6 +54,8 @@ final class WrapperRunner implements RunnerInterface /** @var list */ private array $progressFiles = []; /** @var list */ + private array $unexpectedOutputFiles = []; + /** @var list */ private array $testresultFiles = []; /** @var list */ private array $coverageFiles = []; @@ -165,6 +167,7 @@ private function flushWorker(WrapperWorker $worker): void $this->exitcode = max($this->exitcode, $worker->getExitCode()); $this->printer->printFeedback( $worker->progressFile, + $worker->unexpectedOutputFile, $this->teamcityFiles, ); $worker->reset(); @@ -208,9 +211,10 @@ private function startWorker(int $token): WrapperWorker $worker->start(); $this->batches[$token] = 0; - $this->statusFiles[] = $worker->statusFile; - $this->progressFiles[] = $worker->progressFile; - $this->testresultFiles[] = $worker->testresultFile; + $this->statusFiles[] = $worker->statusFile; + $this->progressFiles[] = $worker->progressFile; + $this->unexpectedOutputFiles[] = $worker->unexpectedOutputFile; + $this->testresultFiles[] = $worker->testresultFile; if (isset($worker->junitFile)) { $this->junitFiles[] = $worker->junitFile; @@ -300,6 +304,7 @@ private function complete(TestResult $testResultSum): int $this->clearFiles($this->statusFiles); $this->clearFiles($this->progressFiles); + $this->clearFiles($this->unexpectedOutputFiles); $this->clearFiles($this->testresultFiles); $this->clearFiles($this->coverageFiles); $this->clearFiles($this->junitFiles); diff --git a/src/WrapperRunner/WrapperWorker.php b/src/WrapperRunner/WrapperWorker.php index a6d9c5f5..2b58dc34 100644 --- a/src/WrapperRunner/WrapperWorker.php +++ b/src/WrapperRunner/WrapperWorker.php @@ -32,6 +32,7 @@ final class WrapperWorker public readonly SplFileInfo $statusFile; public readonly SplFileInfo $progressFile; + public readonly SplFileInfo $unexpectedOutputFile; public readonly SplFileInfo $testresultFile; public readonly SplFileInfo $junitFile; public readonly SplFileInfo $coverageFile; @@ -62,6 +63,8 @@ public function __construct( touch($this->statusFile->getPathname()); $this->progressFile = new SplFileInfo($commonTmpFilePath . 'progress'); touch($this->progressFile->getPathname()); + $this->unexpectedOutputFile = new SplFileInfo($commonTmpFilePath . 'unexpected_output'); + touch($this->unexpectedOutputFile->getPathname()); $this->testresultFile = new SplFileInfo($commonTmpFilePath . 'testresult'); if ($options->configuration->hasLogfileJunit()) { $this->junitFile = new SplFileInfo($commonTmpFilePath . 'junit'); @@ -83,6 +86,8 @@ public function __construct( $parameters[] = $this->statusFile->getPathname(); $parameters[] = '--progress-file'; $parameters[] = $this->progressFile->getPathname(); + $parameters[] = '--unexpected-output-file'; + $parameters[] = $this->unexpectedOutputFile->getPathname(); $parameters[] = '--testresult-file'; $parameters[] = $this->testresultFile->getPathname(); if (isset($this->teamcityFile)) { diff --git a/test/MemoryPrinter.php b/test/MemoryPrinter.php new file mode 100644 index 00000000..3c04f309 --- /dev/null +++ b/test/MemoryPrinter.php @@ -0,0 +1,37 @@ +memory .= $buffer; + } + + public function tail(): string + { + $memory = $this->memory; + $this->memory = ''; + + return $memory; + } + + public function flush(): void + { + $this->flushed = true; + } + + public function hasBeenFlushed(): bool + { + return $this->flushed; + } +} diff --git a/test/Unit/WrapperRunner/ProgressPrinterOutputTest.php b/test/Unit/WrapperRunner/ProgressPrinterOutputTest.php new file mode 100644 index 00000000..dc9b7d60 --- /dev/null +++ b/test/Unit/WrapperRunner/ProgressPrinterOutputTest.php @@ -0,0 +1,81 @@ +progress = new MemoryPrinter(); + $this->output = new MemoryPrinter(); + + $this->printer = new ProgressPrinterOutput( + $this->progress, + $this->output, + ); + } + + public function testSkipProgressRelatedContents(): void + { + $this->printer->print("\n"); + $this->printer->print(' '); + $this->printer->print(' '); + $this->printer->print(' 65 / 75 ( 86%)'); + $this->printer->print(' 2484 / 2484 (100%)'); + + self::assertSame('', $this->progress->tail()); + self::assertSame('', $this->output->tail()); + } + + public function testAProgressGoesIntoProgressTheRestInOutput(): void + { + foreach (['E', 'F', 'I', 'N', 'D', 'R', 'W', 'S', '.'] as $progress) { + $this->printer->print($progress); + } + + $this->printer->print('var_dump'); + + self::assertSame('EFINDRWS.', $this->progress->tail()); + self::assertSame('var_dump', $this->output->tail()); + + $this->printer->print('a '); + self::assertSame('', $this->progress->tail()); + self::assertSame('a ', $this->output->tail()); + + $this->printer->print(' z'); + self::assertSame('', $this->progress->tail()); + self::assertSame(' z', $this->output->tail()); + + $this->printer->print(' 65 / 75 ( 86%)'); + self::assertSame('', $this->progress->tail()); + self::assertSame(' 65 / 75 ( 86%)', $this->output->tail()); + + $this->printer->print(' 2484 / 2484 (100%) '); + self::assertSame('', $this->progress->tail()); + self::assertSame(' 2484 / 2484 (100%) ', $this->output->tail()); + } + + public function testFlushBoth(): void + { + self::assertFalse($this->progress->hasBeenFlushed()); + self::assertFalse($this->output->hasBeenFlushed()); + + $this->printer->flush(); + + self::assertTrue($this->progress->hasBeenFlushed()); + self::assertTrue($this->output->hasBeenFlushed()); + } +} diff --git a/test/Unit/WrapperRunner/ResultPrinterTest.php b/test/Unit/WrapperRunner/ResultPrinterTest.php index a35f8ffc..fc843be2 100644 --- a/test/Unit/WrapperRunner/ResultPrinterTest.php +++ b/test/Unit/WrapperRunner/ResultPrinterTest.php @@ -18,6 +18,8 @@ use function file_put_contents; use function phpversion; use function sprintf; +use function str_repeat; +use function touch; use function uniqid; use const DIRECTORY_SEPARATOR; @@ -132,14 +134,18 @@ public function testPrintFeedbackForMixed(): void { $this->printer->setTestCount(20); $feedbackFile = $this->tmpDir . DIRECTORY_SEPARATOR . 'feedback1'; - file_put_contents($feedbackFile, 'EWWFFFRRSSSS....... 19 / 19 (100%)'); - $this->printer->printFeedback(new SplFileInfo($feedbackFile), []); + $outputFile = $this->tmpDir . DIRECTORY_SEPARATOR . 'output1'; + file_put_contents($feedbackFile, 'EWWFFFRRSSSS.......'); + touch($outputFile); + $this->printer->printFeedback(new SplFileInfo($feedbackFile), new SplFileInfo($outputFile), []); $contents = $this->output->fetch(); self::assertSame('EWWFFFRRSSSS.......', $contents); $feedbackFile = $this->tmpDir . DIRECTORY_SEPARATOR . 'feedback2'; - file_put_contents($feedbackFile, 'E 1 / 1 (100%)'); - $this->printer->printFeedback(new SplFileInfo($feedbackFile), []); + $outputFile = $this->tmpDir . DIRECTORY_SEPARATOR . 'output2'; + file_put_contents($feedbackFile, 'E'); + touch($outputFile); + $this->printer->printFeedback(new SplFileInfo($feedbackFile), new SplFileInfo($outputFile), []); $contents = $this->output->fetch(); self::assertSame("E 20 / 20 (100%)\n", $contents); } @@ -150,8 +156,10 @@ public function testColorsForFailing(): void $this->printer = new ResultPrinter($this->output, $this->options); $this->printer->setTestCount(20); $feedbackFile = $this->tmpDir . DIRECTORY_SEPARATOR . 'feedback1'; + $outputFile = $this->tmpDir . DIRECTORY_SEPARATOR . 'output1'; file_put_contents($feedbackFile, 'E'); - $this->printer->printFeedback(new SplFileInfo($feedbackFile), []); + touch($outputFile); + $this->printer->printFeedback(new SplFileInfo($feedbackFile), new SplFileInfo($outputFile), []); $contents = $this->output->fetch(); self::assertStringContainsString('E', $contents); self::assertStringContainsString('31;1', $contents); @@ -169,9 +177,11 @@ public function testTeamcityFeedbackOnFile(): void $this->printer->setTestCount(20); $feedbackFile = $this->tmpDir . DIRECTORY_SEPARATOR . 'feedback1'; + $outputFile = $this->tmpDir . DIRECTORY_SEPARATOR . 'output1'; file_put_contents($feedbackFile, 'E'); + touch($outputFile); - $this->printer->printFeedback(new SplFileInfo($feedbackFile), [new SplFileInfo($teamcitySource)]); + $this->printer->printFeedback(new SplFileInfo($feedbackFile), new SplFileInfo($outputFile), [new SplFileInfo($teamcitySource)]); self::assertSame('E', $this->output->fetch()); self::assertFileExists($teamcityLog); @@ -193,9 +203,11 @@ public function testTeamcityFeedbackOnStdout(): void $this->printer->setTestCount(20); $feedbackFile = $this->tmpDir . DIRECTORY_SEPARATOR . 'feedback1'; + $outputFile = $this->tmpDir . DIRECTORY_SEPARATOR . 'output1'; file_put_contents($feedbackFile, 'E'); + touch($outputFile); - $this->printer->printFeedback(new SplFileInfo($feedbackFile), [new SplFileInfo($teamcitySource)]); + $this->printer->printFeedback(new SplFileInfo($feedbackFile), new SplFileInfo($outputFile), [new SplFileInfo($teamcitySource)]); $this->printer->printResults($this->getEmptyTestResult(), [new SplFileInfo($teamcitySource)], []); self::assertSame($teamcitySourceContent, $this->output->fetch()); @@ -212,9 +224,11 @@ public function testTestdoxOutputWithProgress(): void $this->printer->setTestCount(20); $feedbackFile = $this->tmpDir . DIRECTORY_SEPARATOR . 'feedback1'; + $outputFile = $this->tmpDir . DIRECTORY_SEPARATOR . 'output1'; file_put_contents($feedbackFile, 'EEE'); + touch($outputFile); - $this->printer->printFeedback(new SplFileInfo($feedbackFile), []); + $this->printer->printFeedback(new SplFileInfo($feedbackFile), new SplFileInfo($outputFile), []); $this->printer->printResults($this->getEmptyTestResult(), [], [new SplFileInfo($testdoxSource)]); self::assertSame('EEE' . $testdoxSourceContent, $this->output->fetch()); @@ -231,9 +245,11 @@ public function testTestdoxOutputWithoutProgress(): void $this->printer->setTestCount(20); $feedbackFile = $this->tmpDir . DIRECTORY_SEPARATOR . 'feedback1'; + $outputFile = $this->tmpDir . DIRECTORY_SEPARATOR . 'output1'; file_put_contents($feedbackFile, 'EEE'); + touch($outputFile); - $this->printer->printFeedback(new SplFileInfo($feedbackFile), []); + $this->printer->printFeedback(new SplFileInfo($feedbackFile), new SplFileInfo($outputFile), []); $this->printer->printResults($this->getEmptyTestResult(), [], [new SplFileInfo($testdoxSource)]); self::assertSame($testdoxSourceContent, $this->output->fetch()); @@ -241,7 +257,7 @@ public function testTestdoxOutputWithoutProgress(): void public function testPrintFeedbackFromMultilineSource(): void { - $source = <<<'EOF' + $expected = <<<'EOF' ............................................................... 63 / 300 ( 21%) ............................................................... 126 / 300 ( 42%) ............................................................... 189 / 300 ( 63%) @@ -254,43 +270,16 @@ public function testPrintFeedbackFromMultilineSource(): void $this->printer->start(); $this->output->fetch(); $feedbackFile = $this->tmpDir . DIRECTORY_SEPARATOR . 'feedback1'; - file_put_contents($feedbackFile, $source); - $this->printer->printFeedback(new SplFileInfo($feedbackFile), []); + $outputFile = $this->tmpDir . DIRECTORY_SEPARATOR . 'output1'; + file_put_contents($feedbackFile, str_repeat('.', 300)); + touch($outputFile); + $this->printer->printFeedback(new SplFileInfo($feedbackFile), new SplFileInfo($outputFile), []); $contents = $this->output->fetch(); - self::assertSame($source, $contents); + self::assertSame($expected, $contents); } public function testPrintFeedbackFromMultilineSource2(): void { - $source = <<<'EOF' - ..................................................................................................... 101 / 2484 ( 4%) - ..................................................................................................... 202 / 2484 ( 8%) - ..................................................................................................... 303 / 2484 ( 12%) - ..................................................................................................... 404 / 2484 ( 16%) - ..................................................................................................... 505 / 2484 ( 20%) - ..................................................................................................... 606 / 2484 ( 24%) - ..................................................................................................... 707 / 2484 ( 28%) - ..................................................................................................... 808 / 2484 ( 32%) - ..................................................................................................... 909 / 2484 ( 36%) - ..................................................................................................... 1010 / 2484 ( 40%) - ..................................................................................................... 1111 / 2484 ( 44%) - ..................................................................................................... 1212 / 2484 ( 48%) - ..................................................................................................... 1313 / 2484 ( 52%) - ..................................................................................................... 1414 / 2484 ( 56%) - ..................................................................................................... 1515 / 2484 ( 60%) - ..................................................................................................... 1616 / 2484 ( 65%) - ..................................................................................................... 1717 / 2484 ( 69%) - ..................................................................................................... 1818 / 2484 ( 73%) - ..................................................................................................... 1919 / 2484 ( 77%) - ..................................................................................................... 2020 / 2484 ( 81%) - ..................................................................................................... 2121 / 2484 ( 85%) - ..................................................................................................... 2222 / 2484 ( 89%) - ..................................................................................................... 2323 / 2484 ( 93%) - ..................................................................................................... 2424 / 2484 ( 97%) - ............................................................ 2484 / 2484 (100%) - - EOF; - $expected = <<<'EOF' ............................................................. 61 / 2484 ( 2%) ............................................................. 122 / 2484 ( 4%) @@ -340,8 +329,10 @@ public function testPrintFeedbackFromMultilineSource2(): void $this->printer->start(); $this->output->fetch(); $feedbackFile = $this->tmpDir . DIRECTORY_SEPARATOR . 'feedback1'; - file_put_contents($feedbackFile, $source); - $this->printer->printFeedback(new SplFileInfo($feedbackFile), []); + $outputFile = $this->tmpDir . DIRECTORY_SEPARATOR . 'output1'; + file_put_contents($feedbackFile, str_repeat('.', 2484)); + touch($outputFile); + $this->printer->printFeedback(new SplFileInfo($feedbackFile), new SplFileInfo($outputFile), []); $contents = $this->output->fetch(); self::assertSame($expected, $contents); } diff --git a/test/Unit/WrapperRunner/WrapperRunnerTest.php b/test/Unit/WrapperRunner/WrapperRunnerTest.php index a870137a..f39a8b46 100644 --- a/test/Unit/WrapperRunner/WrapperRunnerTest.php +++ b/test/Unit/WrapperRunner/WrapperRunnerTest.php @@ -515,6 +515,49 @@ public function testIgnoreAttributes(): void self::assertStringContainsString($expectedContains, $runnerResult->output); } + public function testHandleUnexpectedOutput(): void + { + $this->bareOptions['path'] = $this->fixture('unexpected_output' . DIRECTORY_SEPARATOR . 'UnexpectedOutputTest.php'); + + $expectedOutput = <<<'EOF' +Processes: 2 +Runtime: PHP %s + +foobar. 1 / 1 (100%) + +Time: %s, Memory: %s MB + +OK%a +EOF; + + $runnerResult = $this->runRunner(); + self::assertStringMatchesFormat($expectedOutput, $runnerResult->output); + + $this->bareOptions['--disallow-test-output'] = true; + + $expectedOutput = <<<'EOF' +Processes: 2 +Runtime: PHP %s + +foobarR 1 / 1 (100%) + +Time: %s, Memory: %s MB + +There was 1 risky test: + +1) ParaTest\Tests\fixtures\unexpected_output\UnexpectedOutputTest::testInvalidLogic +This test printed output: foobar + +%s/test/fixtures/unexpected_output/UnexpectedOutputTest.php:%d + +OK, but there are issues! +%a +EOF; + + $runnerResult = $this->runRunner(); + self::assertStringMatchesFormat($expectedOutput, $runnerResult->output); + } + public function testProcessIsolation(): void { $this->bareOptions['path'] = $this->fixture('process_isolation' . DIRECTORY_SEPARATOR . 'FooTest.php'); diff --git a/test/fixtures/unexpected_output/UnexpectedOutputTest.php b/test/fixtures/unexpected_output/UnexpectedOutputTest.php new file mode 100644 index 00000000..c8ef0491 --- /dev/null +++ b/test/fixtures/unexpected_output/UnexpectedOutputTest.php @@ -0,0 +1,18 @@ +assertTrue(true); + } +}