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
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ dotnet run --project Containers.Benchmarks --configuration Release -- --filter "
## Testing Conventions

- Use MSTest framework (`[TestClass]`, `[TestMethod]`)
- Use `CollectionAssert.AreEqual` for collection equality (use `.ToArray()` if type inference issues arise)
- Use `Assert.AreSequenceEqual` for collection equality; `CollectionAssert.AreEqual` is flagged by MSTEST0068. It accepts any `IEnumerable<T>`, so no `.ToArray()` is needed
- Use `Assert.HasCount` rather than asserting `.Count`/`.Length` via `Assert.AreEqual` (MSTEST0037)
- Test edge cases: empty containers, boundary conditions, constructor validation

## Benchmarking Standards
Expand Down
9 changes: 9 additions & 0 deletions Containers.Benchmarks/Containers.Benchmarks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@
<Nullable>enable</Nullable>
<AssemblyName>Containers.Benchmarks</AssemblyName>
<RootNamespace>ktsu.Containers.Benchmarks</RootNamespace>
<!--
Exclude the benchmark harness from SonarQube/SonarCloud analysis. Benchmarks are
deliberately written in a non-idiomatic style: explicit foreach/counter loops are kept
identical across competing collections so the comparisons stay fair, which Sonar reports
as S3267 ("use Where"), and operations on intentionally-empty collections read as S4158.
Benchmark code is also never executed by the test suite, so it can only ever drag the
new-code coverage gate to 0%. It is not shipped in the package.
-->
<SonarQubeExclude>true</SonarQubeExclude>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,7 @@ public RingBuffer<int> CacheUsagePatternRingBuffer()
if (cache.Count > 10 && random.Next(5) == 0)
{
int index = random.Next(Math.Min(cache.Count, 10));
int value = cache[index]; // Access recent item
_ = cache[index]; // Access recent item; result intentionally unused
}
}

Expand Down
2 changes: 0 additions & 2 deletions Containers.Benchmarks/OrderedMapBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ public class OrderedMapBenchmarks
private readonly Random random = new(42);
private int[] keys = [];
private string[] values = [];
private KeyValuePair<int, string>[] keyValuePairs = [];

/// <summary>
/// Gets or sets the number of elements to use in benchmarks.
Expand All @@ -30,7 +29,6 @@ public void Setup()
{
keys = [.. Enumerable.Range(1, ElementCount).OrderBy(x => random.Next())];
values = [.. keys.Select(k => $"value{k}")];
keyValuePairs = [.. keys.Zip(values, (k, v) => new KeyValuePair<int, string>(k, v))];
}

/// <summary>
Expand Down
8 changes: 4 additions & 4 deletions Containers.Test/ContiguousCollectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ public void GetEnumerator_IteratesInOrder()
}

// Assert
CollectionAssert.AreEqual(expected, enumerated);
Assert.AreSequenceEqual(expected, enumerated);
}

[TestMethod]
Expand All @@ -364,7 +364,7 @@ public void AsSpan_ReturnsCorrectSpan()
Span<int> span = collection.AsSpan();

// Assert
Assert.AreEqual(4, span.Length);
Assert.HasCount(4, span);
Assert.AreEqual(3, span[0]);
Assert.AreEqual(1, span[1]);
Assert.AreEqual(4, span[2]);
Expand All @@ -381,7 +381,7 @@ public void AsReadOnlySpan_ReturnsCorrectSpan()
ReadOnlySpan<int> span = collection.AsReadOnlySpan();

// Assert
Assert.AreEqual(4, span.Length);
Assert.HasCount(4, span);
Assert.AreEqual(3, span[0]);
Assert.AreEqual(1, span[1]);
Assert.AreEqual(4, span[2]);
Expand Down Expand Up @@ -486,7 +486,7 @@ public void ContiguousMemoryLayout_OptimalForCachePerformance()

