resources/shared/benchmark.mjs (109 lines of code) (raw):
/* eslint-disable no-case-declarations */
import { TestRunner } from "./test-runner.mjs";
import { Params } from "./params.mjs";
/**
* BenchmarkStep
*
* A single test step, with a common interface to interact with.
*/
export class BenchmarkStep {
constructor(name, run) {
this.name = name;
this.run = run;
}
async runAndRecord(params, suite, test, callback) {
const testRunner = new TestRunner(null, null, params, suite, test, callback);
const result = await testRunner.runTest();
return result;
}
}
/**
* BenchmarkSuite
*
* A single test suite that contains one or more test steps.
*/
export class BenchmarkSuite {
constructor(name, tests) {
this.name = name;
this.tests = tests;
}
record(_test, syncTime, asyncTime) {
const total = syncTime + asyncTime;
const results = {
tests: { Sync: syncTime, Async: asyncTime },
total: total,
};
return results;
}
async runAndRecord(params, onProgress) {
const measuredValues = {
tests: {},
total: 0,
};
const suiteStartLabel = `suite-${this.name}-start`;
const suiteEndLabel = `suite-${this.name}-end`;
performance.mark(suiteStartLabel);
for (const test of this.tests) {
const result = await test.runAndRecord(params, this, test, this.record);
measuredValues.tests[test.name] = result;
measuredValues.total += result.total;
onProgress?.(test.name);
}
performance.mark(suiteEndLabel);
performance.measure(`suite-${this.name}`, suiteStartLabel, suiteEndLabel);
return {
type: "suite-tests-complete",
status: "success",
result: measuredValues,
suitename: this.name,
};
}
}
/** **********************************************************************
* BenchmarkConnector
*
* postMessage is used to communicate between app and benchmark.
* When the app is ready, an 'app-ready' message is sent to signal that the app can receive instructions.
*
* A prepare script within the apps appends window.name and window.version from the package.json file.
* The appId is build by appending name-version
* It's used as an additional safe-guard to ensure the correct app responds to a message.
*************************************************************************/
export class BenchmarkConnector {
constructor(suites, name, version) {
this.suites = suites;
this.name = name;
this.version = version;
if (!name || !version)
console.warn("No name or version supplied, to create a unique appId");
this.appId = name && version ? `${name}-${version}` : -1;
this.onMessage = this.onMessage.bind(this);
}
async onMessage(event) {
if (event.data.id !== this.appId || event.data.key !== "benchmark-connector")
return;
switch (event.data.type) {
case "benchmark-suite":
const params = new Params(new URLSearchParams(window.location.search));
const suite = this.suites[event.data.name];
if (!suite)
console.error(`Suite with the name of "${event.data.name}" not found!`);
const { result } = await suite.runAndRecord(params, (test) => this.sendMessage({ type: "step-complete", status: "success", appId: this.appId, name: this.name, test }));
this.sendMessage({ type: "suite-complete", status: "success", appId: this.appId, result });
this.disconnect();
break;
default:
console.error(`Message data type not supported: ${event.data.type}`);
}
}
sendMessage(message) {
window.top.postMessage(message, "*");
}
connect() {
window.addEventListener("message", this.onMessage);
this.sendMessage({ type: "app-ready", status: "success", appId: this.appId });
}
disconnect() {
window.removeEventListener("message", this.onMessage);
}
}