From e97e2729dca4e0a0236e78379d258715cb3f9e4f Mon Sep 17 00:00:00 2001 From: Camillo Bruni Date: Tue, 22 Apr 2025 16:44:30 +0200 Subject: [PATCH 01/11] Add --no-prefetch cli flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --no-prefetch will directly use `load(...)` instead of just injecting the script sources which makes it easier to navigate and investigate profiles. - Support more complex command line args and flags - Add --help to cli.js Bug: 411303884 Change-Id: I43a032342a6da42bfb61ebd9415e62f9d9597ecc Reviewed-on: https://chromium-review.googlesource.com/c/external/github.com/WebKit/JetStream/+/6468518 Reviewed-by: Leszek Swirski Reviewed-by: Marja Hölttä --- JetStreamDriver.js | 14 +++++++++++++- cli.js | 48 +++++++++++++++++++++++++++++++++++++++------- index.html | 2 ++ 3 files changed, 56 insertions(+), 8 deletions(-) diff --git a/JetStreamDriver.js b/JetStreamDriver.js index 77d74c59..da9a5054 100644 --- a/JetStreamDriver.js +++ b/JetStreamDriver.js @@ -39,6 +39,7 @@ globalThis.testWorstCaseCountMap ??= new Map(); globalThis.dumpJSONResults ??= false; globalThis.customTestList ??= []; globalThis.startDelay ??= undefined; +globalThis.prefetchResources ??= true; let shouldReport = false; @@ -52,6 +53,13 @@ function getIntParam(urlParams, key) { return value } +function getBoolParam(urlParams, key, defaultValue=false) { + if (!urlParams.has(key)) + return defaultValue; + const rawValue = urlParams.get(key).toLowerCase() + return (rawValue !== "false" && rawValue !== "0") + } + if (typeof(URLSearchParams) !== "undefined") { const urlParameters = new URLSearchParams(window.location.search); shouldReport = urlParameters.has('report') && urlParameters.get('report').toLowerCase() == 'true'; @@ -62,6 +70,7 @@ if (typeof(URLSearchParams) !== "undefined") { customTestList = urlParameters.getAll("test"); globalThis.testIterationCount = getIntParam(urlParameters, "iterationCount"); globalThis.testWorstCaseCount = getIntParam(urlParameters, "worstCaseCount"); + globalThis.prefetchResources = getBoolParam(urlParameters, "prefetchResources", true) } // Used for the promise representing the current benchmark run. @@ -197,8 +206,11 @@ const fileLoader = (function() { } async _loadInternal(url) { - if (!isInBrowser) + if (!isInBrowser) { + if (!globalThis.prefetchResources) + return Promise.resolve(`load("${url}");`); return Promise.resolve(readFile(url)); + } let response; const tries = 3; diff --git a/cli.js b/cli.js index 7a3a5ccb..fac9f0f4 100644 --- a/cli.js +++ b/cli.js @@ -23,6 +23,7 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ +globalThis.prefetchResources = true; const isInBrowser = false; console = { log: globalThis?.console?.log ?? print, @@ -33,15 +34,35 @@ const isD8 = typeof Realm !== "undefined"; if (isD8) globalThis.readFile = read; const isSpiderMonkey = typeof newGlobal !== "undefined"; -if (isSpiderMonkey) { +if (isSpiderMonkey) globalThis.readFile = readRelativeToScript; - globalThis.arguments = scriptArgs; + + +let cliFlags = {}; +let cliArgs = []; + +if (typeof arguments != "undefined" && arguments.length > 0) { + for (const arg of arguments) { + if (arg.startsWith("--")) { + const parts = arg.split("="); + cliFlags[parts[0]] = parts.slice(1).join("="); + } else { + cliArgs.push(arg); + } + } +} + +if (typeof testList === "undefined") { + if (cliArgs.length > 0) { + testList = cliArgs; + } else { + testList = undefined; + } } -if (typeof arguments !== "undefined" && arguments.length > 0) - testList = arguments.slice(); -if (typeof testList === "undefined") - testList = undefined; +if ("--no-prefetch" in cliFlags || "--noprefetch" in cliFlags) + globalThis.prefetchResources = false + if (typeof testIterationCount === "undefined") testIterationCount = undefined; @@ -53,6 +74,20 @@ else load("./JetStreamDriver.js"); +if ("--help" in cliFlags) { + print("JetStream Driver Help") + print("") + print("Options:") + print(" --no-prefetch: directly use load('...') for benchmark resources.") + print("") + print("Available tests:") + for (const test of testPlans) + print(" ", test.name) +} else { + print("Running tests: " + testList) + runJetStream(); +} + async function runJetStream() { try { await JetStream.initialize(); @@ -63,4 +98,3 @@ async function runJetStream() { throw e; } } -runJetStream(); diff --git a/index.html b/index.html index 98992b58..27bc1ecb 100644 --- a/index.html +++ b/index.html @@ -36,7 +36,9 @@ const isInBrowser = true; const isD8 = false; const isSpiderMonkey = false; + globalThis.prefetchResources = true; globalThis.allIsGood = true; + window.onerror = function(e) { if (e == "Script error.") { // This is a workaround for Firefox on iOS which has an uncaught exception from From 1f4b223d6fdc4ce4b3774ac3bbfc47af2e87404a Mon Sep 17 00:00:00 2001 From: Camillo Bruni Date: Wed, 23 Apr 2025 18:53:54 +0200 Subject: [PATCH 02/11] skip resources --- JetStreamDriver.js | 105 +++++++++++++++++++++++++-------------------- cli.js | 1 + 2 files changed, 60 insertions(+), 46 deletions(-) diff --git a/JetStreamDriver.js b/JetStreamDriver.js index da9a5054..b7fb0e12 100644 --- a/JetStreamDriver.js +++ b/JetStreamDriver.js @@ -57,7 +57,7 @@ function getBoolParam(urlParams, key, defaultValue=false) { if (!urlParams.has(key)) return defaultValue; const rawValue = urlParams.get(key).toLowerCase() - return (rawValue !== "false" && rawValue !== "0") + return !(rawValue === "false" || rawValue === "0") } if (typeof(URLSearchParams) !== "undefined") { @@ -70,9 +70,12 @@ if (typeof(URLSearchParams) !== "undefined") { customTestList = urlParameters.getAll("test"); globalThis.testIterationCount = getIntParam(urlParameters, "iterationCount"); globalThis.testWorstCaseCount = getIntParam(urlParameters, "worstCaseCount"); - globalThis.prefetchResources = getBoolParam(urlParameters, "prefetchResources", true) + globalThis.prefetchResources = getBoolParam(urlParameters, "prefetchResources", true); } +if (!globalThis.prefetchResources) + console.warn("Disabling resource prefetching!", globalThis.prefetchResources) + // Used for the promise representing the current benchmark run. this.currentResolve = null; this.currentReject = null; @@ -199,54 +202,56 @@ function uiFriendlyDuration(time) return result; } -const fileLoader = (function() { - class Loader { - constructor() { - this.requests = new Map; +class FileLoader { + constructor() { + this.requests = new Map; + } + + async _loadInternal(url) { + if (!isInBrowser) { + if (!globalThis.prefetchResources) + return Promise.resolve(`load("${url}");`); + return Promise.resolve(readFile(url)); } - async _loadInternal(url) { - if (!isInBrowser) { - if (!globalThis.prefetchResources) - return Promise.resolve(`load("${url}");`); - return Promise.resolve(readFile(url)); - } + if (!globalThis.prefetchResources) + return Promise.resolve(`"`); - let response; - const tries = 3; - while (tries--) { - let hasError = false; - try { - response = await fetch(url); - } catch (e) { - hasError = true; - } - if (!hasError && response.ok) - break; - if (tries) - continue; - globalThis.allIsGood = false; - throw new Error("Fetch failed"); + let response; + const tries = 3; + while (tries--) { + let hasError = false; + try { + response = await fetch(url); + } catch (e) { + hasError = true; } - if (url.indexOf(".js") !== -1) - return response.text(); - else if (url.indexOf(".wasm") !== -1) - return response.arrayBuffer(); - - throw new Error("should not be reached!"); + if (!hasError && response.ok) + break; + if (tries) + continue; + globalThis.allIsGood = false; + throw new Error("Fetch failed"); } + if (url.indexOf(".js") !== -1) + return response.text(); + else if (url.indexOf(".wasm") !== -1) + return response.arrayBuffer(); - async load(url) { - if (this.requests.has(url)) - return this.requests.get(url); + throw new Error("should not be reached!"); + } - const promise = this._loadInternal(url); - this.requests.set(url, promise); - return promise; - } + async load(url) { + if (this.requests.has(url)) + return this.requests.get(url); + + const promise = this._loadInternal(url); + this.requests.set(url, promise); + return promise; } - return new Loader; -})(); +} + +const fileLoader = new FileLoader(); class Driver { constructor() { @@ -295,7 +300,7 @@ class Driver { benchmark.updateUIAfterRun(); console.log(benchmark.name) - if (isInBrowser) { + if (isInBrowser && globalThis.prefetchResources) { const cache = JetStream.blobDataCache; for (const file of benchmark.plan.files) { const blobData = cache[file]; @@ -803,8 +808,12 @@ class Benchmark { addScript(text); } else { const cache = JetStream.blobDataCache; - for (const file of this.plan.files) - addScriptWithURL(cache[file].blobURL); + for (const file of this.plan.files) { + if (globalThis.prefetchResources) + addScriptWithURL(cache[file].blobURL); + else + addScriptWithURL(file); + } } const promise = new Promise((resolve, reject) => { @@ -857,6 +866,11 @@ class Benchmark { } async doLoadBlob(resource) { + const blobData = JetStream.blobDataCache[resource]; + if (!globalThis.prefetchResources) { + blobData.blobURL = resource; + return blobData; + } let response; let tries = 3; while (tries--) { @@ -873,7 +887,6 @@ class Benchmark { throw new Error("Fetch failed"); } const blob = await response.blob(); - const blobData = JetStream.blobDataCache[resource]; blobData.blob = blob; blobData.blobURL = URL.createObjectURL(blob); return blobData; diff --git a/cli.js b/cli.js index fac9f0f4..5dba1990 100644 --- a/cli.js +++ b/cli.js @@ -27,6 +27,7 @@ globalThis.prefetchResources = true; const isInBrowser = false; console = { log: globalThis?.console?.log ?? print, + warn: globalThis?.console?.warn ?? print, error: globalThis?.console?.error ?? print, } From 0842af58794af80b06be18d84a857e82a76da2da Mon Sep 17 00:00:00 2001 From: Camillo Bruni Date: Wed, 30 Apr 2025 09:57:09 +0200 Subject: [PATCH 03/11] cleanup --- cli.js | 22 +++++++++++----------- index.html | 1 - 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/cli.js b/cli.js index 5dba1990..a3473ecb 100644 --- a/cli.js +++ b/cli.js @@ -75,6 +75,17 @@ else load("./JetStreamDriver.js"); +async function runJetStream() { + try { + await JetStream.initialize(); + await JetStream.start(); + } catch (e) { + console.error("JetStream3 failed: " + e); + console.error(e.stack); + throw e; + } +} + if ("--help" in cliFlags) { print("JetStream Driver Help") print("") @@ -88,14 +99,3 @@ if ("--help" in cliFlags) { print("Running tests: " + testList) runJetStream(); } - -async function runJetStream() { - try { - await JetStream.initialize(); - await JetStream.start(); - } catch (e) { - console.error("JetStream3 failed: " + e); - console.error(e.stack); - throw e; - } -} diff --git a/index.html b/index.html index 27bc1ecb..703202dd 100644 --- a/index.html +++ b/index.html @@ -38,7 +38,6 @@ const isSpiderMonkey = false; globalThis.prefetchResources = true; globalThis.allIsGood = true; - window.onerror = function(e) { if (e == "Script error.") { // This is a workaround for Firefox on iOS which has an uncaught exception from From 6c92f778cbf639563b522fa570f5ff13c2adfd55 Mon Sep 17 00:00:00 2001 From: Camillo Bruni Date: Wed, 30 Jul 2025 16:24:55 +0200 Subject: [PATCH 04/11] cleanup --- JetStreamDriver.js | 60 +++++++--------------------------------------- cli.js | 14 ++--------- index.html | 1 - shell-config.js | 1 + 4 files changed, 11 insertions(+), 65 deletions(-) diff --git a/JetStreamDriver.js b/JetStreamDriver.js index 1b021778..37ec1458 100644 --- a/JetStreamDriver.js +++ b/JetStreamDriver.js @@ -78,7 +78,7 @@ if (typeof(URLSearchParams) !== "undefined") { globalThis.testList = getTestListParam(urlParameters, "test"); globalThis.testIterationCount = getIntParam(urlParameters, "iterationCount"); globalThis.testWorstCaseCount = getIntParam(urlParameters, "worstCaseCount"); - globalThis.prefetchResources = getBoolParam(urlParameters, "prefetchResources", true); + globalThis.prefetchResources = getBoolParam(urlParameters, "prefetchResources", globalThis.prefetchResources); } if (!globalThis.prefetchResources) @@ -189,7 +189,7 @@ function uiFriendlyDuration(time) { // TODO: Cleanup / remove / merge. This is only used for caching loads in the // non-browser setting. In the browser we use exclusively `loadCache`, // `loadBlob`, `doLoadBlob`, `prefetchResourcesForBrowser` etc., see below. -class FileLoader { +class ShellFileLoader { constructor() { this.requests = new Map; } @@ -198,6 +198,8 @@ class FileLoader { // share common code. load(url) { assert(!isInBrowser); + if (!globalThis.prefetchResources) + return `load("${url}");` if (this.requests.has(url)) { return this.requests.get(url); @@ -207,52 +209,9 @@ class FileLoader { this.requests.set(url, contents); return contents; } +}; - async _loadInternal(url) { - if (!isInBrowser) { - if (!globalThis.prefetchResources) - return Promise.resolve(`load("${url}");`); - return Promise.resolve(readFile(url)); - } - - if (!globalThis.prefetchResources) - return Promise.resolve(`"`); - - let response; - const tries = 3; - while (tries--) { - let hasError = false; - try { - response = await fetch(url); - } catch (e) { - hasError = true; - } - if (!hasError && response.ok) - break; - if (tries) - continue; - globalThis.allIsGood = false; - throw new Error("Fetch failed"); - } - if (url.indexOf(".js") !== -1) - return response.text(); - else if (url.indexOf(".wasm") !== -1) - return response.arrayBuffer(); - - throw new Error("should not be reached!"); - } - - async load(url) { - if (this.requests.has(url)) - return this.requests.get(url); - - const promise = this._loadInternal(url); - this.requests.set(url, promise); - return promise; - } -} - -const fileLoader = new FileLoader(); +const fileLoader = new ShellFileLoader(); class Driver { constructor() { @@ -835,10 +794,7 @@ class Benchmark { } else { const cache = JetStream.blobDataCache; for (const file of this.plan.files) { - if (globalThis.prefetchResources) - addScriptWithURL(cache[file].blobURL); - else - addScriptWithURL(file); + addScriptWithURL(globalThis.prefetchResources ? cache[file].blobURL : file); } } @@ -894,7 +850,7 @@ class Benchmark { async doLoadBlob(resource) { const blobData = JetStream.blobDataCache[resource]; if (!globalThis.prefetchResources) { - blobData.blobURL = resource; + blobData.blobURL = resource; return blobData; } let response; diff --git a/cli.js b/cli.js index 679f5940..a3279c4e 100644 --- a/cli.js +++ b/cli.js @@ -37,15 +37,5 @@ async function runJetStream() { } } -if ("--help" in cliFlags) { - print("JetStream Driver Help") - print("") - print("Options:") - print(" --no-prefetch: directly use load('...') for benchmark resources.") - print("") - print("Available tests:") - for (const test of testPlans) - print(" ", test.name) -} else { - runJetStream(); -} +// FIXME: Add flag for setting prefetch resources +runJetStream(); diff --git a/index.html b/index.html index 703202dd..98992b58 100644 --- a/index.html +++ b/index.html @@ -36,7 +36,6 @@ const isInBrowser = true; const isD8 = false; const isSpiderMonkey = false; - globalThis.prefetchResources = true; globalThis.allIsGood = true; window.onerror = function(e) { if (e == "Script error.") { diff --git a/shell-config.js b/shell-config.js index c38f4437..3f47eb85 100644 --- a/shell-config.js +++ b/shell-config.js @@ -27,6 +27,7 @@ const isInBrowser = false; console = { log: globalThis?.console?.log ?? print, error: globalThis?.console?.error ?? print, + warn: globalThis?.console?.warn ?? print, } const isD8 = typeof Realm !== "undefined"; From de526d9d1b8c9c0127857de8c55a48948ed96183 Mon Sep 17 00:00:00 2001 From: Camillo Bruni Date: Wed, 30 Jul 2025 16:26:03 +0200 Subject: [PATCH 05/11] cleanup --- JetStreamDriver.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JetStreamDriver.js b/JetStreamDriver.js index 37ec1458..0be0554d 100644 --- a/JetStreamDriver.js +++ b/JetStreamDriver.js @@ -82,7 +82,7 @@ if (typeof(URLSearchParams) !== "undefined") { } if (!globalThis.prefetchResources) - console.warn("Disabling resource prefetching!", globalThis.prefetchResources) + console.warn("Disabling resource prefetching!"); // Used for the promise representing the current benchmark run. this.currentResolve = null; From c57ba7c7c19e16635ab363857b38dba31efba322 Mon Sep 17 00:00:00 2001 From: Camillo Bruni Date: Tue, 5 Aug 2025 14:52:56 +0200 Subject: [PATCH 06/11] cleanup and add test --- package.json | 1 + tests/run-shell.mjs | 1 + tests/run.mjs | 10 +++++++--- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index efc89908..7760f4b9 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "test:firefox": "node tests/run.mjs --browser firefox", "test:safari": "node tests/run.mjs --browser safari", "test:edge": "node tests/run.mjs --browser edge", + "test:shell": "npm run test:v8 && npm run test:jsc && npm run test:spidermonkey", "test:v8": "node tests/run-shell.mjs --shell v8", "test:jsc": "node tests/run-shell.mjs --shell jsc", "test:spidermonkey": "node tests/run-shell.mjs --shell spidermonkey" diff --git a/tests/run-shell.mjs b/tests/run-shell.mjs index f31a646b..c2353c0a 100644 --- a/tests/run-shell.mjs +++ b/tests/run-shell.mjs @@ -84,6 +84,7 @@ async function runTests() { let success = true; success &&= await runTest("Run UnitTests", () => sh(shellBinary, UNIT_TEST_PATH)); success &&= await runCLITest("Run Single Suite", shellBinary, "proxy-mobx"); + success &&= await runCLITest("Run Tag No Prefetch", shellBinary, "proxy", "--no-prefetch"); success &&= await runCLITest("Run Disabled Suite", shellBinary, "disabled"); success &&= await runCLITest("Run Default Suite", shellBinary); if (!success) diff --git a/tests/run.mjs b/tests/run.mjs index 6ef609a9..27a71db6 100644 --- a/tests/run.mjs +++ b/tests/run.mjs @@ -60,9 +60,10 @@ const server = await serve(PORT); async function runTests() { let success = true; try { - success &&= await runTest("Run Single Suite", () => testEnd2End({ test: "proxy-mobx" })); - success &&= await runTest("Run Disabled Suite", () => testEnd2End({ tag: "disabled" })); - success &&= await runTest("Run Default Suite", () => testEnd2End()); + success &&= await runEnd2EndTest("Run Single Suite", { test: "proxy-mobx" }); + success &&= await runEnd2EndTest("Run Tag No Prefetch", { tag: "proxy", prefetchResources:"false"}); + success &&= await runEnd2EndTest("Run Disabled Suite", { tag: "disabled" }); + success &&= await runEnd2EndTest("Run Default Suite"); } finally { server.close(); } @@ -70,6 +71,9 @@ async function runTests() { process.exit(1); } +async function runEnd2EndTest(name, params) { + return runTest(name, () => testEnd2End(params)); +} async function testEnd2End(params) { const driver = await new Builder().withCapabilities(capabilities).build(); From fe070cc5ffc73aacf4eef54dfd23e2acb0e1fe0a Mon Sep 17 00:00:00 2001 From: Camillo Bruni Date: Tue, 5 Aug 2025 15:37:46 +0200 Subject: [PATCH 07/11] fix test and runner --- JetStreamDriver.js | 39 +++++++++++++++++++++++---------------- cli.js | 2 +- tests/run-shell.mjs | 43 +++++++++++++++++++++++++++++++++++++------ tests/run.mjs | 2 +- 4 files changed, 62 insertions(+), 24 deletions(-) diff --git a/JetStreamDriver.js b/JetStreamDriver.js index 932432e5..0ca7c68e 100644 --- a/JetStreamDriver.js +++ b/JetStreamDriver.js @@ -97,7 +97,7 @@ function displayCategoryScores() { let summaryElement = document.getElementById("result-summary"); for (let [category, scores] of categoryScores) - summaryElement.innerHTML += `

