BREAKING: 3-tier split step 2 + engine-bridge v3 EgBim lambdas wired
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>
This commit is contained in:
@@ -2,7 +2,7 @@ using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
|
||||
namespace Recordingtest.EngineBridge.IntegrationTests;
|
||||
namespace Recordingtest.Hmeg.Catalog.IntegrationTests;
|
||||
|
||||
public sealed class FakeBridgeServer : IDisposable
|
||||
{
|
||||
@@ -1,7 +1,7 @@
|
||||
using Recordingtest.EngineBridge.Client;
|
||||
using Recordingtest.Hmeg.Bridge.Client;
|
||||
using Xunit;
|
||||
|
||||
namespace Recordingtest.EngineBridge.IntegrationTests;
|
||||
namespace Recordingtest.Hmeg.Catalog.IntegrationTests;
|
||||
|
||||
public class HmEgHttpSnapshotTests
|
||||
{
|
||||
@@ -5,7 +5,8 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<IsPackable>false</IsPackable>
|
||||
<RootNamespace>Recordingtest.EngineBridge.IntegrationTests</RootNamespace>
|
||||
<RootNamespace>Recordingtest.Hmeg.Catalog.IntegrationTests</RootNamespace>
|
||||
<AssemblyName>Recordingtest.Hmeg.Catalog.IntegrationTests</AssemblyName>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
@@ -13,6 +14,6 @@
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Recordingtest.EngineBridge.Client\Recordingtest.EngineBridge.Client.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Hmeg\Recordingtest.Hmeg.Bridge.Client\Recordingtest.Hmeg.Bridge.Client.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,9 +1,9 @@
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using Recordingtest.EngineBridge;
|
||||
using Recordingtest.Hmeg.Catalog;
|
||||
using Xunit;
|
||||
|
||||
namespace Recordingtest.EngineBridge.Tests;
|
||||
namespace Recordingtest.Hmeg.Catalog.Tests;
|
||||
|
||||
public sealed class EngineBridgeTests
|
||||
{
|
||||
@@ -1,8 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<IsPackable>false</IsPackable>
|
||||
<RootNamespace>Recordingtest.EngineBridge.Tests</RootNamespace>
|
||||
<AssemblyName>Recordingtest.EngineBridge.Tests</AssemblyName>
|
||||
<RootNamespace>Recordingtest.Hmeg.Catalog.Tests</RootNamespace>
|
||||
<AssemblyName>Recordingtest.Hmeg.Catalog.Tests</AssemblyName>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
@@ -10,6 +10,6 @@
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Recordingtest.EngineBridge\Recordingtest.EngineBridge.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Hmeg\Recordingtest.Hmeg.Catalog\Recordingtest.Hmeg.Catalog.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
115
tests/Recordingtest.Architecture.Tests/DependencyGraphTests.cs
Normal file
115
tests/Recordingtest.Architecture.Tests/DependencyGraphTests.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<IsPackable>false</IsPackable>
|
||||
<RootNamespace>Recordingtest.Architecture.Tests</RootNamespace>
|
||||
<AssemblyName>Recordingtest.Architecture.Tests</AssemblyName>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<!-- Reference every Generic-tier assembly so we can inspect its dependency
|
||||
graph at test time. Add HmEG-aware here too; do NOT reference any
|
||||
Sut/* project or app-specific DLL. -->
|
||||
<ProjectReference Include="..\..\src\Recordingtest.Bridge.Abstractions\Recordingtest.Bridge.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\..\src\Recordingtest.Recorder\Recordingtest.Recorder.csproj" />
|
||||
<ProjectReference Include="..\..\src\Recordingtest.Player\Recordingtest.Player.csproj" />
|
||||
<ProjectReference Include="..\..\src\Recordingtest.Normalizer\Recordingtest.Normalizer.csproj" />
|
||||
<ProjectReference Include="..\..\src\Recordingtest.DiffReporter\Recordingtest.DiffReporter.csproj" />
|
||||
<ProjectReference Include="..\..\src\Recordingtest.Runner\Recordingtest.Runner.csproj" />
|
||||
<ProjectReference Include="..\..\src\Recordingtest.SutProber\Recordingtest.SutProber.csproj" />
|
||||
<ProjectReference Include="..\..\src\Hmeg\Recordingtest.Hmeg.Bridge\Recordingtest.Hmeg.Bridge.csproj" />
|
||||
<ProjectReference Include="..\..\src\Hmeg\Recordingtest.Hmeg.Catalog\Recordingtest.Hmeg.Catalog.csproj" />
|
||||
<ProjectReference Include="..\..\src\Hmeg\Recordingtest.Hmeg.Bridge.Client\Recordingtest.Hmeg.Bridge.Client.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,8 +1,8 @@
|
||||
using Recordingtest.Bridge;
|
||||
using Recordingtest.EgPlugin;
|
||||
using Recordingtest.Sut.EgBim.PluginHost;
|
||||
using Xunit;
|
||||
|
||||
namespace Recordingtest.EgPlugin.Tests;
|
||||
namespace Recordingtest.Sut.EgBim.PluginHost.Tests;
|
||||
|
||||
public class ChainedEngineStateProviderTests
|
||||
{
|
||||
@@ -5,7 +5,8 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<IsPackable>false</IsPackable>
|
||||
<RootNamespace>Recordingtest.EgPlugin.Tests</RootNamespace>
|
||||
<RootNamespace>Recordingtest.Sut.EgBim.PluginHost.Tests</RootNamespace>
|
||||
<AssemblyName>Recordingtest.Sut.EgBim.PluginHost.Tests</AssemblyName>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
@@ -13,6 +14,7 @@
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Recordingtest.EgPlugin\Recordingtest.EgPlugin.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Sut\EgBim\Recordingtest.Sut.EgBim.PluginHost\Recordingtest.Sut.EgBim.PluginHost.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Recordingtest.Bridge.Abstractions\Recordingtest.Bridge.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,8 +1,8 @@
|
||||
using Recordingtest.Bridge;
|
||||
using Recordingtest.EgPlugin;
|
||||
using Recordingtest.Sut.EgBim.PluginHost;
|
||||
using Xunit;
|
||||
|
||||
namespace Recordingtest.EgPlugin.Tests;
|
||||
namespace Recordingtest.Sut.EgBim.PluginHost.Tests;
|
||||
|
||||
public class ReflectionEngineStateProviderTests
|
||||
{
|
||||
@@ -1,9 +1,9 @@
|
||||
using System.Net;
|
||||
using Recordingtest.Bridge;
|
||||
using Recordingtest.EgPlugin;
|
||||
using Recordingtest.Sut.EgBim.PluginHost;
|
||||
using Xunit;
|
||||
|
||||
namespace Recordingtest.EgPlugin.Tests;
|
||||
namespace Recordingtest.Sut.EgBim.PluginHost.Tests;
|
||||
|
||||
public class StateRouterTests
|
||||
{
|
||||
Reference in New Issue
Block a user