test(app): freeze GameWindow lifecycle boundaries
Pin the accepted startup, input, frame, resize, shutdown, and native-window order before Slice 8 moves those edges, while deleting only unread duplicate state and test-only GameWindow facades. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
20ebf217e2
commit
476c2e6de1
7 changed files with 380 additions and 56 deletions
|
|
@ -0,0 +1,277 @@
|
|||
namespace AcDream.App.Tests.Rendering;
|
||||
|
||||
/// <summary>
|
||||
/// Temporary source-shape freeze for the Slice 8 cutover. Each later checkpoint
|
||||
/// replaces the corresponding assertion with functional tests on the extracted
|
||||
/// owner; this file prevents a mechanical move from silently reordering host
|
||||
/// behavior before that owner exists.
|
||||
/// </summary>
|
||||
public sealed class GameWindowSlice8BoundaryTests
|
||||
{
|
||||
[Fact]
|
||||
public void Run_PreservesNativeAttributeAndCallbackOrder()
|
||||
{
|
||||
string body = MethodBody("public void Run()", "private void OnLoad()");
|
||||
|
||||
AssertAppearsInOrder(
|
||||
body,
|
||||
"startupStore.LoadDisplay()",
|
||||
"QualitySettings.WithEnvOverrides(startupBase)",
|
||||
"_displayFramePacing.InitializeStartup(startupDisplay.VSync)",
|
||||
"VSync = startupPacing.UseVSync",
|
||||
"Samples = startupQuality.MsaaSamples",
|
||||
"Window.Create(options)",
|
||||
"_displayFramePacing.BindSurface(",
|
||||
"_window.Load += OnLoad;",
|
||||
"_window.Update += OnUpdate;",
|
||||
"_window.Render += OnRender;",
|
||||
"_window.Render += _displayFramePacing.OnFrameRendered;",
|
||||
"_window.Closing += OnClosing;",
|
||||
"_window.FocusChanged += OnFocusChanged;",
|
||||
"_window.Move += _displayFramePacing.OnWindowMoved;",
|
||||
"_window.StateChanged += _displayFramePacing.OnWindowStateChanged;",
|
||||
"_window.FramebufferResize += OnFramebufferResize;",
|
||||
"_window.Run();");
|
||||
|
||||
string[] registrations =
|
||||
[
|
||||
"_window.Load += OnLoad;",
|
||||
"_window.Update += OnUpdate;",
|
||||
"_window.Render += OnRender;",
|
||||
"_window.Render += _displayFramePacing.OnFrameRendered;",
|
||||
"_window.Closing += OnClosing;",
|
||||
"_window.FocusChanged += OnFocusChanged;",
|
||||
"_window.Move += _displayFramePacing.OnWindowMoved;",
|
||||
"_window.StateChanged += _displayFramePacing.OnWindowStateChanged;",
|
||||
"_window.FramebufferResize += OnFramebufferResize;",
|
||||
];
|
||||
foreach (string registration in registrations)
|
||||
Assert.Equal(1, CountOccurrences(body, registration));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SessionStart_FollowsFrameGraphPublication()
|
||||
{
|
||||
string body = MethodBody(
|
||||
"private void OnLoad()",
|
||||
"private AcDream.App.Net.LiveSessionLifecycleHost");
|
||||
|
||||
AssertAppearsInOrder(
|
||||
body,
|
||||
"_gl = GL.GetApi(_window!);",
|
||||
"_input = _window!.CreateInput();",
|
||||
"_kbSource = new AcDream.App.Input.SilkKeyboardSource(firstKb);",
|
||||
"_mouseSource = new AcDream.App.Input.SilkMouseSource(",
|
||||
"_inputDispatcher = new AcDream.UI.Abstractions.Input.InputDispatcher(",
|
||||
"_movementInput.Bind(_inputDispatcher);",
|
||||
"_cameraInput.Bind(_inputDispatcher);",
|
||||
"_inputDispatcher.Fired += OnInputAction;",
|
||||
"mouse.MouseMove +=",
|
||||
"_cameraController = new CameraController(orbit, fly);",
|
||||
"_dats = RuntimeDatCollectionFactory.OpenReadOnly(_datDir);",
|
||||
"LoadAndApplyPersistedSettings();",
|
||||
"_uiHost = new AcDream.App.UI.UiHost(",
|
||||
"_uiHost.WireMouse(m)",
|
||||
"_uiHost.WireKeyboard(kb)",
|
||||
"_retailUiRuntime = AcDream.App.UI.RetailUiRuntime.Mount(",
|
||||
"_liveEntities = new AcDream.App.World.LiveEntityRuntime(",
|
||||
"_renderFrameOrchestrator =",
|
||||
"_updateFrameOrchestrator = new AcDream.App.Update.UpdateFrameOrchestrator(",
|
||||
"_liveSessionController.Start(");
|
||||
Assert.Equal(1, CountOccurrences(body, "_liveSessionController.Start("));
|
||||
int start = body.IndexOf("_liveSessionController.Start(", StringComparison.Ordinal);
|
||||
string postStart = body[start..];
|
||||
Assert.Contains("switch (liveStart.Status)", postStart, StringComparison.Ordinal);
|
||||
Assert.Empty(System.Text.RegularExpressions.Regex.Matches(
|
||||
postStart,
|
||||
@"(?m)^\s+_[A-Za-z]\w*\s*(?:\?\?=|=)"));
|
||||
Assert.DoesNotContain("new ", postStart, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(".Mount(", postStart, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(".Bind(", postStart, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(".Attach(", postStart, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(".Compose(", postStart, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("PrepareResources(", postStart, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InputAction_PreservesRetailAcceptedPriority()
|
||||
{
|
||||
string body = MethodBody(
|
||||
"private void OnInputAction(",
|
||||
"private void ToggleLiveCombatMode()");
|
||||
|
||||
AssertAppearsInOrder(
|
||||
body,
|
||||
"HandlePointerAction(action, activation)",
|
||||
"action == AcDream.UI.Abstractions.Input.InputAction.ScrollUp",
|
||||
"HandleScrollAction(action);",
|
||||
"HandleCombatAction(action, activation)",
|
||||
"activation != AcDream.UI.Abstractions.Input.ActivationType.Press",
|
||||
"_retailUiRuntime?.HandleInputAction(action)",
|
||||
"_selectionInteractions?.HandleInputAction(action)",
|
||||
"HandlePressedMovementAction(action)",
|
||||
"switch (action)");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FramebufferResize_PreservesViewportCameraAndDevtoolsOrder()
|
||||
{
|
||||
string body = MethodBody(
|
||||
"private void OnFramebufferResize(",
|
||||
"private void ApplyDisplayWindowState(");
|
||||
|
||||
AssertAppearsInOrder(
|
||||
body,
|
||||
"if (newSize.X <= 0 || newSize.Y <= 0) return;",
|
||||
"_gl?.Viewport(",
|
||||
"_viewportAspect.Update(",
|
||||
"_cameraController?.SetAspect(",
|
||||
"_devToolsFramePresenter?.ResetLayout(");
|
||||
Assert.DoesNotContain("_uiHost", body, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UpdateRenderFocusAndCloseRemainNarrowHostEdges()
|
||||
{
|
||||
string source = GameWindowSource();
|
||||
string update = Slice(
|
||||
source,
|
||||
"private void OnUpdate(double dt)",
|
||||
"private void OnCameraModeChanged(");
|
||||
string render = Slice(
|
||||
source,
|
||||
"private void OnRender(double deltaSeconds)",
|
||||
"private AcDream.App.Diagnostics.WorldLifecycleResourceSnapshot");
|
||||
string focus = Slice(
|
||||
source,
|
||||
"private void OnFocusChanged(bool focused)",
|
||||
"public void Dispose()");
|
||||
string close = Slice(
|
||||
source,
|
||||
"private void OnClosing()",
|
||||
"private ResourceShutdownTransaction CreateShutdownTransaction()");
|
||||
|
||||
Assert.Equal(1, CountOccurrences(update, "_updateFrameOrchestrator.Tick("));
|
||||
Assert.Equal(1, CountOccurrences(render, "_renderFrameOrchestrator!.Render("));
|
||||
AssertAppearsInOrder(
|
||||
render,
|
||||
"Vector2D<int> size = _window!.Size;",
|
||||
"_renderFrameOrchestrator!.Render(",
|
||||
"new AcDream.App.Rendering.RenderFrameInput(");
|
||||
Assert.DoesNotContain("FramebufferSize", render, StringComparison.Ordinal);
|
||||
AssertAppearsInOrder(focus, "if (!focused)", "_gameplayInputFrame?.EndMouseLook();");
|
||||
AssertAppearsInOrder(close, "=> CompleteShutdown();", "_shutdown.CompleteOrThrow();");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Shutdown_PreservesDependencyStagesAndNativeWindowLast()
|
||||
{
|
||||
string source = GameWindowSource();
|
||||
string manifest = Slice(
|
||||
source,
|
||||
"private ResourceShutdownTransaction CreateShutdownTransaction()",
|
||||
"private void OnFocusChanged(bool focused)");
|
||||
string dispose = Slice(
|
||||
source,
|
||||
"public void Dispose()",
|
||||
"private sealed class NullAnimLoader");
|
||||
|
||||
string[] stages =
|
||||
[
|
||||
"new ResourceShutdownStage(\"session lifetime\"",
|
||||
"new ResourceShutdownStage(\"frame borrowers\"",
|
||||
"new ResourceShutdownStage(\"session dependents\"",
|
||||
"new ResourceShutdownStage(\"live entities\"",
|
||||
"new ResourceShutdownStage(\"live entity dependents\"",
|
||||
"new ResourceShutdownStage(\"submitted GPU work\"",
|
||||
"new ResourceShutdownStage(\"render frontends\"",
|
||||
"new ResourceShutdownStage(\"shared texture owners\"",
|
||||
"new ResourceShutdownStage(\"mesh adapter\"",
|
||||
"new ResourceShutdownStage(\"remaining render owners\"",
|
||||
"new ResourceShutdownStage(\"frame flight owner\"",
|
||||
"new ResourceShutdownStage(\"content mappings\"",
|
||||
"new ResourceShutdownStage(\"input\"",
|
||||
"new ResourceShutdownStage(\"OpenGL context\"",
|
||||
];
|
||||
AssertAppearsInOrder(manifest, stages);
|
||||
Assert.Equal(stages.Length, CountOccurrences(manifest, "new ResourceShutdownStage("));
|
||||
foreach (string stage in stages)
|
||||
Assert.Equal(1, CountOccurrences(manifest, stage));
|
||||
AssertAppearsInOrder(
|
||||
dispose,
|
||||
"CompleteShutdown();",
|
||||
"_window?.Dispose();",
|
||||
"_window = null;");
|
||||
Assert.Equal(1, CountOccurrences(dispose, "CompleteShutdown();"));
|
||||
Assert.Equal(1, CountOccurrences(dispose, "_window?.Dispose();"));
|
||||
Assert.Equal(1, CountOccurrences(dispose, "_window = null;"));
|
||||
int nativeReleased = dispose.IndexOf("_window = null;", StringComparison.Ordinal)
|
||||
+ "_window = null;".Length;
|
||||
string afterNativeRelease = dispose[nativeReleased..];
|
||||
int fallbackDocumentation = afterNativeRelease.IndexOf(
|
||||
"/// <summary>",
|
||||
StringComparison.Ordinal);
|
||||
if (fallbackDocumentation >= 0)
|
||||
afterNativeRelease = afterNativeRelease[..fallbackDocumentation];
|
||||
Assert.All(
|
||||
afterNativeRelease,
|
||||
character => Assert.True(char.IsWhiteSpace(character) || character == '}'));
|
||||
}
|
||||
|
||||
private static string MethodBody(string start, string end) =>
|
||||
Slice(GameWindowSource(), start, end);
|
||||
|
||||
private static string Slice(string source, string start, string end)
|
||||
{
|
||||
int first = source.IndexOf(start, StringComparison.Ordinal);
|
||||
int last = source.IndexOf(end, first + 1, StringComparison.Ordinal);
|
||||
Assert.True(first >= 0, $"Missing source boundary: {start}");
|
||||
Assert.True(last > first, $"Missing source boundary: {end}");
|
||||
return source[first..last];
|
||||
}
|
||||
|
||||
private static int CountOccurrences(string source, string value)
|
||||
{
|
||||
int count = 0;
|
||||
int cursor = 0;
|
||||
while ((cursor = source.IndexOf(value, cursor, StringComparison.Ordinal)) >= 0)
|
||||
{
|
||||
count++;
|
||||
cursor += value.Length;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private static void AssertAppearsInOrder(string source, params string[] fragments)
|
||||
{
|
||||
int cursor = -1;
|
||||
foreach (string fragment in fragments)
|
||||
{
|
||||
int next = source.IndexOf(fragment, cursor + 1, StringComparison.Ordinal);
|
||||
Assert.True(next >= 0, $"Missing expected source fragment: {fragment}");
|
||||
Assert.True(next > cursor, $"Out-of-order source fragment: {fragment}");
|
||||
cursor = next;
|
||||
}
|
||||
}
|
||||
|
||||
private static string GameWindowSource() => File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Rendering",
|
||||
"GameWindow.cs"));
|
||||
|
||||
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.");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue