using Godosa.Core.Runs; using Xunit; namespace Godosa.Core.Tests.Runs; public class RunsTests { private static RunFlags Parse(params string[] args) { var flags = new RunFlags(); for (var i = 0; i < args.Length; i++) Assert.True(RunFlags.TryTake(args, ref i, ref flags), args[i]); return flags; } [Fact] public void Flags_DeterministicWithAndWithoutSeed() { Assert.Equal(new RunFlags(Deterministic: true, Seed: 7), Parse("--deterministic", "7")); Assert.Equal(new RunFlags(Deterministic: true), Parse("--deterministic")); Assert.Equal(1UL, Parse("--deterministic").SeedOr(1)); Assert.Equal(7UL, Parse("--deterministic", "7").SeedOr(1)); } [Fact] public void Flags_SeedNotTakenWhenNextIsAnOption() { string[] args = ["--deterministic", "--mute"]; var flags = new RunFlags(); var i = 0; Assert.True(RunFlags.TryTake(args, ref i, ref flags)); Assert.Equal(0, i); i = 1; Assert.False(RunFlags.TryTake(args, ref i, ref flags)); } [Fact] public void Flags_DigestEveryAndHeadless() { Assert.Equal(new RunFlags(Digest: true), Parse("--digest")); Assert.Equal(new RunFlags(Digest: true, DigestEvery: 60), Parse("--digest", "every=60")); Assert.Equal(new RunFlags(Deterministic: true, Headless: true), Parse("--headless")); Assert.Equal(new RunFlags(Deterministic: true, Seed: 3, Headless: true), Parse("--headless", "--deterministic", "3")); } [Fact] public void Flags_BadDigestEvery_Throws() => Assert.Throws(() => Parse("--digest", "every=0")); [Fact] public void Clock_DeterministicFixedStep() { var clock = new FrameClock(true); Assert.Equal(1.0 / 60, clock.Next(0.5)); Assert.Equal(1.0 / 60, clock.Next(0.001)); Assert.Equal(2, clock.Frame); Assert.Equal(2.0 / 60, clock.Seconds, 12); } [Fact] public void Clock_WallClamped() { var clock = new FrameClock(false); Assert.Equal(0.1, clock.Next(0.5)); Assert.Equal(0.02, clock.Next(0.02)); Assert.Equal(0, clock.Next(-1)); Assert.Equal(3, clock.Frame); } [Fact] public void Clock_Held_NoTimeNoFrame() { var clock = new FrameClock(true) { Held = true }; Assert.Equal(0, clock.Next(1.0 / 60)); Assert.Equal(0, clock.Frame); Assert.Equal(0, clock.Seconds); clock.Held = false; clock.Next(0); Assert.Equal(1, clock.Frame); } // Published FNV-1a 64 vectors (isthe.com/chongo/tech/comp/fnv). [Theory] [InlineData("", 0xcbf29ce484222325UL)] [InlineData("a", 0xaf63dc4c8601ec8cUL)] [InlineData("foobar", 0x85944171f73967e8UL)] public void Digest_FnvVectors(string text, ulong expected) { var d = new StateDigest(); d.Add(text); Assert.Equal(expected, d.Value); Assert.Equal(expected, StateDigest.Of(System.Text.Encoding.UTF8.GetBytes(text))); } // Hand-checked: FNV-1a 64 of the little-endian bytes 01 00 00 00 and of 1.5 as an IEEE double. [Fact] public void Digest_NumbersLittleEndian() { var u = new StateDigest(); u.Add(1u); Assert.Equal(0xad2aca7747985764UL, u.Value); var d = new StateDigest(); d.Add(1.5); Assert.Equal(0xaa95e93229a27c80UL, d.Value); Assert.Equal("aa95e93229a27c80", d.Hex); } [Fact] public void Line_Format() => Assert.Equal("digest 120 3.5 000000000000beef", DigestLine.Format(120, 3.5, 0xbeef)); [Fact] public void Line_FormatRoundsGameTime() => Assert.Equal("digest 1 0.017 0000000000000001", DigestLine.Format(1, 1.0 / 60, 1)); [Fact] public void Line_Due() { Assert.True(DigestLine.Due(60, 60)); Assert.False(DigestLine.Due(59, 60)); Assert.False(DigestLine.Due(0, 60)); Assert.False(DigestLine.Due(60, 0)); } // SplitMix64 seeded 0: outputs e220a8397b1dcdaf, 6e789e6aa1b965f4 (published reference sequence). [Fact] public void Seeds_SplitMixReference() { Assert.Equal(0xe220a8397b1dcdafUL, SeedStreams.Of(0, 0)); Assert.Equal(0x6e789e6aa1b965f4UL, SeedStreams.Of(0, 1)); Assert.Equal(0x63cbe1e459320dd7UL, SeedStreams.Of(7, 0)); } [Fact] public void Loop_StopsWhenFrameReturnsFalse() { var seen = new List(); var (frames, capped) = HeadlessLoop.Run(n => { seen.Add(n); return n < 3; }, null); Assert.Equal([1L, 2, 3], seen); Assert.Equal(3, frames); Assert.False(capped); } [Fact] public void Loop_FramesLimit() => Assert.Equal((5L, false), HeadlessLoop.Run(_ => true, 5)); [Fact] public void Loop_Cap() => Assert.Equal((10L, true), HeadlessLoop.Run(_ => true, null, cap: 10)); }