From cb1fe35a1f3b400c1eb50e456e08a5bf43d8600c Mon Sep 17 00:00:00 2001 From: Jamie King Date: Mon, 18 Mar 2024 03:44:36 -0700 Subject: [PATCH] fix(printResult): tables included colors even when outputStream did not support it (#513) --- lib/printResult.js | 17 ++++-- package.json | 1 + test/printResult.test.js | 121 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 134 insertions(+), 5 deletions(-) diff --git a/lib/printResult.js b/lib/printResult.js index 5b3b0960..a2fed8ed 100644 --- a/lib/printResult.js +++ b/lib/printResult.js @@ -15,20 +15,27 @@ const defaults = { verbose: true } +class TableWithoutColor extends Table { + constructor (opts = {}) { + super({ ...opts, style: { head: [], border: [] } }) + } +} + const printResult = (result, opts) => { opts = Object.assign({}, defaults, opts) let strResult = '' if (opts.verbose) { const chalk = new Chalk.Instance(testColorSupport({ stream: opts.outputStream, alwaysReturn: true })) + const ColorSafeTable = chalk.level === 0 ? TableWithoutColor : Table - const shortLatency = new Table({ + const shortLatency = new ColorSafeTable({ head: asColor(chalk.cyan, ['Stat', '2.5%', '50%', '97.5%', '99%', 'Avg', 'Stdev', 'Max']) }) shortLatency.push(asLowRow(chalk.bold('Latency'), asMs(result.latency))) logToLocalStr('\n' + shortLatency.toString()) - const requests = new Table({ + const requests = new ColorSafeTable({ head: asColor(chalk.cyan, ['Stat', '1%', '2.5%', '50%', '97.5%', 'Avg', 'Stdev', 'Min']) }) @@ -37,7 +44,7 @@ const printResult = (result, opts) => { logToLocalStr(requests.toString()) if (opts.renderStatusCodes === true) { - const statusCodeStats = new Table({ + const statusCodeStats = new ColorSafeTable({ head: asColor(chalk.cyan, ['Code', 'Count']) }) Object.keys(result.statusCodeStats).forEach(statusCode => { @@ -57,7 +64,7 @@ const printResult = (result, opts) => { logToLocalStr('') if (opts.renderLatencyTable) { - const latencies = new Table({ + const latencies = new ColorSafeTable({ head: asColor(chalk.cyan, ['Percentile', 'Latency (ms)']) }) percentiles.map((perc) => { @@ -85,7 +92,7 @@ const printResult = (result, opts) => { logToLocalStr(`${format(result.mismatches)} requests with mismatched body`) } if (result.resets) { - logToLocalStr(`request pipeline was resetted ${format(result.resets)} ${result.resets === 1 ? 'time' : 'times'}`) + logToLocalStr(`request pipeline was reset ${format(result.resets)} ${result.resets === 1 ? 'time' : 'times'}`) } function logToLocalStr (msg) { diff --git a/package.json b/package.json index db5ef250..ff558a17 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ }, "homepage": "https://github.com/mcollina/autocannon#readme", "devDependencies": { + "ansi-regex": "^5.0.1", "bl": "^6.0.0", "busboy": "^0.3.1", "pre-commit": "^1.1.2", diff --git a/test/printResult.test.js b/test/printResult.test.js index b2c30078..b7a47a18 100644 --- a/test/printResult.test.js +++ b/test/printResult.test.js @@ -4,6 +4,8 @@ const test = require('tap').test const split = require('split2') const path = require('path') const childProcess = require('child_process') +const { Writable } = require('stream') +const ansiRegex = require('ansi-regex') const printResult = require('../lib/printResult') test('should stdout (print) the result', (t) => { @@ -139,3 +141,122 @@ test('should not print when verbose(V=0) is false', (t) => { const output = printResult(result, { verbose: false }) t.ok(output.split('\n').length === 2) }) + +test('should print with color when color is supported', (t) => { + t.plan(1) + + const connections = 10 + const pipelining = 2 + const result = { + connections, + pipelining, + latency: {}, + requests: { + sent: connections * pipelining + }, + throughput: { + average: 3319, + mean: 3319, + stddev: 0, + min: 3318, + max: 3318, + total: 3318, + p0_001: 3319, + p0_01: 3319, + p0_1: 3319, + p1: 3319, + p2_5: 3319, + p10: 3319, + p25: 3319, + p50: 3319, + p75: 3319, + p90: 3319, + p97_5: 3319, + p99: 3319, + p99_9: 3319, + p99_99: 3319, + p99_999: 3319 + } + } + + const { FORCE_COLOR, NO_COLOR, COLOR, CI, COLORTERM } = process.env + delete process.env.FORCE_COLOR + delete process.env.NO_COLOR + delete process.env.COLOR + delete process.env.CI + process.env.COLORTERM = 'truecolor' + const outputStream = new Writable({ + write () {} + }) + outputStream.isTTY = true + + // act + const output = printResult(result, { outputStream }) + t.ok(ansiRegex().test(output)) + + // cleanup + process.env.FORCE_COLOR = FORCE_COLOR + process.env.NO_COLOR = NO_COLOR + process.env.COLOR = COLOR + process.env.CI = CI + process.env.COLORTERM = COLORTERM +}) + +test('should not print with any color when color is not supported', (t) => { + t.plan(1) + + const connections = 10 + const pipelining = 2 + const result = { + connections, + pipelining, + latency: {}, + requests: { + sent: connections * pipelining + }, + throughput: { + average: 3319, + mean: 3319, + stddev: 0, + min: 3318, + max: 3318, + total: 3318, + p0_001: 3319, + p0_01: 3319, + p0_1: 3319, + p1: 3319, + p2_5: 3319, + p10: 3319, + p25: 3319, + p50: 3319, + p75: 3319, + p90: 3319, + p97_5: 3319, + p99: 3319, + p99_9: 3319, + p99_99: 3319, + p99_999: 3319 + } + } + + const { FORCE_COLOR, NO_COLOR, COLOR, CI, COLORTERM } = process.env + delete process.env.FORCE_COLOR + delete process.env.NO_COLOR + delete process.env.COLOR + delete process.env.CI + process.env.COLORTERM = 'truecolor' + const outputStream = new Writable({ + write () {} + }) + + // act + const output = printResult(result, { outputStream }) + t.ok(!ansiRegex().test(output)) + + // cleanup + process.env.FORCE_COLOR = FORCE_COLOR + process.env.NO_COLOR = NO_COLOR + process.env.COLOR = COLOR + process.env.CI = CI + process.env.COLORTERM = COLORTERM +})