Skip to content

Commit c848152

Browse files
joyeecheungpriyank-p
authored andcommitted
ci: detect resume failures, tweak markdown output
1 parent 0207cf7 commit c848152

File tree

2 files changed

+68
-21
lines changed

2 files changed

+68
-21
lines changed

lib/ci/ci_failure_parser.js

+14-3
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,11 @@ const CC_TEST_FAILURE = 'CC_TEST_FAILURE';
3333
const JENKINS_FAILURE = 'JENKINS_FAILURE';
3434
const GIT_FAILURE = 'GIT_FAILURE';
3535
const NCU_FAILURE = 'NCU_FAILURE';
36+
const RESUME_FAILURE = 'RESUME_FAILURE';
3637

3738
const FAILURE_TYPES = {
3839
BUILD_FAILURE, JS_TEST_FAILURE, CC_TEST_FAILURE,
39-
JENKINS_FAILURE, GIT_FAILURE, NCU_FAILURE
40+
JENKINS_FAILURE, GIT_FAILURE, NCU_FAILURE, RESUME_FAILURE
4041
};
4142

4243
class CIResult {
@@ -103,6 +104,14 @@ class NCUFailure extends CIResult {
103104
}
104105
}
105106

107+
// Refs: https://github.com/nodejs/build/issues/1496
108+
class ResumeFailure extends CIResult {
109+
constructor(ctx, reason) {
110+
super(ctx, reason);
111+
this.type = RESUME_FAILURE;
112+
}
113+
}
114+
106115
function failureMatcher(Failure, patterns, ctx, text) {
107116
for (const pattern of patterns) {
108117
const matches = text.match(pattern.pattern);
@@ -277,7 +286,8 @@ CIFailureParser.FAILURE_CONSTRUCTORS = {
277286
JS_TEST_FAILURE: JSTestFailure,
278287
CC_TEST_FAILURE: CCTestFailure,
279288
GIT_FAILURE: GitFailure,
280-
NCU_FAILURE: NCUFailure
289+
NCU_FAILURE: NCUFailure,
290+
RESUME_FAILURE: ResumeFailure
281291
};
282292
CIFailureParser.CIResult = CIResult;
283293
CIFailureParser.FAILURE_TYPES_NAME = {
@@ -286,6 +296,7 @@ CIFailureParser.FAILURE_TYPES_NAME = {
286296
JS_TEST_FAILURE: 'JSTest Failure',
287297
CC_TEST_FAILURE: 'CCTest Failure',
288298
GIT_FAILURE: 'Git Failure',
289-
NCU_FAILURE: 'node-core-utils failure'
299+
NCU_FAILURE: 'node-core-utils failure',
300+
RESUME_FAILURE: 'resume failure'
290301
};
291302
module.exports = CIFailureParser;

lib/ci/ci_result_parser.js

+54-18
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@ const CIFailureParser = require('./ci_failure_parser');
88
const {
99
FAILURE_TYPES: {
1010
BUILD_FAILURE,
11-
NCU_FAILURE
11+
NCU_FAILURE,
12+
GIT_FAILURE,
13+
RESUME_FAILURE
1214
},
1315
FAILURE_CONSTRUCTORS: {
1416
[BUILD_FAILURE]: BuildFailure,
15-
[NCU_FAILURE]: NCUFailure
17+
[NCU_FAILURE]: NCUFailure,
18+
[RESUME_FAILURE]: ResumeFailure
1619
},
1720
CIResult,
1821
FAILURE_TYPES_NAME
@@ -46,11 +49,12 @@ const COMMIT_TREE =
4649
`subBuilds[${BUILD_FIELDS}]`;
4750
// com.tikal.jenkins.plugins.multijob.MultiJobBuild
4851
const FANNED_TREE =
49-
`result,url,number,subBuilds[phaseName,${BUILD_FIELDS}],builtOn`;
52+
`result,url,number,subBuilds[phaseName,${BUILD_FIELDS}]`;
5053
// hudson.matrix.MatrixBuild
5154
const BUILD_TREE = 'result,runs[url,number,result],builtOn';
5255
const LINTER_TREE = 'result,url,number,builtOn';
53-
const RUN_TREE = 'actions[causes[upstreamBuild,upstreamProject]],builtOn';
56+
const CAUSE_TREE = 'upstreamBuild,upstreamProject,shortDescription,_class';
57+
const RUN_TREE = `actions[causes[${CAUSE_TREE}]],builtOn`;
5458

5559
function getPath(url) {
5660
return url.replace(`https://${CI_DOMAIN}/`, '').replace('api/json', '');
@@ -128,6 +132,13 @@ class Job {
128132
return data;
129133
}
130134

135+
getCause(actions) {
136+
if (actions && actions.find(item => item.causes)) {
137+
const action = actions.find(item => item.causes);
138+
return action.causes[0];
139+
}
140+
}
141+
131142
async getAPIData() {
132143
const { cli, request, path } = this;
133144
const url = this.apiUrl;
@@ -355,7 +366,9 @@ function getHighlight(f) {
355366
)
356367
.replace(
357368
/fatal: loose object \w+ \(stored in .git\/objects\/.+\) is corrupt/,
358-
'fatal: loose object ... (stored in .git/objects/...) is corrupt');
369+
'fatal: loose object ... (stored in .git/objects/...) is corrupt')
370+
.replace(/hudson\.plugins\.git\.GitException: /, '')
371+
.replace(/java\.io\.IOException: /, '');
359372
}
360373

361374
function markdownRow(...args) {
@@ -427,13 +440,15 @@ class FailureAggregator {
427440
output += `[${jobName}/${first.jobid}](${first.link}) to `;
428441
output += `[${jobName}/${last.jobid}](${last.link}) `;
429442
output += `that failed more than 2 PRs\n`;
443+
output += `(Generated with \`ncu-ci `;
444+
output += `${process.argv.slice(2).join(' ')}\`)\n`;
430445
const todo = [];
431446
for (const type of Object.keys(aggregates)) {
432447
output += `\n### ${FAILURE_TYPES_NAME[type]}\n\n`;
433448
for (const item of aggregates[type]) {
434449
const { reason, type, prs, failures, machines } = item;
435450
if (prs.length < 2) { continue; }
436-
todo.push(reason);
451+
todo.push({ count: prs.length, reason });
437452
output += markdownRow('Reason', `<code>${reason}</code>`);
438453
output += markdownRow('-', ':-');
439454
output += markdownRow('Type', type);
@@ -449,16 +464,19 @@ class FailureAggregator {
449464
}
450465
output += markdownRow('Last CI', `${prs[prs.length - 1].upstream}`);
451466
output += '\n';
467+
const example = failures[0].reason;
452468
output += fold(
453469
`<a href="${failures[0].url}">Example</a>`,
454-
failures[0].reason
470+
(example.length > 1024 ? example.slice(0, 1024) + '...' : example)
455471
);
456472
output += '\n\n-------\n\n';
457473
}
458474
}
459475

460476
output += '### Progress\n\n';
461-
output += todo.map(i => `- \`${i}\``).join('\n');
477+
output += todo.map(
478+
({count, reason}) => `- \`${reason}\` (${count})`).join('\n'
479+
);
462480
return output + '\n';
463481
}
464482

@@ -559,7 +577,10 @@ class CommitBuild extends TestBuild {
559577
cli.startSpinner(`Querying failures of ${path}`);
560578
const promises = builds.failed.map(({jobName, buildNumber, url}) => {
561579
if (jobName.includes('fanned')) {
562-
return new FannedBuild(cli, request, jobName, buildNumber).getResults();
580+
const cause = this.getCause(data.actions);
581+
const isResumed = cause && cause._class.includes('ResumeCause');
582+
return new FannedBuild(cli, request, jobName, buildNumber, isResumed)
583+
.getResults();
563584
} else if (jobName.includes('linter')) {
564585
return new LinterBuild(cli, request, jobName, buildNumber).getResults();
565586
} else if (jobName.includes('freestyle')) {
@@ -622,6 +643,7 @@ class PRBuild extends TestBuild {
622643
const allBuilds = commitBuild.build.subBuilds;
623644
// TODO: fetch result, builtOn, timestamp in the commit build's own data
624645
// ..or maybe they do not worth an additional API call?
646+
// Note that we have to pass the actions down to detect resume builds.
625647
const buildData = {
626648
result, subBuilds: allBuilds, changeSet, actions, timestamp
627649
};
@@ -682,14 +704,15 @@ async function listBuilds(cli, request, type) {
682704
}
683705

684706
class FannedBuild extends Job {
685-
constructor(cli, request, jobName, id) {
707+
constructor(cli, request, jobName, id, isResumed) {
686708
// assert(jobName.includes('fanned'));
687709
const path = `job/${jobName}/${id}/`;
688710
const tree = FANNED_TREE;
689711
super(cli, request, path, tree);
690712

691713
this.failures = [];
692714
this.builtOn = undefined;
715+
this.isResumed = isResumed;
693716
}
694717

695718
// Get the failures and their reasons of this build
@@ -724,17 +747,33 @@ class FannedBuild extends Job {
724747
!failedPhase.phaseName.toLowerCase().includes('compilation')) {
725748
this.failures = [
726749
new BuildFailure(
727-
{ url: failedPhase.url, builtOn: failedPhase.builtOn },
728-
`Failed in ${failedPhase.phaseName} phase`
750+
{ url: failedPhase.url },
751+
`Failed in ${failedPhase.phaseName} phase (${failedPhase.jobName})`
752+
// TODO: parse console text for the failed phase
729753
)
730754
];
731755
return this.failures;
732756
}
733757

734758
const { jobName, buildNumber } = failedPhase;
735759
const build = new NormalBuild(cli, request, jobName, buildNumber);
736-
const failures = await build.getResults();
737-
this.failures = flatten(failures);
760+
let failures = await build.getResults();
761+
failures = flatten(failures);
762+
763+
if (this.isResumed) {
764+
// XXX: if it's a resumed build, we replace the build/git failures
765+
// with resume failures. Probably just a random guess, though
766+
for (let i = 0; i < failures.length; ++i) {
767+
const item = failures[i];
768+
if (item.type === BUILD_FAILURE || item.type === GIT_FAILURE) {
769+
failures[i] = new ResumeFailure(
770+
item,
771+
`Possible resume failure\n${item.reason}`
772+
);
773+
}
774+
}
775+
}
776+
this.failures = failures;
738777
return this.failures;
739778
}
740779
}
@@ -853,10 +892,7 @@ class TestRun extends Job {
853892
];
854893
return this.failures;
855894
}
856-
if (data.actions && data.actions.find(item => item.causes)) {
857-
const actions = data.actions.find(item => item.causes);
858-
this.cause = actions.causes[0];
859-
}
895+
this.causes = this.getCause(data.actions) || {};
860896
this.builtOn = data.builtOn;
861897
}
862898

0 commit comments

Comments
 (0)