test: replace render leaf source freezes

This commit is contained in:
Erik 2026-08-18 16:12:50 +02:00
parent 3c492aedc2
commit 5e56045077
3 changed files with 378 additions and 214 deletions

View file

@ -1301,3 +1301,43 @@ Verification:
attributed methods, and reduces direct/total source readers from 44/70 to attributed methods, and reduces direct/total source readers from 44/70 to
41/64. The remaining 64 reconcile to the 22 approved retained 41/64. The remaining 64 reconcile to the 22 approved retained
policies/contracts and 42 staged replacements. policies/contracts and 42 staged replacements.
## Batch AB render-leaf and frame-handoff source-freeze replacement
Batch AB converts all nine approved `GameWindowRenderLeafCompositionTests`
source readers. No product source changes and no test is removed.
Private presentation, resource preparation, world/private/diagnostic routing,
GPU measurement, and immutable outcome construction now follow compiled typed
calls in their production order. The portal-tunnel check follows the compiled
session transfer/bind/publication path, both local teleport factory targets,
the session-before-frame phase references in `OnLoad`, and the exact frame-root
borrower construction order. `OnRender` now proves its single
`GameFrameGraphSlot.Render` handoff and absence of the five displaced leaf
field reads directly from the built method.
The broad legacy-owner blacklist is narrowed to what it actually protects:
reflected `GameWindow` fields and methods reject the displaced owners, while
compiled composition metadata positively locates paperdoll, frame-resource,
weather, private-presentation, orchestrator, frame-pacing, and mouse-capture
ownership behind their focused types. Shutdown ordering uses the compiled
operation/stage labels. Terrain diagnostics are constructed with the scene
executor but not driven directly by the frame root.
The optional paperdoll contract is stronger than the old source-expression
match: compiled branch destinations prove that either a missing viewport or a
missing inventory frame jumps past `PaperdollFramePresenter` construction.
`CompiledCallGraph` gained a reusable branch-target reader for this kind of
optional-dependency guard.
Verification:
- all nine focused render-leaf methods pass;
- the complete locked Release build covers all 44 projects with zero warnings
and zero errors;
- the no-retry complete hermetic Release gate remains 14,346/14,346 with zero
skips or failures across all 12 test assemblies; and
- the regenerated 1,254-file inventory parses every file, remains at 11,414
attributed methods, and reduces direct/total source readers from 41/64 to
39/55. The remaining 55 reconcile to the 22 approved retained
policies/contracts and 33 staged replacements.

View file

@ -9,6 +9,10 @@ internal readonly record struct CompiledFieldReference(
int Offset, int Offset,
OpCode OpCode, OpCode OpCode,
FieldInfo Field); FieldInfo Field);
internal readonly record struct CompiledBranch(
int Offset,
OpCode OpCode,
int TargetOffset);
/// <summary> /// <summary>
/// Reads compiled call/new-object edges from a method body. Architecture tests /// Reads compiled call/new-object edges from a method body. Architecture tests
@ -146,6 +150,40 @@ internal static class CompiledCallGraph
return references; return references;
} }
/// <summary>
/// Reads short and long branch destinations. Tests use this to prove that
/// an optional dependency guard jumps around a construction edge without
/// freezing the source expression that produced the branch.
/// </summary>
public static IReadOnlyList<CompiledBranch> ReadBranches(MethodBase method)
{
ArgumentNullException.ThrowIfNull(method);
byte[] il = method.GetMethodBody()?.GetILAsByteArray()
?? throw new InvalidOperationException(
$"{method.DeclaringType?.FullName}.{method.Name} has no compiled body.");
var branches = new List<CompiledBranch>();
for (int cursor = 0; cursor < il.Length;)
{
int instructionOffset = cursor;
OpCode opCode = ReadOpCode(il, ref cursor);
if (opCode.OperandType == OperandType.ShortInlineBrTarget)
{
int target = cursor + sizeof(sbyte) + unchecked((sbyte)il[cursor]);
branches.Add(new CompiledBranch(instructionOffset, opCode, target));
}
else if (opCode.OperandType == OperandType.InlineBrTarget)
{
int target = cursor + sizeof(int) + BitConverter.ToInt32(il, cursor);
branches.Add(new CompiledBranch(instructionOffset, opCode, target));
}
cursor += OperandSize(opCode.OperandType, il, cursor);
}
return branches;
}
/// <summary> /// <summary>
/// Reads types named by compiled type operands such as casts, boxing, and /// Reads types named by compiled type operands such as casts, boxing, and
/// <c>isinst</c>. This lets tests retain an exact type boundary without /// <c>isinst</c>. This lets tests retain an exact type boundary without

