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