| import { BenchmarkRunner } from "./benchmark-runner.mjs"; |
| import { params } from "./params.mjs"; |
| import { Suites } from "./tests.mjs"; |
| |
| class InteractiveBenchmarkRunner extends BenchmarkRunner { |
| _stepPromise = undefined; |
| _stepPromiseResolve = undefined; |
| _isRunning = false; |
| _isStepping = false; |
| |
| constructor(suites, iterationCount) { |
| super(suites); |
| this._client = this._createClient(); |
| if (!Number.isInteger(iterationCount) || iterationCount <= 0) |
| throw Error("iterationCount must be a positive integer."); |
| this._iterationCount = iterationCount; |
| } |
| |
| _createClient() { |
| return { |
| willStartFirstIteration: this._start.bind(this), |
| willRunTest: this._testStart.bind(this), |
| didRunTest: this._testDone.bind(this), |
| didRunSuites: this._iterationDone.bind(this), |
| didFinishLastIteration: this._done.bind(this), |
| }; |
| } |
| |
| _start() { |
| if (this._isRunning) |
| throw Error("Runner was not stopped before starting;"); |
| this._isRunning = true; |
| if (this._isStepping) |
| this._stepPromise = this._newStepPromise(); |
| } |
| |
| _step() { |
| if (!this._stepPromise) { |
| // Allow switching to stepping mid-run. |
| this._stepPromise = this._newStepPromise(); |
| } else { |
| const resolve = this._stepPromiseResolve; |
| this._stepPromise = this._newStepPromise(); |
| resolve(); |
| } |
| } |
| |
| _newStepPromise() { |
| return new Promise((resolve) => { |
| this._stepPromiseResolve = resolve; |
| }); |
| } |
| |
| _testStart(suite, test) { |
| test.anchor.classList.add("running"); |
| } |
| |
| async _testDone(suite, test) { |
| const classList = test.anchor.classList; |
| classList.remove("running"); |
| classList.add("ran"); |
| if (this._isStepping) |
| await this._stepPromise; |
| } |
| |
| _iterationDone(measuredValues) { |
| let results = ""; |
| for (const suiteName in measuredValues.tests) { |
| let suiteResults = measuredValues.tests[suiteName]; |
| for (const testName in suiteResults.tests) { |
| let testResults = suiteResults.tests[testName]; |
| for (const subtestName in testResults.tests) |
| results += `${suiteName} : ${testName} : ${subtestName}: ${testResults.tests[subtestName]} ms\n`; |
| } |
| results += `${suiteName} : ${suiteResults.total} ms\n`; |
| } |
| results += `Arithmetic Mean : ${measuredValues.mean} ms\n`; |
| results += `Geometric Mean : ${measuredValues.geomean} ms\n`; |
| results += `Total : ${measuredValues.total} ms\n`; |
| results += `Score : ${measuredValues.score} rpm\n`; |
| |
| if (!results) |
| return; |
| |
| const pre = document.createElement("pre"); |
| document.body.appendChild(pre); |
| pre.textContent = results; |
| } |
| |
| _done() { |
| this.isRunning = false; |
| } |
| |
| runStep() { |
| this._isStepping = true; |
| if (!this._isRunning) |
| this.runMultipleIterations(this._iterationCount); |
| else |
| this._step(); |
| } |
| |
| runSuites() { |
| if (this._isRunning) { |
| if (this._isStepping) { |
| // Switch to continuous running only if we've been stepping. |
| this._isStepping = false; |
| this._step(); |
| } |
| } else { |
| this._isStepping = false; |
| this.runMultipleIterations(this._iterationCount); |
| } |
| } |
| } |
| |
| // Expose Suites/BenchmarkRunner for backwards compatibility |
| globalThis.BenchmarkRunner = InteractiveBenchmarkRunner; |
| |
| function formatTestName(suiteName, testName) { |
| return suiteName + (testName ? `/${testName}` : ""); |
| } |
| |
| function createUIForSuites(suites, onStep, onRunSuites) { |
| const control = document.createElement("nav"); |
| const ol = document.createElement("ol"); |
| const checkboxes = []; |
| for (let suiteIndex = 0; suiteIndex < suites.length; suiteIndex++) { |
| const suite = suites[suiteIndex]; |
| const li = document.createElement("li"); |
| const checkbox = document.createElement("input"); |
| checkbox.id = suite.name; |
| checkbox.type = "checkbox"; |
| checkbox.checked = !suite.disabled; |
| checkbox.onchange = () => { |
| suite.disabled = !checkbox.checked; |
| }; |
| checkbox.onchange(); |
| checkboxes.push(checkbox); |
| |
| li.appendChild(checkbox); |
| var label = document.createElement("label"); |
| label.appendChild(document.createTextNode(formatTestName(suite.name))); |
| li.appendChild(label); |
| label.htmlFor = checkbox.id; |
| |
| const testList = document.createElement("ol"); |
| for (let testIndex = 0; testIndex < suite.tests.length; testIndex++) { |
| const testItem = document.createElement("li"); |
| const test = suite.tests[testIndex]; |
| const anchor = document.createElement("a"); |
| anchor.id = `${suite.name}-${test.name}`; |
| test.anchor = anchor; |
| anchor.appendChild(document.createTextNode(formatTestName(suite.name, test.name))); |
| testItem.appendChild(anchor); |
| testList.appendChild(testItem); |
| } |
| li.appendChild(testList); |
| |
| ol.appendChild(li); |
| } |
| |
| control.appendChild(ol); |
| |
| let button = document.createElement("button"); |
| button.textContent = "Step"; |
| button.onclick = onStep; |
| control.appendChild(button); |
| |
| button = document.createElement("button"); |
| button.textContent = "Run"; |
| button.id = "runSuites"; |
| button.onclick = onRunSuites; |
| control.appendChild(button); |
| |
| button = document.createElement("button"); |
| button.textContent = "Select all"; |
| button.onclick = () => { |
| for (var suiteIndex = 0; suiteIndex < suites.length; suiteIndex++) { |
| suites[suiteIndex].disabled = false; |
| checkboxes[suiteIndex].checked = true; |
| } |
| }; |
| control.appendChild(button); |
| |
| button = document.createElement("button"); |
| button.textContent = "Unselect all"; |
| button.onclick = () => { |
| for (var suiteIndex = 0; suiteIndex < suites.length; suiteIndex++) { |
| suites[suiteIndex].disabled = true; |
| checkboxes[suiteIndex].checked = false; |
| } |
| }; |
| control.appendChild(button); |
| |
| return control; |
| } |
| |
| function startTest() { |
| if (params.suites.length > 0 || params.tags.length > 0) |
| Suites.enable(params.suites, params.tags); |
| |
| const interactiveRunner = new window.BenchmarkRunner(Suites, params.iterationCount); |
| if (!(interactiveRunner instanceof InteractiveBenchmarkRunner)) |
| throw Error("window.BenchmarkRunner must be a subclass of InteractiveBenchmarkRunner"); |
| |
| // Don't call step while step is already executing. |
| document.body.appendChild(createUIForSuites(Suites, interactiveRunner.runStep.bind(interactiveRunner), interactiveRunner.runSuites.bind(interactiveRunner))); |
| |
| if (params.startAutomatically) |
| document.getElementById("runSuites").click(); |
| } |
| |
| window.addEventListener("load", startTest); |