using Microsoft.Extensions.DependencyInjection; using ZB.MOM.WW.ScadaBridge.SiteEventLogging; namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests.TestSupport; /// /// An that counts /// calls and each scope's /// , so a test can assert that one script run creates /// exactly one DI scope and disposes it exactly once — on the success, failure, AND timeout /// paths alike. Removing the per-run execution actor moved the scope's finally into /// ; a scope leaked /// there would silently leak every scoped service a script touches. /// public sealed class ScopeSpyServiceProvider(ISiteEventLogger? logger = null) : IServiceProvider, IServiceScopeFactory { private int _scopesCreated; private int _scopesDisposed; /// Scopes created so far. public int ScopesCreated => Volatile.Read(ref _scopesCreated); /// Scope disposals observed so far (double-disposal is counted twice, deliberately). public int ScopesDisposed => Volatile.Read(ref _scopesDisposed); /// Resolves the site event logger and this scope factory; every other service resolves to null. /// The requested service type. /// The matching service, or null when unsupported. public object? GetService(Type serviceType) { if (serviceType == typeof(ISiteEventLogger)) return logger; if (serviceType == typeof(IServiceScopeFactory)) return this; return null; } /// Creates a counted scope whose disposal is also counted. /// A spy scope. public IServiceScope CreateScope() { Interlocked.Increment(ref _scopesCreated); return new SpyScope(this); } private sealed class SpyScope(ScopeSpyServiceProvider owner) : IServiceScope { /// Gets the owning provider as the scope's service provider. public IServiceProvider ServiceProvider => owner; /// Releases the resources held by this instance. public void Dispose() => Interlocked.Increment(ref owner._scopesDisposed); } }