tests(stdlib): reorganize and expand StandardLibrary tests

- Switch tests to use Initializer.InitializeAll for setup
- Split stdlib tests by category: Angle, Rounding, Trig (incl. hyperbolic), NumberTheory, Random
- Add comprehensive tests invoking functions via InputInterpreter.Interpret()
- Register Bernoulli in Initializer so random tests cover
This commit is contained in:
Jordan Wages 2025-08-21 04:04:46 -05:00
commit 106efbc86e
7 changed files with 410 additions and 3 deletions

View file

@ -118,6 +118,7 @@ namespace CSMic.StandardLibrary
}
inputInterpreter.RegisterFunction(new FairFlip());
inputInterpreter.RegisterFunction(new Bernoulli());
inputInterpreter.RegisterFunction(new RandomUniform());
inputInterpreter.RegisterFunction(new RandomUniformSpread());
inputInterpreter.RegisterFunction(new RandomNormal());

View file

@ -0,0 +1,57 @@
using System.Globalization;
using CSMic;
using CSMic.StandardLibrary;
namespace CSMic.Tests;
public class AngleFunctionsTests
{
private InputInterpreter _interp = null!;
[SetUp]
public void Setup()
{
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
_interp = new InputInterpreter();
Initializer.InitializeAll(_interp);
}
private static void AssertSuccess(decimal result, decimal expected, InputInterpreter interp)
{
Assert.That(result, Is.EqualTo(expected));
Assert.That(interp.NumericValue, Is.EqualTo(expected));
Assert.That(interp.StringValue, Is.EqualTo(string.Empty));
}
private static void AssertApprox(decimal result, decimal expected, decimal tol, InputInterpreter interp)
{
Assert.That(Math.Abs(result - expected) <= tol, $"Expected ~{expected} ± {tol}, got {result}");
Assert.That(interp.StringValue, Is.EqualTo(string.Empty));
}
[Test]
public void Degrees_Works_OnCommonValues()
{
AssertSuccess(_interp.Interpret("degrees(0)"), 0m, _interp);
AssertSuccess(_interp.Interpret("degrees(pi)"), 180m, _interp);
AssertSuccess(_interp.Interpret("degrees(tau)"), 360m, _interp);
}
[Test]
public void Radians_Works_OnCommonValues()
{
AssertSuccess(_interp.Interpret("radians(0)"), 0m, _interp);
var res = _interp.Interpret("radians(180)");
// Approximately pi
AssertApprox(res, 3.1415926535897931m, 0.0000000000001m, _interp);
}
[Test]
public void WrapAngle_WrapsIntoRange()
{
AssertSuccess(_interp.Interpret("wrapangle(-10, 0, 360)"), 350m, _interp);
AssertSuccess(_interp.Interpret("wrapangle(370, 0, 360)"), 10m, _interp);
AssertSuccess(_interp.Interpret("wrapangle(361, -180, 180)"), -179m, _interp);
}
}

View file

