using Akka.Cluster; using Akka.TestKit.Xunit2; using Microsoft.EntityFrameworkCore; using ZB.MOM.WW.OtOpcUa.Configuration; namespace ZB.MOM.WW.OtOpcUa.Runtime.Tests.Harness; /// /// Mirrors the ControlPlane test harness: single-node Akka cluster (self-join) + in-memory /// EF Core via an . /// public abstract class RuntimeActorTestBase : TestKit { /// /// Shared upper bound for a presence wait (AwaitAssert / AwaitCondition). /// /// /// A budget, not a delay. These helpers poll and return the instant the condition holds, /// so the value is how long to wait before giving up. Raising it costs nothing on the happy path and /// cannot make a genuinely failing assertion pass — it only changes how quickly a real /// breakage is reported. That is the opposite of an absence window (ExpectNoMsg, /// settle-then-assert), where the elapsed time IS the assertion and every millisecond is spent on /// every run. Absence windows keep their own short, individually-calibrated literals and must not be /// routed through here. /// Why it exists (Gitea #500). This assembly runs 44 Akka test classes roughly 14-way /// parallel, each with its own ActorSystem. Budgets of 300–500 ms sized on an /// idle machine are comfortable in isolation and marginal under that contention: three consecutive /// 30-run verification rounds each surfaced a different test failing on one, in three /// different files. Fixing them one at a time was chasing a distribution rather than a defect. /// protected static readonly TimeSpan PresenceBudget = TimeSpan.FromSeconds(15); /// Gets the Akka test HOCON configuration string for single-node cluster setup. protected static string AkkaTestHocon => @" akka { loglevel = ""WARNING"" extensions = [ ""Akka.Cluster.Tools.PublishSubscribe.DistributedPubSubExtensionProvider, Akka.Cluster.Tools"" ] actor { provider = ""Akka.Cluster.ClusterActorRefProvider, Akka.Cluster"" } remote.dot-netty.tcp { hostname = ""127.0.0.1"" port = 0 } cluster { seed-nodes = [] roles = [""driver""] min-nr-of-members = 1 run-coordinated-shutdown-when-down = off } }"; /// Initializes a new instance of the RuntimeActorTestBase class with Akka test setup. protected RuntimeActorTestBase() : base(AkkaTestHocon) { var cluster = Akka.Cluster.Cluster.Get(Sys); cluster.Join(cluster.SelfAddress); AwaitCondition(() => cluster.State.Members.Any(m => m.Status == MemberStatus.Up), TimeSpan.FromSeconds(5)); } /// Creates a new in-memory database factory for testing. /// The database name, or null to generate a unique name. /// An IDbContextFactory for the in-memory database. protected static IDbContextFactory NewInMemoryDbFactory(string? dbName = null) { dbName ??= Guid.NewGuid().ToString("N"); return new InMemoryConfigDbFactory(dbName); } private sealed class InMemoryConfigDbFactory(string dbName) : IDbContextFactory { /// Creates a new in-memory database context. /// A new OtOpcUaConfigDbContext instance. public OtOpcUaConfigDbContext CreateDbContext() { var opts = new DbContextOptionsBuilder() .UseInMemoryDatabase(dbName) .Options; return new OtOpcUaConfigDbContext(opts); } } }