using AcDream.App.Rendering; namespace AcDream.App.Tests.Rendering; public sealed class ResourceCleanupGroupTests { [Fact] public void CleanupRunsInReverseOrderAndNeverReplaysSuccess() { var calls = new List(); var resources = new ResourceCleanupGroup(); int middleFailures = 1; resources.Add("first", () => calls.Add("first")); resources.Add("middle", () => { calls.Add("middle"); if (middleFailures-- > 0) throw new InvalidOperationException("middle failed"); }); resources.Add("last", () => calls.Add("last")); Assert.Throws(resources.RetryCleanup); Assert.Equal(["last", "middle", "first"], calls); resources.RetryCleanup(); resources.RetryCleanup(); Assert.True(resources.IsCleanupComplete); Assert.Equal(["last", "middle", "first", "middle"], calls); } [Fact] public void TextRendererPublishesEveryConstructorResourceBeforeLaterGlWork() { string source = File.ReadAllText(Path.Combine( FindRepoRoot(), "src", "AcDream.App", "Rendering", "TextRenderer.cs")); AssertAppearsInOrder( source, "shader = new Shader(gl,", "resources.Add(\"text shader\", shader.Dispose);", "frameBuffers[i] = CreateFrameBufferSet(resources);", "whiteTexture = GlResourceCommand.CreateTexture(", "resources.Add(", "\"white texture\"", "GlResourceCommand.Execute(", "initialize TextRenderer white texture", "resources.RetryCleanup();", "_resources = resources;"); AssertAppearsInOrder( source, "private FrameBufferSet CreateFrameBufferSet(ResourceCleanupGroup resources)", "TrackedGlResource.CreateVertexArray(", "resources.Add(\"frame VAO\", vaoRelease.Run);", "TrackedGlResource.CreateBuffer(", "resources.Add(", "\"frame VBO\""); Assert.Contains("_resources.RetryCleanup();", source, StringComparison.Ordinal); Assert.DoesNotContain("private FrameBufferSet CreateFrameBufferSet()", source, StringComparison.Ordinal); } [Fact] public void ReturnedNameIsRetainedWhenPostconditionAndFirstRollbackFail() { bool deleteFails = true; int deleteCalls = 0; GlResourceConstructionException failure = Assert.Throws(() => GlResourceCommand.CreateNameCore( "synthetic buffer", static () => { }, () => 42, () => throw new InvalidOperationException("postcondition failed"), name => { Assert.Equal(42u, name); deleteCalls++; if (deleteFails) throw new InvalidOperationException("delete failed"); })); Assert.False(failure.IsCleanupComplete); Assert.Equal(1, deleteCalls); deleteFails = false; failure.RetryCleanup(); failure.RetryCleanup(); Assert.True(failure.IsCleanupComplete); Assert.Equal(2, deleteCalls); } private static void AssertAppearsInOrder(string source, params string[] values) { int cursor = -1; foreach (string value in values) { int next = source.IndexOf(value, cursor + 1, StringComparison.Ordinal); Assert.True(next > cursor, $"Missing or out-of-order source fragment: {value}"); cursor = next; } } private static string FindRepoRoot() { DirectoryInfo? directory = new(AppContext.BaseDirectory); while (directory is not null) { if (File.Exists(Path.Combine(directory.FullName, "AcDream.slnx"))) return directory.FullName; directory = directory.Parent; } throw new DirectoryNotFoundException("Could not find AcDream.slnx."); } }