mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
Add script to check test file code ownership (#173411)
## Summary This PR adds a script that determines GitHub code ownership for functional test files in the Kibana repository. ### Why do we need this? We want to be able to determine test ownership to allow teams to get a better overview of their tests (number of tests, number of skipped tests, number of failures in the last x days, etc). ### What does this PR bring? This PR is a first step on closing the test ownership gaps. It adds functionality to determine the GitHub code owner for a given file (in the `@kbn/code-owners` package) and adds a script that makes use of this to check if all functional test files have a code owner, reporting the gaps. ### Future plans The idea is to include the test ownership information in our ingested test results, such that we can create dashboards, reports, etc based on it. At some point (once all ownership gaps are closed), we might consider running this check on CI to prevent new test files without owners. ### How to run? ``` node scripts/check_ftr_code_owners.js ``` The script lists the functional test files that are not covered by code owners and also gives a summary like this: ``` ERROR Found 2592 test files without code owner (checked 7550 test files in 12.73 s) ```
This commit is contained in:
parent
164427463e
commit
6272d5af6f
15 changed files with 214 additions and 4 deletions
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
@ -87,6 +87,7 @@ x-pack/plugins/cloud_integrations/cloud_links @elastic/kibana-core
|
|||
x-pack/plugins/cloud @elastic/kibana-core
|
||||
x-pack/plugins/cloud_security_posture @elastic/kibana-cloud-security-posture
|
||||
packages/shared-ux/code_editor @elastic/appex-sharedux
|
||||
packages/kbn-code-owners @elastic/appex-qa
|
||||
packages/kbn-coloring @elastic/kibana-visualizations
|
||||
packages/kbn-config @elastic/kibana-core
|
||||
packages/kbn-config-mocks @elastic/kibana-core
|
||||
|
|
|
@ -1176,6 +1176,7 @@
|
|||
"@kbn/ci-stats-reporter": "link:packages/kbn-ci-stats-reporter",
|
||||
"@kbn/ci-stats-shipper-cli": "link:packages/kbn-ci-stats-shipper-cli",
|
||||
"@kbn/cli-dev-mode": "link:packages/kbn-cli-dev-mode",
|
||||
"@kbn/code-owners": "link:packages/kbn-code-owners",
|
||||
"@kbn/core-analytics-browser-mocks": "link:packages/core/analytics/core-analytics-browser-mocks",
|
||||
"@kbn/core-analytics-server-mocks": "link:packages/core/analytics/core-analytics-server-mocks",
|
||||
"@kbn/core-application-browser-mocks": "link:packages/core/application/core-application-browser-mocks",
|
||||
|
@ -1558,6 +1559,7 @@
|
|||
"html": "1.0.0",
|
||||
"html-loader": "^1.3.2",
|
||||
"http-proxy": "^1.18.1",
|
||||
"ignore": "^5.3.0",
|
||||
"is-path-inside": "^3.0.2",
|
||||
"jest": "^29.6.1",
|
||||
"jest-axe": "^5.0.0",
|
||||
|
|
3
packages/kbn-code-owners/README.md
Normal file
3
packages/kbn-code-owners/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# @kbn/code-owners
|
||||
|
||||
This package contains utility methods to determine GitHub code ownership for files in the repository.
|
10
packages/kbn-code-owners/index.ts
Normal file
10
packages/kbn-code-owners/index.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export type { PathWithOwners } from './src/file_code_owner';
|
||||
export { getPathsWithOwnersReversed, getCodeOwnersForFile } from './src/file_code_owner';
|
13
packages/kbn-code-owners/jest.config.js
Normal file
13
packages/kbn-code-owners/jest.config.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test/jest_node',
|
||||
rootDir: '../..',
|
||||
roots: ['<rootDir>/packages/kbn-code-owners'],
|
||||
};
|
6
packages/kbn-code-owners/kibana.jsonc
Normal file
6
packages/kbn-code-owners/kibana.jsonc
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/code-owners",
|
||||
"owner": "@elastic/appex-qa",
|
||||
"devOnly": true
|
||||
}
|
6
packages/kbn-code-owners/package.json
Normal file
6
packages/kbn-code-owners/package.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "@kbn/code-owners",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0"
|
||||
}
|
67
packages/kbn-code-owners/src/file_code_owner.ts
Normal file
67
packages/kbn-code-owners/src/file_code_owner.ts
Normal file
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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 { REPO_ROOT } from '@kbn/repo-info';
|
||||
import { createFailError } from '@kbn/dev-cli-errors';
|
||||
import { join as joinPath } from 'path';
|
||||
import { existsSync, readFileSync } from 'fs';
|
||||
|
||||
import type { Ignore } from 'ignore';
|
||||
import ignore from 'ignore';
|
||||
|
||||
export interface PathWithOwners {
|
||||
path: string;
|
||||
teams: string;
|
||||
ignorePattern: Ignore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the .github/CODEOWNERS entries, prepared for path matching.
|
||||
* The last matching CODEOWNERS entry has highest precedence:
|
||||
* https://help.github.com/articles/about-codeowners/
|
||||
* so entries are returned in reversed order to later search for the first match.
|
||||
*/
|
||||
export function getPathsWithOwnersReversed(): PathWithOwners[] {
|
||||
const codeownersPath = joinPath(REPO_ROOT, '.github', 'CODEOWNERS');
|
||||
if (existsSync(codeownersPath) === false) {
|
||||
throw createFailError(`Unable to determine code owners: file ${codeownersPath} not found`);
|
||||
}
|
||||
const codeownersContent = readFileSync(codeownersPath, { encoding: 'utf8', flag: 'r' });
|
||||
const codeownersLines = codeownersContent.split(/\r?\n/);
|
||||
const codeowners = codeownersLines
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line && line[0] !== '#');
|
||||
|
||||
const pathsWithOwners: PathWithOwners[] = codeowners.map((c) => {
|
||||
const [path, ...ghTeams] = c.split(/\s+/);
|
||||
return {
|
||||
path,
|
||||
teams: ghTeams.map((t) => t.replace('@', '')).join(),
|
||||
// register CODEOWNERS entries with the `ignores` lib for later path matching
|
||||
ignorePattern: ignore().add([path]),
|
||||
};
|
||||
});
|
||||
|
||||
return pathsWithOwners.reverse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the GitHub CODEOWNERS for a file in the repository
|
||||
* @param filePath the file to get code owners for
|
||||
* @param reversedCodeowners a cached reversed code owners list, use to speed up multiple requests
|
||||
*/
|
||||
export function getCodeOwnersForFile(
|
||||
filePath: string,
|
||||
reversedCodeowners?: PathWithOwners[]
|
||||
): string | undefined {
|
||||
const pathsWithOwners = reversedCodeowners ?? getPathsWithOwnersReversed();
|
||||
|
||||
const match = pathsWithOwners.find((p) => p.ignorePattern.test(filePath).ignored);
|
||||
|
||||
return match?.teams;
|
||||
}
|
20
packages/kbn-code-owners/tsconfig.json
Normal file
20
packages/kbn-code-owners/tsconfig.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": [
|
||||
"jest",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*"
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/repo-info",
|
||||
"@kbn/dev-cli-errors"
|
||||
]
|
||||
}
|
|
@ -52,6 +52,8 @@ export { getUrl } from './src/jest/get_url';
|
|||
|
||||
export { runCheckJestConfigsCli } from './src/jest/run_check_jest_configs_cli';
|
||||
|
||||
export { runCheckFtrCodeOwnersCli } from './src/functional_test_runner/run_check_ftr_code_owners';
|
||||
|
||||
export { runJest } from './src/jest/run';
|
||||
|
||||
export * from './src/kbn_archiver_cli';
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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 { run } from '@kbn/dev-cli-runner';
|
||||
import { createFailError } from '@kbn/dev-cli-errors';
|
||||
import { getRepoFiles } from '@kbn/get-repo-files';
|
||||
import { getCodeOwnersForFile, getPathsWithOwnersReversed } from '@kbn/code-owners';
|
||||
|
||||
const TEST_DIRECTORIES = ['test', 'x-pack/test', 'x-pack/test_serverless'];
|
||||
|
||||
const fmtMs = (ms: number) => {
|
||||
if (ms < 1000) {
|
||||
return `${Math.round(ms)} ms`;
|
||||
}
|
||||
|
||||
return `${(Math.round(ms) / 1000).toFixed(2)} s`;
|
||||
};
|
||||
|
||||
const fmtList = (list: Iterable<string>) => [...list].map((i) => ` - ${i}`).join('\n');
|
||||
|
||||
export async function runCheckFtrCodeOwnersCli() {
|
||||
run(
|
||||
async ({ log }) => {
|
||||
const start = performance.now();
|
||||
|
||||
const missingOwners = new Set<string>();
|
||||
|
||||
// cache codeowners for quicker lookup
|
||||
const reversedCodeowners = getPathsWithOwnersReversed();
|
||||
|
||||
const testFiles = await getRepoFiles(TEST_DIRECTORIES);
|
||||
for (const { repoRel } of testFiles) {
|
||||
const owners = getCodeOwnersForFile(repoRel, reversedCodeowners);
|
||||
if (owners === undefined) {
|
||||
missingOwners.add(repoRel);
|
||||
}
|
||||
}
|
||||
|
||||
const timeSpent = fmtMs(performance.now() - start);
|
||||
|
||||
if (missingOwners.size) {
|
||||
log.error(
|
||||
`The following test files do not have a GitHub code owner:\n${fmtList(missingOwners)}`
|
||||
);
|
||||
throw createFailError(
|
||||
`Found ${missingOwners.size} test files without code owner (checked ${testFiles.length} test files in ${timeSpent})`
|
||||
);
|
||||
}
|
||||
|
||||
log.success(
|
||||
`All test files have a code owner (checked ${testFiles.length} test files in ${timeSpent})`
|
||||
);
|
||||
},
|
||||
{
|
||||
description: 'Check that all test files are covered by GitHub CODEOWNERS',
|
||||
}
|
||||
);
|
||||
}
|
|
@ -33,5 +33,6 @@
|
|||
"@kbn/repo-packages",
|
||||
"@kbn/core-saved-objects-api-server",
|
||||
"@kbn/mock-idp-plugin",
|
||||
"@kbn/code-owners",
|
||||
]
|
||||
}
|
||||
|
|
10
scripts/check_ftr_code_owners.js
Normal file
10
scripts/check_ftr_code_owners.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
require('../src/setup_node_env');
|
||||
require('@kbn/test').runCheckFtrCodeOwnersCli();
|
|
@ -168,6 +168,8 @@
|
|||
"@kbn/cloud-security-posture-plugin/*": ["x-pack/plugins/cloud_security_posture/*"],
|
||||
"@kbn/code-editor": ["packages/shared-ux/code_editor"],
|
||||
"@kbn/code-editor/*": ["packages/shared-ux/code_editor/*"],
|
||||
"@kbn/code-owners": ["packages/kbn-code-owners"],
|
||||
"@kbn/code-owners/*": ["packages/kbn-code-owners/*"],
|
||||
"@kbn/coloring": ["packages/kbn-coloring"],
|
||||
"@kbn/coloring/*": ["packages/kbn-coloring/*"],
|
||||
"@kbn/config": ["packages/kbn-config"],
|
||||
|
|
12
yarn.lock
12
yarn.lock
|
@ -3271,6 +3271,10 @@
|
|||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/code-owners@link:packages/kbn-code-owners":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/coloring@link:packages/kbn-coloring":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
@ -18734,10 +18738,10 @@ ignore@^4.0.3:
|
|||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
|
||||
integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
|
||||
|
||||
ignore@^5.0.5, ignore@^5.1.1, ignore@^5.1.4, ignore@^5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a"
|
||||
integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==
|
||||
ignore@^5.0.5, ignore@^5.1.1, ignore@^5.1.4, ignore@^5.2.0, ignore@^5.3.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.0.tgz#67418ae40d34d6999c95ff56016759c718c82f78"
|
||||
integrity sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==
|
||||
|
||||
immediate@~3.0.5:
|
||||
version "3.0.6"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue