forked from profectus/Profectus
Fix some tests. Boy tests run slow
This commit is contained in:
parent
5f3dd1162d
commit
7593fea512
2 changed files with 326 additions and 280 deletions
|
@ -32,14 +32,18 @@ function isVariableFormula(value: FormulaSource): value is VariableFormula {
|
|||
}
|
||||
|
||||
function calculateInvertibility(...inputs: FormulaSource[]) {
|
||||
const invertible = !inputs.some(input => input instanceof Formula && !input.invertible);
|
||||
const hasVariable =
|
||||
invertible &&
|
||||
inputs.filter(input => input instanceof Formula && input.invertible && input.hasVariable)
|
||||
.length === 1;
|
||||
if (inputs.some(input => input instanceof Formula && !input.invertible)) {
|
||||
return {
|
||||
invertible,
|
||||
hasVariable
|
||||
invertible: false,
|
||||
hasVariable: false
|
||||
};
|
||||
}
|
||||
const numVariables = inputs.filter(
|
||||
input => input instanceof Formula && input.invertible && input.hasVariable
|
||||
).length;
|
||||
return {
|
||||
invertible: numVariables <= 1,
|
||||
hasVariable: numVariables === 1
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -622,11 +626,9 @@ export default class Formula {
|
|||
return new Formula(
|
||||
() => this.evaluate().sub(unrefFormulaSource(v)),
|
||||
invertible
|
||||
? value =>
|
||||
Decimal.add(
|
||||
value,
|
||||
isVariableFormula(this) ? unrefFormulaSource(v) : this.evaluate()
|
||||
)
|
||||
? isVariableFormula(this)
|
||||
? value => Decimal.add(value, unrefFormulaSource(v))
|
||||
: value => Decimal.sub(this.evaluate(), value)
|
||||
: undefined,
|
||||
hasVariable
|
||||
);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import Formula, { InvertibleFormula } from "game/formulas";
|
||||
import Decimal, { DecimalSource } from "util/bignum";
|
||||
import { beforeAll, describe, expect, test } from "vitest";
|
||||
import Formula, { FormulaSource, InvertibleFormula } from "game/formulas";
|
||||
import Decimal, { DecimalSource, format } from "util/bignum";
|
||||
import { beforeAll, describe, expect, test, vi } from "vitest";
|
||||
import { ref } from "vue";
|
||||
|
||||
type FormulaFunctions = keyof Formula & keyof typeof Formula & keyof typeof Decimal;
|
||||
|
@ -9,8 +9,34 @@ interface FixedLengthArray<T, L extends number> extends ArrayLike<T> {
|
|||
length: L;
|
||||
}
|
||||
|
||||
function compare_tolerance(value: DecimalSource) {
|
||||
return (other: DecimalSource) => Decimal.eq_tolerance(value, other);
|
||||
expect.extend({
|
||||
compare_tolerance(received, expected) {
|
||||
const { isNot } = this;
|
||||
return {
|
||||
// do not alter your "pass" based on isNot. Vitest does it for you
|
||||
pass: Decimal.eq_tolerance(received, expected),
|
||||
message: () =>
|
||||
`Expected ${received} to${
|
||||
(isNot as boolean) ? " not" : ""
|
||||
} be close to ${expected}`,
|
||||
expected: format(expected),
|
||||
actual: format(received)
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
interface CustomMatchers<R = unknown> {
|
||||
compare_tolerance(expected: DecimalSource): R;
|
||||
}
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace Vi {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
interface Assertion extends CustomMatchers {}
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
interface AsymmetricMatchersContaining extends CustomMatchers {}
|
||||
}
|
||||
}
|
||||
|
||||
function testConstant(
|
||||
|
@ -23,17 +49,20 @@ function testConstant(
|
|||
beforeAll(() => {
|
||||
formula = formulaFunc();
|
||||
});
|
||||
test("evaluates correctly", () => expect(formula.evaluate()).toEqual(expectedValue));
|
||||
test("inverts correctly", () => expect(formula.invert(10)).toEqual(expectedValue));
|
||||
test("is invertible", () => expect(formula.invertible).toBe(true));
|
||||
test("is not marked as having a variable", () => expect(formula.hasVariable).toBe(false));
|
||||
test("evaluates correctly", async () =>
|
||||
expect(formula.evaluate()).compare_tolerance(expectedValue));
|
||||
test("invert is pass-through", async () =>
|
||||
expect(formula.invert(25)).compare_tolerance(25));
|
||||
test("is invertible", async () => expect(formula.invertible).toBe(true));
|
||||
test("is not marked as having a variable", async () =>
|
||||
expect(formula.hasVariable).toBe(false));
|
||||
});
|
||||
}
|
||||
|
||||
// Utility function that will test all the different
|
||||
// It's a lot of tests, but I'd rather be exhaustive
|
||||
function testFormula<T extends FormulaFunctions>(
|
||||
functionNames: readonly T[],
|
||||
functionName: T,
|
||||
args: Readonly<FixedLengthArray<number, Parameters<typeof Formula[T]>["length"]>>,
|
||||
invertible = true
|
||||
) {
|
||||
|
@ -43,8 +72,7 @@ function testFormula<T extends FormulaFunctions>(
|
|||
value = testValueFormulas[args[0]].evaluate();
|
||||
});
|
||||
|
||||
functionNames.forEach(name => {
|
||||
let testName = name + "(";
|
||||
let testName = functionName + "(";
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (i !== 0) {
|
||||
testName += ", ";
|
||||
|
@ -54,7 +82,7 @@ function testFormula<T extends FormulaFunctions>(
|
|||
testName += ")";
|
||||
describe(testName, () => {
|
||||
let expectedEvaluation: Decimal | undefined;
|
||||
let formulaArgs: Formula[];
|
||||
const formulaArgs: Formula[] = [];
|
||||
let staticFormula: Formula;
|
||||
let instanceFormula: Formula;
|
||||
beforeAll(() => {
|
||||
|
@ -63,135 +91,136 @@ function testFormula<T extends FormulaFunctions>(
|
|||
}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
staticFormula = Formula[name](...formulaArgs);
|
||||
staticFormula = Formula[functionName](...formulaArgs);
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
instanceFormula = formulaArgs[0][name](...formulaArgs.slice(1));
|
||||
instanceFormula = formulaArgs[0][functionName](...formulaArgs.slice(1));
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
expectedEvaluation = Decimal[name](...args);
|
||||
expectedEvaluation = Decimal[functionName](...args);
|
||||
} catch {
|
||||
// If this is an invalid Decimal operation, then ignore this test case
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
test("Static formula is not marked as having a variable", () =>
|
||||
test("Static formula is not marked as having a variable", async () =>
|
||||
expect(staticFormula.hasVariable).toBe(false));
|
||||
test("Static function evaluates correctly", () =>
|
||||
test("Static function evaluates correctly", async () =>
|
||||
expectedEvaluation != null &&
|
||||
expect(staticFormula.evaluate()).toSatisfy(compare_tolerance(expectedEvaluation)));
|
||||
test("Static function invertible", () =>
|
||||
expect(staticFormula.evaluate()).compare_tolerance(expectedEvaluation));
|
||||
test("Static function invertible", async () =>
|
||||
expect(staticFormula.invertible).toBe(invertible));
|
||||
if (invertible) {
|
||||
test("Static function inverts correctly", () =>
|
||||
test("Static function inverts correctly", async () =>
|
||||
expectedEvaluation != null &&
|
||||
!Decimal.isNaN(expectedEvaluation) &&
|
||||
expect(staticFormula.invert(expectedEvaluation)).toSatisfy(
|
||||
compare_tolerance(value)
|
||||
));
|
||||
expect(staticFormula.invert(expectedEvaluation)).compare_tolerance(value));
|
||||
}
|
||||
|
||||
// Do those tests again but for non-static methods
|
||||
test("Instance formula is not marked as having a variable", () =>
|
||||
test("Instance formula is not marked as having a variable", async () =>
|
||||
expect(instanceFormula.hasVariable).toBe(false));
|
||||
test("Instance function evaluates correctly", () =>
|
||||
test("Instance function evaluates correctly", async () =>
|
||||
expectedEvaluation != null &&
|
||||
expect(instanceFormula.evaluate()).toSatisfy(
|
||||
compare_tolerance(expectedEvaluation)
|
||||
));
|
||||
test("Instance function invertible", () =>
|
||||
expect(instanceFormula.evaluate()).compare_tolerance(expectedEvaluation));
|
||||
test("Instance function invertible", async () =>
|
||||
expect(instanceFormula.invertible).toBe(invertible));
|
||||
if (invertible) {
|
||||
test("Instance function inverts correctly", () =>
|
||||
test("Instance function inverts correctly", async () =>
|
||||
expectedEvaluation != null &&
|
||||
!Decimal.isNaN(expectedEvaluation) &&
|
||||
expect(instanceFormula.invert(expectedEvaluation)).toSatisfy(
|
||||
compare_tolerance(value)
|
||||
));
|
||||
expect(instanceFormula.invert(expectedEvaluation)).compare_tolerance(value));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function testAliases<T extends FormulaFunctions[]>(
|
||||
formula: Formula,
|
||||
aliases: T,
|
||||
args: FormulaSource[]
|
||||
) {
|
||||
const spy = vi.spyOn(formula, aliases[0]);
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
aliases.slice(1).forEach(name => formula[name](...args));
|
||||
expect(spy).toHaveBeenCalledTimes(aliases.length - 1);
|
||||
}
|
||||
|
||||
const testValues = [-2.5, -1, -0.1, 0, 0.1, 1, 2.5] as const;
|
||||
let testValueFormulas: InvertibleFormula[] = [];
|
||||
|
||||
const invertibleZeroParamFunctionNames = [
|
||||
["neg", "negate", "negated"],
|
||||
["recip", "reciprocal", "reciprocate"],
|
||||
["log10"],
|
||||
["log2"],
|
||||
["ln"],
|
||||
["pow10"],
|
||||
["exp"],
|
||||
["sqr"],
|
||||
["sqrt"],
|
||||
["cube"],
|
||||
["cbrt"],
|
||||
["lambertw"],
|
||||
["ssqrt"],
|
||||
["sin"],
|
||||
["cos"],
|
||||
["tan"],
|
||||
["asin"],
|
||||
["acos"],
|
||||
["atan"],
|
||||
["sinh"],
|
||||
["cosh"],
|
||||
["tanh"],
|
||||
["asinh"],
|
||||
["acosh"],
|
||||
["atanh"]
|
||||
"neg",
|
||||
"recip",
|
||||
"log10",
|
||||
"log2",
|
||||
"ln",
|
||||
"pow10",
|
||||
"exp",
|
||||
"sqr",
|
||||
"sqrt",
|
||||
"cube",
|
||||
"cbrt",
|
||||
"lambertw",
|
||||
"ssqrt",
|
||||
"sin",
|
||||
"cos",
|
||||
"tan",
|
||||
"asin",
|
||||
"acos",
|
||||
"atan",
|
||||
"sinh",
|
||||
"cosh",
|
||||
"tanh",
|
||||
"asinh",
|
||||
"acosh",
|
||||
"atanh"
|
||||
] as const;
|
||||
|
||||
const nonInvertibleZeroParamFunctionNames = [
|
||||
["abs"],
|
||||
["sign", "sgn"],
|
||||
["round"],
|
||||
["floor"],
|
||||
["ceil"],
|
||||
["trunc"],
|
||||
["pLog10"],
|
||||
["absLog10"],
|
||||
["factorial"],
|
||||
["gamma"],
|
||||
["lngamma"]
|
||||
"abs",
|
||||
"sign",
|
||||
"round",
|
||||
"floor",
|
||||
"ceil",
|
||||
"trunc",
|
||||
"pLog10",
|
||||
"absLog10",
|
||||
"factorial",
|
||||
"gamma",
|
||||
"lngamma"
|
||||
] as const;
|
||||
|
||||
const invertibleOneParamFunctionNames = [
|
||||
["add", "plus"],
|
||||
["sub", "subtract", "minus"],
|
||||
["mul", "multiply", "times"],
|
||||
["div", "divide"],
|
||||
["log", "logarithm"],
|
||||
["pow"],
|
||||
["root"],
|
||||
["slog"]
|
||||
"add",
|
||||
"sub",
|
||||
"mul",
|
||||
"div",
|
||||
"log",
|
||||
"pow",
|
||||
"root",
|
||||
"slog"
|
||||
] as const;
|
||||
|
||||
const nonInvertibleOneParamFunctionNames = [
|
||||
["max"],
|
||||
["min"],
|
||||
["maxabs"],
|
||||
["minabs"],
|
||||
["clampMin"],
|
||||
["clampMax"],
|
||||
["layeradd10"]
|
||||
"max",
|
||||
"min",
|
||||
"maxabs",
|
||||
"minabs",
|
||||
"clampMin",
|
||||
"clampMax",
|
||||
"layeradd10"
|
||||
] as const;
|
||||
|
||||
const invertibleTwoParamFunctionNames = [["tetrate"], ["layeradd"]] as const;
|
||||
const invertibleTwoParamFunctionNames = ["tetrate", "layeradd", "iteratedexp"] as const;
|
||||
|
||||
const nonInvertibleTwoParamFunctionNames = [
|
||||
["clamp"],
|
||||
["iteratedexp"],
|
||||
["iteratedlog"],
|
||||
["pentate"]
|
||||
] as const;
|
||||
const nonInvertibleTwoParamFunctionNames = ["clamp", "iteratedlog", "pentate"] as const;
|
||||
|
||||
describe("Creating Formulas", () => {
|
||||
describe.concurrent("Creating Formulas", () => {
|
||||
beforeAll(() => {
|
||||
testValueFormulas = testValues.map(v => Formula.constant(v));
|
||||
});
|
||||
|
@ -204,6 +233,23 @@ describe("Creating Formulas", () => {
|
|||
testConstant("ref", () => Formula.constant(ref(10)));
|
||||
});
|
||||
|
||||
// Test that these are just pass-throughts so we don't need to test each one everywhere else
|
||||
describe("Function aliases", () => {
|
||||
let formula: Formula;
|
||||
beforeAll(() => {
|
||||
formula = Formula.constant(10);
|
||||
});
|
||||
test("neg", async () => testAliases(formula, ["neg", "negate", "negated"], [0]));
|
||||
test("recip", async () =>
|
||||
testAliases(formula, ["recip", "reciprocal", "reciprocate"], [0]));
|
||||
test("sign", async () => testAliases(formula, ["sign", "sgn"], [0]));
|
||||
test("add", async () => testAliases(formula, ["add", "plus"], [0]));
|
||||
test("sub", async () => testAliases(formula, ["sub", "subtract", "minus"], [0]));
|
||||
test("mul", async () => testAliases(formula, ["mul", "multiply", "times"], [0]));
|
||||
test("div", async () => testAliases(formula, ["div", "divide"], [1]));
|
||||
test("log", async () => testAliases(formula, ["log", "logarithm"], [0]));
|
||||
});
|
||||
|
||||
describe("Invertible 0-param", () => {
|
||||
invertibleZeroParamFunctionNames.forEach(names => {
|
||||
for (let i = 0; i < testValues.length; i++) {
|
||||
|
@ -258,8 +304,9 @@ describe("Creating Formulas", () => {
|
|||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Variables", () => {
|
||||
describe("Variables", () => {
|
||||
let variable: Formula;
|
||||
let constant: Formula;
|
||||
beforeAll(() => {
|
||||
|
@ -267,15 +314,13 @@ describe("Creating Formulas", () => {
|
|||
constant = Formula.constant(10);
|
||||
});
|
||||
|
||||
test("Created variable is marked as a variable", () =>
|
||||
expect(variable.hasVariable).toBe(true));
|
||||
test("Created variable is marked as a variable", () => expect(variable.hasVariable).toBe(true));
|
||||
test("Evaluate() returns variable's value", () =>
|
||||
expect(variable.evaluate()).toSatisfy(compare_tolerance(10)));
|
||||
test("Invert() is pass-through", () =>
|
||||
expect(variable.invert(100)).toSatisfy(compare_tolerance(100)));
|
||||
expect(variable.evaluate()).compare_tolerance(10));
|
||||
test("Invert() is pass-through", () => expect(variable.invert(100)).compare_tolerance(100));
|
||||
|
||||
test("Nested variable is marked as having a variable", () =>
|
||||
expect(variable.add(10).div(3).pow(2).hasVariable).toBe(false));
|
||||
expect(variable.add(10).div(3).pow(2).hasVariable).toBe(true));
|
||||
test("Nested non-variable is marked as not having a variable", () =>
|
||||
expect(constant.add(10).div(3).pow(2).hasVariable).toBe(false));
|
||||
|
||||
|
@ -284,13 +329,13 @@ describe("Creating Formulas", () => {
|
|||
expect(formula.invertible).toBe(expectedBool);
|
||||
expect(formula.hasVariable).toBe(expectedBool);
|
||||
}
|
||||
invertibleZeroParamFunctionNames.flat().forEach(name => {
|
||||
invertibleZeroParamFunctionNames.forEach(name => {
|
||||
describe(name, () => {
|
||||
test(`${name}(var) is marked as invertible and having a variable`, () =>
|
||||
checkFormula(Formula[name](variable)));
|
||||
});
|
||||
});
|
||||
invertibleOneParamFunctionNames.flat().forEach(name => {
|
||||
invertibleOneParamFunctionNames.forEach(name => {
|
||||
describe(name, () => {
|
||||
test(`${name}(var, const) is marked as invertible and having a variable`, () =>
|
||||
checkFormula(Formula[name](variable, constant)));
|
||||
|
@ -300,7 +345,7 @@ describe("Creating Formulas", () => {
|
|||
checkFormula(Formula[name](variable, variable), false));
|
||||
});
|
||||
});
|
||||
invertibleTwoParamFunctionNames.flat().forEach(name => {
|
||||
invertibleTwoParamFunctionNames.forEach(name => {
|
||||
describe(name, () => {
|
||||
test(`${name}(var, const, const) is marked as invertible and having a variable`, () =>
|
||||
checkFormula(Formula[name](variable, constant, constant)));
|
||||
|
@ -325,13 +370,13 @@ describe("Creating Formulas", () => {
|
|||
expect(formula.invertible).toBe(false);
|
||||
expect(formula.hasVariable).toBe(false);
|
||||
}
|
||||
nonInvertibleZeroParamFunctionNames.flat().forEach(name => {
|
||||
nonInvertibleZeroParamFunctionNames.forEach(name => {
|
||||
describe(name, () => {
|
||||
test(`${name}(var) is marked as not invertible and not having a variable`, () =>
|
||||
checkFormula(Formula[name](variable)));
|
||||
});
|
||||
});
|
||||
nonInvertibleOneParamFunctionNames.flat().forEach(name => {
|
||||
nonInvertibleOneParamFunctionNames.forEach(name => {
|
||||
describe(name, () => {
|
||||
test(`${name}(var, const) is marked as not invertible and not having a variable`, () =>
|
||||
checkFormula(Formula[name](variable, constant)));
|
||||
|
@ -341,13 +386,13 @@ describe("Creating Formulas", () => {
|
|||
checkFormula(Formula[name](variable, variable)));
|
||||
});
|
||||
});
|
||||
nonInvertibleTwoParamFunctionNames.flat().forEach(name => {
|
||||
nonInvertibleTwoParamFunctionNames.forEach(name => {
|
||||
describe(name, () => {
|
||||
test(`${name}(var, const, const) is marked as invertible and having a variable`, () =>
|
||||
test(`${name}(var, const, const) is marked as not invertible and not having a variable`, () =>
|
||||
checkFormula(Formula[name](variable, constant, constant)));
|
||||
test(`${name}(const, var, const) is marked as invertible and having a variable`, () =>
|
||||
test(`${name}(const, var, const) is marked as not invertible and not having a variable`, () =>
|
||||
checkFormula(Formula[name](constant, variable, constant)));
|
||||
test(`${name}(const, const, var) is marked as invertible and having a variable`, () =>
|
||||
test(`${name}(const, const, var) is marked as not invertible and not having a variable`, () =>
|
||||
checkFormula(Formula[name](constant, constant, variable)));
|
||||
test(`${name}(var, var, const) is marked as not invertible and not having a variable`, () =>
|
||||
checkFormula(Formula[name](variable, variable, constant)));
|
||||
|
@ -368,39 +413,38 @@ describe("Creating Formulas", () => {
|
|||
variable = Formula.variable(2);
|
||||
constant = Formula.constant(3);
|
||||
});
|
||||
invertibleOneParamFunctionNames.flat().forEach(name =>
|
||||
invertibleOneParamFunctionNames.forEach(name =>
|
||||
describe(name, () => {
|
||||
test(`${name}(var, const).invert()`, () => {
|
||||
const formula = Formula[name](variable, constant);
|
||||
const result = formula.evaluate();
|
||||
expect(formula.invert(result)).toSatisfy(compare_tolerance(2));
|
||||
expect(formula.invert(result)).compare_tolerance(2);
|
||||
});
|
||||
test(`${name}(const, var).invert()`, () => {
|
||||
const formula = Formula[name](constant, variable);
|
||||
const result = formula.evaluate();
|
||||
expect(formula.invert(result)).toSatisfy(compare_tolerance(2));
|
||||
expect(formula.invert(result)).compare_tolerance(2);
|
||||
});
|
||||
})
|
||||
);
|
||||
invertibleTwoParamFunctionNames.flat().forEach(name =>
|
||||
invertibleTwoParamFunctionNames.forEach(name =>
|
||||
describe(name, () => {
|
||||
test(`${name}(var, const, const).invert()`, () => {
|
||||
const formula = Formula[name](variable, constant, constant);
|
||||
const result = formula.evaluate();
|
||||
expect(formula.invert(result)).toSatisfy(compare_tolerance(2));
|
||||
expect(formula.invert(result)).compare_tolerance(2);
|
||||
});
|
||||
test(`${name}(const, var, const).invert()`, () => {
|
||||
const formula = Formula[name](constant, variable, constant);
|
||||
const result = formula.evaluate();
|
||||
expect(formula.invert(result)).toSatisfy(compare_tolerance(2));
|
||||
expect(formula.invert(result)).compare_tolerance(2);
|
||||
});
|
||||
test(`${name}(const, const, var).invert()`, () => {
|
||||
const formula = Formula[name](constant, constant, variable);
|
||||
const result = formula.evaluate();
|
||||
expect(formula.invert(result)).toSatisfy(compare_tolerance(2));
|
||||
expect(formula.invert(result)).compare_tolerance(2);
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue