From e65d2965d89f4dc43ae9be4dcef252c0d976f2f8 Mon Sep 17 00:00:00 2001 From: RafaelGSS Date: Mon, 23 Dec 2024 01:08:11 -0300 Subject: [PATCH] feat: add JSONReporter --- examples/json-report/node.js | 21 +++++++++++ lib/index.js | 8 +++- lib/report.js | 2 + lib/reporter/json.js | 28 ++++++++++++++ test/reporter.js | 72 +++++++++++++++++++++++++++++++++++- 5 files changed, 129 insertions(+), 2 deletions(-) create mode 100644 examples/json-report/node.js create mode 100644 lib/reporter/json.js diff --git a/examples/json-report/node.js b/examples/json-report/node.js new file mode 100644 index 0000000..cac20cf --- /dev/null +++ b/examples/json-report/node.js @@ -0,0 +1,21 @@ +const { Suite, jsonReport } = require('../../lib'); +const assert = require('node:assert'); + +const suite = new Suite({ + reporter: jsonReport, +}); + +suite + .add('single with matcher', function () { + const pattern = /[123]/g + const replacements = { 1: 'a', 2: 'b', 3: 'c' } + const subject = '123123123123123123123123123123123123123123123123' + const r = subject.replace(pattern, m => replacements[m]) + assert.ok(r); + }) + .add('Multiple replaces', function () { + const subject = '123123123123123123123123123123123123123123123123' + const r = subject.replace(/1/g, 'a').replace(/2/g, 'b').replace(/3/g, 'c') + assert.ok(r); + }) + .run(); diff --git a/lib/index.js b/lib/index.js index 0ccb269..4ae5843 100644 --- a/lib/index.js +++ b/lib/index.js @@ -2,7 +2,12 @@ const { Worker } = require('node:worker_threads'); const { types } = require('node:util'); const path = require('node:path'); -const { textReport, chartReport, htmlReport } = require('./report'); +const { + textReport, + chartReport, + htmlReport, + jsonReport, +} = require('./report'); const { getInitialIterations, runBenchmark, runWarmup } = require('./lifecycle'); const { debugBench, timer, createFnString } = require('./clock'); const { @@ -203,4 +208,5 @@ module.exports = { chartReport, textReport, htmlReport, + jsonReport, }; diff --git a/lib/report.js b/lib/report.js index 5b6d526..d53953b 100644 --- a/lib/report.js +++ b/lib/report.js @@ -1,9 +1,11 @@ const { textReport } = require('./reporter/text'); const { chartReport } = require('./reporter/chart'); const { htmlReport } = require('./reporter/html'); +const { jsonReport } = require('./reporter/json'); module.exports = { chartReport, textReport, htmlReport, + jsonReport, }; diff --git a/lib/reporter/json.js b/lib/reporter/json.js new file mode 100644 index 0000000..6acba85 --- /dev/null +++ b/lib/reporter/json.js @@ -0,0 +1,28 @@ +'use strict' + +const { timer } = require('../clock') + +function jsonReport (results) { + const output = results.map((result) => { + const opsSecReported = + result.opsSec < 100 + ? result.opsSec.toFixed(2) + : result.opsSec.toFixed(0) + + return { + name: result.name, + opsSec: Number(opsSecReported), + runsSampled: result.histogram.samples.length, + min: timer.format(result.histogram.min), + max: timer.format(result.histogram.max), + // Report anything the plugins returned + plugins: result.plugins.map((p) => p.report).filter(Boolean) + } + }) + + console.log(JSON.stringify(output, null, 2)) +} + +module.exports = { + jsonReport +} diff --git a/test/reporter.js b/test/reporter.js index 5555646..3636e9e 100644 --- a/test/reporter.js +++ b/test/reporter.js @@ -2,7 +2,12 @@ const { describe, it, before } = require('node:test'); const assert = require('node:assert'); const fs = require('node:fs'); -const { Suite, chartReport, htmlReport } = require('../lib'); +const { + Suite, + chartReport, + htmlReport, + jsonReport, +} = require('../lib'); describe('chartReport outputs benchmark results as a bar chart', async (t) => { let output = ''; @@ -107,3 +112,68 @@ describe('htmlReport should create a file', async (t) => { assert.ok(htmlContent.includes('}}') === false); }); }); + +describe('jsonReport should produce valid JSON output', async () => { + let output = '' + + before(async () => { + const originalStdoutWrite = process.stdout.write + process.stdout.write = function (data) { + output += data + } + + // Create a new Suite with the JSON reporter + const suite = new Suite({ + reporter: jsonReport + }) + + suite + .add('single with matcher', function () { + const pattern = /[123]/g + const replacements = { 1: 'a', 2: 'b', 3: 'c' } + const subject = '123123123123123123123123123123123123123123123123' + const r = subject.replace(pattern, (m) => replacements[m]) + assert.ok(r) + }) + .add('Multiple replaces', function () { + const subject = '123123123123123123123123123123123123123123123123' + const r = subject.replace(/1/g, 'a').replace(/2/g, 'b').replace(/3/g, 'c') + assert.ok(r) + }) + + // Run the suite + await suite.run() + + // Restore stdout + process.stdout.write = originalStdoutWrite + }); + + it('should print valid JSON', () => { + // Verify if the output can be parsed as JSON + let data + try { + data = JSON.parse(output) + } catch (err) { + assert.fail(`Output is not valid JSON: ${err.message}`) + } + + assert.ok(Array.isArray(data), 'Output should be an array of results') + }); + + it('should contain the required benchmark fields', () => { + const data = JSON.parse(output) + + // We expect the two benchmarks we added: 'single with matcher' and 'Multiple replaces' + assert.strictEqual(data.length, 2, 'Should have results for 2 benchmarks') + + for (const entry of data) { + // Ensure each entry has expected keys + assert.ok(typeof entry.name === 'string', 'name should be a string') + assert.ok(typeof entry.opsSec === 'number', 'opsSec should be a number') + assert.ok(typeof entry.runsSampled === 'number', 'runsSampled should be a number') + assert.ok(typeof entry.min === 'string', 'min should be a string (formatted time)') + assert.ok(typeof entry.max === 'string', 'max should be a string (formatted time)') + assert.ok(Array.isArray(entry.plugins), 'plugins should be an array') + } + }); +});