129 lines
4.9 KiB
C#
129 lines
4.9 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Runtime.CompilerServices;
|
|
using Xunit;
|
|
using XWorld.Server.Host;
|
|
|
|
namespace XWorld.Server.Host.Tests
|
|
{
|
|
// 同 (gameId,version) 磁盘内容变化(覆盖重发)→ Registry 自动失效重载;
|
|
// 旧房间打完旧代码(退役条目引用归零即卸载),新房间用新代码。
|
|
public class RegistryContentInvalidationTests : IDisposable
|
|
{
|
|
private string _root;
|
|
|
|
private string CopyRps1ToTempRoot()
|
|
{
|
|
string root = Path.Combine(Path.GetTempPath(), "games_" + Guid.NewGuid().ToString("N"));
|
|
string dst = Path.Combine(root, "rps", "1");
|
|
Directory.CreateDirectory(dst);
|
|
string src = Path.Combine(TestGames.Root, "rps", "1");
|
|
foreach (var f in Directory.GetFiles(src))
|
|
File.Copy(f, Path.Combine(dst, Path.GetFileName(f)));
|
|
_root = root;
|
|
return root;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (_root != null)
|
|
{
|
|
try { Directory.Delete(_root, true); } catch { }
|
|
}
|
|
}
|
|
|
|
// 改变内容指纹(game.json 在指纹范围内;追加空白不影响 JSON 解析)
|
|
private static void TouchContent(string root)
|
|
{
|
|
string gameJson = Path.Combine(root, "rps", "1", "game.json");
|
|
File.AppendAllText(gameJson, " ");
|
|
}
|
|
|
|
[Fact]
|
|
public void SameContent_ReturnsCachedInstance()
|
|
{
|
|
string root = CopyRps1ToTempRoot();
|
|
var reg = new GameModuleRegistry(new GameModuleLoader(), root);
|
|
var a = reg.Acquire("rps", 1);
|
|
var b = reg.Acquire("rps", 1);
|
|
Assert.Same(a, b);
|
|
Assert.Equal(2, reg.RefCount("rps", 1));
|
|
}
|
|
|
|
[Fact]
|
|
public void ContentChange_AfterRefZero_ReloadsNewInstance()
|
|
{
|
|
string root = CopyRps1ToTempRoot();
|
|
var reg = new GameModuleRegistry(new GameModuleLoader(), root);
|
|
var a = reg.Acquire("rps", 1);
|
|
reg.Release(a); // 引用归零,未淘汰 → 保留缓存
|
|
TouchContent(root);
|
|
var b = reg.Acquire("rps", 1);
|
|
Assert.NotSame(a, b); // 内容变了 → 新模块实例
|
|
Assert.Equal(1, reg.RefCount("rps", 1));
|
|
Assert.True(reg.IsLoaded("rps", 1));
|
|
}
|
|
|
|
[Fact]
|
|
public void ContentChange_WhileOldActive_OldAndNewCoexist_ReleaseBothCorrect()
|
|
{
|
|
string root = CopyRps1ToTempRoot();
|
|
var reg = new GameModuleRegistry(new GameModuleLoader(), root);
|
|
var oldM = reg.Acquire("rps", 1); // 旧房间在打
|
|
TouchContent(root);
|
|
var newM = reg.Acquire("rps", 1); // 新房间 → 新内容
|
|
Assert.NotSame(oldM, newM);
|
|
Assert.Equal(1, reg.RefCount("rps", 1)); // RefCount 只统计当前条目
|
|
|
|
reg.Release(oldM); // 旧房间结束:退役条目按实例识别释放,不抛
|
|
reg.Release(newM);
|
|
Assert.Equal(0, reg.RefCount("rps", 1));
|
|
Assert.True(reg.IsLoaded("rps", 1)); // 当前条目未被淘汰 → 保留复用
|
|
}
|
|
|
|
[Fact]
|
|
public void ContentChange_RetiredModule_UnloadsAndReclaims()
|
|
{
|
|
string root = CopyRps1ToTempRoot();
|
|
var weak = AcquireChangeReleaseOld(root);
|
|
for (int i = 0; i < 12 && weak.IsAlive; i++)
|
|
{
|
|
GC.Collect();
|
|
GC.WaitForPendingFinalizers();
|
|
}
|
|
Assert.False(weak.IsAlive, "退役模块未被回收 —— 存在泄漏引用");
|
|
}
|
|
|
|
// 独立方法确保局部强引用随返回消失(同 AlcUnloadTests 套路)
|
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
private static WeakReference AcquireChangeReleaseOld(string root)
|
|
{
|
|
var reg = new GameModuleRegistry(new GameModuleLoader(), root);
|
|
var oldM = reg.Acquire("rps", 1);
|
|
TouchContent(root);
|
|
var newM = reg.Acquire("rps", 1); // oldM 退役
|
|
var weak = new WeakReference(oldM);
|
|
reg.Release(oldM); // 退役 + 归零 → 卸载
|
|
reg.Release(newM);
|
|
return weak;
|
|
}
|
|
|
|
[Fact]
|
|
public void MissingDir_ServesCachedEntry()
|
|
{
|
|
string root = CopyRps1ToTempRoot();
|
|
var reg = new GameModuleRegistry(new GameModuleLoader(), root);
|
|
var a = reg.Acquire("rps", 1);
|
|
string dir = Path.Combine(root, "rps", "1");
|
|
string away = dir + ".away";
|
|
Directory.Move(dir, away); // 模拟覆盖交换的瞬间窗口
|
|
try
|
|
{
|
|
var b = reg.Acquire("rps", 1); // 指纹算不出 → 用缓存兜底
|
|
Assert.Same(a, b);
|
|
}
|
|
finally { Directory.Move(away, dir); }
|
|
}
|
|
}
|
|
}
|