From 106efbc86ee26d3f09e496ec8cf1394f97308713 Mon Sep 17 00:00:00 2001 From: wagesj45 Date: Thu, 21 Aug 2025 04:04:46 -0500 Subject: [PATCH] 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 --- src/StandardLibrary/Initializer.cs | 1 + src/Tests/AngleFunctionsTests.cs | 57 ++++++++++++++ src/Tests/NumberTheoryFunctionsTests.cs | 84 +++++++++++++++++++++ src/Tests/RandomFunctionsTests.cs | 99 +++++++++++++++++++++++++ src/Tests/RoundingFunctionsTests.cs | 69 +++++++++++++++++ src/Tests/StdlibFunctionsTests.cs | 5 +- src/Tests/TrigonometryFunctionsTests.cs | 98 ++++++++++++++++++++++++ 7 files changed, 410 insertions(+), 3 deletions(-) create mode 100644 src/Tests/AngleFunctionsTests.cs create mode 100644 src/Tests/NumberTheoryFunctionsTests.cs create mode 100644 src/Tests/RandomFunctionsTests.cs create mode 100644 src/Tests/RoundingFunctionsTests.cs create mode 100644 src/Tests/TrigonometryFunctionsTests.cs diff --git a/src/StandardLibrary/Initializer.cs b/src/StandardLibrary/Initializer.cs index 386514e..bdb246a 100644 --- a/src/StandardLibrary/Initializer.cs +++ b/src/StandardLibrary/Initializer.cs @@ -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()); diff --git a/src/Tests/AngleFunctionsTests.cs b/src/Tests/AngleFunctionsTests.cs new file mode 100644 index 0000000..f16787b --- /dev/null +++ b/src/Tests/AngleFunctionsTests.cs @@ -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); + } +} + diff --git a/src/Tests/NumberTheoryFunctionsTests.cs b/src/Tests/NumberTheoryFunctionsTests.cs new file mode 100644 index 0000000..e4c09b4 --- /dev/null +++ b/src/Tests/NumberTheoryFunctionsTests.cs @@ -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); + } +} + diff --git a/src/Tests/RandomFunctionsTests.cs b/src/Tests/RandomFunctionsTests.cs new file mode 100644 index 0000000..6c68045 --- /dev/null +++ b/src/Tests/RandomFunctionsTests.cs @@ -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)); + } +} diff --git a/src/Tests/RoundingFunctionsTests.cs b/src/Tests/RoundingFunctionsTests.cs new file mode 100644 index 0000000..8153fcb --- /dev/null +++ b/src/Tests/RoundingFunctionsTests.cs @@ -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); + } +} + diff --git a/src/Tests/StdlibFunctionsTests.cs b/src/Tests/StdlibFunctionsTests.cs index add1b05..263795e 100644 --- a/src/Tests/StdlibFunctionsTests.cs +++ b/src/Tests/StdlibFunctionsTests.cs @@ -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) diff --git a/src/Tests/TrigonometryFunctionsTests.cs b/src/Tests/TrigonometryFunctionsTests.cs new file mode 100644 index 0000000..4c2665f --- /dev/null +++ b/src/Tests/TrigonometryFunctionsTests.cs @@ -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); + } +} +