diff --git a/README.md b/README.md index e648898..1d25714 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ npm install -g k6-to-junit ``` -## Usage +## Usage as command the `k6-to-junit` command is created which reads input from the stdin and can output to either stdout or a file. @@ -25,3 +25,72 @@ this passes through the stdin to the stdout ```shell k6 run script.js | k6-to-junit junit.xml ``` + +## Usage as library + +### `K6Parser` + +the main way of using this library is to use the K6Parser class. This allows creating a stateful object that can internaly store multiple test results before outputing them to a single xml structure. + +```javascript +const K6Parser = require("k6-to-junit").K6Parser; +const parser = new K6Parser(); +``` + +```typescript +import K6Parser from "k6-to-junit"; +const parser = new K6Parser(); +``` + +#### `K6Parser.pipeFrom(input, options)` + +- `input` (Readable): a stream to read from. + Common examples would be reading from stdin or a file + +- `options` (optional): options to use when parsing. all of the following are optional. + + - `name` (string): the name to use for the generated TestSuite(s). will be read from input if omited. + - `startTime` (number): the start time to use for the test. will use `Date.now()` when stream starts if omited. + - `endTime` (number): the end time to use for the test. will use `Date.now()` when stream ends if omited. + - `output` (Writable): stream to forward input to. All data written to input stream will be mirrored here. + +- Returns a `Promise` that resolves when the input stream closes or rejects if stream is interupted. + +```javascript +k6Parser.pipeFrom(process.stdin, { output: process.stdout }).then(() => { + //do next stuff +}); +``` + +#### `K6Parser.allPassed()` + +- Returns false if any currently parsed tests have failed, else returns true. + +```javascript +process.exit(k6Parser.allPassed() ? 0 : 99); +``` + +#### `K6Parser.toXml(stream)` + +- `stream` (Writable): an optional writable stream to write to. otherwise returns the xml data as a string. +- Returns a string reprenstation of the junit xml data. + +```javascript +k6Parser.toXml(fs.createWriteStream("junit.xml")); +``` + +## Examples + +```javascript +const { spawn } = require("child_process"); +const { createWriteStream } = require("fs"); +const { K6Parser } = require("k6-to-junit"); +const parser = new K6Parser(); +parser.pipefrom(spawn("k6", ["run", "k6test.js"]).stdio).then(() => { + const writer = createWriteStream("junit.xml"); + parser.toXml(writer); + writer.once("finished", () => { + process.exit(k6Parser.allPassed() ? 0 : 99); + }); +}); +``` diff --git a/package.json b/package.json index a01362f..229aa43 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "k6-to-junit", - "version": "0.9.0", + "version": "1.0.0", "description": "tool to convert k6 output to junit xml", "main": "dist/index.js", "bin": "dist/command.js", @@ -11,7 +11,7 @@ "format": "./node_modules/.bin/prettier --write \"src/**\"", "lint": "./node_modules/.bin/eslint \"src/**\"", "build": "tsc", - "example": "cat test/out.txt test/out.txt | npm start --silent -- junit.xml" + "example": "cat test/out.txt test/out.txt | npm start --silent" }, "keywords": [ "k6", diff --git a/src/index.test.ts b/src/index.test.ts deleted file mode 100644 index a2dd45f..0000000 --- a/src/index.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { spawnSync } from "child_process"; -import { parse } from "./index"; - -const k6Command = [ - "run", - "-v", - "C:\\Users\\Matt\\Code\\git\\k6tojunit:/k6", - "--name", - "k6Test", - "--rm", - "loadimpact/k6", - "run", - //'--out', 'json=k6/test/exampleResults.json', - "/k6/test/example.k6.js" -]; - -test("parse sync", () => { - const k6Result = spawnSync("docker", k6Command); - const text = String(k6Result.stdout); - console.log(text); - const testSuites = parse(text); - expect(testSuites).toHaveLength(1); - expect(testSuites[0].thresholds).toHaveLength(2); - expect(testSuites[0].thresholds.filter(t => t.passed)).toHaveLength(1); -}); diff --git a/src/index.ts b/src/index.ts index e24ecae..1bc39bd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ import { create, streamWriter } from "xmlbuilder"; import { createInterface } from "readline"; import { EOL } from "os"; -import { Writable } from "stream"; +import { Writable, Readable } from "stream"; export interface TestSuite { name: string; @@ -19,10 +19,6 @@ export interface Threshold { const nameRegex = /script: (.*)$/m; -function isThreshold(threshold?: unknown): threshold is Threshold { - return Boolean(threshold && (threshold as Threshold).name); -} - export function parseLine(line: string): Threshold | null { const threshold = /([✓|✗]) (\w*?)\./g.exec(line); if (threshold && threshold.length > 2) { @@ -40,26 +36,6 @@ export function parseName(line: string): string | null { return (name && name[1]) || null; } -export function parse(input: string): TestSuite[] { - return input - .split(/vus_max.*$/gm) - .map(suite => { - const thresholds = suite - .split(/\r?\n/) - .map(parseLine) - .filter(isThreshold); - const name = parseName(suite); - if (name) { - return { - name, - thresholds, - stdout: suite - }; - } - return null; - }) - .filter(Boolean) as TestSuite[]; -} /** * https://llg.cubic.org/docs/junit/ * https://dzone.com/articles/viewing-junit-xml-files-locally @@ -106,18 +82,20 @@ export function toXml(testsuites: TestSuite[], stream?: Writable): string { return xmlObj.end((stream && streamWriter(stream, { pretty: true })) || { pretty: true }); } -export default class K6Parser { +export class K6Parser { private _testSuites: TestSuite[] = []; - public parse(input: string, options?: { name?: string; startTime?: number; endTime?: number }): void { - parse(input).forEach(testSuite => { - this._testSuites.push({ ...testSuite, endTime: Date.now(), ...options }); - }); + public parse(input: string, options?: { name?: string; startTime?: number; endTime?: number }): Promise { + const inputStream = new Readable(); + const promise = this.pipeFrom(inputStream, options); + inputStream.push(input); + inputStream.push(null); + return promise; } public pipeFrom( - input: NodeJS.ReadStream, - options?: { name?: string; startTime?: number; output?: NodeJS.WriteStream } + input: Readable, + options?: { name?: string; startTime?: number; endTime?: number; output?: Writable } ): Promise { let testSuite: Partial & { thresholds: Threshold[] }; const reset = (): void => { @@ -135,7 +113,7 @@ export default class K6Parser { name: testSuite.name, thresholds: testSuite.thresholds, startTime: testSuite.startTime, - endTime: Date.now(), + endTime: (options && options.endTime) || Date.now(), stdout: testSuite.stdout }; this._testSuites.push(result); @@ -186,3 +164,4 @@ export default class K6Parser { return toXml(this._testSuites, stream); } } +export default K6Parser;