From 530b2ff6c9e645fc7df9e6d5b2186e0a07d681f5 Mon Sep 17 00:00:00 2001 From: dannc Date: Thu, 21 Sep 2023 14:58:17 +0700 Subject: [PATCH] add tests --- .../Command/CloverMergeCommandTest.php | 283 ++++++++++++++++++ tests/Helpers/Traits/MakeCliApplication.php | 20 ++ tests/Pest.php | 59 ++++ tests/TestCase.php | 11 + .../Handler/HandleSingleDocumentTest.php | 95 ++++++ tests/Unit/Clover/Handler/HandleTest.php | 60 ++++ tests/Unit/Clover/RendererTest.php | 101 +++++++ tests/Unit/ExampleTest.php | 7 + tests/data/.gitignore | 3 + .../data/examples/empty-file-with-package.xml | 8 + .../examples/empty-file-without-package.xml | 7 + tests/data/examples/empty-package.xml | 6 + tests/data/examples/empty-project.xml | 4 + tests/data/examples/file-with-bad-line.xml | 14 + tests/data/examples/file-with-differences.xml | 16 + tests/data/examples/file-with-errors.xml | 10 + tests/data/examples/file-with-junk.xml | 22 ++ tests/data/examples/file-with-no-name.xml | 6 + tests/data/examples/file-with-package.xml | 14 + tests/data/examples/file-without-package.xml | 12 + tests/data/examples/metrics-and-classes.xml | 30 ++ tests/data/examples/minimal.xml | 2 + tests/data/examples/non-clover.xml | 4 + 23 files changed, 794 insertions(+) create mode 100644 tests/Feature/Command/CloverMergeCommandTest.php create mode 100644 tests/Helpers/Traits/MakeCliApplication.php create mode 100644 tests/Pest.php create mode 100644 tests/TestCase.php create mode 100644 tests/Unit/Clover/Handler/HandleSingleDocumentTest.php create mode 100644 tests/Unit/Clover/Handler/HandleTest.php create mode 100644 tests/Unit/Clover/RendererTest.php create mode 100644 tests/Unit/ExampleTest.php create mode 100644 tests/data/.gitignore create mode 100644 tests/data/examples/empty-file-with-package.xml create mode 100644 tests/data/examples/empty-file-without-package.xml create mode 100644 tests/data/examples/empty-package.xml create mode 100644 tests/data/examples/empty-project.xml create mode 100644 tests/data/examples/file-with-bad-line.xml create mode 100644 tests/data/examples/file-with-differences.xml create mode 100644 tests/data/examples/file-with-errors.xml create mode 100644 tests/data/examples/file-with-junk.xml create mode 100644 tests/data/examples/file-with-no-name.xml create mode 100644 tests/data/examples/file-with-package.xml create mode 100644 tests/data/examples/file-without-package.xml create mode 100644 tests/data/examples/metrics-and-classes.xml create mode 100644 tests/data/examples/minimal.xml create mode 100644 tests/data/examples/non-clover.xml diff --git a/tests/Feature/Command/CloverMergeCommandTest.php b/tests/Feature/Command/CloverMergeCommandTest.php new file mode 100644 index 0000000..ea6efd1 --- /dev/null +++ b/tests/Feature/Command/CloverMergeCommandTest.php @@ -0,0 +1,283 @@ +makeCliApp(); + $cliApp->add( + new CloverMergeCommand( + new Handler(new Parser()), + new Renderer(), + $cliApp, + ), + ); + + $mergedPath = '/tmp/merged.xml'; + \expect($mergedPath)->not->toBeFile(); + + $cliApp->handle([ + './merger', + 'clover', + '-o', + $mergedPath, + \getExamplePath('metrics-and-classes.xml'), + \getExamplePath('file-with-differences.xml'), + ]); + + \expect($mergedPath)->toBeFile()->toBeReadableFile(); + \expect(\file_get_contents($mergedPath))->toBeString() + ->not->toBeEmpty(); +}); + +\test('merge two files with workdir', function (): void { + $cliApp = $this->makeCliApp(); + $cliApp->add( + new CloverMergeCommand( + new Handler(new Parser()), + new Renderer(), + $cliApp, + ), + ); + + $filename1 = 'metrics-and-classes.xml'; + $filename2 = 'file-with-differences.xml'; + + $dir = '/tmp/temp_source'; + if (\is_dir($dir) === false) { + \mkdir($dir); + } + \copy(\getExamplePath($filename1), "{$dir}/{$filename1}"); + \copy(\getExamplePath($filename2), "{$dir}/{$filename2}"); + + $mergedPath = 'some_merged.xml'; + $mergedFullPath = "{$dir}/{$mergedPath}"; + \expect($mergedFullPath)->not->toBeFile(); + + $cliApp->handle([ + './merger', + 'clover', + '-o', + $mergedPath, + '-w', + $dir, + $filename1, + $filename2, + ]); + + \expect($mergedFullPath)->toBeFile()->toBeReadableFile(); + \expect(\file_get_contents($mergedFullPath))->toBeString() + ->not->toBeEmpty(); + + \unlink($mergedFullPath); +}); + +\test('merge two files no workdir with stats', function (): void { + $cliApp = $this->makeCliApp(); + + $interactorMock = \Mockery::mock(Interactor::class); + $interactorMock->shouldReceive('info') + ->once() + ->with( + 'Files Discovered: 2', + true + ) + ->andReturn(\Mockery::mock(Writer::class)); + $interactorMock->shouldReceive('info') + ->once() + ->with( + 'Final Coverage: 5/5 (100.00%)', + true + ) + ->andReturn(\Mockery::mock(Writer::class)); + + $cliApp->io($interactorMock); + $cliApp->add( + new CloverMergeCommand( + new Handler(new Parser()), + new Renderer(), + $cliApp, + ), + ); + + $mergedPath = '/tmp/merged.xml'; + \expect($mergedPath)->not->toBeFile(); + + $cliApp->handle([ + './merger', + 'clover', + '-o', + $mergedPath, + '-s', + \getExamplePath('empty-package.xml'), + \getExamplePath('file-with-differences.xml'), + ]); + + \expect($mergedPath)->toBeFile()->toBeReadableFile(); + \expect(\file_get_contents($mergedPath))->toBeString() + ->not->toBeEmpty(); +}); + +\test('merge two files error file not exist', function (): void { + $cliApp = $this->makeCliApp(); + $cliApp->add( + new CloverMergeCommand( + new Handler(new Parser()), + new Renderer(), + $cliApp, + ), + ); + + $mergedPath = '/tmp/merged.xml'; + \expect($mergedPath)->not->toBeFile(); + + $badFilePath = \getExamplePath('some-very-bad-file.xml'); + + $this->expectException(ExecuteException::class); + $this->expectExceptionMessage("File {$badFilePath} does not exists or not readable"); + + $cliApp->handle([ + './merger', + 'clover', + '-o', + $mergedPath, + \getExamplePath('metrics-and-classes.xml'), + $badFilePath, + ]); +}); + +\test('merge two files error file not xml', function (): void { + $cliApp = $this->makeCliApp(); + $cliApp->add( + new CloverMergeCommand( + new Handler(new Parser()), + new Renderer(), + $cliApp, + ), + ); + + $mergedPath = '/tmp/merged.xml'; + \expect($mergedPath)->not->toBeFile(); + + $badFilePath = \getExamplePath('not-xml.json'); + + $this->expectException(ExecuteException::class); + $this->expectExceptionMessage("Unable to parse file {$badFilePath}"); + + $cliApp->handle([ + './merger', + 'clover', + '-o', + $mergedPath, + \getExamplePath('metrics-and-classes.xml'), + $badFilePath, + ]); +}); + +\test('merge two files error workdir not exist', function (): void { + $cliApp = $this->makeCliApp(); + $cliApp->add( + new CloverMergeCommand( + new Handler(new Parser()), + new Renderer(), + $cliApp, + ), + ); + + $mergedPath = 'tmp/merged.xml'; + \expect($mergedPath)->not->toBeFile(); + + $badFilePath = \getExamplePath('some-very-bad-file.xml'); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid argument workdir'); + + $cliApp->handle([ + './merger', + 'clover', + '-o', + $mergedPath, + '-w', + '/tmp/foo/bar/baz', + \getExamplePath('metrics-and-classes.xml'), + \getExamplePath('file-with-differences.xml'), + ]); +}); + +\test('merge two files error no arguments', function (): void { + $cliApp = $this->makeCliApp(); + $cliApp->add( + new CloverMergeCommand( + new Handler(new Parser()), + new Renderer(), + $cliApp, + ), + ); + + $mergedPath = '/tmp/merged.xml'; + \expect($mergedPath)->not->toBeFile(); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid argument files'); + + $cliApp->handle([ + './merger', + 'clover', + '-o', + $mergedPath, + ]); +}); + +\test('merge two files error in handler', function (): void { + $cliApp = $this->makeCliApp(); + + $handlerMock = \Mockery::mock(Handler::class); + $handlerMock->shouldReceive('handle') + ->once() + ->with( + \Mockery::type(\SimpleXMLElement::class), + \Mockery::type(\SimpleXMLElement::class), + ) + ->andThrow(new \RuntimeException('some error')); + + $cliApp->add( + new CloverMergeCommand( + $handlerMock, + new Renderer(), + $cliApp, + ), + ); + + $mergedPath = '/tmp/merged.xml'; + \expect($mergedPath)->not->toBeFile(); + + $this->expectException(ExecuteException::class); + $this->expectExceptionMessage('some error'); + + $cliApp->handle([ + './merger', + 'clover', + '-o', + $mergedPath, + \getExamplePath('metrics-and-classes.xml'), + \getExamplePath('file-with-differences.xml'), + ]); +}); diff --git a/tests/Helpers/Traits/MakeCliApplication.php b/tests/Helpers/Traits/MakeCliApplication.php new file mode 100644 index 0000000..540fc54 --- /dev/null +++ b/tests/Helpers/Traits/MakeCliApplication.php @@ -0,0 +1,20 @@ + true); + $cliApp->onException(static function (\Throwable $exception): void { + throw $exception; + }); + + return $cliApp; + } +} diff --git a/tests/Pest.php b/tests/Pest.php new file mode 100644 index 0000000..9e8eceb --- /dev/null +++ b/tests/Pest.php @@ -0,0 +1,59 @@ +group('unit')->in('Unit'); +\uses(TestCase::class)->group('feature')->in('Feature'); + +/* +|-------------------------------------------------------------------------- +| Expectations +|-------------------------------------------------------------------------- +| +| When you're writing tests, you often need to check that values meet certain conditions. The +| "expect()" function gives you access to a set of "expectations" methods that you can use +| to assert different things. Of course, you may extend the Expectation API at any time. +| +*/ + +\expect()->extend('toBeOne', function () { + return $this->toBe(1); +}); + +\expect()->extend('toMatchCallback', function (callable $callback, string $message = '') { + $result = $callback($this->value); + \expect($result)->toBeTrue($message); + + return $this; +}); + +/* +|-------------------------------------------------------------------------- +| Functions +|-------------------------------------------------------------------------- +| +| While Pest is very powerful out-of-the-box, you may have some testing code specific to your +| project that you don't want to repeat in every file. Here you can also expose helpers as +| global functions to help you to reduce the number of lines of code in your test files. +| +*/ + +function getExamplePath(string $filename): string +{ + return __DIR__ . "/data/examples/{$filename}"; +} diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..6e86c3b --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,11 @@ +handleSingleDocument( + \simplexml_load_string($cloverContents), + ); + + $files = $accumulator->getFiles(); + \expect($files)->toHaveCount(0); +})->with([ + 'empty-package.xml', + 'empty-project.xml', + 'file-with-errors.xml', + 'file-with-no-name.xml', + 'minimal.xml', +]); + +\test('examples with single file', function ( + string $exampleFilename, + string $expectedFilename, + int $expectedClassesCount, + int $expectedLinesCount, +): void { + $handler = new Handler(new Parser()); + + $cloverContents = \file_get_contents(\getExamplePath($exampleFilename)); + + $accumulator = $handler->handleSingleDocument( + \simplexml_load_string($cloverContents), + ); + + $files = $accumulator->getFiles(); + \expect($files)->toHaveCount(1)->toHaveKey($expectedFilename); + + $file = $files[$expectedFilename]; + \expect($file)->toBeInstanceOf(FileDto::class); + \expect($file->getClasses())->toHaveCount($expectedClassesCount); + \expect($file->getLines())->toHaveCount($expectedLinesCount); +}) + ->with([ + ['empty-file-with-package.xml', 'test.php', 0, 0], + ['file-with-package.xml', 'test.php', 0, 5], + ['file-without-package.xml', 'test.php', 0, 4], + ['metrics-and-classes.xml', '/src/Example/Namespace/Class.php', 1, 16], + ]); + +\test('examples with two files', function ( + string $exampleFilename, + string $expectedFilename1, + string $expectedFilename2, +): void { + $handler = new Handler(new Parser()); + + $cloverContents = \file_get_contents(\getExamplePath($exampleFilename)); + + $accumulator = $handler->handleSingleDocument( + \simplexml_load_string($cloverContents), + ); + + $files = $accumulator->getFiles(); + \expect($files)->toHaveCount(2) + ->toHaveKey($expectedFilename1) + ->toHaveKey($expectedFilename2); +}) + ->with([ + ['empty-file-without-package.xml', 'test.php', 'other.php'], + ['file-with-differences.xml', 'test.php', 'other.php'], + ]); + +\test('examples with invalid structure', function (string $exampleFilename): void { + $handler = new Handler(new Parser()); + + $cloverContents = \file_get_contents(\getExamplePath($exampleFilename)); + + $this->expectException(HandleException::class); + $handler->handleSingleDocument( + \simplexml_load_string($cloverContents), + ); +}) + ->with([ + 'file-with-bad-line.xml', + 'file-with-junk.xml', + 'non-clover.xml', + ]); diff --git a/tests/Unit/Clover/Handler/HandleTest.php b/tests/Unit/Clover/Handler/HandleTest.php new file mode 100644 index 0000000..2a2101d --- /dev/null +++ b/tests/Unit/Clover/Handler/HandleTest.php @@ -0,0 +1,60 @@ +handle( + \simplexml_load_string($fileWithPackage), + \simplexml_load_string($fileWithoutPackage), + \simplexml_load_string($fileWithDifferences), + \simplexml_load_string($metricsAndClasses), + ); + + $files = $accumulator->getFiles(); + \expect($files)->toHaveCount(3) + ->toHaveKey('test.php') + ->toHaveKey('other.php') + ->toHaveKey('/src/Example/Namespace/Class.php') + ->each->toBeInstanceOf(FileDto::class); + + $testFile = $files['test.php']; + \expect($testFile->getClasses())->toHaveCount(0); + $testFileLines = $testFile->getLines(); + \expect($testFileLines)->toHaveCount(7) + ->toHaveKeys([1, 2, 3, 4, 5, 6, 8]) + ->each->toBeInstanceOf(LineDto::class) + ->toMatchCallback(fn (LineDto $line): bool => match ($line->getNum()) { + 1 => $line->getCount() === 0, + 2, 8 => $line->getCount() === 3, + 3, 5 => $line->getCount() === 4, + 6 => $line->getCount() === 1, + 4 => $line->getCount() === 9, + default => true, + }); + + $classFile = $files['/src/Example/Namespace/Class.php']; + \expect($classFile->getClasses())->toHaveCount(1) + ->each->toBeInstanceOf(ClassDto::class) + ->toMatchCallback(function (ClassDto $class): bool { + $properties = $class->getProperties(); + + return $properties['name'] === 'Example\Namespace\Class' + && $properties['namespace'] === 'Example\Namespace'; + }); +}); + +// todo merge multiple files with empty report +// todo merge multiple files with invalid report diff --git a/tests/Unit/Clover/RendererTest.php b/tests/Unit/Clover/RendererTest.php new file mode 100644 index 0000000..d0e638a --- /dev/null +++ b/tests/Unit/Clover/RendererTest.php @@ -0,0 +1,101 @@ +mergeLine(1, new LineDto(['num' => '1', 'type' => 'stmt'], 2)); + $file1->mergeLine(2, new LineDto(['num' => '2', 'type' => 'stmt'], 3)); + + $file2 = new FileDto($package); + $file2->mergeLine(22, new LineDto([ + 'num' => '22', + 'type' => 'method', + 'name' => '__construct', + 'visibility' => 'public', + 'complexity' => '7', + 'crap' => '8.23', + ], 1)); + $file2->mergeLine(24, new LineDto(['num' => '24', 'type' => 'stmt'], 3)); + $file2->mergeClass('Example\Namespace\Class', new ClassDto([ + 'name' => 'Example\Namespace\Class', + 'namespace' => 'Example\Namespace', + ])); + + $file3 = new FileDto(); + $file3->mergeLine(34, new LineDto(['num' => '34', 'type' => 'cond'], 0)); + $file3->mergeLine(38, new LineDto(['num' => '38', 'type' => 'cond'], 1)); + + $accumulator->addFile('test1.php', $file1); + $accumulator->addFile('test2.php', $file2); + $accumulator->addFile('test3.php', $file3); + + $renderer = new Renderer(); + $result = $renderer->renderAccumulator($accumulator); + + \expect($result)->not->toBeEmpty() + ->toHaveCount(2); + + $resultXmlString = $result[0]; + \expect($resultXmlString)->toBeString() + ->not->toBeEmpty() + ->toStartWith("\ntoBeInstanceOf(\SimpleXMLElement::class) + ->toMatchCallback(function (\SimpleXMLElement $actualCoverage) use ($package): bool { + $packageXpath = "/coverage/project/package[@name=\"{$package}\"]"; + $packageFiles = $actualCoverage->xpath("{$packageXpath}/file"); + \expect($packageFiles)->toBeArray()->toHaveCount(2); + + $nonPackageFiles = $actualCoverage->xpath('/coverage/project/file'); + \expect($nonPackageFiles)->toBeArray()->toHaveCount(1); + + $packageMetrics = $actualCoverage->xpath("{$packageXpath}/metrics")[0]; + \expect($packageMetrics)->toBeInstanceOf(\SimpleXMLElement::class); + \expect($packageMetrics->attributes()) + ->toMatchCallback( + static fn (\SimpleXMLElement $attributes): bool => (int) $attributes->elements === 4 + && (int) $attributes->coveredelements === 4 + && (int) $attributes->conditionals === 0 + && (int) $attributes->coveredconditionals === 0 + && (int) $attributes->statements === 3 + && (int) $attributes->coveredstatements === 3 + && (int) $attributes->methods === 1 + && (int) $attributes->coveredmethods === 1 + && (int) $attributes->classes === 1, + 'invalid package metrics', + ); + + $projectMetrics = $actualCoverage->xpath('/coverage/project/metrics')[0]; + \expect($projectMetrics)->toBeInstanceOf(\SimpleXMLElement::class); + \expect($projectMetrics->attributes()) + ->toMatchCallback( + static fn (\SimpleXMLElement $attributes): bool => (int) $attributes->elements === 6 + && (int) $attributes->coveredelements === 5 + && (int) $attributes->conditionals === 2 + && (int) $attributes->coveredconditionals === 1 + && (int) $attributes->statements === 3 + && (int) $attributes->coveredstatements === 3 + && (int) $attributes->methods === 1 + && (int) $attributes->coveredmethods === 1 + && (int) $attributes->classes === 1, + 'invalid project metrics', + ); + + return true; + }); + + $resultMetrics = $result[1]; + \expect($resultMetrics)->toBeInstanceOf(Metrics::class); +}); diff --git a/tests/Unit/ExampleTest.php b/tests/Unit/ExampleTest.php new file mode 100644 index 0000000..8ae95d7 --- /dev/null +++ b/tests/Unit/ExampleTest.php @@ -0,0 +1,7 @@ +toBeTrue(); +}); diff --git a/tests/data/.gitignore b/tests/data/.gitignore new file mode 100644 index 0000000..74eec5a --- /dev/null +++ b/tests/data/.gitignore @@ -0,0 +1,3 @@ +* +!examples/ +!.gitignore diff --git a/tests/data/examples/empty-file-with-package.xml b/tests/data/examples/empty-file-with-package.xml new file mode 100644 index 0000000..81d4757 --- /dev/null +++ b/tests/data/examples/empty-file-with-package.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/tests/data/examples/empty-file-without-package.xml b/tests/data/examples/empty-file-without-package.xml new file mode 100644 index 0000000..af7dc89 --- /dev/null +++ b/tests/data/examples/empty-file-without-package.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/tests/data/examples/empty-package.xml b/tests/data/examples/empty-package.xml new file mode 100644 index 0000000..1fbc166 --- /dev/null +++ b/tests/data/examples/empty-package.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/tests/data/examples/empty-project.xml b/tests/data/examples/empty-project.xml new file mode 100644 index 0000000..41f6019 --- /dev/null +++ b/tests/data/examples/empty-project.xml @@ -0,0 +1,4 @@ + + + + diff --git a/tests/data/examples/file-with-bad-line.xml b/tests/data/examples/file-with-bad-line.xml new file mode 100644 index 0000000..ee9738e --- /dev/null +++ b/tests/data/examples/file-with-bad-line.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/tests/data/examples/file-with-differences.xml b/tests/data/examples/file-with-differences.xml new file mode 100644 index 0000000..9753fe9 --- /dev/null +++ b/tests/data/examples/file-with-differences.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/tests/data/examples/file-with-errors.xml b/tests/data/examples/file-with-errors.xml new file mode 100644 index 0000000..119ca07 --- /dev/null +++ b/tests/data/examples/file-with-errors.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/tests/data/examples/file-with-junk.xml b/tests/data/examples/file-with-junk.xml new file mode 100644 index 0000000..67822d3 --- /dev/null +++ b/tests/data/examples/file-with-junk.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/data/examples/file-with-no-name.xml b/tests/data/examples/file-with-no-name.xml new file mode 100644 index 0000000..8608a6a --- /dev/null +++ b/tests/data/examples/file-with-no-name.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/tests/data/examples/file-with-package.xml b/tests/data/examples/file-with-package.xml new file mode 100644 index 0000000..05d9e5d --- /dev/null +++ b/tests/data/examples/file-with-package.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/tests/data/examples/file-without-package.xml b/tests/data/examples/file-without-package.xml new file mode 100644 index 0000000..5e3cb45 --- /dev/null +++ b/tests/data/examples/file-without-package.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/tests/data/examples/metrics-and-classes.xml b/tests/data/examples/metrics-and-classes.xml new file mode 100644 index 0000000..c366a19 --- /dev/null +++ b/tests/data/examples/metrics-and-classes.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/data/examples/minimal.xml b/tests/data/examples/minimal.xml new file mode 100644 index 0000000..e1cbd1e --- /dev/null +++ b/tests/data/examples/minimal.xml @@ -0,0 +1,2 @@ + + diff --git a/tests/data/examples/non-clover.xml b/tests/data/examples/non-clover.xml new file mode 100644 index 0000000..4803d11 --- /dev/null +++ b/tests/data/examples/non-clover.xml @@ -0,0 +1,4 @@ + + John Doe + CEO, Widget Inc. +