L4 briefly pass unit test

This commit is contained in:
Youmu Liu
2026-03-17 20:31:06 +08:00
parent 98d711a3ca
commit dccb815811
13 changed files with 1710 additions and 32 deletions
+322
View File
@@ -0,0 +1,322 @@
//-----------------------------------------------------------------------
// -----------------------------------------------------------------------
// file="TestL4"
// Project: StgSharp
// AuthorGroup: Nitload
// Copyright (c) Nitload. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
using StgSharp.HighPerformance.Memory;
using System;
using System.Runtime.CompilerServices;
namespace StgSharp
{
// ====================================================================
// SequentialPredictor
// 辅助类:给定一批地址,每次 Predict 返回全部地址(幂等);
// Prefetch 将缓存行填充为 0xAB,WriteBack 为空操作。
// 仅供 L4 内存安全测试使用。
// ====================================================================
internal sealed unsafe class SequentialPredictor : IPrefetchPredict
{
private readonly nuint[] _addresses;
public SequentialPredictor(
nuint[] addresses
)
{
_addresses = addresses;
}
public bool Predict(
Span<CacheLinePrediction> node,
out int count
)
{
count = Math.Min(_addresses.Length, node.Length);
for (int i = 0; i < count; i++) {
node[i] = new CacheLinePrediction
{
Address = _addresses[i],
MapPolicy = 0,
};
}
return count > 0;
}
// 将缓存行填充 0xAB,便于数据完整性断言
public void Prefetch(
nuint origin,
ulong policy,
Span<byte> cache
)
{
cache.Fill(0xAB);
}
// 只读路径测试:无需回写
public void WriteBack(
nuint origin,
ulong policy,
ReadOnlySpan<byte> cache
) { }
}
// ====================================================================
// L4MemorySafetyTest — 挂载到 Program partial class
// ====================================================================
internal static partial class Program
{
// ----------------------------------------------------------------
// 统一的断言辅助:条件为 false 时打印 FAIL 并记录失败数
// ----------------------------------------------------------------
private static int _l4FailCount;
// ================================================================
// 入口:从 Main 调用 TestL4MemorySafety()
// ================================================================
public static void TestL4MemorySafety()
{
_l4FailCount = 0;
Console.WriteLine("=== L4 Memory Safety Tests (P0) ===");
Console.WriteLine();
L4Test_Dispose_NoLeak();
L4Test_Map_CacheHit_DataIntegrity();
L4Test_CacheLine_Dispose_DecrementsCounts();
L4Test_CacheLine_Dispose_Idempotent();
Console.WriteLine();
if (_l4FailCount == 0)
{
Console.WriteLine("=== ALL P0 TESTS PASSED ===");
} else
{
Console.WriteLine($"=== {_l4FailCount} TEST(S) FAILED ===");
}
}
private static void L4Assert(
bool condition,
string message,
[CallerMemberName] string caller = "",
[CallerLineNumber] int line = 0
)
{
if (!condition)
{
Console.WriteLine($" [FAIL] {caller}:{line} {message}");
_l4FailCount++;
}
}
// ================================================================
// P0-3 CacheLine_Dispose_DecrementsCounts
// Dispose 后 refCount/activeCount 正确递减:
// 连续 Map → Dispose 同一地址 3 轮,每轮都能拿到有效句柄。
// 若 refCount 未递减,驱逐时 CAS 永远失败,缓存满后
// Evict 超时会抛出 OverflowException。
// ================================================================
private static void L4Test_CacheLine_Dispose_DecrementsCounts()
{
Console.Write("[P0-3] CacheLine_Dispose_DecrementsCounts ... ");
try
{
nuint[] addresses = MakeL4FakeAddresses(1);
using L4 l4 = new L4();
l4.PrefetchPredict = new SequentialPredictor(addresses);
nuint target = addresses[0];
bool passed = true;
for (int round = 0; round < 3; round++)
{
L4.CacheLine line = l4.Map(target);
if (line.Address == nuint.Zero)
{
L4Assert(false,
$"Round {round}: Map returned zero address");
passed = false;
line.Dispose();
break;
}
line.Dispose();
}
if (passed) {
Console.WriteLine("PASS");
}
}
catch (Exception ex)
{
Console.WriteLine($"FAIL ({ex.GetType().Name}: {ex.Message})");
_l4FailCount++;
}
}
// ================================================================
// P0-4 CacheLine_Dispose_Idempotent
// 对同一 CacheLine 连续 Dispose 两次:
// - 不抛异常
// - 计数未被双减:之后再次 Map 同地址仍能成功
// ================================================================
private static void L4Test_CacheLine_Dispose_Idempotent()
{
Console.Write("[P0-4] CacheLine_Dispose_Idempotent ... ");
try
{
nuint[] addresses = MakeL4FakeAddresses(1);
using L4 l4 = new L4();
l4.PrefetchPredict = new SequentialPredictor(addresses);
nuint target = addresses[0];
// 第一轮:Map → Dispose × 2
L4.CacheLine line = l4.Map(target);
line.Dispose(); // 正常释放
line.Dispose(); // 幂等:_handle 已为 0,直接返回
// 第二轮:计数未双减,应仍能成功 Map
L4.CacheLine line2 = l4.Map(target);
L4Assert(line2.Address != nuint.Zero,
"After double-dispose, Map should still return valid address");
line2.Dispose();
if (_l4FailCount == 0) {
Console.WriteLine("PASS");
}
}
catch (Exception ex)
{
Console.WriteLine($"FAIL ({ex.GetType().Name}: {ex.Message})");
_l4FailCount++;
}
}
// ================================================================
// P0-1 Dispose_NoLeak
// 构造后立即 Dispose,不抛异常;连续 Dispose 两次幂等。
// ================================================================
private static void L4Test_Dispose_NoLeak()
{
Console.Write("[P0-1] Dispose_NoLeak ... ");
try
{
L4 l4 = new L4();
l4.PrefetchPredict = new SequentialPredictor(Array.Empty<nuint>());
l4.Dispose(); // 第一次:正常释放
l4.Dispose(); // 第二次:_disposed 保护,应幂等
Console.WriteLine("PASS");
}
catch (Exception ex)
{
Console.WriteLine($"FAIL ({ex.GetType().Name}: {ex.Message})");
_l4FailCount++;
}
}
// ================================================================
// P0-2 Map_CacheHit_DataIntegrity
// 经由 SequentialPredictor 预取后,Map() 命中的缓存行数据
// 应与 Prefetch() 写入的内容一致(全 0xAB)。
//
// 注意:需要 CacheLine 暴露 public nuint Address 才能读取内容。
// 若该属性尚未暴露,本测试会编译报错,届时先添加该属性。
// ================================================================
private static unsafe void L4Test_Map_CacheHit_DataIntegrity()
{
Console.Write("[P0-2] Map_CacheHit_DataIntegrity ... ");
try
{
nuint[] addresses = MakeL4FakeAddresses(4);
using L4 l4 = new L4();
l4.PrefetchPredict = new SequentialPredictor(addresses);
// Map 触发内部同步 Prefetch,命中第一个地址
L4.CacheLine line = l4.Map(addresses[0]);
try
{
// 读取缓存行内容,应全为 0xAB
ReadOnlySpan<byte> data = new ReadOnlySpan<byte>(
(byte*)line.Address,
8192 // CacheLineData.Data 固定 8192 字节
);
bool allAb = true;
for (int i = 0; i < data.Length; i++)
{
if (data[i] != 0xAB)
{
allAb = false;
L4Assert(false,
$"data[{i}] == 0x{data[i]:X2}, expected 0xAB");
break;
}
}
if (allAb) {
Console.WriteLine("PASS");
}
}
finally
{
line.Dispose();
}
}
catch (Exception ex)
{
Console.WriteLine($"FAIL ({ex.GetType().Name}: {ex.Message})");
_l4FailCount++;
}
}
// ----------------------------------------------------------------
// 生成 count 个互不相同的假地址(仅用作 SwissTable key,
// 不会被解引用;偏移足够大以避开地址 0)
// ----------------------------------------------------------------
private static nuint[] MakeL4FakeAddresses(
int count
)
{
nuint[] result = new nuint[count];
for (int i = 0; i < count; i++) {
result[i] = (nuint)(0x1_0000_0000UL + (ulong)i * 0x1000);
}
return result;
}
}
}