From b34d4b1bfe30834973913a18e52217168545ae06 Mon Sep 17 00:00:00 2001 From: Michal Dorner Date: Mon, 28 Jun 2021 22:24:27 +0200 Subject: [PATCH 1/7] Add NUnit XML results fixtures --- __tests__/fixtures/dotnet-nunit.xml | 112 ++++++++++++++++ __tests__/fixtures/external/nunit-sample.xml | 125 ++++++++++++++++++ package.json | 1 + .../CalculatorTests.cs | 64 +++++++++ .../DotnetTests.NUnitV3Tests.csproj | 18 +++ reports/dotnet/DotnetTests.sln | 7 + 6 files changed, 327 insertions(+) create mode 100644 __tests__/fixtures/dotnet-nunit.xml create mode 100644 __tests__/fixtures/external/nunit-sample.xml create mode 100644 reports/dotnet/DotnetTests.NUnitV3Tests/CalculatorTests.cs create mode 100644 reports/dotnet/DotnetTests.NUnitV3Tests/DotnetTests.NUnitV3Tests.csproj diff --git a/__tests__/fixtures/dotnet-nunit.xml b/__tests__/fixtures/dotnet-nunit.xml new file mode 100644 index 00000000..e08a9f33 --- /dev/null +++ b/__tests__/fixtures/dotnet-nunit.xml @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/__tests__/fixtures/external/nunit-sample.xml b/__tests__/fixtures/external/nunit-sample.xml new file mode 100644 index 00000000..b2615600 --- /dev/null +++ b/__tests__/fixtures/external/nunit-sample.xml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/package.json b/package.json index 3f156422..a52075ae 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "all": "npm run build && npm run format && npm run lint && npm run package && npm test", "dart-fixture": "cd \"reports/dart\" && dart test --file-reporter=\"json:../../__tests__/fixtures/dart-json.json\"", "dotnet-fixture": "dotnet test reports/dotnet/DotnetTests.XUnitTests --logger \"trx;LogFileName=../../../../__tests__/fixtures/dotnet-trx.trx\"", + "dotnet-nunit-fixture": "nunit.exe reports/dotnet/DotnetTests.NUnitV3Tests/bin/Debug/netcoreapp3.1/DotnetTests.NUnitV3Tests.dll --result=__tests__/fixtures/dotnet-nunit.xml", "jest-fixture": "cd \"reports/jest\" && npm test", "mocha-fixture": "cd \"reports/mocha\" && npm test" }, diff --git a/reports/dotnet/DotnetTests.NUnitV3Tests/CalculatorTests.cs b/reports/dotnet/DotnetTests.NUnitV3Tests/CalculatorTests.cs new file mode 100644 index 00000000..9452ae93 --- /dev/null +++ b/reports/dotnet/DotnetTests.NUnitV3Tests/CalculatorTests.cs @@ -0,0 +1,64 @@ +using System; +using System.Threading; +using DotnetTests.Unit; +using NUnit.Framework; + +namespace DotnetTests.XUnitTests +{ + public class CalculatorTests + { + private readonly Calculator _calculator = new Calculator(); + + [Test] + public void Passing_Test() + { + Assert.That(_calculator.Sum(1, 1), Is.EqualTo(2)); + } + + [Test(Description = "Some description")] + public void Passing_Test_With_Description() + { + Assert.That(2, Is.EqualTo(2)); + } + + [Test] + public void Failing_Test() + { + Assert.That(_calculator.Sum(1, 1), Is.EqualTo(3)); + } + + [Test] + public void Exception_In_TargetTest() + { + _calculator.Div(1, 0); + } + + [Test] + public void Exception_In_Test() + { + throw new Exception("Test"); + } + + [Test] + [Timeout(1)] + public void Timeout_Test() + { + Thread.Sleep(100); + } + + [Test] + [Ignore("Skipped")] + public void Skipped_Test() + { + throw new Exception("Test"); + } + + [Theory] + [TestCase(2)] + [TestCase(3)] + public void Is_Even_Number(int i) + { + Assert.True(i % 2 == 0); + } + } +} diff --git a/reports/dotnet/DotnetTests.NUnitV3Tests/DotnetTests.NUnitV3Tests.csproj b/reports/dotnet/DotnetTests.NUnitV3Tests/DotnetTests.NUnitV3Tests.csproj new file mode 100644 index 00000000..932ff5ba --- /dev/null +++ b/reports/dotnet/DotnetTests.NUnitV3Tests/DotnetTests.NUnitV3Tests.csproj @@ -0,0 +1,18 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + + + + diff --git a/reports/dotnet/DotnetTests.sln b/reports/dotnet/DotnetTests.sln index 1c0bbeb6..7c5ff87c 100644 --- a/reports/dotnet/DotnetTests.sln +++ b/reports/dotnet/DotnetTests.sln @@ -9,6 +9,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{BCAC3B31 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotnetTests.XUnitTests", "DotnetTests.XUnitTests\DotnetTests.XUnitTests.csproj", "{F8607EDB-D25D-47AA-8132-38ACA242E845}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotnetTests.NUnitV3Tests", "DotnetTests.NUnitV3Tests\DotnetTests.NUnitV3Tests.csproj", "{81023ED7-56CB-47E9-86C5-9125A0873C55}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -23,12 +25,17 @@ Global {F8607EDB-D25D-47AA-8132-38ACA242E845}.Debug|Any CPU.Build.0 = Debug|Any CPU {F8607EDB-D25D-47AA-8132-38ACA242E845}.Release|Any CPU.ActiveCfg = Release|Any CPU {F8607EDB-D25D-47AA-8132-38ACA242E845}.Release|Any CPU.Build.0 = Release|Any CPU + {81023ED7-56CB-47E9-86C5-9125A0873C55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {81023ED7-56CB-47E9-86C5-9125A0873C55}.Debug|Any CPU.Build.0 = Debug|Any CPU + {81023ED7-56CB-47E9-86C5-9125A0873C55}.Release|Any CPU.ActiveCfg = Release|Any CPU + {81023ED7-56CB-47E9-86C5-9125A0873C55}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {F8607EDB-D25D-47AA-8132-38ACA242E845} = {BCAC3B31-ADB1-4221-9D5B-182EE868648C} + {81023ED7-56CB-47E9-86C5-9125A0873C55} = {BCAC3B31-ADB1-4221-9D5B-182EE868648C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6ED5543C-74AA-4B21-8050-943550F3F66E} From 49c1f3ae6cb51d8ac7a071fc65b192917e761534 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Thu, 19 Jan 2023 12:27:34 +1100 Subject: [PATCH 2/7] Implement NUnit 3 parser. --- __tests__/__outputs__/dotnet-nunit.md | 28 ++++ .../__snapshots__/dotnet-nunit.test.ts.snap | 107 +++++++++++++ __tests__/dotnet-nunit.test.ts | 29 ++++ .../dotnet-nunit/dotnet-nunit-parser.ts | 151 ++++++++++++++++++ .../dotnet-nunit/dotnet-nunit-types.ts | 57 +++++++ 5 files changed, 372 insertions(+) create mode 100644 __tests__/__outputs__/dotnet-nunit.md create mode 100644 __tests__/__snapshots__/dotnet-nunit.test.ts.snap create mode 100644 __tests__/dotnet-nunit.test.ts create mode 100644 src/parsers/dotnet-nunit/dotnet-nunit-parser.ts create mode 100644 src/parsers/dotnet-nunit/dotnet-nunit-types.ts diff --git a/__tests__/__outputs__/dotnet-nunit.md b/__tests__/__outputs__/dotnet-nunit.md new file mode 100644 index 00000000..5b1d9c7e --- /dev/null +++ b/__tests__/__outputs__/dotnet-nunit.md @@ -0,0 +1,28 @@ +![Tests failed](https://img.shields.io/badge/tests-3%20passed%2C%205%20failed%2C%201%20skipped-critical) +## ❌ fixtures/dotnet-nunit.xml +**9** tests were completed in **0ms** with **3** passed, **5** failed and **1** skipped. +|Test suite|Passed|Failed|Skipped|Time| +|:---|---:|---:|---:|---:| +|[DotnetTests.NUnitV3Tests.dll.DotnetTests.XUnitTests](#r0s0)|3✅|5❌|1⚪|0ms| +### ❌ DotnetTests.NUnitV3Tests.dll.DotnetTests.XUnitTests +``` +CalculatorTests + ✅ Is_Even_Number(2) + ❌ Is_Even_Number(3) + Expected: True + But was: False + + ❌ Exception_In_TargetTest + System.DivideByZeroException : Attempted to divide by zero. + ❌ Exception_In_Test + System.Exception : Test + ❌ Failing_Test + Expected: 3 + But was: 2 + + ✅ Passing_Test + ✅ Passing_Test_With_Description + ⚪ Skipped_Test + ❌ Timeout_Test + +``` \ No newline at end of file diff --git a/__tests__/__snapshots__/dotnet-nunit.test.ts.snap b/__tests__/__snapshots__/dotnet-nunit.test.ts.snap new file mode 100644 index 00000000..203fcc48 --- /dev/null +++ b/__tests__/__snapshots__/dotnet-nunit.test.ts.snap @@ -0,0 +1,107 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`dotnet-nunit tests report from ./reports/dotnet test results matches snapshot 1`] = ` +TestRunResult { + "path": "fixtures/dotnet-nunit.xml", + "suites": Array [ + TestSuiteResult { + "groups": Array [ + TestGroupResult { + "name": "CalculatorTests", + "tests": Array [ + TestCaseResult { + "error": undefined, + "name": "Is_Even_Number(2)", + "result": "success", + "time": 0.000622, + }, + TestCaseResult { + "error": Object { + "details": " at DotnetTests.XUnitTests.CalculatorTests.Is_Even_Number(Int32 i) in C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-reporter\\\\reports\\\\dotnet\\\\DotnetTests.NUnitV3Tests\\\\CalculatorTests.cs:line 61 +", + "line": undefined, + "message": " Expected: True + But was: False +", + "path": undefined, + }, + "name": "Is_Even_Number(3)", + "result": "failed", + "time": 0.001098, + }, + TestCaseResult { + "error": Object { + "details": " at DotnetTests.Unit.Calculator.Div(Int32 a, Int32 b) in C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-reporter\\\\reports\\\\dotnet\\\\DotnetTests.Unit\\\\Calculator.cs:line 9 + at DotnetTests.XUnitTests.CalculatorTests.Exception_In_TargetTest() in C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-reporter\\\\reports\\\\dotnet\\\\DotnetTests.NUnitV3Tests\\\\CalculatorTests.cs:line 33", + "line": undefined, + "message": "System.DivideByZeroException : Attempted to divide by zero.", + "path": undefined, + }, + "name": "Exception_In_TargetTest", + "result": "failed", + "time": 0.022805, + }, + TestCaseResult { + "error": Object { + "details": " at DotnetTests.XUnitTests.CalculatorTests.Exception_In_Test() in C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-reporter\\\\reports\\\\dotnet\\\\DotnetTests.NUnitV3Tests\\\\CalculatorTests.cs:line 39", + "line": undefined, + "message": "System.Exception : Test", + "path": undefined, + }, + "name": "Exception_In_Test", + "result": "failed", + "time": 0.000528, + }, + TestCaseResult { + "error": Object { + "details": " at DotnetTests.XUnitTests.CalculatorTests.Failing_Test() in C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-reporter\\\\reports\\\\dotnet\\\\DotnetTests.NUnitV3Tests\\\\CalculatorTests.cs:line 27 +", + "line": undefined, + "message": " Expected: 3 + But was: 2 +", + "path": undefined, + }, + "name": "Failing_Test", + "result": "failed", + "time": 0.028162, + }, + TestCaseResult { + "error": undefined, + "name": "Passing_Test", + "result": "success", + "time": 0.000238, + }, + TestCaseResult { + "error": undefined, + "name": "Passing_Test_With_Description", + "result": "success", + "time": 0.000135, + }, + TestCaseResult { + "error": undefined, + "name": "Skipped_Test", + "result": "skipped", + "time": 0.000398, + }, + TestCaseResult { + "error": Object { + "details": "", + "line": undefined, + "message": "", + "path": undefined, + }, + "name": "Timeout_Test", + "result": "failed", + "time": 0.014949, + }, + ], + }, + ], + "name": "DotnetTests.NUnitV3Tests.dll.DotnetTests.XUnitTests", + "totalTime": undefined, + }, + ], + "totalTime": 0.230308, +} +`; diff --git a/__tests__/dotnet-nunit.test.ts b/__tests__/dotnet-nunit.test.ts new file mode 100644 index 00000000..731aeeab --- /dev/null +++ b/__tests__/dotnet-nunit.test.ts @@ -0,0 +1,29 @@ +import * as fs from 'fs' +import * as path from 'path' + +import {DotNetNunitParser} from '../src/parsers/dotnet-nunit/dotnet-nunit-parser' +import {ParseOptions} from '../src/test-parser' +import {getReport} from '../src/report/get-report' +import {normalizeFilePath} from '../src/utils/path-utils' + +describe('dotnet-nunit tests', () => { + it('report from ./reports/dotnet test results matches snapshot', async () => { + const fixturePath = path.join(__dirname, 'fixtures', 'dotnet-nunit.xml') + const outputPath = path.join(__dirname, '__outputs__', 'dotnet-nunit.md') + const filePath = normalizeFilePath(path.relative(__dirname, fixturePath)) + const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'}) + + const opts: ParseOptions = { + parseErrors: true, + trackedFiles: ['DotnetTests.Unit/Calculator.cs', 'DotnetTests.NUnitV3Tests/CalculatorTests.cs'] + } + + const parser = new DotNetNunitParser(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/src/parsers/dotnet-nunit/dotnet-nunit-parser.ts b/src/parsers/dotnet-nunit/dotnet-nunit-parser.ts new file mode 100644 index 00000000..d4a5791d --- /dev/null +++ b/src/parsers/dotnet-nunit/dotnet-nunit-parser.ts @@ -0,0 +1,151 @@ +import {ParseOptions, TestParser} from '../../test-parser' +import {parseStringPromise} from 'xml2js' + +import {NunitReport, TestCase, TestRun, TestSuite} from './dotnet-nunit-types' +import {getExceptionSource} from '../../utils/node-utils' +import {getBasePath, normalizeFilePath} from '../../utils/path-utils' + +import { + TestExecutionResult, + TestRunResult, + TestSuiteResult, + TestGroupResult, + TestCaseResult, + TestCaseError +} from '../../test-results' + +export class DotNetNunitParser implements TestParser { + assumedWorkDir: string | undefined + + constructor(readonly options: ParseOptions) {} + + async parse(path: string, content: string): Promise { + const ju = await this.getNunitReport(path, content) + return this.getTestRunResult(path, ju) + } + + private async getNunitReport(path: string, content: string): Promise { + try { + return (await parseStringPromise(content)) as NunitReport + } catch (e) { + throw new Error(`Invalid XML at ${path}\n\n${e}`) + } + } + + private getTestRunResult(path: string, nunit: NunitReport): TestRunResult { + const suites: TestSuiteResult[] = [] + const time = parseFloat(nunit['test-run'].$.duration) + + this.populateTestCasesRecursive(suites, [], nunit['test-run']['test-suite']) + + return new TestRunResult(path, suites, time) + } + + private populateTestCasesRecursive( + result: TestSuiteResult[], + suitePath: TestSuite[], + testSuites: TestSuite[] | undefined + ): void { + if (testSuites === undefined) { + return + } + + testSuites.forEach(suite => { + suitePath.push(suite) + + this.populateTestCasesRecursive(result, suitePath, suite['test-suite']) + + const testcases = suite['test-case'] + if (testcases !== undefined) { + testcases.forEach(testcase => { + this.addTestCase(result, suitePath, testcase) + }) + } + + suitePath.pop() + }) + } + + private addTestCase(result: TestSuiteResult[], suitePath: TestSuite[], testCase: TestCase) { + // The last suite in the suite path is the "group". + // The rest are concatenated together to form the "suite". + // But ignore "Theory" suites. + const suitesWithoutTheories = suitePath.filter(suite => suite.$.type !== 'Theory') + const suiteName = suitesWithoutTheories + .slice(0, suitesWithoutTheories.length - 1) + .map(suite => suite.$.name) + .join('.') + const groupName = suitesWithoutTheories[suitesWithoutTheories.length - 1].$.name + + let existingSuite = result.find(existingSuite => existingSuite.name === suiteName) + if (existingSuite === undefined) { + existingSuite = new TestSuiteResult(suiteName, []) + result.push(existingSuite) + } + + let existingGroup = existingSuite.groups.find(existingGroup => existingGroup.name === groupName) + if (existingGroup === undefined) { + existingGroup = new TestGroupResult(groupName, []) + existingSuite.groups.push(existingGroup) + } + + existingGroup.tests.push( + new TestCaseResult( + testCase.$.name, + this.getTestExecutionResult(testCase), + parseFloat(testCase.$.duration), + this.getTestCaseError(testCase) + ) + ) + } + + private getTestExecutionResult(test: TestCase): TestExecutionResult { + if (test.$.result === 'Failed' || test.failure) return 'failed' + if (test.$.result === 'Skipped') return 'skipped' + return 'success' + } + + private getTestCaseError(tc: TestCase): TestCaseError | undefined { + if (!this.options.parseErrors || !tc.failure || tc.failure.length === 0) { + return undefined + } + + const details = tc.failure[0] + let path + let line + + if (details['stack-trace'] !== undefined && details['stack-trace'].length > 0) { + const src = getExceptionSource(details['stack-trace'][0], this.options.trackedFiles, file => + this.getRelativePath(file) + ) + if (src) { + path = src.path + line = src.line + } + } + + return { + path: path, + line: line, + message: details.message && details.message.length > 0 ? details.message[0] : '', + details: details['stack-trace'] && details['stack-trace'].length > 0 ? details['stack-trace'][0] : '' + } + } + + private getRelativePath(path: string): string { + path = normalizeFilePath(path) + const workDir = this.getWorkDir(path) + if (workDir !== undefined && path.startsWith(workDir)) { + path = path.substr(workDir.length) + } + return path + } + + private getWorkDir(path: string): string | undefined { + return ( + this.options.workDir ?? + this.assumedWorkDir ?? + (this.assumedWorkDir = getBasePath(path, this.options.trackedFiles)) + ) + } +} diff --git a/src/parsers/dotnet-nunit/dotnet-nunit-types.ts b/src/parsers/dotnet-nunit/dotnet-nunit-types.ts new file mode 100644 index 00000000..3ac76c1e --- /dev/null +++ b/src/parsers/dotnet-nunit/dotnet-nunit-types.ts @@ -0,0 +1,57 @@ +export interface NunitReport { + "test-run": TestRun +} + +export interface TestRun { + $: { + id: string + runstate: string + testcasecount: string + result: string + total: string + passed: string + failed: string + inconclusive: string + skipped: string + asserts: string + 'engine-version': string + 'clr-version': string + 'start-time': string + 'end-time': string + duration: string + } + 'test-suite'?: TestSuite[] +} + +export interface TestSuite { + $: { + name: string + type: string + } + 'test-case'?: TestCase[] + 'test-suite'?: TestSuite[] +} + +export interface TestCase { + $: { + id: string + name: string + fullname: string + methodname: string + classname: string + runstate: string + seed: string + result: string + label: string + 'start-time': string + 'end-time': string + duration: string + asserts: string + } + failure?: TestFailure[] +} + +export interface TestFailure { + message?: string[] + 'stack-trace'?: string[] +} From 953e623fd82f07b92539887b1966588ce3a90134 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Thu, 19 Jan 2023 12:37:16 +1100 Subject: [PATCH 3/7] Report times in milliseconds. --- __tests__/__outputs__/dotnet-nunit.md | 4 ++-- .../__snapshots__/dotnet-nunit.test.ts.snap | 20 +++++++++---------- .../dotnet-nunit/dotnet-nunit-parser.ts | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/__tests__/__outputs__/dotnet-nunit.md b/__tests__/__outputs__/dotnet-nunit.md index 5b1d9c7e..904e7d2c 100644 --- a/__tests__/__outputs__/dotnet-nunit.md +++ b/__tests__/__outputs__/dotnet-nunit.md @@ -1,9 +1,9 @@ ![Tests failed](https://img.shields.io/badge/tests-3%20passed%2C%205%20failed%2C%201%20skipped-critical) ## ❌ fixtures/dotnet-nunit.xml -**9** tests were completed in **0ms** with **3** passed, **5** failed and **1** skipped. +**9** tests were completed in **230ms** with **3** passed, **5** failed and **1** skipped. |Test suite|Passed|Failed|Skipped|Time| |:---|---:|---:|---:|---:| -|[DotnetTests.NUnitV3Tests.dll.DotnetTests.XUnitTests](#r0s0)|3✅|5❌|1⚪|0ms| +|[DotnetTests.NUnitV3Tests.dll.DotnetTests.XUnitTests](#r0s0)|3✅|5❌|1⚪|69ms| ### ❌ DotnetTests.NUnitV3Tests.dll.DotnetTests.XUnitTests ``` CalculatorTests diff --git a/__tests__/__snapshots__/dotnet-nunit.test.ts.snap b/__tests__/__snapshots__/dotnet-nunit.test.ts.snap index 203fcc48..3e2bb414 100644 --- a/__tests__/__snapshots__/dotnet-nunit.test.ts.snap +++ b/__tests__/__snapshots__/dotnet-nunit.test.ts.snap @@ -13,7 +13,7 @@ TestRunResult { "error": undefined, "name": "Is_Even_Number(2)", "result": "success", - "time": 0.000622, + "time": 0.622, }, TestCaseResult { "error": Object { @@ -27,7 +27,7 @@ TestRunResult { }, "name": "Is_Even_Number(3)", "result": "failed", - "time": 0.001098, + "time": 1.098, }, TestCaseResult { "error": Object { @@ -39,7 +39,7 @@ TestRunResult { }, "name": "Exception_In_TargetTest", "result": "failed", - "time": 0.022805, + "time": 22.805, }, TestCaseResult { "error": Object { @@ -50,7 +50,7 @@ TestRunResult { }, "name": "Exception_In_Test", "result": "failed", - "time": 0.000528, + "time": 0.528, }, TestCaseResult { "error": Object { @@ -64,25 +64,25 @@ TestRunResult { }, "name": "Failing_Test", "result": "failed", - "time": 0.028162, + "time": 28.162, }, TestCaseResult { "error": undefined, "name": "Passing_Test", "result": "success", - "time": 0.000238, + "time": 0.23800000000000002, }, TestCaseResult { "error": undefined, "name": "Passing_Test_With_Description", "result": "success", - "time": 0.000135, + "time": 0.135, }, TestCaseResult { "error": undefined, "name": "Skipped_Test", "result": "skipped", - "time": 0.000398, + "time": 0.398, }, TestCaseResult { "error": Object { @@ -93,7 +93,7 @@ TestRunResult { }, "name": "Timeout_Test", "result": "failed", - "time": 0.014949, + "time": 14.949, }, ], }, @@ -102,6 +102,6 @@ TestRunResult { "totalTime": undefined, }, ], - "totalTime": 0.230308, + "totalTime": 230.30800000000002, } `; diff --git a/src/parsers/dotnet-nunit/dotnet-nunit-parser.ts b/src/parsers/dotnet-nunit/dotnet-nunit-parser.ts index d4a5791d..73a0438f 100644 --- a/src/parsers/dotnet-nunit/dotnet-nunit-parser.ts +++ b/src/parsers/dotnet-nunit/dotnet-nunit-parser.ts @@ -34,7 +34,7 @@ export class DotNetNunitParser implements TestParser { private getTestRunResult(path: string, nunit: NunitReport): TestRunResult { const suites: TestSuiteResult[] = [] - const time = parseFloat(nunit['test-run'].$.duration) + const time = parseFloat(nunit['test-run'].$.duration) * 1000 this.populateTestCasesRecursive(suites, [], nunit['test-run']['test-suite']) @@ -93,7 +93,7 @@ export class DotNetNunitParser implements TestParser { new TestCaseResult( testCase.$.name, this.getTestExecutionResult(testCase), - parseFloat(testCase.$.duration), + parseFloat(testCase.$.duration) * 1000, this.getTestCaseError(testCase) ) ) From ce340de8b981c032cb0e58920b6c84d41f554387 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Thu, 19 Jan 2023 13:52:46 +1100 Subject: [PATCH 4/7] Add reporter, eslint, formatting. --- README.md | 1 + action.yml | 1 + src/main.ts | 3 +++ src/parsers/dotnet-nunit/dotnet-nunit-parser.ts | 16 ++++++++-------- src/parsers/dotnet-nunit/dotnet-nunit-types.ts | 2 +- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 6d34e2c4..c4de5e6c 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,7 @@ jobs: # Format of test results. Supported options: # dart-json + # dotnet-nunit # dotnet-trx # flutter-json # java-junit diff --git a/action.yml b/action.yml index 8d9d7282..3ea90c15 100644 --- a/action.yml +++ b/action.yml @@ -26,6 +26,7 @@ inputs: description: | Format of test results. Supported options: - dart-json + - dotnet-nunit - dotnet-trx - flutter-json - java-junit diff --git a/src/main.ts b/src/main.ts index 8f481afe..458d1066 100644 --- a/src/main.ts +++ b/src/main.ts @@ -11,6 +11,7 @@ import {getAnnotations} from './report/get-annotations' import {getReport} from './report/get-report' import {DartJsonParser} from './parsers/dart-json/dart-json-parser' +import {DotNetNunitParser} from './parsers/dotnet-nunit/dotnet-nunit-parser' import {DotnetTrxParser} from './parsers/dotnet-trx/dotnet-trx-parser' import {JavaJunitParser} from './parsers/java-junit/java-junit-parser' import {JestJunitParser} from './parsers/jest-junit/jest-junit-parser' @@ -214,6 +215,8 @@ class TestReporter { switch (reporter) { case 'dart-json': return new DartJsonParser(options, 'dart') + case 'dotnet-nunit': + return new DotNetNunitParser(options) case 'dotnet-trx': return new DotnetTrxParser(options) case 'flutter-json': diff --git a/src/parsers/dotnet-nunit/dotnet-nunit-parser.ts b/src/parsers/dotnet-nunit/dotnet-nunit-parser.ts index 73a0438f..70d3269a 100644 --- a/src/parsers/dotnet-nunit/dotnet-nunit-parser.ts +++ b/src/parsers/dotnet-nunit/dotnet-nunit-parser.ts @@ -1,7 +1,7 @@ import {ParseOptions, TestParser} from '../../test-parser' import {parseStringPromise} from 'xml2js' -import {NunitReport, TestCase, TestRun, TestSuite} from './dotnet-nunit-types' +import {NunitReport, TestCase, TestSuite} from './dotnet-nunit-types' import {getExceptionSource} from '../../utils/node-utils' import {getBasePath, normalizeFilePath} from '../../utils/path-utils' @@ -50,23 +50,23 @@ export class DotNetNunitParser implements TestParser { return } - testSuites.forEach(suite => { + for (const suite of testSuites) { suitePath.push(suite) this.populateTestCasesRecursive(result, suitePath, suite['test-suite']) const testcases = suite['test-case'] if (testcases !== undefined) { - testcases.forEach(testcase => { + for (const testcase of testcases) { this.addTestCase(result, suitePath, testcase) - }) + } } suitePath.pop() - }) + } } - private addTestCase(result: TestSuiteResult[], suitePath: TestSuite[], testCase: TestCase) { + private addTestCase(result: TestSuiteResult[], suitePath: TestSuite[], testCase: TestCase): void { // The last suite in the suite path is the "group". // The rest are concatenated together to form the "suite". // But ignore "Theory" suites. @@ -125,8 +125,8 @@ export class DotNetNunitParser implements TestParser { } return { - path: path, - line: line, + path, + line, message: details.message && details.message.length > 0 ? details.message[0] : '', details: details['stack-trace'] && details['stack-trace'].length > 0 ? details['stack-trace'][0] : '' } diff --git a/src/parsers/dotnet-nunit/dotnet-nunit-types.ts b/src/parsers/dotnet-nunit/dotnet-nunit-types.ts index 3ac76c1e..ec1696cd 100644 --- a/src/parsers/dotnet-nunit/dotnet-nunit-types.ts +++ b/src/parsers/dotnet-nunit/dotnet-nunit-types.ts @@ -1,5 +1,5 @@ export interface NunitReport { - "test-run": TestRun + 'test-run': TestRun } export interface TestRun { From c40b69fc4aa16a395b19d369855d7e8ba8dcdf62 Mon Sep 17 00:00:00 2001 From: Jozef Izso Date: Tue, 25 Jun 2024 14:01:36 +0200 Subject: [PATCH 5/7] Fix code formatting and update snapshot for `dotnet-unit` tests --- .../__snapshots__/dotnet-nunit.test.ts.snap | 26 +++++++++---------- src/parsers/rspec-json/rspec-json-parser.ts | 1 - src/parsers/rspec-json/rspec-json-types.ts | 2 +- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/__tests__/__snapshots__/dotnet-nunit.test.ts.snap b/__tests__/__snapshots__/dotnet-nunit.test.ts.snap index 3e2bb414..60d55f2c 100644 --- a/__tests__/__snapshots__/dotnet-nunit.test.ts.snap +++ b/__tests__/__snapshots__/dotnet-nunit.test.ts.snap @@ -3,12 +3,12 @@ exports[`dotnet-nunit tests report from ./reports/dotnet test results matches snapshot 1`] = ` TestRunResult { "path": "fixtures/dotnet-nunit.xml", - "suites": Array [ + "suites": [ TestSuiteResult { - "groups": Array [ + "groups": [ TestGroupResult { "name": "CalculatorTests", - "tests": Array [ + "tests": [ TestCaseResult { "error": undefined, "name": "Is_Even_Number(2)", @@ -16,8 +16,8 @@ TestRunResult { "time": 0.622, }, TestCaseResult { - "error": Object { - "details": " at DotnetTests.XUnitTests.CalculatorTests.Is_Even_Number(Int32 i) in C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-reporter\\\\reports\\\\dotnet\\\\DotnetTests.NUnitV3Tests\\\\CalculatorTests.cs:line 61 + "error": { + "details": " at DotnetTests.XUnitTests.CalculatorTests.Is_Even_Number(Int32 i) in C:\\Users\\Michal\\Workspace\\dorny\\test-reporter\\reports\\dotnet\\DotnetTests.NUnitV3Tests\\CalculatorTests.cs:line 61 ", "line": undefined, "message": " Expected: True @@ -30,9 +30,9 @@ TestRunResult { "time": 1.098, }, TestCaseResult { - "error": Object { - "details": " at DotnetTests.Unit.Calculator.Div(Int32 a, Int32 b) in C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-reporter\\\\reports\\\\dotnet\\\\DotnetTests.Unit\\\\Calculator.cs:line 9 - at DotnetTests.XUnitTests.CalculatorTests.Exception_In_TargetTest() in C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-reporter\\\\reports\\\\dotnet\\\\DotnetTests.NUnitV3Tests\\\\CalculatorTests.cs:line 33", + "error": { + "details": " at DotnetTests.Unit.Calculator.Div(Int32 a, Int32 b) in C:\\Users\\Michal\\Workspace\\dorny\\test-reporter\\reports\\dotnet\\DotnetTests.Unit\\Calculator.cs:line 9 + at DotnetTests.XUnitTests.CalculatorTests.Exception_In_TargetTest() in C:\\Users\\Michal\\Workspace\\dorny\\test-reporter\\reports\\dotnet\\DotnetTests.NUnitV3Tests\\CalculatorTests.cs:line 33", "line": undefined, "message": "System.DivideByZeroException : Attempted to divide by zero.", "path": undefined, @@ -42,8 +42,8 @@ TestRunResult { "time": 22.805, }, TestCaseResult { - "error": Object { - "details": " at DotnetTests.XUnitTests.CalculatorTests.Exception_In_Test() in C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-reporter\\\\reports\\\\dotnet\\\\DotnetTests.NUnitV3Tests\\\\CalculatorTests.cs:line 39", + "error": { + "details": " at DotnetTests.XUnitTests.CalculatorTests.Exception_In_Test() in C:\\Users\\Michal\\Workspace\\dorny\\test-reporter\\reports\\dotnet\\DotnetTests.NUnitV3Tests\\CalculatorTests.cs:line 39", "line": undefined, "message": "System.Exception : Test", "path": undefined, @@ -53,8 +53,8 @@ TestRunResult { "time": 0.528, }, TestCaseResult { - "error": Object { - "details": " at DotnetTests.XUnitTests.CalculatorTests.Failing_Test() in C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-reporter\\\\reports\\\\dotnet\\\\DotnetTests.NUnitV3Tests\\\\CalculatorTests.cs:line 27 + "error": { + "details": " at DotnetTests.XUnitTests.CalculatorTests.Failing_Test() in C:\\Users\\Michal\\Workspace\\dorny\\test-reporter\\reports\\dotnet\\DotnetTests.NUnitV3Tests\\CalculatorTests.cs:line 27 ", "line": undefined, "message": " Expected: 3 @@ -85,7 +85,7 @@ TestRunResult { "time": 0.398, }, TestCaseResult { - "error": Object { + "error": { "details": "", "line": undefined, "message": "", diff --git a/src/parsers/rspec-json/rspec-json-parser.ts b/src/parsers/rspec-json/rspec-json-parser.ts index bf404a12..bf723935 100644 --- a/src/parsers/rspec-json/rspec-json-parser.ts +++ b/src/parsers/rspec-json/rspec-json-parser.ts @@ -1,4 +1,3 @@ -import { Console } from 'console' import {ParseOptions, TestParser} from '../../test-parser' import { TestCaseError, diff --git a/src/parsers/rspec-json/rspec-json-types.ts b/src/parsers/rspec-json/rspec-json-types.ts index 495af897..dca00b76 100644 --- a/src/parsers/rspec-json/rspec-json-types.ts +++ b/src/parsers/rspec-json/rspec-json-types.ts @@ -17,7 +17,7 @@ export interface RspecExample { exception?: RspecException } -type TestStatus = 'passed' | 'failed' | 'pending'; +type TestStatus = 'passed' | 'failed' | 'pending' export interface RspecException { class: string From d8481703bc6fee181700523ffb4cc51f5a31b848 Mon Sep 17 00:00:00 2001 From: Jozef Izso Date: Tue, 25 Jun 2024 14:02:56 +0200 Subject: [PATCH 6/7] Rebuild release `index.js` file --- dist/index.js | 133 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/dist/index.js b/dist/index.js index 715691f7..323abc0a 100644 --- a/dist/index.js +++ b/dist/index.js @@ -261,6 +261,7 @@ const local_file_provider_1 = __nccwpck_require__(9399); const get_annotations_1 = __nccwpck_require__(5867); const get_report_1 = __nccwpck_require__(3737); const dart_json_parser_1 = __nccwpck_require__(4528); +const dotnet_nunit_parser_1 = __nccwpck_require__(5706); const dotnet_trx_parser_1 = __nccwpck_require__(2664); const java_junit_parser_1 = __nccwpck_require__(676); const jest_junit_parser_1 = __nccwpck_require__(1113); @@ -425,6 +426,8 @@ class TestReporter { switch (reporter) { case 'dart-json': return new dart_json_parser_1.DartJsonParser(options, 'dart'); + case 'dotnet-nunit': + return new dotnet_nunit_parser_1.DotNetNunitParser(options); case 'dotnet-trx': return new dotnet_trx_parser_1.DotnetTrxParser(options); case 'flutter-json': @@ -722,6 +725,136 @@ function isMessageEvent(event) { exports.isMessageEvent = isMessageEvent; +/***/ }), + +/***/ 5706: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.DotNetNunitParser = void 0; +const xml2js_1 = __nccwpck_require__(6189); +const node_utils_1 = __nccwpck_require__(5824); +const path_utils_1 = __nccwpck_require__(4070); +const test_results_1 = __nccwpck_require__(2768); +class DotNetNunitParser { + constructor(options) { + this.options = options; + } + parse(path, content) { + return __awaiter(this, void 0, void 0, function* () { + const ju = yield this.getNunitReport(path, content); + return this.getTestRunResult(path, ju); + }); + } + getNunitReport(path, content) { + return __awaiter(this, void 0, void 0, function* () { + try { + return (yield (0, xml2js_1.parseStringPromise)(content)); + } + catch (e) { + throw new Error(`Invalid XML at ${path}\n\n${e}`); + } + }); + } + getTestRunResult(path, nunit) { + const suites = []; + const time = parseFloat(nunit['test-run'].$.duration) * 1000; + this.populateTestCasesRecursive(suites, [], nunit['test-run']['test-suite']); + return new test_results_1.TestRunResult(path, suites, time); + } + populateTestCasesRecursive(result, suitePath, testSuites) { + if (testSuites === undefined) { + return; + } + for (const suite of testSuites) { + suitePath.push(suite); + this.populateTestCasesRecursive(result, suitePath, suite['test-suite']); + const testcases = suite['test-case']; + if (testcases !== undefined) { + for (const testcase of testcases) { + this.addTestCase(result, suitePath, testcase); + } + } + suitePath.pop(); + } + } + addTestCase(result, suitePath, testCase) { + // The last suite in the suite path is the "group". + // The rest are concatenated together to form the "suite". + // But ignore "Theory" suites. + const suitesWithoutTheories = suitePath.filter(suite => suite.$.type !== 'Theory'); + const suiteName = suitesWithoutTheories + .slice(0, suitesWithoutTheories.length - 1) + .map(suite => suite.$.name) + .join('.'); + const groupName = suitesWithoutTheories[suitesWithoutTheories.length - 1].$.name; + let existingSuite = result.find(existingSuite => existingSuite.name === suiteName); + if (existingSuite === undefined) { + existingSuite = new test_results_1.TestSuiteResult(suiteName, []); + result.push(existingSuite); + } + let existingGroup = existingSuite.groups.find(existingGroup => existingGroup.name === groupName); + if (existingGroup === undefined) { + existingGroup = new test_results_1.TestGroupResult(groupName, []); + existingSuite.groups.push(existingGroup); + } + existingGroup.tests.push(new test_results_1.TestCaseResult(testCase.$.name, this.getTestExecutionResult(testCase), parseFloat(testCase.$.duration) * 1000, this.getTestCaseError(testCase))); + } + getTestExecutionResult(test) { + if (test.$.result === 'Failed' || test.failure) + return 'failed'; + if (test.$.result === 'Skipped') + return 'skipped'; + return 'success'; + } + getTestCaseError(tc) { + if (!this.options.parseErrors || !tc.failure || tc.failure.length === 0) { + return undefined; + } + const details = tc.failure[0]; + let path; + let line; + if (details['stack-trace'] !== undefined && details['stack-trace'].length > 0) { + const src = (0, node_utils_1.getExceptionSource)(details['stack-trace'][0], this.options.trackedFiles, file => this.getRelativePath(file)); + if (src) { + path = src.path; + line = src.line; + } + } + return { + path, + line, + message: details.message && details.message.length > 0 ? details.message[0] : '', + details: details['stack-trace'] && details['stack-trace'].length > 0 ? details['stack-trace'][0] : '' + }; + } + getRelativePath(path) { + path = (0, path_utils_1.normalizeFilePath)(path); + const workDir = this.getWorkDir(path); + if (workDir !== undefined && path.startsWith(workDir)) { + path = path.substr(workDir.length); + } + return path; + } + getWorkDir(path) { + var _a, _b; + return ((_b = (_a = this.options.workDir) !== null && _a !== void 0 ? _a : this.assumedWorkDir) !== null && _b !== void 0 ? _b : (this.assumedWorkDir = (0, path_utils_1.getBasePath)(path, this.options.trackedFiles))); + } +} +exports.DotNetNunitParser = DotNetNunitParser; + + /***/ }), /***/ 2664: From 1397b99b7c2bf8a3feadebbd8514d6d97385803f Mon Sep 17 00:00:00 2001 From: Jozef Izso Date: Tue, 25 Jun 2024 14:09:10 +0200 Subject: [PATCH 7/7] Refactor `dotnet-nunit` parser to `DotnetNunitParser` --- __tests__/dotnet-nunit.test.ts | 4 ++-- dist/index.js | 8 ++++---- src/main.ts | 4 ++-- src/parsers/dotnet-nunit/dotnet-nunit-parser.ts | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/__tests__/dotnet-nunit.test.ts b/__tests__/dotnet-nunit.test.ts index 731aeeab..e0b47783 100644 --- a/__tests__/dotnet-nunit.test.ts +++ b/__tests__/dotnet-nunit.test.ts @@ -1,7 +1,7 @@ import * as fs from 'fs' import * as path from 'path' -import {DotNetNunitParser} from '../src/parsers/dotnet-nunit/dotnet-nunit-parser' +import {DotnetNunitParser} from '../src/parsers/dotnet-nunit/dotnet-nunit-parser' import {ParseOptions} from '../src/test-parser' import {getReport} from '../src/report/get-report' import {normalizeFilePath} from '../src/utils/path-utils' @@ -18,7 +18,7 @@ describe('dotnet-nunit tests', () => { trackedFiles: ['DotnetTests.Unit/Calculator.cs', 'DotnetTests.NUnitV3Tests/CalculatorTests.cs'] } - const parser = new DotNetNunitParser(opts) + const parser = new DotnetNunitParser(opts) const result = await parser.parse(filePath, fileContent) expect(result).toMatchSnapshot() diff --git a/dist/index.js b/dist/index.js index 323abc0a..ff99141e 100644 --- a/dist/index.js +++ b/dist/index.js @@ -427,7 +427,7 @@ class TestReporter { case 'dart-json': return new dart_json_parser_1.DartJsonParser(options, 'dart'); case 'dotnet-nunit': - return new dotnet_nunit_parser_1.DotNetNunitParser(options); + return new dotnet_nunit_parser_1.DotnetNunitParser(options); case 'dotnet-trx': return new dotnet_trx_parser_1.DotnetTrxParser(options); case 'flutter-json': @@ -742,12 +742,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }); }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.DotNetNunitParser = void 0; +exports.DotnetNunitParser = void 0; const xml2js_1 = __nccwpck_require__(6189); const node_utils_1 = __nccwpck_require__(5824); const path_utils_1 = __nccwpck_require__(4070); const test_results_1 = __nccwpck_require__(2768); -class DotNetNunitParser { +class DotnetNunitParser { constructor(options) { this.options = options; } @@ -852,7 +852,7 @@ class DotNetNunitParser { return ((_b = (_a = this.options.workDir) !== null && _a !== void 0 ? _a : this.assumedWorkDir) !== null && _b !== void 0 ? _b : (this.assumedWorkDir = (0, path_utils_1.getBasePath)(path, this.options.trackedFiles))); } } -exports.DotNetNunitParser = DotNetNunitParser; +exports.DotnetNunitParser = DotnetNunitParser; /***/ }), diff --git a/src/main.ts b/src/main.ts index 458d1066..d50427b1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -11,7 +11,7 @@ import {getAnnotations} from './report/get-annotations' import {getReport} from './report/get-report' import {DartJsonParser} from './parsers/dart-json/dart-json-parser' -import {DotNetNunitParser} from './parsers/dotnet-nunit/dotnet-nunit-parser' +import {DotnetNunitParser} from './parsers/dotnet-nunit/dotnet-nunit-parser' import {DotnetTrxParser} from './parsers/dotnet-trx/dotnet-trx-parser' import {JavaJunitParser} from './parsers/java-junit/java-junit-parser' import {JestJunitParser} from './parsers/jest-junit/jest-junit-parser' @@ -216,7 +216,7 @@ class TestReporter { case 'dart-json': return new DartJsonParser(options, 'dart') case 'dotnet-nunit': - return new DotNetNunitParser(options) + return new DotnetNunitParser(options) case 'dotnet-trx': return new DotnetTrxParser(options) case 'flutter-json': diff --git a/src/parsers/dotnet-nunit/dotnet-nunit-parser.ts b/src/parsers/dotnet-nunit/dotnet-nunit-parser.ts index 70d3269a..4bc27f1f 100644 --- a/src/parsers/dotnet-nunit/dotnet-nunit-parser.ts +++ b/src/parsers/dotnet-nunit/dotnet-nunit-parser.ts @@ -14,7 +14,7 @@ import { TestCaseError } from '../../test-results' -export class DotNetNunitParser implements TestParser { +export class DotnetNunitParser implements TestParser { assumedWorkDir: string | undefined constructor(readonly options: ParseOptions) {}