using GameOverlay.Capture; using Xunit; namespace GameOverlay.Capture.Tests; public class FrameTimeStatisticsTests { [Fact] public void Empty_ReturnsEmptySummary() { var stats = new FrameTimeStatistics(); Assert.Equal(0, stats.Count); Assert.Equal(MetricsSummary.Empty, stats.ComputeSummary()); } [Fact] public void ConstantFrameTimes_GiveExactFps() { var stats = new FrameTimeStatistics(); for (int i = 0; i < 100; i++) stats.AddFrameTime(10.0); // 10 ms => 100 fps var s = stats.ComputeSummary(); Assert.Equal(100, s.FrameCount); Assert.Equal(100.0, s.AverageFps, 6); Assert.Equal(100.0, s.MinFps, 6); Assert.Equal(100.0, s.MaxFps, 6); Assert.Equal(100.0, s.OnePercentLowFps, 6); Assert.Equal(100.0, s.PointOnePercentLowFps, 6); Assert.Equal(10.0, s.AverageFrameTimeMs, 6); Assert.Equal(1.0, s.DurationSeconds, 6); // 100 * 10ms = 1s } [Fact] public void NonPositiveFrameTimes_AreIgnored() { var stats = new FrameTimeStatistics(); stats.AddFrameTime(0); stats.AddFrameTime(-5); stats.AddFrameTime(double.NaN); stats.AddFrameTime(double.PositiveInfinity); stats.AddFrameTime(16.6); Assert.Equal(1, stats.Count); } [Fact] public void MinMaxFps_DerivedFromExtremeFrameTimes() { var stats = new FrameTimeStatistics(); // Mostly fast frames, a couple of slow spikes. for (int i = 0; i < 98; i++) stats.AddFrameTime(8.0); // 125 fps stats.AddFrameTime(4.0); // 250 fps -> fastest frame => MaxFps stats.AddFrameTime(50.0); // 20 fps -> slowest frame => MinFps var s = stats.ComputeSummary(); Assert.Equal(250.0, s.MaxFps, 6); Assert.Equal(20.0, s.MinFps, 6); } [Fact] public void OnePercentLow_UsesNinetyNinthPercentileFrameTime() { var stats = new FrameTimeStatistics(); // 99 frames at 10 ms, 1 frame at 100 ms. Nearest-rank P99 of 100 samples = the 99th value. for (int i = 0; i < 99; i++) stats.AddFrameTime(10.0); stats.AddFrameTime(100.0); // Sorted ascending: [10 x99, 100]. rank at P99 = ceil(0.99*100)=99 => element#99 = 10ms => 100 fps. Assert.Equal(10.0, stats.PercentileFrameTimeMs(99.0), 6); // P99.9 = ceil(0.999*100)=100 => element#100 = 100ms => 10 fps. Assert.Equal(100.0, stats.PercentileFrameTimeMs(99.9), 6); var s = stats.ComputeSummary(); Assert.Equal(100.0, s.OnePercentLowFps, 6); // 1000/10 Assert.Equal(10.0, s.PointOnePercentLowFps, 6); // 1000/100 } [Fact] public void PresentTimestamps_ConvertToFrameTimeDeltas() { var stats = new FrameTimeStatistics(); const long tps = 1000; // 1 tick = 1 ms stats.AddPresentTimestamp(0, tps); // baseline, no sample stats.AddPresentTimestamp(16, tps); // 16 ms stats.AddPresentTimestamp(32, tps); // 16 ms stats.AddPresentTimestamp(50, tps); // 18 ms Assert.Equal(3, stats.Count); var s = stats.ComputeSummary(); Assert.Equal((16.0 + 16.0 + 18.0) / 3.0, s.AverageFrameTimeMs, 6); } [Fact] public void Percentile_OutOfRange_Throws() { var stats = new FrameTimeStatistics(); stats.AddFrameTime(10); Assert.Throws(() => stats.PercentileFrameTimeMs(-1)); Assert.Throws(() => stats.PercentileFrameTimeMs(101)); } [Fact] public void Reset_ClearsState() { var stats = new FrameTimeStatistics(); stats.AddFrameTime(10); stats.AddPresentTimestamp(100, 1000); stats.Reset(); Assert.Equal(0, stats.Count); stats.AddPresentTimestamp(200, 1000); // first after reset is a baseline again Assert.Equal(0, stats.Count); } }