blob: 7944dce04b7d5cac152f0e846db3a4a2127c4fdb [file] [log] [blame] [edit]
#! /usr/bin/env node
// 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.
import commandLineArgs from "command-line-args";
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import { fileURLToPath } from "url";
import { logGroup, logInfo, printHelp, runTest, sh } from "./helper.mjs";
const FILE_PATH = fileURLToPath(import.meta.url);
const SRC_DIR = path.dirname(path.dirname(FILE_PATH));
const CLI_PATH = path.join(SRC_DIR, "cli.js");
const UNIT_TEST_PATH = path.join(SRC_DIR, "tests", "unit-tests.js");
const TESTS = [
{
name: "UnitTests",
tags: ["all", "main", "unit"],
run(shell_binary) {
return runTest("UnitTests", () => sh(shell_binary, UNIT_TEST_PATH));
},
},
{
name: "Single Suite",
tags: ["all", "main", "single"],
run(shell_binary) {
return runCLITest("Single Suite", shell_binary, "proxy-mobx");
},
},
{
name: "Tag No Prefetch",
tags: ["all", "main", "no-prefetch"],
run(shell_binary) {
return runCLITest(
"Tag No Prefetch",
shell_binary,
"proxy",
"argon2-wasm",
"--no-prefetch"
);
},
},
{
name: "Grouped with Details",
tags: ["all", "main", "group-details"],
run(shell_binary) {
return runCLITest("Grouped with Details", shell_binary, "SunSpider", "--group-details");
},
},
{
name: "Disabled Suite",
tags: ["all", "disabled"],
run(shell_binary) {
return runCLITest("Disabled Suite", shell_binary, "disabled");
},
},
{
name: "Default Suite",
tags: ["all", "default"],
run(shell_binary) {
return runCLITest("Default Suite", shell_binary);
},
},
];
const VALID_TAGS = Array.from(new Set(TESTS.map((each) => each.tags).flat()));
const optionDefinitions = [
{
name: "shell",
type: String,
description: "Set the shell to test, choices are [jsc, v8, spidermonkey].",
},
{ name: "help", alias: "h", description: "Print this help text." },
{
name: "suite",
type: String,
defaultOption: true,
typeLabel: `choices: ${VALID_TAGS.join(", ")}`,
description: "Run a specific suite by name.",
},
];
const options = commandLineArgs(optionDefinitions);
if ("help" in options) {
printHelp("", optionDefinitions);
}
if (options.suite && !VALID_TAGS.includes(options.suite)) {
printHelp(
`Invalid suite: ${options.suite}. Choices are: ${VALID_TAGS.join(", ")}`,
optionDefinitions
);
}
const JS_SHELL = options?.shell;
if (!JS_SHELL) {
printHelp("No javascript shell specified, use --shell", optionDefinitions);
}
const SHELL_NAME = (function () {
switch (JS_SHELL) {
case "javascriptcore":
case "jsc": {
return "javascriptcore";
}
case "spidermonkey": {
return "spidermonkey";
}
case "v8": {
return "v8";
}
default: {
printHelp(
`Invalid shell "${JS_SHELL}", choices are: "jsc", "spidermonkey" and "v8)`,
optionDefinitions
);
}
}
})();
function convertCliArgs(cli, ...cliArgs) {
if (SHELL_NAME == "spidermonkey") return [cli, ...cliArgs];
return [cli, "--", ...cliArgs];
}
async function runTests() {
const shell_binary = await logGroup(`Installing JavaScript Shell: ${SHELL_NAME}`, testSetup);
const suiteFilter = options.suite || "all";
let success = true;
const testsToRun = TESTS.filter((test) => test.tags.includes(suiteFilter));
if (testsToRun.length === 0) {
console.error(`No suite found for filter: ${suiteFilter}`);
process.exit(1);
}
for (const test of testsToRun) {
success &&= await test.run(shell_binary);
}
if (!success) {
process.exit(1);
}
}
function jsvuOSName() {
const osName = () => {
switch (os.platform()) {
case "win32":
return "win";
case "darwin":
return "mac";
case "linux":
return "linux";
default:
throw new Error("Unsupported OS");
}
};
const osArch = () => {
switch (os.arch()) {
case "x64":
return "64";
case "arm64":
return "64arm";
default:
throw new Error("Unsupported architecture");
}
};
return `${osName()}${osArch()}`;
}
const DEFAULT_JSC_LOCATION =
"/System/Library/Frameworks/JavaScriptCore.framework/Versions/Current/Helpers/jsc";
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;
if (!fs.existsSync(shellBinary)) throw new Error(`Could not find shell binary: ${shellBinary}`);
logInfo(`Installed JavaScript Shell: ${shellBinary}`);
return shellBinary;
}
function runCLITest(name, shellBinary, ...args) {
return runTest(name, () => runShell(shellBinary, ...convertCliArgs(CLI_PATH, ...args)));
}
async function runShell(shellBinary, ...args) {
const result = await sh(shellBinary, ...args);
// JSC does not set a non-0 exit status on async exceptions.
if (SHELL_NAME == "javascriptcore" && result.stdoutString.includes("JetStream3 failed")) {
throw new Error("test failed");
}
}
setImmediate(runTests);