diff --git a/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/failed_tests_reporter_cli.ts b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/failed_tests_reporter_cli.ts index d86f7f5213125..36466c3e3637e 100644 --- a/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/failed_tests_reporter_cli.ts +++ b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/failed_tests_reporter_cli.ts @@ -44,6 +44,7 @@ run( let branch: string = ''; let pipeline: string = ''; + let prependTitle: string = ''; if (updateGithub) { let isPr = false; @@ -52,6 +53,7 @@ run( pipeline = process.env.BUILDKITE_PIPELINE_SLUG || ''; isPr = process.env.BUILDKITE_PULL_REQUEST === 'true'; updateGithub = process.env.REPORT_FAILED_TESTS_TO_GITHUB === 'true'; + prependTitle = process.env.PREPEND_FAILURE_TITLE || ''; } else { // JOB_NAME is formatted as `elastic+kibana+7.x` in some places and `elastic+kibana+7.x/JOB=kibana-intake,node=immutable` in others const jobNameSplit = (process.env.JOB_NAME || '').split(/\+|\//); @@ -154,7 +156,14 @@ run( continue; } - const newIssue = await createFailureIssue(buildUrl, failure, githubApi, branch, pipeline); + const newIssue = await createFailureIssue( + buildUrl, + failure, + githubApi, + branch, + pipeline, + prependTitle + ); existingIssues.addNewlyCreated(failure, newIssue); pushMessage('Test has not failed recently on tracked branches'); if (updateGithub) { diff --git a/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_failure.test.ts b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_failure.test.ts index dce47461377c4..c7dbd2db77319 100644 --- a/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_failure.test.ts +++ b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_failure.test.ts @@ -60,6 +60,53 @@ describe('createFailureIssue()', () => { } `); }); + + it('creates new github issue with title prepended', async () => { + const api = new GithubApi(); + + await createFailureIssue( + 'https://build-url', + { + classname: 'some.classname', + failure: 'this is the failure text', + name: 'test name', + time: '2018-01-01T01:00:00Z', + likelyIrrelevant: false, + }, + api, + 'main', + 'kibana-on-merge', + '[MKI][QA]' + ); + + expect(api.createIssue).toMatchInlineSnapshot(` + [MockFunction] { + "calls": Array [ + Array [ + "Failing test: [MKI][QA] some.classname - test name", + "A test failed on a tracked branch + + \`\`\` + this is the failure text + \`\`\` + + First failure: [kibana-on-merge - main](https://build-url) + + ", + Array [ + "failed-test", + ], + ], + ], + "results": Array [ + Object { + "type": "return", + "value": undefined, + }, + ], + } + `); + }); }); describe('updateFailureIssue()', () => { diff --git a/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_failure.ts b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_failure.ts index 182bde666c250..d941f94b6b24d 100644 --- a/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_failure.ts +++ b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_failure.ts @@ -17,9 +17,15 @@ export async function createFailureIssue( failure: TestFailure, api: GithubApi, branch: string, - pipeline: string + pipeline: string, + prependTitle: string = '' ) { - const title = `Failing test: ${failure.classname} - ${failure.name}`; + // PrependTitle is introduced to provide some clarity by prepending the failing test title + // in order to give the whole info in the title according to each team's preference. + const title = + prependTitle && prependTitle.trim() !== '' + ? `Failing test: ${prependTitle} ${failure.classname} - ${failure.name}` + : `Failing test: ${failure.classname} - ${failure.name}`; // Github API body length maximum is 65536 characters // Let's keep consistency with Mocha output that is truncated to 8192 characters diff --git a/x-pack/plugins/security_solution/scripts/junit_transformer/lib.ts b/x-pack/plugins/security_solution/scripts/junit_transformer/lib.ts index 725baa689cd20..e09a057978bce 100644 --- a/x-pack/plugins/security_solution/scripts/junit_transformer/lib.ts +++ b/x-pack/plugins/security_solution/scripts/junit_transformer/lib.ts @@ -18,6 +18,20 @@ import { PathReporter } from 'io-ts/lib/PathReporter'; import globby from 'globby'; import del from 'del'; +// Function to remove specific fields from an XML object in order to +// compare them as strings. +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function removeFields(obj: any, fieldsToRemove: string[]): any { + for (const key in obj) { + if (fieldsToRemove.includes(key)) { + delete obj[key]; + } else if (typeof obj[key] === 'object') { + obj[key] = removeFields(obj[key], fieldsToRemove); // Recursively remove fields + } + } + return obj; +} + /** * Updates the `name` and `classname` attributes of each testcase. * `name` will have the value of `classname` appended to it. This makes sense because they each contain part of the bdd spec. @@ -174,6 +188,10 @@ export async function command({ flags, log }: CommandArgs) { throw createFlagError('please provide a single --reportName flag'); } + // Going to be used in order to store results as string in order to compare + // and remove duplicated reports. + const xmlResultFiles: string[] = []; + for (const path of await globby(flags.pathPattern)) { // Read the file const source: string = await fs.readFile(path, 'utf8'); @@ -242,6 +260,26 @@ ${boilerplate} rootDirectory: flags.rootDirectory, }); + // We need to check if a XML Junit report is duplicate + // Only if we remove the time and timestamp and the rest of the + // report as a string is completely identical. + const fieldsToRemove = ['time', 'timestamp']; + const tempReport = await parseStringPromise(reportString); + const truncatedReport = removeFields(tempReport, fieldsToRemove); + + // Rebuild the XML to compare (optional, if you want to compare XML strings) + const builder = new Builder(); + const rebuildComparableReport = builder.buildObject(truncatedReport); + + // Compare the cleaned and rebuilt XML objects or strings + if (xmlResultFiles.includes(rebuildComparableReport)) { + // If the report is a duplicate, we need to remove the file + // in order to be excluded from the uploaded results. + await del(path, { force: true }); + continue; + } + xmlResultFiles.push(rebuildComparableReport); + // If the writeInPlace flag was passed, overwrite the original file, otherwise log the output to stdout if (flags.writeInPlace) { log.info(`Wrote transformed junit report to ${path}`);