Skip to content

Commit c1dfbd0

Browse files
authored
feat: add ncu-ci cigtm <jobid> (#454)
1 parent 158efdf commit c1dfbd0

File tree

8 files changed

+4389
-17
lines changed

8 files changed

+4389
-17
lines changed

bin/ncu-ci

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,19 @@ const {
66
JobParser,
77
parseJobFromURL,
88
CI_TYPES_KEYS: {
9-
PR, COMMIT, BENCHMARK
9+
PR, COMMIT, BENCHMARK, CITGM
1010
}
1111
} = require('../lib/ci/ci_type_parser');
1212

1313
const {
14-
PRBuild, BenchmarkRun, CommitBuild, HealthBuild,
15-
listBuilds, FailureAggregator, jobCache
14+
PRBuild,
15+
BenchmarkRun,
16+
CommitBuild,
17+
CITGMBuild,
18+
HealthBuild,
19+
listBuilds,
20+
FailureAggregator,
21+
jobCache
1622
} = require('../lib/ci/ci_result_parser');
1723
const clipboardy = require('clipboardy');
1824
const { writeJson, writeFile } = require('../lib/file');
@@ -28,7 +34,8 @@ const commandKeys = [
2834
'walk',
2935
'url',
3036
'pr',
31-
'commit'
37+
'commit',
38+
'citgm'
3239
];
3340

3441
// eslint-disable-next-line no-unused-vars
@@ -118,6 +125,18 @@ const argv = yargs
118125
},
119126
handler
120127
})
128+
.command({
129+
command: 'citgm <jobid>',
130+
desc: 'Show results of a citgm-smoker CI job',
131+
builder: (yargs) => {
132+
yargs
133+
.positional('jobid', {
134+
describe: 'id of the job',
135+
type: 'number'
136+
});
137+
},
138+
handler
139+
})
121140
.demandCommand(1, 'must provide a valid command')
122141
.option('copy', {
123142
default: false,
@@ -144,7 +163,8 @@ const argv = yargs
144163
const commandToType = {
145164
commit: COMMIT,
146165
pr: PR,
147-
benchmark: BENCHMARK
166+
benchmark: BENCHMARK,
167+
citgm: CITGM
148168
};
149169

150170
class CICommand {
@@ -188,6 +208,9 @@ class CICommand {
188208
case COMMIT:
189209
build = new CommitBuild(cli, request, job.jobid);
190210
break;
211+
case CITGM:
212+
build = new CITGMBuild(cli, request, job.jobid);
213+
break;
191214
case BENCHMARK:
192215
build = new BenchmarkRun(cli, request, job.jobid);
193216
break;
@@ -208,8 +231,7 @@ class CICommand {
208231
}
209232
}
210233

211-
async aggregate() { // noop
212-
}
234+
async aggregate() {} // noop
213235

214236
async serialize() {
215237
const { argv, cli } = this;
@@ -238,7 +260,7 @@ class CICommand {
238260
if (this.json.length) {
239261
writeJson(argv.json, this.json);
240262
cli.separator('');
241-
cli.log(`Written JSON to ${argv.json}`);
263+
cli.log(`Wrote JSON to ${argv.json}`);
242264
} else {
243265
cli.error('No JSON generated');
244266
}
@@ -357,6 +379,7 @@ async function main(command, argv) {
357379
}
358380
case 'pr':
359381
case 'commit':
382+
case 'citgm':
360383
case 'benchmark': {
361384
commandHandler = new JobCommand(cli, request, argv, command);
362385
break;

docs/ncu-ci.md

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Commands:
2121
ncu-ci commit <jobid> Show results of a node-test-commit CI job
2222
ncu-ci benchmark <jobid> Show results of a benchmark-node-micro-benchmarks CI
2323
job
24+
ncu-ci citgm <jobid> Show results of a citgm-smoker job
2425
2526
Options:
2627
--version Show version number [boolean]
@@ -62,7 +63,7 @@ node on git:master ❯ ncu-ci rate commit
6263

6364
`ncu-ci walk <type>` walks CI and displays failures, where `<type>` can be either `pr` for `node-test-pull-request` or `commit` for `node-test-commit`.
6465

65-
Example
66+
Example:
6667
```sh
6768
node on git:master ❯ ncu-ci walk commit
6869
✔ Done--------------------------------------------------------------------------------
@@ -226,6 +227,40 @@ Notifying upstream projects of job completion
226227
Finished: SUCCESS
227228
```
228229
230+
### `ncu-ci citgm <jobid>`
231+
232+
`ncu-ci citgm <jobid>` shows the results of a given citgm-smoker job.
233+
234+
Example:
235+
```
236+
node on git:master ❯ ncu-ci citgm 2400 10:25AM
237+
--------------------------------------------------------------------------------
238+
[1/1] Running CITGM: 2400
239+
--------------------------------------------------------------------------------
240+
✔ Header data downloaded
241+
✔ Report data downloaded
242+
----------------------------------- Summary ------------------------------------
243+
Result FAILURE
244+
URL https://ci.nodejs.org/job/citgm-smoker/2400/testReport/
245+
Source https://github.com/nodejs/node/pull/34093/
246+
Commit [9ec07f42864c] 2020-06-30, Version 14.5.0 (Current)
247+
Date 2020-06-29 21:17:56 -0700
248+
Author Shelley Vohr <shelley.vohr@gmail.com>
249+
----------------------------------- Failures -----------------------------------
250+
┌────────────────────────┬───────────────────────┬───────────────────────┬─────────────────────────┬─────────────────────┬─────────────────┬────────────────────┐
251+
│ (index) │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │
252+
├────────────────────────┼───────────────────────┼───────────────────────┼─────────────────────────┼─────────────────────┼─────────────────┼────────────────────┤
253+
│ debian9-64 │ 'coffeescript-v2.5.1''through2-v4.0.2' │ │ │ │ │
254+
│ rhel7-s390x │ 'through2-v4.0.2' │ │ │ │ │ │
255+
│ fedora-latest-x64 │ 'coffeescript-v2.5.1''through2-v4.0.2' │ │ │ │ │
256+
│ ubuntu1604-64 │ 'coffeescript-v2.5.1''through2-v4.0.2' │ │ │ │ │
257+
│ osx1014 │ 'acorn-v7.3.1''coffeescript-v2.5.1''clinic-v6.0.2''ember-cli-v3.19.0''semver-v7.3.2''watchify-v3.11.1'
258+
│ ubuntu1804-64 │ 'coffeescript-v2.5.1''through2-v4.0.2' │ │ │ │ │
259+
│ fedora-last-latest-x64 │ 'coffeescript-v2.5.1''through2-v4.0.2' │ │ │ │ │
260+
│ centos7-ppcle │ 'coffeescript-v2.5.1''clinic-v6.0.2''torrent-stream-v1.2.0''through2-v4.0.2' │ │ │
261+
└────────────────────────┴───────────────────────┴───────────────────────┴─────────────────────────┴─────────────────────┴─────────────────┴────────────────────┘
262+
```
263+
229264
## Caveats
230265
231266
The CI failures are parsed using pattern matching and could be incorrect. Feel

lib/ci/ci_result_parser.js

Lines changed: 161 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,24 @@ const PR_TREE =
5050
const COMMIT_TREE =
5151
`result,url,number,${ACTION_TREE},${CHANGE_TREE},builtOn,` +
5252
`subBuilds[${BUILD_FIELDS}]`;
53+
const CITGM_MAIN_TREE =
54+
`result,url,number,${ACTION_TREE},${CHANGE_TREE},builtOn`;
55+
5356
// com.tikal.jenkins.plugins.multijob.MultiJobBuild
5457
const FANNED_TREE =
5558
`result,url,number,subBuilds[phaseName,${BUILD_FIELDS}]`;
59+
5660
// hudson.matrix.MatrixBuild
5761
const BUILD_TREE = 'result,runs[url,number,result],builtOn';
5862
const LINTER_TREE = 'result,url,number,builtOn';
5963
const CAUSE_TREE = 'upstreamBuild,upstreamProject,shortDescription,_class';
6064
const RUN_TREE = `actions[causes[${CAUSE_TREE}]],builtOn`;
6165

66+
// hudson.tasks.test.MatrixTestResult
67+
const RESULT_TREE = 'result[suites[cases[name,status]]]';
68+
const CITGM_REPORT_TREE =
69+
`failCount,skipCount,totalCount,childReports[child[url],${RESULT_TREE}]`;
70+
6271
function getPath(url) {
6372
return url.replace(`https://${CI_DOMAIN}/`, '').replace('api/json', '');
6473
}
@@ -115,11 +124,11 @@ class Job {
115124
return `https://${CI_DOMAIN}/${path}console`;
116125
}
117126

118-
async getBuildData() {
127+
async getBuildData(type = 'Build') {
119128
const { cli, path } = this;
120129
cli.startSpinner(`Querying data for ${path}`);
121130
const data = await this.getAPIData();
122-
cli.stopSpinner('Build data downloaded');
131+
cli.stopSpinner(`${type} data downloaded`);
123132
return data;
124133
}
125134

@@ -133,13 +142,13 @@ class Job {
133142
async getAPIData() {
134143
const { cli, request, path } = this;
135144
const url = this.apiUrl;
136-
cli.updateSpinner(`Querying API of ${path}`);
145+
cli.updateSpinner(`Querying API for ${path}`);
137146
return request.json(url);
138147
}
139148

140149
async getConsoleText() {
141150
const { cli, request, path } = this;
142-
cli.updateSpinner(`Querying console text of ${path}`);
151+
cli.updateSpinner(`Querying console text for ${path}`);
143152
const data = await request.text(this.consoleUrl);
144153
return data.replace(/\r/g, '');
145154
}
@@ -334,10 +343,13 @@ class TestBuild extends Job {
334343
}
335344

336345
formatAsJson() {
337-
const result = this.failures.map(item => Object.assign({
338-
source: this.sourceURL,
339-
upstream: this.jobUrl
346+
const { jobUrl, failures, sourceURL } = this;
347+
348+
const result = failures.map(item => Object.assign({
349+
source: sourceURL,
350+
upstream: jobUrl
340351
}, item));
352+
341353
return JSON.parse(JSON.stringify(result));
342354
}
343355
}
@@ -740,6 +752,147 @@ class PRBuild extends TestBuild {
740752
}
741753
}
742754

755+
class CITGMBuild extends TestBuild {
756+
constructor(cli, request, id) {
757+
const path = `job/citgm-smoker/${id}/`;
758+
const tree = CITGM_MAIN_TREE;
759+
760+
super(cli, request, path, tree);
761+
762+
this.id = id;
763+
}
764+
765+
async getResults() {
766+
const { id } = this;
767+
768+
let headerData;
769+
try {
770+
headerData = await this.getBuildData('Summary');
771+
} catch (err) {
772+
this.failures = [
773+
new NCUFailure({ url: this.apiUrl }, err.message)
774+
];
775+
return this.failures;
776+
}
777+
const { result } = headerData;
778+
779+
this.setBuildData(headerData);
780+
781+
// CITGM jobs store results in a different location than
782+
// they do summary data, so we need to update the endpoint
783+
// and issue a second API call in order to fetch result data.
784+
this.tree = CITGM_REPORT_TREE;
785+
this.path = `job/citgm-smoker/${this.id}/testReport/`;
786+
787+
let resultData;
788+
try {
789+
resultData = await this.getBuildData('Results');
790+
} catch (err) {
791+
this.failures = [
792+
new NCUFailure({ url: this.apiUrl }, err.message)
793+
];
794+
return this.failures;
795+
}
796+
797+
this.results = this.parseResults(resultData);
798+
799+
// Update id again so that it correctly displays in Summary output.
800+
this.path = `job/citgm-smoker/${id}/`;
801+
802+
return { result };
803+
}
804+
805+
parseResults(data) {
806+
const { childReports, totalCount, skipCount, failCount } = data;
807+
const results = { all: {}, failures: {}, statistics: {} };
808+
809+
const passCount = totalCount - failCount - skipCount;
810+
results.statistics.passed = passCount;
811+
results.statistics.total = totalCount;
812+
results.statistics.skipped = skipCount;
813+
results.statistics.failed = failCount;
814+
815+
childReports.forEach(platform => {
816+
const cases = flatten(platform.result.suites[0].cases);
817+
const url = platform.child.url;
818+
const nodeName = getNodeName(url);
819+
820+
results.all[nodeName] = { url, modules: cases };
821+
822+
const failedModules = cases.filter(c => c.status === 'FAILED');
823+
results.failures[nodeName] = { url, modules: failedModules };
824+
});
825+
826+
return results;
827+
}
828+
829+
displayBuilds() {
830+
const { cli, results } = this;
831+
const { failed, skipped, passed, total } = results.statistics;
832+
833+
cli.separator('Statistics');
834+
console.table({
835+
Failed: failed,
836+
Skipped: skipped,
837+
Passed: passed,
838+
Total: total
839+
});
840+
841+
cli.separator('Failures');
842+
const output = {};
843+
for (const platform in results.failures) {
844+
const modules = results.failures[platform].modules;
845+
const failures = modules.map(f => f.name);
846+
847+
output[platform] = failures;
848+
}
849+
850+
console.table(output);
851+
}
852+
853+
formatAsJson() {
854+
const { jobUrl, results, sourceURL } = this;
855+
856+
const result = {
857+
source: sourceURL,
858+
upstream: jobUrl,
859+
...results.statistics,
860+
...results.failures
861+
};
862+
863+
return JSON.parse(JSON.stringify(result));
864+
}
865+
866+
formatAsMarkdown() {
867+
const { jobUrl, result, results, id } = this;
868+
869+
let output = `# CITGM Data for [${id}](${jobUrl})\n\n`;
870+
871+
const { failed, skipped, passed, total } = results.statistics;
872+
873+
output += `## Statistics for job [${id}](${jobUrl})\n\n`;
874+
output += '| FAILED | SKIPPED | PASSED | TOTAL |\n';
875+
output += '| -------- | --------- | -------- | ------- |\n';
876+
output += `| ${pad(failed, 8)} | ${pad(skipped, 9)} |`;
877+
output += ` ${pad(passed, 8)} | ${pad(total, 7)} |\n\n`;
878+
879+
if (result === SUCCESS) {
880+
output += `Job [${id}](${jobUrl}) is green.`;
881+
return output;
882+
}
883+
884+
output += `## Failures in job [${id}](${jobUrl})\n\n`;
885+
for (const failure in results.failures) {
886+
const data = results.failures[failure];
887+
output += `### [${failure}](${data.url})\n\n`;
888+
889+
const failures = data.modules.map(f => `* ${f.name}`);
890+
output += `${failures.join('\n')}\n\n`;
891+
}
892+
return output;
893+
}
894+
}
895+
743896
function filterBuild(builds, type) {
744897
return builds
745898
.filter(build => build.result === type)
@@ -1034,6 +1187,7 @@ module.exports = {
10341187
PRBuild,
10351188
BenchmarkRun,
10361189
CommitBuild,
1190+
CITGMBuild,
10371191
HealthBuild,
10381192
jobCache,
10391193
parseJobFromURL,

0 commit comments

Comments
 (0)