Files
bitburner-src/test/jest/Netscript/RunScript.test.ts
David Walker 8f4313b180 Revert "PIPE: Add pipe support for passing data into and out of terminal commands (#2395)" (#2524)
This reverts commit 92b8b58588.

Accidental merge on my part - the code is in decent shape, but isn't meant to go in for 3.0.
2026-02-22 11:28:10 -08:00

306 lines
10 KiB
TypeScript

import type { ScriptFilePath } from "../../../src/Paths/ScriptFilePath";
import { runScriptFromScript, startWorkerScript } from "../../../src/NetscriptWorker";
import { workerScripts } from "../../../src/Netscript/WorkerScripts";
import { RunningScript } from "../../../src/Script/RunningScript";
import { GetServerOrThrow } from "../../../src/Server/AllServers";
import { AlertEvents } from "../../../src/ui/React/AlertManager";
import { fixDoImportIssue, initGameEnvironment, setupBasicTestingEnvironment } from "../Utilities";
import { Terminal } from "../../../src/Terminal";
import { runScript } from "../../../src/Terminal/commands/runScript";
import { Player } from "@player";
import { resetPidCounter } from "../../../src/Netscript/Pid";
import { SpecialServers } from "../../../src/Server/data/SpecialServers";
import { WorkerScript } from "../../../src/Netscript/WorkerScript";
import { NetscriptFunctions } from "../../../src/NetscriptFunctions";
import type { PositiveInteger } from "../../../src/types";
import { ErrorState } from "../../../src/ErrorHandling/ErrorState";
fixDoImportIssue();
initGameEnvironment();
const testScriptPath = "test.js" as ScriptFilePath;
const parentTestScriptPath = "parent_script.js" as ScriptFilePath;
const runOptions = {
threads: 1 as PositiveInteger,
temporary: false,
preventDuplicates: false,
};
async function expectErrorWhenRunningScript(
scripts: { filePath: ScriptFilePath; code: string }[],
testScriptPath: ScriptFilePath,
errorShown: Promise<unknown>,
errorMessage: string,
): Promise<void> {
for (const script of scripts) {
Player.getHomeComputer().writeToScriptFile(script.filePath, script.code);
}
runScript(testScriptPath, [], Player.getHomeComputer());
const workerScript = workerScripts.get(1);
if (!workerScript) {
throw new Error(`Invalid worker script`);
}
/**
* Suppress console.error(). When there is a thrown error in the player's script, we print it to the console. In
* this test, we intentionally throw an error, so we can ignore it.
*/
const consoleError = jest.spyOn(console, "error").mockImplementation(jest.fn());
const result = await Promise.race([
errorShown,
new Promise<void>((resolve) => (workerScript.atExit = new Map([["default", resolve]]))),
]);
consoleError.mockRestore();
expect(result).toBeDefined();
expect(workerScript.scriptRef.logs[0]).toContain(errorMessage);
}
let alertEventCleanUpFunction: () => void;
let alerted: Promise<unknown>;
let errorPopUpEventCleanUpFunction: () => void;
let errorShown: Promise<unknown>;
beforeEach(() => {
setupBasicTestingEnvironment();
Terminal.clear();
resetPidCounter();
alerted = new Promise((resolve) => {
alertEventCleanUpFunction = AlertEvents.subscribe((x) => resolve(x));
});
errorShown = new Promise((resolve) => {
errorPopUpEventCleanUpFunction = ErrorState.ErrorUpdate.subscribe((x) => resolve(x));
});
});
afterEach(() => {
alertEventCleanUpFunction();
errorPopUpEventCleanUpFunction();
ErrorState.ActiveError = null;
ErrorState.Errors.length = 0;
ErrorState.UnreadErrors = 0;
});
test("Netscript execution", async () => {
const scripts = [
{
name: "import.js",
code: `
export function getInfo(ns) {
return ns.stock.has4SData();
}
`,
},
{
name: "simple_test.js",
code: `
import { getInfo } from "./import.js";
export async function main(ns) {
var access = getInfo(ns);
var server = ns.getServer();
ns.printf("%s %s %d", access, server.hostname, server.maxRam);
}
`,
},
];
const server = Player.getHomeComputer();
for (const script of scripts) {
expect(server.writeToScriptFile(script.name as ScriptFilePath, script.code)).toEqual({ overwritten: false });
}
const script = server.scripts.get(scripts[scripts.length - 1].name as ScriptFilePath);
if (!script) {
throw new Error("Invalid script");
}
expect(script.filename).toEqual(scripts[scripts.length - 1].name);
const ramUsage = script.getRamUsage(server.scripts);
if (!ramUsage) {
throw new Error(`ramUsage calculated to be ${ramUsage}`);
}
const runningScript = new RunningScript(script, ramUsage);
const pid = startWorkerScript(runningScript, server);
expect(pid).toBeGreaterThan(0);
const workerScript = workerScripts.get(pid);
if (!workerScript) {
throw new Error(`Invalid worker script`);
}
const result = await Promise.race([
alerted,
new Promise<void>((resolve) => (workerScript.atExit = new Map([["default", resolve]]))),
]);
expect(result).not.toBeDefined();
expect(runningScript.logs).toStrictEqual(["false home 8", "Script finished running"]);
});
describe("runScript and runScriptFromScript", () => {
describe("runScript", () => {
describe("Success", () => {
test("Normal", async () => {
Player.getHomeComputer().writeToScriptFile(
testScriptPath,
`export async function main(ns) {
const server = ns.getServer("home");
ns.print(server.hostname);
}`,
);
runScript(testScriptPath, [], Player.getHomeComputer());
const workerScript = workerScripts.get(1);
if (!workerScript) {
throw new Error(`Invalid worker script`);
}
const result = await Promise.race([
alerted,
new Promise<void>((resolve) => (workerScript.atExit = new Map([["default", resolve]]))),
]);
expect(result).not.toBeDefined();
expect(workerScript.scriptRef.logs[0]).toStrictEqual(SpecialServers.Home);
});
});
describe("Failure", () => {
test("Script does not exist", () => {
runScript(testScriptPath, [], Player.getHomeComputer());
expect((Terminal.outputHistory[1] as { text: string }).text).toContain(
`Script ${testScriptPath} does not exist on home`,
);
});
test("No root access", () => {
const server = GetServerOrThrow("n00dles");
server.writeToScriptFile(
testScriptPath,
`export async function main(ns) {
}`,
);
runScript(testScriptPath, [], server);
expect((Terminal.outputHistory[1] as { text: string }).text).toContain(
`You do not have root access on ${server.hostname}`,
);
});
test("Cannot calculate RAM", () => {
Player.getHomeComputer().writeToScriptFile(
testScriptPath,
`export async function main(ns) {
{
}`,
);
runScript(testScriptPath, [], Player.getHomeComputer());
expect((Terminal.outputHistory[1] as { text: string }).text).toContain(
`Cannot calculate RAM usage of ${testScriptPath}`,
);
});
test("Not enough RAM", () => {
Player.getHomeComputer().writeToScriptFile(
testScriptPath,
`export async function main(ns) {
ns.ramOverride(1024);
}`,
);
runScript(testScriptPath, [], Player.getHomeComputer());
expect((Terminal.outputHistory[1] as { text: string }).text).toContain("This script requires 1.02TB of RAM");
});
test("Thrown error in main function", async () => {
const errorMessage = `Test error ${Date.now()}`;
await expectErrorWhenRunningScript(
[
{
filePath: testScriptPath,
code: `export async function main(ns) {
throw new Error("${errorMessage}");
}`,
},
],
testScriptPath,
errorShown,
errorMessage,
);
});
test("Circular dependencies: Import itself", async () => {
await expectErrorWhenRunningScript(
[
{
filePath: testScriptPath,
code: `import * as test from "./test";
export async function main(ns) {
}`,
},
],
testScriptPath,
errorShown,
"Circular dependencies detected",
);
});
test("Circular dependencies: Circular import", async () => {
await expectErrorWhenRunningScript(
[
{
filePath: testScriptPath,
code: `import { libValue } from "./lib";
export const testValue = 1;
export async function main(ns) {
}`,
},
{
filePath: "lib.js" as ScriptFilePath,
code: `import { testValue } from "./test";
export const libValue = testValue;`,
},
],
testScriptPath,
errorShown,
"Circular dependencies detected",
);
});
});
});
describe("runScriptFromScript", () => {
let parentWorkerScript: WorkerScript;
beforeEach(() => {
// Set up parentWorkerScript for passing to runScriptFromScript.
const home = GetServerOrThrow(SpecialServers.Home);
home.writeToScriptFile(parentTestScriptPath, "");
const script = home.scripts.get(parentTestScriptPath);
if (!script) {
throw new Error("Invalid script");
}
const runningScript = new RunningScript(script, 4);
parentWorkerScript = new WorkerScript(runningScript, 1, NetscriptFunctions);
home.runScript(runningScript);
});
describe("Success", () => {
test("Normal", async () => {
Player.getHomeComputer().writeToScriptFile(
testScriptPath,
`export async function main(ns) {
const server = ns.getServer("home");
ns.print(server.hostname);
}`,
);
runScriptFromScript("run", Player.getHomeComputer(), testScriptPath, [], parentWorkerScript, runOptions);
const workerScript = workerScripts.get(1);
if (!workerScript) {
throw new Error(`Invalid worker script`);
}
const result = await Promise.race([
alerted,
new Promise<void>((resolve) => (workerScript.atExit = new Map([["default", resolve]]))),
]);
expect(result).not.toBeDefined();
expect(workerScript.scriptRef.logs[0]).toStrictEqual(SpecialServers.Home);
});
});
describe("Failure", () => {
test("Prevent duplicates", () => {
runScriptFromScript("run", Player.getHomeComputer(), parentTestScriptPath, [], parentWorkerScript, {
...runOptions,
preventDuplicates: true,
});
expect(parentWorkerScript.scriptRef.logs[0]).toContain("is already running");
});
});
});
});