Skip to content

Commit e68f5b6

Browse files
committed
[feat] Parse K6 command output from stdout
- Ignore progress updates from stdout - Extract cloud test run urls from k6 command output
1 parent cd405e1 commit e68f5b6

File tree

4 files changed

+160
-9
lines changed

4 files changed

+160
-9
lines changed

dist/index.js

+76-4
Original file line numberDiff line numberDiff line change
@@ -29899,7 +29899,8 @@ const core = __importStar(__nccwpck_require__(2186));
2989929899
const glob = __importStar(__nccwpck_require__(8090));
2990029900
const child_process_1 = __nccwpck_require__(2081);
2990129901
const fs = __importStar(__nccwpck_require__(5630));
29902-
var TEST_PIDS = [];
29902+
const k6OutputParser_1 = __nccwpck_require__(5035);
29903+
const TEST_PIDS = [], TEST_RESULT_URLS_MAP = {};
2990329904
run();
2990429905
/**
2990529906
* The main function for the action.
@@ -29913,7 +29914,7 @@ async function run() {
2991329914
const flags = core.getInput('flags', { required: false });
2991429915
const cloudRunLocally = core.getInput('cloud-run-locally', { required: false }) === 'true';
2991529916
const isCloud = await isCloudIntegrationEnabled();
29916-
const commands = testPaths.map(testPath => generateCommand(testPath));
29917+
const commands = testPaths.map(testPath => generateCommand(testPath)), TOTAL_TEST_RUNS = commands.length;
2991729918
if (commands.length === 0) {
2991829919
throw new Error('No test files found');
2991929920
}
@@ -29990,7 +29991,6 @@ async function run() {
2999029991
function generateCommand(path) {
2999129992
const args = [
2999229993
// `--address=""`, // Disable the REST API. THIS DOESN'T WORK???? TODO: Investigate
29993-
// '--quiet',
2999429994
...(flags ? flags.split(' ') : []),
2999529995
];
2999629996
if (isCloud && cloudRunLocally) {
@@ -30006,10 +30006,13 @@ async function run() {
3000630006
const args = parts.slice(1);
3000730007
console.log(`🤖 Running test: ${cmd} ${args.join(' ')}`);
3000830008
const child = (0, child_process_1.spawn)(cmd, args, {
30009-
stdio: 'inherit', // piping all stdio to /dev/null
30009+
stdio: ['inherit'],
3001030010
detached: true,
3001130011
env: process.env,
3001230012
});
30013+
// Parse k6 command output and extract test run URLs if running in cloud mode.
30014+
// Also, print the output to the console, excluding the progress lines.
30015+
child.stdout?.on('data', (data) => (0, k6OutputParser_1.parseK6Output)(data, isCloud ? TEST_RESULT_URLS_MAP : null, TOTAL_TEST_RUNS));
3001330016
return child;
3001430017
}
3001530018
}
@@ -30056,6 +30059,75 @@ function isDirectory(filepath) {
3005630059
}
3005730060

3005830061

30062+
/***/ }),
30063+
30064+
/***/ 5035:
30065+
/***/ ((__unused_webpack_module, exports) => {
30066+
30067+
"use strict";
30068+
30069+
Object.defineProperty(exports, "__esModule", ({ value: true }));
30070+
exports.parseK6Output = void 0;
30071+
const REGEX_EXPRESSIONS = {
30072+
scriptPath: /^\s*script:\s*(.+)$/m,
30073+
output: /^\s*output:\s*(.+)$/m,
30074+
runningIteration: /running \(.*\), \d+\/\d+ VUs, \d+ complete and \d+ interrupted iterations/g,
30075+
runProgress: /\[ *(\d+)% *\] *\d+ VUs/g
30076+
};
30077+
function extractTestRunUrl(data, testResultUrlsMap) {
30078+
/**
30079+
* This function extracts the script path and output URL from the k6 output.
30080+
* It then adds the script path and output URL to the testResultUrlsMap which is a reference to
30081+
* an object passed from the main function to store test run urls mapped to corresponding test script.
30082+
*
30083+
* @param {string} data - The k6 command output data as string
30084+
* @param {TestResultUrlsMap} testResultUrlsMap - The map containing the script path and output URL
30085+
*
30086+
* @returns {void}
30087+
*
30088+
*/
30089+
// Extracting the script path
30090+
const scriptMatch = data.match(REGEX_EXPRESSIONS.scriptPath);
30091+
const scriptPath = scriptMatch ? scriptMatch[1] : null;
30092+
// Extracting the output URL
30093+
const outputMatch = data.match(REGEX_EXPRESSIONS.output);
30094+
const output = outputMatch ? outputMatch[1] : null;
30095+
if (scriptPath && output) {
30096+
testResultUrlsMap[scriptPath] = output;
30097+
}
30098+
}
30099+
function parseK6Output(data, testResultUrlsMap, totalTestRuns) {
30100+
/*
30101+
* This function is responsible for parsing the output of the k6 command.
30102+
* It filters out the progress lines and logs the rest of the output.
30103+
* It also extracts the test run URLs from the output.
30104+
*
30105+
* @param {Buffer} data - The k6 command output data
30106+
* @param {TestResultUrlsMap | null} testResultUrlsMap - The map containing the script path and output URL. If null, the function will not extract test run URLs.
30107+
* @param {number} totalTestRuns - The total number of test runs. This is used to determine when all test run URLs have been extracted.
30108+
*
30109+
* @returns {void}
30110+
*/
30111+
const dataString = data.toString(), lines = dataString.split('\n');
30112+
// Extract test run URLs
30113+
if (testResultUrlsMap && Object.keys(testResultUrlsMap).length < totalTestRuns) {
30114+
extractTestRunUrl(dataString, testResultUrlsMap);
30115+
}
30116+
const filteredLines = lines.filter((line) => {
30117+
const isRegexMatch = REGEX_EXPRESSIONS.runningIteration.test(line) || REGEX_EXPRESSIONS.runProgress.test(line);
30118+
return !isRegexMatch;
30119+
});
30120+
if (filteredLines.length < lines.length) {
30121+
// ignore empty lines only when progress lines output was ignored.
30122+
if (filteredLines.join("") === "") {
30123+
return;
30124+
}
30125+
}
30126+
console.log(filteredLines.join('\n'));
30127+
}
30128+
exports.parseK6Output = parseK6Output;
30129+
30130+
3005930131
/***/ }),
3006030132

