Skip to content

Commit dd08c17

Browse files
authored
Merge pull request #41 from camillobruni/2025-01-28_runner_log
Improve end2end browser tests
2 parents fbc5eb5 + e381ad6 commit dd08c17

File tree

3 files changed

+150
-15
lines changed

3 files changed

+150
-15
lines changed

JetStream.css

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,7 @@ a.button {
312312
background-image: none;
313313
}
314314

315+
315316
.benchmark-done h3, .benchmark-done h4, .benchmark-done .result, .benchmark-done label {
316317
background-color: transparent;
317318
background-image: none;
@@ -322,6 +323,16 @@ a.button {
322323
user-select: text;
323324
}
324325

326+
.benchmark-error h4, .benchmark-error .result, .benchmark-error label {
327+
color: #ff8686;
328+
background-color: #ff8686;
329+
background-image: none;
330+
}
331+
332+
.benchmark-error h3 {
333+
color: #ff8686;
334+
}
335+
325336
.benchmark h3 {
326337
font-weight: 400;
327338
font-size: 1.6rem;

JetStreamDriver.js

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,8 @@ const fileLoader = (function() {
239239
class Driver {
240240
constructor() {
241241
this.isReady = false;
242+
this.isDone = false;
243+
this.errors = [];
242244
this.benchmarks = [];
243245
this.blobDataCache = { };
244246
this.loadCache = { };
@@ -274,11 +276,12 @@ class Driver {
274276
try {
275277
await benchmark.run();
276278
} catch(e) {
277-
JetStream.reportError(benchmark);
279+
this.reportError(benchmark, e);
278280
throw e;
279281
}
280282

281283
benchmark.updateUIAfterRun();
284+
console.log(benchmark.name)
282285

283286
if (isInBrowser) {
284287
const cache = JetStream.blobDataCache;
@@ -334,6 +337,8 @@ class Driver {
334337

335338
this.reportScoreToRunBenchmarkRunner();
336339
this.dumpJSONResultsIfNeeded();
340+
this.isDone = true;
341+
337342
if (isInBrowser) {
338343
globalThis.dispatchEvent(new CustomEvent("JetStreamDone", {
339344
detail: this.resultsObject()
@@ -438,16 +443,32 @@ class Driver {
438443
});
439444
}
440445

441-
reportError(benchmark)
446+
reportError(benchmark, error)
442447
{
448+
this.pushError(benchmark.name, error);
449+
443450
if (!isInBrowser)
444451
return;
445452

446-
for (const id of benchmark.scoreIdentifiers())
453+
for (const id of benchmark.scoreIdentifiers()) {
447454
document.getElementById(id).innerHTML = "error";
455+
const benchmarkResultsUI = document.getElementById(`benchmark-${benchmark.name}`);
456+
benchmarkResultsUI.classList.remove("benchmark-running");
457+
benchmarkResultsUI.classList.add("benchmark-error");
458+
}
459+
}
460+
461+
pushError(name, error) {
462+
this.errors.push({
463+
benchmark: name,
464+
error: error.toString(),
465+
stack: error.stack
466+
});
448467
}
449468

450469
async initialize() {
470+
if (isInBrowser)
471+
window.addEventListener("error", (e) => this.pushError("driver startup", e.error));
451472
await this.prefetchResourcesForBrowser();
452473
await this.fetchResources();
453474
this.prepareToRun();
@@ -508,7 +529,18 @@ class Driver {
508529
}
509530
}
510531

511-
resultsObject()
532+
resultsObject(format = "run-benchmark") {
533+
switch(format) {
534+
case "run-benchmark":
535+
return this.runBenchmarkResultsObject();
536+
case "simple":
537+
return this.simpleResultsObject();
538+
default:
539+
throw Error(`Unknown result format '${format}'`);
540+
}
541+
}
542+
543+
runBenchmarkResultsObject()
512544
{
513545
let results = {};
514546
for (const benchmark of this.benchmarks) {
@@ -528,12 +560,29 @@ class Driver {
528560

529561
results = {"JetStream3.0": {"metrics" : {"Score" : ["Geometric"]}, "tests" : results}};
530562
return results;
563+
}
531564

565+
simpleResultsObject() {
566+
const results = {__proto__: null};
567+
for (const benchmark of this.benchmarks) {
568+
if (!benchmark.isDone)
569+
continue;
570+
if (!benchmark.isSuccess) {
571+
results[benchmark.name] = "FAILED";
572+
} else {
573+
results[benchmark.name] = {
574+
Score: benchmark.score,
575+
...benchmark.subScores(),
576+
577+
};
578+
}
579+
}
580+
return results;
532581
}
533582

534-
resultsJSON()
583+
resultsJSON(format = "run-benchmark")
535584
{
536-
return JSON.stringify(this.resultsObject());
585+
return JSON.stringify(this.resultsObject(format));
537586
}
538587

539588
dumpJSONResultsIfNeeded()
@@ -566,6 +615,15 @@ class Driver {
566615
}
567616
};
568617

618+
const BenchmarkState = Object.freeze({
619+
READY: "READY",
620+
SETUP: "SETUP",
621+
RUNNING: "RUNNING",
622+
FINALIZE: "FINALIZE",
623+
ERROR: "ERROR",
624+
DONE: "DONE"
625+
})
626+
569627
class Benchmark {
570628
constructor(plan)
571629
{
@@ -575,10 +633,16 @@ class Benchmark {
575633
this.isAsync = !!plan.isAsync;
576634
this.scripts = null;
577635
this._resourcesPromise = null;
636+
this._state = BenchmarkState.READY;
578637
}
579638

580639
get name() { return this.plan.name; }
581640

641+
get isDone() {
642+
return this._state == BenchmarkState.DONE || this._state == BenchmarkState.ERROR;
643+
}
644+
get isSuccess() { return this._state = BenchmarkState.DONE; }
645+
582646
get runnerCode() {
583647
return `
584648
let __benchmark = new Benchmark(${this.iterations});
@@ -640,6 +704,9 @@ class Benchmark {
640704
}
641705

642706
async run() {
707+
if (this.isDone)
708+
throw new Error(`Cannot run Benchmark ${this.name} twice`);
709+
this._state = BenchmarkState.PREPARE;
643710
let code;
644711
if (isInBrowser)
645712
code = "";
@@ -748,11 +815,15 @@ class Benchmark {
748815

749816
let magicFrame;
750817
try {
818+
this._state = BenchmarkState.RUNNING;
751819
magicFrame = JetStream.runCode(code);
752820
} catch(e) {
821+
this._state = BenchmarkState.ERROR;
753822
console.log("Error in runCode: ", e);
754823
console.log(e.stack)
755824
throw e;
825+
} finally {
826+
this._state = BenchmarkState.FINALIZE;
756827
}
757828
const results = await promise;
758829

@@ -765,6 +836,8 @@ class Benchmark {
765836
}
766837

767838
this.processResults(results);
839+
this._state = BenchmarkState.DONE;
840+
768841
if (isInBrowser)
769842
magicFrame.contentDocument.close();
770843
else if (isD8)

tests/run.mjs

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import serve from "./server.mjs";
44
import { Builder, Capabilities } from "selenium-webdriver";
55
import commandLineArgs from "command-line-args";
66
import commandLineUsage from "command-line-usage";
7-
import assert from "assert";
87

98
const optionDefinitions = [
109
{ name: "browser", type: String, description: "Set the browser to test, choices are [safari, firefox, chrome]. By default the $BROWSER env variable is used." },
@@ -81,7 +80,9 @@ async function testEnd2End() {
8180
const driver = await new Builder().withCapabilities(capabilities).build();
8281
let results;
8382
try {
84-
await driver.get(`http://localhost:${PORT}/index.html?worstCaseCount=2&iterationCount=3`);
83+
const url = `http://localhost:${PORT}/index.html?worstCaseCount=2&iterationCount=3`;
84+
console.log(`JetStream PREPARE ${url}`);
85+
await driver.get(url);
8586
await driver.executeAsyncScript((callback) => {
8687
// callback() is explicitly called without the default event
8788
// as argument to avoid serialization issues with chromedriver.
@@ -91,14 +92,9 @@ async function testEnd2End() {
9192
if (globalThis?.JetStream?.isReady)
9293
callback()
9394
});
94-
await driver.manage().setTimeouts({ script: 3 * 60_000 });
95-
results = await driver.executeAsyncScript((callback) => {
96-
globalThis.addEventListener("JetStreamDone", event => callback(event.detail));
97-
JetStream.start();
98-
});
95+
results = await benchmarkResults(driver);
96+
// FIXME: validate results;
9997
console.log("\n✅ Tests completed!");
100-
console.log("RESULTS:")
101-
console.log(results)
10298
} catch(e) {
10399
console.error("\n❌ Tests failed!");
104100
console.error(e);
@@ -109,4 +105,59 @@ async function testEnd2End() {
109105
}
110106
}
111107

108+
async function benchmarkResults(driver) {
109+
console.log("JetStream START");
110+
await driver.manage().setTimeouts({ script: 60_000 });
111+
await driver.executeAsyncScript((callback) => {
112+
globalThis.JetStream.start();
113+
callback();
114+
});
115+
116+
await new Promise((resolve, reject) => pollResultsUntilDone(driver, resolve, reject));
117+
const resultsJSON = await driver.executeScript(() => {
118+
return globalThis.JetStream.resultsJSON();
119+
});
120+
return JSON.parse(resultsJSON);
121+
}
122+
123+
class JetStreamTestError extends Error {
124+
constructor(errors) {
125+
super(`Tests failed: ${errors.map(e => e.name).join(", ")}`);
126+
this.errors = errors;
127+
}
128+
129+
}
130+
131+
const UPDATE_INTERVAL = 250;
132+
async function pollResultsUntilDone(driver, resolve, reject) {
133+
const previousResults = new Set();
134+
const intervalId = setInterval(async function logResult() {
135+
const {done, errors, resultsJSON} = await driver.executeScript(() => {
136+
return {
137+
done: globalThis.JetStream.isDone,
138+
errors: globalThis.JetStream.errors,
139+
resultsJSON: JSON.stringify(globalThis.JetStream.resultsObject("simple")),
140+
};
141+
});
142+
if (errors.length) {
143+
clearInterval(intervalId);
144+
reject(new JetStreamTestError(errors));
145+
}
146+
logIncrementalResult(previousResults, JSON.parse(resultsJSON));
147+
if (done) {
148+
clearInterval(intervalId);
149+
resolve();
150+
}
151+
}, UPDATE_INTERVAL)
152+
}
153+
154+
function logIncrementalResult(previousResults, benchmarkResults) {
155+
for (const [testName, testResults] of Object.entries(benchmarkResults)) {
156+
if (previousResults.has(testName))
157+
continue;
158+
console.log(testName, testResults);
159+
previousResults.add(testName);
160+
}
161+
}
162+
112163
setImmediate(testEnd2End);

0 commit comments

Comments
 (0)