aboutsummaryrefslogtreecommitdiffziptar.gz
path: root/src/Godosa.Core.Tests/Diagnostics
diff options
context:
space:
mode:
authorgodosa <godosa@godosa.eu>2026-10-06 23:39:36 +0200
committergodosa <godosa@godosa.eu>2026-10-06 23:39:36 +0200
commit39066900773e7857faf2d02e7ed51b71d219d97d (patch)
treeffab8e4ddd971626776c3b1f4ce1da2c186c2187 /src/Godosa.Core.Tests/Diagnostics
downloadgodosa-engine-39066900773e7857faf2d02e7ed51b71d219d97d.tar.gz
godosa-engine-39066900773e7857faf2d02e7ed51b71d219d97d.zip
godosa-engine: initial public history
Diffstat (limited to 'src/Godosa.Core.Tests/Diagnostics')
-rw-r--r--src/Godosa.Core.Tests/Diagnostics/DebugFlagsTests.cs59
-rw-r--r--src/Godosa.Core.Tests/Diagnostics/FrameTimeStatsTests.cs97
-rw-r--r--src/Godosa.Core.Tests/Diagnostics/GpuAndCsvTests.cs53
-rw-r--r--src/Godosa.Core.Tests/Diagnostics/PerfCsvTests.cs38
4 files changed, 247 insertions, 0 deletions
diff --git a/src/Godosa.Core.Tests/Diagnostics/DebugFlagsTests.cs b/src/Godosa.Core.Tests/Diagnostics/DebugFlagsTests.cs
new file mode 100644
index 0000000..c67042e
--- /dev/null
+++ b/src/Godosa.Core.Tests/Diagnostics/DebugFlagsTests.cs
@@ -0,0 +1,59 @@
+using Godosa.Core.Diagnostics;
+using Xunit;
+
+namespace Godosa.Core.Tests.Diagnostics;
+
+public class DebugFlagsTests
+{
+ [Fact]
+ public void Categories_AreCommaSeparated_Trimmed_CaseInsensitive()
+ {
+ var flags = new DebugFlags(" input , AUDIO");
+ Assert.True(flags.On("input"));
+ Assert.True(flags.On("audio"));
+ Assert.False(flags.On("frames"));
+ }
+
+ [Fact]
+ public void All_TurnsOnEveryCategory_ButNotExactSwitches()
+ {
+ var flags = new DebugFlags("all");
+ Assert.True(flags.On("script"));
+ Assert.False(flags.Exact("nohud"));
+ }
+
+ [Fact]
+ public void Exact_MatchesOnlyTheNamedSwitch()
+ {
+ var flags = new DebugFlags("frames,NoHud");
+ Assert.True(flags.Exact("nohud"));
+ Assert.False(flags.Exact("input"));
+ }
+
+ [Fact]
+ public void MissingOrEmpty_TurnsNothingOn()
+ {
+ Assert.False(new DebugFlags(null).On("input"));
+ Assert.False(new DebugFlags(" , ,").On("input"));
+ }
+
+ [Fact]
+ public void EngineDebug_Apply_SetsEachLibCategory()
+ {
+ try
+ {
+ EngineDebug.Apply(new DebugFlags("script"));
+ Assert.True(EngineDebug.Script);
+ Assert.False(EngineDebug.Input);
+ Assert.False(EngineDebug.Audio);
+ EngineDebug.Apply(new DebugFlags("input,audio"));
+ Assert.True(EngineDebug.Input);
+ Assert.True(EngineDebug.Audio);
+ Assert.False(EngineDebug.Script);
+ }
+ finally
+ {
+ EngineDebug.Apply(new DebugFlags(null));
+ }
+ }
+}
diff --git a/src/Godosa.Core.Tests/Diagnostics/FrameTimeStatsTests.cs b/src/Godosa.Core.Tests/Diagnostics/FrameTimeStatsTests.cs
new file mode 100644
index 0000000..44b6758
--- /dev/null
+++ b/src/Godosa.Core.Tests/Diagnostics/FrameTimeStatsTests.cs
@@ -0,0 +1,97 @@
+using System;
+using Godosa.Core.Diagnostics;
+using Xunit;
+
+namespace Godosa.Core.Tests.Diagnostics;
+
+public class FrameTimeStatsTests
+{
+ /// <summary>99 smooth 10 ms frames plus one 100 ms hitch.</summary>
+ private static FrameTimeStats OneHitch()
+ {
+ var stats = new FrameTimeStats(capacity: 100);
+ for (int i = 0; i < 99; i++)
+ stats.Record(0.010, workSeconds: 0.002, simTicks: 0, simSeconds: 0);
+ stats.Record(0.100, workSeconds: 0.002, simTicks: 0, simSeconds: 0);
+ return stats;
+ }
+
+ [Fact]
+ public void Fps_IsFramesOverTotalTime_NotMeanOfInstantaneousFps()
+ {
+ var s = OneHitch().Summarize();
+
+ Assert.Equal(100, s.Frames);
+ // 100 frames / 1.09 s. (Mean of per-frame FPS would be (99*100 + 10)/100 = 99.1.)
+ Assert.Equal(91.743, s.Fps, 3);
+ Assert.Equal(10.9, s.AvgMs, 6);
+ }
+
+ [Fact]
+ public void Percentiles_UseNearestRank_AndMaxCatchesTheHitch()
+ {
+ var s = OneHitch().Summarize();
+
+ Assert.Equal(10.0, s.P50Ms, 6);
+ Assert.Equal(10.0, s.P95Ms, 6);
+ Assert.Equal(10.0, s.P99Ms, 6); // rank ceil(0.99*100) = 99th of 100
+ Assert.Equal(100.0, s.MaxMs, 6);
+ }
+
+ [Fact]
+ public void OnePercentLow_AveragesTheSlowestOnePercentOfFrames()
+ {
+ var s = OneHitch().Summarize();
+
+ Assert.Equal(10.0, s.OnePercentLowFps, 6); // worst 1 of 100 frames = 100 ms
+ }
+
+ [Fact]
+ public void Hitches_CountFramesOverTwiceTheMedian()
+ {
+ var stats = new FrameTimeStats(capacity: 10);
+ foreach (var ms in new[] { 10, 10, 10, 10, 10, 20, 21, 35 })
+ stats.Record(ms / 1000.0, 0, 0, 0);
+
+ Assert.Equal(2, stats.Summarize().Hitches); // median 10 → 21 and 35 (20 is not > 20)
+ }
+
+ [Fact]
+ public void Window_DropsOldestFrames()
+ {
+ var stats = new FrameTimeStats(capacity: 4);
+ foreach (var ms in new[] { 100, 100, 3, 4, 5, 6 })
+ stats.Record(ms / 1000.0, 0, 0, 0);
+
+ Span<float> intervals = stackalloc float[4];
+ int n = stats.CopyIntervalsMs(intervals);
+
+ Assert.Equal(4, n);
+ Assert.Equal(new float[] { 3, 4, 5, 6 }, intervals.ToArray());
+ Assert.Equal(6.0, stats.Summarize().MaxMs, 4);
+ }
+
+ [Fact]
+ public void Sim_ReportsAchievedTickRateAndCostPerTick()
+ {
+ var stats = new FrameTimeStats(capacity: 60);
+ for (int i = 0; i < 60; i++) // 1 s at 60 fps; a tick every other frame, 1 ms each
+ stats.Record(1.0 / 60.0, workSeconds: 0.004, simTicks: i % 2, simSeconds: (i % 2) * 0.001);
+
+ var s = stats.Summarize();
+
+ Assert.Equal(30.0, s.SimTickRate, 3);
+ Assert.Equal(1.0, s.AvgTickMs, 6);
+ Assert.Equal(4.0, s.AvgWorkMs, 6);
+ }
+
+ [Fact]
+ public void Empty_SummarizesToZerosWithoutDividingByZero()
+ {
+ var s = new FrameTimeStats().Summarize();
+
+ Assert.Equal(0, s.Frames);
+ Assert.Equal(0.0, s.Fps);
+ Assert.Equal(0.0, s.AvgTickMs);
+ }
+}
diff --git a/src/Godosa.Core.Tests/Diagnostics/GpuAndCsvTests.cs b/src/Godosa.Core.Tests/Diagnostics/GpuAndCsvTests.cs
new file mode 100644
index 0000000..46cf5ed
--- /dev/null
+++ b/src/Godosa.Core.Tests/Diagnostics/GpuAndCsvTests.cs
@@ -0,0 +1,53 @@
+using System.Globalization;
+using System.Threading;
+using Godosa.Core.Diagnostics;
+using Xunit;
+
+namespace Godosa.Core.Tests.Diagnostics;
+
+public class GpuAndCsvTests
+{
+ [Fact]
+ public void GpuSamples_AverageSeparatelyFromFrames()
+ {
+ var stats = new FrameTimeStats(capacity: 4);
+ stats.Record(0.016, 0.002, 1, 0.0001);
+ Assert.Equal(0, stats.Summarize().GpuSamples);
+ Assert.Equal(0.0, stats.Summarize().AvgGpuMs);
+
+ stats.RecordGpu(0.001);
+ stats.RecordGpu(0.003);
+
+ var s = stats.Summarize();
+ Assert.Equal(2, s.GpuSamples);
+ Assert.Equal(2.0, s.AvgGpuMs, precision: 9);
+ }
+
+ [Fact]
+ public void GpuSamples_RollOverTheWindow()
+ {
+ var stats = new FrameTimeStats(capacity: 2);
+ stats.RecordGpu(0.010);
+ stats.RecordGpu(0.001);
+ stats.RecordGpu(0.003);
+
+ Assert.Equal(2.0, stats.Summarize().AvgGpuMs, precision: 9);
+ }
+
+ [Fact]
+ public void CsvRow_IsInvariantCulture_MsWithThreeDecimals_GpuBlankWhenUnknown()
+ {
+ var previous = Thread.CurrentThread.CurrentCulture;
+ Thread.CurrentThread.CurrentCulture = new CultureInfo("de-DE"); // comma decimals must not leak in
+ try
+ {
+ Assert.Equal("t_s,frame_ms,work_ms,sim_ticks,sim_ms,gpu_ms", PerfCsv.Header());
+ Assert.Equal("12.500,16.667,2.000,1,0.100,1.250", PerfCsv.Row(12.5, 0.0166667, 0.002, 1, 0.0001, 0.00125));
+ Assert.Equal("0.000,16.667,2.000,0,0.000,", PerfCsv.Row(0, 0.0166667, 0.002, 0, 0, null));
+ }
+ finally
+ {
+ Thread.CurrentThread.CurrentCulture = previous;
+ }
+ }
+}
diff --git a/src/Godosa.Core.Tests/Diagnostics/PerfCsvTests.cs b/src/Godosa.Core.Tests/Diagnostics/PerfCsvTests.cs
new file mode 100644
index 0000000..fd64778
--- /dev/null
+++ b/src/Godosa.Core.Tests/Diagnostics/PerfCsvTests.cs
@@ -0,0 +1,38 @@
+using System.Globalization;
+using Godosa.Core.Diagnostics;
+using Xunit;
+
+namespace Godosa.Core.Tests.Diagnostics;
+
+public class PerfCsvTests
+{
+ [Fact]
+ public void Header_AppendsCallerColumnsInOrder()
+ {
+ Assert.Equal("t_s,frame_ms,work_ms,sim_ticks,sim_ms,gpu_ms", PerfCsv.Header());
+ Assert.Equal("t_s,frame_ms,work_ms,sim_ticks,sim_ms,gpu_ms,draw_calls,triangles",
+ PerfCsv.Header("draw_calls", "triangles"));
+ }
+
+ [Fact]
+ public void Row_AppendsCallerValuesInOrder()
+ {
+ Assert.Equal("1.500,16.000,4.000,2,1.000,,12,2.500,",
+ PerfCsv.Row(1.5, 0.016, 0.004, 2, 0.001, null, "12", PerfCsv.Ms(0.0025), PerfCsv.Ms(null)));
+ }
+
+ [Fact]
+ public void Ms_IsInvariant_ThreeDecimals()
+ {
+ var previous = Thread.CurrentThread.CurrentCulture;
+ try
+ {
+ Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("de-DE");
+ Assert.Equal("12.500", PerfCsv.Ms(0.0125));
+ }
+ finally
+ {
+ Thread.CurrentThread.CurrentCulture = previous;
+ }
+ }
+}