@ -0,0 +1,84 @@
using System.Globalization;
using CSMic;
using CSMic.StandardLibrary;
namespace CSMic.Tests;
public class NumberTheoryFunctionsTests
{
private InputInterpreter _interp = null!;
[SetUp]
public void Setup()
{
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
_interp = new InputInterpreter();
Initializer.InitializeAll(_interp);
}
private static void AssertSuccess(decimal result, decimal expected, InputInterpreter interp)
{
Assert.That(result, Is.EqualTo(expected));
Assert.That(interp.NumericValue, Is.EqualTo(expected));
Assert.That(interp.StringValue, Is.EqualTo(string.Empty));
}
private static void AssertApprox(decimal result, decimal expected, decimal tol, InputInterpreter interp)
{
Assert.That(Math.Abs(result - expected) <= tol, $"Expected ~{expected} ± {tol}, got {result}");
Assert.That(interp.StringValue, Is.EqualTo(string.Empty));
}
[Test]
public void Gcd_Works_And_Validates()
{
AssertSuccess(_interp.Interpret("gcd(54, 24)"), 6m, _interp);
AssertSuccess(_interp.Interpret("gcd(7, 3)"), 1m, _interp);
AssertSuccess(_interp.Interpret("gcd(0, 5)"), 0m, _interp);
AssertSuccess(_interp.Interpret("gcd(5.5, 2)"), 0m, _interp);
}
[Test]
public void Lcm_Works_And_Validates()
{
AssertSuccess(_interp.Interpret("lcm(4, 6)"), 12m, _interp);
AssertSuccess(_interp.Interpret("lcm(21, 6)"), 42m, _interp);
AssertSuccess(_interp.Interpret("lcm(-2, 6)"), 0m, _interp);
AssertSuccess(_interp.Interpret("lcm(2.5, 6)"), 0m, _interp);
}
[Test]
public void BinomialCoefficient_Works_And_Validates()
{
AssertSuccess(_interp.Interpret("ncr(5, 2)"), 10m, _interp);
AssertSuccess(_interp.Interpret("ncr(20, 0)"), 1m, _interp);
AssertSuccess(_interp.Interpret("ncr(5, -1)"), 0m, _interp);
AssertSuccess(_interp.Interpret("ncr(5, 6)"), 0m, _interp);
AssertSuccess(_interp.Interpret("ncr(5.2, 2)"), 0m, _interp);
}
[Test]
public void Permutations_Works_And_Validates()
{
AssertSuccess(_interp.Interpret("npr(5, 2)"), 20m, _interp);
AssertSuccess(_interp.Interpret("npr(6, 6)"), 720m, _interp);
AssertSuccess(_interp.Interpret("npr(5, 6)"), 0m, _interp);
AssertSuccess(_interp.Interpret("npr(5.2, 2)"), 0m, _interp);
}
[Test]
public void Factorial_Works_And_Validates()
{
AssertSuccess(_interp.Interpret("fac(0)"), 1m, _interp);
AssertSuccess(_interp.Interpret("fac(1)"), 1m, _interp);
AssertSuccess(_interp.Interpret("fac(5)"), 120m, _interp);
AssertSuccess(_interp.Interpret("fac(20)"), 2432902008176640000m, _interp);
AssertSuccess(_interp.Interpret("fac(-1)"), 0m, _interp);
AssertSuccess(_interp.Interpret("fac(21)"), 0m, _interp);
var res = _interp.Interpret("fac(0.5)");
// Gamma(1.5) ~= sqrt(pi)/2 ~= 0.8862269254527579
AssertApprox(res, 0.8862269254527579m, 0.0000000000001m, _interp);
}
}

View file

@ -0,0 +1,99 @@
using System.Globalization;
using CSMic;
using CSMic.StandardLibrary;
namespace CSMic.Tests;
public class RandomFunctionsTests
{
private InputInterpreter _interp = null!;
[SetUp]
public void Setup()
{
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
_interp = new InputInterpreter();
Initializer.InitializeAll(_interp);
}
private static void AssertSuccess(decimal result, decimal expected, InputInterpreter interp)
{
Assert.That(result, Is.EqualTo(expected));
Assert.That(interp.NumericValue, Is.EqualTo(expected));
Assert.That(interp.StringValue, Is.EqualTo(string.Empty));
}
[Test]
public void Flip_ReturnsBooleanAsNumeric()
{
for (int i = 0; i < 5; i++)
{
var res = _interp.Interpret("flip()");
Assert.That(res == 0m || res == 1m, "flip() must return 0 or 1");
Assert.That(_interp.StringValue, Is.EqualTo(string.Empty));
}
}
[Test]
public void Rand_IsWithinUnitInterval()
{
for (int i = 0; i < 5; i++)
{
var res = _interp.Interpret("rand()");
Assert.That(res, Is.GreaterThanOrEqualTo(0m));
Assert.That(res, Is.LessThan(1m));
Assert.That(_interp.StringValue, Is.EqualTo(string.Empty));
}
}
[Test]
public void RandSpread_IsWithinBounds_And_Validates()
{
for (int i = 0; i < 5; i++)
{
var res = _interp.Interpret("rands(5, 10)");
Assert.That(res, Is.GreaterThanOrEqualTo(5m));
Assert.That(res, Is.LessThan(10m));
Assert.That(_interp.StringValue, Is.EqualTo(string.Empty));
}
AssertSuccess(_interp.Interpret("rands(10, 5)"), 0m, _interp);
}
[Test]
public void RandNormal_ProducesNumeric()
{
bool sawNonZero = false;
for (int i = 0; i < 10; i++)
{
var res = _interp.Interpret("randn()");
if (res != 0m) sawNonZero = true;
Assert.That(_interp.StringValue, Is.EqualTo(string.Empty));
}
Assert.That(sawNonZero, Is.True);
}
[Test]
public void RandNormalSpread_ValidatesBounds()
{
AssertSuccess(_interp.Interpret("randns(10, 5)"), 0m, _interp);
// With valid bounds, it should succeed and produce a numeric (any value)
var res = _interp.Interpret("randns(5, 10)");
Assert.That(_interp.StringValue, Is.EqualTo(string.Empty));
}
[Test]
public void Bernoulli_ValidatesP_And_ReturnsBoolean()
{
// Invalid p produces soft error (message populated)
_interp.Interpret("bern(-0.1)");
Assert.That(string.IsNullOrEmpty(_interp.StringValue), Is.False);
_interp.Interpret("bern(1.1)");
Assert.That(string.IsNullOrEmpty(_interp.StringValue), Is.False);
// Valid p returns 0 or 1 without error
var res = _interp.Interpret("bern(0.7)");
Assert.That(res == 0m || res == 1m);
Assert.That(_interp.StringValue, Is.EqualTo(string.Empty));
}
}

