mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-12-18 20:25:45 +01:00
Make ram checks more robust
* Instead of hardcoded categories, automatically walk through all layers of ns, check for their associated costs, and check that ingame static and dynamic costs match the expected assigned costs.
This commit is contained in:
parent
8bb88a5080
commit
a78a84c5b5
@ -7,8 +7,23 @@ 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();
|
||||
@ -20,15 +35,14 @@ describe("Netscript RAM Calculation/Generation Tests", function () {
|
||||
return runningScript;
|
||||
}
|
||||
|
||||
type potentiallyAsyncFunction = (arg?: unknown) => { catch?: potentiallyAsyncFunction };
|
||||
/** Runs a Netscript function and properly catches an error even if it returns promise. */
|
||||
function tryFunction(fn: potentiallyAsyncFunction) {
|
||||
function tryFunction(fn: PotentiallyAsyncFunction) {
|
||||
try {
|
||||
fn()?.catch?.(() => undefined);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
let runningScript = createRunningScript("");
|
||||
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[],
|
||||
@ -37,28 +51,40 @@ describe("Netscript RAM Calculation/Generation Tests", function () {
|
||||
dynamicLoadedFns: {},
|
||||
dynamicRamUsage: RamCostConstants.ScriptBaseRamCost,
|
||||
env: new Environment(),
|
||||
ramUsage: runningScript.ramUsage,
|
||||
scriptRef: runningScript,
|
||||
ramUsage: scriptRef.ramUsage,
|
||||
scriptRef,
|
||||
};
|
||||
const ns = NetscriptFunctions(workerScript as WorkerScript);
|
||||
|
||||
function dynamicCheck(fnPath: string[], expectedRamCost: number, extraLayerCost = 0) {
|
||||
function combinedRamCheck(
|
||||
fn: PotentiallyAsyncFunction,
|
||||
fnPath: string[],
|
||||
expectedRamCost: number,
|
||||
extraLayerCost = 0,
|
||||
) {
|
||||
const code = `${fnPath.join(".")}();\n`.repeat(3);
|
||||
const fnName = fnPath[fnPath.length - 1];
|
||||
|
||||
// update our existing WorkerScript
|
||||
runningScript = createRunningScript(code);
|
||||
workerScript.code = code;
|
||||
workerScript.scriptRef = runningScript;
|
||||
workerScript.ramUsage = workerScript.scriptRef.ramUsage;
|
||||
workerScript.dynamicRamUsage = ScriptBaseCost;
|
||||
workerScript.env = new Environment();
|
||||
workerScript.dynamicLoadedFns = {};
|
||||
//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
|
||||
const fn = fnPath.reduce((prev, curr) => prev[curr], ns as any);
|
||||
|
||||
if (typeof fn === "function") {
|
||||
tryFunction(fn);
|
||||
tryFunction(fn);
|
||||
@ -69,163 +95,55 @@ describe("Netscript RAM Calculation/Generation Tests", function () {
|
||||
|
||||
expect(workerScript.dynamicLoadedFns).toHaveProperty(fnName);
|
||||
expect(workerScript.dynamicRamUsage - ScriptBaseCost).toBeCloseTo(expectedRamCost, 5);
|
||||
expect(workerScript.dynamicRamUsage).toBeCloseTo(runningScript.ramUsage - extraLayerCost, 5);
|
||||
expect(workerScript.dynamicRamUsage).toBeCloseTo(scriptRef.ramUsage - extraLayerCost, 5);
|
||||
}
|
||||
|
||||
function testFunctionExpectZero(fnPath: string[], extraLayerCost = 0) {
|
||||
const wholeFn = `${fnPath.join(".")}()`;
|
||||
describe(wholeFn, () => {
|
||||
it("Zero Ram Static Check", () => {
|
||||
const ramCost = getRamCost(...fnPath);
|
||||
expect(ramCost).toEqual(0);
|
||||
const code = wholeFn;
|
||||
const staticCost = calculateRamUsage(code, []).cost;
|
||||
expect(staticCost).toEqual(ScriptBaseCost + extraLayerCost);
|
||||
});
|
||||
it("Zero Ram Dynamic check", () => dynamicCheck(fnPath, 0, extraLayerCost));
|
||||
describe("ns", () => {
|
||||
Object.entries(ns 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, [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 testFunctionExpectNonzero(fnPath: string[], extraLayerCost = 0) {
|
||||
const wholeFn = `${fnPath.join(".")}()`;
|
||||
const ramCost = getRamCost(...fnPath);
|
||||
describe(wholeFn, () => {
|
||||
it("Static Check", () => {
|
||||
expect(ramCost).toBeGreaterThan(0);
|
||||
const code = wholeFn;
|
||||
const staticCost = calculateRamUsage(code, []).cost;
|
||||
expect(staticCost).toBeCloseTo(ramCost + ScriptBaseCost + extraLayerCost, 5);
|
||||
});
|
||||
it("Dynamic Check", () => dynamicCheck(fnPath, ramCost, extraLayerCost));
|
||||
});
|
||||
}
|
||||
|
||||
//input type for testSingularityFunctions
|
||||
type singularityData = { fnPath: string[]; baseCost: number };
|
||||
|
||||
function testSingularityFunctions(data: singularityData[]) {
|
||||
const sf4 = Player.sourceFiles[0];
|
||||
data.forEach(({ fnPath, baseCost }) => {
|
||||
const wholeFn = `${fnPath.join(".")}()`;
|
||||
describe(wholeFn, () => {
|
||||
it("SF4.3", () => {
|
||||
sf4.lvl = 3;
|
||||
const ramCost = getRamCost(...fnPath);
|
||||
expect(ramCost).toEqual(baseCost);
|
||||
const code = wholeFn;
|
||||
const staticCost = calculateRamUsage(code, []).cost;
|
||||
expect(staticCost).toBeCloseTo(ramCost + ScriptBaseCost);
|
||||
dynamicCheck(fnPath, baseCost);
|
||||
});
|
||||
it("SF4.2", () => {
|
||||
sf4.lvl = 2;
|
||||
const ramCost = getRamCost(...fnPath);
|
||||
expect(ramCost).toBeCloseTo(baseCost * 4, 5);
|
||||
const code = wholeFn;
|
||||
const staticCost = calculateRamUsage(code, []).cost;
|
||||
expect(staticCost).toBeCloseTo(ramCost + ScriptBaseCost);
|
||||
dynamicCheck(fnPath, ramCost);
|
||||
});
|
||||
it("SF4.1", () => {
|
||||
sf4.lvl = 1;
|
||||
const ramCost = getRamCost(...fnPath);
|
||||
expect(ramCost).toBeCloseTo(baseCost * 16, 5);
|
||||
const code = wholeFn;
|
||||
const staticCost = calculateRamUsage(code, []).cost;
|
||||
expect(staticCost).toBeCloseTo(ramCost + ScriptBaseCost);
|
||||
dynamicCheck(fnPath, ramCost);
|
||||
});
|
||||
function testLayer(nsLayer: NSLayer, ramLayer: RamLayer, path: string[], extraLayerCost: number) {
|
||||
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, newPath, expectedRam, extraLayerCost));
|
||||
}
|
||||
//A layer should be the only other option.
|
||||
else testLayer(val, ramLayer[key] as RamLayer, newPath, 0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
type NSLayer = {
|
||||
[key: string]: NSLayer | unknown[] | (() => unknown);
|
||||
};
|
||||
type RamLayer = {
|
||||
[key: string]: RamLayer | number | (() => number);
|
||||
};
|
||||
function testLayer(nsLayer: NSLayer, ramLayer: RamLayer, path: string[], extraLayerCost = 0) {
|
||||
const zeroCostFunctions = Object.entries(nsLayer)
|
||||
.filter(([key, val]) => ramLayer[key] === 0 && typeof val === "function")
|
||||
.map(([key]) => [...path, key]);
|
||||
zeroCostFunctions.forEach((fn) => testFunctionExpectZero(fn, extraLayerCost));
|
||||
|
||||
const nonzeroCostFunctions = Object.entries(nsLayer)
|
||||
.filter(([key, val]) => ramLayer[key] > 0 && typeof val === "function")
|
||||
.map(([key]) => [...path, key]);
|
||||
nonzeroCostFunctions.forEach((fn) => testFunctionExpectNonzero(fn, extraLayerCost));
|
||||
}
|
||||
|
||||
describe("Top level ns functions", () => {
|
||||
const nsScope = ns as unknown as NSLayer;
|
||||
const ramScope = RamCosts;
|
||||
testLayer(nsScope, ramScope, []);
|
||||
});
|
||||
|
||||
describe("Bladeburner API (bladeburner) functions", () => {
|
||||
const nsScope = ns.bladeburner as unknown as NSLayer;
|
||||
const ramScope = RamCosts.bladeburner;
|
||||
testLayer(nsScope, ramScope, ["bladeburner"]);
|
||||
});
|
||||
|
||||
describe("Corporation API (corporation) functions", () => {
|
||||
const nsScope = ns.corporation as unknown as NSLayer;
|
||||
const ramScope = RamCosts.corporation;
|
||||
testLayer(nsScope, ramScope, ["corporation"], 1024 - ScriptBaseCost);
|
||||
});
|
||||
|
||||
describe("TIX API (stock) functions", () => {
|
||||
const nsScope = ns.stock as unknown as NSLayer;
|
||||
const ramScope = RamCosts.stock;
|
||||
testLayer(nsScope, ramScope, ["stock"]);
|
||||
});
|
||||
|
||||
describe("Gang API (gang) functions", () => {
|
||||
const nsScope = ns.gang as unknown as NSLayer;
|
||||
const ramScope = RamCosts.gang;
|
||||
testLayer(nsScope, ramScope, ["gang"]);
|
||||
});
|
||||
|
||||
describe("Coding Contract API (codingcontract) functions", () => {
|
||||
const nsScope = ns.codingcontract as unknown as NSLayer;
|
||||
const ramScope = RamCosts.codingcontract;
|
||||
testLayer(nsScope, ramScope, ["codingcontract"]);
|
||||
});
|
||||
|
||||
describe("Sleeve API (sleeve) functions", () => {
|
||||
const nsScope = ns.sleeve as unknown as NSLayer;
|
||||
const ramScope = RamCosts.sleeve;
|
||||
testLayer(nsScope, ramScope, ["sleeve"]);
|
||||
});
|
||||
|
||||
//Singularity functions are tested in a different way because they also check SF4 level effect
|
||||
describe("ns.singularity functions", () => {
|
||||
const singularityFunctions = Object.entries(RamCosts.singularity).map(([fnName, ramFn]) => {
|
||||
describe("Singularity multiplier checks", () => {
|
||||
sf4.lvl = 3;
|
||||
const singFunctions = Object.entries(ns.singularity).filter(([key, val]) => typeof val === "function");
|
||||
const singObjects = singFunctions.map(([key, val]) => {
|
||||
return {
|
||||
fnPath: ["singularity", fnName],
|
||||
// This will error if a singularity function is assigned a flat cost instead of using the SF4 function
|
||||
baseCost: (ramFn as () => number)(),
|
||||
name: key,
|
||||
fn: val,
|
||||
baseRam: grabCost(RamCosts.singularity, ["singularity", key]),
|
||||
};
|
||||
});
|
||||
testSingularityFunctions(singularityFunctions);
|
||||
});
|
||||
|
||||
//Formulas requires deeper layer penetration
|
||||
function formulasTest(
|
||||
newLayer = "formulas",
|
||||
oldNSLayer = ns as unknown as NSLayer,
|
||||
oldRamLayer = RamCosts as unknown as RamLayer,
|
||||
path = ["formulas"],
|
||||
nsLayer = oldNSLayer[newLayer] as NSLayer,
|
||||
ramLayer = oldRamLayer[newLayer] as RamLayer,
|
||||
) {
|
||||
testLayer(nsLayer, ramLayer, path);
|
||||
for (const [key, val] of Object.entries(nsLayer)) {
|
||||
if (Array.isArray(val) || typeof val === "function" || key === "enums") continue;
|
||||
// Only other option is an object / new layer
|
||||
describe(key, () => formulasTest(key, nsLayer, ramLayer, [...path, 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, ["singularity", obj.name], obj.baseRam * lvlToMult[lvl], 0),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
describe("ns.formulas functions", formulasTest);
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user