// Test span access (only possible with contiguous memory)
ReadOnlySpan<int> span = collection.AsReadOnlySpan();
Assert.AreEqual(1000, span.Length);
Assert.HasCount(1000, span);
for (int i = 0; i < 1000; i++)
{
Assert.AreEqual(i, span[i]);
Expand Down
60 changes: 56 additions & 4 deletions Containers.Test/ContiguousMapTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ public void GetKeysSpan_ReturnsCorrectSpan()
ReadOnlySpan<int> keysSpan = map.GetKeysSpan();

// Assert
Assert.AreEqual(4, keysSpan.Length);
Assert.HasCount(4, keysSpan);

// Verify all keys are in the span
HashSet<int> spanKeys = [];
Expand Down Expand Up @@ -440,7 +440,7 @@ public void GetValuesSpan_ReturnsCorrectSpan()
ReadOnlySpan<string> valuesSpan = map.GetValuesSpan();

// Assert
Assert.AreEqual(4, valuesSpan.Length);
Assert.HasCount(4, valuesSpan);

// Verify all values are in the span
HashSet<string> spanValues = [];
Expand Down Expand Up @@ -478,8 +478,8 @@ public void ContiguousMemoryLayout_OptimalForCachePerformance()
// Test span access (only possible with contiguous memory)
ReadOnlySpan<int> keysSpan = map.GetKeysSpan();
ReadOnlySpan<string> valuesSpan = map.GetValuesSpan();
Assert.AreEqual(1000, keysSpan.Length);
Assert.AreEqual(1000, valuesSpan.Length);
Assert.HasCount(1000, keysSpan);
Assert.HasCount(1000, valuesSpan);

// Verify all keys are in the span
HashSet<int> spanKeys = [];
Expand Down Expand Up @@ -525,4 +525,56 @@ public void HandlesNullValues()
Assert.IsTrue(map.TryGetValue(1, out string? value), "TryGetValue should return true for key with null value");
Assert.IsNull(value);
}

[TestMethod]
public void Entry_Equals_SameKeyAndValue_ReturnsTrue()
{
// Arrange
ContiguousMap<int, string>.Entry left = new(1, "one");
ContiguousMap<int, string>.Entry right = new(1, "one");

// Act & Assert
Assert.IsTrue(left.Equals(right), "Entries with the same key and value should be equal");
Assert.IsTrue(left.Equals((object)right), "Equals(object) should agree with Equals(Entry)");
Assert.IsTrue(left == right, "operator == should report equality");
Assert.IsFalse(left != right, "operator != should report equality");
Assert.AreEqual(left.GetHashCode(), right.GetHashCode());
}

[TestMethod]
public void Entry_Equals_DifferentKeyOrValue_ReturnsFalse()
{
// Arrange
ContiguousMap<int, string>.Entry entry = new(1, "one");
ContiguousMap<int, string>.Entry differentKey = new(2, "one");
ContiguousMap<int, string>.Entry differentValue = new(1, "two");

// Act & Assert
Assert.IsFalse(entry.Equals(differentKey), "Entries with different keys should not be equal");
Assert.IsFalse(entry.Equals(differentValue), "Entries with different values should not be equal");
Assert.IsTrue(entry != differentKey, "operator != should report inequality");
Assert.IsFalse(entry == differentValue, "operator == should report inequality");
}

[TestMethod]
public void Entry_Equals_NonEntryObject_ReturnsFalse()
{
// Arrange
ContiguousMap<int, string>.Entry entry = new(1, "one");

// Act & Assert
Assert.IsFalse(entry.Equals(null), "An entry should not equal null");
Assert.IsFalse(entry.Equals("not an entry"), "An entry should not equal an unrelated type");
}

[TestMethod]
public void Entry_KeyAndValue_ExposeConstructorArguments()
{
// Arrange & Act
ContiguousMap<int, string>.Entry entry = new(7, "seven");

// Assert
Assert.AreEqual(7, entry.Key);
Assert.AreEqual("seven", entry.Value);
}
}
4 changes: 2 additions & 2 deletions Containers.Test/ContiguousSetTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ public void AsReadOnlySpan_ReturnsCorrectSpan()
ReadOnlySpan<int> span = set.AsReadOnlySpan();

