Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions Source/Mockolate/MockRegistry.Interactions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,28 @@ public void ClearAllInteractions()
return table[memberId];
}

/// <summary>
/// Returns the current default-scope property setup registered under the generator-emitted
/// <paramref name="memberId" />, or <see langword="null" /> when no setup has been registered.
/// </summary>
/// <remarks>
/// Property dispatch reads the snapshot via <see cref="GetPropertyFast{TResult}(int, string, Func{MockBehavior, TResult}, Func{TResult}?)" />
/// 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.
/// </remarks>
/// <param name="memberId">The generator-emitted member id for the property accessor.</param>
/// <returns>The setup for the property, or <see langword="null" /> when none is registered.</returns>
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];
}

/// <summary>
/// Enumerates method setups of type <typeparamref name="T" /> matching <paramref name="methodName" />
/// in latest-registered-first order, scenario-scoped setups before default-scope setups.
Expand Down
8 changes: 8 additions & 0 deletions Source/Mockolate/MockRegistry.Setup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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][];
Expand Down Expand Up @@ -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][];
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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][];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<int> setupLow = new(registry, "P5");
setupLow.InitializeWith(50);
PropertySetup<int> 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<int> defaultA = new("P1", 0);
PropertySetup.Default<int> 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()
{
Expand All @@ -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<int> userSetup = new(registry, "P1");
userSetup.InitializeWith(99);

registry.SetupProperty(1, userSetup);
registry.SetupProperty(1, new PropertySetup.Default<int>("P1", 0));

PropertySetup? snapshot = registry.GetPropertySetupSnapshot(1);
await That(snapshot).IsSameAs(userSetup);
}

[Fact]
public async Task PublishPropertyToMemberIdBucket_WithUserThenDefaultSetup_RetainsUserSetup()
{
Expand Down Expand Up @@ -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<int> 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()
{
Expand All @@ -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<int> 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()
{
Expand Down
Loading