mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-17 21:23:54 +01:00
Better RamCost testing (see desc)
* 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.
This commit is contained in:
parent
e71e5988cb
commit
8bb88a5080
@ -1,19 +1,12 @@
|
||||
import { Player } from "../Player";
|
||||
import { NSFull } from "../NetscriptFunctions";
|
||||
|
||||
import { NS as INS } from "../ScriptEditor/NetscriptDefinitions";
|
||||
import { INetscriptExtra } from "../NetscriptFunctions/Extra";
|
||||
|
||||
/** This type assumes any value that isn't an API layer or a function has been omitted (args and enum) */
|
||||
type RamCostTree<API> = {
|
||||
[Property in keyof API]: API[Property] extends () => void
|
||||
? number | (() => void)
|
||||
: API[Property] extends object
|
||||
? RamCostTree<API[Property]>
|
||||
: never;
|
||||
[Property in keyof API]: API[Property] extends Function ? number | (() => number) : RamCostTree<API[Property]>;
|
||||
};
|
||||
|
||||
// TODO remember to update RamCalculations.js and WorkerScript.js
|
||||
|
||||
// RAM costs for Netscript functions
|
||||
/** Constants for assigning costs to ns functions */
|
||||
export const RamCostConstants: Record<string, number> = {
|
||||
ScriptBaseRamCost: 1.6,
|
||||
ScriptDomRamCost: 25,
|
||||
@ -120,7 +113,7 @@ const hacknet = {
|
||||
getHashUpgradeLevel: 0,
|
||||
getStudyMult: 0,
|
||||
getTrainingMult: 0,
|
||||
};
|
||||
} as const;
|
||||
|
||||
// Stock API
|
||||
const stock = {
|
||||
@ -149,7 +142,7 @@ const stock = {
|
||||
purchase4SMarketDataTixApi: RamCostConstants.ScriptBuySellStockRamCost,
|
||||
purchaseWseAccount: RamCostConstants.ScriptBuySellStockRamCost,
|
||||
purchaseTixApi: RamCostConstants.ScriptBuySellStockRamCost,
|
||||
};
|
||||
} as const;
|
||||
|
||||
// Singularity API
|
||||
const singularity = {
|
||||
@ -208,7 +201,7 @@ const singularity = {
|
||||
b1tflum3: SF4Cost(16),
|
||||
destroyW0r1dD43m0n: SF4Cost(32),
|
||||
getCurrentWork: SF4Cost(0.5),
|
||||
};
|
||||
} as const;
|
||||
|
||||
// Gang API
|
||||
const gang = {
|
||||
@ -233,7 +226,7 @@ const gang = {
|
||||
setTerritoryWarfare: RamCostConstants.ScriptGangApiBaseRamCost / 2,
|
||||
getChanceToWinClash: RamCostConstants.ScriptGangApiBaseRamCost,
|
||||
getBonusTime: 0,
|
||||
};
|
||||
} as const;
|
||||
|
||||
// Bladeburner API
|
||||
const bladeburner = {
|
||||
@ -272,12 +265,12 @@ const bladeburner = {
|
||||
joinBladeburnerFaction: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
|
||||
joinBladeburnerDivision: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
|
||||
getBonusTime: 0,
|
||||
};
|
||||
} as const;
|
||||
|
||||
const infiltration = {
|
||||
getPossibleLocations: RamCostConstants.ScriptInfiltrationGetLocations,
|
||||
getInfiltration: RamCostConstants.ScriptInfiltrationGetInfiltrations,
|
||||
};
|
||||
} as const;
|
||||
|
||||
// Coding Contract API
|
||||
const codingcontract = {
|
||||
@ -286,7 +279,7 @@ const codingcontract = {
|
||||
getData: RamCostConstants.ScriptCodingContractBaseRamCost / 2,
|
||||
getDescription: RamCostConstants.ScriptCodingContractBaseRamCost / 2,
|
||||
getNumTriesRemaining: RamCostConstants.ScriptCodingContractBaseRamCost / 5,
|
||||
};
|
||||
} as const;
|
||||
|
||||
// Duplicate Sleeve API
|
||||
const sleeve = {
|
||||
@ -308,7 +301,7 @@ const sleeve = {
|
||||
setToBladeburnerAction: RamCostConstants.ScriptSleeveBaseRamCost,
|
||||
getSleeveAugmentationPrice: RamCostConstants.ScriptSleeveBaseRamCost,
|
||||
getSleeveAugmentationRepReq: RamCostConstants.ScriptSleeveBaseRamCost,
|
||||
};
|
||||
} as const;
|
||||
|
||||
// Stanek API
|
||||
const stanek = {
|
||||
@ -323,7 +316,7 @@ const stanek = {
|
||||
getFragment: RamCostConstants.ScriptStanekFragmentAt,
|
||||
removeFragment: RamCostConstants.ScriptStanekDeleteAt,
|
||||
acceptGift: RamCostConstants.ScriptStanekAcceptGift,
|
||||
};
|
||||
} as const;
|
||||
|
||||
// UI API
|
||||
const ui = {
|
||||
@ -336,7 +329,7 @@ const ui = {
|
||||
getGameInfo: 0,
|
||||
clearTerminal: 0,
|
||||
windowSize: 0,
|
||||
};
|
||||
} as const;
|
||||
|
||||
// Grafting API
|
||||
const grafting = {
|
||||
@ -344,7 +337,7 @@ const grafting = {
|
||||
getAugmentationGraftTime: 3.75,
|
||||
getGraftableAugmentations: 5,
|
||||
graftAugmentation: 7.5,
|
||||
};
|
||||
} as const;
|
||||
|
||||
const corporation = {
|
||||
getMaterialNames: 0,
|
||||
@ -412,16 +405,17 @@ const corporation = {
|
||||
hasResearched: 0,
|
||||
setAutoJobAssignment: 0,
|
||||
getOfficeSizeUpgradeCost: 0,
|
||||
};
|
||||
} as const;
|
||||
|
||||
const SourceRamCosts = {
|
||||
args: undefined as unknown as never[], // special use case
|
||||
enums: undefined as unknown as never,
|
||||
/** RamCosts guaranteed to match ns structure 1:1 (aside from args and enums).
|
||||
* An error will be generated if there are missing OR additional ram costs defined.
|
||||
* To avoid errors, define every function in NetscriptDefinition.d.ts and NetscriptFunctions,
|
||||
* and have a ram cost associated here. */
|
||||
export const RamCosts: RamCostTree<Omit<NSFull, "args" | "enums">> = {
|
||||
corporation,
|
||||
hacknet,
|
||||
stock,
|
||||
singularity,
|
||||
...singularity, // singularity is in namespace & toplevel
|
||||
gang,
|
||||
bladeburner,
|
||||
infiltration,
|
||||
@ -601,13 +595,7 @@ const SourceRamCosts = {
|
||||
factionGains: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const RamCosts: Record<string, any> = SourceRamCosts;
|
||||
|
||||
// This line in particular is there so typescript typechecks that we are not missing any static ram cost.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const _typecheck: RamCostTree<INS & INetscriptExtra> = SourceRamCosts;
|
||||
} as const;
|
||||
|
||||
export function getRamCost(...args: string[]): number {
|
||||
if (args.length === 0) {
|
||||
@ -615,7 +603,7 @@ export function getRamCost(...args: string[]): number {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let curr = RamCosts[args[0]];
|
||||
let curr = RamCosts[args[0] as keyof typeof RamCosts];
|
||||
for (let i = 1; i < args.length; ++i) {
|
||||
if (curr == null) {
|
||||
console.warn(`Invalid function passed to getRamCost: ${args}`);
|
||||
@ -627,7 +615,7 @@ export function getRamCost(...args: string[]): number {
|
||||
break;
|
||||
}
|
||||
|
||||
curr = curr[args[i]];
|
||||
curr = curr[args[i] as keyof typeof curr];
|
||||
}
|
||||
|
||||
if (typeof curr === "number") {
|
||||
|
24
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
24
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
@ -4408,54 +4408,55 @@ export interface NS {
|
||||
* @remarks RAM cost: 4 GB
|
||||
*/
|
||||
readonly hacknet: Hacknet;
|
||||
|
||||
/**
|
||||
*
|
||||
* Namespace for bladeburner functions.
|
||||
* @remarks RAM cost: 0 GB
|
||||
*/
|
||||
readonly bladeburner: Bladeburner;
|
||||
|
||||
/**
|
||||
*
|
||||
* Namespace for codingcontract functions.
|
||||
* @remarks RAM cost: 0 GB
|
||||
*/
|
||||
readonly codingcontract: CodingContract;
|
||||
|
||||
/**
|
||||
*
|
||||
* Namespace for gang functions.
|
||||
* @remarks RAM cost: 0 GB
|
||||
*/
|
||||
readonly gang: Gang;
|
||||
|
||||
/**
|
||||
*
|
||||
* Namespace for sleeve functions.
|
||||
* @remarks RAM cost: 0 GB
|
||||
*/
|
||||
readonly sleeve: Sleeve;
|
||||
|
||||
/**
|
||||
*
|
||||
* Namespace for stock functions.
|
||||
* @remarks
|
||||
* RAM cost: 0 GB
|
||||
* @remarks RAM cost: 0 GB
|
||||
*/
|
||||
readonly stock: TIX;
|
||||
|
||||
/**
|
||||
*
|
||||
* Namespace for formulas functions.
|
||||
* @remarks
|
||||
* RAM cost: 0 GB
|
||||
* @remarks RAM cost: 0 GB
|
||||
*/
|
||||
readonly formulas: Formulas;
|
||||
|
||||
/**
|
||||
* Namespace for stanek functions.
|
||||
* RAM cost: 0 GB
|
||||
*/
|
||||
readonly stanek: Stanek;
|
||||
|
||||
/**
|
||||
* Namespace for infiltration functions.
|
||||
* RAM cost: 0 GB
|
||||
*/
|
||||
readonly infiltration: Infiltration;
|
||||
|
||||
/**
|
||||
* Namespace for corporation functions.
|
||||
* RAM cost: 1022.4 GB
|
||||
@ -4476,8 +4477,7 @@ export interface NS {
|
||||
|
||||
/**
|
||||
* Namespace for grafting functions.
|
||||
* @remarks
|
||||
* RAM cost: 0 GB
|
||||
* @remarks RAM cost: 0 GB
|
||||
*/
|
||||
readonly grafting: Grafting;
|
||||
|
||||
|
@ -42,20 +42,7 @@ describe("Netscript RAM Calculation/Generation Tests", function () {
|
||||
};
|
||||
const ns = NetscriptFunctions(workerScript as WorkerScript);
|
||||
|
||||
/**
|
||||
* Tests that:
|
||||
* 1. A function has non-zero RAM cost, or zero if it is flagged as zero cost.
|
||||
* 2. Running the function properly updates the MockWorkerScript's dynamic RAM calculation
|
||||
* 3. Running multiple calls of the function does not result in additional RAM cost
|
||||
* @param {string[]} fnDesc - describes the name of the function being tested,
|
||||
* including the namespace(s). e.g. ["gang", "getMemberNames"]
|
||||
*/
|
||||
function testDynamicRamCost(fnDesc: string[], zero: boolean = false) {
|
||||
const expected = getRamCost(...fnDesc);
|
||||
zero ? expect(expected).toEqual(0) : expect(expected).toBeGreaterThan(0);
|
||||
}
|
||||
|
||||
function dynamicCheck(fnPath: string[], expectedRamCost: number) {
|
||||
function dynamicCheck(fnPath: string[], expectedRamCost: number, extraLayerCost = 0) {
|
||||
const code = `${fnPath.join(".")}();\n`.repeat(3);
|
||||
const fnName = fnPath[fnPath.length - 1];
|
||||
|
||||
@ -82,10 +69,10 @@ 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, 5);
|
||||
expect(workerScript.dynamicRamUsage).toBeCloseTo(runningScript.ramUsage - extraLayerCost, 5);
|
||||
}
|
||||
|
||||
function testFunctionExpectZero(fnPath: string[]) {
|
||||
function testFunctionExpectZero(fnPath: string[], extraLayerCost = 0) {
|
||||
const wholeFn = `${fnPath.join(".")}()`;
|
||||
describe(wholeFn, () => {
|
||||
it("Zero Ram Static Check", () => {
|
||||
@ -93,13 +80,13 @@ describe("Netscript RAM Calculation/Generation Tests", function () {
|
||||
expect(ramCost).toEqual(0);
|
||||
const code = wholeFn;
|
||||
const staticCost = calculateRamUsage(code, []).cost;
|
||||
expect(staticCost).toEqual(ScriptBaseCost);
|
||||
expect(staticCost).toEqual(ScriptBaseCost + extraLayerCost);
|
||||
});
|
||||
it("Zero Ram Dynamic check", () => dynamicCheck(fnPath, 0));
|
||||
it("Zero Ram Dynamic check", () => dynamicCheck(fnPath, 0, extraLayerCost));
|
||||
});
|
||||
}
|
||||
|
||||
function testFunctionExpectNonzero(fnPath: string[]) {
|
||||
function testFunctionExpectNonzero(fnPath: string[], extraLayerCost = 0) {
|
||||
const wholeFn = `${fnPath.join(".")}()`;
|
||||
const ramCost = getRamCost(...fnPath);
|
||||
describe(wholeFn, () => {
|
||||
@ -107,13 +94,15 @@ describe("Netscript RAM Calculation/Generation Tests", function () {
|
||||
expect(ramCost).toBeGreaterThan(0);
|
||||
const code = wholeFn;
|
||||
const staticCost = calculateRamUsage(code, []).cost;
|
||||
expect(staticCost).toBeCloseTo(ramCost + ScriptBaseCost, 5);
|
||||
expect(staticCost).toBeCloseTo(ramCost + ScriptBaseCost + extraLayerCost, 5);
|
||||
});
|
||||
it("Dynamic Check", () => dynamicCheck(fnPath, ramCost));
|
||||
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 }) => {
|
||||
@ -154,64 +143,89 @@ describe("Netscript RAM Calculation/Generation Tests", function () {
|
||||
[key: string]: NSLayer | unknown[] | (() => unknown);
|
||||
};
|
||||
type RamLayer = {
|
||||
[key: string]: number | (() => number);
|
||||
[key: string]: RamLayer | number | (() => number);
|
||||
};
|
||||
function testLayer(nsLayer: NSLayer, ramLayer: RamLayer, path: string[]) {
|
||||
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(testFunctionExpectZero);
|
||||
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(testFunctionExpectNonzero);
|
||||
nonzeroCostFunctions.forEach((fn) => testFunctionExpectNonzero(fn, extraLayerCost));
|
||||
}
|
||||
|
||||
describe("Top level ns functions", function () {
|
||||
describe("Top level ns functions", () => {
|
||||
const nsScope = ns as unknown as NSLayer;
|
||||
const ramScope = RamCosts as unknown as RamLayer;
|
||||
const ramScope = RamCosts;
|
||||
testLayer(nsScope, ramScope, []);
|
||||
});
|
||||
|
||||
describe("TIX API (stock) functions", function () {
|
||||
const nsScope = ns.stock as unknown as NSLayer;
|
||||
const ramScope = RamCosts.stock as unknown as RamLayer;
|
||||
testLayer(nsScope, ramScope, ["stock"]);
|
||||
});
|
||||
|
||||
describe("Bladeburner API (bladeburner) functions", function () {
|
||||
describe("Bladeburner API (bladeburner) functions", () => {
|
||||
const nsScope = ns.bladeburner as unknown as NSLayer;
|
||||
const ramScope = RamCosts.bladeburner as unknown as RamLayer;
|
||||
const ramScope = RamCosts.bladeburner;
|
||||
testLayer(nsScope, ramScope, ["bladeburner"]);
|
||||
});
|
||||
|
||||
describe("Gang API (gang) functions", function () {
|
||||
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 as unknown as RamLayer;
|
||||
const ramScope = RamCosts.gang;
|
||||
testLayer(nsScope, ramScope, ["gang"]);
|
||||
});
|
||||
|
||||
describe("Coding Contract API (codingcontract) functions", function () {
|
||||
describe("Coding Contract API (codingcontract) functions", () => {
|
||||
const nsScope = ns.codingcontract as unknown as NSLayer;
|
||||
const ramScope = RamCosts.codingcontract as unknown as RamLayer;
|
||||
const ramScope = RamCosts.codingcontract;
|
||||
testLayer(nsScope, ramScope, ["codingcontract"]);
|
||||
});
|
||||
|
||||
describe("Sleeve API (sleeve) functions", function () {
|
||||
describe("Sleeve API (sleeve) functions", () => {
|
||||
const nsScope = ns.sleeve as unknown as NSLayer;
|
||||
const ramScope = RamCosts.sleeve as unknown as RamLayer;
|
||||
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", function () {
|
||||
const singularityFunctions = Object.entries(RamCosts.singularity).map(([key, val]) => {
|
||||
describe("ns.singularity functions", () => {
|
||||
const singularityFunctions = Object.entries(RamCosts.singularity).map(([fnName, ramFn]) => {
|
||||
return {
|
||||
fnPath: ["singularity", key],
|
||||
baseCost: (val as () => number)(),
|
||||
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);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user