${category}: ${uiFriendlyScore(geomean(scores))}

` + summaryElement.innerHTML += `

${category}: ${uiFriendlyScore(geomeanScore(scores))}

` categoryScores = null; } @@ -147,12 +147,14 @@ function mean(values) { return sum / values.length; } -function geomean(values) { +function geomeanScore(values) { assert(values instanceof Array); let product = 1; for (let x of values) product *= x; - return product ** (1 / values.length); + const score = product ** (1 / values.length); + assert(score >= 0, `Got invalid score: ${score}`) + return score; } function toScore(timeValue) { @@ -211,7 +213,7 @@ class ShellFileLoader { } }; -const fileLoader = new ShellFileLoader(); +const shellFileLoader = new ShellFileLoader(); class Driver { constructor(benchmarks) { @@ -221,6 +223,7 @@ class Driver { // Make benchmark list unique and sort it. this.benchmarks = Array.from(new Set(benchmarks)); this.benchmarks.sort((a, b) => a.plan.name.toLowerCase() < b.plan.name.toLowerCase() ? 1 : -1); + assert(this.benchmarks.length, "No benchmarks selected"); // TODO: Cleanup / remove / merge `blobDataCache` and `loadCache` vs. // the global `fileLoader` cache. this.blobDataCache = { }; @@ -280,8 +283,10 @@ class Driver { } const allScores = []; - for (const benchmark of this.benchmarks) - allScores.push(benchmark.score); + for (const benchmark of this.benchmarks) { + const score = benchmark.score; + allScores.push(score); + } categoryScores = new Map; for (const benchmark of this.benchmarks) { @@ -292,23 +297,26 @@ class Driver { for (const benchmark of this.benchmarks) { for (let [category, value] of Object.entries(benchmark.subScores())) { const arr = categoryScores.get(category); + assert(value > 0, `Invalid ${benchmark.name} ${category}: ${value}`); arr.push(value); } } + const totalScore = geomeanScore(allScores); + if (isInBrowser) { - summaryElement.classList.add('done'); - summaryElement.innerHTML = `
${uiFriendlyScore(geomean(allScores))}
`; + summaryElement.classList.add("done"); + summaryElement.innerHTML = `
${uiFriendlyScore(totalScore)}
`; summaryElement.onclick = displayCategoryScores; if (showScoreDetails) displayCategoryScores(); - statusElement.innerHTML = ''; + statusElement.innerHTML = ""; } else if (!dumpJSONResults) { console.log("\n"); for (let [category, scores] of categoryScores) - console.log(`${category}: ${uiFriendlyScore(geomean(scores))}`); + console.log(`${category}: ${uiFriendlyScore(geomeanScore(scores))}`); - console.log("\nTotal Score: ", uiFriendlyScore(geomean(allScores)), "\n"); + console.log("\nTotal Score: ", uiFriendlyScore(totalScore), "\n"); } this.reportScoreToRunBenchmarkRunner(); @@ -653,7 +661,7 @@ class Benchmark { get score() { const subScores = Object.values(this.subScores()); - return geomean(subScores); + return geomeanScore(subScores); } subScores() { @@ -978,7 +986,7 @@ class Benchmark { assert(!isInBrowser); assert(this.scripts === null, "This initialization should be called only once."); - this.scripts = this.plan.files.map(file => fileLoader.load(file)); + this.scripts = this.plan.files.map(file => shellFileLoader.load(file)); assert(this.preloads === null, "This initialization should be called only once."); this.preloads = Object.entries(this.plan.preload ?? {}); @@ -2399,8 +2407,7 @@ for (const benchmark of BENCHMARKS) { } -function processTestList(testList) -{ +function processTestList(testList) { let benchmarkNames = []; let benchmarks = []; @@ -2412,7 +2419,7 @@ function processTestList(testList) for (let name of benchmarkNames) { name = name.toLowerCase(); if (benchmarksByTag.has(name)) - benchmarks.concat(findBenchmarksByTag(name)); + benchmarks = benchmarks.concat(findBenchmarksByTag(name)); else benchmarks.push(findBenchmarkByName(name)); } diff --git a/cli.js b/cli.js index b5c467f0..b670d21e 100644 --- a/cli.js +++ b/cli.js @@ -56,7 +56,7 @@ if (typeof runMode !== "undefined" && runMode == "RAMification") globalThis.RAMification = true; if ("--ramification" in cliFlags) globalThis.RAMification = true; -if ("--no-prefetch:" in cliFlags) +if ("--no-prefetch" in cliFlags) globalThis.prefetchResources = false; if (cliArgs.length) globalThis.testList = cliArgs; diff --git a/tests/run-shell.mjs b/tests/run-shell.mjs index c2353c0a..c9343860 100644 --- a/tests/run-shell.mjs +++ b/tests/run-shell.mjs @@ -1,7 +1,7 @@ #! /usr/bin/env node import commandLineArgs from "command-line-args"; -import { spawnSync } from "child_process"; +import { spawnSync, spawn } from "child_process"; import { fileURLToPath } from "url"; import { styleText } from "node:util"; import * as path from "path"; @@ -59,7 +59,7 @@ const SPAWN_OPTIONS = { stdio: ["inherit", "inherit", "inherit"] }; -function sh(binary, ...args) { +async function sh(binary, ...args) { const cmd = `${binary} ${args.join(" ")}`; if (GITHUB_ACTIONS_OUTPUT) { core.startGroup(binary); @@ -68,17 +68,42 @@ function sh(binary, ...args) { console.log(styleText("blue", cmd)); } try { - const result = spawnSync(binary, args, SPAWN_OPTIONS); + const result = await sp(binary, args, SPAWN_OPTIONS); if (result.status || result.error) { logError(result.error); throw new Error(`Shell CMD failed: ${binary} ${args.join(" ")}`); } + return result; } finally { if (GITHUB_ACTIONS_OUTPUT) core.endGroup(); } } +async function sp(binary, args) { + const childProcess = spawn(binary, args); + childProcess.stdout.pipe(process.stdout); + return new Promise((resolve, reject) => { + childProcess.stdoutString = ""; + childProcess.stdio[1].on("data", (data) => { + childProcess.stdoutString += data.toString(); + }); + childProcess.on('close', (code) => { + if (code === 0) { + resolve(childProcess); + } else { + // Reject the Promise with an Error on failure + const error = new Error(`Command failed with exit code ${code}: ${binary} ${args.join(" ")}`); + error.process = childProcess; + error.stdout = childProcess.stdoutString; + error.exitCode = code; + reject(error); + } + }); + childProcess.on('error', reject); + }) +} + async function runTests() { const shellBinary = await logGroup(`Installing JavaScript Shell: ${SHELL_NAME}`, testSetup); let success = true; @@ -112,8 +137,8 @@ function jsvuOSName() { const DEFAULT_JSC_LOCATION = "/System/Library/Frameworks/JavaScriptCore.framework/Versions/Current/Helpers/jsc" -function testSetup() { - sh("jsvu", `--engines=${SHELL_NAME}`, `--os=${jsvuOSName()}`); +async function testSetup() { + await sh("jsvu", `--engines=${SHELL_NAME}`, `--os=${jsvuOSName()}`); let shellBinary = path.join(os.homedir(), ".jsvu/bin", SHELL_NAME); if (!fs.existsSync(shellBinary) && SHELL_NAME == "javascriptcore") shellBinary = DEFAULT_JSC_LOCATION; @@ -124,7 +149,13 @@ function testSetup() { } function runCLITest(name, shellBinary, ...args) { - return runTest(name, () => sh(shellBinary, ...convertCliArgs(CLI_PATH, ...args))); + return runTest(name, () => runShell(shellBinary, ...convertCliArgs(CLI_PATH, ...args))); +} + +async function runShell(shellBinary, ...args) { + const result = await sh(shellBinary, ...args); + if (result.stdoutString.includes("JetStream3 failed")) + throw new Error("test failed") } setImmediate(runTests); diff --git a/tests/run.mjs b/tests/run.mjs index 6bdfcf18..0b9f5959 100644 --- a/tests/run.mjs +++ b/tests/run.mjs @@ -61,7 +61,7 @@ async function runTests() { let success = true; try { success &&= await runEnd2EndTest("Run Single Suite", { test: "proxy-mobx" }); - success &&= await runEnd2EndTest("Run Tag No Prefetch", { tag: "proxy", prefetchResources:"false"}); + success &&= await runEnd2EndTest("Run Tag No Prefetch", { tag: "proxy", prefetchResources: "false" }); success &&= await runEnd2EndTest("Run Disabled Suite", { tag: "disabled" }); success &&= await runEnd2EndTest("Run Default Suite"); } finally { From 0a1a7de40b7e964b95459b9d544f2c3070a2460b Mon Sep 17 00:00:00 2001 From: Camillo Bruni Date: Tue, 5 Aug 2025 15:40:40 +0200 Subject: [PATCH 08/11] improve assertions --- JetStreamDriver.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/JetStreamDriver.js b/JetStreamDriver.js index 0ca7c68e..8685eaa8 100644 --- a/JetStreamDriver.js +++ b/JetStreamDriver.js @@ -153,6 +153,7 @@ function geomeanScore(values) { for (let x of values) product *= x; const score = product ** (1 / values.length); + // Allow 0 for uninitialized subScores(). assert(score >= 0, `Got invalid score: ${score}`) return score; } @@ -303,6 +304,7 @@ class Driver { } const totalScore = geomeanScore(allScores); + assert(totalScore > 0, `Invalid total score: ${totalScore}`); if (isInBrowser) { summaryElement.classList.add("done"); From 14c75440600dad798962fbf136b815a7e704db23 Mon Sep 17 00:00:00 2001 From: Camillo Bruni Date: Tue, 5 Aug 2025 15:49:10 +0200 Subject: [PATCH 09/11] remove unused spawnSync import --- tests/run-shell.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/run-shell.mjs b/tests/run-shell.mjs index c9343860..1c732755 100644 --- a/tests/run-shell.mjs +++ b/tests/run-shell.mjs @@ -1,7 +1,7 @@ #! /usr/bin/env node import commandLineArgs from "command-line-args"; -import { spawnSync, spawn } from "child_process"; +import { spawn } from "child_process"; import { fileURLToPath } from "url"; import { styleText } from "node:util"; import * as path from "path"; From d9236738ed82a61394a4e506890a67b87091d16e Mon Sep 17 00:00:00 2001 From: Camillo Bruni Date: Tue, 5 Aug 2025 15:50:05 +0200 Subject: [PATCH 10/11] rename --- tests/run-shell.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/run-shell.mjs b/tests/run-shell.mjs index 1c732755..1b626a7d 100644 --- a/tests/run-shell.mjs +++ b/tests/run-shell.mjs @@ -68,7 +68,7 @@ async function sh(binary, ...args) { console.log(styleText("blue", cmd)); } try { - const result = await sp(binary, args, SPAWN_OPTIONS); + const result = await spawnCaptureStdout(binary, args, SPAWN_OPTIONS); if (result.status || result.error) { logError(result.error); throw new Error(`Shell CMD failed: ${binary} ${args.join(" ")}`); @@ -80,7 +80,7 @@ async function sh(binary, ...args) { } } -async function sp(binary, args) { +async function spawnCaptureStdout(binary, args) { const childProcess = spawn(binary, args); childProcess.stdout.pipe(process.stdout); return new Promise((resolve, reject) => { From c82202ae282e7d1ec4b6853f84c4656f916ea2c7 Mon Sep 17 00:00:00 2001 From: Camillo Bruni Date: Wed, 6 Aug 2025 11:26:57 +0200 Subject: [PATCH 11/11] re-add assertion --- JetStreamDriver.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/JetStreamDriver.js b/JetStreamDriver.js index b6aa00ae..76cf9861 100644 --- a/JetStreamDriver.js +++ b/JetStreamDriver.js @@ -286,6 +286,7 @@ class Driver { const allScores = []; for (const benchmark of this.benchmarks) { const score = benchmark.score; + assert(score > 0, `Invalid ${benchmark.name} score: ${score}`); allScores.push(score); } @@ -298,7 +299,7 @@ class Driver { for (const benchmark of this.benchmarks) { for (let [category, value] of Object.entries(benchmark.subScores())) { const arr = categoryScores.get(category); - assert(value > 0, `Invalid ${benchmark.name} ${category}: ${value}`); + assert(value > 0, `Invalid ${benchmark.name} ${category} score: ${value}`); arr.push(value); } }