GameOverlay.Capture/tests/GameOverlay.Capture.Tests/FrameTimeStatisticsTests.cs
2026-07-29 20:03:02 -05:00

115 lines
3.9 KiB
C#

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<ArgumentOutOfRangeException>(() => stats.PercentileFrameTimeMs(-1));
Assert.Throws<ArgumentOutOfRangeException>(() => 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);
}
}