mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-12-30 18:07:33 +01:00
6af36e3b29
* ns API is wrapped once * when a new workerscript is created, each layer of ns is stamped with a private workerscript field that allows the functions to work. * Test has been refactored to account for new method of wrapping * BREAKING: ns functions need access to `this` value of their parent ns layer (or any ns layer) * Enums are passed directly to player (no cloning) but are frozen.
159 lines
6.0 KiB
TypeScript
159 lines
6.0 KiB
TypeScript
import { Player } from "../../../src/Player";
|
|
import { NetscriptFunctions, wrappedNS } from "../../../src/NetscriptFunctions";
|
|
import { RamCosts, getRamCost, RamCostConstants } from "../../../src/Netscript/RamCostGenerator";
|
|
import { Environment } from "../../../src/Netscript/Environment";
|
|
import { RunningScript } from "../../../src/Script/RunningScript";
|
|
import { Script } from "../../../src/Script/Script";
|
|
import { WorkerScript } from "../../../src/Netscript/WorkerScript";
|
|
import { calculateRamUsage } from "../../../src/Script/RamCalculations";
|
|
|
|
type PotentiallyAsyncFunction = (arg?: unknown) => { catch?: PotentiallyAsyncFunction };
|
|
type NSLayer = {
|
|
[key: string]: NSLayer | PotentiallyAsyncFunction;
|
|
};
|
|
type RamLayer = {
|
|
[key: string]: number | (() => number) | RamLayer;
|
|
};
|
|
function grabCost(ramLayer: RamLayer, fullPath: string[]) {
|
|
const ramEntry = ramLayer[fullPath[fullPath.length - 1]];
|
|
const expectedRam = typeof ramEntry === "function" ? ramEntry() : ramEntry;
|
|
if (typeof expectedRam !== "number") throw new Error(`There was no defined ram cost for ${fullPath.join(".")}().`);
|
|
return expectedRam;
|
|
}
|
|
|
|
describe("Netscript RAM Calculation/Generation Tests", function () {
|
|
Player.sourceFiles[0] = { n: 4, lvl: 3 };
|
|
const sf4 = Player.sourceFiles[0];
|
|
// For simulating costs of singularity functions.
|
|
const ScriptBaseCost = RamCostConstants.ScriptBaseRamCost;
|
|
const script = new Script();
|
|
/** Creates a RunningScript object which calculates static ram usage */
|
|
function createRunningScript(code: string) {
|
|
script.code = code;
|
|
script.updateRamUsage([]);
|
|
const runningScript = new RunningScript(script);
|
|
return runningScript;
|
|
}
|
|
|
|
/** Runs a Netscript function and properly catches an error even if it returns promise. */
|
|
function tryFunction(fn: PotentiallyAsyncFunction) {
|
|
try {
|
|
fn()?.catch?.(() => undefined);
|
|
} catch {}
|
|
}
|
|
|
|
let scriptRef = createRunningScript("");
|
|
//Since it is expensive to create a workerscript and wrap the ns API, this is done once
|
|
const workerScript = {
|
|
args: [] as string[],
|
|
code: "",
|
|
delay: null,
|
|
dynamicLoadedFns: {},
|
|
dynamicRamUsage: RamCostConstants.ScriptBaseRamCost,
|
|
env: new Environment(),
|
|
ramUsage: scriptRef.ramUsage,
|
|
scriptRef,
|
|
};
|
|
const ns = NetscriptFunctions(workerScript as WorkerScript);
|
|
|
|
function combinedRamCheck(
|
|
fn: PotentiallyAsyncFunction,
|
|
fnPath: string[],
|
|
expectedRamCost: number,
|
|
extraLayerCost = 0,
|
|
) {
|
|
const code = `${fnPath.join(".")}();\n`.repeat(3);
|
|
const fnName = fnPath[fnPath.length - 1];
|
|
|
|
//check imported getRamCost fn vs. expected ram from test
|
|
expect(getRamCost(...fnPath)).toEqual(expectedRamCost);
|
|
|
|
// Static ram check
|
|
const staticCost = calculateRamUsage(code, []).cost;
|
|
expect(staticCost).toBeCloseTo(ScriptBaseCost + expectedRamCost + extraLayerCost);
|
|
|
|
// reset workerScript for dynamic check
|
|
scriptRef = createRunningScript(code);
|
|
Object.assign(workerScript, {
|
|
code,
|
|
scriptRef,
|
|
ramUsage: scriptRef.ramUsage,
|
|
dynamicRamUsage: ScriptBaseCost,
|
|
env: new Environment(),
|
|
dynamicLoadedFns: {},
|
|
});
|
|
workerScript.env.vars = ns;
|
|
|
|
// Run the function through the workerscript's args
|
|
if (typeof fn === "function") {
|
|
tryFunction(fn);
|
|
tryFunction(fn);
|
|
tryFunction(fn);
|
|
} else {
|
|
throw new Error(`Invalid function specified: [${fnPath.toString()}]`);
|
|
}
|
|
|
|
expect(workerScript.dynamicLoadedFns).toHaveProperty(fnName);
|
|
expect(workerScript.dynamicRamUsage - ScriptBaseCost).toBeCloseTo(expectedRamCost, 5);
|
|
expect(workerScript.dynamicRamUsage).toBeCloseTo(scriptRef.ramUsage - extraLayerCost, 5);
|
|
}
|
|
|
|
describe("ns", () => {
|
|
Object.entries(wrappedNS as unknown as NSLayer).forEach(([key, val]) => {
|
|
if (key === "args" || key === "enums") return;
|
|
if (typeof val === "function") {
|
|
const expectedRam = grabCost(RamCosts, [key]);
|
|
it(`${key}()`, () => combinedRamCheck(val.bind(ns), [key], expectedRam));
|
|
}
|
|
//The only other option should be an NSLayer
|
|
const extraLayerCost = { corporation: 1022.4, hacknet: 4 }[key] ?? 0;
|
|
testLayer(val as NSLayer, RamCosts[key as keyof typeof RamCosts] as RamLayer, [key], extraLayerCost);
|
|
});
|
|
});
|
|
|
|
function testLayer(nsLayer: NSLayer, ramLayer: RamLayer, path: string[], extraLayerCost: number) {
|
|
// nsLayer is the layer on the main, unstamped wrappedNS object. The actualLayer is needed to check correct stamping.
|
|
const actualLayer = path.reduce((prev, curr) => prev[curr], ns as any); //todo: do this typesafely?
|
|
describe(path[path.length - 1], () => {
|
|
Object.entries(nsLayer).forEach(([key, val]) => {
|
|
const newPath = [...path, key];
|
|
if (typeof val === "function") {
|
|
const fnName = newPath.join(".");
|
|
const expectedRam = grabCost(ramLayer, newPath);
|
|
it(`${fnName}()`, () => combinedRamCheck(val.bind(actualLayer), newPath, expectedRam, extraLayerCost));
|
|
}
|
|
//Skip enums layers
|
|
else if (key === "enums") return;
|
|
//A layer should be the only other option.
|
|
else testLayer(val, ramLayer[key] as RamLayer, newPath, 0);
|
|
});
|
|
});
|
|
}
|
|
|
|
describe("Singularity multiplier checks", () => {
|
|
sf4.lvl = 3;
|
|
const singFunctions = Object.entries(wrappedNS.singularity).filter(([__, val]) => typeof val === "function");
|
|
const singObjects = singFunctions.map(([key, val]) => {
|
|
return {
|
|
name: key,
|
|
fn: val.bind(ns.singularity),
|
|
baseRam: grabCost(RamCosts.singularity, ["singularity", key]),
|
|
};
|
|
});
|
|
const lvlToMult: Record<number, number> = { 0: 16, 1: 16, 2: 4 };
|
|
for (const lvl of [0, 1, 2]) {
|
|
it(`SF4.${lvl} check for x${lvlToMult[lvl]} costs`, () => {
|
|
sf4.lvl = lvl;
|
|
singObjects.forEach((obj) =>
|
|
combinedRamCheck(
|
|
obj.fn as PotentiallyAsyncFunction,
|
|
["singularity", obj.name],
|
|
obj.baseRam * lvlToMult[lvl],
|
|
0,
|
|
),
|
|
);
|
|
});
|
|
}
|
|
});
|
|
});
|