Merge branch 'main' into plugin-options

This commit is contained in:
Peter Hedenskog 2025-02-28 04:25:20 +01:00 committed by GitHub
commit cf0b2a9ca7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 117 additions and 81 deletions

View File

@ -81,12 +81,18 @@ export async function loadPlugins(pluginNames, options, context, queue) {
plugins.push(p);
} catch (error) {
// try global
let { default: plugin } = await importGlobalSilent(name);
if (plugin) {
let p = new plugin(options, context, queue);
plugins.push(p);
} else {
console.error("Couldn't load plugin %s: %s", name, error_);
try {
let { default: plugin } = await importGlobalSilent(name);
if (plugin) {
let p = new plugin(options, context, queue);
plugins.push(p);
} else {
console.error("Couldn't load plugin %s: %s", name, error_);
// if it fails here, let it fail hard
throw error;
}
} catch {
console.error("Couldn't find/load plugin %s", name);
// if it fails here, let it fail hard
throw error;
}

View File

@ -1,69 +1,95 @@
import path from 'node:path';
import { parse } from 'node:url';
import jrp from 'junit-report-builder';
import fs from 'node:fs';
import merge from 'lodash.merge';
import { getLogger } from '@sitespeed.io/log';
const log = getLogger('sitespeedio.plugin.budget');
import merge from 'lodash.merge';
/**
* Escapes XML special characters.
*
* @param {string} str - The text to escape.
* @returns {string} The escaped text.
*/
function xmlEscape(str) {
return String(str)
.replaceAll('&', '&')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&apos;');
}
/**
* Wraps a string in a CDATA block.
*
* @param {string} str - The string to wrap.
* @returns {string} The CDATA-wrapped string.
*/
function cdata(str) {
return `<![CDATA[${str}]]>`;
}
/**
* Writes a JUnit XML report mimicking the original output.
*
* @param {object} results - Object containing `failing` and `working` results.
* @param {string} dir - Directory where `junit.xml` will be written.
* @param {object} options - Options (expects `options.budget.friendlyName`).
*/
export function writeJunit(results, dir, options) {
// lets have one suite per URL
const urls = Object.keys(merge({}, results.failing, results.working));
const failing = results.failing || {};
const working = results.working || {};
const urls = Object.keys(merge({}, failing, working));
let totalTests = 0;
let totalFailures = 0;
let suitesXml = '';
for (const url of urls) {
// The URL can be an alias
let name = url;
if (url.startsWith('http')) {
const parsedUrl = parse(url);
name = url.startsWith('http') ? url : url;
parsedUrl.hostname.replaceAll('.', '_') +
'.' +
parsedUrl.path.replaceAll('.', '_').replaceAll('/', '_');
}
const suiteName = `${options.budget.friendlyName || 'sitespeed.io'}.${url}`;
let suiteTests = 0;
let suiteFailures = 0;
let testCasesXml = '';
const suite = jrp
.testSuite()
.name(options.budget.friendlyName || 'sitespeed.io' + '.' + name);
if (results.failing[url]) {
for (const result of results.failing[url]) {
suite
.testCase()
.className(name)
.name(result.type + '.' + result.metric)
.failure(
result.metric + ' is ' + result.friendlyValue ||
result.value +
' and limit ' +
result.limitType +
' ' +
result.friendlyLimit ||
result.limit + ' ' + url
);
if (failing[url]) {
for (const result of failing[url]) {
suiteTests++;
totalTests++;
suiteFailures++;
totalFailures++;
const testCaseName = `${result.type}.${result.metric}`;
const failureMessage = `${result.metric} is ${result.friendlyValue || result.value}`;
testCasesXml += ` <testcase classname="${xmlEscape(url)}" name="${xmlEscape(testCaseName)}">\n`;
testCasesXml += ` <failure message="${xmlEscape(failureMessage)}"/>\n`;
testCasesXml += ` </testcase>\n`;
}
}
if (results.working[url]) {
for (const result of results.working[url]) {
suite
.testCase()
.className(name)
.name(result.type + '.' + result.metric)
.standardOutput(
result.metric + ' is ' + result.friendlyValue ||
result.value +
' and limit ' +
result.limitType +
' ' +
result.friendlyLimit ||
result.limit + ' ' + url
);
if (working[url]) {
for (const result of working[url]) {
suiteTests++;
totalTests++;
const testCaseName = `${result.type}.${result.metric}`;
const systemOutMessage = `${result.metric} is ${result.friendlyValue || result.value}`;
testCasesXml += ` <testcase classname="${xmlEscape(url)}" name="${xmlEscape(testCaseName)}">\n`;
testCasesXml += ` <system-out>${cdata(systemOutMessage)}</system-out>\n`;
testCasesXml += ` </testcase>\n`;
}
}
suitesXml += ` <testsuite name="${xmlEscape(suiteName)}" tests="${suiteTests}" failures="${suiteFailures}" errors="0" skipped="0">\n`;
suitesXml += testCasesXml;
suitesXml += ` </testsuite>\n`;
}
const xml =
`<?xml version="1.0" encoding="UTF-8"?>\n` +
`<testsuites tests="${totalTests}" failures="${totalFailures}" errors="0" skipped="0">\n` +
suitesXml +
`</testsuites>\n`;
const file = path.join(dir, 'junit.xml');
log.info('Write junit budget to %s', path.resolve(file));
jrp.writeTo(file);
fs.writeFileSync(file, xml);
}

View File

@ -1,36 +1,44 @@
import path from 'node:path';
import fs from 'node:fs';
import { EOL } from 'node:os';
import tap from 'tape';
import { getLogger } from '@sitespeed.io/log';
const log = getLogger('sitespeedio.plugin.budget');
export function writeTap(results, dir) {
const file = path.join(dir, 'budget.tap');
log.info('Write budget to %s', path.resolve(file));
const tapOutput = fs.createWriteStream(file);
tap.createStream().pipe(tapOutput);
const lines = [];
lines.push('TAP version 13');
let testCount = 0;
// Iterate over each result group (e.g. "passing" and "failing")
for (const resultType of Object.keys(results)) {
const urls = Object.keys(results.failing);
const group = results[resultType];
if (!group) {
continue;
}
const urls = Object.keys(group);
for (const url of urls) {
for (const result of results.failing[url]) {
tap(result.type + '.' + result.metric + ' ' + url, function (t) {
let extra = '';
if (resultType === 'failing') {
extra =
' limit ' + result.limitType + ' ' + result.friendlyLimit ||
result.limit + EOL;
}
t.ok(
resultType === 'failing' ? false : true,
result.type + '.' + result.metric + ' ' + result.friendlyValue ||
result.value + ' ' + extra + ' ' + url
);
t.end();
});
for (const result of group[url]) {
testCount += 1;
const testTitle = `${result.type}.${result.metric} ${url}`;
let extra = '';
if (resultType === 'failing') {
extra = ` limit ${result.limitType} ${result.friendlyLimit || result.limit}`;
}
const valueDisplay = result.friendlyValue || result.value;
lines.push(`# ${testTitle}`);
const status = resultType === 'failing' ? 'not ok' : 'ok';
lines.push(
`${status} ${testCount} ${testTitle} ${valueDisplay}${extra ? ` ${extra}` : ''}`
);
}
}
}
lines.push(`1..${testCount}`);
fs.writeFileSync(file, lines.join(EOL) + EOL);
}

2
npm-shrinkwrap.json generated
View File

@ -25,7 +25,6 @@
"fast-stats": "0.0.7",
"import-global": "1.1.1",
"influx": "5.9.3",
"junit-report-builder": "3.2.1",
"lodash.get": "4.4.2",
"lodash.merge": "4.6.2",
"lodash.set": "4.3.2",
@ -34,7 +33,6 @@
"ora": "8.0.1",
"pug": "3.0.3",
"simplecrawler": "1.1.9",
"tape": "5.8.1",
"yargs": "17.7.2"
},
"bin": {

View File

@ -96,7 +96,6 @@
"fast-stats": "0.0.7",
"import-global": "1.1.1",
"influx": "5.9.3",
"junit-report-builder": "3.2.1",
"lodash.get": "4.4.2",
"lodash.merge": "4.6.2",
"lodash.set": "4.3.2",
@ -105,7 +104,6 @@
"ora": "8.0.1",
"pug": "3.0.3",
"simplecrawler": "1.1.9",
"tape": "5.8.1",
"yargs": "17.7.2"
},
"overrides": {