Skip to content

Commit 26d78cc

Browse files
authored
Merge pull request #5 from grafana/validate-tests
[feat] Validate test paths before executing test run
2 parents ac75a01 + 4d919af commit 26d78cc

File tree

8 files changed

+181
-8
lines changed

8 files changed

+181
-8
lines changed

.github/workflows/test.yaml

+19-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
steps:
1212
- uses: actions/checkout@v4
1313
- name: Setup Grafana k6
14-
uses: grafana/setup-k6-action@v0.0.1
14+
uses: grafana/setup-k6-action@main
1515
with:
1616
k6-version: '0.49.0'
1717
- uses: ./
@@ -21,4 +21,21 @@ jobs:
2121
./dev/protocol*.js
2222
flags: --vus 10 --duration 30s
2323
parallel: true
24-
cloud-run-locally: false
24+
cloud-run-locally: false
25+
verify-scripts:
26+
runs-on: ubuntu-latest
27+
env:
28+
K6_CLOUD_TOKEN: ${{ secrets.K6_CLOUD_TOKEN }}
29+
K6_CLOUD_PROJECT_ID: ${{ secrets.K6_CLOUD_PROJECT_ID }}
30+
steps:
31+
- uses: actions/checkout@v4
32+
- name: Setup Grafana k6
33+
uses: grafana/setup-k6-action@main
34+
with:
35+
k6-version: '0.49.0'
36+
- uses: ./
37+
continue-on-error: true
38+
with:
39+
path: |
40+
./dev/verify-script-tests/**.js
41+
only-verify-scripts: true

action.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ inputs:
2929
description: 'If true, comment the cloud test run URL on the pull request'
3030
default: "true"
3131
required: false
32+
only-verify-scripts:
33+
description: 'If true, only check if the test scripts are valid and skip test execution'
34+
default: "false"
35+
required: false
3236

3337
runs:
3438
using: node20
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { sleep } from "k6";
2+
import http from "k6/http";
3+
4+
export const options = {
5+
scenarios: {
6+
createBrowser: {
7+
executor: "constant-arrival-rate",
8+
},
9+
},
10+
};
11+
12+
export default function () {
13+
http.get("http://test.k6.io");
14+
sleep(1);
15+
}

dev/verify-script-tests/no-test.js

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// Sample JS file with no tests
2+
console.log("No tests in this file");

dev/verify-script-tests/valid-test.js

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import http from 'k6/http';
2+
import { sleep } from 'k6';
3+
4+
export default function () {
5+
http.get('http://test.k6.io');
6+
sleep(1);
7+
}

dist/index.js

+60-3
Original file line numberDiff line numberDiff line change
@@ -34346,6 +34346,7 @@ const child_process_1 = __nccwpck_require__(2081);
3434634346
const fs = __importStar(__nccwpck_require__(5630));
3434734347
const githubHelper_1 = __nccwpck_require__(2179);
3434834348
const k6OutputParser_1 = __nccwpck_require__(5035);
34349+
const k6helper_1 = __nccwpck_require__(1034);
3434934350
const TEST_PIDS = [];
3435034351
run();
3435134352
/**
@@ -34359,8 +34360,28 @@ async function run() {
3435934360
const failFast = core.getInput('fail-fast', { required: false }) === 'true';
3436034361
const flags = core.getInput('flags', { required: false });
3436134362
const cloudRunLocally = core.getInput('cloud-run-locally', { required: false }) === 'true';
34363+
const onlyVerifyScripts = core.getInput('only-verify-scripts', { required: false }) === 'true';
3436234364
const shouldCommentCloudTestRunUrlOnPR = core.getInput('cloud-comment-on-pr', { required: false }) === 'true';
3436334365
const allPromises = [];
34366+
core.debug(`🔍 Found following ${testPaths.length} test run files:`);
34367+
testPaths.forEach((testPath, index) => {
34368+
core.debug(`${index + 1}. ${testPath}`);
34369+
});
34370+
if (testPaths.length === 0) {
34371+
throw new Error('No test files found');
34372+
}
34373+
const verifiedTestPaths = await (0, k6helper_1.validateTestPaths)(testPaths);
34374+
if (verifiedTestPaths.length === 0) {
34375+
throw new Error('No valid test files found');
34376+
}
34377+
console.log(`🧪 Found ${verifiedTestPaths.length} valid K6 tests out of total ${testPaths.length} test files.`);
34378+
verifiedTestPaths.forEach((testPath, index) => {
34379+
console.log(` ${index + 1}. ${testPath}`);
34380+
});
34381+
if (onlyVerifyScripts) {
34382+
console.log('🔍 Only verifying scripts. Skipping test execution');
34383+
return;
34384+
}
3436434385
const isCloud = await isCloudIntegrationEnabled();
3436534386
const commands = testPaths.map(testPath => generateCommand(testPath)), TOTAL_TEST_RUNS = commands.length, TEST_RESULT_URLS_MAP = new Proxy({}, {
3436634387
set: (target, key, value) => {
@@ -34375,9 +34396,6 @@ async function run() {
3437534396
}
3437634397
});
3437734398
;
34378-
if (commands.length === 0) {
34379-
throw new Error('No test files found');
34380-
}
3438134399
let allTestsPassed = true;
3438234400
if (parallel) {
3438334401
const childProcesses = [];
@@ -34665,6 +34683,45 @@ function parseK6Output(data, testRunUrlsMap, totalTestRuns) {
3466534683
exports.parseK6Output = parseK6Output;
3466634684

3466734685

34686+
/***/ }),
34687+
34688+
/***/ 1034:
34689+
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
34690+
34691+
"use strict";
34692+
34693+
Object.defineProperty(exports, "__esModule", ({ value: true }));
34694+
exports.validateTestPaths = void 0;
34695+
// Common helper functions used in the action
34696+
const child_process_1 = __nccwpck_require__(2081);
34697+
async function validateTestPaths(testPaths) {
34698+
if (testPaths.length === 0) {
34699+
throw new Error('No test files found');
34700+
}
34701+
console.log(`🔍 Validating test run files.`);
34702+
const validK6TestPaths = [], command = "k6", defaultArgs = ["inspect", "--execution-requirements"];
34703+
const allPromises = [];
34704+
testPaths.forEach(async (testPath) => {
34705+
const args = [...defaultArgs, testPath];
34706+
const child = (0, child_process_1.spawn)(command, args, {
34707+
stdio: ['inherit', 'ignore', 'inherit'], // 'ignore' is for stdout
34708+
detached: false,
34709+
});
34710+
allPromises.push(new Promise(resolve => {
34711+
child.on('exit', (code, signal) => {
34712+
if (code === 0) {
34713+
validK6TestPaths.push(testPath);
34714+
}
34715+
resolve();
34716+
});
34717+
}));
34718+
});
34719+
await Promise.all(allPromises);
34720+
return validK6TestPaths;
34721+
}
34722+
exports.validateTestPaths = validateTestPaths;
34723+
34724+
3466834725
/***/ }),
3466934726

3467034727
/***/ 9491:

src/index.ts

+27-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { spawn } from 'child_process';
44
import * as fs from 'fs-extra';
55
import { generatePRComment } from './githubHelper';
66
import { parseK6Output } from './k6OutputParser';
7+
import { validateTestPaths } from './k6helper';
78
import { TestRunUrlsMap } from './types';
89

910
const TEST_PIDS: number[] = [];
@@ -21,9 +22,35 @@ export async function run(): Promise<void> {
2122
const failFast = core.getInput('fail-fast', { required: false }) === 'true'
2223
const flags = core.getInput('flags', { required: false })
2324
const cloudRunLocally = core.getInput('cloud-run-locally', { required: false }) === 'true'
25+
const onlyVerifyScripts = core.getInput('only-verify-scripts', { required: false }) === 'true'
2426
const shouldCommentCloudTestRunUrlOnPR = core.getInput('cloud-comment-on-pr', { required: false }) === 'true'
2527
const allPromises: Promise<void>[] = [];
2628

29+
core.debug(`🔍 Found following ${testPaths.length} test run files:`);
30+
testPaths.forEach((testPath, index) => {
31+
core.debug(`${index + 1}. ${testPath}`);
32+
});
33+
34+
if (testPaths.length === 0) {
35+
throw new Error('No test files found')
36+
}
37+
38+
const verifiedTestPaths = await validateTestPaths(testPaths);
39+
40+
if (verifiedTestPaths.length === 0) {
41+
throw new Error('No valid test files found')
42+
}
43+
44+
console.log(`🧪 Found ${verifiedTestPaths.length} valid K6 tests out of total ${testPaths.length} test files.`);
45+
verifiedTestPaths.forEach((testPath, index) => {
46+
console.log(` ${index + 1}. ${testPath}`);
47+
});
48+
49+
if (onlyVerifyScripts) {
50+
console.log('🔍 Only verifying scripts. Skipping test execution');
51+
return;
52+
}
53+
2754
const isCloud = await isCloudIntegrationEnabled()
2855

2956
const commands = testPaths.map(testPath => generateCommand(testPath)),
@@ -41,9 +68,6 @@ export async function run(): Promise<void> {
4168
}
4269
});
4370
;
44-
if (commands.length === 0) {
45-
throw new Error('No test files found')
46-
}
4771

4872
let allTestsPassed = true;
4973

src/k6helper.ts

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Common helper functions used in the action
2+
import { spawn } from 'child_process';
3+
4+
export async function validateTestPaths(testPaths: string[]): Promise<string[]> {
5+
/**
6+
* Validates the test paths by running `k6 inspect --execution-requirements` on each test file.
7+
* A test path is considered valid if the command returns an exit code of 0.
8+
*
9+
* @export
10+
* @param {string[]} testPaths - List of test paths to validate
11+
* @return {Promise<string[]>} - List of valid test paths
12+
*/
13+
14+
if (testPaths.length === 0) {
15+
throw new Error('No test files found')
16+
}
17+
18+
console.log(`🔍 Validating test run files.`);
19+
20+
const validK6TestPaths: string[] = [],
21+
command = "k6",
22+
defaultArgs = ["inspect", "--execution-requirements"];
23+
24+
const allPromises = [] as any[];
25+
26+
testPaths.forEach(async testPath => {
27+
const args = [...defaultArgs, testPath];
28+
29+
const child = spawn(command, args, {
30+
stdio: ['inherit', 'ignore', 'inherit'], // 'ignore' is for stdout
31+
detached: false,
32+
});
33+
34+
allPromises.push(new Promise<void>(resolve => {
35+
child.on('exit', (code: number, signal: string) => {
36+
if (code === 0) {
37+
validK6TestPaths.push(testPath);
38+
}
39+
resolve();
40+
});
41+
}));
42+
});
43+
44+
await Promise.all(allPromises);
45+
46+
return validK6TestPaths;
47+
}

0 commit comments

Comments
 (0)