diff --git a/.gitignore b/.gitignore index 639696a..f43ecf7 100644 --- a/.gitignore +++ b/.gitignore @@ -100,3 +100,5 @@ lib/**/* # Project specific __tests__/__results__ + +.idea diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..79bdb1b --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v18.7.0 diff --git a/__tests__/java-stack-trace-element-parser.test.ts b/__tests__/java-stack-trace-element-parser.test.ts new file mode 100644 index 0000000..6fe1ac2 --- /dev/null +++ b/__tests__/java-stack-trace-element-parser.test.ts @@ -0,0 +1,75 @@ +import {parseStackTraceElement} from '../src/parsers/java-junit/java-stack-trace-element-parser' + +describe('parseStackTraceLine tests', () => { + it('empty line is not parsed', async () => { + const line = '' + expect(parseStackTraceElement(line)).toBe(undefined) + }) + + describe('java class', () => { + it('simple', async () => { + const line = + 'at org.apache.pulsar.AddMissingPatchVersionTest.testVersionStrings(AddMissingPatchVersionTest.java:29)' + expect(parseStackTraceElement(line)).toEqual({ + tracePath: 'org.apache.pulsar.AddMissingPatchVersionTest.testVersionStrings', + fileName: 'AddMissingPatchVersionTest.java', + lineStr: '29' + }) + }) + + it('inner class', async () => { + const line = 'at com.foo.Main$Inner.run(Main.java:29)' + expect(parseStackTraceElement(line)).toEqual({ + tracePath: 'com.foo.Main$Inner.run', + fileName: 'Main.java', + lineStr: '29' + }) + }) + + it('starts with whitespaces', async () => { + const line = + ' \tat org.apache.pulsar.AddMissingPatchVersionTest.testVersionStrings(AddMissingPatchVersionTest.java:29)' + expect(parseStackTraceElement(line)).toEqual({ + tracePath: 'org.apache.pulsar.AddMissingPatchVersionTest.testVersionStrings', + fileName: 'AddMissingPatchVersionTest.java', + lineStr: '29' + }) + }) + + describe('since Java 9', () => { + it('with classloader and module', async () => { + // Based on Java 9 StackTraceElement.toString() Doc: https://docs.oracle.com/javase/9/docs/api/java/lang/StackTraceElement.html#toString-- + const line = 'at com.foo.loader/foo@9.0/com.foo.Main.run(Main.java:101)' + expect(parseStackTraceElement(line)).toEqual({ + classLoader: 'com.foo.loader', + moduleNameAndVersion: 'foo@9.0', + tracePath: 'com.foo.Main.run', + fileName: 'Main.java', + lineStr: '101' + }) + }) + + it('with classloader', async () => { + const line = 'at com.foo.loader//com.foo.Main.run(Main.java:101)' + expect(parseStackTraceElement(line)).toEqual({ + classLoader: 'com.foo.loader', + moduleNameAndVersion: undefined, + tracePath: 'com.foo.Main.run', + fileName: 'Main.java', + lineStr: '101' + }) + }) + }) + }) + + describe('Kotlin class', () => { + it('method name containing whitespaces', async () => { + const line = 'at com.foo.Main.method with whitespaces(Main.kt:18)' + expect(parseStackTraceElement(line)).toEqual({ + tracePath: 'com.foo.Main.method with whitespaces', + fileName: 'Main.kt', + lineStr: '18' + }) + }) + }) +}) diff --git a/dist/index.js b/dist/index.js index 21c0fe8..19587c3 100644 --- a/dist/index.js +++ b/dist/index.js @@ -926,6 +926,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true })); exports.JavaJunitParser = void 0; const path = __importStar(__nccwpck_require__(1017)); const xml2js_1 = __nccwpck_require__(6189); +const java_stack_trace_element_parser_1 = __nccwpck_require__(5775); const path_utils_1 = __nccwpck_require__(4070); const test_results_1 = __nccwpck_require__(2768); class JavaJunitParser { @@ -1053,11 +1054,10 @@ class JavaJunitParser { } exceptionThrowSource(stackTrace) { const lines = stackTrace.split(/\r?\n/); - const re = /^at (.*)\((.*):(\d+)\)$/; for (const str of lines) { - const match = str.match(re); - if (match !== null) { - const [_, tracePath, fileName, lineStr] = match; + const stackTraceElement = (0, java_stack_trace_element_parser_1.parseStackTraceElement)(str); + if (stackTraceElement) { + const { tracePath, fileName, lineStr } = stackTraceElement; const filePath = this.getFilePath(tracePath, fileName); if (filePath !== undefined) { const line = parseInt(lineStr); @@ -1110,6 +1110,49 @@ class JavaJunitParser { exports.JavaJunitParser = JavaJunitParser; +/***/ }), + +/***/ 5775: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.parseStackTraceElement = void 0; +// classloader and module name are optional: +// at <CLASSLOADER>/<MODULE_NAME_AND_VERSION>/<FULLY_QUALIFIED_METHOD_NAME>(<FILE_NAME>:<LINE_NUMBER>) +// https://github.com/eclipse-openj9/openj9/issues/11452#issuecomment-754946992 +const re = /^\s*at (\S+\/\S*\/)?(.*)\((.*):(\d+)\)$/; +function parseStackTraceElement(stackTraceLine) { + const match = stackTraceLine.match(re); + if (match !== null) { + const [_, maybeClassLoaderAndModuleNameAndVersion, tracePath, fileName, lineStr] = match; + const { classLoader, moduleNameAndVersion } = parseClassLoaderAndModule(maybeClassLoaderAndModuleNameAndVersion); + return { + classLoader, + moduleNameAndVersion, + tracePath, + fileName, + lineStr + }; + } + return undefined; +} +exports.parseStackTraceElement = parseStackTraceElement; +function parseClassLoaderAndModule(maybeClassLoaderAndModuleNameAndVersion) { + if (maybeClassLoaderAndModuleNameAndVersion) { + const res = maybeClassLoaderAndModuleNameAndVersion.split('/'); + const classLoader = res[0]; + let moduleNameAndVersion = res[1]; + if (moduleNameAndVersion === '') { + moduleNameAndVersion = undefined; + } + return { classLoader, moduleNameAndVersion }; + } + return { classLoader: undefined, moduleNameAndVersion: undefined }; +} + + /***/ }), /***/ 1113: diff --git a/src/parsers/java-junit/java-junit-parser.ts b/src/parsers/java-junit/java-junit-parser.ts index 42b7b08..9925365 100644 --- a/src/parsers/java-junit/java-junit-parser.ts +++ b/src/parsers/java-junit/java-junit-parser.ts @@ -3,6 +3,7 @@ import {ParseOptions, TestParser} from '../../test-parser' import {parseStringPromise} from 'xml2js' import {JunitReport, SingleSuiteReport, TestCase, TestSuite} from './java-junit-types' +import {parseStackTraceElement} from './java-stack-trace-element-parser' import {normalizeFilePath} from '../../utils/path-utils' import { @@ -146,12 +147,11 @@ export class JavaJunitParser implements TestParser { private exceptionThrowSource(stackTrace: string): {filePath: string; line: number} | undefined { const lines = stackTrace.split(/\r?\n/) - const re = /^at (.*)\((.*):(\d+)\)$/ for (const str of lines) { - const match = str.match(re) - if (match !== null) { - const [_, tracePath, fileName, lineStr] = match + const stackTraceElement = parseStackTraceElement(str) + if (stackTraceElement) { + const {tracePath, fileName, lineStr} = stackTraceElement const filePath = this.getFilePath(tracePath, fileName) if (filePath !== undefined) { const line = parseInt(lineStr) diff --git a/src/parsers/java-junit/java-stack-trace-element-parser.ts b/src/parsers/java-junit/java-stack-trace-element-parser.ts new file mode 100644 index 0000000..894dd42 --- /dev/null +++ b/src/parsers/java-junit/java-stack-trace-element-parser.ts @@ -0,0 +1,44 @@ +export interface StackTraceElement { + classLoader: string | undefined + moduleNameAndVersion: string | undefined + tracePath: string + fileName: string + lineStr: string +} + +// classloader and module name are optional: +// at <CLASSLOADER>/<MODULE_NAME_AND_VERSION>/<FULLY_QUALIFIED_METHOD_NAME>(<FILE_NAME>:<LINE_NUMBER>) +// https://github.com/eclipse-openj9/openj9/issues/11452#issuecomment-754946992 +const re = /^\s*at (\S+\/\S*\/)?(.*)\((.*):(\d+)\)$/ + +export function parseStackTraceElement(stackTraceLine: string): StackTraceElement | undefined { + const match = stackTraceLine.match(re) + if (match !== null) { + const [_, maybeClassLoaderAndModuleNameAndVersion, tracePath, fileName, lineStr] = match + const {classLoader, moduleNameAndVersion} = parseClassLoaderAndModule(maybeClassLoaderAndModuleNameAndVersion) + return { + classLoader, + moduleNameAndVersion, + tracePath, + fileName, + lineStr + } + } + return undefined +} + +function parseClassLoaderAndModule(maybeClassLoaderAndModuleNameAndVersion?: string): { + classLoader?: string + moduleNameAndVersion?: string +} { + if (maybeClassLoaderAndModuleNameAndVersion) { + const res = maybeClassLoaderAndModuleNameAndVersion.split('/') + const classLoader = res[0] + let moduleNameAndVersion: string | undefined = res[1] + if (moduleNameAndVersion === '') { + moduleNameAndVersion = undefined + } + return {classLoader, moduleNameAndVersion} + } + return {classLoader: undefined, moduleNameAndVersion: undefined} +}