From d92228612c27c2b036a4b40888c64124732ae2b3 Mon Sep 17 00:00:00 2001 From: Matteo Beccati Date: Tue, 9 Apr 2024 19:50:15 +0200 Subject: [PATCH] Support for the PHPUnit dialect of JUnit --- __tests__/__outputs__/phpunit-test-results.md | 38 ++++ .../java-junit-phpunit.test.ts.snap | 188 ++++++++++++++++++ .../empty/java-junit-empty-phpunit.xml | 2 + __tests__/fixtures/external/java/phpunit.xml | 79 ++++++++ __tests__/java-junit-phpunit.test.ts | 46 +++++ dist/index.js | 22 +- src/parsers/java-junit/java-junit-parser.ts | 27 ++- src/parsers/java-junit/java-junit-types.ts | 3 +- 8 files changed, 385 insertions(+), 20 deletions(-) create mode 100644 __tests__/__outputs__/phpunit-test-results.md create mode 100644 __tests__/__snapshots__/java-junit-phpunit.test.ts.snap create mode 100644 __tests__/fixtures/empty/java-junit-empty-phpunit.xml create mode 100644 __tests__/fixtures/external/java/phpunit.xml create mode 100644 __tests__/java-junit-phpunit.test.ts diff --git a/__tests__/__outputs__/phpunit-test-results.md b/__tests__/__outputs__/phpunit-test-results.md new file mode 100644 index 00000000..af8ee4af --- /dev/null +++ b/__tests__/__outputs__/phpunit-test-results.md @@ -0,0 +1,38 @@ +![Tests failed](https://img.shields.io/badge/tests-10%20passed%2C%202%20failed-critical) +## ❌ fixtures/external/java/phpunit.xml +**12** tests were completed in **148ms** with **10** passed, **2** failed and **0** skipped. +|Test suite|Passed|Failed|Skipped|Time| +|:---|---:|---:|---:|---:| +|[CLI Arguments](#r0s0)||2❌||140ms| +|[PHPUnit\Event\CollectingDispatcherTest](#r0s1)|2✅|||4ms| +|[PHPUnit\Event\DeferringDispatcherTest](#r0s2)|4✅|||3ms| +|[PHPUnit\Event\DirectDispatcherTest](#r0s3)|4✅|||1ms| +### ❌ CLI Arguments +``` +❌ targeting-traits-with-coversclass-attribute-is-deprecated.phpt + targeting-traits-with-coversclass-attribute-is-deprecated.phptFailed asserting that string matches format description. +❌ targeting-traits-with-usesclass-attribute-is-deprecated.phpt + targeting-traits-with-usesclass-attribute-is-deprecated.phptFailed asserting that string matches format description. +``` +### ✅ PHPUnit\Event\CollectingDispatcherTest +``` +PHPUnit.Event.CollectingDispatcherTest + ✅ testHasNoCollectedEventsWhenFlushedImmediatelyAfterCreation + ✅ testCollectsDispatchedEventsUntilFlushed +``` +### ✅ PHPUnit\Event\DeferringDispatcherTest +``` +PHPUnit.Event.DeferringDispatcherTest + ✅ testCollectsEventsUntilFlush + ✅ testFlushesCollectedEvents + ✅ testSubscriberCanBeRegistered + ✅ testTracerCanBeRegistered +``` +### ✅ PHPUnit\Event\DirectDispatcherTest +``` +PHPUnit.Event.DirectDispatcherTest + ✅ testDispatchesEventToKnownSubscribers + ✅ testDispatchesEventToTracers + ✅ testRegisterRejectsUnknownSubscriber + ✅ testDispatchRejectsUnknownEventType +``` \ No newline at end of file diff --git a/__tests__/__snapshots__/java-junit-phpunit.test.ts.snap b/__tests__/__snapshots__/java-junit-phpunit.test.ts.snap new file mode 100644 index 00000000..627a936c --- /dev/null +++ b/__tests__/__snapshots__/java-junit-phpunit.test.ts.snap @@ -0,0 +1,188 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`java-junit phpunit tests report from phpunit test results matches snapshot 1`] = ` +TestRunResult { + "path": "fixtures/external/java/phpunit.xml", + "suites": [ + TestSuiteResult { + "groups": [ + TestGroupResult { + "name": "PHPUnit.Event.CollectingDispatcherTest", + "tests": [ + TestCaseResult { + "error": undefined, + "name": "testHasNoCollectedEventsWhenFlushedImmediatelyAfterCreation", + "result": "success", + "time": 1.441, + }, + TestCaseResult { + "error": undefined, + "name": "testCollectsDispatchedEventsUntilFlushed", + "result": "success", + "time": 2.815, + }, + ], + }, + ], + "name": "PHPUnit\\Event\\CollectingDispatcherTest", + "totalTime": 4.256, + }, + TestSuiteResult { + "groups": [ + TestGroupResult { + "name": "PHPUnit.Event.DeferringDispatcherTest", + "tests": [ + TestCaseResult { + "error": undefined, + "name": "testCollectsEventsUntilFlush", + "result": "success", + "time": 1.6720000000000002, + }, + TestCaseResult { + "error": undefined, + "name": "testFlushesCollectedEvents", + "result": "success", + "time": 0.661, + }, + TestCaseResult { + "error": undefined, + "name": "testSubscriberCanBeRegistered", + "result": "success", + "time": 0.33399999999999996, + }, + TestCaseResult { + "error": undefined, + "name": "testTracerCanBeRegistered", + "result": "success", + "time": 0.262, + }, + ], + }, + ], + "name": "PHPUnit\\Event\\DeferringDispatcherTest", + "totalTime": 2.928, + }, + TestSuiteResult { + "groups": [ + TestGroupResult { + "name": "PHPUnit.Event.DirectDispatcherTest", + "tests": [ + TestCaseResult { + "error": undefined, + "name": "testDispatchesEventToKnownSubscribers", + "result": "success", + "time": 0.17, + }, + TestCaseResult { + "error": undefined, + "name": "testDispatchesEventToTracers", + "result": "success", + "time": 0.248, + }, + TestCaseResult { + "error": undefined, + "name": "testRegisterRejectsUnknownSubscriber", + "result": "success", + "time": 0.257, + }, + TestCaseResult { + "error": undefined, + "name": "testDispatchRejectsUnknownEventType", + "result": "success", + "time": 0.11900000000000001, + }, + ], + }, + ], + "name": "PHPUnit\\Event\\DirectDispatcherTest", + "totalTime": 0.794, + }, + TestSuiteResult { + "groups": [ + TestGroupResult { + "name": undefined, + "tests": [ + TestCaseResult { + "error": { + "details": "targeting-traits-with-coversclass-attribute-is-deprecated.phptFailed asserting that string matches format description. +--- Expected ++++ Actual +@@ @@ + PHPUnit Started (PHPUnit 11.2-g0c2333363 using PHP 8.2.17 (cli) on Linux) + Test Runner Configured + Test Suite Loaded (1 test) ++Test Runner Triggered Warning (No code coverage driver available) + Event Facade Sealed + Test Runner Started + Test Suite Sorted +@@ @@ + Test Preparation Started (PHPUnit\\DeprecatedAnnotationsTestFixture\\TraitTargetedWithCoversClassTest::testSomething) + Test Prepared (PHPUnit\\DeprecatedAnnotationsTestFixture\\TraitTargetedWithCoversClassTest::testSomething) + Test Passed (PHPUnit\\DeprecatedAnnotationsTestFixture\\TraitTargetedWithCoversClassTest::testSomething) +-Test Runner Triggered Deprecation (Targeting a trait such as PHPUnit\\TestFixture\\CoveredTrait with #[CoversClass] is deprecated, please refactor your test to use #[CoversTrait] instead.) + Test Finished (PHPUnit\\DeprecatedAnnotationsTestFixture\\TraitTargetedWithCoversClassTest::testSomething) + Test Suite Finished (PHPUnit\\DeprecatedAnnotationsTestFixture\\TraitTargetedWithCoversClassTest, 1 test) + Test Runner Execution Finished + Test Runner Finished +-PHPUnit Finished (Shell Exit Code: 0) ++PHPUnit Finished (Shell Exit Code: 1) + +/home/matteo/OSS/phpunit/tests/end-to-end/metadata/targeting-traits-with-coversclass-attribute-is-deprecated.phpt:28 +/home/matteo/OSS/phpunit/src/Framework/TestSuite.php:369 +/home/matteo/OSS/phpunit/src/TextUI/TestRunner.php:62 +/home/matteo/OSS/phpunit/src/TextUI/Application.php:200", + "line": undefined, + "message": undefined, + "path": undefined, + }, + "name": "targeting-traits-with-coversclass-attribute-is-deprecated.phpt", + "result": "failed", + "time": 68.151, + }, + TestCaseResult { + "error": { + "details": "targeting-traits-with-usesclass-attribute-is-deprecated.phptFailed asserting that string matches format description. +--- Expected ++++ Actual +@@ @@ + PHPUnit Started (PHPUnit 11.2-g0c2333363 using PHP 8.2.17 (cli) on Linux) + Test Runner Configured + Test Suite Loaded (1 test) ++Test Runner Triggered Warning (No code coverage driver available) + Event Facade Sealed + Test Runner Started + Test Suite Sorted +@@ @@ + Test Preparation Started (PHPUnit\\DeprecatedAnnotationsTestFixture\\TraitTargetedWithUsesClassTest::testSomething) + Test Prepared (PHPUnit\\DeprecatedAnnotationsTestFixture\\TraitTargetedWithUsesClassTest::testSomething) + Test Passed (PHPUnit\\DeprecatedAnnotationsTestFixture\\TraitTargetedWithUsesClassTest::testSomething) +-Test Runner Triggered Deprecation (Targeting a trait such as PHPUnit\\TestFixture\\CoveredTrait with #[UsesClass] is deprecated, please refactor your test to use #[UsesTrait] instead.) + Test Finished (PHPUnit\\DeprecatedAnnotationsTestFixture\\TraitTargetedWithUsesClassTest::testSomething) + Test Suite Finished (PHPUnit\\DeprecatedAnnotationsTestFixture\\TraitTargetedWithUsesClassTest, 1 test) + Test Runner Execution Finished + Test Runner Finished +-PHPUnit Finished (Shell Exit Code: 0) ++PHPUnit Finished (Shell Exit Code: 1) + +/home/matteo/OSS/phpunit/tests/end-to-end/metadata/targeting-traits-with-usesclass-attribute-is-deprecated.phpt:28 +/home/matteo/OSS/phpunit/src/Framework/TestSuite.php:369 +/home/matteo/OSS/phpunit/src/TextUI/TestRunner.php:62 +/home/matteo/OSS/phpunit/src/TextUI/Application.php:200", + "line": undefined, + "message": undefined, + "path": undefined, + }, + "name": "targeting-traits-with-usesclass-attribute-is-deprecated.phpt", + "result": "failed", + "time": 64.268, + }, + ], + }, + ], + "name": "CLI Arguments", + "totalTime": 140.397, + }, + ], + "totalTime": undefined, +} +`; diff --git a/__tests__/fixtures/empty/java-junit-empty-phpunit.xml b/__tests__/fixtures/empty/java-junit-empty-phpunit.xml new file mode 100644 index 00000000..0d3d2e2e --- /dev/null +++ b/__tests__/fixtures/empty/java-junit-empty-phpunit.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/__tests__/fixtures/external/java/phpunit.xml b/__tests__/fixtures/external/java/phpunit.xml new file mode 100644 index 00000000..b0e0cc06 --- /dev/null +++ b/__tests__/fixtures/external/java/phpunit.xml @@ -0,0 +1,79 @@ + + + + + targeting-traits-with-coversclass-attribute-is-deprecated.phptFailed asserting that string matches format description. +--- Expected ++++ Actual +@@ @@ + PHPUnit Started (PHPUnit 11.2-g0c2333363 using PHP 8.2.17 (cli) on Linux) + Test Runner Configured + Test Suite Loaded (1 test) ++Test Runner Triggered Warning (No code coverage driver available) + Event Facade Sealed + Test Runner Started + Test Suite Sorted +@@ @@ + Test Preparation Started (PHPUnit\DeprecatedAnnotationsTestFixture\TraitTargetedWithCoversClassTest::testSomething) + Test Prepared (PHPUnit\DeprecatedAnnotationsTestFixture\TraitTargetedWithCoversClassTest::testSomething) + Test Passed (PHPUnit\DeprecatedAnnotationsTestFixture\TraitTargetedWithCoversClassTest::testSomething) +-Test Runner Triggered Deprecation (Targeting a trait such as PHPUnit\TestFixture\CoveredTrait with #[CoversClass] is deprecated, please refactor your test to use #[CoversTrait] instead.) + Test Finished (PHPUnit\DeprecatedAnnotationsTestFixture\TraitTargetedWithCoversClassTest::testSomething) + Test Suite Finished (PHPUnit\DeprecatedAnnotationsTestFixture\TraitTargetedWithCoversClassTest, 1 test) + Test Runner Execution Finished + Test Runner Finished +-PHPUnit Finished (Shell Exit Code: 0) ++PHPUnit Finished (Shell Exit Code: 1) + +/home/matteo/OSS/phpunit/tests/end-to-end/metadata/targeting-traits-with-coversclass-attribute-is-deprecated.phpt:28 +/home/matteo/OSS/phpunit/src/Framework/TestSuite.php:369 +/home/matteo/OSS/phpunit/src/TextUI/TestRunner.php:62 +/home/matteo/OSS/phpunit/src/TextUI/Application.php:200 + + + targeting-traits-with-usesclass-attribute-is-deprecated.phptFailed asserting that string matches format description. +--- Expected ++++ Actual +@@ @@ + PHPUnit Started (PHPUnit 11.2-g0c2333363 using PHP 8.2.17 (cli) on Linux) + Test Runner Configured + Test Suite Loaded (1 test) ++Test Runner Triggered Warning (No code coverage driver available) + Event Facade Sealed + Test Runner Started + Test Suite Sorted +@@ @@ + Test Preparation Started (PHPUnit\DeprecatedAnnotationsTestFixture\TraitTargetedWithUsesClassTest::testSomething) + Test Prepared (PHPUnit\DeprecatedAnnotationsTestFixture\TraitTargetedWithUsesClassTest::testSomething) + Test Passed (PHPUnit\DeprecatedAnnotationsTestFixture\TraitTargetedWithUsesClassTest::testSomething) +-Test Runner Triggered Deprecation (Targeting a trait such as PHPUnit\TestFixture\CoveredTrait with #[UsesClass] is deprecated, please refactor your test to use #[UsesTrait] instead.) + Test Finished (PHPUnit\DeprecatedAnnotationsTestFixture\TraitTargetedWithUsesClassTest::testSomething) + Test Suite Finished (PHPUnit\DeprecatedAnnotationsTestFixture\TraitTargetedWithUsesClassTest, 1 test) + Test Runner Execution Finished + Test Runner Finished +-PHPUnit Finished (Shell Exit Code: 0) ++PHPUnit Finished (Shell Exit Code: 1) + +/home/matteo/OSS/phpunit/tests/end-to-end/metadata/targeting-traits-with-usesclass-attribute-is-deprecated.phpt:28 +/home/matteo/OSS/phpunit/src/Framework/TestSuite.php:369 +/home/matteo/OSS/phpunit/src/TextUI/TestRunner.php:62 +/home/matteo/OSS/phpunit/src/TextUI/Application.php:200 + + + + + + + + + + + + + + + + + + + diff --git a/__tests__/java-junit-phpunit.test.ts b/__tests__/java-junit-phpunit.test.ts new file mode 100644 index 00000000..0173c856 --- /dev/null +++ b/__tests__/java-junit-phpunit.test.ts @@ -0,0 +1,46 @@ +import * as fs from 'fs' +import * as path from 'path' + +import {JavaJunitParser} from '../src/parsers/java-junit/java-junit-parser' +import {ParseOptions} from '../src/test-parser' +import {getReport} from '../src/report/get-report' +import {normalizeFilePath} from '../src/utils/path-utils' + +describe('java-junit phpunit tests', () => { + it('produces empty test run result when there are no test cases', async () => { + const fixturePath = path.join(__dirname, 'fixtures', 'empty', 'java-junit-empty-phpunit.xml') + const filePath = normalizeFilePath(path.relative(__dirname, fixturePath)) + const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'}) + + const opts: ParseOptions = { + parseErrors: true, + trackedFiles: [] + } + + const parser = new JavaJunitParser(opts) + const result = await parser.parse(filePath, fileContent) + expect(result.tests).toBe(0) + expect(result.result).toBe('success') + }) + + it('report from phpunit test results matches snapshot', async () => { + const fixturePath = path.join(__dirname, 'fixtures', 'external', 'java', 'phpunit.xml') + const trackedFilesPath = path.join(__dirname, 'fixtures', 'external', 'java', 'phpunit.txt') + const outputPath = path.join(__dirname, '__outputs__', 'phpunit-test-results.md') + const filePath = normalizeFilePath(path.relative(__dirname, fixturePath)) + const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'}) + + const opts: ParseOptions = { + parseErrors: true, + trackedFiles: [] + } + + const parser = new JavaJunitParser(opts) + const result = await parser.parse(filePath, fileContent) + expect(result).toMatchSnapshot() + + const report = getReport([result]) + fs.mkdirSync(path.dirname(outputPath), {recursive: true}) + fs.writeFileSync(outputPath, report) + }) +}) diff --git a/dist/index.js b/dist/index.js index dee44688..dee52b25 100644 --- a/dist/index.js +++ b/dist/index.js @@ -994,19 +994,21 @@ class JavaJunitParser { }); } getTestRunResult(filePath, junit) { - var _a; - const suites = junit.testsuites.testsuite === undefined - ? [] - : junit.testsuites.testsuite.map(ts => { - const name = ts.$.name.trim(); - const time = parseFloat(ts.$.time) * 1000; - const sr = new test_results_1.TestSuiteResult(name, this.getGroups(ts), time); - return sr; - }); - const seconds = parseFloat((_a = junit.testsuites.$) === null || _a === void 0 ? void 0 : _a.time); + var _a, _b; + let suites = []; + this.appendSuites(suites, (_a = junit.testsuites.testsuite) !== null && _a !== void 0 ? _a : []); + const seconds = parseFloat((_b = junit.testsuites.$) === null || _b === void 0 ? void 0 : _b.time); const time = isNaN(seconds) ? undefined : seconds * 1000; return new test_results_1.TestRunResult(filePath, suites, time); } + appendSuites(results, testsuites) { + if (testsuites === undefined) + return; + for (const ts of testsuites) { + this.appendSuites(results, ts.testsuite); + results.push(new test_results_1.TestSuiteResult(ts.$.name.trim(), this.getGroups(ts), parseFloat(ts.$.time) * 1000)); + } + } getGroups(suite) { if (suite.testcase === undefined) { return []; diff --git a/src/parsers/java-junit/java-junit-parser.ts b/src/parsers/java-junit/java-junit-parser.ts index 99253658..c31ff443 100644 --- a/src/parsers/java-junit/java-junit-parser.ts +++ b/src/parsers/java-junit/java-junit-parser.ts @@ -61,21 +61,30 @@ export class JavaJunitParser implements TestParser { } private getTestRunResult(filePath: string, junit: JunitReport): TestRunResult { - const suites = - junit.testsuites.testsuite === undefined - ? [] - : junit.testsuites.testsuite.map(ts => { - const name = ts.$.name.trim() - const time = parseFloat(ts.$.time) * 1000 - const sr = new TestSuiteResult(name, this.getGroups(ts), time) - return sr - }) + let suites: TestSuiteResult[] = [] + + this.appendSuites(suites, junit.testsuites.testsuite ?? []) const seconds = parseFloat(junit.testsuites.$?.time) const time = isNaN(seconds) ? undefined : seconds * 1000 return new TestRunResult(filePath, suites, time) } + private appendSuites(results: TestSuiteResult[], testsuites?: TestSuite[]): void + { + if (testsuites === undefined) return + + for (const ts of testsuites) { + this.appendSuites(results, ts.testsuite) + + results.push(new TestSuiteResult( + ts.$.name.trim(), + this.getGroups(ts), + parseFloat(ts.$.time) * 1000 + )); + } + } + private getGroups(suite: TestSuite): TestGroupResult[] { if (suite.testcase === undefined) { return [] diff --git a/src/parsers/java-junit/java-junit-types.ts b/src/parsers/java-junit/java-junit-types.ts index 7fab71e2..fdf22803 100644 --- a/src/parsers/java-junit/java-junit-types.ts +++ b/src/parsers/java-junit/java-junit-types.ts @@ -23,7 +23,8 @@ export interface TestSuite { time: string timestamp?: Date } - testcase: TestCase[] + testcase?: TestCase[] + testsuite?: TestSuite[] } export interface TestCase {