Skip to content

Commit

Permalink
[CI] Comment flaky test results on tested PR (elastic#183043)
Browse files Browse the repository at this point in the history
## Summary
Extends the flaky-test-runner with the capability to comment on the
flaky test runs on the PR that's being tested.

Closes: elastic#173129

- chore(flaky-test-runner): Add a step to collect results and comment on
the tested PR

(cherry picked from commit 38d4230)
  • Loading branch information
delanni committed May 13, 2024
1 parent 57b5dc4 commit 93677b4
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 4 deletions.
22 changes: 19 additions & 3 deletions .buildkite/pipeline-utils/buildkite/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,19 @@ export interface BuildkiteGroup {
steps: BuildkiteStep[];
}

export type BuildkiteStep = BuildkiteCommandStep | BuildkiteInputStep | BuildkiteTriggerStep;
export type BuildkiteStep =
| BuildkiteCommandStep
| BuildkiteInputStep
| BuildkiteTriggerStep
| BuildkiteWaitStep;

export interface BuildkiteCommandStep {
command: string;
label: string;
parallelism?: number;
concurrency?: number;
concurrency_group?: string;
concurrency_method?: 'eager' | 'ordered';
agents:
| {
queue: string;
Expand All @@ -49,14 +56,15 @@ export interface BuildkiteCommandStep {
};
timeout_in_minutes?: number;
key?: string;
cancel_on_build_failing?: boolean;
depends_on?: string | string[];
retry?: {
automatic: Array<{
exit_status: string;
limit: number;
}>;
};
env?: { [key: string]: string };
env?: { [key: string]: string | number };
}

interface BuildkiteInputTextField {
Expand Down Expand Up @@ -100,7 +108,7 @@ export interface BuildkiteInputStep {
limit: number;
}>;
};
env?: { [key: string]: string };
env?: { [key: string]: string | number };
}

export interface BuildkiteTriggerStep {
Expand Down Expand Up @@ -138,6 +146,14 @@ export interface BuildkiteTriggerBuildParams {
pull_request_repository?: string;
}

export interface BuildkiteWaitStep {
wait: string;
if?: string;
allow_dependency_failure?: boolean;
continue_on_failure?: boolean;
branches?: string;
}

export class BuildkiteClient {
http: AxiosInstance;
exec: ExecType;
Expand Down
22 changes: 21 additions & 1 deletion .buildkite/pipelines/flaky_tests/pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import { groups } from './groups.json';
import { BuildkiteStep } from '#pipeline-utils';

const configJson = process.env.KIBANA_FLAKY_TEST_RUNNER_CONFIG;
if (!configJson) {
Expand Down Expand Up @@ -138,7 +139,7 @@ if (totalJobs > MAX_JOBS) {
process.exit(1);
}

const steps: any[] = [];
const steps: BuildkiteStep[] = [];
const pipeline = {
env: {
IGNORE_SHIP_CI_STATS_ERROR: 'true',
Expand All @@ -154,6 +155,7 @@ steps.push({
if: "build.env('KIBANA_BUILD_ID') == null || build.env('KIBANA_BUILD_ID') == ''",
});

let suiteIndex = 0;
for (const testSuite of testSuites) {
if (testSuite.count <= 0) {
continue;
Expand All @@ -165,6 +167,7 @@ for (const testSuite of testSuites) {
env: {
FTR_CONFIG: testSuite.ftrConfig,
},
key: `ftr-suite-${suiteIndex++}`,
label: `${testSuite.ftrConfig}`,
parallelism: testSuite.count,
concurrency,
Expand Down Expand Up @@ -195,6 +198,7 @@ for (const testSuite of testSuites) {
command: `.buildkite/scripts/steps/functional/${suiteName}.sh`,
label: group.name,
agents: getAgentRule(agentQueue),
key: `cypress-suite-${suiteIndex++}`,
depends_on: 'build',
timeout_in_minutes: 150,
parallelism: testSuite.count,
Expand All @@ -221,4 +225,20 @@ for (const testSuite of testSuites) {
}
}

pipeline.steps.push({
wait: '~',
continue_on_failure: true,
});

pipeline.steps.push({
command: 'ts-node .buildkite/pipelines/flaky_tests/post_stats_on_pr.ts',
label: 'Post results on Github pull request',
agents: getAgentRule('n2-4-spot'),
timeout_in_minutes: 15,
retry: {
automatic: [{ exit_status: '-1', limit: 3 }],
},
soft_fail: true,
});

console.log(JSON.stringify(pipeline, null, 2));
112 changes: 112 additions & 0 deletions .buildkite/pipelines/flaky_tests/post_stats_on_pr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { BuildkiteClient, getGithubClient } from '#pipeline-utils';

interface TestSuiteResult {
name: string;
success: boolean;
successCount: number;
groupSize: number;
}

async function main() {
// Get buildkite build
const buildkite = new BuildkiteClient();
const buildkiteBuild = await buildkite.getBuild(
process.env.BUILDKITE_PIPELINE_SLUG!,
process.env.BUILDKITE_BUILD_NUMBER!
);
const buildLink = `[${buildkiteBuild.pipeline.slug}#${buildkiteBuild.number}](${buildkiteBuild.web_url})`;

// Calculate success metrics
const jobs = buildkiteBuild.jobs;
const testSuiteRuns = jobs.filter((step) => {
return step.step_key?.includes('ftr-suite') || step.step_key?.includes('cypress-suite');
});
const testSuiteGroups = groupBy('name', testSuiteRuns);

const success = testSuiteRuns.every((job) => job.state === 'passed');
const testGroupResults = Object.entries(testSuiteGroups).map(([name, group]) => {
const passingTests = group.filter((job) => job.state === 'passed');
return {
name,
success: passingTests.length === group.length,
successCount: passingTests.length,
groupSize: group.length,
};
});

// Comment results on the PR
const prNumber = Number(extractPRNumberFromBranch(buildkiteBuild.branch));
if (isNaN(prNumber)) {
throw new Error(`Couldn't find PR number for build ${buildkiteBuild.web_url}.`);
}
const flakyRunHistoryLink = `https://buildkite.com/elastic/${
buildkiteBuild.pipeline.slug
}/builds?branch=${encodeURIComponent(buildkiteBuild.branch)}`;

const prComment = `
## Flaky Test Runner Stats
### ${success ? '🎉 All tests passed!' : '🟠 Some tests failed.'} - ${buildLink}
${testGroupResults.map(formatTestGroupResult).join('\n')}
[see run history](${flakyRunHistoryLink})
`;

const githubClient = getGithubClient();
const commentResult = await githubClient.issues.createComment({
owner: 'elastic',
repo: 'kibana',
body: prComment,
issue_number: prNumber,
});

console.log(`Comment added: ${commentResult.data.html_url}`);
}

function formatTestGroupResult(result: TestSuiteResult) {
const statusIcon = result.success ? '✅' : '❌';
const testName = result.name;
const successCount = result.successCount;
const groupSize = result.groupSize;

return `[${statusIcon}] ${testName}: ${successCount}/${groupSize} tests passed.`;
}

function groupBy<T>(field: keyof T, values: T[]): Record<string, T[]> {
return values.reduce((acc, value) => {
const key = value[field];
if (typeof key !== 'string') {
throw new Error('Cannot group by non-string value field');
}

if (!acc[key]) {
acc[key] = [];
}
acc[key].push(value);
return acc;
}, {} as Record<string, T[]>);
}

function extractPRNumberFromBranch(branch: string | undefined) {
if (!branch) {
return null;
} else {
return branch.match(/refs\/pull\/(\d+)\/head/)?.[1];
}
}

main()
.then(() => {
console.log('Flaky runner stats comment added to PR!');
})
.catch((e) => {
console.error(e);
process.exit(1);
});

0 comments on commit 93677b4

Please sign in to comment.