Skip to content

Commit eb44641

Browse files
authored
Merge pull request #2 from grafana/remove-quite-flag
Remove `--quite` flag and extract cloud test run URL
2 parents cbcccb3 + e97c981 commit eb44641

File tree

4 files changed

+288
-14
lines changed

4 files changed

+288
-14
lines changed

dist/index.js

+136-5
Original file line numberDiff line numberDiff line change
@@ -29897,9 +29897,10 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
2989729897
exports.run = void 0;
2989829898
const core = __importStar(__nccwpck_require__(2186));
2989929899
const glob = __importStar(__nccwpck_require__(8090));
29900-
const fs = __importStar(__nccwpck_require__(5630));
2990129900
const child_process_1 = __nccwpck_require__(2081);
29902-
var TEST_PIDS = [];
29901+
const fs = __importStar(__nccwpck_require__(5630));
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, TEST_RESULT_URLS_MAP, TOTAL_TEST_RUNS));
3001330016
return child;
3001430017
}
3001530018
}
@@ -30056,6 +30059,134 @@ 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 {boolean} - Returns true if the script path and output URL were successfully extracted and added to the map. Otherwise, returns false.
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+
return true;
30098+
}
30099+
else {
30100+
return false;
30101+
}
30102+
}
30103+
function checkIfK6ASCIIArt(data) {
30104+
/**
30105+
* This function checks if the given data is the k6 ASCII art or not.
30106+
*
30107+
* @param {string} data - The data to check
30108+
*
30109+
* @returns {boolean} - Returns true if the data is the k6 ASCII art. Otherwise, returns false.
30110+
*
30111+
* The k6 ASCII art is as follows:
30112+
*
30113+
*
30114+
*
30115+
* /\ |‾‾| /‾‾/ /‾‾/
30116+
* /\ / \ | |/ / / /
30117+
* / \/ \ | ( / ‾‾\
30118+
* / \ | |\ \ | (‾) |
30119+
* / __________ \ |__| \__\ \_____/ .io
30120+
*
30121+
* To determine if the data is the k6 ASCII art, the function checks the following:
30122+
* 1. The function checks if the data contains only the following characters:
30123+
* |, ' ', '\n', '/', '‾', '(', ')', '_', '.', 'i', 'o'
30124+
*
30125+
* 2. The function also checks if the data contains ".io" at the end.
30126+
*
30127+
* */
30128+
if (!data.includes(".io")) {
30129+
return false;
30130+
}
30131+
let K6_ASCII_ART_CHARS = [
30132+
'|', ' ', '\n', '/',
30133+
'‾', '(', ')', '_',
30134+
'.', 'i', 'o', '\\'
30135+
], dataChars = new Set(data);
30136+
if (dataChars.size !== K6_ASCII_ART_CHARS.length) {
30137+
return false;
30138+
}
30139+
else {
30140+
for (let char of dataChars) {
30141+
if (!K6_ASCII_ART_CHARS.includes(char)) {
30142+
return false;
30143+
}
30144+
}
30145+
return true;
30146+
}
30147+
}
30148+
function parseK6Output(data, testResultUrlsMap, totalTestRuns) {
30149+
/*
30150+
* This function is responsible for parsing the output of the k6 command.
30151+
* It filters out the progress lines and logs the rest of the output.
30152+
* It also extracts the test run URLs from the output.
30153+
*
30154+
* @param {Buffer} data - The k6 command output data
30155+
* @param {TestResultUrlsMap | null} testResultUrlsMap - The map containing the script path and output URL. If null, the function will not extract test run URLs.
30156+
* @param {number} totalTestRuns - The total number of test runs. This is used to determine when all test run URLs have been extracted.
30157+
*
30158+
* @returns {void}
30159+
*/
30160+
const dataString = data.toString(), lines = dataString.split('\n');
30161+
// Extract test run URLs
30162+
if (testResultUrlsMap && Object.keys(testResultUrlsMap).length < totalTestRuns) {
30163+
if (extractTestRunUrl(dataString, testResultUrlsMap)) {
30164+
// Test URL was extracted successfully and added to the map.
30165+
// Ignore further output parsing for this data.
30166+
return;
30167+
}
30168+
if (checkIfK6ASCIIArt(dataString)) {
30169+
// Ignore the k6 ASCII art.
30170+
// Checking the k6 ASCII art here because it is printed at the start of execution,
30171+
// hence if all the test URLs are extracted, the ASCII art will not be printed.
30172+
return;
30173+
}
30174+
}
30175+
const filteredLines = lines.filter((line) => {
30176+
const isRegexMatch = REGEX_EXPRESSIONS.runningIteration.test(line) || REGEX_EXPRESSIONS.runProgress.test(line);
30177+
return !isRegexMatch;
30178+
});
30179+
if (filteredLines.length < lines.length) {
30180+
// ignore empty lines only when progress lines output was ignored.
30181+
if (filteredLines.join("") === "") {
30182+
return;
30183+
}
30184+
}
30185+
console.log(filteredLines.join('\n'));
30186+
}
30187+
exports.parseK6Output = parseK6Output;
30188+
30189+
3005930190
/***/ }),
3006030191