// Assert
Assert.AreEqual(4, span.Length);
Assert.HasCount(4, span);

// Verify all elements are in the span
HashSet<int> spanElements = [];
Expand Down Expand Up @@ -429,7 +429,7 @@ public void ContiguousMemoryLayout_OptimalForCachePerformance()

// Test span access (only possible with contiguous memory)
ReadOnlySpan<int> span = set.AsReadOnlySpan();
Assert.AreEqual(1000, span.Length);
Assert.HasCount(1000, span);

// Verify all elements are in the span
HashSet<int> spanElements = [];
Expand Down
2 changes: 1 addition & 1 deletion Containers.Test/InsertionOrderCollectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ public void GetEnumerator_IteratesInInsertionOrder()
}

// Assert
CollectionAssert.AreEqual(expected, enumerated);
Assert.AreSequenceEqual(expected, enumerated);
}

[TestMethod]
Expand Down
12 changes: 6 additions & 6 deletions Containers.Test/InsertionOrderMapTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public void Constructor_WithKeyValuePairs_AddsInInsertionOrder()

// Verify insertion order
int[] expectedOrder = [3, 1, 4, 2];
CollectionAssert.AreEqual(expectedOrder, map.Keys.ToArray());
Assert.AreSequenceEqual(expectedOrder, map.Keys);
}

[TestMethod]
Expand Down Expand Up @@ -242,7 +242,7 @@ public void Remove_ExistingKey_RemovesAndReturnsTrue()

// Verify order maintained
int[] expectedOrder = [1, 3];
CollectionAssert.AreEqual(expectedOrder, map.Keys.ToArray());
Assert.AreSequenceEqual(expectedOrder, map.Keys);
}

[TestMethod]
Expand Down Expand Up @@ -294,7 +294,7 @@ public void Keys_ReturnsKeysInInsertionOrder()

// Assert
int[] expectedOrder = [3, 1, 4, 2];
CollectionAssert.AreEqual(expectedOrder, keys);
Assert.AreSequenceEqual(expectedOrder, keys);
}

[TestMethod]
Expand All @@ -312,7 +312,7 @@ public void Values_ReturnsValuesInInsertionOrder()

// Assert
string[] expectedOrder = ["three", "one", "four", "two"];
CollectionAssert.AreEqual(expectedOrder, values);
Assert.AreSequenceEqual(expectedOrder, values);
}

[TestMethod]
Expand Down Expand Up @@ -406,7 +406,7 @@ public void MaintainsInsertionOrder_AfterMultipleOperations()

// Assert
int[] expectedOrder = [5, 2, 4]; // Insertion order minus removed items
CollectionAssert.AreEqual(expectedOrder, map.Keys.ToArray());
Assert.AreSequenceEqual(expectedOrder, map.Keys);
}

[TestMethod]
Expand All @@ -422,6 +422,6 @@ public void WorksWithCustomTypes()

// Assert
string[] expectedKeyOrder = ["charlie", "alpha", "bravo"];
CollectionAssert.AreEqual(expectedKeyOrder, map.Keys.ToArray());
Assert.AreSequenceEqual(expectedKeyOrder, map.Keys);
}
}
2 changes: 1 addition & 1 deletion Containers.Test/InsertionOrderSetTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ public void GetEnumerator_IteratesInInsertionOrder()
}

// Assert
CollectionAssert.AreEqual(expected, enumerated); // No duplicates, insertion order
Assert.AreSequenceEqual(expected, enumerated); // No duplicates, insertion order
}

[TestMethod]
Expand Down
2 changes: 1 addition & 1 deletion Containers.Test/OrderedCollectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,7 @@ public void GetEnumerator_IteratesInSortedOrder()
List<int> actual = [.. collection];

