Skip to content

chore: split CI builds to new files #464

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 13 additions & 11 deletions bin/ncu-ci
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,20 @@ const {
}
} = require('../lib/ci/ci_type_parser');

const { listBuilds } = require('../lib/ci/ci_utils');

const { jobCache } = require('../lib/ci/build-types/job');
const { PRBuild } = require('../lib/ci/build-types/pr_build');
const { CommitBuild } = require('../lib/ci/build-types/commit_build');
const { DailyBuild } = require('../lib/ci/build-types/daily_build');
const { FailureAggregator } = require('../lib/ci/failure_aggregator');
const { BenchmarkRun } = require('../lib/ci/build-types/benchmark_run');
const { HealthBuild } = require('../lib/ci/build-types/health_build');
const { CITGMBuild } = require('../lib/ci/build-types/citgm_build');
const {
PRBuild,
BenchmarkRun,
CommitBuild,
CITGMBuild,
DailyBuild,
CITGMComparisonBuild,
HealthBuild,
listBuilds,
FailureAggregator,
jobCache
} = require('../lib/ci/ci_result_parser');
CITGMComparisonBuild
} = require('../lib/ci/build-types/citgm_comparison_build');

const {
RunPRJob
} = require('../lib/ci/run_ci');
Expand Down
76 changes: 76 additions & 0 deletions lib/ci/build-types/benchmark_run.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
'use strict';

const { Job } = require('./job');
const { fold } = require('../ci_utils');

class BenchmarkRun extends Job {
constructor(cli, request, id) {
const path = `job/benchmark-node-micro-benchmarks/${id}/`;
super(cli, request, path);

this.results = '';
this.significantResults = '';
}

async getResults() {
const { path, cli } = this;
cli.startSpinner(`Querying results of ${path}`);
const text = await this.getConsoleText();
const index = text.indexOf('improvement');
if (index === -1) {
throw new Error('Not finished');
}
const breakIndex = text.lastIndexOf('\n', index);
const results = text.slice(breakIndex + 1)
.replace(/\nSending e-mails[\s\S]+/mg, '');
this.results = results;
cli.stopSpinner('Data downloaded');
this.significantResults = this.getSignificantResults(results);
return results;
}

getSignificantResults(data) {
const lines = data.split('\n');
const significant = lines.filter(line => line.indexOf('*') !== -1);
return significant.slice(0, -3).join('\n');
}

display() {
const { cli, results, significantResults } = this;
cli.log(results);
cli.separator('significant results');
cli.log(significantResults);
}

formatAsMarkdown() {
const { results, significantResults } = this;
const output = (fold('Benchmark results', results) + '\n\n' +
fold('Significant impact', significantResults) + '\n');
return output;
}

formatAsJson() {
const results = this.significantResults.split('\n').slice(1);
const json = [];
for (const line of results) {
const star = line.indexOf('*');
const name = line.slice(0, star).trim();
const [file, ...config] = name.split(' ');
const confidence = line.match(/(\*+)/)[1];
const lastStar = line.lastIndexOf('*');
const [improvement, ...accuracy] =
line.slice(lastStar + 1).split(/\s*%/).map(i => i.trim() + '%');
accuracy.pop(); // remove the last empty item
json.push({
file,
config,
confidence,
improvement,
accuracy
});
}
return json;
}
}

module.exports = { BenchmarkRun };
114 changes: 114 additions & 0 deletions lib/ci/build-types/commit_build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
'use strict';

const { TestBuild } = require('./test_build');
const { FannedBuild } = require('./fanned_build');
const { LinterBuild } = require('./linter_build');
const { NormalBuild } = require('./normal_build');
const { TestRun } = require('./test_run');
const { flatten } = require('../../utils');
const { statusType } = require('../ci_utils');
const { COMMIT_TREE } = require('../jenkins_constants');
const {
FAILURE_TYPES: {
BUILD_FAILURE,
NCU_FAILURE
},
FAILURE_CONSTRUCTORS: {
[BUILD_FAILURE]: BuildFailure,
[NCU_FAILURE]: NCUFailure
}
} = require('../ci_failure_parser');