3006130192
/***/ 9491:

src/index.ts

+14-9
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
import * as core from '@actions/core'
2-
import * as glob from '@actions/glob'
3-
import * as fs from 'fs-extra'
4-
import { spawn } from 'child_process'
1+
import * as core from '@actions/core';
2+
import * as glob from '@actions/glob';
3+
import { spawn } from 'child_process';
4+
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, TEST_RESULT_URLS_MAP, TOTAL_TEST_RUNS));
127+
123128
return child;
124129
}
125130
} catch (error) {

src/k6OutputParser.ts

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
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): boolean {
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 {boolean} - Returns true if the script path and output URL were successfully extracted and added to the map. Otherwise, returns false.
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+
return true;
35+
} else {
36+
return false;
37+
}
38+
}
39+
40+
41+
function checkIfK6ASCIIArt(data: string): boolean {
42+
/**
43+
* This function checks if the given data is the k6 ASCII art or not.
44+
*
45+
* @param {string} data - The data to check
46+
*
47+
* @returns {boolean} - Returns true if the data is the k6 ASCII art. Otherwise, returns false.
48+
*
49+
* The k6 ASCII art is as follows:
50+
*
51+
*
52+
*
53+
* /\ |‾‾| /‾‾/ /‾‾/
54+
* /\ / \ | |/ / / /
55+
* / \/ \ | ( / ‾‾\
56+
* / \ | |\ \ | (‾) |
57+
* / __________ \ |__| \__\ \_____/ .io
58+
*
59+
* To determine if the data is the k6 ASCII art, the function checks the following:
60+
* 1. The function checks if the data contains only the following characters:
61+
* |, ' ', '\n', '/', '‾', '(', ')', '_', '.', 'i', 'o'
62+
*
63+
* 2. The function also checks if the data contains ".io" at the end.
64+
*
65+
* */
66+
67+
if (!data.includes(".io")) {
68+
return false;
69+
}
70+
71+
let K6_ASCII_ART_CHARS = [
72+
'|', ' ', '\n', '/',
73+
'‾', '(', ')', '_',
74+
'.', 'i', 'o', '\\'
75+
],
76+
dataChars = new Set(data);
77+
78+
79+
if (dataChars.size !== K6_ASCII_ART_CHARS.length) {
80+
return false;
81+
} else {
82+
for (let char of dataChars) {
83+
if (!K6_ASCII_ART_CHARS.includes(char)) {
84+
return false;
85+
}
86+
}
87+
return true;
88+
}
89+
}
90+
91+
export function parseK6Output(data: Buffer, testResultUrlsMap: TestResultUrlsMap | null, totalTestRuns: number): void {
92+
/*
93+
* This function is responsible for parsing the output of the k6 command.
94+
* It filters out the progress lines and logs the rest of the output.
95+
* It also extracts the test run URLs from the output.
96+
*
97+
* @param {Buffer} data - The k6 command output data
98+
* @param {TestResultUrlsMap | null} testResultUrlsMap - The map containing the script path and output URL. If null, the function will not extract test run URLs.
99+
* @param {number} totalTestRuns - The total number of test runs. This is used to determine when all test run URLs have been extracted.
100+
*
101+
* @returns {void}
102+
*/
103+
104+
const dataString = data.toString(),
105+
lines = dataString.split('\n');
106+
107+
// Extract test run URLs
108+
if (testResultUrlsMap && Object.keys(testResultUrlsMap).length < totalTestRuns) {
109+
if (extractTestRunUrl(dataString, testResultUrlsMap)) {
110+
// Test URL was extracted successfully and added to the map.
111+
// Ignore further output parsing for this data.
112+
return;
113+
}
114+
115+
if (checkIfK6ASCIIArt(dataString)) {
116+
// Ignore the k6 ASCII art.
117+
// Checking the k6 ASCII art here because it is printed at the start of execution,
118+
// hence if all the test URLs are extracted, the ASCII art will not be printed.
119+
return;
120+
}
121+
}
122+
123+
const filteredLines = lines.filter((line) => {
124+
const isRegexMatch = REGEX_EXPRESSIONS.runningIteration.test(line) || REGEX_EXPRESSIONS.runProgress.test(line);
125+
return !isRegexMatch;
126+
});
127+
128+
if (filteredLines.length < lines.length) {
129+
// ignore empty lines only when progress lines output was ignored.
130+
if (filteredLines.join("") === "") {
131+
return;
132+
}
133+
}
134+
console.log(filteredLines.join('\n'))
135+
}

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)