// Assert
CollectionAssert.AreEqual(expected, actual);
Assert.AreSequenceEqual(expected, actual);
}

[TestMethod]
Expand Down
22 changes: 11 additions & 11 deletions Containers.Test/OrderedMapTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public void Constructor_WithComparer_UsesCustomComparer()
};

string[] expectedOrder = ["three", "two", "one"];
CollectionAssert.AreEqual(expectedOrder, map.Values.ToArray());
Assert.AreSequenceEqual(expectedOrder, map.Values);
}

/// <summary>
Expand Down Expand Up @@ -68,8 +68,8 @@ public void Constructor_FromDictionary_CopiesAndSortsElements()
Assert.AreEqual(3, map.Count);
int[] expectedKeys = [1, 2, 3];
string[] expectedValues = ["one", "two", "three"];
CollectionAssert.AreEqual(expectedKeys, map.Keys.ToArray());
CollectionAssert.AreEqual(expectedValues, map.Values.ToArray());
Assert.AreSequenceEqual(expectedKeys, map.Keys);
Assert.AreSequenceEqual(expectedValues, map.Values);
}

/// <summary>
Expand All @@ -87,8 +87,8 @@ public void Add_MultipleElements_MaintainsSortedOrder()

int[] expectedKeys = [1, 2, 3];
string[] expectedValues = ["one", "two", "three"];
CollectionAssert.AreEqual(expectedKeys, map.Keys.ToArray());
CollectionAssert.AreEqual(expectedValues, map.Values.ToArray());
Assert.AreSequenceEqual(expectedKeys, map.Keys);
Assert.AreSequenceEqual(expectedValues, map.Values);
}

/// <summary>
Expand Down Expand Up @@ -165,7 +165,7 @@ public void Indexer_SetNewKey_AddsKeyValuePair()

Assert.AreEqual(3, map.Count);
int[] expectedKeys = [1, 2, 3];
CollectionAssert.AreEqual(expectedKeys, map.Keys.ToArray());
Assert.AreSequenceEqual(expectedKeys, map.Keys);
}

/// <summary>
Expand Down Expand Up @@ -237,7 +237,7 @@ public void Remove_ExistingKey_RemovesKeyAndReturnsTrue()
Assert.AreEqual(2, map.Count);
Assert.IsFalse(map.ContainsKey(2), "Removed key should no longer exist");
int[] expectedKeys = [1, 3];
CollectionAssert.AreEqual(expectedKeys, map.Keys.ToArray());
Assert.AreSequenceEqual(expectedKeys, map.Keys);
}

/// <summary>
Expand Down Expand Up @@ -304,7 +304,7 @@ public void Keys_ReturnsSortedKeys()
};

int[] expectedKeys = [1, 2, 3];
CollectionAssert.AreEqual(expectedKeys, map.Keys.ToArray());
Assert.AreSequenceEqual(expectedKeys, map.Keys);
}

/// <summary>
Expand All @@ -321,7 +321,7 @@ public void Values_ReturnsValuesInKeyOrder()
};

string[] expectedValues = ["one", "two", "three"];
CollectionAssert.AreEqual(expectedValues, map.Values.ToArray());
Assert.AreSequenceEqual(expectedValues, map.Values);
}

/// <summary>
Expand Down Expand Up @@ -381,8 +381,8 @@ public void Clone_CreatesShallowCopy()
OrderedMap<int, string> clone = map.Clone();

Assert.AreEqual(map.Count, clone.Count);
CollectionAssert.AreEqual(map.Keys.ToArray(), clone.Keys.ToArray());
CollectionAssert.AreEqual(map.Values.ToArray(), clone.Values.ToArray());
Assert.AreSequenceEqual(map.Keys, clone.Keys);
Assert.AreSequenceEqual(map.Values, clone.Values);

// Verify they are independent
clone.Add(3, "three");
Expand Down
Loading