From 06293e8a91266fc55cf392a86e30f65b4c3f384f Mon Sep 17 00:00:00 2001 From: Adam Tovatt Date: Sun, 19 Apr 2026 19:04:52 +0200 Subject: [PATCH] migrate remaining test projects from MSTest to xUnit --- .../AssemblyInfo.cs | 1 + .../EmbeddingPerformanceTests.cs | 71 ++++---- .../MSTestSettings.cs | 1 - .../NomicEmbedProviderTests.cs | 75 ++++----- .../TestHelpers.cs | 15 ++ .../TokenizerDebugTests.cs | 12 +- ...torSharp.Embedding.NomicEmbed.Tests.csproj | 7 +- .../EmbeddingServiceOptionsTests.cs | 17 +- .../EmbeddingServiceTests.cs | 79 +++++---- VectorSharp.Embedding.Tests/MSTestSettings.cs | 1 - .../VectorSharp.Embedding.Tests.csproj | 6 +- .../BinaryFormatTests.cs | 69 ++++---- .../CosineSimilarityTests.cs | 39 +++-- .../CosineVectorStorePerformanceTests.cs | 49 +++--- .../CosineVectorStorePersistenceTests.cs | 47 +++--- .../CosineVectorStoreTests.cs | 159 +++++++++--------- .../DiskVectorStoreTests.cs | 149 ++++++++-------- VectorSharp.Storage.Tests/MSTestSettings.cs | 1 - VectorSharp.Storage.Tests/MinHeapTests.cs | 89 +++++----- .../SearchResultTests.cs | 25 ++- VectorSharp.Storage.Tests/TestHelpers.cs | 10 ++ .../VectorSearchTests.cs | 57 +++---- .../VectorSharp.Storage.Tests.csproj | 6 +- 23 files changed, 499 insertions(+), 486 deletions(-) create mode 100644 VectorSharp.Embedding.NomicEmbed.Tests/AssemblyInfo.cs delete mode 100644 VectorSharp.Embedding.NomicEmbed.Tests/MSTestSettings.cs create mode 100644 VectorSharp.Embedding.NomicEmbed.Tests/TestHelpers.cs delete mode 100644 VectorSharp.Embedding.Tests/MSTestSettings.cs delete mode 100644 VectorSharp.Storage.Tests/MSTestSettings.cs diff --git a/VectorSharp.Embedding.NomicEmbed.Tests/AssemblyInfo.cs b/VectorSharp.Embedding.NomicEmbed.Tests/AssemblyInfo.cs new file mode 100644 index 0000000..e5cc5d4 --- /dev/null +++ b/VectorSharp.Embedding.NomicEmbed.Tests/AssemblyInfo.cs @@ -0,0 +1 @@ +[assembly: CollectionBehavior(DisableTestParallelization = true)] diff --git a/VectorSharp.Embedding.NomicEmbed.Tests/EmbeddingPerformanceTests.cs b/VectorSharp.Embedding.NomicEmbed.Tests/EmbeddingPerformanceTests.cs index e99eb77..87f1fcf 100644 --- a/VectorSharp.Embedding.NomicEmbed.Tests/EmbeddingPerformanceTests.cs +++ b/VectorSharp.Embedding.NomicEmbed.Tests/EmbeddingPerformanceTests.cs @@ -4,9 +4,7 @@ namespace VectorSharp.Embedding.NomicEmbed.Tests { - [TestClass] - [TestCategory("RequiresModel")] - [DoNotParallelize] + [Trait("Category", "RequiresModel")] public class EmbeddingPerformanceTests { private const int WarmupCount = 5; @@ -17,10 +15,9 @@ private static string GetModelDirectory() string assemblyDir = Path.GetDirectoryName(typeof(EmbeddingPerformanceTests).Assembly.Location)!; string modelsDir = Path.Combine(assemblyDir, "Models"); - if (!Directory.Exists(modelsDir) || !File.Exists(Path.Combine(modelsDir, "model_int8.onnx"))) - { - Assert.Inconclusive("Model files not found. Run tools/download-nomic-model.sh first."); - } + Skip.IfNot( + Directory.Exists(modelsDir) && File.Exists(Path.Combine(modelsDir, "model_int8.onnx")), + "Model files not found. Run tools/download-nomic-model.sh first."); return modelsDir; } @@ -36,7 +33,7 @@ private static string[] CreateSampleTexts(int count) return texts; } - [TestMethod] + [SkippableFact] public async Task PerformanceTest_SingleWorkerThroughput() { string modelsDir = GetModelDirectory(); @@ -62,7 +59,7 @@ public async Task PerformanceTest_SingleWorkerThroughput() for (int i = 0; i < EmbeddingCount; i++) { float[] result = await service.EmbedAsync(texts[i]); - Assert.AreEqual(768, result.Length); + Assert.Equal(768, result.Length); } timer.Stop(); @@ -75,13 +72,13 @@ public async Task PerformanceTest_SingleWorkerThroughput() Console.WriteLine($"Per embedding: {msPerEmbedding:F2} ms"); Console.WriteLine($"Throughput: {embeddingsPerSecond:F1} embeddings/second"); - Assert.IsTrue(timer.ElapsedMilliseconds > 0); + Assert.True(timer.ElapsedMilliseconds > 0); } - [TestMethod] - [DataRow(1)] - [DataRow(2)] - [DataRow(4)] + [SkippableTheory] + [InlineData(1)] + [InlineData(2)] + [InlineData(4)] public async Task PerformanceTest_ThroughputByConcurrency(int concurrency) { string modelsDir = GetModelDirectory(); @@ -118,23 +115,23 @@ public async Task PerformanceTest_ThroughputByConcurrency(int concurrency) Console.WriteLine($"Per embedding: {msPerEmbedding:F2} ms"); Console.WriteLine($"Throughput: {embeddingsPerSecond:F1} embeddings/second"); - Assert.AreEqual(EmbeddingCount, results.Length); + Assert.Equal(EmbeddingCount, results.Length); foreach (float[] result in results) { - Assert.AreEqual(768, result.Length); + Assert.Equal(768, result.Length); } } - [TestMethod] - [DataRow(1, 0)] - [DataRow(2, 0)] - [DataRow(4, 0)] - [DataRow(2, 2)] - [DataRow(3, 2)] - [DataRow(4, 2)] - [DataRow(6, 2)] - [DataRow(6, 3)] - [DataRow(9, 2)] + [SkippableTheory] + [InlineData(1, 0)] + [InlineData(2, 0)] + [InlineData(4, 0)] + [InlineData(2, 2)] + [InlineData(3, 2)] + [InlineData(4, 2)] + [InlineData(6, 2)] + [InlineData(6, 3)] + [InlineData(9, 2)] public async Task PerformanceTest_ThreadsVsConcurrency(int concurrency, int intraOpThreads) { string modelsDir = GetModelDirectory(); @@ -174,10 +171,10 @@ public async Task PerformanceTest_ThreadsVsConcurrency(int concurrency, int intr double embeddingsPerSecond = EmbeddingCount / timer.Elapsed.TotalSeconds; Console.WriteLine($"Total: {timer.ElapsedMilliseconds:N0} ms, Throughput: {embeddingsPerSecond:F1} embeddings/sec"); - Assert.AreEqual(EmbeddingCount, results.Length); + Assert.Equal(EmbeddingCount, results.Length); } - [TestMethod] + [SkippableFact] public async Task PerformanceTest_MaxThroughput() { string modelsDir = GetModelDirectory(); @@ -221,14 +218,14 @@ public async Task PerformanceTest_MaxThroughput() Console.WriteLine($"Per embedding: {timer.Elapsed.TotalMilliseconds / count:F2} ms"); Console.WriteLine($"Throughput: {embeddingsPerSecond:F1} embeddings/sec"); - Assert.AreEqual(count, results.Length); + Assert.Equal(count, results.Length); } - [TestMethod] - [DataRow(0)] - [DataRow(2)] - [DataRow(3)] - [DataRow(4)] + [SkippableTheory] + [InlineData(0)] + [InlineData(2)] + [InlineData(3)] + [InlineData(4)] public async Task PerformanceTest_SweetSpotThroughput(int intraOpThreads) { string modelsDir = GetModelDirectory(); @@ -274,10 +271,10 @@ public async Task PerformanceTest_SweetSpotThroughput(int intraOpThreads) Console.WriteLine($"Per embedding: {timer.Elapsed.TotalMilliseconds / count:F2} ms"); Console.WriteLine($"Throughput: {embeddingsPerSecond:F1} embeddings/sec"); - Assert.AreEqual(count, results.Length); + Assert.Equal(count, results.Length); } - [TestMethod] + [SkippableFact] public async Task PerformanceTest_ConcurrencyScaling() { string modelsDir = GetModelDirectory(); @@ -327,7 +324,7 @@ public async Task PerformanceTest_ConcurrencyScaling() Console.WriteLine($"Concurrency {concurrency}: {speedup:F2}x vs single worker"); } - Assert.IsTrue(results[1] > 0); + Assert.True(results[1] > 0); } } } diff --git a/VectorSharp.Embedding.NomicEmbed.Tests/MSTestSettings.cs b/VectorSharp.Embedding.NomicEmbed.Tests/MSTestSettings.cs deleted file mode 100644 index aaf278c..0000000 --- a/VectorSharp.Embedding.NomicEmbed.Tests/MSTestSettings.cs +++ /dev/null @@ -1 +0,0 @@ -[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)] diff --git a/VectorSharp.Embedding.NomicEmbed.Tests/NomicEmbedProviderTests.cs b/VectorSharp.Embedding.NomicEmbed.Tests/NomicEmbedProviderTests.cs index 02d5c6b..1f98735 100644 --- a/VectorSharp.Embedding.NomicEmbed.Tests/NomicEmbedProviderTests.cs +++ b/VectorSharp.Embedding.NomicEmbed.Tests/NomicEmbedProviderTests.cs @@ -3,9 +3,7 @@ namespace VectorSharp.Embedding.NomicEmbed.Tests { - [TestClass] - [TestCategory("RequiresModel")] - [DoNotParallelize] + [Trait("Category", "RequiresModel")] public class NomicEmbedProviderTests { private static string GetModelDirectory() @@ -13,29 +11,28 @@ private static string GetModelDirectory() string assemblyDir = Path.GetDirectoryName(typeof(NomicEmbedProviderTests).Assembly.Location)!; string modelsDir = Path.Combine(assemblyDir, "Models"); - if (!Directory.Exists(modelsDir) || !File.Exists(Path.Combine(modelsDir, "model_int8.onnx"))) - { - Assert.Inconclusive("Model files not found. Run tools/download-nomic-model.sh first."); - } + Skip.IfNot( + Directory.Exists(modelsDir) && File.Exists(Path.Combine(modelsDir, "model_int8.onnx")), + "Model files not found. Run tools/download-nomic-model.sh first."); return modelsDir; } #region Factory - [TestMethod] + [SkippableFact] public void Create_CustomPath_Succeeds() { string modelsDir = GetModelDirectory(); using NomicEmbedProvider provider = NomicEmbedProvider.Create(modelsDir); - Assert.AreEqual(768, provider.Dimension); + Assert.Equal(768, provider.Dimension); } - [TestMethod] + [Fact] public void Create_InvalidPath_Throws() { - Assert.ThrowsExactly(() => + Assert.Throws(() => NomicEmbedProvider.Create("/nonexistent/path")); } @@ -43,7 +40,7 @@ public void Create_InvalidPath_Throws() #region Embedding - [TestMethod] + [SkippableFact] public async Task EmbedAsync_ShortText_ReturnsVector768() { string modelsDir = GetModelDirectory(); @@ -51,10 +48,10 @@ public async Task EmbedAsync_ShortText_ReturnsVector768() float[] result = await provider.EmbedAsync("hello world"); - Assert.AreEqual(768, result.Length); + Assert.Equal(768, result.Length); } - [TestMethod] + [SkippableFact] public async Task EmbedAsync_SameText_ReturnsSameVector() { string modelsDir = GetModelDirectory(); @@ -63,10 +60,10 @@ public async Task EmbedAsync_SameText_ReturnsSameVector() float[] result1 = await provider.EmbedAsync("deterministic test"); float[] result2 = await provider.EmbedAsync("deterministic test"); - CollectionAssert.AreEqual(result1, result2); + Assert.Equal(result1, result2); } - [TestMethod] + [SkippableFact] public async Task EmbedAsync_DifferentTexts_ReturnsDifferentVectors() { string modelsDir = GetModelDirectory(); @@ -75,10 +72,10 @@ public async Task EmbedAsync_DifferentTexts_ReturnsDifferentVectors() float[] result1 = await provider.EmbedAsync("cats are great"); float[] result2 = await provider.EmbedAsync("quantum physics equations"); - CollectionAssert.AreNotEqual(result1, result2); + Assert.NotEqual(result1, result2); } - [TestMethod] + [SkippableFact] public async Task EmbedAsync_ResultIsL2Normalized() { string modelsDir = GetModelDirectory(); @@ -94,10 +91,10 @@ public async Task EmbedAsync_ResultIsL2Normalized() magnitude = MathF.Sqrt(magnitude); - Assert.AreEqual(1.0f, magnitude, 0.001f); + TestHelpers.AssertApproximatelyEqual(1.0f, magnitude, 0.001f); } - [TestMethod] + [SkippableFact] public async Task EmbedAsync_EmptyString_ReturnsVector768() { string modelsDir = GetModelDirectory(); @@ -105,10 +102,10 @@ public async Task EmbedAsync_EmptyString_ReturnsVector768() float[] result = await provider.EmbedAsync(""); - Assert.AreEqual(768, result.Length); + Assert.Equal(768, result.Length); } - [TestMethod] + [SkippableFact] public async Task EmbedAsync_SimilarTexts_HaveHigherSimilarity() { string modelsDir = GetModelDirectory(); @@ -121,7 +118,7 @@ public async Task EmbedAsync_SimilarTexts_HaveHigherSimilarity() float catKittenSimilarity = CosineSimilarity(catResult, kittenResult); float catMathSimilarity = CosineSimilarity(catResult, mathResult); - Assert.IsTrue(catKittenSimilarity > catMathSimilarity, + Assert.True(catKittenSimilarity > catMathSimilarity, $"Cat-kitten similarity ({catKittenSimilarity:F4}) should be higher than cat-math ({catMathSimilarity:F4})"); } @@ -129,7 +126,7 @@ public async Task EmbedAsync_SimilarTexts_HaveHigherSimilarity() #region Task Prefix - [TestMethod] + [SkippableFact] public async Task EmbedAsync_QueryPurpose_ReturnsDifferentFromDocumentPurpose() { string modelsDir = GetModelDirectory(); @@ -138,14 +135,14 @@ public async Task EmbedAsync_QueryPurpose_ReturnsDifferentFromDocumentPurpose() float[] docResult = await provider.EmbedAsync("test text", EmbeddingPurpose.Document); float[] queryResult = await provider.EmbedAsync("test text", EmbeddingPurpose.Query); - CollectionAssert.AreNotEqual(docResult, queryResult); + Assert.NotEqual(docResult, queryResult); } #endregion #region Integration with EmbeddingService - [TestMethod] + [SkippableFact] public async Task EmbeddingService_WithNomicProvider_ProducesValidEmbeddings() { string modelsDir = GetModelDirectory(); @@ -153,24 +150,24 @@ public async Task EmbeddingService_WithNomicProvider_ProducesValidEmbeddings() await using EmbeddingService service = new EmbeddingService( () => NomicEmbedProvider.Create(modelsDir)); - Assert.AreEqual(768, service.Dimension); + Assert.Equal(768, service.Dimension); float[] result = await service.EmbedAsync("integration test"); - Assert.AreEqual(768, result.Length); + Assert.Equal(768, result.Length); } #endregion #region Disposal - [TestMethod] + [SkippableFact] public async Task Dispose_ThenEmbed_Throws() { string modelsDir = GetModelDirectory(); NomicEmbedProvider provider = NomicEmbedProvider.Create(modelsDir); provider.Dispose(); - await Assert.ThrowsExactlyAsync(() => + await Assert.ThrowsAsync(() => provider.EmbedAsync("hello")); } @@ -178,7 +175,7 @@ await Assert.ThrowsExactlyAsync(() => #region Reference Validation - [TestMethod] + [SkippableFact] public async Task EmbedAsync_HelloWorldDocument_MatchesPythonReference() { string modelsDir = GetModelDirectory(); @@ -203,19 +200,19 @@ public async Task EmbedAsync_HelloWorldDocument_MatchesPythonReference() for (int i = 0; i < 10; i++) { - Assert.AreEqual(expectedFirst10[i], result[i], tolerance, + TestHelpers.AssertApproximatelyEqual(expectedFirst10[i], result[i], tolerance, $"Mismatch at index {i}: expected {expectedFirst10[i]:F6}, got {result[i]:F6}"); } for (int i = 0; i < 5; i++) { int idx = 768 - 5 + i; - Assert.AreEqual(expectedLast5[i], result[idx], tolerance, + TestHelpers.AssertApproximatelyEqual(expectedLast5[i], result[idx], tolerance, $"Mismatch at index {idx}: expected {expectedLast5[i]:F6}, got {result[idx]:F6}"); } } - [TestMethod] + [SkippableFact] public async Task EmbedAsync_HelloWorldQuery_MatchesPythonReference() { string modelsDir = GetModelDirectory(); @@ -240,19 +237,19 @@ public async Task EmbedAsync_HelloWorldQuery_MatchesPythonReference() for (int i = 0; i < 10; i++) { - Assert.AreEqual(expectedFirst10[i], result[i], tolerance, + TestHelpers.AssertApproximatelyEqual(expectedFirst10[i], result[i], tolerance, $"Mismatch at index {i}: expected {expectedFirst10[i]:F6}, got {result[i]:F6}"); } for (int i = 0; i < 5; i++) { int idx = 768 - 5 + i; - Assert.AreEqual(expectedLast5[i], result[idx], tolerance, + TestHelpers.AssertApproximatelyEqual(expectedLast5[i], result[idx], tolerance, $"Mismatch at index {idx}: expected {expectedLast5[i]:F6}, got {result[idx]:F6}"); } } - [TestMethod] + [SkippableFact] public async Task EmbedAsync_FoxSentence_MatchesPythonReference() { string modelsDir = GetModelDirectory(); @@ -277,14 +274,14 @@ public async Task EmbedAsync_FoxSentence_MatchesPythonReference() for (int i = 0; i < 10; i++) { - Assert.AreEqual(expectedFirst10[i], result[i], tolerance, + TestHelpers.AssertApproximatelyEqual(expectedFirst10[i], result[i], tolerance, $"Mismatch at index {i}: expected {expectedFirst10[i]:F6}, got {result[i]:F6}"); } for (int i = 0; i < 5; i++) { int idx = 768 - 5 + i; - Assert.AreEqual(expectedLast5[i], result[idx], tolerance, + TestHelpers.AssertApproximatelyEqual(expectedLast5[i], result[idx], tolerance, $"Mismatch at index {idx}: expected {expectedLast5[i]:F6}, got {result[idx]:F6}"); } } diff --git a/VectorSharp.Embedding.NomicEmbed.Tests/TestHelpers.cs b/VectorSharp.Embedding.NomicEmbed.Tests/TestHelpers.cs new file mode 100644 index 0000000..63d7b47 --- /dev/null +++ b/VectorSharp.Embedding.NomicEmbed.Tests/TestHelpers.cs @@ -0,0 +1,15 @@ +namespace VectorSharp.Embedding.NomicEmbed.Tests +{ + internal static class TestHelpers + { + /// + /// Asserts two floats are equal within an absolute tolerance. + /// + internal static void AssertApproximatelyEqual(float expected, float actual, float tolerance, string? message = null) + { + float diff = MathF.Abs(expected - actual); + Assert.True(diff <= tolerance, + message ?? $"Expected {expected} but got {actual} (diff {diff} exceeds tolerance {tolerance})"); + } + } +} diff --git a/VectorSharp.Embedding.NomicEmbed.Tests/TokenizerDebugTests.cs b/VectorSharp.Embedding.NomicEmbed.Tests/TokenizerDebugTests.cs index ed75dcc..cd13752 100644 --- a/VectorSharp.Embedding.NomicEmbed.Tests/TokenizerDebugTests.cs +++ b/VectorSharp.Embedding.NomicEmbed.Tests/TokenizerDebugTests.cs @@ -1,17 +1,17 @@ namespace VectorSharp.Embedding.NomicEmbed.Tests { - [TestClass] - [TestCategory("RequiresModel")] - [DoNotParallelize] + [Trait("Category", "RequiresModel")] public class TokenizerDebugTests { - [TestMethod] + [SkippableFact] public void Tokenizer_HelloWorld_MatchesPythonTokenIds() { string tokenizerPath = Path.Combine( Path.GetDirectoryName(typeof(TokenizerDebugTests).Assembly.Location)!, "Models", "tokenizer.json"); + Skip.IfNot(File.Exists(tokenizerPath), "Tokenizer file not found. Run tools/download-nomic-model.sh first."); + FastBertTokenizer.BertTokenizer tokenizer = new FastBertTokenizer.BertTokenizer(); using (Stream stream = File.OpenRead(tokenizerPath)) { @@ -30,10 +30,10 @@ public void Tokenizer_HelloWorld_MatchesPythonTokenIds() // Python produces: [101, 3945, 1035, 6254, 1024, 7592, 2088, 102] long[] expected = new long[] { 101, 3945, 1035, 6254, 1024, 7592, 2088, 102 }; - Assert.AreEqual(expected.Length, ids.Length, $"Token count mismatch. Got IDs: [{string.Join(", ", ids)}]"); + Assert.Equal(expected.Length, ids.Length); for (int i = 0; i < expected.Length; i++) { - Assert.AreEqual(expected[i], ids[i], $"Token mismatch at position {i}"); + Assert.Equal(expected[i], ids[i]); } } } diff --git a/VectorSharp.Embedding.NomicEmbed.Tests/VectorSharp.Embedding.NomicEmbed.Tests.csproj b/VectorSharp.Embedding.NomicEmbed.Tests/VectorSharp.Embedding.NomicEmbed.Tests.csproj index 63041b2..2b8277a 100644 --- a/VectorSharp.Embedding.NomicEmbed.Tests/VectorSharp.Embedding.NomicEmbed.Tests.csproj +++ b/VectorSharp.Embedding.NomicEmbed.Tests/VectorSharp.Embedding.NomicEmbed.Tests.csproj @@ -10,11 +10,14 @@ - + + + + - + diff --git a/VectorSharp.Embedding.Tests/EmbeddingServiceOptionsTests.cs b/VectorSharp.Embedding.Tests/EmbeddingServiceOptionsTests.cs index 27c41e9..5fa4603 100644 --- a/VectorSharp.Embedding.Tests/EmbeddingServiceOptionsTests.cs +++ b/VectorSharp.Embedding.Tests/EmbeddingServiceOptionsTests.cs @@ -1,38 +1,37 @@ namespace VectorSharp.Embedding.Tests { - [TestClass] public class EmbeddingServiceOptionsTests { - [TestMethod] + [Fact] public void DefaultConcurrency_IsOne() { EmbeddingServiceOptions options = new EmbeddingServiceOptions(); - Assert.AreEqual(1, options.Concurrency); + Assert.Equal(1, options.Concurrency); } - [TestMethod] + [Fact] public void DefaultChannelCapacity_IsOneThousand() { EmbeddingServiceOptions options = new EmbeddingServiceOptions(); - Assert.AreEqual(1000, options.ChannelCapacity); + Assert.Equal(1000, options.ChannelCapacity); } - [TestMethod] + [Fact] public void Concurrency_CustomValue_IsRetained() { EmbeddingServiceOptions options = new EmbeddingServiceOptions { Concurrency = 4 }; - Assert.AreEqual(4, options.Concurrency); + Assert.Equal(4, options.Concurrency); } - [TestMethod] + [Fact] public void ChannelCapacity_CustomValue_IsRetained() { EmbeddingServiceOptions options = new EmbeddingServiceOptions { ChannelCapacity = 500 }; - Assert.AreEqual(500, options.ChannelCapacity); + Assert.Equal(500, options.ChannelCapacity); } } } diff --git a/VectorSharp.Embedding.Tests/EmbeddingServiceTests.cs b/VectorSharp.Embedding.Tests/EmbeddingServiceTests.cs index c826fee..08db5c1 100644 --- a/VectorSharp.Embedding.Tests/EmbeddingServiceTests.cs +++ b/VectorSharp.Embedding.Tests/EmbeddingServiceTests.cs @@ -1,45 +1,44 @@ namespace VectorSharp.Embedding.Tests { - [TestClass] public class EmbeddingServiceTests { #region Constructor - [TestMethod] + [Fact] public async Task Constructor_ValidFactory_Succeeds() { await using EmbeddingService service = new EmbeddingService(() => new TestEmbeddingProvider()); - Assert.AreEqual(768, service.Dimension); + Assert.Equal(768, service.Dimension); } - [TestMethod] + [Fact] public void Constructor_NullFactory_Throws() { - Assert.ThrowsExactly(() => + Assert.Throws(() => new EmbeddingService(null!)); } - [TestMethod] + [Fact] public void Constructor_ZeroConcurrency_Throws() { - Assert.ThrowsExactly(() => + Assert.Throws(() => new EmbeddingService(() => new TestEmbeddingProvider(), new EmbeddingServiceOptions { Concurrency = 0 })); } - [TestMethod] + [Fact] public void Constructor_NegativeConcurrency_Throws() { - Assert.ThrowsExactly(() => + Assert.Throws(() => new EmbeddingService(() => new TestEmbeddingProvider(), new EmbeddingServiceOptions { Concurrency = -1 })); } - [TestMethod] + [Fact] public void Constructor_ZeroChannelCapacity_Throws() { - Assert.ThrowsExactly(() => + Assert.Throws(() => new EmbeddingService(() => new TestEmbeddingProvider(), new EmbeddingServiceOptions { ChannelCapacity = 0 })); } @@ -48,29 +47,29 @@ public void Constructor_ZeroChannelCapacity_Throws() #region Dimension - [TestMethod] + [Fact] public async Task Dimension_ReturnsProviderDimension() { await using EmbeddingService service = new EmbeddingService(() => new TestEmbeddingProvider(384)); - Assert.AreEqual(384, service.Dimension); + Assert.Equal(384, service.Dimension); } #endregion #region EmbedAsync - [TestMethod] + [Fact] public async Task EmbedAsync_ValidText_ReturnsCorrectDimension() { await using EmbeddingService service = new EmbeddingService(() => new TestEmbeddingProvider(128)); float[] result = await service.EmbedAsync("hello world"); - Assert.AreEqual(128, result.Length); + Assert.Equal(128, result.Length); } - [TestMethod] + [Fact] public async Task EmbedAsync_SameText_ReturnsSameResult() { await using EmbeddingService service = new EmbeddingService(() => new TestEmbeddingProvider()); @@ -78,10 +77,10 @@ public async Task EmbedAsync_SameText_ReturnsSameResult() float[] result1 = await service.EmbedAsync("hello"); float[] result2 = await service.EmbedAsync("hello"); - CollectionAssert.AreEqual(result1, result2); + Assert.Equal(result1, result2); } - [TestMethod] + [Fact] public async Task EmbedAsync_DifferentTexts_ReturnsDifferentResults() { await using EmbeddingService service = new EmbeddingService(() => new TestEmbeddingProvider()); @@ -89,34 +88,34 @@ public async Task EmbedAsync_DifferentTexts_ReturnsDifferentResults() float[] result1 = await service.EmbedAsync("hello"); float[] result2 = await service.EmbedAsync("world"); - CollectionAssert.AreNotEqual(result1, result2); + Assert.NotEqual(result1, result2); } - [TestMethod] + [Fact] public async Task EmbedAsync_NullText_Throws() { await using EmbeddingService service = new EmbeddingService(() => new TestEmbeddingProvider()); - await Assert.ThrowsExactlyAsync(() => + await Assert.ThrowsAsync(() => service.EmbedAsync(null!)); } - [TestMethod] + [Fact] public async Task EmbedAsync_AfterDispose_Throws() { EmbeddingService service = new EmbeddingService(() => new TestEmbeddingProvider()); await service.DisposeAsync(); - await Assert.ThrowsExactlyAsync(() => + await Assert.ThrowsAsync(() => service.EmbedAsync("hello")); } - [TestMethod] + [Fact] public async Task EmbedAsync_ProviderThrows_PropagatesException() { await using EmbeddingService service = new EmbeddingService(() => new FailingProvider()); - await Assert.ThrowsExactlyAsync(() => + await Assert.ThrowsAsync(() => service.EmbedAsync("hello")); } @@ -124,40 +123,40 @@ await Assert.ThrowsExactlyAsync(() => #region EmbedBatchAsync - [TestMethod] + [Fact] public async Task EmbedBatchAsync_MultipleTexts_ReturnsAllResults() { await using EmbeddingService service = new EmbeddingService(() => new TestEmbeddingProvider(128)); float[][] results = await service.EmbedBatchAsync(new[] { "a", "b", "c" }); - Assert.AreEqual(3, results.Length); + Assert.Equal(3, results.Length); foreach (float[] result in results) { - Assert.AreEqual(128, result.Length); + Assert.Equal(128, result.Length); } } - [TestMethod] + [Fact] public async Task EmbedBatchAsync_EmptyList_ReturnsEmptyArray() { await using EmbeddingService service = new EmbeddingService(() => new TestEmbeddingProvider()); float[][] results = await service.EmbedBatchAsync(Array.Empty()); - Assert.AreEqual(0, results.Length); + Assert.Empty(results); } - [TestMethod] + [Fact] public async Task EmbedBatchAsync_NullList_Throws() { await using EmbeddingService service = new EmbeddingService(() => new TestEmbeddingProvider()); - await Assert.ThrowsExactlyAsync(() => + await Assert.ThrowsAsync(() => service.EmbedBatchAsync(null!)); } - [TestMethod] + [Fact] public async Task EmbedBatchAsync_ResultCountMatchesInput() { await using EmbeddingService service = new EmbeddingService(() => new TestEmbeddingProvider()); @@ -170,14 +169,14 @@ public async Task EmbedBatchAsync_ResultCountMatchesInput() float[][] results = await service.EmbedBatchAsync(texts); - Assert.AreEqual(20, results.Length); + Assert.Equal(20, results.Length); } #endregion #region Concurrency - [TestMethod] + [Fact] public async Task EmbedAsync_MultipleConcurrentRequests_AllComplete() { await using EmbeddingService service = new EmbeddingService( @@ -192,14 +191,14 @@ public async Task EmbedAsync_MultipleConcurrentRequests_AllComplete() float[][] results = await Task.WhenAll(tasks); - Assert.AreEqual(20, results.Length); + Assert.Equal(20, results.Length); foreach (float[] result in results) { - Assert.AreEqual(128, result.Length); + Assert.Equal(128, result.Length); } } - [TestMethod] + [Fact] public async Task EmbedAsync_WithConcurrency2_UsesMultipleWorkers() { // Verify that both workers process requests by checking total call count @@ -218,14 +217,14 @@ public async Task EmbedAsync_WithConcurrency2_UsesMultipleWorkers() await Task.WhenAll(tasks); - Assert.AreEqual(20, counter.Value); + Assert.Equal(20, counter.Value); } #endregion #region Disposal - [TestMethod] + [Fact] public async Task DisposeAsync_Idempotent_SecondCallNoOp() { EmbeddingService service = new EmbeddingService(() => new TestEmbeddingProvider()); diff --git a/VectorSharp.Embedding.Tests/MSTestSettings.cs b/VectorSharp.Embedding.Tests/MSTestSettings.cs deleted file mode 100644 index aaf278c..0000000 --- a/VectorSharp.Embedding.Tests/MSTestSettings.cs +++ /dev/null @@ -1 +0,0 @@ -[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)] diff --git a/VectorSharp.Embedding.Tests/VectorSharp.Embedding.Tests.csproj b/VectorSharp.Embedding.Tests/VectorSharp.Embedding.Tests.csproj index 0847009..2ff6fb6 100644 --- a/VectorSharp.Embedding.Tests/VectorSharp.Embedding.Tests.csproj +++ b/VectorSharp.Embedding.Tests/VectorSharp.Embedding.Tests.csproj @@ -10,11 +10,13 @@ - + + + - + diff --git a/VectorSharp.Storage.Tests/BinaryFormatTests.cs b/VectorSharp.Storage.Tests/BinaryFormatTests.cs index bc442b9..022e393 100644 --- a/VectorSharp.Storage.Tests/BinaryFormatTests.cs +++ b/VectorSharp.Storage.Tests/BinaryFormatTests.cs @@ -2,10 +2,9 @@ namespace VectorSharp.Storage.Tests { - [TestClass] public class BinaryFormatTests { - [TestMethod] + [Fact] public void WriteHeader_ReadHeader_RoundTrip() { using MemoryStream stream = new MemoryStream(); @@ -15,12 +14,12 @@ public void WriteHeader_ReadHeader_RoundTrip() stream.Position = 0; (int dimension, int keySize, int recordCount) = BinaryFormat.ReadHeader(stream); - Assert.AreEqual(768, dimension); - Assert.AreEqual(16, keySize); - Assert.AreEqual(100, recordCount); + Assert.Equal(768, dimension); + Assert.Equal(16, keySize); + Assert.Equal(100, recordCount); } - [TestMethod] + [Fact] public void WriteRecord_ReadRecord_RoundTrip_Guid() { using MemoryStream stream = new MemoryStream(); @@ -33,14 +32,14 @@ public void WriteRecord_ReadRecord_RoundTrip_Guid() stream.Position = 0; (byte readStatus, Guid readKey, float readMagnitude, float[] readValues) = BinaryFormat.ReadRecord(stream, 3); - Assert.AreEqual(BinaryFormat.RecordStatusActive, readStatus); + Assert.Equal(BinaryFormat.RecordStatusActive, readStatus); - Assert.AreEqual(key, readKey); - Assert.AreEqual(magnitude, readMagnitude, 0.0001f); - CollectionAssert.AreEqual(values, readValues); + Assert.Equal(key, readKey); + TestHelpers.AssertApproximatelyEqual(magnitude, readMagnitude, 0.0001f); + Assert.Equal(values, readValues); } - [TestMethod] + [Fact] public void WriteRecord_ReadRecord_RoundTrip_Int() { using MemoryStream stream = new MemoryStream(); @@ -53,14 +52,14 @@ public void WriteRecord_ReadRecord_RoundTrip_Int() stream.Position = 0; (byte readStatus, int readKey, float readMagnitude, float[] readValues) = BinaryFormat.ReadRecord(stream, 2); - Assert.AreEqual(BinaryFormat.RecordStatusActive, readStatus); + Assert.Equal(BinaryFormat.RecordStatusActive, readStatus); - Assert.AreEqual(key, readKey); - Assert.AreEqual(magnitude, readMagnitude, 0.0001f); - CollectionAssert.AreEqual(values, readValues); + Assert.Equal(key, readKey); + TestHelpers.AssertApproximatelyEqual(magnitude, readMagnitude, 0.0001f); + Assert.Equal(values, readValues); } - [TestMethod] + [Fact] public void WriteRecord_ReadRecord_RoundTrip_Long() { using MemoryStream stream = new MemoryStream(); @@ -73,49 +72,49 @@ public void WriteRecord_ReadRecord_RoundTrip_Long() stream.Position = 0; (byte readStatus, long readKey, float readMagnitude, float[] readValues) = BinaryFormat.ReadRecord(stream, 4); - Assert.AreEqual(BinaryFormat.RecordStatusActive, readStatus); + Assert.Equal(BinaryFormat.RecordStatusActive, readStatus); - Assert.AreEqual(key, readKey); - Assert.AreEqual(magnitude, readMagnitude, 0.0001f); - CollectionAssert.AreEqual(values, readValues); + Assert.Equal(key, readKey); + TestHelpers.AssertApproximatelyEqual(magnitude, readMagnitude, 0.0001f); + Assert.Equal(values, readValues); } - [TestMethod] + [Fact] public void HeaderSize_IsCorrect() { - Assert.AreEqual(20, BinaryFormat.HeaderSize); + Assert.Equal(20, BinaryFormat.HeaderSize); } - [TestMethod] + [Fact] public void RecordSize_CalculationIsCorrect() { int guidKeySize = Unsafe.SizeOf(); // 16 int intKeySize = Unsafe.SizeOf(); // 4 // Guid key, 768 dimensions: 1 + 16 + 4 + 768*4 = 3093 - Assert.AreEqual(1 + 16 + 4 + 768 * 4, BinaryFormat.CalculateRecordSize(guidKeySize, 768)); + Assert.Equal(1 + 16 + 4 + 768 * 4, BinaryFormat.CalculateRecordSize(guidKeySize, 768)); // Int key, 3 dimensions: 1 + 4 + 4 + 3*4 = 21 - Assert.AreEqual(1 + 4 + 4 + 3 * 4, BinaryFormat.CalculateRecordSize(intKeySize, 3)); + Assert.Equal(1 + 4 + 4 + 3 * 4, BinaryFormat.CalculateRecordSize(intKeySize, 3)); } - [TestMethod] + [Fact] public void ReadHeader_InvalidMagicNumber_Throws() { using MemoryStream stream = new MemoryStream(new byte[20]); - Assert.ThrowsExactly(() => BinaryFormat.ReadHeader(stream)); + Assert.Throws(() => BinaryFormat.ReadHeader(stream)); } - [TestMethod] + [Fact] public void ReadHeader_TooShort_Throws() { using MemoryStream stream = new MemoryStream(new byte[5]); - Assert.ThrowsExactly(() => BinaryFormat.ReadHeader(stream)); + Assert.Throws(() => BinaryFormat.ReadHeader(stream)); } - [TestMethod] + [Fact] public void ReadHeader_UnsupportedVersion_Throws() { using MemoryStream stream = new MemoryStream(); @@ -130,10 +129,10 @@ public void ReadHeader_UnsupportedVersion_Throws() stream.Write(header); stream.Position = 0; - Assert.ThrowsExactly(() => BinaryFormat.ReadHeader(stream)); + Assert.Throws(() => BinaryFormat.ReadHeader(stream)); } - [TestMethod] + [Fact] public void ReadHeader_NegativeDimension_Throws() { using MemoryStream stream = new MemoryStream(); @@ -148,10 +147,10 @@ public void ReadHeader_NegativeDimension_Throws() stream.Write(header); stream.Position = 0; - Assert.ThrowsExactly(() => BinaryFormat.ReadHeader(stream)); + Assert.Throws(() => BinaryFormat.ReadHeader(stream)); } - [TestMethod] + [Fact] public void ReadHeader_NegativeRecordCount_Throws() { using MemoryStream stream = new MemoryStream(); @@ -166,7 +165,7 @@ public void ReadHeader_NegativeRecordCount_Throws() stream.Write(header); stream.Position = 0; - Assert.ThrowsExactly(() => BinaryFormat.ReadHeader(stream)); + Assert.Throws(() => BinaryFormat.ReadHeader(stream)); } } } diff --git a/VectorSharp.Storage.Tests/CosineSimilarityTests.cs b/VectorSharp.Storage.Tests/CosineSimilarityTests.cs index 02a61ce..7280553 100644 --- a/VectorSharp.Storage.Tests/CosineSimilarityTests.cs +++ b/VectorSharp.Storage.Tests/CosineSimilarityTests.cs @@ -1,9 +1,8 @@ namespace VectorSharp.Storage.Tests { - [TestClass] public class CosineSimilarityTests { - [TestMethod] + [Fact] public void CalculateMagnitude_UnitVector_ReturnsOne() { // A unit vector along the first axis @@ -12,20 +11,20 @@ public void CalculateMagnitude_UnitVector_ReturnsOne() float magnitude = CosineSimilarity.CalculateMagnitude(vector.AsSpan()); - Assert.AreEqual(1.0f, magnitude, 0.0001f); + TestHelpers.AssertApproximatelyEqual(1.0f, magnitude, 0.0001f); } - [TestMethod] + [Fact] public void CalculateMagnitude_ZeroVector_ReturnsZero() { float[] vector = new float[10]; float magnitude = CosineSimilarity.CalculateMagnitude(vector.AsSpan()); - Assert.AreEqual(0.0f, magnitude); + Assert.Equal(0.0f, magnitude); } - [TestMethod] + [Fact] public void CalculateMagnitude_KnownVector_ReturnsExpected() { // [3, 4] -> magnitude = 5 @@ -33,10 +32,10 @@ public void CalculateMagnitude_KnownVector_ReturnsExpected() float magnitude = CosineSimilarity.CalculateMagnitude(vector.AsSpan()); - Assert.AreEqual(5.0f, magnitude, 0.0001f); + TestHelpers.AssertApproximatelyEqual(5.0f, magnitude, 0.0001f); } - [TestMethod] + [Fact] public void Calculate_IdenticalVectors_ReturnsOne() { float[] vector = TestHelpers.CreateRandomVector(128, seed: 42); @@ -44,10 +43,10 @@ public void Calculate_IdenticalVectors_ReturnsOne() float similarity = CosineSimilarity.Calculate(vector.AsSpan(), magnitude, vector.AsSpan(), magnitude); - Assert.AreEqual(1.0f, similarity, 0.0001f); + TestHelpers.AssertApproximatelyEqual(1.0f, similarity, 0.0001f); } - [TestMethod] + [Fact] public void Calculate_OrthogonalVectors_ReturnsZero() { float[] a = new float[] { 1.0f, 0.0f, 0.0f }; @@ -57,10 +56,10 @@ public void Calculate_OrthogonalVectors_ReturnsZero() float similarity = CosineSimilarity.Calculate(a.AsSpan(), magA, b.AsSpan(), magB); - Assert.AreEqual(0.0f, similarity, 0.0001f); + TestHelpers.AssertApproximatelyEqual(0.0f, similarity, 0.0001f); } - [TestMethod] + [Fact] public void Calculate_OppositeVectors_ReturnsNegativeOne() { float[] a = new float[] { 1.0f, 2.0f, 3.0f }; @@ -70,10 +69,10 @@ public void Calculate_OppositeVectors_ReturnsNegativeOne() float similarity = CosineSimilarity.Calculate(a.AsSpan(), magA, b.AsSpan(), magB); - Assert.AreEqual(-1.0f, similarity, 0.0001f); + TestHelpers.AssertApproximatelyEqual(-1.0f, similarity, 0.0001f); } - [TestMethod] + [Fact] public void Calculate_ZeroMagnitude_ReturnsZero() { float[] a = new float[] { 1.0f, 2.0f, 3.0f }; @@ -81,10 +80,10 @@ public void Calculate_ZeroMagnitude_ReturnsZero() float similarity = CosineSimilarity.Calculate(a.AsSpan(), 3.74f, b.AsSpan(), 0.0f); - Assert.AreEqual(0.0f, similarity); + Assert.Equal(0.0f, similarity); } - [TestMethod] + [Fact] public void Calculate_768Dimensions_ReturnsCorrectResult() { float[] a = TestHelpers.CreateRandomVector(768, seed: 1); @@ -95,11 +94,11 @@ public void Calculate_768Dimensions_ReturnsCorrectResult() float similarity = CosineSimilarity.Calculate(a.AsSpan(), magA, b.AsSpan(), magB); // Should be a valid cosine similarity in [-1, 1] - Assert.IsTrue(similarity >= -1.0f && similarity <= 1.0f, + Assert.True(similarity >= -1.0f && similarity <= 1.0f, $"Expected similarity in [-1, 1], got {similarity}"); } - [TestMethod] + [Fact] public void Calculate_NonSIMDAlignedDimension_WorksCorrectly() { // Use a dimension that's not aligned to SIMD width @@ -110,12 +109,12 @@ public void Calculate_NonSIMDAlignedDimension_WorksCorrectly() float similarity = CosineSimilarity.Calculate(a.AsSpan(), magA, b.AsSpan(), magB); - Assert.IsTrue(similarity >= -1.0f && similarity <= 1.0f, + Assert.True(similarity >= -1.0f && similarity <= 1.0f, $"Expected similarity in [-1, 1], got {similarity}"); // Also verify identical vectors still return 1.0 float selfSimilarity = CosineSimilarity.Calculate(a.AsSpan(), magA, a.AsSpan(), magA); - Assert.AreEqual(1.0f, selfSimilarity, 0.0001f); + TestHelpers.AssertApproximatelyEqual(1.0f, selfSimilarity, 0.0001f); } } } diff --git a/VectorSharp.Storage.Tests/CosineVectorStorePerformanceTests.cs b/VectorSharp.Storage.Tests/CosineVectorStorePerformanceTests.cs index 029e8a1..3787b2a 100644 --- a/VectorSharp.Storage.Tests/CosineVectorStorePerformanceTests.cs +++ b/VectorSharp.Storage.Tests/CosineVectorStorePerformanceTests.cs @@ -2,7 +2,6 @@ namespace VectorSharp.Storage.Tests { - [TestClass] public class CosineVectorStorePerformanceTests { #region Constants @@ -48,7 +47,7 @@ private static List CreateSearchQueries(int count) #endregion - [TestMethod] + [Fact] public async Task PerformanceTest_LargeDatasetOperations() { Console.WriteLine($"=== Performance Test with {LargeDatasetSize:N0} vectors ==="); @@ -136,7 +135,7 @@ public async Task PerformanceTest_LargeDatasetOperations() foreach (float[] query in searchQueries) { IReadOnlyList> results = await store.FindMostSimilarAsync(query, 10); - Assert.IsTrue(results.Count > 0); + Assert.True(results.Count > 0); } searchTimer.Stop(); @@ -153,7 +152,7 @@ public async Task PerformanceTest_LargeDatasetOperations() foreach (float[] query in searchQueries) { IReadOnlyList> results = await loadedStore.FindMostSimilarAsync(query, 10); - Assert.IsTrue(results.Count > 0); + Assert.True(results.Count > 0); } loadedSearchTimer.Stop(); @@ -175,14 +174,14 @@ public async Task PerformanceTest_LargeDatasetOperations() Console.WriteLine("\n=== Performance Test Complete ==="); - Assert.AreEqual(LargeDatasetSize, store.Count); - Assert.IsTrue(insertionTimeMs > 0); - Assert.IsTrue(saveTimeMs > 0); - Assert.IsTrue(loadTimeMs > 0); - Assert.IsTrue(searchTimeMs > 0); + Assert.Equal(LargeDatasetSize, store.Count); + Assert.True(insertionTimeMs > 0); + Assert.True(saveTimeMs > 0); + Assert.True(loadTimeMs > 0); + Assert.True(searchTimeMs > 0); } - [TestMethod] + [Fact] public async Task PerformanceTest_MemoryUsage() { Console.WriteLine($"=== Memory Usage Test with {LargeDatasetSize:N0} vectors ==="); @@ -207,7 +206,7 @@ public async Task PerformanceTest_MemoryUsage() foreach (float[] query in searchQueries) { IReadOnlyList> results = await store.FindMostSimilarAsync(query, 10); - Assert.IsTrue(results.Count > 0); + Assert.True(results.Count > 0); } long afterSearchMemory = GC.GetTotalMemory(true); @@ -228,10 +227,10 @@ public async Task PerformanceTest_MemoryUsage() Console.WriteLine("=== Memory Usage Test Complete ==="); - Assert.IsTrue(populatedMemory > initialMemory); + Assert.True(populatedMemory > initialMemory); } - [TestMethod] + [Fact] public async Task PerformanceTest_ConcurrentOperations() { Console.WriteLine($"=== Concurrent Operations Test with {LargeDatasetSize:N0} vectors ==="); @@ -277,7 +276,7 @@ public async Task PerformanceTest_ConcurrentOperations() concurrentSearchTasks.Add(Task.Run(async () => { IReadOnlyList> results = await concurrentStore.FindMostSimilarAsync(query, 10); - Assert.IsTrue(results.Count > 0); + Assert.True(results.Count > 0); })); } @@ -306,7 +305,7 @@ public async Task PerformanceTest_ConcurrentOperations() foreach (float[] query in searchQueries) { IReadOnlyList> results = await sequentialStore.FindMostSimilarAsync(query, 10); - Assert.IsTrue(results.Count > 0); + Assert.True(results.Count > 0); } sequentialSearchTimer.Stop(); @@ -325,13 +324,13 @@ public async Task PerformanceTest_ConcurrentOperations() Console.WriteLine("\n=== Concurrent Operations Test Complete ==="); - Assert.IsTrue(concurrentInsertionTimeMs > 0); - Assert.IsTrue(concurrentSearchTimeMs > 0); - Assert.IsTrue(sequentialInsertionTimeMs > 0); - Assert.IsTrue(sequentialSearchTimeMs > 0); + Assert.True(concurrentInsertionTimeMs > 0); + Assert.True(concurrentSearchTimeMs > 0); + Assert.True(sequentialInsertionTimeMs > 0); + Assert.True(sequentialSearchTimeMs > 0); } - [TestMethod] + [Fact] public async Task PerformanceTest_DiskVsInMemory() { Console.WriteLine($"=== Disk vs In-Memory Performance Test ==="); @@ -359,7 +358,7 @@ public async Task PerformanceTest_DiskVsInMemory() // Open disk store using DiskVectorStore diskStore = new DiskVectorStore("disk", filePath, VectorDimension); - Assert.AreEqual(LargeDatasetSize, diskStore.Count); + Assert.Equal(LargeDatasetSize, diskStore.Count); // In-memory search Console.WriteLine("--- In-Memory Search ---"); @@ -367,7 +366,7 @@ public async Task PerformanceTest_DiskVsInMemory() foreach (float[] query in searchQueries) { IReadOnlyList> results = await memStore.FindMostSimilarAsync(query, 10); - Assert.IsTrue(results.Count > 0); + Assert.True(results.Count > 0); } memTimer.Stop(); @@ -381,7 +380,7 @@ public async Task PerformanceTest_DiskVsInMemory() foreach (float[] query in searchQueries) { IReadOnlyList> results = await diskStore.FindMostSimilarAsync(query, 10); - Assert.IsTrue(results.Count > 0); + Assert.True(results.Count > 0); } diskTimer.Stop(); @@ -400,8 +399,8 @@ public async Task PerformanceTest_DiskVsInMemory() float[] verifyQuery = searchQueries[0]; IReadOnlyList> memResults = await memStore.FindMostSimilarAsync(verifyQuery, 1); IReadOnlyList> diskResults = await diskStore.FindMostSimilarAsync(verifyQuery, 1); - Assert.AreEqual(memResults[0].Id, diskResults[0].Id); - Assert.AreEqual(memResults[0].Score, diskResults[0].Score, 0.0001f); + Assert.Equal(memResults[0].Id, diskResults[0].Id); + TestHelpers.AssertApproximatelyEqual(memResults[0].Score, diskResults[0].Score, 0.0001f); Console.WriteLine("\n=== Disk vs In-Memory Test Complete ==="); } diff --git a/VectorSharp.Storage.Tests/CosineVectorStorePersistenceTests.cs b/VectorSharp.Storage.Tests/CosineVectorStorePersistenceTests.cs index 7291ad3..5e9a5c4 100644 --- a/VectorSharp.Storage.Tests/CosineVectorStorePersistenceTests.cs +++ b/VectorSharp.Storage.Tests/CosineVectorStorePersistenceTests.cs @@ -1,12 +1,11 @@ namespace VectorSharp.Storage.Tests { - [TestClass] public class CosineVectorStorePersistenceTests { private const int DefaultDimension = 128; private const string DefaultName = "test-store"; - [TestMethod] + [Fact] public async Task SaveAndLoad_RoundTrip_PreservesVectors() { using CosineVectorStore store = new CosineVectorStore(DefaultName, DefaultDimension); @@ -25,16 +24,16 @@ public async Task SaveAndLoad_RoundTrip_PreservesVectors() using CosineVectorStore loadedStore = new CosineVectorStore(DefaultName, DefaultDimension); await loadedStore.LoadAsync(stream); - Assert.AreEqual(2, loadedStore.Count); + Assert.Equal(2, loadedStore.Count); // Verify search still works IReadOnlyList> results = await loadedStore.FindMostSimilarAsync(values1, 1); - Assert.AreEqual(1, results.Count); - Assert.AreEqual(id1, results[0].Id); - Assert.AreEqual(1.0f, results[0].Score, 0.0001f); + Assert.Single(results); + Assert.Equal(id1, results[0].Id); + TestHelpers.AssertApproximatelyEqual(1.0f, results[0].Score, 0.0001f); } - [TestMethod] + [Fact] public async Task SaveAndLoad_SameQueryResults() { using CosineVectorStore store = await TestHelpers.CreatePopulatedStoreAsync(DefaultName, DefaultDimension, 50); @@ -51,15 +50,15 @@ public async Task SaveAndLoad_SameQueryResults() IReadOnlyList> loadedResults = await loadedStore.FindMostSimilarAsync(query, 5); - Assert.AreEqual(originalResults.Count, loadedResults.Count); + Assert.Equal(originalResults.Count, loadedResults.Count); for (int i = 0; i < originalResults.Count; i++) { - Assert.AreEqual(originalResults[i].Id, loadedResults[i].Id); - Assert.AreEqual(originalResults[i].Score, loadedResults[i].Score, 0.0001f); + Assert.Equal(originalResults[i].Id, loadedResults[i].Id); + TestHelpers.AssertApproximatelyEqual(originalResults[i].Score, loadedResults[i].Score, 0.0001f); } } - [TestMethod] + [Fact] public async Task Load_ClearsExistingVectors() { using CosineVectorStore store = new CosineVectorStore(DefaultName, DefaultDimension); @@ -71,15 +70,15 @@ public async Task Load_ClearsExistingVectors() // Add more vectors after saving await store.AddAsync(Guid.NewGuid(), TestHelpers.CreateRandomVector(DefaultDimension, seed: 2)); await store.AddAsync(Guid.NewGuid(), TestHelpers.CreateRandomVector(DefaultDimension, seed: 3)); - Assert.AreEqual(3, store.Count); + Assert.Equal(3, store.Count); // Load should replace existing vectors stream.Position = 0; await store.LoadAsync(stream); - Assert.AreEqual(1, store.Count); + Assert.Equal(1, store.Count); } - [TestMethod] + [Fact] public async Task Load_DimensionMismatch_Throws() { using CosineVectorStore store128 = new CosineVectorStore(DefaultName, 128); @@ -91,29 +90,29 @@ public async Task Load_DimensionMismatch_Throws() stream.Position = 0; using CosineVectorStore store256 = new CosineVectorStore(DefaultName, 256); - await Assert.ThrowsExactlyAsync(() => + await Assert.ThrowsAsync(() => store256.LoadAsync(stream)); } - [TestMethod] + [Fact] public async Task SaveAsync_NullStream_Throws() { using CosineVectorStore store = new CosineVectorStore(DefaultName, DefaultDimension); - await Assert.ThrowsExactlyAsync(() => + await Assert.ThrowsAsync(() => store.SaveAsync(null!)); } - [TestMethod] + [Fact] public async Task LoadAsync_NullStream_Throws() { using CosineVectorStore store = new CosineVectorStore(DefaultName, DefaultDimension); - await Assert.ThrowsExactlyAsync(() => + await Assert.ThrowsAsync(() => store.LoadAsync(null!)); } - [TestMethod] + [Fact] public async Task SaveAndLoad_WithIntKeys() { using CosineVectorStore store = new CosineVectorStore(DefaultName, DefaultDimension); @@ -127,12 +126,12 @@ public async Task SaveAndLoad_WithIntKeys() using CosineVectorStore loadedStore = new CosineVectorStore(DefaultName, DefaultDimension); await loadedStore.LoadAsync(stream); - Assert.AreEqual(1, loadedStore.Count); + Assert.Equal(1, loadedStore.Count); IReadOnlyList> results = await loadedStore.FindMostSimilarAsync(values, 1); - Assert.AreEqual(42, results[0].Id); + Assert.Equal(42, results[0].Id); } - [TestMethod] + [Fact] public async Task SaveAndLoad_LargeDataset() { int vectorCount = 500; @@ -146,7 +145,7 @@ public async Task SaveAndLoad_LargeDataset() using CosineVectorStore loadedStore = new CosineVectorStore(DefaultName, DefaultDimension); await loadedStore.LoadAsync(stream); - Assert.AreEqual(vectorCount, loadedStore.Count); + Assert.Equal(vectorCount, loadedStore.Count); } } } diff --git a/VectorSharp.Storage.Tests/CosineVectorStoreTests.cs b/VectorSharp.Storage.Tests/CosineVectorStoreTests.cs index 9359d1b..a44ac39 100644 --- a/VectorSharp.Storage.Tests/CosineVectorStoreTests.cs +++ b/VectorSharp.Storage.Tests/CosineVectorStoreTests.cs @@ -1,6 +1,5 @@ namespace VectorSharp.Storage.Tests { - [TestClass] public class CosineVectorStoreTests { private const int DefaultDimension = 128; @@ -8,63 +7,63 @@ public class CosineVectorStoreTests #region Constructor and Validation - [TestMethod] + [Fact] public void Constructor_PositiveDimension_Succeeds() { using CosineVectorStore store = new CosineVectorStore(DefaultName, 768); - Assert.AreEqual(768, store.Dimension); - Assert.AreEqual(DefaultName, store.Name); - Assert.AreEqual(0, store.Count); + Assert.Equal(768, store.Dimension); + Assert.Equal(DefaultName, store.Name); + Assert.Equal(0, store.Count); } - [TestMethod] + [Fact] public void Constructor_ZeroDimension_Throws() { - Assert.ThrowsExactly(() => + Assert.Throws(() => new CosineVectorStore(DefaultName, 0)); } - [TestMethod] + [Fact] public void Constructor_NegativeDimension_Throws() { - Assert.ThrowsExactly(() => + Assert.Throws(() => new CosineVectorStore(DefaultName, -1)); } - [TestMethod] + [Fact] public void Constructor_NullName_Throws() { - Assert.ThrowsExactly(() => + Assert.Throws(() => new CosineVectorStore(null!, 128)); } - [TestMethod] + [Fact] public async Task AddAsync_DimensionMismatch_Throws() { using CosineVectorStore store = new CosineVectorStore(DefaultName, 128); float[] wrongDimension = new float[64]; - await Assert.ThrowsExactlyAsync(() => + await Assert.ThrowsAsync(() => store.AddAsync(Guid.NewGuid(), wrongDimension)); } - [TestMethod] + [Fact] public async Task AddAsync_NullValues_Throws() { using CosineVectorStore store = new CosineVectorStore(DefaultName, 128); - await Assert.ThrowsExactlyAsync(() => + await Assert.ThrowsAsync(() => store.AddAsync(Guid.NewGuid(), null!)); } - [TestMethod] + [Fact] public async Task FindMostSimilarAsync_DimensionMismatch_Throws() { using CosineVectorStore store = new CosineVectorStore(DefaultName, 128); await store.AddAsync(Guid.NewGuid(), TestHelpers.CreateRandomVector(128)); - await Assert.ThrowsExactlyAsync(() => + await Assert.ThrowsAsync(() => store.FindMostSimilarAsync(new float[64], 1)); } @@ -72,16 +71,16 @@ await Assert.ThrowsExactlyAsync(() => #region Basic Operations - [TestMethod] + [Fact] public async Task AddAsync_IncreasesCount() { using CosineVectorStore store = new CosineVectorStore(DefaultName, DefaultDimension); await store.AddAsync(Guid.NewGuid(), TestHelpers.CreateRandomVector(DefaultDimension)); - Assert.AreEqual(1, store.Count); + Assert.Equal(1, store.Count); } - [TestMethod] + [Fact] public async Task RemoveAsync_ExistingId_ReturnsTrue() { using CosineVectorStore store = new CosineVectorStore(DefaultName, DefaultDimension); @@ -90,11 +89,11 @@ public async Task RemoveAsync_ExistingId_ReturnsTrue() bool removed = await store.RemoveAsync(id); - Assert.IsTrue(removed); - Assert.AreEqual(0, store.Count); + Assert.True(removed); + Assert.Equal(0, store.Count); } - [TestMethod] + [Fact] public async Task RemoveAsync_NonExistentId_ReturnsFalse() { using CosineVectorStore store = new CosineVectorStore(DefaultName, DefaultDimension); @@ -102,11 +101,11 @@ public async Task RemoveAsync_NonExistentId_ReturnsFalse() bool removed = await store.RemoveAsync(Guid.NewGuid()); - Assert.IsFalse(removed); - Assert.AreEqual(1, store.Count); + Assert.False(removed); + Assert.Equal(1, store.Count); } - [TestMethod] + [Fact] public async Task FindMostSimilarAsync_EmptyStore_ReturnsEmpty() { using CosineVectorStore store = new CosineVectorStore(DefaultName, DefaultDimension); @@ -114,10 +113,10 @@ public async Task FindMostSimilarAsync_EmptyStore_ReturnsEmpty() IReadOnlyList> results = await store.FindMostSimilarAsync( TestHelpers.CreateRandomVector(DefaultDimension), 10); - Assert.AreEqual(0, results.Count); + Assert.Empty(results); } - [TestMethod] + [Fact] public async Task FindMostSimilarAsync_ReturnsTopMatch() { using CosineVectorStore store = new CosineVectorStore(DefaultName, DefaultDimension); @@ -129,12 +128,12 @@ public async Task FindMostSimilarAsync_ReturnsTopMatch() IReadOnlyList> results = await store.FindMostSimilarAsync(targetVector, 1); - Assert.AreEqual(1, results.Count); - Assert.AreEqual(targetId, results[0].Id); - Assert.AreEqual(1.0f, results[0].Score, 0.0001f); + Assert.Single(results); + Assert.Equal(targetId, results[0].Id); + TestHelpers.AssertApproximatelyEqual(1.0f, results[0].Score, 0.0001f); } - [TestMethod] + [Fact] public async Task FindMostSimilarAsync_RespectsCount() { using CosineVectorStore store = new CosineVectorStore(DefaultName, DefaultDimension); @@ -146,10 +145,10 @@ public async Task FindMostSimilarAsync_RespectsCount() IReadOnlyList> results = await store.FindMostSimilarAsync( TestHelpers.CreateRandomVector(DefaultDimension, seed: 99), 3); - Assert.AreEqual(3, results.Count); + Assert.Equal(3, results.Count); } - [TestMethod] + [Fact] public async Task FindMostSimilarAsync_RequestingMoreThanStored_ReturnsAll() { using CosineVectorStore store = new CosineVectorStore(DefaultName, DefaultDimension); @@ -159,20 +158,20 @@ public async Task FindMostSimilarAsync_RequestingMoreThanStored_ReturnsAll() IReadOnlyList> results = await store.FindMostSimilarAsync( TestHelpers.CreateRandomVector(DefaultDimension, seed: 99), 100); - Assert.AreEqual(2, results.Count); + Assert.Equal(2, results.Count); } - [TestMethod] + [Fact] public async Task FindMostSimilarAsync_NullQuery_Throws() { using CosineVectorStore store = new CosineVectorStore(DefaultName, DefaultDimension); await store.AddAsync(Guid.NewGuid(), TestHelpers.CreateRandomVector(DefaultDimension)); - await Assert.ThrowsExactlyAsync(() => + await Assert.ThrowsAsync(() => store.FindMostSimilarAsync(null!, 10)); } - [TestMethod] + [Fact] public async Task FindMostSimilarAsync_ZeroCount_ReturnsEmpty() { using CosineVectorStore store = new CosineVectorStore(DefaultName, DefaultDimension); @@ -181,10 +180,10 @@ public async Task FindMostSimilarAsync_ZeroCount_ReturnsEmpty() IReadOnlyList> results = await store.FindMostSimilarAsync( TestHelpers.CreateRandomVector(DefaultDimension), 0); - Assert.AreEqual(0, results.Count); + Assert.Empty(results); } - [TestMethod] + [Fact] public async Task FindMostSimilarAsync_ZeroVector_ReturnsEmpty() { using CosineVectorStore store = new CosineVectorStore(DefaultName, DefaultDimension); @@ -193,14 +192,14 @@ public async Task FindMostSimilarAsync_ZeroVector_ReturnsEmpty() float[] zeroVector = new float[DefaultDimension]; IReadOnlyList> results = await store.FindMostSimilarAsync(zeroVector, 10); - Assert.AreEqual(0, results.Count); + Assert.Empty(results); } #endregion #region Sorted Results - [TestMethod] + [Fact] public async Task FindMostSimilarAsync_ReturnsSortedDescending() { using CosineVectorStore store = new CosineVectorStore(DefaultName, DefaultDimension); @@ -213,15 +212,15 @@ public async Task FindMostSimilarAsync_ReturnsSortedDescending() IReadOnlyList> results = await store.FindMostSimilarAsync(query, 3); - Assert.AreEqual(3, results.Count); + Assert.Equal(3, results.Count); for (int i = 1; i < results.Count; i++) { - Assert.IsTrue(results[i - 1].Score >= results[i].Score, + Assert.True(results[i - 1].Score >= results[i].Score, $"Results not sorted: index {i - 1} has score {results[i - 1].Score}, index {i} has score {results[i].Score}"); } } - [TestMethod] + [Fact] public async Task FindMostSimilarAsync_ResultsContainStoreName() { string storeName = "my-embeddings"; @@ -231,15 +230,15 @@ public async Task FindMostSimilarAsync_ResultsContainStoreName() IReadOnlyList> results = await store.FindMostSimilarAsync( TestHelpers.CreateRandomVector(DefaultDimension, seed: 99), 1); - Assert.AreEqual(1, results.Count); - Assert.AreEqual(storeName, results[0].StoreName); + Assert.Single(results); + Assert.Equal(storeName, results[0].StoreName); } #endregion #region Generic Key Types - [TestMethod] + [Fact] public async Task Store_WithIntKey_WorksCorrectly() { using CosineVectorStore store = new CosineVectorStore(DefaultName, DefaultDimension); @@ -248,11 +247,11 @@ public async Task Store_WithIntKey_WorksCorrectly() await store.AddAsync(42, vector); IReadOnlyList> results = await store.FindMostSimilarAsync(vector, 1); - Assert.AreEqual(1, results.Count); - Assert.AreEqual(42, results[0].Id); + Assert.Single(results); + Assert.Equal(42, results[0].Id); } - [TestMethod] + [Fact] public async Task Store_WithLongKey_WorksCorrectly() { using CosineVectorStore store = new CosineVectorStore(DefaultName, DefaultDimension); @@ -261,11 +260,11 @@ public async Task Store_WithLongKey_WorksCorrectly() await store.AddAsync(123456789L, vector); IReadOnlyList> results = await store.FindMostSimilarAsync(vector, 1); - Assert.AreEqual(1, results.Count); - Assert.AreEqual(123456789L, results[0].Id); + Assert.Single(results); + Assert.Equal(123456789L, results[0].Id); } - [TestMethod] + [Fact] public async Task Store_RemoveWithIntKey_WorksCorrectly() { using CosineVectorStore store = new CosineVectorStore(DefaultName, DefaultDimension); @@ -274,57 +273,57 @@ public async Task Store_RemoveWithIntKey_WorksCorrectly() bool removed = await store.RemoveAsync(1); - Assert.IsTrue(removed); - Assert.AreEqual(1, store.Count); + Assert.True(removed); + Assert.Equal(1, store.Count); } #endregion #region Dimension Variations - [TestMethod] + [Fact] public async Task Store_SmallDimension64_WorksCorrectly() { await VerifyDimensionWorks(64); } - [TestMethod] + [Fact] public async Task Store_MediumDimension256_WorksCorrectly() { await VerifyDimensionWorks(256); } - [TestMethod] + [Fact] public async Task Store_Dimension768_WorksCorrectly() { await VerifyDimensionWorks(768); } - [TestMethod] + [Fact] public async Task Store_Dimension1024_WorksCorrectly() { await VerifyDimensionWorks(1024); } - [TestMethod] + [Fact] public async Task Store_Dimension1536_WorksCorrectly() { await VerifyDimensionWorks(1536); } - [TestMethod] + [Fact] public async Task Store_LargeDimension2048_WorksCorrectly() { await VerifyDimensionWorks(2048); } - [TestMethod] + [Fact] public async Task Store_NonSIMDAlignedDimension_WorksCorrectly() { await VerifyDimensionWorks(13); } - [TestMethod] + [Fact] public async Task Store_NonSIMDAlignedDimension127_WorksCorrectly() { await VerifyDimensionWorks(127); @@ -334,7 +333,7 @@ public async Task Store_NonSIMDAlignedDimension127_WorksCorrectly() #region Thread Safety - [TestMethod] + [Fact] public async Task ConcurrentAddRemove_IsThreadSafe() { using CosineVectorStore store = new CosineVectorStore(DefaultName, DefaultDimension); @@ -349,7 +348,7 @@ public async Task ConcurrentAddRemove_IsThreadSafe() } await Task.WhenAll(addTasks); - Assert.AreEqual(addCount, store.Count); + Assert.Equal(addCount, store.Count); // Remove half concurrently Task[] removeTasks = new Task[addCount / 2]; @@ -359,11 +358,11 @@ public async Task ConcurrentAddRemove_IsThreadSafe() } bool[] removeResults = await Task.WhenAll(removeTasks); - Assert.IsTrue(removeResults.All(r => r)); - Assert.AreEqual(addCount / 2, store.Count); + Assert.True(removeResults.All(r => r)); + Assert.Equal(addCount / 2, store.Count); } - [TestMethod] + [Fact] public async Task ConcurrentSearch_IsThreadSafe() { using CosineVectorStore store = await TestHelpers.CreatePopulatedIntStoreAsync( @@ -383,7 +382,7 @@ public async Task ConcurrentSearch_IsThreadSafe() foreach (IReadOnlyList> results in allResults) { - Assert.AreEqual(5, results.Count); + Assert.Equal(5, results.Count); } } @@ -391,7 +390,7 @@ public async Task ConcurrentSearch_IsThreadSafe() #region Cosine Similarity Correctness - [TestMethod] + [Fact] public async Task FindMostSimilarAsync_IdenticalVector_ReturnsScoreOfOne() { using CosineVectorStore store = new CosineVectorStore(DefaultName, DefaultDimension); @@ -401,12 +400,12 @@ public async Task FindMostSimilarAsync_IdenticalVector_ReturnsScoreOfOne() IReadOnlyList> results = await store.FindMostSimilarAsync(vector, 1); - Assert.AreEqual(1, results.Count); - Assert.AreEqual(id, results[0].Id); - Assert.AreEqual(1.0f, results[0].Score, 0.0001f); + Assert.Single(results); + Assert.Equal(id, results[0].Id); + TestHelpers.AssertApproximatelyEqual(1.0f, results[0].Score, 0.0001f); } - [TestMethod] + [Fact] public async Task FindMostSimilarAsync_OppositeVector_ReturnsNegativeScore() { using CosineVectorStore store = new CosineVectorStore(DefaultName, DefaultDimension); @@ -417,8 +416,8 @@ public async Task FindMostSimilarAsync_OppositeVector_ReturnsNegativeScore() IReadOnlyList> results = await store.FindMostSimilarAsync(vector, 1); - Assert.AreEqual(1, results.Count); - Assert.AreEqual(-1.0f, results[0].Score, 0.0001f); + Assert.Single(results); + TestHelpers.AssertApproximatelyEqual(-1.0f, results[0].Score, 0.0001f); } #endregion @@ -441,9 +440,9 @@ private static async Task VerifyDimensionWorks(int dimension) IReadOnlyList> results = await store.FindMostSimilarAsync(vector, 1); - Assert.AreEqual(1, results.Count); - Assert.AreEqual(id, results[0].Id); - Assert.AreEqual(1.0f, results[0].Score, 0.0001f); + Assert.Single(results); + Assert.Equal(id, results[0].Id); + TestHelpers.AssertApproximatelyEqual(1.0f, results[0].Score, 0.0001f); } #endregion diff --git a/VectorSharp.Storage.Tests/DiskVectorStoreTests.cs b/VectorSharp.Storage.Tests/DiskVectorStoreTests.cs index b8ea402..45c5448 100644 --- a/VectorSharp.Storage.Tests/DiskVectorStoreTests.cs +++ b/VectorSharp.Storage.Tests/DiskVectorStoreTests.cs @@ -1,6 +1,5 @@ namespace VectorSharp.Storage.Tests { - [TestClass] public class DiskVectorStoreTests { private const int DefaultDimension = 128; @@ -16,7 +15,7 @@ private void CleanupFile(string path) #region Constructor - [TestMethod] + [Fact] public void Constructor_CreatesNewFile_WhenNotExists() { string filePath = GetTempFilePath(); @@ -24,10 +23,10 @@ public void Constructor_CreatesNewFile_WhenNotExists() { using DiskVectorStore store = new DiskVectorStore(DefaultName, filePath, DefaultDimension); - Assert.IsTrue(File.Exists(filePath)); - Assert.AreEqual(0, store.Count); - Assert.AreEqual(DefaultDimension, store.Dimension); - Assert.AreEqual(DefaultName, store.Name); + Assert.True(File.Exists(filePath)); + Assert.Equal(0, store.Count); + Assert.Equal(DefaultDimension, store.Dimension); + Assert.Equal(DefaultName, store.Name); } finally { @@ -35,7 +34,7 @@ public void Constructor_CreatesNewFile_WhenNotExists() } } - [TestMethod] + [Fact] public async Task Constructor_OpensExistingFile() { string filePath = GetTempFilePath(); @@ -50,7 +49,7 @@ public async Task Constructor_OpensExistingFile() // Re-open the same file using DiskVectorStore reopened = new DiskVectorStore(DefaultName, filePath, DefaultDimension); - Assert.AreEqual(2, reopened.Count); + Assert.Equal(2, reopened.Count); } finally { @@ -58,7 +57,7 @@ public async Task Constructor_OpensExistingFile() } } - [TestMethod] + [Fact] public async Task Constructor_DimensionMismatch_Throws() { string filePath = GetTempFilePath(); @@ -69,7 +68,7 @@ public async Task Constructor_DimensionMismatch_Throws() await store.AddAsync(Guid.NewGuid(), TestHelpers.CreateRandomVector(128)); } - Assert.ThrowsExactly(() => + Assert.Throws(() => new DiskVectorStore(DefaultName, filePath, 256)); } finally @@ -78,24 +77,24 @@ public async Task Constructor_DimensionMismatch_Throws() } } - [TestMethod] + [Fact] public void Constructor_NullName_Throws() { - Assert.ThrowsExactly(() => + Assert.Throws(() => new DiskVectorStore(null!, "file.dat", 128)); } - [TestMethod] + [Fact] public void Constructor_NullFilePath_Throws() { - Assert.ThrowsExactly(() => + Assert.Throws(() => new DiskVectorStore(DefaultName, null!, 128)); } - [TestMethod] + [Fact] public void Constructor_ZeroDimension_Throws() { - Assert.ThrowsExactly(() => + Assert.Throws(() => new DiskVectorStore(DefaultName, GetTempFilePath(), 0)); } @@ -103,7 +102,7 @@ public void Constructor_ZeroDimension_Throws() #region Add and Remove - [TestMethod] + [Fact] public async Task AddAsync_AppendsRecord() { string filePath = GetTempFilePath(); @@ -112,10 +111,10 @@ public async Task AddAsync_AppendsRecord() using DiskVectorStore store = new DiskVectorStore(DefaultName, filePath, DefaultDimension); await store.AddAsync(Guid.NewGuid(), TestHelpers.CreateRandomVector(DefaultDimension)); - Assert.AreEqual(1, store.Count); + Assert.Equal(1, store.Count); await store.AddAsync(Guid.NewGuid(), TestHelpers.CreateRandomVector(DefaultDimension, seed: 2)); - Assert.AreEqual(2, store.Count); + Assert.Equal(2, store.Count); } finally { @@ -123,7 +122,7 @@ public async Task AddAsync_AppendsRecord() } } - [TestMethod] + [Fact] public async Task AddAsync_DimensionMismatch_Throws() { string filePath = GetTempFilePath(); @@ -131,7 +130,7 @@ public async Task AddAsync_DimensionMismatch_Throws() { using DiskVectorStore store = new DiskVectorStore(DefaultName, filePath, DefaultDimension); - await Assert.ThrowsExactlyAsync(() => + await Assert.ThrowsAsync(() => store.AddAsync(Guid.NewGuid(), new float[64])); } finally @@ -140,7 +139,7 @@ await Assert.ThrowsExactlyAsync(() => } } - [TestMethod] + [Fact] public async Task RemoveAsync_MarksDeleted() { string filePath = GetTempFilePath(); @@ -153,8 +152,8 @@ public async Task RemoveAsync_MarksDeleted() bool removed = await store.RemoveAsync(id); - Assert.IsTrue(removed); - Assert.AreEqual(1, store.Count); + Assert.True(removed); + Assert.Equal(1, store.Count); } finally { @@ -162,7 +161,7 @@ public async Task RemoveAsync_MarksDeleted() } } - [TestMethod] + [Fact] public async Task RemoveAsync_NonExistent_ReturnsFalse() { string filePath = GetTempFilePath(); @@ -173,8 +172,8 @@ public async Task RemoveAsync_NonExistent_ReturnsFalse() bool removed = await store.RemoveAsync(Guid.NewGuid()); - Assert.IsFalse(removed); - Assert.AreEqual(1, store.Count); + Assert.False(removed); + Assert.Equal(1, store.Count); } finally { @@ -186,7 +185,7 @@ public async Task RemoveAsync_NonExistent_ReturnsFalse() #region Search - [TestMethod] + [Fact] public async Task FindMostSimilarAsync_ReturnsCorrectResults() { string filePath = GetTempFilePath(); @@ -201,9 +200,9 @@ public async Task FindMostSimilarAsync_ReturnsCorrectResults() IReadOnlyList> results = await store.FindMostSimilarAsync(targetVector, 1); - Assert.AreEqual(1, results.Count); - Assert.AreEqual(targetId, results[0].Id); - Assert.AreEqual(1.0f, results[0].Score, 0.0001f); + Assert.Single(results); + Assert.Equal(targetId, results[0].Id); + TestHelpers.AssertApproximatelyEqual(1.0f, results[0].Score, 0.0001f); } finally { @@ -211,7 +210,7 @@ public async Task FindMostSimilarAsync_ReturnsCorrectResults() } } - [TestMethod] + [Fact] public async Task FindMostSimilarAsync_ExcludesDeletedKeys() { string filePath = GetTempFilePath(); @@ -230,8 +229,8 @@ public async Task FindMostSimilarAsync_ExcludesDeletedKeys() IReadOnlyList> results = await store.FindMostSimilarAsync(targetVector, 1); - Assert.AreEqual(1, results.Count); - Assert.AreEqual(otherId, results[0].Id); + Assert.Single(results); + Assert.Equal(otherId, results[0].Id); } finally { @@ -239,7 +238,7 @@ public async Task FindMostSimilarAsync_ExcludesDeletedKeys() } } - [TestMethod] + [Fact] public async Task FindMostSimilarAsync_EmptyStore_ReturnsEmpty() { string filePath = GetTempFilePath(); @@ -250,7 +249,7 @@ public async Task FindMostSimilarAsync_EmptyStore_ReturnsEmpty() IReadOnlyList> results = await store.FindMostSimilarAsync( TestHelpers.CreateRandomVector(DefaultDimension), 10); - Assert.AreEqual(0, results.Count); + Assert.Empty(results); } finally { @@ -258,7 +257,7 @@ public async Task FindMostSimilarAsync_EmptyStore_ReturnsEmpty() } } - [TestMethod] + [Fact] public async Task FindMostSimilarAsync_ReturnsSortedDescending() { string filePath = GetTempFilePath(); @@ -273,10 +272,10 @@ public async Task FindMostSimilarAsync_ReturnsSortedDescending() IReadOnlyList> results = await store.FindMostSimilarAsync(query, 3); - Assert.AreEqual(3, results.Count); + Assert.Equal(3, results.Count); for (int i = 1; i < results.Count; i++) { - Assert.IsTrue(results[i - 1].Score >= results[i].Score, + Assert.True(results[i - 1].Score >= results[i].Score, $"Results not sorted at index {i}"); } } @@ -286,7 +285,7 @@ public async Task FindMostSimilarAsync_ReturnsSortedDescending() } } - [TestMethod] + [Fact] public async Task FindMostSimilarAsync_WithIntKeys() { string filePath = GetTempFilePath(); @@ -300,8 +299,8 @@ public async Task FindMostSimilarAsync_WithIntKeys() IReadOnlyList> results = await store.FindMostSimilarAsync(vector, 1); - Assert.AreEqual(1, results.Count); - Assert.AreEqual(42, results[0].Id); + Assert.Single(results); + Assert.Equal(42, results[0].Id); } finally { @@ -313,7 +312,7 @@ public async Task FindMostSimilarAsync_WithIntKeys() #region Compact - [TestMethod] + [Fact] public async Task CompactAsync_RemovesDeletedRecords() { string filePath = GetTempFilePath(); @@ -330,14 +329,14 @@ public async Task CompactAsync_RemovesDeletedRecords() await store.AddAsync(Guid.NewGuid(), TestHelpers.CreateRandomVector(DefaultDimension, seed: 3)); await store.RemoveAsync(deleteId); - Assert.AreEqual(2, store.Count); + Assert.Equal(2, store.Count); await store.CompactAsync(); - Assert.AreEqual(2, store.Count); + Assert.Equal(2, store.Count); // Verify search still works after compaction IReadOnlyList> results = await store.FindMostSimilarAsync(keepVector, 1); - Assert.AreEqual(keepId, results[0].Id); + Assert.Equal(keepId, results[0].Id); } finally { @@ -345,7 +344,7 @@ public async Task CompactAsync_RemovesDeletedRecords() } } - [TestMethod] + [Fact] public async Task CompactAsync_NoDeleted_IsNoOp() { string filePath = GetTempFilePath(); @@ -359,7 +358,7 @@ public async Task CompactAsync_NoDeleted_IsNoOp() await store.CompactAsync(); long sizeAfter = new FileInfo(filePath).Length; - Assert.AreEqual(sizeBefore, sizeAfter); + Assert.Equal(sizeBefore, sizeAfter); } finally { @@ -367,7 +366,7 @@ public async Task CompactAsync_NoDeleted_IsNoOp() } } - [TestMethod] + [Fact] public async Task CompactAsync_PreservesDataAcrossReopen() { string filePath = GetTempFilePath(); @@ -391,10 +390,10 @@ public async Task CompactAsync_PreservesDataAcrossReopen() // Re-open and verify using DiskVectorStore reopened = new DiskVectorStore(DefaultName, filePath, DefaultDimension); - Assert.AreEqual(2, reopened.Count); + Assert.Equal(2, reopened.Count); IReadOnlyList> results = await reopened.FindMostSimilarAsync(keepVector, 1); - Assert.AreEqual(keepId, results[0].Id); + Assert.Equal(keepId, results[0].Id); } finally { @@ -406,7 +405,7 @@ public async Task CompactAsync_PreservesDataAcrossReopen() #region Persistence Across Reopen - [TestMethod] + [Fact] public async Task Persistence_SurvivesDisposeAndReopen() { string filePath = GetTempFilePath(); @@ -421,11 +420,11 @@ public async Task Persistence_SurvivesDisposeAndReopen() } using DiskVectorStore reopened = new DiskVectorStore(DefaultName, filePath, DefaultDimension); - Assert.AreEqual(1, reopened.Count); + Assert.Equal(1, reopened.Count); IReadOnlyList> results = await reopened.FindMostSimilarAsync(vector, 1); - Assert.AreEqual(id, results[0].Id); - Assert.AreEqual(1.0f, results[0].Score, 0.0001f); + Assert.Equal(id, results[0].Id); + TestHelpers.AssertApproximatelyEqual(1.0f, results[0].Score, 0.0001f); } finally { @@ -437,7 +436,7 @@ public async Task Persistence_SurvivesDisposeAndReopen() #region Format Compatibility - [TestMethod] + [Fact] public async Task FileFormat_CompatibleWithCosineVectorStoreSave() { string filePath = GetTempFilePath(); @@ -457,11 +456,11 @@ public async Task FileFormat_CompatibleWithCosineVectorStoreSave() // Open with disk store using DiskVectorStore diskStore = new DiskVectorStore("disk", filePath, DefaultDimension); - Assert.AreEqual(1, diskStore.Count); + Assert.Equal(1, diskStore.Count); IReadOnlyList> results = await diskStore.FindMostSimilarAsync(vector, 1); - Assert.AreEqual(id, results[0].Id); - Assert.AreEqual(1.0f, results[0].Score, 0.0001f); + Assert.Equal(id, results[0].Id); + TestHelpers.AssertApproximatelyEqual(1.0f, results[0].Score, 0.0001f); } finally { @@ -473,7 +472,7 @@ public async Task FileFormat_CompatibleWithCosineVectorStoreSave() #region Concurrency - [TestMethod] + [Fact] public async Task ConcurrentReads_AreThreadSafe() { string filePath = GetTempFilePath(); @@ -499,7 +498,7 @@ public async Task ConcurrentReads_AreThreadSafe() foreach (IReadOnlyList> results in allResults) { - Assert.AreEqual(5, results.Count); + Assert.Equal(5, results.Count); } } finally @@ -512,7 +511,7 @@ public async Task ConcurrentReads_AreThreadSafe() #region Delete Persistence - [TestMethod] + [Fact] public async Task RemoveAsync_DeletePersistsAcrossReopen() { string filePath = GetTempFilePath(); @@ -528,21 +527,21 @@ public async Task RemoveAsync_DeletePersistsAcrossReopen() { await store.AddAsync(keepId, keepVector); await store.AddAsync(deleteId, deleteVector); - Assert.AreEqual(2, store.Count); + Assert.Equal(2, store.Count); bool removed = await store.RemoveAsync(deleteId); - Assert.IsTrue(removed); - Assert.AreEqual(1, store.Count); + Assert.True(removed); + Assert.Equal(1, store.Count); } // Reopen and verify delete persisted using (DiskVectorStore store = new DiskVectorStore(DefaultName, filePath, DefaultDimension)) { - Assert.AreEqual(1, store.Count); + Assert.Equal(1, store.Count); IReadOnlyList> results = await store.FindMostSimilarAsync(keepVector, 10); - Assert.AreEqual(1, results.Count); - Assert.AreEqual(keepId, results[0].Id); + Assert.Single(results); + Assert.Equal(keepId, results[0].Id); } } finally @@ -551,7 +550,7 @@ public async Task RemoveAsync_DeletePersistsAcrossReopen() } } - [TestMethod] + [Fact] public async Task RemoveAsync_DeletePersistsAcrossReopen_ThenCompact() { string filePath = GetTempFilePath(); @@ -569,17 +568,17 @@ public async Task RemoveAsync_DeletePersistsAcrossReopen_ThenCompact() await store.AddAsync(deleteId, deleteVector); await store.RemoveAsync(deleteId); await store.CompactAsync(); - Assert.AreEqual(1, store.Count); + Assert.Equal(1, store.Count); } // Reopen and verify using (DiskVectorStore store = new DiskVectorStore(DefaultName, filePath, DefaultDimension)) { - Assert.AreEqual(1, store.Count); + Assert.Equal(1, store.Count); IReadOnlyList> results = await store.FindMostSimilarAsync(keepVector, 10); - Assert.AreEqual(1, results.Count); - Assert.AreEqual(keepId, results[0].Id); + Assert.Single(results); + Assert.Equal(keepId, results[0].Id); } } finally @@ -592,7 +591,7 @@ public async Task RemoveAsync_DeletePersistsAcrossReopen_ThenCompact() #region Null Query - [TestMethod] + [Fact] public async Task FindMostSimilarAsync_NullQuery_Throws() { string filePath = GetTempFilePath(); @@ -601,7 +600,7 @@ public async Task FindMostSimilarAsync_NullQuery_Throws() using DiskVectorStore store = new DiskVectorStore(DefaultName, filePath, DefaultDimension); await store.AddAsync(Guid.NewGuid(), TestHelpers.CreateRandomVector(DefaultDimension)); - await Assert.ThrowsExactlyAsync(() => + await Assert.ThrowsAsync(() => store.FindMostSimilarAsync(null!, 10)); } finally diff --git a/VectorSharp.Storage.Tests/MSTestSettings.cs b/VectorSharp.Storage.Tests/MSTestSettings.cs deleted file mode 100644 index aaf278c..0000000 --- a/VectorSharp.Storage.Tests/MSTestSettings.cs +++ /dev/null @@ -1 +0,0 @@ -[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)] diff --git a/VectorSharp.Storage.Tests/MinHeapTests.cs b/VectorSharp.Storage.Tests/MinHeapTests.cs index 0285c51..9e62bd8 100644 --- a/VectorSharp.Storage.Tests/MinHeapTests.cs +++ b/VectorSharp.Storage.Tests/MinHeapTests.cs @@ -1,20 +1,19 @@ namespace VectorSharp.Storage.Tests { - [TestClass] public class MinHeapTests { - [TestMethod] + [Fact] public void Add_SingleItem_ReturnsIt() { MinHeap heap = new MinHeap(5); heap.Add("item1", 0.5f); - Assert.AreEqual(1, heap.Count); + Assert.Equal(1, heap.Count); ReadOnlySpan items = heap.GetItems(); - Assert.AreEqual("item1", items[0]); + Assert.Equal("item1", items[0]); } - [TestMethod] + [Fact] public void Add_BelowCapacity_ReturnsAllItems() { MinHeap heap = new MinHeap(5); @@ -22,11 +21,11 @@ public void Add_BelowCapacity_ReturnsAllItems() heap.Add("b", 0.2f); heap.Add("c", 0.3f); - Assert.AreEqual(3, heap.Count); - Assert.IsFalse(heap.IsFull); + Assert.Equal(3, heap.Count); + Assert.False(heap.IsFull); } - [TestMethod] + [Fact] public void Add_AtCapacity_ReplacesMinWhenHigherPriority() { MinHeap heap = new MinHeap(3); @@ -34,18 +33,18 @@ public void Add_AtCapacity_ReplacesMinWhenHigherPriority() heap.Add("b", 0.2f); heap.Add("c", 0.3f); - Assert.IsTrue(heap.IsFull); + Assert.True(heap.IsFull); // Add item with higher priority than the current minimum heap.Add("d", 0.5f); - Assert.AreEqual(3, heap.Count); + Assert.Equal(3, heap.Count); // The minimum should no longer be 0.1 - Assert.IsTrue(heap.MinPriority >= 0.2f); + Assert.True(heap.MinPriority >= 0.2f); } - [TestMethod] + [Fact] public void Add_AtCapacity_IgnoresWhenLowerPriority() { MinHeap heap = new MinHeap(3); @@ -58,11 +57,11 @@ public void Add_AtCapacity_IgnoresWhenLowerPriority() // Add item with priority lower than the minimum heap.Add("d", 0.1f); - Assert.AreEqual(3, heap.Count); - Assert.AreEqual(minBefore, heap.MinPriority); + Assert.Equal(3, heap.Count); + Assert.Equal(minBefore, heap.MinPriority); } - [TestMethod] + [Fact] public void GetSortedDescending_ReturnsSortedByPriorityDescending() { MinHeap heap = new MinHeap(5); @@ -74,24 +73,24 @@ public void GetSortedDescending_ReturnsSortedByPriorityDescending() (string Item, float Priority)[] sorted = heap.GetSortedDescending(); - Assert.AreEqual(5, sorted.Length); - Assert.AreEqual("high", sorted[0].Item); - Assert.AreEqual(0.9f, sorted[0].Priority); - Assert.AreEqual("midhigh", sorted[1].Item); - Assert.AreEqual("mid", sorted[2].Item); - Assert.AreEqual("midlow", sorted[3].Item); - Assert.AreEqual("low", sorted[4].Item); + Assert.Equal(5, sorted.Length); + Assert.Equal("high", sorted[0].Item); + Assert.Equal(0.9f, sorted[0].Priority); + Assert.Equal("midhigh", sorted[1].Item); + Assert.Equal("mid", sorted[2].Item); + Assert.Equal("midlow", sorted[3].Item); + Assert.Equal("low", sorted[4].Item); } - [TestMethod] + [Fact] public void MinPriority_EmptyHeap_ReturnsMinValue() { MinHeap heap = new MinHeap(5); - Assert.AreEqual(float.MinValue, heap.MinPriority); + Assert.Equal(float.MinValue, heap.MinPriority); } - [TestMethod] + [Fact] public void MinPriority_ReturnsSmallestPriority() { MinHeap heap = new MinHeap(5); @@ -99,49 +98,49 @@ public void MinPriority_ReturnsSmallestPriority() heap.Add("b", 0.2f); heap.Add("c", 0.8f); - Assert.AreEqual(0.2f, heap.MinPriority); + Assert.Equal(0.2f, heap.MinPriority); } - [TestMethod] + [Fact] public void IsFull_ReturnsTrueAtCapacity() { MinHeap heap = new MinHeap(2); - Assert.IsFalse(heap.IsFull); + Assert.False(heap.IsFull); heap.Add("a", 0.1f); - Assert.IsFalse(heap.IsFull); + Assert.False(heap.IsFull); heap.Add("b", 0.2f); - Assert.IsTrue(heap.IsFull); + Assert.True(heap.IsFull); } - [TestMethod] + [Fact] public void Empty_ReturnsNoItems() { MinHeap heap = new MinHeap(5); - Assert.AreEqual(0, heap.Count); - Assert.AreEqual(0, heap.GetItems().Length); - Assert.AreEqual(0, heap.GetSortedDescending().Length); + Assert.Equal(0, heap.Count); + Assert.Equal(0, heap.GetItems().Length); + Assert.Empty(heap.GetSortedDescending()); } - [TestMethod] + [Fact] public void Capacity_One_WorksCorrectly() { MinHeap heap = new MinHeap(1); heap.Add("first", 0.3f); - Assert.IsTrue(heap.IsFull); + Assert.True(heap.IsFull); heap.Add("second", 0.5f); - Assert.AreEqual(1, heap.Count); - Assert.AreEqual("second", heap.GetItems()[0]); + Assert.Equal(1, heap.Count); + Assert.Equal("second", heap.GetItems()[0]); heap.Add("third", 0.1f); - Assert.AreEqual(1, heap.Count); - Assert.AreEqual("second", heap.GetItems()[0]); + Assert.Equal(1, heap.Count); + Assert.Equal("second", heap.GetItems()[0]); } - [TestMethod] + [Fact] public void LargeCapacity_MaintainsCorrectTopK() { int capacity = 10; @@ -153,16 +152,16 @@ public void LargeCapacity_MaintainsCorrectTopK() heap.Add(i, i); } - Assert.AreEqual(capacity, heap.Count); + Assert.Equal(capacity, heap.Count); // The top 10 should be items 90-99 (int Item, float Priority)[] sorted = heap.GetSortedDescending(); - Assert.AreEqual(10, sorted.Length); + Assert.Equal(10, sorted.Length); for (int i = 0; i < 10; i++) { - Assert.AreEqual(99 - i, sorted[i].Item); - Assert.AreEqual(99 - i, sorted[i].Priority); + Assert.Equal(99 - i, sorted[i].Item); + Assert.Equal(99 - i, sorted[i].Priority); } } } diff --git a/VectorSharp.Storage.Tests/SearchResultTests.cs b/VectorSharp.Storage.Tests/SearchResultTests.cs index c56685c..8543210 100644 --- a/VectorSharp.Storage.Tests/SearchResultTests.cs +++ b/VectorSharp.Storage.Tests/SearchResultTests.cs @@ -1,37 +1,36 @@ namespace VectorSharp.Storage.Tests { - [TestClass] public class SearchResultTests { - [TestMethod] + [Fact] public void Constructor_SetsAllProperties() { Guid id = Guid.NewGuid(); SearchResult result = new SearchResult { Id = id, Score = 0.95f, StoreName = "test-store" }; - Assert.AreEqual(id, result.Id); - Assert.AreEqual(0.95f, result.Score); - Assert.AreEqual("test-store", result.StoreName); + Assert.Equal(id, result.Id); + Assert.Equal(0.95f, result.Score); + Assert.Equal("test-store", result.StoreName); } - [TestMethod] + [Fact] public void SearchResult_WithIntKey_SetsAllProperties() { SearchResult result = new SearchResult { Id = 42, Score = 0.8f, StoreName = "int-store" }; - Assert.AreEqual(42, result.Id); - Assert.AreEqual(0.8f, result.Score); - Assert.AreEqual("int-store", result.StoreName); + Assert.Equal(42, result.Id); + Assert.Equal(0.8f, result.Score); + Assert.Equal("int-store", result.StoreName); } - [TestMethod] + [Fact] public void SearchResult_WithLongKey_SetsAllProperties() { SearchResult result = new SearchResult { Id = 123456789L, Score = 0.5f, StoreName = "long-store" }; - Assert.AreEqual(123456789L, result.Id); - Assert.AreEqual(0.5f, result.Score); - Assert.AreEqual("long-store", result.StoreName); + Assert.Equal(123456789L, result.Id); + Assert.Equal(0.5f, result.Score); + Assert.Equal("long-store", result.StoreName); } } } diff --git a/VectorSharp.Storage.Tests/TestHelpers.cs b/VectorSharp.Storage.Tests/TestHelpers.cs index fc93936..efbebe8 100644 --- a/VectorSharp.Storage.Tests/TestHelpers.cs +++ b/VectorSharp.Storage.Tests/TestHelpers.cs @@ -72,5 +72,15 @@ internal static async Task> CreatePopulatedIntStoreAsync( return store; } + + /// + /// Asserts two floats are equal within an absolute tolerance. + /// + internal static void AssertApproximatelyEqual(float expected, float actual, float tolerance, string? message = null) + { + float diff = MathF.Abs(expected - actual); + Assert.True(diff <= tolerance, + message ?? $"Expected {expected} but got {actual} (diff {diff} exceeds tolerance {tolerance})"); + } } } diff --git a/VectorSharp.Storage.Tests/VectorSearchTests.cs b/VectorSharp.Storage.Tests/VectorSearchTests.cs index 662a504..931adc4 100644 --- a/VectorSharp.Storage.Tests/VectorSearchTests.cs +++ b/VectorSharp.Storage.Tests/VectorSearchTests.cs @@ -1,11 +1,10 @@ namespace VectorSharp.Storage.Tests { - [TestClass] public class VectorSearchTests { private const int DefaultDimension = 128; - [TestMethod] + [Fact] public async Task SearchAsync_SingleStore_ReturnsSameAsDirectSearch() { using CosineVectorStore store = await TestHelpers.CreatePopulatedStoreAsync("store-a", DefaultDimension, 20); @@ -14,15 +13,15 @@ public async Task SearchAsync_SingleStore_ReturnsSameAsDirectSearch() IReadOnlyList> directResults = await store.FindMostSimilarAsync(query, 5); IReadOnlyList> searchResults = await VectorSearch.SearchAsync(query, 5, store); - Assert.AreEqual(directResults.Count, searchResults.Count); + Assert.Equal(directResults.Count, searchResults.Count); for (int i = 0; i < directResults.Count; i++) { - Assert.AreEqual(directResults[i].Id, searchResults[i].Id); - Assert.AreEqual(directResults[i].Score, searchResults[i].Score, 0.0001f); + Assert.Equal(directResults[i].Id, searchResults[i].Id); + TestHelpers.AssertApproximatelyEqual(directResults[i].Score, searchResults[i].Score, 0.0001f); } } - [TestMethod] + [Fact] public async Task SearchAsync_MultipleStores_MergesByScore() { using CosineVectorStore storeA = new CosineVectorStore("store-a", DefaultDimension); @@ -39,12 +38,12 @@ public async Task SearchAsync_MultipleStores_MergesByScore() IReadOnlyList> results = await VectorSearch.SearchAsync(query, 3, storeA, storeB); - Assert.AreEqual(3, results.Count); - Assert.AreEqual(bestId, results[0].Id); - Assert.AreEqual("store-b", results[0].StoreName); + Assert.Equal(3, results.Count); + Assert.Equal(bestId, results[0].Id); + Assert.Equal("store-b", results[0].StoreName); } - [TestMethod] + [Fact] public async Task SearchAsync_RespectsCount() { using CosineVectorStore storeA = await TestHelpers.CreatePopulatedStoreAsync("store-a", DefaultDimension, 10); @@ -53,10 +52,10 @@ public async Task SearchAsync_RespectsCount() float[] query = TestHelpers.CreateRandomVector(DefaultDimension, seed: 500); IReadOnlyList> results = await VectorSearch.SearchAsync(query, 5, storeA, storeB); - Assert.AreEqual(5, results.Count); + Assert.Equal(5, results.Count); } - [TestMethod] + [Fact] public async Task SearchAsync_ResultsAreSortedDescending() { using CosineVectorStore storeA = await TestHelpers.CreatePopulatedStoreAsync("store-a", DefaultDimension, 20); @@ -67,12 +66,12 @@ public async Task SearchAsync_ResultsAreSortedDescending() for (int i = 1; i < results.Count; i++) { - Assert.IsTrue(results[i - 1].Score >= results[i].Score, + Assert.True(results[i - 1].Score >= results[i].Score, $"Results not sorted at index {i}: {results[i - 1].Score} vs {results[i].Score}"); } } - [TestMethod] + [Fact] public async Task SearchAsync_ResultsIncludeCorrectStoreName() { using CosineVectorStore storeA = new CosineVectorStore("alpha", DefaultDimension); @@ -84,13 +83,13 @@ public async Task SearchAsync_ResultsIncludeCorrectStoreName() float[] query = TestHelpers.CreateRandomVector(DefaultDimension, seed: 500); IReadOnlyList> results = await VectorSearch.SearchAsync(query, 10, storeA, storeB); - Assert.AreEqual(2, results.Count); + Assert.Equal(2, results.Count); HashSet storeNames = new HashSet(results.Select(r => r.StoreName)); - Assert.IsTrue(storeNames.Contains("alpha")); - Assert.IsTrue(storeNames.Contains("beta")); + Assert.Contains("alpha", storeNames); + Assert.Contains("beta", storeNames); } - [TestMethod] + [Fact] public async Task SearchAsync_EmptyStores_ReturnsEmpty() { using CosineVectorStore storeA = new CosineVectorStore("store-a", DefaultDimension); @@ -99,39 +98,39 @@ public async Task SearchAsync_EmptyStores_ReturnsEmpty() float[] query = TestHelpers.CreateRandomVector(DefaultDimension); IReadOnlyList> results = await VectorSearch.SearchAsync(query, 10, storeA, storeB); - Assert.AreEqual(0, results.Count); + Assert.Empty(results); } - [TestMethod] + [Fact] public async Task SearchAsync_ZeroCount_Throws() { using CosineVectorStore store = await TestHelpers.CreatePopulatedStoreAsync("store", DefaultDimension, 10); float[] query = TestHelpers.CreateRandomVector(DefaultDimension); - await Assert.ThrowsExactlyAsync(() => + await Assert.ThrowsAsync(() => VectorSearch.SearchAsync(query, 0, store)); } - [TestMethod] + [Fact] public async Task SearchAsync_NullQuery_Throws() { using CosineVectorStore store = new CosineVectorStore("store", DefaultDimension); - await Assert.ThrowsExactlyAsync(() => + await Assert.ThrowsAsync(() => VectorSearch.SearchAsync(null!, 10, store)); } - [TestMethod] + [Fact] public async Task SearchAsync_NoStores_Throws() { float[] query = TestHelpers.CreateRandomVector(DefaultDimension); - await Assert.ThrowsExactlyAsync(() => + await Assert.ThrowsAsync(() => VectorSearch.SearchAsync(query, 10)); } - [TestMethod] + [Fact] public async Task SearchAsync_MixedInMemoryAndDisk_WorksTogether() { string filePath = Path.Combine(Path.GetTempPath(), $"vectorsharp_test_{Guid.NewGuid()}.dat"); @@ -147,9 +146,9 @@ public async Task SearchAsync_MixedInMemoryAndDisk_WorksTogether() IReadOnlyList> results = await VectorSearch.SearchAsync( bestVector, 2, memStore, diskStore); - Assert.AreEqual(2, results.Count); - Assert.AreEqual(1, results[0].Id); // best match is in memory store - Assert.AreEqual("memory", results[0].StoreName); + Assert.Equal(2, results.Count); + Assert.Equal(1, results[0].Id); // best match is in memory store + Assert.Equal("memory", results[0].StoreName); } finally { diff --git a/VectorSharp.Storage.Tests/VectorSharp.Storage.Tests.csproj b/VectorSharp.Storage.Tests/VectorSharp.Storage.Tests.csproj index 4043bd2..745ceb0 100644 --- a/VectorSharp.Storage.Tests/VectorSharp.Storage.Tests.csproj +++ b/VectorSharp.Storage.Tests/VectorSharp.Storage.Tests.csproj @@ -10,11 +10,13 @@ - + + + - +