View file

@ -0,0 +1,69 @@
using System.Globalization;
using CSMic;
using CSMic.StandardLibrary;
namespace CSMic.Tests;
public class RoundingFunctionsTests
{
private InputInterpreter _interp = null!;
[SetUp]
public void Setup()
{
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
_interp = new InputInterpreter();
Initializer.InitializeAll(_interp);
}
private static void AssertSuccess(decimal result, decimal expected, InputInterpreter interp)
{
Assert.That(result, Is.EqualTo(expected));
Assert.That(interp.NumericValue, Is.EqualTo(expected));
Assert.That(interp.StringValue, Is.EqualTo(string.Empty));
}
[Test]
public void Floor_Works()
{
AssertSuccess(_interp.Interpret("floor(2.3)"), 2m, _interp);
AssertSuccess(_interp.Interpret("floor(-2.3)"), -3m, _interp);
}
[Test]
public void Ceiling_Works()
{
AssertSuccess(_interp.Interpret("ceiling(2.3)"), 3m, _interp);
AssertSuccess(_interp.Interpret("ceiling(-2.3)"), -2m, _interp);
}
[Test]
public void Truncate_Works()
{
AssertSuccess(_interp.Interpret("truncate(2.9)"), 2m, _interp);
AssertSuccess(_interp.Interpret("truncate(-2.9)"), -2m, _interp);
}
[Test]
public void Fractional_Works()
{
AssertSuccess(_interp.Interpret("frac(2.75)"), 0.75m, _interp);
AssertSuccess(_interp.Interpret("frac(-2.75)"), -0.75m, _interp);
}
[Test]
public void Round_WithPrecision_Works()
{
AssertSuccess(_interp.Interpret("round(2.346, 2)"), 2.35m, _interp);
AssertSuccess(_interp.Interpret("round(2.344, 2)"), 2.34m, _interp);
}
[Test]
public void Clamp_Works()
{
AssertSuccess(_interp.Interpret("clamp(5, 1, 10)"), 5m, _interp);
AssertSuccess(_interp.Interpret("clamp(0, 1, 10)"), 1m, _interp);
AssertSuccess(_interp.Interpret("clamp(11, 1, 10)"), 10m, _interp);
}
}

View file

@ -3,7 +3,6 @@ using CSMic.StandardLibrary;
using NUnit.Framework;
using CSMic.StandardLibrary.Functions;
using System.Globalization;
using System.Reflection.Metadata;
namespace CSMic.Tests;
@ -16,8 +15,8 @@ public class StdlibFunctionsTests
{
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
_interp = new InputInterpreter();
Constants.Initialize(_interp);
Functions.Initialize(_interp); ;
// Initialize full standard library (functions + constants)
Initializer.InitializeAll(_interp);
}
private static void AssertSuccess(decimal result, decimal expected, InputInterpreter interp)

View file

@ -0,0 +1,98 @@
using System.Globalization;
using CSMic;
using CSMic.StandardLibrary;
namespace CSMic.Tests;
public class TrigonometryFunctionsTests
{
private InputInterpreter _interp = null!;
[SetUp]
public void Setup()
{
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
_interp = new InputInterpreter();
Initializer.InitializeAll(_interp);
}
private static void AssertSuccess(decimal result, decimal expected, InputInterpreter interp)
{
Assert.That(result, Is.EqualTo(expected));
Assert.That(interp.NumericValue, Is.EqualTo(expected));
Assert.That(interp.StringValue, Is.EqualTo(string.Empty));
}
private static void AssertApprox(decimal result, decimal expected, decimal tol, InputInterpreter interp)
{
Assert.That(Math.Abs(result - expected) <= tol, $"Expected ~{expected} ± {tol}, got {result}");
Assert.That(interp.StringValue, Is.EqualTo(string.Empty));
}
[Test]
public void BasicTrig_ZeroPoints()
{
AssertSuccess(_interp.Interpret("sin(0)"), 0m, _interp);
AssertSuccess(_interp.Interpret("cos(0)"), 1m, _interp);
AssertSuccess(_interp.Interpret("tan(0)"), 0m, _interp);
}
[Test]
public void BasicTrig_CommonAngles()
{
AssertApprox(_interp.Interpret("sin(pi/2)"), 1m, 0.0000000000001m, _interp);
AssertApprox(_interp.Interpret("cos(pi/2)"), 0m, 0.0000000000001m, _interp);
AssertApprox(_interp.Interpret("tan(pi/4)"), 1m, 0.0000000000001m, _interp);
}
[Test]
public void InverseTrig_ZeroPoints()
{
AssertSuccess(_interp.Interpret("asin(0)"), 0m, _interp);
AssertSuccess(_interp.Interpret("acos(1)"), 0m, _interp);
AssertSuccess(_interp.Interpret("atan(0)"), 0m, _interp);
AssertSuccess(_interp.Interpret("atan2(0, 1)"), 0m, _interp);
}
[Test]
public void InverseTrig_CommonValues()
{
// asin(1) ~= pi/2, acos(0) ~= pi/2, atan(1) ~= pi/4, atan2(1,0) ~= pi/2
AssertApprox(_interp.Interpret("asin(1)"), 1.5707963267948966m, 0.0000000000001m, _interp);
AssertApprox(_interp.Interpret("acos(0)"), 1.5707963267948966m, 0.0000000000001m, _interp);
AssertApprox(_interp.Interpret("atan(1)"), 0.7853981633974483m, 0.0000000000001m, _interp);
AssertApprox(_interp.Interpret("atan2(1, 0)"), 1.5707963267948966m, 0.0000000000001m, _interp);
}
}
public class HyperbolicTrigFunctionsTests
{
private InputInterpreter _interp = null!;
[SetUp]
public void Setup()
{
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
_interp = new InputInterpreter();
Initializer.InitializeAll(_interp);
}
private static void AssertSuccess(decimal result, decimal expected, InputInterpreter interp)
{
Assert.That(result, Is.EqualTo(expected));
Assert.That(interp.NumericValue, Is.EqualTo(expected));
Assert.That(interp.StringValue, Is.EqualTo(string.Empty));
}
[Test]
public void Hyperbolic_ZeroPoints()
{
AssertSuccess(_interp.Interpret("sinh(0)"), 0m, _interp);
AssertSuccess(_interp.Interpret("cosh(0)"), 1m, _interp);
AssertSuccess(_interp.Interpret("tanh(0)"), 0m, _interp);
AssertSuccess(_interp.Interpret("asinh(0)"), 0m, _interp);
AssertSuccess(_interp.Interpret("acosh(1)"), 0m, _interp);
AssertSuccess(_interp.Interpret("atanh(0)"), 0m, _interp);
}
}