Initial commit: Client Doc docs Server Tools
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
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); }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user