All important runner changes have landed upstream. Bug: 358290763 Change-Id: Ib47f6838618e9c0081f60f3b60d52b1ce149b2af Reviewed-on: https://chromium-review.googlesource.com/c/external/github.com/WebKit/JetStream/+/7094856 Reviewed-by: Marja Hölttä <marja@chromium.org>
344 lines
11 KiB
JavaScript
344 lines
11 KiB
JavaScript
// Copyright (C) 2007-2025 Apple Inc. All rights reserved.
|
|
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions
|
|
// are met:
|
|
// 1. Redistributions of source code must retain the above copyright
|
|
// notice, this list of conditions and the following disclaimer.
|
|
// 2. Redistributions in binary form must reproduce the above copyright
|
|
// notice, this list of conditions and the following disclaimer in the
|
|
// documentation and/or other materials provided with the distribution.
|
|
|
|
// THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
|
|
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
|
|
// BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
|
// THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
load("utils/shell-config.js");
|
|
load("utils/params.js");
|
|
load("utils/StartupBenchmark.js");
|
|
load("JetStreamDriver.js");
|
|
|
|
function assertTrue(condition, message) {
|
|
if (!condition) {
|
|
throw new Error(message || "Assertion failed");
|
|
}
|
|
}
|
|
|
|
function assertFalse(condition, message) {
|
|
if (condition) {
|
|
throw new Error(message || "Assertion failed");
|
|
}
|
|
}
|
|
|
|
function assertEquals(actual, expected, message) {
|
|
if (actual !== expected) {
|
|
throw new Error(message || `Expected ${expected}, but got ${actual}`);
|
|
}
|
|
}
|
|
|
|
function assertThrows(message, func) {
|
|
let didThrow = false;
|
|
try {
|
|
func();
|
|
} catch (e) {
|
|
didThrow = true;
|
|
}
|
|
assertTrue(didThrow, `Test did not throw: ${message}`);
|
|
}
|
|
|
|
(function testTagsAreLowerCaseStrings() {
|
|
for (const benchmark of BENCHMARKS) {
|
|
benchmark.tags.forEach((tag) => {
|
|
assertTrue(typeof tag == "string");
|
|
assertTrue(tag == tag.toLowerCase());
|
|
});
|
|
}
|
|
})();
|
|
|
|
(function testTagsAll() {
|
|
for (const benchmark of BENCHMARKS) {
|
|
const tags = benchmark.tags;
|
|
assertTrue(tags instanceof Set);
|
|
assertTrue(tags.size > 0);
|
|
assertTrue(tags.has("all"));
|
|
assertFalse(tags.has("All"));
|
|
assertTrue(tags.has("default") ^ tags.has("disabled"));
|
|
}
|
|
})();
|
|
|
|
|
|
(function tagsAreNotBenchmarkNames() {
|
|
const benchmarkNames = new Set(BENCHMARKS.map(e => e.name));
|
|
for (const benchmark of benchmarks) {
|
|
for (const tag of benchmark.tags) {
|
|
assertFalse(benchmarkNames.has(tag), `'${tag}' is also a benchmark name`);
|
|
}
|
|
}
|
|
})();
|
|
|
|
(function tagsHasWasmOrJS() {
|
|
for (const benchmark of benchmarks) {
|
|
const tags = benchmark.tags;
|
|
assertTrue(tags.has("wasm") || tags.has("js"), `'${benchmark.name}' has no 'js' or 'wasm' tag`);
|
|
}
|
|
})();
|
|
|
|
(function testDriverBenchmarksOrder() {
|
|
const benchmarks = findBenchmarksByTag("all");
|
|
const driver = new Driver(benchmarks);
|
|
assertEquals(driver.benchmarks.length, BENCHMARKS.length);
|
|
const names = driver.benchmarks
|
|
.map((b) => b.name.toLowerCase())
|
|
.sort()
|
|
.reverse();
|
|
for (let i = 0; i < names.length; i++) {
|
|
assertEquals(driver.benchmarks[i].name.toLowerCase(), names[i]);
|
|
}
|
|
})();
|
|
|
|
(function testEnableByTag() {
|
|
const driverA = new Driver(findBenchmarksByTag("Default"));
|
|
const driverB = new Driver(findBenchmarksByTag("default"));
|
|
assertTrue(driverA.benchmarks.length > 0);
|
|
assertEquals(driverA.benchmarks.length, driverB.benchmarks.length);
|
|
const enabledBenchmarkNames = new Set(
|
|
Array.from(driverA.benchmarks).map((b) => b.name)
|
|
);
|
|
for (const benchmark of BENCHMARKS) {
|
|
if (benchmark.tags.has("default"))
|
|
assertTrue(enabledBenchmarkNames.has(benchmark.name));
|
|
}
|
|
})();
|
|
|
|
(function testDriverEnableDuplicateAndSort() {
|
|
const benchmarks = [
|
|
...findBenchmarksByTag("wasm"),
|
|
...findBenchmarksByTag("wasm"),
|
|
];
|
|
assertTrue(benchmarks.length > 0);
|
|
const uniqueBenchmarks = new Set(benchmarks);
|
|
assertFalse(uniqueBenchmarks.size == benchmarks.length);
|
|
const driver = new Driver(benchmarks);
|
|
assertEquals(driver.benchmarks.length, uniqueBenchmarks.size);
|
|
})();
|
|
|
|
(function testBenchmarkSubScores() {
|
|
for (const benchmark of BENCHMARKS) {
|
|
const subScores = benchmark.subScores();
|
|
assertTrue(subScores instanceof Object);
|
|
assertTrue(Object.keys(subScores).length > 0);
|
|
for (const [name, value] of Object.entries(subScores)) {
|
|
assertTrue(typeof name == "string");
|
|
// "Score" can only be part of allScores().
|
|
assertFalse(name == "Score");
|
|
// Without running values should be either null (or 0 for GroupedBenchmark)
|
|
assertFalse(value);
|
|
}
|
|
}
|
|
})();
|
|
|
|
(function testBenchmarkAllScores() {
|
|
for (const benchmark of BENCHMARKS) {
|
|
const subScores = benchmark.subScores();
|
|
const allScores = benchmark.allScores();
|
|
assertTrue("Score" in allScores);
|
|
// All subScore items are part of allScores.
|
|
for (const name of Object.keys(subScores)) assertTrue(name in allScores);
|
|
}
|
|
})();
|
|
|
|
(function checkUtf16Sources() {
|
|
// Test that only explicitly UTF16-enabled benchmarks can have sources
|
|
// with non-8-byte characters.
|
|
const twoByteCharsRegex = /[^\x00-\xFF]/g;
|
|
const jsFileRegex = /\.(js|mjs)$/;
|
|
|
|
function checkFile(benchmarkName, file, type) {
|
|
if (!jsFileRegex.test(file))
|
|
return;
|
|
const content = read(file);
|
|
const match = content.match(twoByteCharsRegex);
|
|
if (!match)
|
|
return;
|
|
const uniqueMatches = Array.from(new Set(match));
|
|
const offendingChars = uniqueMatches.map(char => {
|
|
const hex = char.charCodeAt(0).toString(16).padStart(4, "0");
|
|
return `\n - \\u${hex}: '${char}'`;
|
|
}).join("");
|
|
throw new Error(
|
|
`Benchmark '${benchmarkName}' has two-byte characters in ${type} '${file}':\n` +
|
|
` Offending characters: ${offendingChars}`);
|
|
}
|
|
|
|
for (const benchmark of BENCHMARKS) {
|
|
if (benchmark.allowUtf16)
|
|
continue;
|
|
|
|
for (const file of benchmark.files) {
|
|
checkFile(benchmark.name, file, "file");
|
|
}
|
|
|
|
if (benchmark.plan.preload) {
|
|
for (const [name, file] of Object.entries(benchmark.plan.preload)) {
|
|
checkFile(benchmark.name, file, `preload.${name}`);
|
|
}
|
|
}
|
|
}
|
|
})();
|
|
|
|
function validateIterationSources(sources) {
|
|
for (const source of sources) {
|
|
assertTrue(typeof source == "string");
|
|
assertFalse(source.includes(CACHE_BUST_COMMENT));
|
|
}
|
|
}
|
|
|
|
(async function testStartupBenchmark() {
|
|
try {
|
|
JetStream.preload = { BUNDLE: "test-bundle.js" };
|
|
JetStream.getString = (file) => {
|
|
assertEquals(file, "test-bundle.js");
|
|
return `function test() {
|
|
${CACHE_BUST_COMMENT}
|
|
return 1;
|
|
}`;
|
|
};
|
|
await testStartupBenchmarkInnerTests();
|
|
} finally {
|
|
JetStream.preload = undefined;
|
|
JetStream.getString = undefined;
|
|
}
|
|
})();
|
|
|
|
async function testStartupBenchmarkInnerTests() {
|
|
const benchmark = new StartupBenchmark({
|
|
iterationCount: 12,
|
|
expectedCacheCommentCount: 1,
|
|
});
|
|
assertEquals(benchmark.iterationCount, 12);
|
|
assertEquals(benchmark.expectedCacheCommentCount, 1);
|
|
assertEquals(benchmark.iterationSourceCodes.length, 0);
|
|
assertEquals(benchmark.sourceCode, undefined);
|
|
assertEquals(benchmark.sourceHash, 0);
|
|
await benchmark.init();
|
|
assertEquals(benchmark.sourceHash, 177573);
|
|
assertEquals(benchmark.sourceCode.length, 68);
|
|
assertEquals(benchmark.iterationSourceCodes.length, 12);
|
|
assertEquals(new Set(benchmark.iterationSourceCodes).size, 12);
|
|
validateIterationSources(benchmark.iterationSourceCodes);
|
|
|
|
const noReuseBenchmark = new StartupBenchmark({
|
|
iterationCount: 12,
|
|
expectedCacheCommentCount: 1,
|
|
sourceCodeReuseCount: 0,
|
|
});
|
|
assertEquals(noReuseBenchmark.iterationSourceCodes.length, 0);
|
|
await noReuseBenchmark.init();
|
|
assertEquals(noReuseBenchmark.iterationSourceCodes.length, 12);
|
|
assertEquals(new Set(noReuseBenchmark.iterationSourceCodes).size, 1);
|
|
validateIterationSources(noReuseBenchmark.iterationSourceCodes);
|
|
|
|
const reuseBenchmark = new StartupBenchmark({
|
|
iterationCount: 12,
|
|
expectedCacheCommentCount: 1,
|
|
sourceCodeReuseCount: 3,
|
|
});
|
|
assertEquals(reuseBenchmark.iterationSourceCodes.length, 0);
|
|
await reuseBenchmark.init();
|
|
assertEquals(reuseBenchmark.iterationSourceCodes.length, 12);
|
|
assertEquals(new Set(reuseBenchmark.iterationSourceCodes).size, 4);
|
|
validateIterationSources(reuseBenchmark.iterationSourceCodes);
|
|
|
|
const reuseBenchmark2 = new StartupBenchmark({
|
|
iterationCount: 12,
|
|
expectedCacheCommentCount: 1,
|
|
sourceCodeReuseCount: 5,
|
|
});
|
|
assertEquals(reuseBenchmark2.iterationSourceCodes.length, 0);
|
|
await reuseBenchmark2.init();
|
|
assertEquals(reuseBenchmark2.iterationSourceCodes.length, 12);
|
|
assertEquals(new Set(reuseBenchmark2.iterationSourceCodes).size, 3);
|
|
validateIterationSources(reuseBenchmark2.iterationSourceCodes);
|
|
}
|
|
|
|
(function testStartupBenchmarkThrow() {
|
|
assertThrows(
|
|
"StartupBenchmark constructor should throw with no arguments.",
|
|
() => new StartupBenchmark()
|
|
);
|
|
|
|
assertThrows(
|
|
"StartupBenchmark constructor should throw with missing expectedCacheCommentCount.",
|
|
() => new StartupBenchmark({ iterationCount: 1 })
|
|
);
|
|
|
|
assertThrows(
|
|
"StartupBenchmark constructor should throw with missing iterationCount.",
|
|
() => new StartupBenchmark({ expectedCacheCommentCount: 1 })
|
|
);
|
|
|
|
assertThrows(
|
|
"StartupBenchmark constructor should throw with iterationCount=0.",
|
|
() => {
|
|
new StartupBenchmark({ iterationCount: 0, expectedCacheCommentCount: 1 });
|
|
}
|
|
);
|
|
|
|
assertThrows(
|
|
"StartupBenchmark constructor should throw with expectedCacheCommentCount=0.",
|
|
() => {
|
|
new StartupBenchmark({ iterationCount: 1, expectedCacheCommentCount: 0 });
|
|
}
|
|
);
|
|
})();
|
|
|
|
(function testDefaultParams() {
|
|
assertTrue(DefaultJetStreamParams.isDefault);
|
|
assertTrue(Object.isFrozen(DefaultJetStreamParams));
|
|
assertEquals(Object.entries(DefaultJetStreamParams.nonDefaultParams).length, 0);
|
|
})();
|
|
|
|
(function testCustomParamsSingle() {
|
|
const sourceParams = new Map(Object.entries({ developerMode: "true" }));
|
|
const params = new Params(sourceParams);
|
|
assertFalse(params.isDefault);
|
|
const nonDefaults = params.nonDefaultParams;
|
|
assertEquals(Object.entries(nonDefaults).length, 1);
|
|
assertEquals(nonDefaults.developerMode, true);
|
|
})();
|
|
|
|
|
|
(function testCustomParamsMultiple() {
|
|
const sourceParams = new Map(Object.entries({ iterationCount: 123, test: "wasm,js"}));
|
|
const params = new Params(sourceParams);
|
|
assertFalse(params.isDefault);
|
|
const nonDefaults = params.nonDefaultParams;
|
|
assertEquals(Object.entries(nonDefaults).length, 2);
|
|
assertEquals(nonDefaults.testIterationCount, 123);
|
|
assertEquals(JSON.stringify(nonDefaults.testList), JSON.stringify(["wasm", "js"]));
|
|
})();
|
|
|
|
|
|
(function testParseIterationCount() {
|
|
assertThrows("Cannot parse negative iterationCounts",
|
|
() => {
|
|
const sourceParams = new Map(Object.entries({ iterationCount: -123, }));
|
|
new Params(sourceParams);
|
|
});
|
|
assertThrows("Cannot parse multiple iterationCounts",
|
|
() => {
|
|
const sourceParams = new Map(Object.entries({ iterationCount: 123, testIterationCount: 10 }));
|
|
new Params(sourceParams);
|
|
});
|
|
let sourceParams = new Map(Object.entries({ iterationCount: 123 }));
|
|
let params = new Params(sourceParams);
|
|
assertEquals(params.testIterationCount, 123);
|
|
})();
|