class CommitBuild extends TestBuild {
constructor(cli, request, id) {
const path = `job/node-test-commit/${id}/`;
const tree = COMMIT_TREE;
super(cli, request, path, tree);
}

getBuilds({ result, subBuilds }) {
if (result === statusType.SUCCESS) {
const builds = this.builds = {
failed: [], aborted: [], pending: [], unstable: []
};
return { result: statusType.SUCCESS, builds };
}

const failed = subBuilds.filter(build => {
return build.result === statusType.FAILURE;
});
const aborted = subBuilds.filter(build => {
return build.result === statusType.ABORTED;
});
const pending = subBuilds.filter(build => {
return build.result === null;
});
const unstable = subBuilds.filter(build => {
return build.result === statusType.UNSTABLE;
});

// build: { buildNumber, jobName, result, url }
const builds = this.builds = { failed, aborted, pending, unstable };
return { result, builds };
}

// Get the failures and their reasons of this build
async getResults(data) {
const { path, cli, request } = this;

if (!data) {
try {
data = await this.getBuildData();
} catch (err) {
this.failures = [
new NCUFailure({ url: this.apiUrl }, err.message)
];
return this.failures;
}
}
this.setBuildData(data);
// No builds at all
if (data.result === statusType.FAILURE && !data.subBuilds.length) {
const failure = new BuildFailure(this, 'Failed to trigger sub builds');
this.failures = [failure];
return {
result: data.result,
builds: { failed: [], aborted: [], pending: [], unstable: [] },
failures: this.failures
};
}

const { result, builds } = this.getBuilds(data);

if (result !== statusType.FAILURE) {
return { result, builds, failures: [] };
}

if (!builds.failed.length) {
const failures = await this.parseConsoleText();
return { result, builds, failures };
}

cli.startSpinner(`Querying failures of ${path}`);
const promises = builds.failed.map(({ jobName, buildNumber, url }) => {
if (jobName.includes('fanned')) {
const cause = this.getCause(data.actions);
const isResumed = cause && cause._class.includes('ResumeCause');
return new FannedBuild(cli, request, jobName, buildNumber, isResumed)
.getResults();
} else if (jobName.includes('linter')) {
return new LinterBuild(cli, request, jobName, buildNumber).getResults();
} else if (jobName.includes('freestyle')) {
return new TestRun(cli, request, url).getResults();
}
return new NormalBuild(cli, request, jobName, buildNumber).getResults();
});
const rawFailures = await Promise.all(promises);

const failures = this.failures = flatten(rawFailures);
cli.stopSpinner('Data downloaded');
return { result, failures, builds };
}
}

module.exports = { CommitBuild };
28 changes: 28 additions & 0 deletions lib/ci/build-types/daily_build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use strict';

const { TestBuild } = require('./test_build');
const { PR_TREE } = require('../jenkins_constants');

class DailyBuild extends TestBuild {
constructor(cli, request, id) {
const path = `job/node-daily-master/${id}/`;
const tree = PR_TREE;
super(cli, request, path, tree);

this.commitBuild = null;
}

formatAsMarkdown() {
if (!this.commitBuild) {
let result = 'Failed to trigger node-daily-master';
if (this.builtOn) {
result += ` on ${this.builtOn}`;
}
result += `\n\nURL: ${this.jobUrl}`;
return result;
}
return super.formatAsMarkdown();
}
}

module.exports = { DailyBuild };
89 changes: 89 additions & 0 deletions lib/ci/build-types/fanned_build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
'use strict';

const { Job } = require('./job');
const { NormalBuild } = require('./normal_build');
const { statusType } = require('../ci_utils');
const { flatten } = require('../../utils');
const { FANNED_TREE } = require('../jenkins_constants');
const {
FAILURE_TYPES: {
BUILD_FAILURE,
NCU_FAILURE,
GIT_FAILURE,
RESUME_FAILURE
},
FAILURE_CONSTRUCTORS: {
[BUILD_FAILURE]: BuildFailure,
[NCU_FAILURE]: NCUFailure,
[RESUME_FAILURE]: ResumeFailure
}
} = require('../ci_failure_parser');

class FannedBuild extends Job {
constructor(cli, request, jobName, id, isResumed) {
// assert(jobName.includes('fanned'));
const path = `job/${jobName}/${id}/`;
const tree = FANNED_TREE;
super(cli, request, path, tree);

this.failures = [];
this.builtOn = undefined;
this.isResumed = isResumed;
}

// Get the failures and their reasons of this build
async getResults() {
const { cli, request } = this;

let data;
try {
data = await this.getAPIData();
} catch (err) {
this.failures = [
new NCUFailure({ url: this.apiUrl }, err.message)
];
return this.failures;
}
this.builtOn = data.builtOn;

if (!data.subBuilds.length) {
this.failures = [
new BuildFailure(this, 'Failed to trigger fanned build')
];
return this.failures;
}

const failedPhase = data.subBuilds.find(build => {
return build.result === statusType.FAILURE;
});

if (!failedPhase) {
return this.parseConsoleText();
}

const { jobName, buildNumber } = failedPhase;
const build = new NormalBuild(cli, request, jobName, buildNumber);
let failures = await build.getResults();
if (failures !== undefined) {
failures = flatten(failures);
}

if (this.isResumed) {
// XXX: if it's a resumed build, we replace the build/git failures
// with resume failures. Probably just a random guess, though
for (let i = 0; i < failures.length; ++i) {
const item = failures[i];
if (item.type === BUILD_FAILURE || item.type === GIT_FAILURE) {
failures[i] = new ResumeFailure(
item,
`Possible resume failure\n${item.reason}`
);
}
}
}
this.failures = failures;
return this.failures;
}
}

module.exports = { FannedBuild };
Loading