CI / build-test (push) Failing after 1m8s
Add MrGameEng.AI utility-AI module; format codebase with CSharpier New MrGameEng.AI module (ResponseCurve, Consideration, UtilityAction, UtilityAi selector, Blackboard) plus CSharpier formatting applied across the whole engine. Documents the CSharpier convention in CLAUDE.md. @
116 lines
3.8 KiB
C#
116 lines
3.8 KiB
C#
using Xunit;
|
||
|
||
namespace MrGameEng.Mods.Tests;
|
||
|
||
public sealed class ModLoaderTests : IDisposable
|
||
{
|
||
private readonly string _root = Directory.CreateTempSubdirectory("mrge-mods-tests-").FullName;
|
||
|
||
private string ModsRoot => Path.Combine(_root, "Mods");
|
||
|
||
public void Dispose() => Directory.Delete(_root, recursive: true);
|
||
|
||
/// <summary>Создаёт мод с About.json; зависимости — id модов, которые должны грузиться раньше.</summary>
|
||
private void WriteMod(string id, params string[] dependencies)
|
||
{
|
||
var aboutDir = Path.Combine(ModsRoot, id, "About");
|
||
Directory.CreateDirectory(aboutDir);
|
||
var deps = string.Join(", ", dependencies.Select(d => $"\"{d}\""));
|
||
File.WriteAllText(
|
||
Path.Combine(aboutDir, "About.json"),
|
||
$$"""{ "id": "{{id}}", "name": "{{id}} mod", "version": "1.0", "dependencies": [{{deps}}] }"""
|
||
);
|
||
}
|
||
|
||
[Fact]
|
||
public void Load_OrdersDependenciesFirst_ThenAlphabetically()
|
||
{
|
||
WriteMod("zebra", "Core");
|
||
WriteMod("apple", "Core");
|
||
WriteMod("Core");
|
||
|
||
var mods = ModLoader.Load(ModsRoot);
|
||
|
||
Assert.Equal(["Core", "apple", "zebra"], mods.Select(m => m.Id));
|
||
}
|
||
|
||
[Fact]
|
||
public void Load_MissingDependency_Throws()
|
||
{
|
||
WriteMod("orphan", "NoSuchMod");
|
||
|
||
var exception = Assert.Throws<InvalidDataException>(() => ModLoader.Load(ModsRoot));
|
||
Assert.Contains("NoSuchMod", exception.Message);
|
||
}
|
||
|
||
[Fact]
|
||
public void Load_CyclicDependency_Throws()
|
||
{
|
||
WriteMod("a", "b");
|
||
WriteMod("b", "a");
|
||
|
||
var exception = Assert.Throws<InvalidDataException>(() => ModLoader.Load(ModsRoot));
|
||
Assert.Contains("Cyclic", exception.Message);
|
||
}
|
||
|
||
[Fact]
|
||
public void Load_ActiveSubset_LoadsOnlyRequested()
|
||
{
|
||
WriteMod("Core");
|
||
WriteMod("extra", "Core");
|
||
WriteMod("unused");
|
||
|
||
var mods = ModLoader.Load(ModsRoot, ["extra", "Core"]);
|
||
|
||
Assert.Equal(["Core", "extra"], mods.Select(m => m.Id));
|
||
}
|
||
|
||
[Fact]
|
||
public void Load_IgnoresDirectoriesWithoutAbout()
|
||
{
|
||
WriteMod("Core");
|
||
Directory.CreateDirectory(Path.Combine(ModsRoot, "not-a-mod"));
|
||
|
||
var mod = Assert.Single(ModLoader.Load(ModsRoot));
|
||
Assert.Equal("Core", mod.Id);
|
||
Assert.Equal("Core mod", mod.Info.Name);
|
||
}
|
||
|
||
[Fact]
|
||
public void FindModsRoot_WalksUpFromNestedDirectory()
|
||
{
|
||
WriteMod("Core");
|
||
var nested = Path.Combine(_root, "bin", "Debug", "net8.0");
|
||
Directory.CreateDirectory(nested);
|
||
|
||
Assert.Equal(ModsRoot, ModLoader.FindModsRoot(nested));
|
||
}
|
||
|
||
[Fact]
|
||
public void ContentTree_LaterMod_OverridesSameRelativePath()
|
||
{
|
||
WriteMod("Core");
|
||
WriteMod("patch", "Core");
|
||
File.WriteAllText(CreateContentFile("Core", "Textures", "things/rock.png"), "core");
|
||
File.WriteAllText(CreateContentFile("Core", "Textures", "things/tree.png"), "core");
|
||
File.WriteAllText(CreateContentFile("patch", "Textures", "things/rock.png"), "patched");
|
||
|
||
var mods = ModLoader.Load(ModsRoot);
|
||
var tree = ModContentTree.Build(mods, "Textures", ".png");
|
||
|
||
Assert.Equal(2, tree.Files.Count);
|
||
Assert.True(tree.TryGet("things/rock.png", out var rock));
|
||
Assert.Equal("patch", rock.Mod.Id);
|
||
Assert.Equal("patched", File.ReadAllText(rock.FullPath));
|
||
Assert.True(tree.TryGet("things/tree.png", out var oak));
|
||
Assert.Equal("Core", oak.Mod.Id);
|
||
}
|
||
|
||
private string CreateContentFile(string modId, string folder, string relativePath)
|
||
{
|
||
var fullPath = Path.Combine(ModsRoot, modId, folder, relativePath);
|
||
Directory.CreateDirectory(Path.GetDirectoryName(fullPath)!);
|
||
return fullPath;
|
||
}
|
||
}
|