View file

@ -1,303 +1,389 @@
using System.Reflection;
using AcDream.App.Composition;
using AcDream.App.Input;
using AcDream.App.Rendering; using AcDream.App.Rendering;
using AcDream.App.Streaming;
using AcDream.App.Tests.Architecture;
using AcDream.App.UI;
namespace AcDream.App.Tests.Rendering; namespace AcDream.App.Tests.Rendering;
public sealed class GameWindowRenderLeafCompositionTests public sealed class GameWindowRenderLeafCompositionTests
{ {
private const BindingFlags Declared = BindingFlags.Instance
| BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic
| BindingFlags.DeclaredOnly;
[Fact] [Fact]
public void ProductionRender_PreservesPrivatePresentationAndCaptureOrder() public void ProductionRender_PreservesPrivatePresentationAndCaptureOrder()
{ {
string presentation = Source("PrivatePresentationRenderer.cs"); MethodInfo presentation = RequiredMethod(
string orchestrator = Source("RenderFrameOrchestrator.cs"); typeof(PrivatePresentationRenderer),
nameof(PrivatePresentationRenderer.Render));
AssertAppearsInOrder( AssertCallOrder(
presentation, presentation,
"_portal.Draw(", (typeof(IPrivatePortalViewport), nameof(IPrivatePortalViewport.Draw)),
"_entityViewports?.Render();", (typeof(IPrivateEntityViewportFrame), nameof(IPrivateEntityViewportFrame.Render)),
"_gameplayUi?.Render(", (typeof(IRetainedGameplayUiFrame), nameof(IRetainedGameplayUiFrame.Render)),
"_devTools?.Render(", (typeof(IDevToolsFrameLifecycle), nameof(IDevToolsFrameLifecycle.Render)),
"_screenshots?.CapturePending("); (typeof(IPrivateFrameScreenshot), nameof(IPrivateFrameScreenshot.CapturePending)));
AssertAppearsInOrder(
MethodInfo orchestrator = RequiredMethod(
typeof(RenderFrameOrchestrator),
nameof(RenderFrameOrchestrator.Render));
AssertCallOrder(
orchestrator, orchestrator,
"_world.Render(input);", (typeof(IWorldSceneFramePhase), nameof(IWorldSceneFramePhase.Render)),
"_presentation.Render(input, world);", (typeof(IPrivatePresentationFramePhase), nameof(IPrivatePresentationFramePhase.Render)),
"_diagnostics.Publish(input, outcome);"); (typeof(IRenderFrameDiagnosticsPhase), nameof(IRenderFrameDiagnosticsPhase.Publish)));
} }
[Fact] [Fact]
public void ProductionRender_Prepares_resources_then_devtools_weather_and_world() public void ProductionRender_Prepares_resources_then_devtools_weather_and_world()
{ {
string source = Source("RenderFramePreparationController.cs"); MethodInfo prepare = RequiredMethod(
typeof(RenderFramePreparationController),
AssertAppearsInOrder( nameof(RenderFramePreparationController.Prepare));
source, AssertCallOrder(
"_resources.Prepare(input);", prepare,
"_devTools?.BeginFrame((float)input.DeltaSeconds);", (typeof(IRenderFrameResourcePhase), nameof(IRenderFrameResourcePhase.Prepare)),
"_weather.Tick(input.DeltaSeconds);"); (typeof(IDevToolsFrameLifecycle), nameof(IDevToolsFrameLifecycle.BeginFrame)),
(typeof(IRenderWeatherFramePhase), nameof(IRenderWeatherFramePhase.Tick)));
} }
[Fact] [Fact]
public void Composition_transfers_portal_tunnel_before_constructing_frame_borrowers() public void Composition_transfers_portal_tunnel_before_constructing_frame_borrowers()
{ {
string phase = File.ReadAllText(Path.Combine( MethodInfo complete = RequiredMethod(
FindRepoRoot(), typeof(SessionPlayerCompositionPhase),
"src", "CompleteSessionPlayer");
"AcDream.App", AssertCallOrder(
"Composition", complete,
"SessionPlayerComposition.cs")); (typeof(TransferableResourceSlot<>), "Transfer"),
string source = GameWindowSource(); (typeof(DeferredLocalPlayerTeleportNetworkSink), "BindOwned"),
string framePhase = FrameRootSource(); (typeof(IGameWindowSessionPlayerPublication), "PublishSessionPlayer"));
AssertAppearsInOrder( MethodBase tunnelFactory = ReferencedMethodConstructing(
phase, complete,
"d.PortalTunnelFallback.Transfer(", typeof(LocalPlayerTeleportPresentation));
"new LocalPlayerTeleportController(", Assert.NotNull(ReferencedMethodConstructing(
"new LocalPlayerTeleportPresentation(portalTunnel)", tunnelFactory,
"d.TeleportSink.BindOwned(localTeleport)", typeof(LocalPlayerTeleportController)));
"_publication.PublishSessionPlayer(result);");
AssertAppearsInOrder( MethodInfo onLoad = RequiredMethod(typeof(GameWindow), "OnLoad");
source, IReadOnlyList<CompiledCall> loadReferences =
"new SessionPlayerCompositionPhase(", CompiledCallGraph.ReadMethodReferences(onLoad);
"new FrameRootCompositionPhase("); CompiledCall sessionPhase = Assert.Single(
AssertAppearsInOrder( loadReferences,
framePhase, reference => Constructs(
"new LocalPlayerTeleportRenderStateSource(", reference.Target,
"new RenderFrameResourceController(", typeof(SessionPlayerCompositionPhase)));
"new PrivatePresentationRenderer(", CompiledCall framePhase = Assert.Single(
"new RenderFrameOrchestrator("); loadReferences,
reference => Constructs(
reference.Target,
typeof(FrameRootCompositionPhase)));
Assert.True(sessionPhase.Offset < framePhase.Offset);
MethodInfo composeFrame = RequiredMethod(
typeof(FrameRootCompositionPhase),
"ComposeCore");
AssertCallOrder(
composeFrame,
(typeof(LocalPlayerTeleportRenderStateSource), ".ctor"),
(typeof(RenderFrameResourceController), ".ctor"),
(typeof(PrivatePresentationRenderer), ".ctor"),
(typeof(RenderFrameOrchestrator), ".ctor"));
} }
[Fact] [Fact]
public void ProductionComposition_RemovesLegacyLeafOwnership() public void ProductionComposition_RemovesLegacyLeafOwnership()
{ {
string source = GameWindowSource(); FieldInfo[] windowFields = typeof(GameWindow).GetFields(Declared);
HashSet<string> removedFieldNames =
string[] removed =
[ [
"_imguiBootstrap", "_imguiBootstrap",
"_panelHost", "_panelHost",
"_paperdollDollDirty", "_paperdollDollDirty",
"_lastRenderSignature",
"_lastVisibleLandblocks",
"_perfAccum",
"_entityUploadTiming",
"_weatherAccum",
];
HashSet<string> removedFieldTypes =
[
nameof(WorldRenderDiagnostics),
nameof(WorldRenderFrameBuilder),
nameof(SkyPesFrameController),
nameof(RetailPViewRenderer),
nameof(RetailPViewPassExecutor),
nameof(RetailPViewCellSource),
nameof(TerrainDrawDiagnosticsController),
];
Assert.DoesNotContain(
windowFields,
field => removedFieldNames.Contains(field.Name)
|| removedFieldTypes.Contains(field.FieldType.Name));
HashSet<string> removedMethods =
[
"RefreshPaperdollDoll", "RefreshPaperdollDoll",
"ApplyPaperdollPose", "ApplyPaperdollPose",
"ResolvePaperdollPoseDid", "ResolvePaperdollPoseDid",
"EnumerateDebugPanel", "EnumerateDebugPanel",
"ResetPanelLayout", "ResetPanelLayout",
"SetPanelLayout", "SetPanelLayout",
"_lastRenderSignature", "EmitRenderSignatureIfChanged",
"private void EmitRenderSignatureIfChanged(", "EmitRetailPViewDiagnostics",
"private void EmitRetailPViewDiagnostics(", "EmitGlStateTripwireIfChanged",
"EmitGlStateTripwireIfChanged();", "EmitClipRouteScissorProbe",
"EmitClipRouteScissorProbe(scissor",
"_lastVisibleLandblocks",
"_perfAccum",
"_entityUploadTiming",
"_weatherAccum",
"TryGetLoginWorldCell", "TryGetLoginWorldCell",
"ApplyFramePacingPreference", "ApplyFramePacingPreference",
"RefreshActiveMonitorFramePacing", "RefreshActiveMonitorFramePacing",
"private void OnFrameRendered(", "OnFrameRendered",
"private AcDream.App.Rendering.WorldRenderDiagnostics? _worldRenderDiagnostics",
"private AcDream.App.Rendering.WorldRenderFrameBuilder?",
"private AcDream.App.Rendering.SkyPesFrameController?",
"private AcDream.App.Rendering.RetailPViewRenderer?",
"private AcDream.App.Rendering.RetailPViewPassExecutor?",
"private AcDream.App.Rendering.RetailPViewCellSource?",
"private AcDream.App.Rendering.TerrainDrawDiagnosticsController?",
]; ];
foreach (string identifier in removed) Assert.DoesNotContain(
Assert.DoesNotContain(identifier, source, StringComparison.Ordinal); typeof(GameWindow).GetMethods(Declared),
method => removedMethods.Contains(method.Name));
Assert.Contains("new PaperdollFramePresenter(", LivePresentationSource()); Assert.Contains(
string framePhase = FrameRootSource(); CompiledCallGraph.ReadDeclared(typeof(LivePresentationCompositionPhase)),
Assert.Contains("new RenderFrameResourceController(", framePhase); call => call.Target.DeclaringType == typeof(PaperdollFramePresenter)
Assert.Contains("new RenderWeatherFrameController(", framePhase); && call.Target.IsConstructor);
Assert.Contains("new PrivatePresentationRenderer(", framePhase); IReadOnlyList<CompiledCall> frameCalls =
Assert.Contains("new RenderFrameOrchestrator(", framePhase); CompiledCallGraph.ReadDeclared(typeof(FrameRootCompositionPhase));
Assert.Contains("new DisplayFramePacingController(", source); Assert.All(
string pointerSource = File.ReadAllText(Path.Combine( new[]
FindRepoRoot(), {
"src", typeof(RenderFrameResourceController),
"AcDream.App", typeof(RenderWeatherFrameController),
"Input", typeof(PrivatePresentationRenderer),
"CameraPointerInputController.cs")); typeof(RenderFrameOrchestrator),
Assert.Contains("_capture.WantCaptureMouse", pointerSource); },
type => Assert.Contains(
frameCalls,
call => call.Target.DeclaringType == type
&& call.Target.IsConstructor));
Assert.Contains(
CompiledCallGraph.ReadDeclared(typeof(GameWindow)),
call => call.Target.DeclaringType == typeof(DisplayFramePacingController)
&& call.Target.IsConstructor);
Assert.Contains(
CompiledCallGraph.ReadDeclared(typeof(CameraPointerInputController)),
call => call.Target.DeclaringType == typeof(IInputCaptureSource)
&& call.Target.Name == "get_WantCaptureMouse");
} }
[Fact] [Fact]
public void Shutdown_DrainsGpuBeforeFrontendsAndPreservesFrameBorrowerOrder() public void Shutdown_DrainsGpuBeforeFrontendsAndPreservesFrameBorrowerOrder()
{ {
// Campaign V slice V11 removed the ImGui developer-tools frontend and MethodInfo create = RequiredMethod(
// its "developer tools" shutdown stage entry along with it; this test typeof(GameWindowShutdownManifest),
// used to pin that entry's position and is now renamed to pin what nameof(GameWindowShutdownManifest.Create));
// survives it. IReadOnlyList<string> labels = CompiledCallGraph.ReadStringLiterals(create);
string source = GameWindowLifetimeSource();
AssertAppearsInOrder( AssertLabelOrder(
source, labels,
"new ResourceShutdownStage(\"submitted GPU work\"", "submitted GPU work",
"new ResourceShutdownStage(\"render frontends\"", "render frontends",
"Hard(\"portal tunnel\"", "portal tunnel",
"Hard(\"paperdoll viewport\"", "paperdoll viewport",
"new ResourceShutdownStage(\"graphics API context\""); "graphics API context");
AssertAppearsInOrder( AssertLabelOrder(
source, labels,
"new ResourceShutdownStage(\"frame borrowers\"", "frame borrowers",
"frame.FrameGraphPublication?.Dispose()", "world frame composition",
"frame.FrameBindings?.Dispose()", "frame-root bindings",
"new ResourceShutdownStage(\"session dependents\""); "session dependents");
AssertAppearsInOrder( AssertLabelOrder(labels, "frame pacing", "frame profiler");
source, AssertLabelOrder(
"Hard(\"frame pacing\", render.FramePacing.Dispose)", labels,
"Hard(\"frame profiler\", render.FrameProfiler.Dispose)"); "world frame composition",
AssertAppearsInOrder( "render frontends",
source, "input context",
"frame.FrameGraphPublication?.Dispose()", "graphics API context");
"new ResourceShutdownStage(\"render frontends\"",
"new ResourceShutdownStage(\"input context\"",
"platform.Input?.Dispose()",
"new ResourceShutdownStage(\"graphics API context\"");
} }
[Fact] [Fact]
public void ProductionOutcome_UsesObservedWorldAndScreenshotFacts() public void ProductionOutcome_UsesObservedWorldAndScreenshotFacts()
{ {
string presentation = Source("PrivatePresentationRenderer.cs"); MethodInfo presentation = RequiredMethod(
string orchestrator = Source("RenderFrameOrchestrator.cs"); typeof(PrivatePresentationRenderer),
nameof(PrivatePresentationRenderer.Render));
AssertAppearsInOrder( AssertCallOrder(
presentation, presentation,
"_foundation.Foundation.PortalViewportVisible;", (typeof(RenderFrameFoundation), "get_PortalViewportVisible"),
"_portal.Draw(", (typeof(IPrivatePortalViewport), nameof(IPrivatePortalViewport.Draw)),
"bool screenshotCaptured = _screenshots?.CapturePending(", (typeof(IPrivateFrameScreenshot), nameof(IPrivateFrameScreenshot.CapturePending)),
"portalViewportVisible,", (typeof(PrivatePresentationFrameOutcome), ".ctor"));
"screenshotCaptured);");
AssertAppearsInOrder( MethodInfo orchestrator = RequiredMethod(
typeof(RenderFrameOrchestrator),
nameof(RenderFrameOrchestrator.Render));
AssertCallOrder(
orchestrator, orchestrator,
"_gpuMeasurement.BeginFrame();", (typeof(IRenderFrameGpuMeasurement), nameof(IRenderFrameGpuMeasurement.BeginFrame)),
"world = _world.Render(input);", (typeof(IWorldSceneFramePhase), nameof(IWorldSceneFramePhase.Render)),
"_presentation.Render(input, world);", (typeof(IPrivatePresentationFramePhase), nameof(IPrivatePresentationFramePhase.Render)),
"_gpuMeasurement.EndFrame();", (typeof(IRenderFrameGpuMeasurement), nameof(IRenderFrameGpuMeasurement.EndFrame)),
"new RenderFrameOutcome(world, presentation);", (typeof(RenderFrameOutcome), ".ctor"),
"_diagnostics.Publish(input, outcome);"); (typeof(IRenderFrameDiagnosticsPhase), nameof(IRenderFrameDiagnosticsPhase.Publish)));
} }
[Fact] [Fact]
public void GameWindow_OnRenderIsOneImmutableOrchestratorHandoff() public void GameWindow_OnRenderIsOneImmutableOrchestratorHandoff()
{ {
string source = GameWindowSource(); MethodInfo onRender = RequiredMethod(typeof(GameWindow), "OnRender");
int start = source.IndexOf( IReadOnlyList<CompiledCall> calls = CompiledCallGraph.Read(onRender);
"private void OnRender(double deltaSeconds)", Assert.Single(
StringComparison.Ordinal); calls,
int end = source.IndexOf( call => call.Target.DeclaringType == typeof(GameFrameGraphSlot)
"private void OnFramebufferResize(", && call.Target.Name == nameof(GameFrameGraphSlot.Render));
start,
StringComparison.Ordinal);
Assert.True(start >= 0 && end > start);
string body = source[start..end];
Assert.Equal(1, CountOccurrences(body, "_frameGraphs.Render(")); HashSet<string> forbiddenFields =
Assert.DoesNotContain("_gpuFrameFlights", body); [
Assert.DoesNotContain("_worldScene", body); "_gpuFrameFlights",
Assert.DoesNotContain("_devToolsFramePresenter", body); "_worldScene",
Assert.DoesNotContain("_retailUiRuntime", body); "_devToolsFramePresenter",
Assert.DoesNotContain("_frameScreenshots", body); "_retailUiRuntime",
"_frameScreenshots",
];
Assert.DoesNotContain(
CompiledCallGraph.ReadFieldReferences(onRender),
reference => forbiddenFields.Contains(reference.Field.Name));
} }
[Fact] [Fact]
public void PaperdollComposition_SkipsEitherMissingOptionalUiSurface() public void PaperdollComposition_SkipsEitherMissingOptionalUiSurface()
{ {
string source = LivePresentationSource(); MethodBase compose = MethodConstructing(
typeof(LivePresentationCompositionPhase),
typeof(PaperdollFramePresenter));
IReadOnlyList<CompiledCall> calls = CompiledCallGraph.Read(compose);
CompiledCall viewport = Assert.Single(
calls,
call => call.Target.DeclaringType == typeof(RetailUiRuntime)
&& call.Target.Name == "get_PaperdollViewportWidget");
CompiledCall inventory = Assert.Single(
calls,
call => call.Target.DeclaringType == typeof(RetailUiRuntime)
&& call.Target.Name == "get_InventoryFrame");
CompiledCall presenter = Assert.Single(
calls,
call => call.Target.DeclaringType == typeof(PaperdollFramePresenter)
&& call.Target.IsConstructor);
Assert.True(viewport.Offset < inventory.Offset);
Assert.True(inventory.Offset < presenter.Offset);
AssertAppearsInOrder( IReadOnlyList<CompiledBranch> branches = CompiledCallGraph.ReadBranches(compose);
source, Assert.Contains(
"PaperdollViewportWidget is { } viewport", branches,
"InventoryFrame is { } inventoryFrame", branch => branch.OpCode.FlowControl == System.Reflection.Emit.FlowControl.Cond_Branch
"new PaperdollFramePresenter("); && branch.Offset > viewport.Offset
Assert.DoesNotContain("Paperdoll inventory frame is required.", source); && branch.Offset < inventory.Offset
&& branch.TargetOffset > presenter.Offset);
Assert.Contains(
branches,
branch => branch.OpCode.FlowControl == System.Reflection.Emit.FlowControl.Cond_Branch
&& branch.Offset > inventory.Offset
&& branch.Offset < presenter.Offset
&& branch.TargetOffset > presenter.Offset);
Assert.DoesNotContain(
CompiledCallGraph.ReadStringLiterals(compose),
value => value == "Paperdoll inventory frame is required.");
} }
[Fact] [Fact]
public void TerrainAndFrameDiagnostics_AreComposedAsOneFocusedOwner() public void TerrainAndFrameDiagnostics_AreComposedAsOneFocusedOwner()
{ {
string source = FrameRootSource(); MethodInfo compose = RequiredMethod(
typeof(FrameRootCompositionPhase),
Assert.Contains( "ComposeCore");
"new TerrainDrawDiagnosticsController(", IReadOnlyList<CompiledCall> calls = CompiledCallGraph.Read(compose);
source); AssertCallOrder(
Assert.Contains("new WorldScenePassExecutor(", source); compose,
Assert.DoesNotContain("_terrainDrawDiagnostics!.Begin();", source); (typeof(TerrainDrawDiagnosticsController), ".ctor"),
Assert.DoesNotContain("_terrainDrawDiagnostics.Complete();", source); (typeof(WorldScenePassExecutor), ".ctor"));
Assert.DoesNotContain(
calls,
call => call.Target.DeclaringType == typeof(TerrainDrawDiagnosticsController)
&& (call.Target.Name == nameof(TerrainDrawDiagnosticsController.Begin)
|| call.Target.Name == nameof(TerrainDrawDiagnosticsController.Complete)));
} }
private static string GameWindowSource() => File.ReadAllText(Path.Combine( private static MethodInfo RequiredMethod(Type type, string name) =>
FindRepoRoot(), type.GetMethod(
"src", name,
"AcDream.App", BindingFlags.Instance | BindingFlags.Static
"Rendering", | BindingFlags.Public | BindingFlags.NonPublic)
"GameWindow.cs")); ?? throw new MissingMethodException(type.FullName, name);
private static string GameWindowLifetimeSource() => File.ReadAllText(Path.Combine( private static MethodBase MethodConstructing(Type owner, Type constructed) =>
FindRepoRoot(), Assert.Single(
"src", owner.GetMethods(Declared)
"AcDream.App", .Cast<MethodBase>()
"Rendering", .Concat(owner.GetConstructors(Declared))
"GameWindowLifetime.cs")); .Where(method => method.GetMethodBody() is not null),
method => Constructs(method, constructed));
private static string LivePresentationSource() => File.ReadAllText(Path.Combine( private static MethodBase ReferencedMethodConstructing(
FindRepoRoot(), MethodBase owner,
"src", Type constructed) =>
"AcDream.App", Assert.Single(
"Composition", CompiledCallGraph.ReadMethodReferences(owner)
"LivePresentationComposition.cs")); .Select(reference => reference.Target)
.Where(method => method.GetMethodBody() is not null)
.Distinct(),
method => Constructs(method, constructed));
private static string FrameRootSource() => File.ReadAllText(Path.Combine( private static bool Constructs(MethodBase method, Type type) =>
FindRepoRoot(), method.GetMethodBody() is not null
"src", && CompiledCallGraph.Read(method).Any(call =>
"AcDream.App", call.Target.DeclaringType == type && call.Target.IsConstructor);
"Composition",
"FrameRootComposition.cs"));
private static string Source(string fileName) => File.ReadAllText(Path.Combine( private static void AssertCallOrder(
FindRepoRoot(), MethodBase method,
"src", params (Type Type, string Method)[] expected)
"AcDream.App",
"Rendering",
fileName));
private static int CountOccurrences(string source, string value)
{ {
int count = 0; IReadOnlyList<CompiledCall> calls = CompiledCallGraph.Read(method);
int cursor = 0; int cursor = -1;
while ((cursor = source.IndexOf(value, cursor, StringComparison.Ordinal)) >= 0) foreach ((Type type, string name) in expected)
{ {
count++; int found = calls
cursor += value.Length; .Select((call, index) => (call, index))
.Where(pair => pair.index > cursor)
.Select(pair => pair.call)
.Select((call, index) => (call, index: index + cursor + 1))
.FirstOrDefault(
pair => MatchesType(pair.call.Target.DeclaringType, type)
&& pair.call.Target.Name == name,
defaultValue: (default, -1))
.index;
Assert.True(
found > cursor,
$"Missing compiled edge after index {cursor}: {type.FullName}.{name}.");
cursor = found;
} }
return count;
} }
private static void AssertAppearsInOrder(string source, params string[] needles) private static bool MatchesType(Type? actual, Type expected) =>
actual == expected
|| expected.IsGenericTypeDefinition
&& actual?.IsGenericType == true
&& actual.GetGenericTypeDefinition() == expected;
private static void AssertLabelOrder(
IReadOnlyList<string> labels,
params string[] expected)
{ {
int cursor = -1; int cursor = -1;
foreach (string needle in needles) foreach (string label in expected)
{ {
int next = source.IndexOf(needle, cursor + 1, StringComparison.Ordinal); int found = Enumerable.Range(cursor + 1, labels.Count - cursor - 1)
Assert.True(next >= 0, $"Missing expected source fragment: {needle}"); .FirstOrDefault(index => labels[index] == label, -1);
Assert.True(next > cursor, $"Out-of-order source fragment: {needle}"); Assert.True(found > cursor, $"Missing shutdown label after {cursor}: {label}.");
cursor = next; cursor = found;
} }
} }
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.");
}
} }