3006130133
/***/ 9491:

src/index.ts

+10-5
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import * as core from '@actions/core';
22
import * as glob from '@actions/glob';
33
import { spawn } from 'child_process';
44
import * as fs from 'fs-extra';
5+
import { parseK6Output } from './k6OutputParser';
56

6-
var TEST_PIDS: number[] = [];
7+
const TEST_PIDS: number[] = [],
8+
TEST_RESULT_URLS_MAP: { [key: string]: string } = {};
79

810
run()
911

@@ -21,7 +23,8 @@ export async function run(): Promise<void> {
2123

2224
const isCloud = await isCloudIntegrationEnabled()
2325

24-
const commands = testPaths.map(testPath => generateCommand(testPath))
26+
const commands = testPaths.map(testPath => generateCommand(testPath)),
27+
TOTAL_TEST_RUNS = commands.length;
2528
if (commands.length === 0) {
2629
throw new Error('No test files found')
2730
}
@@ -98,7 +101,6 @@ export async function run(): Promise<void> {
98101
function generateCommand(path: string): string {
99102
const args = [
100103
// `--address=""`, // Disable the REST API. THIS DOESN'T WORK???? TODO: Investigate
101-
// '--quiet',
102104
...(flags ? flags.split(' ') : []),
103105
]
104106
if (isCloud && cloudRunLocally) {
@@ -115,11 +117,14 @@ export async function run(): Promise<void> {
115117

116118
console.log(`🤖 Running test: ${cmd} ${args.join(' ')}`);
117119
const child = spawn(cmd, args, {
118-
stdio: 'inherit', // piping all stdio to /dev/null
120+
stdio: ['inherit'],
119121
detached: true,
120122
env: process.env,
121123
});
122-
124+
// Parse k6 command output and extract test run URLs if running in cloud mode.
125+
// Also, print the output to the console, excluding the progress lines.
126+
child.stdout?.on('data', (data) => parseK6Output(data, isCloud ? TEST_RESULT_URLS_MAP : null, TOTAL_TEST_RUNS));
127+
123128
return child;
124129
}
125130
} catch (error) {

src/k6OutputParser.ts

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { TestResultUrlsMap } from './types';
2+
3+
const REGEX_EXPRESSIONS = {
4+
scriptPath: /^\s*script:\s*(.+)$/m,
5+
output: /^\s*output:\s*(.+)$/m,
6+
runningIteration: /running \(.*\), \d+\/\d+ VUs, \d+ complete and \d+ interrupted iterations/g,
7+
runProgress: /\[ *(\d+)% *\] *\d+ VUs/g
8+
};
9+
10+
11+
function extractTestRunUrl(data: string, testResultUrlsMap: TestResultUrlsMap): void {
12+
/**
13+
* This function extracts the script path and output URL from the k6 output.
14+
* It then adds the script path and output URL to the testResultUrlsMap which is a reference to
15+
* an object passed from the main function to store test run urls mapped to corresponding test script.
16+
*
17+
* @param {string} data - The k6 command output data as string
18+
* @param {TestResultUrlsMap} testResultUrlsMap - The map containing the script path and output URL
19+
*
20+
* @returns {void}
21+
*
22+
*/
23+
24+
// Extracting the script path
25+
const scriptMatch = data.match(REGEX_EXPRESSIONS.scriptPath);
26+
const scriptPath = scriptMatch ? scriptMatch[1] : null;
27+
28+
// Extracting the output URL
29+
const outputMatch = data.match(REGEX_EXPRESSIONS.output);
30+
const output = outputMatch ? outputMatch[1] : null;
31+
32+
if (scriptPath && output) {
33+
testResultUrlsMap[scriptPath] = output;
34+
}
35+
}
36+
37+
38+
export function parseK6Output(data: Buffer, testResultUrlsMap: TestResultUrlsMap | null, totalTestRuns: number): void {
39+
/*
40+
* This function is responsible for parsing the output of the k6 command.
41+
* It filters out the progress lines and logs the rest of the output.
42+
* It also extracts the test run URLs from the output.
43+
*
44+
* @param {Buffer} data - The k6 command output data
45+
* @param {TestResultUrlsMap | null} testResultUrlsMap - The map containing the script path and output URL. If null, the function will not extract test run URLs.
46+
* @param {number} totalTestRuns - The total number of test runs. This is used to determine when all test run URLs have been extracted.
47+
*
48+
* @returns {void}
49+
*/
50+
51+
const dataString = data.toString(),
52+
lines = dataString.split('\n');
53+
54+
// Extract test run URLs
55+
if (testResultUrlsMap && Object.keys(testResultUrlsMap).length < totalTestRuns) {
56+
extractTestRunUrl(dataString, testResultUrlsMap);
57+
}
58+
59+
const filteredLines = lines.filter((line) => {
60+
const isRegexMatch = REGEX_EXPRESSIONS.runningIteration.test(line) || REGEX_EXPRESSIONS.runProgress.test(line);
61+
return !isRegexMatch;
62+
});
63+
64+
if (filteredLines.length < lines.length) {
65+
// ignore empty lines only when progress lines output was ignored.
66+
if (filteredLines.join("") === "") {
67+
return;
68+
}
69+
}
70+
console.log(filteredLines.join('\n'))
71+
}

src/types.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export type TestResultUrlsMap = {
2+
[key: string]: string;
3+
};

0 commit comments

Comments
 (0)