mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-10-20 00:33:13 +02:00
b90849e9fd
* handle enums differently * Enums are frozen and fed directly to the proxy * Enums are not included in the NSFull definition, allowing samekeys for RamCostTree<API>, InternalAPI<API>, and ExternalAPI<API> * Rewrote a lot of the ramcalc test, with better typing thanks to the samekeys above * Fix ns1 for proxy (args, pid, and enums after above changes were not being added to ns1 scripts.) Update changelog and bump version to 2.2.1
171 lines
6.3 KiB
TypeScript
171 lines
6.3 KiB
TypeScript
import { Player } from "../../../src/Player";
|
|
import { NetscriptFunctions } from "../../../src/NetscriptFunctions";
|
|
import { RamCosts, getRamCost, RamCostConstants, RamCostTree } 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";
|
|
import { ns } from "../../../src/NetscriptFunctions";
|
|
import { ExternalAPI, InternalAPI } from "src/Netscript/APIWrapper";
|
|
import { Singularity } from "@nsdefs";
|
|
|
|
type PotentiallyAsyncFunction = (arg?: unknown) => { catch?: PotentiallyAsyncFunction };
|
|
|
|
/** Get a potentiallyAsyncFunction from a layer of the external ns */
|
|
function getFunction(fn: unknown) {
|
|
if (typeof fn !== "function") throw new Error("Expected a function at this location.");
|
|
return fn as PotentiallyAsyncFunction;
|
|
}
|
|
function grabCost<API>(ramEntry: RamCostTree<API>[keyof API]) {
|
|
if (typeof ramEntry === "function") return ramEntry();
|
|
if (typeof ramEntry === "number") return ramEntry;
|
|
throw new Error("Invalid ramcost");
|
|
}
|
|
function isRemovedFunction(fn: Function) {
|
|
try {
|
|
fn();
|
|
} catch {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
describe("Netscript RAM Calculation/Generation Tests", function () {
|
|
Player.sourceFiles[0] = { n: 4, lvl: 3 };
|
|
// For simulating costs of singularity functions.
|
|
const sf4 = Player.sourceFiles[0];
|
|
const baseCost = RamCostConstants.Base;
|
|
const maxCost = RamCostConstants.Max;
|
|
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.Base,
|
|
env: new Environment(),
|
|
ramUsage: scriptRef.ramUsage,
|
|
scriptRef,
|
|
};
|
|
const nsExternal = 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(Math.min(baseCost + expectedRamCost + extraLayerCost, maxCost));
|
|
|
|
// reset workerScript for dynamic check
|
|
scriptRef = createRunningScript(code);
|
|
Object.assign(workerScript, {
|
|
code,
|
|
scriptRef,
|
|
ramUsage: scriptRef.ramUsage,
|
|
dynamicRamUsage: baseCost,
|
|
env: new Environment(),
|
|
dynamicLoadedFns: {},
|
|
});
|
|
workerScript.env.vars = nsExternal;
|
|
|
|
// 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).toBeCloseTo(Math.min(expectedRamCost + baseCost, maxCost), 5);
|
|
expect(workerScript.dynamicRamUsage).toBeCloseTo(scriptRef.ramUsage - extraLayerCost, 5);
|
|
}
|
|
|
|
describe("ns", () => {
|
|
function testLayer<API>(
|
|
internalLayer: InternalAPI<API>,
|
|
externalLayer: ExternalAPI<API>,
|
|
ramLayer: RamCostTree<API>,
|
|
path: string[],
|
|
extraLayerCost: number,
|
|
) {
|
|
describe(path[path.length - 1] ?? "Base ns layer", () => {
|
|
for (const [key, val] of Object.entries(internalLayer) as [keyof API, InternalAPI<API>[keyof API]][]) {
|
|
const newPath = [...path, key as string];
|
|
if (typeof val === "function") {
|
|
// Removed functions have no ram cost and should be skipped.
|
|
if (isRemovedFunction(val)) return;
|
|
const fn = getFunction(externalLayer[key]);
|
|
const fnName = newPath.join(".");
|
|
const expectedRam = grabCost(ramLayer[key]);
|
|
it(`${fnName}()`, () => combinedRamCheck(fn, newPath, expectedRam, extraLayerCost));
|
|
}
|
|
//A layer should be the only other option. Hacknet is currently the only layer with a layer cost.
|
|
else {
|
|
//hacknet is currently the only layer with a layer cost.
|
|
const layerCost = key === "hacknet" ? 4 : 0;
|
|
testLayer(val as InternalAPI<unknown>, externalLayer[key], ramLayer[key], newPath, layerCost);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
testLayer(ns, nsExternal, RamCosts, [], 0);
|
|
});
|
|
|
|
describe("Singularity multiplier checks", () => {
|
|
// Checks were already done above for SF4.3 having normal ramcost.
|
|
sf4.lvl = 3;
|
|
const lvlToMult = { 0: 16, 1: 16, 2: 4 };
|
|
const externalSingularity = nsExternal.singularity;
|
|
const ramCostSingularity = RamCosts.singularity;
|
|
const singObjects = (
|
|
Object.entries(ns.singularity) as [keyof Singularity, InternalAPI<Singularity>[keyof Singularity]][]
|
|
)
|
|
.filter(([_, v]) => typeof v === "function" && !isRemovedFunction(v))
|
|
.map(([name]) => {
|
|
return {
|
|
name,
|
|
baseRam: grabCost<Singularity>(ramCostSingularity[name]),
|
|
};
|
|
});
|
|
for (const lvl of [0, 1, 2] as const) {
|
|
it(`SF4.${lvl} check for x${lvlToMult[lvl]} costs`, () => {
|
|
sf4.lvl = lvl;
|
|
const expectedMult = lvlToMult[lvl];
|
|
singObjects.forEach(({ name, baseRam }) => {
|
|
const fn = getFunction(externalSingularity[name]);
|
|
combinedRamCheck(fn, ["singularity", name], baseRam * expectedMult);
|
|
});
|
|
});
|
|
}
|
|
});
|
|
});
|