blob: d4fb2677da7e4101d47f7ec6199d653cc021df60 [file] [log] [blame]
import {assert} from '@open-wc/testing';
import './chromium-binary-size';
import {
ChecksFetcher,
ChromiumBinarySizeListing,
ChromiumBinarySizeConfig,
} from './chromium-binary-size';
import {DATA_SYMBOL} from './checks-result';
import {
Category,
LinkIcon,
RunStatus,
} from '@gerritcodereview/typescript-api/checks';
import {
ChangeInfo,
ChangeStatus,
RevisionKind,
} from '@gerritcodereview/typescript-api/rest-api';
import {Build, BuildbucketV2Client} from './buildbucket-client';
suite('chromium-binary-size basic tests', () => {
let sandbox: sinon.SinonSandbox;
let fetcher: ChecksFetcher;
const config: ChromiumBinarySizeConfig = {
gerritHost: 'chromium-review.googlesource.com',
tryBucket: 'try',
tryBuilder: 'android-binary-size',
tryProject: 'chromium',
};
function stubSearch(searchPromise: object) {
sandbox.restore();
sandbox
.stub(BuildbucketV2Client.prototype, 'getAuthorizationHeader')
.returns(
new Promise((resolve: any, _) =>
resolve({access_token: 'accessToken', expires_at: 0})
)
);
return sandbox
.stub(BuildbucketV2Client.prototype, 'searchBuilds')
.returns(searchPromise as any);
}
setup(() => {
const plugin = {
getPluginName() {
return 'chromium-binary-size';
},
restApi() {
return {
getLoggedIn: async () => true,
get: async () => config,
};
},
checks() {
return {
register: () => {
return {};
},
announceUpdate: () => {
return {};
},
};
},
};
fetcher = new ChecksFetcher(plugin as any, 'host');
sandbox = sinon.createSandbox();
});
teardown(() => {
sandbox.restore();
});
test('getBuilds', async () => {
fetcher.pluginConfig = config;
const stub = stubSearch(
Promise.resolve({
builds: [{id: '1'}, {id: '2'}],
})
);
const predicate1 = {host: 'gerrit.example.com', change: 42, patchset: 2};
assert.deepEqual(await fetcher.getBuilds([predicate1]), [
{id: '1'},
{id: '2'},
] as Build[]);
const args: any = stub.getCall(0).args;
assert.deepEqual(args[0].predicate.gerritChanges[0], predicate1);
assert.deepEqual(args[0].predicate.builder.builder, 'android-binary-size');
});
test('selectRelevantBuilds', () => {
const builds = [
{endTime: '1234', output: {properties: {binary_size_plugin: {}}}},
{output: {properties: {binary_size_plugin: {}}}},
{output: {properties: {}}},
{},
];
assert.deepEqual(
fetcher.selectRelevantBuild(builds as unknown as Build[]),
builds[0] as unknown as Build
);
});
test('tryjobPatchPredicates', () => {
fetcher.pluginConfig = config;
assert.deepEqual(
fetcher.tryjobPatchPredicates({_number: 42} as ChangeInfo, [2, 3, 4]),
[
{
host: 'chromium-review.googlesource.com',
change: 42,
patchset: 2,
},
{
host: 'chromium-review.googlesource.com',
change: 42,
patchset: 3,
},
{
host: 'chromium-review.googlesource.com',
change: 42,
patchset: 4,
},
]
);
});
test('valid patch numbers', () => {
let change = {
revisions: {
rev1: {_number: 1, kind: RevisionKind.TRIVIAL_REBASE},
rev2: {_number: 2, kind: RevisionKind.TRIVIAL_REBASE},
rev3: {_number: 3, kind: RevisionKind.TRIVIAL_REBASE},
},
} as any;
assert.deepEqual(fetcher.computeValidPatchNums(change, 3), [3, 2, 1]);
assert.deepEqual(fetcher.computeValidPatchNums(change, 2), [2, 1]);
change.status = ChangeStatus.MERGED;
assert.deepEqual(fetcher.computeValidPatchNums(change, 3), [2, 1]);
change = {
revisions: {
rev1: {_number: 1, kind: RevisionKind.TRIVIAL_REBASE},
rev2: {_number: 2, kind: RevisionKind.REWORK},
rev3: {_number: 3, kind: RevisionKind.TRIVIAL_REBASE},
},
};
assert.deepEqual(fetcher.computeValidPatchNums(change, 3), [3, 2]);
});
test('getCheckRunStatus', () => {
assert.strictEqual(
fetcher.getCheckRunStatus({} as Build),
RunStatus.RUNNABLE
);
assert.strictEqual(
fetcher.getCheckRunStatus({status: 'SCHEDULED'} as Build),
'SCHEDULED'
);
assert.strictEqual(
fetcher.getCheckRunStatus({status: 'STARTED'} as Build),
RunStatus.RUNNING
);
assert.strictEqual(
fetcher.getCheckRunStatus({status: 'FAILURE'} as Build),
RunStatus.COMPLETED
);
});
test('getCheckRunStatusDesc', () => {
fetcher.pluginConfig = config;
assert.strictEqual(
fetcher.getCheckRunStatusDesc({} as Build),
'Run the android-binary-size trybot'
);
assert.strictEqual(
fetcher.getCheckRunStatusDesc({status: 'SCHEDULED'} as Build),
'Scheduling the android-binary-size tryjob'
);
assert.strictEqual(
fetcher.getCheckRunStatusDesc({status: 'STARTED'} as Build),
'Waiting for the android-binary-size trybot run to complete'
);
assert.strictEqual(
fetcher.getCheckRunStatusDesc({status: 'FAILURE'} as Build),
''
);
});
test('getCheckResultCategory', () => {
assert.strictEqual(
fetcher.getCheckResultCategory({status: 'SUCCESS'} as Build, [
{allowed: true} as ChromiumBinarySizeListing,
]),
Category.INFO
);
assert.strictEqual(
fetcher.getCheckResultCategory({status: 'SUCCESS'} as Build, [
{allowed: false} as ChromiumBinarySizeListing,
]),
Category.WARNING
);
assert.strictEqual(
fetcher.getCheckResultCategory({status: 'FAILURE'} as Build, [
{allowed: true} as ChromiumBinarySizeListing,
]),
Category.INFO
);
assert.strictEqual(
fetcher.getCheckResultCategory({status: 'FAILURE'} as Build, [
{allowed: false} as ChromiumBinarySizeListing,
]),
Category.ERROR
);
});
test('isEnabled caches results', async () => {
const stub = sinon.stub();
stub
.withArgs('/projects/foo/chromium-binary-size~config')
.returns(Promise.resolve(config));
stub
.withArgs('/projects/bar/chromium-binary-size~config')
.returns(Promise.reject());
fetcher.plugin.restApi = () =>
({
get: stub,
} as any);
assert.strictEqual(await fetcher.isEnabled('foo'), true);
assert.strictEqual(await fetcher.isEnabled('bar'), false);
sinon.assert.calledTwice(stub);
// Results should be cached.
assert.strictEqual(await fetcher.isEnabled('foo'), true);
assert.strictEqual(await fetcher.isEnabled('bar'), false);
sinon.assert.calledTwice(stub);
});
test('fetchChecks formats extra CheckResult links', async () => {
sinon.stub(fetcher, 'isEnabled').returns(Promise.resolve(true));
stubSearch(
Promise.resolve({
builds: [
{
id: '1',
status: 'SUCCESS',
endTime: '2019-05-29T23:41:18.637847Z',
output: {
properties: {
binary_size_plugin: {
extras: [
{text: 'APK Breakdown', url: 'http://apk.org'},
{text: 'Extra Link', url: 'http://extra.org'},
],
listings: [],
},
},
},
},
],
})
);
const res = await fetcher.fetchChecks({
changeNumber: 1,
patchsetNumber: 1,
repo: 'repo',
changeInfo: {
project: 'project-foo',
_number: 1,
revisions: {
deadbeef: {
_number: 1,
},
},
},
} as any);
const {links} = res.runs![0].results![0];
assert.deepEqual(links, [
{
url: 'http://apk.org',
tooltip: 'APK Breakdown',
primary: true,
icon: LinkIcon.FILE_PRESENT,
},
{
url: 'http://extra.org',
tooltip: 'Extra Link',
primary: false,
icon: LinkIcon.EXTERNAL,
},
]);
});
test('fetchChecks uses the latest build with output', async () => {
sinon.stub(fetcher, 'isEnabled').returns(Promise.resolve(true));
stubSearch(
Promise.resolve({
builds: [
{
id: '3',
status: 'SUCCESS',
endTime: '2019-05-29T23:41:18.637847Z',
output: {
properties: {
binary_size_plugin: {
extras: [],
listings: [{name: 'foo', allowed: true}],
},
},
},
},
{
id: '2',
status: 'SUCCESS',
endTime: '2019-06-29T23:41:18.637847Z',
output: {
properties: {
binary_size_plugin: {
extras: [],
listings: [{name: 'bar', allowed: true}],
},
},
},
},
{
id: '1',
status: 'CANCELED',
endTime: '2019-06-29T23:41:18.637847Z',
output: {
properties: {},
},
},
],
})
);
const res = await fetcher.fetchChecks({
changeNumber: 1,
patchsetNumber: 1,
repo: 'repo',
changeInfo: {
project: 'project-foo',
_number: 1,
revisions: {
deadbeef: {
_number: 1,
},
},
},
} as any);
const run = res.runs![0];
assert.deepEqual((run.results![0] as any)[DATA_SYMBOL].listings, [
{name: 'bar', allowed: true},
]);
});
test('fetchChecks creates CheckResults', async () => {
fetcher.pluginConfig = config;
const changeData: any = {
changeNumber: 1,
patchsetNumber: 1,
repo: 'repo',
changeInfo: {
project: 'project-foo',
_number: 1,
revisions: {
deadbeef: {
_number: 1,
},
},
},
};
let res;
let run;
// android-binary-size trybot has not been run.
stubSearch(Promise.resolve({builds: []}));
res = await fetcher.fetchChecks(changeData);
run = res.runs![0];
assert.strictEqual(run.status, RunStatus.RUNNABLE);
assert.strictEqual(run.results!.length, 0);
assert.strictEqual(run.actions![0].name, 'Run');
// android-binary-size trybot has been scheduled.
stubSearch(Promise.resolve({builds: [{status: 'SCHEDULED'}]}));
res = await fetcher.fetchChecks(changeData);
run = res.runs![0];
assert.strictEqual(run.status, 'SCHEDULED');
assert.strictEqual(run.results!.length, 1);
assert.strictEqual(run.results![0].category, Category.INFO);
assert.deepEqual((run.results![0] as any)[DATA_SYMBOL].listings, []);
assert.strictEqual(
run.results![0].summary,
'Scheduling the android-binary-size tryjob.'
);
assert.strictEqual(run.actions!.length, 0);
// android-binary-size trybot has started.
stubSearch(Promise.resolve({builds: [{status: 'STARTED'}]}));
res = await fetcher.fetchChecks(changeData);
run = res.runs![0];
assert.strictEqual(run.status, RunStatus.RUNNING);
assert.strictEqual(run.results!.length, 1);
assert.strictEqual(run.results![0].category, Category.INFO);
assert.deepEqual((run.results![0] as any)[DATA_SYMBOL].listings, []);
assert.strictEqual(
run.results![0].summary,
'Waiting for android-binary-size trybot run to complete.'
);
assert.strictEqual(run.actions!.length, 0);
// android-binary-size trybot but did not produce useful results.
stubSearch(
Promise.resolve({
builds: [
{
id: '1',
status: 'SUCCESS',
endTime: '2019-05-29T23:41:18.637847Z',
output: {
properties: {
binary_size_plugin: {
listings: [],
},
},
},
},
],
})
);
res = await fetcher.fetchChecks(changeData);
run = res.runs![0];
assert.strictEqual(run.status, RunStatus.COMPLETED);
assert.strictEqual(run.results!.length, 0);
// android-binary-size trybot and its checks were successful.
const allowedListings = [
{name: 'foo', allowed: true},
{name: 'bar', allowed: true},
];
stubSearch(
Promise.resolve({
builds: [
{
id: '1',
status: 'SUCCESS',
endTime: '2019-05-29T23:41:18.637847Z',
output: {
properties: {
binary_size_plugin: {
listings: allowedListings,
},
},
},
},
],
})
);
res = await fetcher.fetchChecks(changeData);
run = res.runs![0];
assert.strictEqual(run.status, RunStatus.COMPLETED);
assert.strictEqual(run.results!.length, 1);
assert.strictEqual(run.results![0].category, Category.INFO);
assert.deepEqual(
(run.results![0] as any)[DATA_SYMBOL].listings,
allowedListings
);
assert.strictEqual(run.actions!.length, 0);
// android-binary-size trybot but its checks were not successful.
const unallowedListings = [
{name: 'foo', allowed: true},
{name: 'bar', allowed: false},
];
stubSearch(
Promise.resolve({
builds: [
{
id: '1',
status: 'SUCCESS',
endTime: '2019-05-29T23:41:18.637847Z',
output: {
properties: {
binary_size_plugin: {
listings: unallowedListings,
},
},
},
},
],
})
);
res = await fetcher.fetchChecks(changeData);
run = res.runs![0];
assert.strictEqual(run.status, RunStatus.COMPLETED);
assert.deepEqual(
(run.results![0] as any)[DATA_SYMBOL].listings,
unallowedListings
);
assert.strictEqual(run.results!.length, 1);
assert.strictEqual(run.results![0].category, Category.WARNING);
assert.strictEqual(run.actions!.length, 0);
// android-binary-size trybot was unsuccessful.
stubSearch(
Promise.resolve({
builds: [
{
id: '1',
status: 'FAILURE',
endTime: '2019-05-29T23:41:18.637847Z',
output: {
properties: {
binary_size_plugin: {
listings: unallowedListings,
},
},
},
},
],
})
);
res = await fetcher.fetchChecks(changeData);
run = res.runs![0];
assert.strictEqual(run.status, RunStatus.COMPLETED);
assert.strictEqual(run.results!.length, 1);
assert.deepEqual(
(run.results![0] as any)[DATA_SYMBOL].listings,
unallowedListings
);
assert.strictEqual(run.results![0].category, Category.ERROR);
assert.strictEqual(run.actions!.length, 0);
// android-binary-size trybot was canceled.
stubSearch(
Promise.resolve({
builds: [
{
id: '1',
status: 'CANCELED',
endTime: '2019-05-29T23:41:18.637847Z',
},
],
})
);
res = await fetcher.fetchChecks(changeData);
run = res.runs![0];
assert.strictEqual(run.status, RunStatus.COMPLETED);
assert.strictEqual(run.results!.length, 1);
assert.deepEqual((run.results![0] as any)[DATA_SYMBOL].listings, []);
assert.strictEqual(run.results![0].category, Category.INFO);
assert.strictEqual(
run.results![0].summary,
'Run the android-binary-size trybot on the latest patchset to see ' +
'your binary size impact.'
);
assert.strictEqual(run.actions![0].name, 'Run');
});
test('fetchChecks creates message and summary from listings', async () => {
const changeData: any = {
changeNumber: 1,
patchsetNumber: 1,
repo: 'repo',
changeInfo: {
project: 'project-foo',
_number: 1,
revisions: {
deadbeef: {
_number: 1,
},
},
},
};
// Some listings failed.
stubSearch(
Promise.resolve({
builds: [
{
id: '1',
status: 'SUCCESS',
endTime: '2019-05-29T23:41:18.637847Z',
output: {
properties: {
binary_size_plugin: {
listings: [
{
name: 'Android Binary Size',
delta: '+999 bytes',
allowed: true,
},
{name: 'foo', allowed: false},
{name: 'baz', allowed: false},
],
},
},
},
},
],
})
);
let res = await fetcher.fetchChecks(changeData);
let run = res.runs![0];
assert.strictEqual(run.results!.length, 1);
assert.strictEqual(
run.results![0].summary,
'Android Binary Size changed by +999 bytes. 2 of 3 checks failed.'
);
assert.strictEqual(
run.results![0].message,
'Failing checks: foo, baz. Expand to view more.'
);
// All listings passed.
stubSearch(
Promise.resolve({
builds: [
{
id: '1',
status: 'SUCCESS',
endTime: '2019-05-29T23:41:18.637847Z',
output: {
properties: {
binary_size_plugin: {
listings: [
{
name: 'Android Binary Size',
delta: '+2 bytes',
allowed: true,
},
{name: 'foo', allowed: true},
],
},
},
},
},
],
})
);
res = await fetcher.fetchChecks(changeData);
run = res.runs![0];
assert.strictEqual(run.results!.length, 1);
assert.strictEqual(
run.results![0].summary,
'Android Binary Size changed by +2 bytes. All checks passed.'
);
assert.strictEqual(run.results![0].message, 'Expand to view more.');
});
test('getHumanReadableDelta', () => {
assert.strictEqual(fetcher.getHumanReadableDelta(null), '(unknown)');
assert.strictEqual(
fetcher.getHumanReadableDelta('+1023 bytes'),
'(+1023 B)'
);
assert.strictEqual(
fetcher.getHumanReadableDelta('+1024 bytes'),
'(+1 KiB)'
);
assert.strictEqual(
fetcher.getHumanReadableDelta('+102,400 bytes'),
'(+100 KiB)'
);
// 1024*1024 = 1048576
assert.strictEqual(
fetcher.getHumanReadableDelta('+104,857,600 bytes'),
'(+100 MiB)'
);
assert.strictEqual(
fetcher.getHumanReadableDelta('+104,857,600,000 bytes'),
'(+100,000 MiB)'
);
// 1024*1024*2.5 = 2621440
assert.strictEqual(
fetcher.getHumanReadableDelta('-2,621,440 bytes'),
'(-2.5 MiB)'
);
// 2226000/1024/1024 = 2.12287902832
assert.strictEqual(
fetcher.getHumanReadableDelta('-2,226,000 bytes'),
'(-2.1 MiB)'
);
});
});