From c9ae6d27e2399a296201e02ff82736c8a4fe5562 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Breu=C3=9F?= Date: Thu, 30 Apr 2026 21:07:43 +0200 Subject: [PATCH] coverage: add tests and accessor for property setup snapshot in MockRegistry --- Source/Mockolate/MockRegistry.Interactions.cs | 22 ++++++ Source/Mockolate/MockRegistry.Setup.cs | 8 +++ .../MockRegistrySetupSnapshotTests.cs | 70 +++++++++++++++++++ 3 files changed, 100 insertions(+) diff --git a/Source/Mockolate/MockRegistry.Interactions.cs b/Source/Mockolate/MockRegistry.Interactions.cs index 75492975..90b7c9b9 100644 --- a/Source/Mockolate/MockRegistry.Interactions.cs +++ b/Source/Mockolate/MockRegistry.Interactions.cs @@ -89,6 +89,28 @@ public void ClearAllInteractions() return table[memberId]; } + /// + /// Returns the current default-scope property setup registered under the generator-emitted + /// , or when no setup has been registered. + /// + /// + /// Property dispatch reads the snapshot via + /// and falls back to the cold path when the snapshot is empty, so this accessor is intended for + /// diagnostics and tests that need to verify the fast-path table directly. + /// + /// The generator-emitted member id for the property accessor. + /// The setup for the property, or when none is registered. + internal PropertySetup? GetPropertySetupSnapshot(int memberId) + { + PropertySetup?[]? table = Volatile.Read(ref _propertySetupsByMemberId); + if (table is null || (uint)memberId >= (uint)table.Length) + { + return null; + } + + return table[memberId]; + } + /// /// Enumerates method setups of type matching /// in latest-registered-first order, scenario-scoped setups before default-scope setups. diff --git a/Source/Mockolate/MockRegistry.Setup.cs b/Source/Mockolate/MockRegistry.Setup.cs index 704e7a8c..7b99aedd 100644 --- a/Source/Mockolate/MockRegistry.Setup.cs +++ b/Source/Mockolate/MockRegistry.Setup.cs @@ -77,8 +77,10 @@ private void AppendToIndexerMemberIdBucket(int memberId, IndexerSetup indexerSet { IndexerSetup[]?[]? oldTable = _indexerSetupsByMemberId; int requiredLen = memberId + 1; + // Stryker disable once Conditional : when oldTable is not null and requiredLen <= oldTable.Length, the resize-condition below is false either way and the else-branch reuses oldTable; newLen is unread, so collapsing the ternary to requiredLen is observationally identical. int newLen = oldTable is null ? requiredLen : Math.Max(oldTable.Length, requiredLen); IndexerSetup[]?[] newTable; + // Stryker disable once Equality : flipping > to >= only fires the realloc-and-copy path when newLen == oldTable.Length, producing a fresh table with identical contents — externally indistinguishable from reusing oldTable. if (oldTable is null || newLen > oldTable.Length) { newTable = new IndexerSetup[newLen][]; @@ -168,8 +170,10 @@ private void AppendToMemberIdBucket(int memberId, MethodSetup methodSetup) { MethodSetup[]?[]? oldTable = _setupsByMemberId; int requiredLen = memberId + 1; + // Stryker disable once Conditional : when oldTable is not null and requiredLen <= oldTable.Length, the resize-condition below is false either way and the else-branch reuses oldTable; newLen is unread, so collapsing the ternary to requiredLen is observationally identical. int newLen = oldTable is null ? requiredLen : Math.Max(oldTable.Length, requiredLen); MethodSetup[]?[] newTable; + // Stryker disable once Equality : flipping > to >= only fires the realloc-and-copy path when newLen == oldTable.Length, producing a fresh table with identical contents — externally indistinguishable from reusing oldTable. if (oldTable is null || newLen > oldTable.Length) { newTable = new MethodSetup[newLen][]; @@ -261,8 +265,10 @@ private void PublishPropertyToMemberIdBucket(int memberId, PropertySetup propert { PropertySetup?[]? oldTable = _propertySetupsByMemberId; int requiredLen = memberId + 1; + // Stryker disable once Conditional : when oldTable is not null and requiredLen <= oldTable.Length, the resize-condition below is false either way and the else-branch reuses oldTable; newLen is unread, so collapsing the ternary to requiredLen is observationally identical. int newLen = oldTable is null ? requiredLen : Math.Max(oldTable.Length, requiredLen); PropertySetup?[] newTable; + // Stryker disable once Equality : flipping > to >= only fires the realloc-and-copy path when newLen == oldTable.Length, producing a fresh table with identical contents — externally indistinguishable from reusing oldTable. if (oldTable is null || newLen > oldTable.Length) { newTable = new PropertySetup?[newLen]; @@ -344,8 +350,10 @@ private void AppendToEventMemberIdBucket(int memberId, EventSetup eventSetup) { EventSetup[]?[]? oldTable = _eventSetupsByMemberId; int requiredLen = memberId + 1; + // Stryker disable once Conditional : when oldTable is not null and requiredLen <= oldTable.Length, the resize-condition below is false either way and the else-branch reuses oldTable; newLen is unread, so collapsing the ternary to requiredLen is observationally identical. int newLen = oldTable is null ? requiredLen : Math.Max(oldTable.Length, requiredLen); EventSetup[]?[] newTable; + // Stryker disable once Equality : flipping > to >= only fires the realloc-and-copy path when newLen == oldTable.Length, producing a fresh table with identical contents — externally indistinguishable from reusing oldTable. if (oldTable is null || newLen > oldTable.Length) { newTable = new EventSetup[newLen][]; diff --git a/Tests/Mockolate.Internal.Tests/Registry/MockRegistrySetupSnapshotTests.cs b/Tests/Mockolate.Internal.Tests/Registry/MockRegistrySetupSnapshotTests.cs index b71ac00b..5051a7ff 100644 --- a/Tests/Mockolate.Internal.Tests/Registry/MockRegistrySetupSnapshotTests.cs +++ b/Tests/Mockolate.Internal.Tests/Registry/MockRegistrySetupSnapshotTests.cs @@ -103,6 +103,36 @@ public async Task SetupMethod_WithSameMemberIdTwice_AppendsToBucket() public sealed class PropertyTests { + [Fact] + public async Task PublishPropertyToMemberIdBucket_WhenResized_PreservesEarlierEntries() + { + MockRegistry registry = new(MockBehavior.Default, new FastMockInteractions(0)); + PropertySetup setupLow = new(registry, "P5"); + setupLow.InitializeWith(50); + PropertySetup setupHigh = new(registry, "P10"); + setupHigh.InitializeWith(100); + + registry.SetupProperty(5, setupLow); + registry.SetupProperty(10, setupHigh); + + await That(registry.GetPropertySetupSnapshot(5)).IsSameAs(setupLow); + await That(registry.GetPropertySetupSnapshot(10)).IsSameAs(setupHigh); + } + + [Fact] + public async Task PublishPropertyToMemberIdBucket_WithDefaultThenDefault_OverwritesInSnapshotTable() + { + MockRegistry registry = new(MockBehavior.Default, new FastMockInteractions(0)); + PropertySetup.Default defaultA = new("P1", 0); + PropertySetup.Default defaultB = new("P1", 0); + + registry.SetupProperty(1, defaultA); + registry.SetupProperty(1, defaultB); + + PropertySetup? snapshot = registry.GetPropertySetupSnapshot(1); + await That(snapshot).IsSameAs(defaultB); + } + [Fact] public async Task PublishPropertyToMemberIdBucket_WithDefaultThenUserSetup_RetainsUserSetup() { @@ -119,6 +149,20 @@ public async Task PublishPropertyToMemberIdBucket_WithDefaultThenUserSetup_Retai await That(observed).IsEqualTo(99); } + [Fact] + public async Task PublishPropertyToMemberIdBucket_WithUserThenDefault_RetainsUserSetupInSnapshotTable() + { + MockRegistry registry = new(MockBehavior.Default, new FastMockInteractions(0)); + PropertySetup userSetup = new(registry, "P1"); + userSetup.InitializeWith(99); + + registry.SetupProperty(1, userSetup); + registry.SetupProperty(1, new PropertySetup.Default("P1", 0)); + + PropertySetup? snapshot = registry.GetPropertySetupSnapshot(1); + await That(snapshot).IsSameAs(userSetup); + } + [Fact] public async Task PublishPropertyToMemberIdBucket_WithUserThenDefaultSetup_RetainsUserSetup() { @@ -166,6 +210,19 @@ public async Task SetupProperty_WithMemberId_PublishesToSnapshot() await That(observed).IsEqualTo(50); } + [Fact] + public async Task SetupProperty_WithMemberId_PublishesUserSetupToSnapshotTable() + { + MockRegistry registry = new(MockBehavior.Default, new FastMockInteractions(0)); + PropertySetup setup = new(registry, "P5"); + setup.InitializeWith(50); + + registry.SetupProperty(5, setup); + + PropertySetup? snapshot = registry.GetPropertySetupSnapshot(5); + await That(snapshot).IsSameAs(setup); + } + [Fact] public async Task SetupProperty_WithMemberIdAndDefaultScenario_PublishesToSnapshot() { @@ -179,6 +236,19 @@ public async Task SetupProperty_WithMemberIdAndDefaultScenario_PublishesToSnapsh await That(observed).IsEqualTo(50); } + [Fact] + public async Task SetupProperty_WithMemberIdAndDefaultScenario_PublishesUserSetupToSnapshotTable() + { + MockRegistry registry = new(MockBehavior.Default, new FastMockInteractions(0)); + PropertySetup setup = new(registry, "P5"); + setup.InitializeWith(50); + + registry.SetupProperty(5, "", setup); + + PropertySetup? snapshot = registry.GetPropertySetupSnapshot(5); + await That(snapshot).IsSameAs(setup); + } + [Fact] public async Task SetupProperty_WithMemberIdAndNamedScenario_DoesNotPublishToSnapshot() {