[QA] [Code Coverage] Add Three Dot Compare Url (#70525)

This commit is contained in:
Tre 2020-07-02 10:01:54 -06:00 committed by GitHub
parent 0b6674edf5
commit 12460b456c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 268 additions and 59 deletions

View file

@ -23,15 +23,22 @@ kibanaPipeline(timeoutMinutes: 240) {
}
def handleIngestion(timestamp) {
def previousSha = handlePreviousSha()
kibanaPipeline.downloadCoverageArtifacts()
kibanaCoverage.prokLinks("### Process HTML Links")
kibanaCoverage.collectVcsInfo("### Collect VCS Info")
kibanaCoverage.generateReports("### Merge coverage reports")
kibanaCoverage.uploadCombinedReports()
kibanaCoverage.ingest(env.JOB_NAME, BUILD_NUMBER, BUILD_URL, timestamp, '### Ingest && Upload')
kibanaCoverage.ingest(env.JOB_NAME, BUILD_NUMBER, BUILD_URL, timestamp, previousSha, '### Ingest && Upload')
kibanaCoverage.uploadCoverageStaticSite(timestamp)
}
def handlePreviousSha() {
def previous = kibanaCoverage.downloadPrevious('### Download OLD Previous')
kibanaCoverage.uploadPrevious('### Upload NEW Previous')
return previous
}
def handleFail() {
def buildStatus = buildUtils.getBuildStatus()
if(params.NOTIFY_ON_FAILURE && buildStatus != 'SUCCESS' && buildStatus != 'ABORTED' && buildStatus != 'UNSTABLE') {

View file

@ -17,39 +17,39 @@
* under the License.
*/
import { fromNullable, tryCatch, left, right } from '../either';
import * as Either from '../either';
import { noop } from '../utils';
import expect from '@kbn/expect';
const pluck = (x) => (obj) => obj[x];
const expectNull = (x) => expect(x).to.equal(null);
const attempt = (obj) => fromNullable(obj).map(pluck('detail'));
const attempt = (obj) => Either.fromNullable(obj).map(pluck('detail'));
describe(`either datatype functions`, () => {
describe(`helpers`, () => {
it(`'fromNullable' should be a fn`, () => {
expect(typeof fromNullable).to.be('function');
expect(typeof Either.fromNullable).to.be('function');
});
it(`'tryCatch' should be a fn`, () => {
expect(typeof tryCatch).to.be('function');
it(`' Either.tryCatch' should be a fn`, () => {
expect(typeof Either.tryCatch).to.be('function');
});
it(`'left' should be a fn`, () => {
expect(typeof left).to.be('function');
expect(typeof Either.left).to.be('function');
});
it(`'right' should be a fn`, () => {
expect(typeof right).to.be('function');
expect(typeof Either.right).to.be('function');
});
});
describe('tryCatch', () => {
describe(' Either.tryCatch', () => {
let sut = undefined;
it(`should return a 'Left' on error`, () => {
sut = tryCatch(() => {
sut = Either.tryCatch(() => {
throw new Error('blah');
});
expect(sut.inspect()).to.be('Left(Error: blah)');
});
it(`should return a 'Right' on successful execution`, () => {
sut = tryCatch(noop);
sut = Either.tryCatch(noop);
expect(sut.inspect()).to.be('Right(undefined)');
});
});

View file

@ -18,7 +18,7 @@
*/
import expect from '@kbn/expect';
import { ciRunUrl, coveredFilePath, itemizeVcs } from '../transforms';
import { ciRunUrl, coveredFilePath, itemizeVcs, prokPrevious } from '../transforms';
describe(`Transform fn`, () => {
describe(`ciRunUrl`, () => {
@ -61,6 +61,14 @@ describe(`Transform fn`, () => {
});
});
});
describe(`prokPrevious`, () => {
const comparePrefixF = () => 'https://github.com/elastic/kibana/compare';
process.env.FETCHED_PREVIOUS = 'A';
it(`should return a previous compare url`, () => {
const actual = prokPrevious(comparePrefixF)('B');
expect(actual).to.be(`https://github.com/elastic/kibana/compare/A...B`);
});
});
describe(`itemizeVcs`, () => {
it(`should return a sha url`, () => {
const vcsInfo = [

View file

@ -31,6 +31,7 @@ const env = {
ES_HOST: 'https://super:changeme@some.fake.host:9243',
NODE_ENV: 'integration_test',
COVERAGE_INGESTION_KIBANA_ROOT: '/var/lib/jenkins/workspace/elastic+kibana+code-coverage/kibana',
FETCHED_PREVIOUS: 'FAKE_PREVIOUS_SHA',
};
describe('Ingesting coverage', () => {
@ -68,31 +69,64 @@ describe('Ingesting coverage', () => {
expect(folderStructure.test(actualUrl)).ok();
});
});
describe(`vcsInfo`, () => {
let stdOutWithVcsInfo = '';
describe(`without a commit msg in the vcs info file`, () => {
let vcsInfo;
const args = [
'scripts/ingest_coverage.js',
'--verbose',
'--vcsInfoPath',
'src/dev/code_coverage/ingest_coverage/integration_tests/mocks/VCS_INFO_missing_commit_msg.txt',
'--path',
];
beforeAll(async () => {
const args = [
'scripts/ingest_coverage.js',
'--verbose',
'--vcsInfoPath',
'src/dev/code_coverage/ingest_coverage/integration_tests/mocks/VCS_INFO_missing_commit_msg.txt',
'--path',
];
const opts = [...args, resolved];
const { stdout } = await execa(process.execPath, opts, { cwd: ROOT_DIR, env });
vcsInfo = stdout;
stdOutWithVcsInfo = stdout;
});
it(`should be an obj w/o a commit msg`, () => {
const commitMsgRE = /"commitMsg"/;
expect(commitMsgRE.test(vcsInfo)).to.not.be.ok();
expect(commitMsgRE.test(stdOutWithVcsInfo)).to.not.be.ok();
});
});
describe(`including previous sha`, () => {
let stdOutWithPrevious = '';
beforeAll(async () => {
const opts = [...verboseArgs, resolved];
const { stdout } = await execa(process.execPath, opts, { cwd: ROOT_DIR, env });
stdOutWithPrevious = stdout;
});
it(`should have a vcsCompareUrl`, () => {
const previousCompareUrlRe = /vcsCompareUrl.+\s*.*https.+compare\/FAKE_PREVIOUS_SHA\.\.\.f07b34f6206/;
expect(previousCompareUrlRe.test(stdOutWithPrevious)).to.be.ok();
});
});
describe(`with a commit msg in the vcs info file`, () => {
beforeAll(async () => {
const args = [
'scripts/ingest_coverage.js',
'--verbose',
'--vcsInfoPath',
'src/dev/code_coverage/ingest_coverage/integration_tests/mocks/VCS_INFO.txt',
'--path',
];
const opts = [...args, resolved];
const { stdout } = await execa(process.execPath, opts, { cwd: ROOT_DIR, env });
stdOutWithVcsInfo = stdout;
});
it(`should be an obj w/ a commit msg`, () => {
const commitMsgRE = /commitMsg/;
expect(commitMsgRE.test(stdOutWithVcsInfo)).to.be.ok();
});
});
});
describe(`team assignment`, () => {
let shouldNotHavePipelineOut = '';
let shouldIndeedHavePipelineOut = '';
const args = [
'scripts/ingest_coverage.js',
'--verbose',
@ -101,26 +135,30 @@ describe('Ingesting coverage', () => {
'--path',
];
it(`should not occur when going to the totals index`, async () => {
const teamAssignRE = /"pipeline":/;
const shouldNotHavePipelineOut = await prokJustTotalOrNot(true, args);
const teamAssignRE = /pipeline:/;
beforeAll(async () => {
const summaryPath = 'jest-combined/coverage-summary-just-total.json';
const resolved = resolve(MOCKS_DIR, summaryPath);
const opts = [...args, resolved];
const { stdout } = await execa(process.execPath, opts, { cwd: ROOT_DIR, env });
shouldNotHavePipelineOut = stdout;
});
beforeAll(async () => {
const summaryPath = 'jest-combined/coverage-summary-manual-mix.json';
const resolved = resolve(MOCKS_DIR, summaryPath);
const opts = [...args, resolved];
const { stdout } = await execa(process.execPath, opts, { cwd: ROOT_DIR, env });
shouldIndeedHavePipelineOut = stdout;
});
it(`should not occur when going to the totals index`, () => {
const actual = teamAssignRE.test(shouldNotHavePipelineOut);
expect(actual).to.not.be.ok();
});
it(`should indeed occur when going to the coverage index`, async () => {
const shouldIndeedHavePipelineOut = await prokJustTotalOrNot(false, args);
const onlyForTestingRe = /ingest-pipe=>team_assignment/;
const actual = onlyForTestingRe.test(shouldIndeedHavePipelineOut);
it(`should indeed occur when going to the coverage index`, () => {
const actual = /ingest-pipe=>team_assignment/.test(shouldIndeedHavePipelineOut);
expect(actual).to.be.ok();
});
});
});
async function prokJustTotalOrNot(isTotal, args) {
const justTotalPath = 'jest-combined/coverage-summary-just-total.json';
const notJustTotalPath = 'jest-combined/coverage-summary-manual-mix.json';
const resolved = resolve(MOCKS_DIR, isTotal ? justTotalPath : notJustTotalPath);
const opts = [...args, resolved];
const { stdout } = await execa(process.execPath, opts, { cwd: ROOT_DIR, env });
return stdout;
}

View file

@ -0,0 +1,84 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/* eslint new-cap: 0 */
/* eslint no-unused-vars: 0 */
/**
* Just monad used for valid values
*/
export function Just(x) {
return {
value: () => x,
map: (f) => Maybe.of(f(x)),
isJust: () => true,
inspect: () => `Just(${x})`,
};
}
Just.of = function of(x) {
return Just(x);
};
export function just(x) {
return Just.of(x);
}
/**
* Maybe monad.
* Maybe.fromNullable` lifts an `x` into either a `Just`
* or a `Nothing` typeclass.
*/
export function Maybe(x) {
return {
chain: (f) => f(x),
map: (f) => Maybe(f(x)),
inspect: () => `Maybe(${x})`,
nothing: () => Nothing(),
isNothing: () => false,
isJust: () => false,
};
}
Maybe.of = function of(x) {
return just(x);
};
export function maybe(x) {
return Maybe.of(x);
}
export function fromNullable(x) {
return x !== null && x !== undefined && x !== false && x !== 'undefined' ? just(x) : nothing();
}
/**
* Nothing wraps undefined or null values and prevents errors
* that otherwise occur when mapping unexpected undefined or null
* values
*/
export function Nothing() {
return {
value: () => {
throw new TypeError(`Nothing algebraic data type returns...no value :)`);
},
map: (f) => {},
isNothing: () => true,
inspect: () => `[Nothing]`,
};
}
export function nothing() {
return Nothing();
}

View file

@ -17,10 +17,11 @@
* under the License.
*/
import { left, right, fromNullable } from './either';
import * as Either from './either';
import { fromNullable } from './maybe';
import { always, id, noop } from './utils';
const maybeTotal = (x) => (x === 'total' ? left(x) : right(x));
const maybeTotal = (x) => (x === 'total' ? Either.left(x) : Either.right(x));
const trimLeftFrom = (text, x) => x.substr(x.indexOf(text));
@ -54,13 +55,13 @@ const root = (urlBase) => (ts) => (testRunnerType) =>
`${urlBase}/${ts}/${testRunnerType.toLowerCase()}-combined`;
const prokForTotalsIndex = (mutateTrue) => (urlRoot) => (obj) =>
right(obj)
Either.right(obj)
.map(mutateTrue)
.map(always(`${urlRoot}/index.html`))
.fold(noop, id);
const prokForCoverageIndex = (root) => (mutateFalse) => (urlRoot) => (obj) => (siteUrl) =>
right(siteUrl)
Either.right(siteUrl)
.map((x) => {
mutateFalse(obj);
return x;
@ -87,7 +88,7 @@ export const coveredFilePath = (obj) => {
const withoutCoveredFilePath = always(obj);
const leadingSlashRe = /^\//;
const maybeDropLeadingSlash = (x) => (leadingSlashRe.test(x) ? right(x) : left(x));
const maybeDropLeadingSlash = (x) => (leadingSlashRe.test(x) ? Either.right(x) : Either.left(x));
const dropLeadingSlash = (x) => x.replace(leadingSlashRe, '');
const dropRoot = (root) => (x) =>
maybeDropLeadingSlash(x.replace(root, '')).fold(id, dropLeadingSlash);
@ -97,11 +98,23 @@ export const coveredFilePath = (obj) => {
};
export const ciRunUrl = (obj) =>
fromNullable(process.env.CI_RUN_URL).fold(always(obj), (ciRunUrl) => ({ ...obj, ciRunUrl }));
Either.fromNullable(process.env.CI_RUN_URL).fold(always(obj), (ciRunUrl) => ({
...obj,
ciRunUrl,
}));
const size = 50;
const truncateMsg = (msg) => (msg.length > size ? `${msg.slice(0, 50)}...` : msg);
const truncateMsg = (msg) => {
const res = msg.length > size ? `${msg.slice(0, 50)}...` : msg;
return res;
};
const comparePrefix = () => 'https://github.com/elastic/kibana/compare';
export const prokPrevious = (comparePrefixF) => (currentSha) => {
return Either.fromNullable(process.env.FETCHED_PREVIOUS).fold(
noop,
(previousSha) => `${comparePrefixF()}/${previousSha}...${currentSha}`
);
};
export const itemizeVcs = (vcsInfo) => (obj) => {
const [branch, sha, author, commitMsg] = vcsInfo;
@ -111,12 +124,23 @@ export const itemizeVcs = (vcsInfo) => (obj) => {
author,
vcsUrl: `https://github.com/elastic/kibana/commit/${sha}`,
};
const res = fromNullable(commitMsg).fold(always({ ...obj, vcs }), (msg) => ({
...obj,
vcs: { ...vcs, commitMsg: truncateMsg(msg) },
}));
return res;
const mutateVcs = (x) => (vcs.commitMsg = truncateMsg(x));
fromNullable(commitMsg).map(mutateVcs);
const vcsCompareUrl = process.env.FETCHED_PREVIOUS
? `${comparePrefix()}/${process.env.FETCHED_PREVIOUS}...${sha}`
: 'PREVIOUS SHA NOT PROVIDED';
// const withoutPreviousL = always({ ...obj, vcs });
const withPreviousR = () => ({
...obj,
vcs: {
...vcs,
vcsCompareUrl,
},
});
return withPreviousR();
};
export const testRunner = (obj) => {
const { jsonSummaryPath } = obj;

View file

@ -14,6 +14,10 @@ CI_RUN_URL=$3
export CI_RUN_URL
echo "### debug CI_RUN_URL: ${CI_RUN_URL}"
FETCHED_PREVIOUS=$4
export FETCHED_PREVIOUS
echo "### debug FETCHED_PREVIOUS: ${FETCHED_PREVIOUS}"
ES_HOST="https://${USER_FROM_VAULT}:${PASS_FROM_VAULT}@${HOST_FROM_VAULT}"
export ES_HOST

View file

@ -1,3 +1,46 @@
def downloadPrevious(title) {
def vaultSecret = 'secret/gce/elastic-bekitzur/service-account/kibana'
withGcpServiceAccount.fromVaultSecret(vaultSecret, 'value') {
kibanaPipeline.bash('''
gsutil -m cp -r gs://elastic-bekitzur-kibana-coverage-live/previous_pointer/previous.txt . || echo "### Previous Pointer NOT FOUND?"
if [ -e ./previous.txt ]; then
mv previous.txt downloaded_previous.txt
echo "### downloaded_previous.txt"
cat downloaded_previous.txt
fi
''', title)
def previous = sh(script: 'cat downloaded_previous.txt', label: '### Capture Previous Sha', returnStdout: true).trim()
return previous
}
}
def uploadPrevious(title) {
def vaultSecret = 'secret/gce/elastic-bekitzur/service-account/kibana'
withGcpServiceAccount.fromVaultSecret(vaultSecret, 'value') {
kibanaPipeline.bash('''
collectPrevious() {
PREVIOUS=$(git log --pretty=format:%h -1)
echo "### PREVIOUS: ${PREVIOUS}"
echo $PREVIOUS > previous.txt
}
collectPrevious
gsutil cp previous.txt gs://elastic-bekitzur-kibana-coverage-live/previous_pointer/
''', title)
}
}
def uploadCoverageStaticSite(timestamp) {
def uploadPrefix = "gs://elastic-bekitzur-kibana-coverage-live/"
def uploadPrefixWithTimeStamp = "${uploadPrefix}${timestamp}/"
@ -67,6 +110,7 @@ EOF
cat src/dev/code_coverage/www/index.html
''', "### Combine Index Partials")
}
def collectVcsInfo(title) {
kibanaPipeline.bash('''
predicate() {
@ -125,31 +169,31 @@ def uploadCombinedReports() {
)
}
def ingestData(jobName, buildNum, buildUrl, title) {
def ingestData(jobName, buildNum, buildUrl, previousSha, title) {
kibanaPipeline.bash("""
source src/dev/ci_setup/setup_env.sh
yarn kbn bootstrap --prefer-offline
# Using existing target/kibana-coverage folder
. src/dev/code_coverage/shell_scripts/ingest_coverage.sh '${jobName}' ${buildNum} '${buildUrl}'
. src/dev/code_coverage/shell_scripts/ingest_coverage.sh '${jobName}' ${buildNum} '${buildUrl}' ${previousSha}
""", title)
}
def ingestWithVault(jobName, buildNum, buildUrl, title) {
def ingestWithVault(jobName, buildNum, buildUrl, previousSha, title) {
def vaultSecret = 'secret/kibana-issues/prod/coverage/elasticsearch'
withVaultSecret(secret: vaultSecret, secret_field: 'host', variable_name: 'HOST_FROM_VAULT') {
withVaultSecret(secret: vaultSecret, secret_field: 'username', variable_name: 'USER_FROM_VAULT') {
withVaultSecret(secret: vaultSecret, secret_field: 'password', variable_name: 'PASS_FROM_VAULT') {
ingestData(jobName, buildNum, buildUrl, title)
ingestData(jobName, buildNum, buildUrl, previousSha, title)
}
}
}
}
def ingest(jobName, buildNumber, buildUrl, timestamp, title) {
def ingest(jobName, buildNumber, buildUrl, timestamp, previousSha, title) {
withEnv([
"TIME_STAMP=${timestamp}",
]) {
ingestWithVault(jobName, buildNumber, buildUrl, title)
ingestWithVault(jobName, buildNumber, buildUrl, previousSha, title)
}
}