sharpemu/tests/SharpEmu.Libs.Tests/Kernel/KernelRuntimeCompatExportsT...

131 lines
4.5 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.Libs.Kernel;
using Xunit;
namespace SharpEmu.Libs.Tests.Kernel;
// sceKernelGetTscFrequency must describe the same clock that sceKernelReadTsc returns. ReadTsc
// only returns the CPU's RDTSC when the host RDTSC reader is available (64-bit Windows) and
// otherwise falls back to the QPC-based Stopwatch, so the frequency selection has to follow suit.
public sealed class KernelRuntimeCompatExportsTests
{
private static KernelRuntimeCompatExports.TryGetFrequency Yields(ulong hz) =>
(out ulong frequencyHz) =>
{
frequencyHz = hz;
return true;
};
private static readonly KernelRuntimeCompatExports.TryGetFrequency Fails =
(out ulong frequencyHz) =>
{
frequencyHz = 0;
return false;
};
[Fact]
public void WithoutHostRdtsc_ReportsStopwatchFrequency_NotHardwareTsc()
{
// Regression: on Linux/macOS ReadTsc returns the Stopwatch counter, so the reported
// frequency must be the Stopwatch's, never the CPU's much larger hardware TSC frequency.
var (frequencyHz, source) = KernelRuntimeCompatExports.SelectKernelTscFrequency(
rdtscAvailable: false,
overrideHzText: null,
tryCalibrate: Yields(2_400_000_000UL),
tryResolveCpuid: Yields(3_000_000_000UL),
stopwatchFrequency: 10_000_000);
Assert.Equal(10_000_000UL, frequencyHz);
Assert.Equal("qpc", source);
}
[Fact]
public void WithHostRdtsc_PrefersCalibratedFrequency()
{
var (frequencyHz, source) = KernelRuntimeCompatExports.SelectKernelTscFrequency(
rdtscAvailable: true,
overrideHzText: null,
tryCalibrate: Yields(2_400_000_000UL),
tryResolveCpuid: Yields(3_000_000_000UL),
stopwatchFrequency: 10_000_000);
Assert.Equal(2_400_000_000UL, frequencyHz);
Assert.Equal("calibrated-rdtsc", source);
}
[Fact]
public void WithHostRdtsc_FallsBackToCpuid_WhenCalibrationFails()
{
var (frequencyHz, source) = KernelRuntimeCompatExports.SelectKernelTscFrequency(
rdtscAvailable: true,
overrideHzText: null,
tryCalibrate: Fails,
tryResolveCpuid: Yields(3_000_000_000UL),
stopwatchFrequency: 10_000_000);
Assert.Equal(3_000_000_000UL, frequencyHz);
Assert.Equal("cpuid", source);
}
[Fact]
public void WithHostRdtsc_UsesStopwatch_WhenRdtscFrequencyUnknown()
{
var (frequencyHz, source) = KernelRuntimeCompatExports.SelectKernelTscFrequency(
rdtscAvailable: true,
overrideHzText: null,
tryCalibrate: Fails,
tryResolveCpuid: Fails,
stopwatchFrequency: 10_000_000);
Assert.Equal(10_000_000UL, frequencyHz);
Assert.Equal("qpc", source);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void EnvOverride_Wins_WhenSane(bool rdtscAvailable)
{
var (frequencyHz, source) = KernelRuntimeCompatExports.SelectKernelTscFrequency(
rdtscAvailable,
overrideHzText: "1500000000",
tryCalibrate: Yields(2_400_000_000UL),
tryResolveCpuid: Yields(3_000_000_000UL),
stopwatchFrequency: 10_000_000);
Assert.Equal(1_500_000_000UL, frequencyHz);
Assert.Equal("env", source);
}
[Fact]
public void EnvOverride_BelowMinimum_IsIgnored()
{
// 500 kHz is below the sanity floor, so it is dropped; with rdtsc unavailable the
// hardware-TSC path is gated off and the Stopwatch frequency is used.
var (frequencyHz, _) = KernelRuntimeCompatExports.SelectKernelTscFrequency(
rdtscAvailable: false,
overrideHzText: "500000",
tryCalibrate: Fails,
tryResolveCpuid: Yields(3_000_000_000UL),
stopwatchFrequency: 10_000_000);
Assert.Equal(10_000_000UL, frequencyHz);
}
[Fact]
public void NonPositiveStopwatchFrequency_FallsBackToDefault()
{
var (frequencyHz, source) = KernelRuntimeCompatExports.SelectKernelTscFrequency(
rdtscAvailable: false,
overrideHzText: null,
tryCalibrate: Fails,
tryResolveCpuid: Fails,
stopwatchFrequency: 0);
Assert.Equal(10_000_000UL, frequencyHz); // DefaultKernelTscFrequency
Assert.Equal("qpc", source);
}
}