Skip to content

Commit 2387b9f

Browse files
authored
Split off SuiteRunner class (#433)
Move Suite-related code to a separate SuiteRunner class. This happens in preparation for async and remote suites where it will be cleaner to handle the code paths independently instead of adding more functionality to the BenchmarkRunner class.
1 parent bb4a4ad commit 2387b9f

File tree

2 files changed

+107
-82
lines changed

2 files changed

+107
-82
lines changed

resources/benchmark-runner.mjs

Lines changed: 93 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -454,8 +454,99 @@ export class BenchmarkRunner {
454454
}
455455

456456
async runSuite(suite) {
457-
await this._prepareSuite(suite);
458-
await this._runSuite(suite);
457+
// FIXME: Encapsulate more state in the SuiteRunner.
458+
// FIXME: Return and use measured values from SuiteRunner.
459+
const suiteRunner = new SuiteRunner(this._measuredValues, this._frame, this._page, this._client, suite);
460+
await suiteRunner.run();
461+
}
462+
463+
async _finalize() {
464+
this._appendIterationMetrics();
465+
if (this._client?.didRunSuites) {
466+
let product = 1;
467+
const values = [];
468+
for (const suiteName in this._measuredValues.tests) {
469+
const suiteTotal = this._measuredValues.tests[suiteName].total;
470+
product *= suiteTotal;
471+
values.push(suiteTotal);
472+
}
473+
474+
values.sort((a, b) => a - b); // Avoid the loss of significance for the sum.
475+
const total = values.reduce((a, b) => a + b);
476+
const geomean = Math.pow(product, 1 / values.length);
477+
478+
this._measuredValues.total = total;
479+
this._measuredValues.mean = total / values.length;
480+
this._measuredValues.geomean = geomean;
481+
this._measuredValues.score = geomeanToScore(geomean);
482+
await this._client.didRunSuites(this._measuredValues);
483+
}
484+
}
485+
486+
_appendIterationMetrics() {
487+
const getMetric = (name, unit = "ms") => this._metrics[name] || (this._metrics[name] = new Metric(name, unit));
488+
const iterationTotalMetric = (i) => {
489+
if (i >= params.iterationCount)
490+
throw new Error(`Requested iteration=${i} does not exist.`);
491+
return getMetric(`Iteration-${i}-Total`);
492+
};
493+
494+
const collectSubMetrics = (prefix, items, parent) => {
495+
for (let name in items) {
496+
const results = items[name];
497+
const metric = getMetric(prefix + name);
498+
metric.add(results.total ?? results);
499+
if (metric.parent !== parent)
500+
parent.addChild(metric);
501+
if (results.tests)
502+
collectSubMetrics(`${metric.name}${Metric.separator}`, results.tests, metric);
503+
}
504+
};
505+
const initializeMetrics = this._metrics === null;
506+
if (initializeMetrics)
507+
this._metrics = { __proto__: null };
508+
509+
const iterationResults = this._measuredValues.tests;
510+
collectSubMetrics("", iterationResults);
511+
512+
if (initializeMetrics) {
513+
// Prepare all iteration metrics so they are listed at the end of
514+
// of the _metrics object, before "Total" and "Score".
515+
for (let i = 0; i < this._iterationCount; i++)
516+
iterationTotalMetric(i).description = `Test totals for iteration ${i}`;
517+
getMetric("Geomean", "ms").description = "Geomean of test totals";
518+
getMetric("Score", "score").description = "Scaled inverse of the Geomean";
519+
}
520+
521+
const geomean = getMetric("Geomean");
522+
const iterationTotal = iterationTotalMetric(geomean.length);
523+
for (const results of Object.values(iterationResults))
524+
iterationTotal.add(results.total);
525+
iterationTotal.computeAggregatedMetrics();
526+
geomean.add(iterationTotal.geomean);
527+
getMetric("Score").add(geomeanToScore(iterationTotal.geomean));
528+
529+
for (const metric of Object.values(this._metrics))
530+
metric.computeAggregatedMetrics();
531+
}
532+
}
533+
534+
// FIXME: Create AsyncSuiteRunner subclass.
535+
// FIXME: Create RemoteSuiteRunner subclass.
536+
export class SuiteRunner {
537+
constructor(measuredValues, frame, page, client, suite) {
538+
// FIXME: Create SuiteRunner-local measuredValues.
539+
this._measuredValues = measuredValues;
540+
this._frame = frame;
541+
this._page = page;
542+
this._client = client;
543+
this._suite = suite;
544+
}
545+
546+
async run() {
547+
// FIXME: use this._suite in all SuiteRunner methods directly.
548+
await this._prepareSuite(this._suite);
549+
await this._runSuite(this._suite);
459550
}
460551

461552
async _prepareSuite(suite) {
@@ -570,74 +661,4 @@ export class BenchmarkRunner {
570661
if (this._client?.didRunTest)
571662
await this._client.didRunTest(suite, test);
572663
}
573-
574-
async _finalize() {
575-
this._appendIterationMetrics();
576-
if (this._client?.didRunSuites) {
577-
let product = 1;
578-
const values = [];
579-
for (const suiteName in this._measuredValues.tests) {
580-
const suiteTotal = this._measuredValues.tests[suiteName].total;
581-
product *= suiteTotal;
582-
values.push(suiteTotal);
583-
}
584-
585-
values.sort((a, b) => a - b); // Avoid the loss of significance for the sum.
586-
const total = values.reduce((a, b) => a + b);
587-
const geomean = Math.pow(product, 1 / values.length);
588-
589-
this._measuredValues.total = total;
590-
this._measuredValues.mean = total / values.length;
591-
this._measuredValues.geomean = geomean;
592-
this._measuredValues.score = geomeanToScore(geomean);
593-
await this._client.didRunSuites(this._measuredValues);
594-
}
595-
}
596-
597-
_appendIterationMetrics() {
598-
const getMetric = (name, unit = "ms") => this._metrics[name] || (this._metrics[name] = new Metric(name, unit));
599-
const iterationTotalMetric = (i) => {
600-
if (i >= params.iterationCount)
601-
throw new Error(`Requested iteration=${i} does not exist.`);
602-
return getMetric(`Iteration-${i}-Total`);
603-
};
604-
605-
const collectSubMetrics = (prefix, items, parent) => {
606-
for (let name in items) {
607-
const results = items[name];
608-
const metric = getMetric(prefix + name);
609-
metric.add(results.total ?? results);
610-
if (metric.parent !== parent)
611-
parent.addChild(metric);
612-
if (results.tests)
613-
collectSubMetrics(`${metric.name}${Metric.separator}`, results.tests, metric);
614-
}
615-
};
616-
const initializeMetrics = this._metrics === null;
617-
if (initializeMetrics)
618-
this._metrics = { __proto__: null };
619-
620-
const iterationResults = this._measuredValues.tests;
621-
collectSubMetrics("", iterationResults);
622-
623-
if (initializeMetrics) {
624-
// Prepare all iteration metrics so they are listed at the end of
625-
// of the _metrics object, before "Total" and "Score".
626-
for (let i = 0; i < this._iterationCount; i++)
627-
iterationTotalMetric(i).description = `Test totals for iteration ${i}`;
628-
getMetric("Geomean", "ms").description = "Geomean of test totals";
629-
getMetric("Score", "score").description = "Scaled inverse of the Geomean";
630-
}
631-
632-
const geomean = getMetric("Geomean");
633-
const iterationTotal = iterationTotalMetric(geomean.length);
634-
for (const results of Object.values(iterationResults))
635-
iterationTotal.add(results.total);
636-
iterationTotal.computeAggregatedMetrics();
637-
geomean.add(iterationTotal.geomean);
638-
getMetric("Score").add(geomeanToScore(iterationTotal.geomean));
639-
640-
for (const metric of Object.values(this._metrics))
641-
metric.computeAggregatedMetrics();
642-
}
643664
}

tests/benchmark-runner-tests.mjs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { BenchmarkRunner } from "../resources/benchmark-runner.mjs";
1+
import { BenchmarkRunner, SuiteRunner } from "../resources/benchmark-runner.mjs";
22
import { defaultParams } from "../resources/params.mjs";
33

44
function TEST_FIXTURE(name) {
@@ -112,9 +112,9 @@ describe("BenchmarkRunner", () => {
112112
let _runSuiteStub, _finalizeStub, _loadFrameStub, _appendFrameStub, _removeFrameStub;
113113

114114
before(async () => {
115-
_runSuiteStub = stub(runner, "_runSuite").callsFake(async () => null);
115+
_runSuiteStub = stub(SuiteRunner.prototype, "_runSuite").callsFake(async () => null);
116116
_finalizeStub = stub(runner, "_finalize").callsFake(async () => null);
117-
_loadFrameStub = stub(runner, "_loadFrame").callsFake(async () => null);
117+
_loadFrameStub = stub(SuiteRunner.prototype, "_loadFrame").callsFake(async () => null);
118118
_appendFrameStub = stub(runner, "_appendFrame").callsFake(async () => null);
119119
_removeFrameStub = stub(runner, "_removeFrame").callsFake(() => null);
120120
for (const suite of runner._suites)
@@ -148,18 +148,19 @@ describe("BenchmarkRunner", () => {
148148
});
149149

150150
describe("runSuite", () => {
151-
let _prepareSuiteSpy, _loadFrameStub, _runTestAndRecordResultsStub, _suitePrepareSpy, performanceMarkSpy;
151+
let _prepareSuiteSpy, _loadFrameStub, _runTestAndRecordResultsStub, _validateSuiteTotalStub, _suitePrepareSpy, performanceMarkSpy;
152152

153153
const suite = SUITES_FIXTURE[0];
154154

155155
before(async () => {
156-
_prepareSuiteSpy = spy(runner, "_prepareSuite");
157-
_loadFrameStub = stub(runner, "_loadFrame").callsFake(async () => null);
158-
_runTestAndRecordResultsStub = stub(runner, "_runTestAndRecordResults").callsFake(async () => null);
156+
_prepareSuiteSpy = spy(SuiteRunner.prototype, "_prepareSuite");
157+
_loadFrameStub = stub(SuiteRunner.prototype, "_loadFrame").callsFake(async () => null);
158+
_runTestAndRecordResultsStub = stub(SuiteRunner.prototype, "_runTestAndRecordResults").callsFake(async () => null);
159+
_validateSuiteTotalStub = stub(SuiteRunner.prototype, "_validateSuiteTotal").callsFake(async () => null);
159160
performanceMarkSpy = spy(window.performance, "mark");
160161
_suitePrepareSpy = spy(suite, "prepare");
161162

162-
runner.runSuite(suite);
163+
await runner.runSuite(suite);
163164
});
164165

165166
it("should prepare the suite first", async () => {
@@ -170,6 +171,7 @@ describe("BenchmarkRunner", () => {
170171

171172
it("should run and record results for every test in suite", async () => {
172173
assert.calledThrice(_runTestAndRecordResultsStub);
174+
assert.calledOnce(_validateSuiteTotalStub);
173175
assert.calledWith(performanceMarkSpy, "suite-Suite 1-prepare-start");
174176
assert.calledWith(performanceMarkSpy, "suite-Suite 1-prepare-end");
175177
assert.calledWith(performanceMarkSpy, "suite-Suite 1-start");
@@ -188,7 +190,8 @@ describe("BenchmarkRunner", () => {
188190
before(async () => {
189191
await runner._appendFrame();
190192
performanceMarkSpy = spy(window.performance, "mark");
191-
await runner._runTestAndRecordResults(suite, suite.tests[0]);
193+
const suiteRunner = new SuiteRunner(runner._measuredValues, runner._frame, runner._page, runner._client, runner._suite);
194+
await suiteRunner._runTestAndRecordResults(suite, suite.tests[0]);
192195
});
193196

194197
it("should run client pre and post hooks if present", () => {
@@ -222,7 +225,8 @@ describe("BenchmarkRunner", () => {
222225
stubPerformanceNowCalls(syncStart, syncEnd, asyncStart, asyncEnd);
223226

224227
// instantiate recorded test results
225-
await runner._runTestAndRecordResults(suite, suite.tests[0]);
228+
const suiteRunner = new SuiteRunner(runner._measuredValues, runner._frame, runner._page, runner._client, runner._suite);
229+
await suiteRunner._runTestAndRecordResults(suite, suite.tests[0]);
226230

227231
await runner._finalize();
228232
});

0 commit comments

Comments
 (0)