mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-09-20 04:48:38 +02:00
8bb88a5080
* RamCostGenerator will have an error if ramcosts are defined for nonexistent functions, in addition to error if not all functions have ram costs defined * Removed a few random blank comment lines in NetscriptDefinitions.d.ts * RamCalculation.test.ts checks exact expected static and dynamic ram usage from (almost) every function, based on defined RamCosts in RamCostGenerator.
232 lines
8.8 KiB
TypeScript
232 lines
8.8 KiB
TypeScript
import { Player } from "../../../src/Player";
|
|
import { NetscriptFunctions } 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";
|
|
|
|
describe("Netscript RAM Calculation/Generation Tests", function () {
|
|
Player.sourceFiles[0] = { n: 4, lvl: 3 };
|
|
// 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;
|
|
}
|
|
|
|
type potentiallyAsyncFunction = (arg?: unknown) => { catch?: potentiallyAsyncFunction };
|
|
/** Runs a Netscript function and properly catches an error even if it returns promise. */
|
|
function tryFunction(fn: potentiallyAsyncFunction) {
|
|
try {
|
|
fn()?.catch?.(() => undefined);
|
|
} catch {}
|
|
}
|
|
|
|
let runningScript = 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: runningScript.ramUsage,
|
|
scriptRef: runningScript,
|
|
};
|
|
const ns = NetscriptFunctions(workerScript as WorkerScript);
|
|
|
|
function dynamicCheck(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 = {};
|
|
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);
|
|
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(runningScript.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));
|
|
});
|
|
}
|
|
|
|
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);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
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]) => {
|
|
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)(),
|
|
};
|
|
});
|
|
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]));
|
|
}
|
|
}
|
|
describe("ns.formulas functions", formulasTest);
|
|
});
|