blob: c0f44d39c12000508f706b8e8637ce42d6b959a1 [file] [log] [blame] [edit]
/**
* @license
* Copyright 2021 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
import {assert} from '@open-wc/testing';
import './test-setup';
import {PluginApi} from '@gerritcodereview/typescript-api/plugin';
import {
CoverageRange,
CoverageType,
Side,
} from '@gerritcodereview/typescript-api/diff';
import {
ChangeInfo,
RevisionInfo,
} from '@gerritcodereview/typescript-api/rest-api';
import './coverage';
import {CoverageClient, CoverageResponse, PercentageData} from './coverage';
suite('coverage test', () => {
// Sample change info used for testing.
const sampleChangeInfo = {
host: 'chromium-review.googlesource.com',
project: 'chromium/src',
changeNum: 12345,
patchNum: 2,
};
// Sample coverage lines response from service; used for testing.
const sampleLinesResponse: CoverageResponse = {
data: {
files: [
{
path: 'base/test.cc',
lines: [
{
line: 10,
count: 10,
},
{
line: 11,
count: 0,
},
{
line: 12,
count: 0,
},
],
},
],
},
};
// Sample coverage ranges that match with the lines response.
const sampleCoverageRanges: {[path: string]: CoverageRange[]} = {
'base/test.cc': [
{
side: Side.RIGHT,
type: CoverageType.COVERED,
code_range: {
start_line: 10,
end_line: 10,
},
},
{
side: Side.RIGHT,
type: CoverageType.NOT_COVERED,
code_range: {
start_line: 11,
end_line: 12,
},
},
],
};
// Sample coverage percentages response from service; used for testing.
const samplePercentagesResponse: CoverageResponse = {
data: {
files: [
{
path: 'base/test.cc' as string,
absolute_coverage: {
covered: 3,
total: 10,
},
incremental_coverage: {
covered: 3,
total: 4,
},
},
],
},
};
// Sample coverage percentages that match with the percentages response.
const sampleCoveragePercentages: {[path: string]: PercentageData} = {
'base/test.cc': {
absolute: 30,
incremental: 75,
},
};
let coverageClient: CoverageClient;
let fetchStub = sinon.stub(window, 'fetch');
fetchStub.restore();
setup(() => {
fetchStub = sinon.stub(window, 'fetch');
coverageClient = new CoverageClient((null as unknown) as PluginApi);
});
teardown(() => {
fetchStub.restore();
});
test('get normalized host', () => {
assert.equal(
'chromium-review.googlesource.com',
coverageClient.getNormalizedHost('chromium-review.googlesource.com')
);
assert.equal(
'chromium-review.googlesource.com',
coverageClient.getNormalizedHost(
'canary-chromium-review.googlesource.com'
)
);
});
test('parse project name from gerrit url', () => {
assert.equal(
'chromium/src',
coverageClient.parseProjectFromPathName('/c/chromium/src/+/1369646')
);
assert.equal(
'chromium/src',
coverageClient.parseProjectFromPathName('/c/chromium/src/+/1369646')
);
assert.equal(
'chromium/src',
coverageClient.parseProjectFromPathName('/c/chromium/src/+/1369646/3')
);
assert.equal(
'chromium/src',
coverageClient.parseProjectFromPathName('/c/chromium/src/+/1369646/3/')
);
assert.equal(
'chromium/src',
coverageClient.parseProjectFromPathName(
'/c/chromium/src/+/1369646/3/base/test.cc'
)
);
assert.equal(
'chromium/src',
coverageClient.parseProjectFromPathName(
'/c/chromium/src/+/1369646/3/base/base/test.cc/'
)
);
});
test('fetch coverage lines for chromium', async () => {
const response = new window.Response(JSON.stringify(sampleLinesResponse), {
status: 200,
});
fetchStub.returns(Promise.resolve(response));
const responseJson = await coverageClient.fetchCoverageJsonData(
sampleChangeInfo,
'lines'
);
assert.equal(
'https://findit-for-me.appspot.com/coverage/api/' +
'coverage-data?host=chromium-review.googlesource.com&' +
'project=chromium%2Fsrc&change=12345&patchset=2&' +
'type=lines&format=json&concise=1',
fetchStub.getCall(0).args[0]
);
assert.deepEqual(sampleLinesResponse, responseJson);
});
test('fetch coverage percentages uses googlesource host', async () => {
const response = new window.Response(JSON.stringify(sampleLinesResponse), {
status: 200,
});
fetchStub.returns(Promise.resolve(response));
const responseJson = await coverageClient.fetchCoverageJsonData(
{
host: 'chromium-review.git.corp.google.com',
project: 'chromium/src',
changeNum: 12345,
patchNum: 2,
},
'percentages'
);
assert.equal(
'https://findit-for-me.appspot.com/coverage/api/' +
'coverage-data?host=chromium-review.googlesource.com&' +
'project=chromium%2Fsrc&change=12345&patchset=2&' +
'type=percentages&format=json&concise=1',
fetchStub.getCall(0).args[0]
);
assert.deepEqual(sampleLinesResponse, responseJson);
});
test('fetch coverage percentages for chromium', async () => {
const response = new window.Response(JSON.stringify(sampleLinesResponse), {
status: 200,
});
fetchStub.returns(Promise.resolve(response));
const responseJson = await coverageClient.fetchCoverageJsonData(
sampleChangeInfo,
'percentages'
);
assert.equal(
'https://findit-for-me.appspot.com/coverage/api/' +
'coverage-data?host=chromium-review.googlesource.com&' +
'project=chromium%2Fsrc&change=12345&patchset=2&' +
'type=percentages&format=json&concise=1',
fetchStub.getCall(0).args[0]
);
assert.deepEqual(sampleLinesResponse, responseJson);
});
test('fetch coverage lines for libassistant', async () => {
const response = new window.Response(JSON.stringify(sampleLinesResponse), {
status: 200,
});
fetchStub.returns(Promise.resolve(response));
const responseJson = await coverageClient.fetchCoverageJsonData(
{
host: 'libassistant-internal-review.googlesource.com',
project: 'libassistant/internal',
changeNum: 12345,
patchNum: 2,
},
'lines'
);
assert.equal(
'https://gob-coverage.googleplex.com/coverage/api/' +
'coverage-data?host=libassistant-internal-review.' +
'googlesource.com&project=libassistant%2Finternal&' +
'change=12345&patchset=2&type=lines&format=json&concise=1',
fetchStub.getCall(0).args[0]
);
assert.deepEqual(sampleLinesResponse, responseJson);
});
test('fetch coverage lines for unknown host', async () => {
const response = new window.Response(JSON.stringify(sampleLinesResponse), {
status: 200,
});
fetchStub.returns(Promise.resolve(response));
const responseJson = await coverageClient.fetchCoverageJsonData(
{
host: 'unknown-review.googlesource.com',
project: 'unknown/src',
changeNum: 12345,
patchNum: 2,
},
'lines'
);
assert.equal(
'https://findit-for-me.appspot.com/coverage/api/' +
'coverage-data?host=unknown-review.googlesource.com&' +
'project=unknown%2Fsrc&change=12345&patchset=2&' +
'type=lines&format=json&concise=1',
fetchStub.getCall(0).args[0]
);
assert.deepEqual(sampleLinesResponse, responseJson);
});
test('parse coverage ranges', () => {
const coverageRanges =
coverageClient.convertResponseJsonToCoverageRanges(sampleLinesResponse);
assert.deepEqual(sampleCoverageRanges, coverageRanges);
});
test('parse coverage percentages', () => {
const coveragePercentages =
coverageClient.convertResponseJsonToCoveragePercentages(
samplePercentagesResponse
);
assert.deepEqual(sampleCoveragePercentages, coveragePercentages);
});
test('coverage data are cached', async () => {
const gnhStub = sinon.stub(coverageClient, 'getNormalizedHost');
const ppfpnStub = sinon.stub(coverageClient, 'parseProjectFromPathName');
const fcjdStub = sinon.stub(coverageClient, 'fetchCoverageJsonData');
gnhStub.returns(sampleChangeInfo.host);
ppfpnStub.returns(sampleChangeInfo.project);
coverageClient.coverageData = {
changeInfo: sampleChangeInfo,
rangesPromise: new Promise((resolve, reject) => {
resolve(sampleCoverageRanges);
}),
percentagesPromise: new Promise((resolve, reject) => {
resolve(sampleCoveragePercentages);
}),
};
const ranges = await coverageClient.provideCoverageRanges(
12345,
'base/test.cc',
undefined,
2
);
assert.equal(false, fcjdStub.called);
assert.deepEqual(sampleCoverageRanges['base/test.cc'], ranges);
const percentages = await coverageClient.provideCoveragePercentages(
'12345',
'base/test.cc',
'2'
);
assert.equal(false, fcjdStub.called);
assert.deepEqual(sampleCoveragePercentages['base/test.cc'], percentages);
gnhStub.restore();
ppfpnStub.restore();
fcjdStub.restore();
});
test('coverage ranges are not cached', async () => {
const gnhStub = sinon.stub(coverageClient, 'getNormalizedHost');
const ppfpnStub = sinon.stub(coverageClient, 'parseProjectFromPathName');
const fcjdStub = sinon.stub(coverageClient, 'fetchCoverageJsonData');
gnhStub.returns(sampleChangeInfo.host);
ppfpnStub.returns(sampleChangeInfo.project);
fcjdStub
.withArgs(sampleChangeInfo, 'lines')
.returns(Promise.resolve(sampleLinesResponse));
const ranges = await coverageClient.provideCoverageRanges(
12345,
'base/test.cc',
undefined,
2
);
assert.deepEqual(sampleChangeInfo, coverageClient.coverageData!.changeInfo);
assert.deepEqual(sampleCoverageRanges['base/test.cc'], ranges);
gnhStub.restore();
ppfpnStub.restore();
fcjdStub.restore();
});
test('coverage percentages are not cached', async () => {
const gnhStub = sinon.stub(coverageClient, 'getNormalizedHost');
const ppfpnStub = sinon.stub(coverageClient, 'parseProjectFromPathName');
const fcjdStub = sinon.stub(coverageClient, 'fetchCoverageJsonData');
gnhStub.returns(sampleChangeInfo.host);
ppfpnStub.returns(sampleChangeInfo.project);
fcjdStub
.withArgs(sampleChangeInfo, 'percentages')
.returns(Promise.resolve(samplePercentagesResponse));
const percentages = await coverageClient.provideCoveragePercentages(
'12345',
'base/test.cc',
'2'
);
assert.deepEqual(sampleChangeInfo, coverageClient.coverageData!.changeInfo);
assert.deepEqual(sampleCoveragePercentages['base/test.cc'], percentages);
gnhStub.restore();
ppfpnStub.restore();
fcjdStub.restore();
});
test('incremental percentage is not available', async () => {
const response = {
data: {
files: [
{
path: 'base/test.cc' as string,
absolute_coverage: {
covered: 3,
total: 10,
},
},
],
},
};
const gnhStub = sinon.stub(coverageClient, 'getNormalizedHost');
const ppfpnStub = sinon.stub(coverageClient, 'parseProjectFromPathName');
const fcjdStub = sinon.stub(coverageClient, 'fetchCoverageJsonData');
gnhStub.returns(sampleChangeInfo.host);
ppfpnStub.returns(sampleChangeInfo.project);
fcjdStub
.withArgs(sampleChangeInfo, 'percentages')
.returns(Promise.resolve(response));
const percentages = await coverageClient.provideCoveragePercentages(
'12345',
'base/test.cc',
'2'
);
assert.deepEqual(sampleChangeInfo, coverageClient.coverageData!.changeInfo);
assert.deepEqual({absolute: 30}, percentages);
gnhStub.restore();
ppfpnStub.restore();
fcjdStub.restore();
});
test('prefetch coverage ranges', async () => {
const gnhStub = sinon.stub(coverageClient, 'getNormalizedHost');
const fcjdStub = sinon.stub(coverageClient, 'fetchCoverageJsonData');
gnhStub.returns(sampleChangeInfo.host);
fcjdStub
.withArgs(sampleChangeInfo, 'lines')
.returns(Promise.resolve(sampleLinesResponse));
coverageClient.prefetchCoverageRanges(
{project: 'chromium/src', _number: 12345} as unknown as ChangeInfo,
{_number: 2} as unknown as RevisionInfo
);
assert.deepEqual(sampleChangeInfo, coverageClient.coverageData!.changeInfo);
assert.deepEqual(
sampleCoverageRanges,
await coverageClient.coverageData!.rangesPromise
);
gnhStub.restore();
fcjdStub.restore();
});
test('invalid patchset number', async () => {
const gnhStub = sinon.stub(coverageClient, 'getNormalizedHost');
const ppfpnStub = sinon.stub(coverageClient, 'parseProjectFromPathName');
const fcjdStub = sinon.stub(coverageClient, 'fetchCoverageJsonData');
gnhStub.returns(sampleChangeInfo.host);
ppfpnStub.returns(sampleChangeInfo.project);
await coverageClient.provideCoverageRanges(
12345,
'base/test.cc',
undefined,
undefined
);
assert.equal(false, fcjdStub.called);
await coverageClient.provideCoveragePercentages(
'12345',
'base/test.cc',
'-1'
);
assert.equal(false, fcjdStub.called);
gnhStub.restore();
ppfpnStub.restore();
fcjdStub.restore();
});
// This test tests the scenario that if two requests were issued to fetch
// coverage ranges for different change/patchset, the eventual coverage
// ranges won't be overwritten incorrectly.
test('racing condition between multiple fetches', async () => {
const changeInfo1 = JSON.parse(JSON.stringify(sampleChangeInfo));
changeInfo1.patchNum = 1;
const changeInfo2 = JSON.parse(JSON.stringify(sampleChangeInfo));
changeInfo2.patchNum = 2;
const ranges1: {[path: string]: CoverageRange[]} = {
'base/test.cc': [
{
side: Side.RIGHT,
type: CoverageType.NOT_COVERED,
code_range: {
start_line: 10,
end_line: 10,
},
},
],
};
const fetchPromise1 = new Promise<{[path: string]: CoverageRange[]}>(
(resolve, reject) => {
setTimeout(() => {
resolve(ranges1);
}, 100);
}
);
const ranges2: {[path: string]: CoverageRange[]} = {
'base/test.cc': [
{
side: Side.RIGHT,
type: CoverageType.COVERED,
code_range: {
start_line: 20,
end_line: 20,
},
},
],
};
const fetchPromise2 = Promise.resolve(ranges2);
const gnhStub = sinon.stub(coverageClient, 'getNormalizedHost');
const ppfpnStub = sinon.stub(coverageClient, 'parseProjectFromPathName');
const fcrStub = sinon.stub(coverageClient, 'fetchCoverageRanges');
const fcpStub = sinon.stub(coverageClient, 'fetchCoveragePercentages');
gnhStub.returns(sampleChangeInfo.host);
ppfpnStub.returns(sampleChangeInfo.project);
fcrStub.withArgs(changeInfo1).returns(fetchPromise1);
fcrStub.withArgs(changeInfo2).returns(fetchPromise2);
fcpStub.returns(Promise.resolve(sampleCoveragePercentages));
coverageClient.prefetchCoverageRanges(
{
project: changeInfo1.project,
_number: changeInfo1.changeNum,
} as unknown as ChangeInfo,
{
_number: changeInfo1.patchNum,
} as unknown as RevisionInfo
);
coverageClient.provideCoverageRanges(
changeInfo2.changeNum,
'base/test.cc',
undefined,
changeInfo2.patchNum
);
// Even though the first request finishes after the second request, it
// shouldn't overwrite the coverage ranges promise because the user has
// moved on to a different change/patchset.
await fetchPromise1;
await fetchPromise2;
assert.deepEqual(ranges2, await coverageClient.coverageData!.rangesPromise);
gnhStub.restore();
ppfpnStub.restore();
fcpStub.restore();
fcrStub.restore();
});
test('show low incremental coverage warning', async () => {
const gnhStub = sinon.stub(coverageClient, 'getNormalizedHost');
const ppfpnStub = sinon.stub(coverageClient, 'parseProjectFromPathName');
gnhStub.returns(sampleChangeInfo.host);
ppfpnStub.returns(sampleChangeInfo.project);
const testCoveragePercentages = {
'base/test.cc': {
absolute: 85,
incremental: 69,
},
};
coverageClient.coverageData = {
changeInfo: sampleChangeInfo,
percentagesPromise: new Promise((resolve, reject) => {
resolve(testCoveragePercentages);
}),
rangesPromise: Promise.resolve(null),
};
const response = await coverageClient.mayBeShowLowCoverageAlert(
sampleChangeInfo.changeNum,
sampleChangeInfo.patchNum,
'Commit message without low coverage reason'
);
assert.equal(response.runs!.length, 1);
assert.equal(response.runs![0]!.results!.length, 1);
assert.equal(response.runs![0]!.results![0]!.category, 'WARNING');
gnhStub.restore();
ppfpnStub.restore();
});
test('show Low-Coverage-Reason provided info', async () => {
const gnhStub = sinon.stub(coverageClient, 'getNormalizedHost');
const ppfpnStub = sinon.stub(coverageClient, 'parseProjectFromPathName');
gnhStub.returns(sampleChangeInfo.host);
ppfpnStub.returns(sampleChangeInfo.project);
const testCoveragePercentages = {
'base/test.cc': {
absolute: 85,
incremental: 64,
},
};
coverageClient.coverageData = {
changeInfo: sampleChangeInfo,
percentagesPromise: new Promise((resolve, reject) => {
resolve(testCoveragePercentages);
}),
rangesPromise: Promise.resolve(null),
};
const response = await coverageClient.mayBeShowLowCoverageAlert(
sampleChangeInfo.changeNum,
sampleChangeInfo.patchNum,
'Commit message with Low-Coverage-Reason: EXPERIMENTAL_CODE experimental code'
);
assert.equal(response.runs!.length, 1);
assert.equal(response.runs![0]!.results!.length, 1);
assert.equal(response.runs![0]!.results![0]!.category, 'INFO');
gnhStub.restore();
ppfpnStub.restore();
});
test('show Low-Coverage-Reason not in desired format warning', async () => {
const gnhStub = sinon.stub(coverageClient, 'getNormalizedHost');
const ppfpnStub = sinon.stub(coverageClient, 'parseProjectFromPathName');
gnhStub.returns(sampleChangeInfo.host);
ppfpnStub.returns(sampleChangeInfo.project);
const testCoveragePercentages = {
'base/test.cc': {
absolute: 85,
incremental: 64,
},
};
coverageClient.coverageData = {
changeInfo: sampleChangeInfo,
percentagesPromise: new Promise((resolve, reject) => {
resolve(testCoveragePercentages);
}),
rangesPromise: Promise.resolve(null),
};
const response = await coverageClient.mayBeShowLowCoverageAlert(
sampleChangeInfo.changeNum,
sampleChangeInfo.patchNum,
'Commit message with Low-Coverage-Reason: experimental code'
);
assert.equal(response.runs!.length, 2);
assert.equal(response.runs![1]!.results!.length, 1);
assert.equal(response.runs![1]!.results![0]!.category, 'WARNING');
gnhStub.restore();
ppfpnStub.restore();
});
test('show percentage columns', async () => {
const ppfpnStub = sinon
.stub(coverageClient, 'parseProjectFromPathName')
.returns('chromium/src');
const configPromise = new Promise((resolve, reject) => {
resolve({enabled: true});
});
const oldPlugin = coverageClient.plugin;
const newPlugin = {
getPluginName: sinon.stub().returns('chromium-coverage'),
restApi: sinon.stub().returns({
get: sinon.stub().returns(configPromise),
}),
};
coverageClient.plugin = newPlugin as unknown as PluginApi;
assert.equal(true, await coverageClient.showPercentageColumns());
assert.equal(
'/projects/chromium%2Fsrc/chromium-coverage~config',
newPlugin.restApi().get.getCall(0).args[0]
);
ppfpnStub.restore();
coverageClient.plugin = oldPlugin;
});
});