Completes the Generic / HmEG-aware / App-specific separation started in
f6b6e74. The legacy EgPlugin / EngineBridge / EngineBridge.Client /
EngineBridge.Probe modules are moved into their proper tiers, namespaces
and csproj/sln entries are renamed, and the HmegDirectStateProvider
lambdas are finally populated with real handles from the EgBim plugin
host. A new Recordingtest.Architecture.Tests project enforces the tier
rule at build time.
Moves (git mv + csproj/RootNamespace/AssemblyName rename + sln):
src/Recordingtest.EgPlugin
-> src/Sut/EgBim/Recordingtest.Sut.EgBim.PluginHost
src/Recordingtest.EngineBridge
-> src/Hmeg/Recordingtest.Hmeg.Catalog
src/Recordingtest.EngineBridge.Client
-> src/Hmeg/Recordingtest.Hmeg.Bridge.Client
src/Recordingtest.EngineBridge.Probe
-> src/Hmeg/Recordingtest.Hmeg.Catalog.Probe
tests/Recordingtest.EgPlugin.Tests
-> tests/Sut/EgBim/Recordingtest.Sut.EgBim.PluginHost.Tests
tests/Recordingtest.EngineBridge.Tests
-> tests/Hmeg/Recordingtest.Hmeg.Catalog.Tests
tests/Recordingtest.EngineBridge.IntegrationTests
-> tests/Hmeg/Recordingtest.Hmeg.Catalog.IntegrationTests
Namespace rename applied across all .cs files and csproj RootNamespace:
Recordingtest.EgPlugin -> Recordingtest.Sut.EgBim.PluginHost
Recordingtest.EngineBridge -> Recordingtest.Hmeg.Catalog
Recordingtest.EngineBridge.Client -> Recordingtest.Hmeg.Bridge.Client
Recordingtest.EngineBridge.Probe -> Recordingtest.Hmeg.Catalog.Probe
New: tests/Recordingtest.Architecture.Tests/
DependencyGraphTests walks Assembly.GetReferencedAssemblies() for each
tier and fails if a forbidden reference leaks in:
- Generic modules must not reference HmEG or any app-specific DLL
- HmEG-aware modules must not reference app-specific DLLs
- Recordingtest.Hmeg.Bridge must reference HmEG (positive check)
11 tests, all passing. Prevents future drift from CLAUDE.md §8.1.
Engine-bridge v3 wire-up (HmEgBridgePlugin.BuildProvider):
Previously the HmegDirectStateProvider lambdas returned null and the
chain fell through to reflection. They now call directly into the
EditorPlugin base class that HmEgBridgePlugin inherits:
spaceProvider = () => RootSpace
// AppManager.ViewportManager.RootSpace
viewportProvider = () => View
// EGViewport : Control, HmEGViewport
documentPathProvider = () => AppManager?.FileManager?.CurrentFile
Every lambda is wrapped in try/catch so plugin construction still
cannot throw back into the SUT. Editor02.HmEGAppManager.dll added as
a reference on Recordingtest.Sut.EgBim.PluginHost.csproj — app-
specific tier, which is allowed by the architecture tests.
Entry points were confirmed from read-only review of the SUT sources at
D:\GiteaAll\EG-BIM_Modeler\EditorPluginInterface\EditorPlugin.cs
D:\GiteaAll\EG-BIM_Modeler\HmEGApplicationManagementLibrary\HmEGAppManager.cs
D:\GiteaAll\EG-BIM_Modeler\HmEGApplicationManagementLibrary\SubManager\FileManager.cs
closing out Q1/Q2/Q6/Q7 from docs/hmeg-api-survey.md.
Tests: 115 -> 126 (+11 Architecture), 0 failures.
Next step: live verification of /scene /camera /selection with a real
SUT session; any discrepancy in HmegDirectStateProvider reflection will
be tightened after observing real HmEG camera field names.
Ref: #10 follow-up, #14 follow-up, docs/contracts/generic-sut-split.md.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
116 lines
3.9 KiB
C#
116 lines
3.9 KiB
C#
using System.IO;
|
|
using System.Reflection;
|
|
using Xunit;
|
|
|
|
namespace Recordingtest.Architecture.Tests;
|
|
|
|
/// <summary>
|
|
/// Enforces the 3-tier separation rule from CLAUDE.md §8.1:
|
|
///
|
|
/// Generic → no SUT assembly references at all
|
|
/// HmEG-aware → HmEG.dll only, no per-app assembly
|
|
/// App-specific → anything (not checked here; not referenced by this project)
|
|
///
|
|
/// These tests walk <see cref="Assembly.GetReferencedAssemblies"/> on each
|
|
/// compiled output and fail if a forbidden reference appears. The test
|
|
/// project itself does NOT reference any Sut/* project, so a physical build
|
|
/// error would catch accidental upward references even before these tests
|
|
/// run — this file is the explicit contract.
|
|
/// </summary>
|
|
public class DependencyGraphTests
|
|
{
|
|
private static readonly string[] ForbiddenAppAssemblies =
|
|
{
|
|
"Editor03.PluginInterface",
|
|
"Editor02.HmEGAppManager",
|
|
"EditorCore",
|
|
};
|
|
|
|
private const string HmegAssembly = "HmEG";
|
|
|
|
private static readonly string[] GenericAssemblies =
|
|
{
|
|
"Recordingtest.Bridge.Abstractions",
|
|
"Recordingtest.Recorder",
|
|
"Recordingtest.Player",
|
|
"Recordingtest.Normalizer",
|
|
"Recordingtest.DiffReporter",
|
|
"Recordingtest.Runner",
|
|
"Recordingtest.SutProber",
|
|
};
|
|
|
|
private static readonly string[] HmegAwareAssemblies =
|
|
{
|
|
"Recordingtest.Hmeg.Bridge",
|
|
"Recordingtest.Hmeg.Catalog",
|
|
"Recordingtest.Hmeg.Bridge.Client",
|
|
};
|
|
|
|
[Theory]
|
|
[InlineData("Recordingtest.Bridge.Abstractions")]
|
|
[InlineData("Recordingtest.Recorder")]
|
|
[InlineData("Recordingtest.Player")]
|
|
[InlineData("Recordingtest.Normalizer")]
|
|
[InlineData("Recordingtest.DiffReporter")]
|
|
[InlineData("Recordingtest.Runner")]
|
|
[InlineData("Recordingtest.SutProber")]
|
|
public void Generic_Tier_Does_Not_Reference_Hmeg_Or_AppAssemblies(string assemblyName)
|
|
{
|
|
var refs = LoadReferences(assemblyName);
|
|
Assert.DoesNotContain(HmegAssembly, refs);
|
|
foreach (var app in ForbiddenAppAssemblies)
|
|
{
|
|
Assert.DoesNotContain(app, refs);
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("Recordingtest.Hmeg.Bridge")]
|
|
[InlineData("Recordingtest.Hmeg.Catalog")]
|
|
[InlineData("Recordingtest.Hmeg.Bridge.Client")]
|
|
public void HmegAware_Tier_Does_Not_Reference_AppAssemblies(string assemblyName)
|
|
{
|
|
var refs = LoadReferences(assemblyName);
|
|
foreach (var app in ForbiddenAppAssemblies)
|
|
{
|
|
Assert.DoesNotContain(app, refs);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Hmeg_Bridge_References_HmEG_Dll()
|
|
{
|
|
// HmEG-aware tier is expected to reference HmEG.dll. This is the
|
|
// positive check that pairs with the App-specific forbidden list.
|
|
var refs = LoadReferences("Recordingtest.Hmeg.Bridge");
|
|
Assert.Contains(HmegAssembly, refs);
|
|
}
|
|
|
|
private static IReadOnlySet<string> LoadReferences(string assemblyName)
|
|
{
|
|
// Locate the assembly via a type we know belongs to it. Each tier
|
|
// member has at least one public type; we use GenericAssemblies/
|
|
// HmegAwareAssemblies as lookups by name.
|
|
Assembly? asm = null;
|
|
foreach (var loaded in AppDomain.CurrentDomain.GetAssemblies())
|
|
{
|
|
if (loaded.GetName().Name == assemblyName)
|
|
{
|
|
asm = loaded;
|
|
break;
|
|
}
|
|
}
|
|
if (asm is null)
|
|
{
|
|
// Force-load by trying to resolve a type: the test project has a
|
|
// ProjectReference to the target, so the binary is next to ours.
|
|
var dir = Path.GetDirectoryName(typeof(DependencyGraphTests).Assembly.Location)!;
|
|
var path = Path.Combine(dir, assemblyName + ".dll");
|
|
asm = Assembly.LoadFrom(path);
|
|
}
|
|
return new HashSet<string>(
|
|
asm.GetReferencedAssemblies().Select(n => n.Name ?? string.Empty),
|
|
StringComparer.Ordinal);
|
|
}
|
|
}
|