From 46ab900481d865f4f8fca265d112058808cc4ecb Mon Sep 17 00:00:00 2001 From: Patrick Dobbs Date: Tue, 3 Dec 2024 00:17:49 +0000 Subject: [PATCH] Feat: Allow ignoring the `classname` attribute when calling `resolveFileAndLine`. Fixes line number parsing for rust nextest junit output. --- __tests__/testParser.test.ts | 46 +++++++++++++++++++++++++++++++ action.yml | 4 +++ src/main.ts | 4 ++- src/testParser.ts | 37 ++++++++++++++++++------- test_results/nextest/basic.xml | 50 ++++++++++++++++++++++++++++++++++ 5 files changed, 130 insertions(+), 11 deletions(-) create mode 100644 test_results/nextest/basic.xml diff --git a/__tests__/testParser.test.ts b/__tests__/testParser.test.ts index e68da5e3..b0147a0c 100644 --- a/__tests__/testParser.test.ts +++ b/__tests__/testParser.test.ts @@ -409,6 +409,52 @@ describe('parseFile', () => { ]) }) + it('should ignore classname when requested', async () => { + const testResult = await parseFile( + 'test_results/nextest/basic.xml', + '', + false, + false, + false, + undefined, + undefined, + '/', + '', + undefined, + false, + -1, + true, + false, + undefined, + true) + expect(testResult).toBeDefined() + const {totalCount, skippedCount, globalAnnotations} = testResult!! + const filtered = globalAnnotations.filter(annotation => annotation.annotation_level !== 'notice') + + expect(totalCount).toBe(3) + expect(skippedCount).toBe(0) + expect(filtered).toStrictEqual([ + { + annotation_level: 'failure', + end_column: 0, + end_line: 154, + message: 'thread \'test_failure\' panicked at tests/parry3d.rs:154:5:\n' + + ' assertion `left == right` failed: 0 must equal 1', + path: 'tests/parry3d.rs', + raw_details: 'thread \'test_failure\' panicked at tests/parry3d.rs:154:5:\n' + + ' assertion `left == right` failed: 0 must equal 1\n' + + ' left: 0\n' + + ' right: 1\n' + + ' note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace', + start_column: 0, + start_line: 154, + retries: 0, + status: 'failure', + title: 'oxidized_navigation::parry3d.test_failure' + } + ]) + }) + it('should parse correctly fileName and line for a Java file with invalid chars', async () => { const {fileName, line} = await resolveFileAndLine( null, diff --git a/action.yml b/action.yml index a7c88b12..01ec7460 100644 --- a/action.yml +++ b/action.yml @@ -128,6 +128,10 @@ inputs: description: 'Truncate stack traces from test output to 2 lines in annotations' required: false default: 'true' + resolve_ignore_classname: + description: 'Force ignore classname in resolveFileAndLine (Fixes nextest annotations)' + required: false + default: 'false' outputs: total: description: 'The total count of all checks' diff --git a/src/main.ts b/src/main.ts index 1f8d62e6..45059b24 100644 --- a/src/main.ts +++ b/src/main.ts @@ -48,6 +48,7 @@ export async function run(): Promise { const annotationsLimit = Number(core.getInput('annotations_limit') || -1) const skipAnnotations = core.getInput('skip_annotations') === 'true' const truncateStackTraces = core.getBooleanInput('truncate_stack_traces') + const resolveIgnoreClassname = core.getBooleanInput('resolve_ignore_classname') if (excludeSources.length === 0) { excludeSources = ['/build/', '/__pycache__/'] @@ -91,7 +92,8 @@ export async function run(): Promise { followSymlink, annotationsLimit, truncateStackTraces, - failOnParseError + failOnParseError, + resolveIgnoreClassname ) mergedResult.totalCount += testResult.totalCount mergedResult.skipped += testResult.skipped diff --git a/src/testParser.ts b/src/testParser.ts index eb2b61a3..186b2d72 100644 --- a/src/testParser.ts +++ b/src/testParser.ts @@ -197,7 +197,8 @@ export async function parseFile( annotationsLimit = -1, truncateStackTraces = true, failOnParseError = false, - globalAnnotations: Annotation[] = [] + globalAnnotations: Annotation[] = [], + resolveIgnoreClassname = false ): Promise { core.debug(`Parsing file ${file}`) @@ -236,7 +237,8 @@ export async function parseFile( followSymlink, annotationsLimit, truncateStackTraces, - globalAnnotations + globalAnnotations, + resolveIgnoreClassname ) } @@ -260,7 +262,8 @@ async function parseSuite( followSymlink: boolean, annotationsLimit: number, truncateStackTraces: boolean, - globalAnnotations: Annotation[] + globalAnnotations: Annotation[], + resolveIgnoreClassname = false ): Promise { if (!suite) { // not a valid suite, return fast @@ -298,7 +301,8 @@ async function parseSuite( transformer, followSymlink, truncateStackTraces, - limit + limit, + resolveIgnoreClassname ) // expand global annotations array @@ -348,7 +352,8 @@ async function parseSuite( followSymlink, annotationsLimit, truncateStackTraces, - globalAnnotations + globalAnnotations, + resolveIgnoreClassname ) if (childSuiteResult) { @@ -398,7 +403,8 @@ async function parseTestCases( transformer: Transformer[], followSymlink: boolean, truncateStackTraces: boolean, - limit = -1 + limit = -1, + resolveIgnoreClassname = false ): Promise { const annotations: Annotation[] = [] let totalCount = 0 @@ -488,10 +494,15 @@ async function parseTestCases( testcase._attributes.name ).trim() + let resolveClassname = testcase._attributes.name + if (!resolveIgnoreClassname && testcase._attributes.classname) { + resolveClassname = testcase._attributes.classname + } + const pos = await resolveFileAndLine( testcase._attributes.file || failure?._attributes?.file || suiteFile, testcase._attributes.line || failure?._attributes?.line || suiteLine, - testcase._attributes.classname ? testcase._attributes.classname : testcase._attributes.name, + resolveClassname, stackTrace ) @@ -530,7 +541,11 @@ async function parseTestCases( .replace(templateVar('TEST_NAME'), testcase._attributes.name) .replace(templateVar('CLASS_NAME'), className) } else if (pos.fileName !== testcase._attributes.name) { - title = `${pos.fileName}.${testcase._attributes.name}` + if (resolveIgnoreClassname && testcase._attributes.classname) { + title = `${testcase._attributes.classname}.${testcase._attributes.name}` + } else { + title = `${pos.fileName}.${testcase._attributes.name}` + } } else { title = `${testcase._attributes.name}` } @@ -591,7 +606,8 @@ export async function parseTestReports( followSymlink = false, annotationsLimit = -1, truncateStackTraces = true, - failOnParseError = false + failOnParseError = false, + resolveIgnoreClassname = false ): Promise { core.debug(`Process test report for: ${reportPaths} (${checkName})`) const globber = await glob.create(reportPaths, {followSymbolicLinks: followSymlink}) @@ -620,7 +636,8 @@ export async function parseTestReports( annotationsLimit, truncateStackTraces, failOnParseError, - globalAnnotations + globalAnnotations, + resolveIgnoreClassname ) if (!testResult) continue diff --git a/test_results/nextest/basic.xml b/test_results/nextest/basic.xml new file mode 100644 index 00000000..b9f8d821 --- /dev/null +++ b/test_results/nextest/basic.xml @@ -0,0 +1,50 @@ + + + + + thread 'test_failure' panicked at tests/parry3d.rs:154:5: + assertion `left == right` failed: 0 must equal 1 + left: 0 + right: 1 + note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + + running 1 test + test test_failure ... FAILED + + failures: + + failures: + test_failure + + test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.66s + + + thread 'test_failure' panicked at tests/parry3d.rs:154:5: + assertion `left == right` failed: 0 must equal 1 + left: 0 + right: 1 + note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + + + + + running 1 test + test test_simple_navigation ... ok + + test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.70s + + + + + + + running 1 test + test test_annotations ... ok + + test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.74s + + + + + +