arch(runtime): establish presentation-independent boundary
Create the dependency-only Runtime project before moving any gameplay owner, enforce its direct, transitive, source, and load-time closure, and pin coherent lifetime-group extraction plus retryable teardown ordering. No production behavior changes in J0. Validated by the four focused Runtime boundary tests, a zero-error Release solution build, and 8,406 passing Release tests with five pre-existing skips. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
9f37d17936
commit
b632672e5c
10 changed files with 457 additions and 2 deletions
127
tests/AcDream.Runtime.Tests/RuntimeDependencyBoundaryTests.cs
Normal file
127
tests/AcDream.Runtime.Tests/RuntimeDependencyBoundaryTests.cs
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
using System.Text.Json;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace AcDream.Runtime.Tests;
|
||||
|
||||
public sealed class RuntimeDependencyBoundaryTests
|
||||
{
|
||||
private static readonly string[] ForbiddenDependencyPrefixes =
|
||||
[
|
||||
"AcDream.App",
|
||||
"AcDream.UI.",
|
||||
"Silk.NET",
|
||||
"OpenAL",
|
||||
"Arch",
|
||||
"ImGui",
|
||||
];
|
||||
|
||||
[Fact]
|
||||
public void RuntimeAssemblyHasNoDirectPresentationOrBackendReferences()
|
||||
{
|
||||
var references = typeof(RuntimeAssemblyMarker).Assembly
|
||||
.GetReferencedAssemblies()
|
||||
.Select(static reference => reference.Name ?? string.Empty)
|
||||
.ToArray();
|
||||
|
||||
Assert.DoesNotContain(references, IsForbidden);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RuntimeDependencyClosureHasNoPresentationOrBackendLibraries()
|
||||
{
|
||||
var depsPath = Path.ChangeExtension(
|
||||
typeof(RuntimeDependencyBoundaryTests).Assembly.Location,
|
||||
".deps.json");
|
||||
|
||||
using var document = JsonDocument.Parse(File.ReadAllText(depsPath));
|
||||
var libraries = document.RootElement
|
||||
.GetProperty("libraries")
|
||||
.EnumerateObject()
|
||||
.Select(static library => LibraryName(library.Name))
|
||||
.ToArray();
|
||||
|
||||
Assert.DoesNotContain(libraries, IsForbidden);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RuntimeProjectDeclaresOnlyApprovedProjectDependencies()
|
||||
{
|
||||
var repositoryRoot = FindRepositoryRoot();
|
||||
var projectPath = Path.Combine(
|
||||
repositoryRoot,
|
||||
"src",
|
||||
"AcDream.Runtime",
|
||||
"AcDream.Runtime.csproj");
|
||||
var project = XDocument.Load(projectPath);
|
||||
var projectDirectory = Path.GetDirectoryName(projectPath)!;
|
||||
|
||||
var actualReferences = project
|
||||
.Descendants("ProjectReference")
|
||||
.Select(reference => reference.Attribute("Include")?.Value)
|
||||
.Where(static include => !string.IsNullOrWhiteSpace(include))
|
||||
.Select(include => Path.GetFullPath(Path.Combine(projectDirectory, include!)))
|
||||
.Order(StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
var expectedReferences = new[]
|
||||
{
|
||||
"AcDream.Content",
|
||||
"AcDream.Core",
|
||||
"AcDream.Core.Net",
|
||||
"AcDream.Plugin.Abstractions",
|
||||
}
|
||||
.Select(projectName => Path.Combine(
|
||||
repositoryRoot,
|
||||
"src",
|
||||
projectName,
|
||||
$"{projectName}.csproj"))
|
||||
.Order(StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
|
||||
Assert.Equal(expectedReferences, actualReferences);
|
||||
Assert.Empty(project.Descendants("PackageReference"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LoadingRuntimeMarkerDoesNotLoadPresentationOrBackendAssemblies()
|
||||
{
|
||||
_ = typeof(RuntimeAssemblyMarker).Assembly;
|
||||
|
||||
var loadedAssemblies = AppDomain.CurrentDomain
|
||||
.GetAssemblies()
|
||||
.Select(static assembly => assembly.GetName().Name ?? string.Empty)
|
||||
.ToArray();
|
||||
|
||||
Assert.DoesNotContain(loadedAssemblies, IsForbidden);
|
||||
}
|
||||
|
||||
private static string FindRepositoryRoot()
|
||||
{
|
||||
var directory = new DirectoryInfo(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 above {AppContext.BaseDirectory}.");
|
||||
}
|
||||
|
||||
private static string LibraryName(string libraryIdentity)
|
||||
{
|
||||
var separatorIndex = libraryIdentity.IndexOf('/');
|
||||
return separatorIndex < 0
|
||||
? libraryIdentity
|
||||
: libraryIdentity[..separatorIndex];
|
||||
}
|
||||
|
||||
private static bool IsForbidden(string assemblyName)
|
||||
{
|
||||
return ForbiddenDependencyPrefixes.Any(prefix =>
|
||||
assemblyName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue