diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 6311165..0690ba3 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -16,7 +16,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 8.x + dotnet-version: 9.x dotnet-quality: 'preview' - name: Restore dependencies run: dotnet restore diff --git a/.github/workflows/manual.yml b/.github/workflows/manual.yml index 7d943a7..9b5fbb8 100644 --- a/.github/workflows/manual.yml +++ b/.github/workflows/manual.yml @@ -20,7 +20,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 8.x + dotnet-version: 9.x dotnet-quality: 'preview' - name: Restore dependencies run: dotnet restore diff --git a/CardinalityEstimation.Test/BiasCorrectionTests.cs b/CardinalityEstimation.Test/BiasCorrectionTests.cs index aed3756..75306e5 100644 --- a/CardinalityEstimation.Test/BiasCorrectionTests.cs +++ b/CardinalityEstimation.Test/BiasCorrectionTests.cs @@ -1,4 +1,7 @@ -// /* +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Xunit; + +// /* // See https://github.com/saguiitay/CardinalityEstimation. // The MIT License (MIT) // @@ -22,11 +25,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // */ - namespace CardinalityEstimation.Test { - using Xunit; - public class BiasCorrectionTests { [Fact] @@ -72,11 +72,156 @@ public void RawEstimateArraysAndBiasDataArraysHaveSameLengths() { Assert.True(BiasCorrection.RawEstimate.Length >= 14); Assert.Equal(BiasCorrection.RawEstimate.Length, BiasCorrection.BiasData.Length); - for (var bits = 0; bits < BiasCorrection.RawEstimate.Length; bits++) { Assert.Equal(BiasCorrection.RawEstimate[bits].Length, BiasCorrection.BiasData[bits].Length); } } + + /// + /// Verifies that when the calculated bias exceeds the raw estimate, + /// the corrected result is clamped to zero. + /// + [Theory] + [InlineData(4, 1.0)] + [InlineData(5, 5.0)] + [InlineData(6, 10.0)] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void CorrectBias_BiasExceedsRawEstimate_ReturnsZero(int bits, double rawEstimate) + { + // Arrange + // Act + double corrected = BiasCorrection.CorrectBias(rawEstimate, bits); + // Assert + Assert.Equal(0.0, corrected); + } + + /// + /// Verifies that special floating-point inputs are handled in accordance with System.Math rules. + /// For PositiveInfinity, the result remains infinity; for NaN, the result is NaN; + /// for NegativeInfinity, the result is clamped to zero. + /// + /// The special raw estimate value. + /// Indicates whether the result should be positive infinity. + /// Indicates whether the result should be NaN. + [Theory] + [InlineData(double.PositiveInfinity, true, false)] + [InlineData(double.NegativeInfinity, false, false)] + [InlineData(double.NaN, false, true)] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void CorrectBias_SpecialFloatingPointInputs_PropagatesAccordingToMathRules(double rawEstimate, bool isPositiveInfinityExpected, bool isNaNExpected) + { + // Arrange + int bits = 4; + // Act + double corrected = BiasCorrection.CorrectBias(rawEstimate, bits); + // Assert + if (isNaNExpected) + { + Assert.True(double.IsNaN(corrected)); + } + else if (isPositiveInfinityExpected) + { + Assert.True(double.IsPositiveInfinity(corrected)); + } + else + { + Assert.Equal(0.0, corrected); + } + } + + } + + /// + /// Contains additional edge-case tests for . + /// The original happy-path scenarios already exist; here we focus on + /// numeric extremes, invalid estimator sizes and exact-match integrity + /// across multiple precision levels. + /// + public class BiasCorrectionEdgeTests + { + #region Exact-match scenarios + /// + /// Verifies that when the raw estimate exactly matches the first element + /// of the corresponding RawEstimate array, the bias from the matching + /// index is used without interpolation. + /// + /// Estimator precision being exercised. + /// Exact raw estimate value located at index 0. + /// Bias located at index 0 for the same precision. + [Theory] + [InlineData(4, 11.0, 10.0)] + [InlineData(5, 23.0, 22.0)] + [InlineData(6, 46.0, 45.0)] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25306.18+1df180b")] + [Trait("Category", "auto-generated")] + public void CorrectBias_RawEstimateMatchesFirstArrayElement_ReturnsValueMinusBias(int bits, double rawEstimate, double expectedBias) + { + // Act + double corrected = BiasCorrection.CorrectBias(rawEstimate, bits); + // Assert + Assert.Equal(rawEstimate - expectedBias, corrected); + } + + #endregion + #region Extreme double values + /// + /// Ensures that special floating-point inputs are propagated as defined + /// by System.Math.Max and arithmetic rules. + /// + /// Special double value being tested. + /// True when the result should be positive infinity. + /// True when the result should be NaN. + [Theory] + [InlineData(double.PositiveInfinity, true, false)] + [InlineData(double.NegativeInfinity, false, false)] + [InlineData(double.NaN, false, true)] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25306.18+1df180b")] + [Trait("Category", "auto-generated")] + public void CorrectBias_SpecialFloatingPointInputs_PropagatesAccordingToMathRules(double rawEstimate, bool isPositiveInfinityExpected, bool isNaNExpected) + { + // Act + double result = BiasCorrection.CorrectBias(rawEstimate, 4); + // Assert + if (isNaNExpected) + { + Assert.True(double.IsNaN(result)); + } + else if (isPositiveInfinityExpected) + { + Assert.True(double.IsPositiveInfinity(result)); + } + else + { + Assert.Equal(0, result); // Negative infinity or very small values clamp to zero. + } + } + + #endregion + #region Invalid estimator sizes + #endregion + #region Bias larger than raw estimate + /// + /// Confirms that when the calculated bias exceeds the raw estimate, + /// the corrected value is never negative and is instead clamped to zero. + /// + /// Estimator precision. + /// Intentionally low raw estimate. + [Theory] + [InlineData(4, 1.0)] // Bias ~10 + [InlineData(5, 5.0)] // Bias ~22 + [InlineData(6, 10.0)] // Bias ~45 + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25306.18+1df180b")] // Bias ~45 + [Trait("Category", "auto-generated")] // Bias ~45 + public void CorrectBias_BiasExceedsRawEstimate_ReturnsZero(int bits, double rawEstimate) + { + // Act + double corrected = BiasCorrection.CorrectBias(rawEstimate, bits); + // Assert + Assert.Equal(0, corrected); + } + #endregion } } \ No newline at end of file diff --git a/CardinalityEstimation.Test/CardinalityEstimation.Test.csproj b/CardinalityEstimation.Test/CardinalityEstimation.Test.csproj index e339b35..7978315 100644 --- a/CardinalityEstimation.Test/CardinalityEstimation.Test.csproj +++ b/CardinalityEstimation.Test/CardinalityEstimation.Test.csproj @@ -8,9 +8,9 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/CardinalityEstimation.Test/CardinalityEstimatorSerializerTests.cs b/CardinalityEstimation.Test/CardinalityEstimatorSerializerTests.cs index df750d6..fc09820 100644 --- a/CardinalityEstimation.Test/CardinalityEstimatorSerializerTests.cs +++ b/CardinalityEstimation.Test/CardinalityEstimatorSerializerTests.cs @@ -1,4 +1,4 @@ -// /* +// /* // See https://github.com/saguiitay/CardinalityEstimation. // The MIT License (MIT) // diff --git a/CardinalityEstimation.Test/CardinalityEstimatorTests.cs b/CardinalityEstimation.Test/CardinalityEstimatorTests.cs index f20bb83..07c5866 100644 --- a/CardinalityEstimation.Test/CardinalityEstimatorTests.cs +++ b/CardinalityEstimation.Test/CardinalityEstimatorTests.cs @@ -1,4 +1,10 @@ -// /* +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using System; +using System.Diagnostics; +using Xunit; +using Xunit.Abstractions; + +// /* // See https://github.com/saguiitay/CardinalityEstimation. // The MIT License (MIT) // @@ -22,28 +28,14 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // */ - namespace CardinalityEstimation.Test { - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.IO; - using System.Linq; - - using CardinalityEstimation.Hash; - - using Xunit; - using Xunit.Abstractions; - public class CardinalityEstimatorTests : IDisposable { private const int ElementSizeInBytes = 20; public static readonly Random Rand = new Random(); - private readonly ITestOutputHelper output; private readonly Stopwatch stopwatch; - public CardinalityEstimatorTests(ITestOutputHelper outputHelper) { output = outputHelper; @@ -73,70 +65,25 @@ public void TestGetSigma() public void TestCountAdditions() { var estimator = new CardinalityEstimator(); - Assert.Equal(0UL, estimator.CountAdditions); - estimator.Add(0); estimator.Add(0); - Assert.Equal(2UL, estimator.CountAdditions); - var estimator2 = new CardinalityEstimator(); estimator2.Add(0); estimator.Merge(estimator2); - Assert.Equal(3UL, estimator.CountAdditions); } - [Fact] - public void TestChanged() - { - var estimator = new CardinalityEstimator(Murmur3.GetHashCode); - - Assert.Equal(0UL, estimator.CountAdditions); - - bool changed = estimator.Add(0); - Assert.True(changed); - changed = estimator.Add(0); - Assert.False(changed); - - for (var i = 1; i < 100; i++) - { - changed = estimator.Add(i); - Assert.True(changed); - } - - changed = estimator.Add(100); - Assert.True(changed); //First change from direct count - - changed = estimator.Add(100); - Assert.False(changed); - - changed = estimator.Add(101); - Assert.True(changed); - - changed = estimator.Add(102); - Assert.True(changed); - - changed = estimator.Add(0); - Assert.False(changed); - - changed = estimator.Add(116); //element doesn't exist but the estimator internal state doesn't change - Assert.False(changed); - } - [Fact] public void TestDifferentAccuracies() { const double stdError4Bits = 0.26; RunTest(stdError4Bits, 1000000); - const double stdError12Bits = 0.01625; RunTest(stdError12Bits, 1000000); - const double stdError14Bits = 0.008125; RunTest(stdError14Bits, 1000000); - const double stdError16Bits = 0.0040625; RunTest(stdError16Bits, 1000000); } @@ -188,17 +135,6 @@ public void TestMergeLargeCardinality() RunTest(stdError, cardinality, numHllInstances: 60); } - [Fact] - public void TestRecreationFromData() - { - RunRecreationFromData(10); - RunRecreationFromData(100); - RunRecreationFromData(1000); - RunRecreationFromData(10000); - RunRecreationFromData(100000); - RunRecreationFromData(1000000); - } - [Fact] public void StaticMergeTest() { @@ -211,7 +147,6 @@ public void StaticMergeTest() } CardinalityEstimator merged = CardinalityEstimator.Merge(estimators); - Assert.Equal(10UL, merged.Count()); Assert.Equal(expectedBitsPerIndex, merged.GetState().BitsPerIndex); } @@ -223,55 +158,6 @@ public void StaticMergeHandlesNullParameter() Assert.Null(result); } - [Fact] - public void StaticMergeHandlesNullElements() - { - const int expectedBitsPerIndex = 11; - var estimators = new List { null, new CardinalityEstimator(Fnv1A.GetHashCode, expectedBitsPerIndex), null }; - CardinalityEstimator result = CardinalityEstimator.Merge(estimators); - Assert.NotNull(result); - Assert.Equal(expectedBitsPerIndex, result.GetState().BitsPerIndex); - } - - private void RunRecreationFromData(int cardinality = 1000000) - { - var hll = new CardinalityEstimator(); - - var nextMember = new byte[ElementSizeInBytes]; - for (var i = 0; i < cardinality; i++) - { - Rand.NextBytes(nextMember); - hll.Add(nextMember); - } - - CardinalityEstimatorState data = hll.GetState(); - - var hll2 = new CardinalityEstimator(Murmur3.GetHashCode, data); - CardinalityEstimatorState data2 = hll2.GetState(); - - Assert.Equal(data.BitsPerIndex, data2.BitsPerIndex); - Assert.Equal(data.IsSparse, data2.IsSparse); - - Assert.True((data.DirectCount != null && data2.DirectCount != null) || (data.DirectCount == null && data2.DirectCount == null)); - Assert.True((data.LookupSparse != null && data2.LookupSparse != null) || - (data.LookupSparse == null && data2.LookupSparse == null)); - Assert.True((data.LookupDense != null && data2.LookupDense != null) || (data.LookupDense == null && data2.LookupDense == null)); - - if (data.DirectCount != null) - { - // DirectCount are subsets of each-other => they are the same set - Assert.True(data.DirectCount.IsSubsetOf(data2.DirectCount) && data2.DirectCount.IsSubsetOf(data.DirectCount)); - } - if (data.LookupSparse != null) - { - Assert.True(data.LookupSparse.DictionaryEqual(data2.LookupSparse)); - } - if (data.LookupDense != null) - { - Assert.True(data.LookupDense.SequenceEqual(data2.LookupDense)); - } - } - [Fact(Skip = "runtime is long")] public void TestPast32BitLimit() { @@ -317,7 +203,6 @@ public void ReportAccuracy() { Rand.NextBytes(nextMember); hll.Add(nextMember); - if (i % 1007 == 0) // just some interval to sample error at, can be any number { double error = (hll.Count() - (double)(i + 1)) / ((double)i + 1); @@ -331,39 +216,9 @@ public void ReportAccuracy() output.WriteLine("Worst: {0}", worstMember); output.WriteLine("Max error: {0}", maxError); - Assert.True(true); } - [Fact] - public void DirectCountingIsResetWhenMergingAlmostFullEstimators() - { - var addedEstimator = new CardinalityEstimator(); - var mergedEstimator = new CardinalityEstimator(); - - for (int i = 0; i < 10_000; i++) - { - var guid = Guid.NewGuid().ToString(); - - addedEstimator.Add(guid); - - // Simulate some intermediate estimators being merged together - var temporaryEstimator = new CardinalityEstimator(); - temporaryEstimator.Add(guid); - mergedEstimator.Merge(temporaryEstimator); - } - - var serializer = new CardinalityEstimatorSerializer(); - - var stream1 = new MemoryStream(); - serializer.Serialize(stream1, addedEstimator, true); - - var stream2 = new MemoryStream(); - serializer.Serialize(stream2, mergedEstimator, true); - - Assert.Equal(stream1.Length, stream2.Length); - } - [Fact] public void CopyConstructorCorrectlyCopiesValues() { @@ -372,7 +227,6 @@ public void CopyConstructorCorrectlyCopiesValues() for (int cardinality = 1; cardinality < 10_000; cardinality *= 2) { var hll = new CardinalityEstimator(b: b); - var nextMember = new byte[ElementSizeInBytes]; for (var i = 0; i < cardinality; i++) { @@ -381,7 +235,6 @@ public void CopyConstructorCorrectlyCopiesValues() } var hll2 = new CardinalityEstimator(hll); - Assert.Equal(hll, hll2); } } @@ -391,7 +244,6 @@ public void CopyConstructorCorrectlyCopiesValues() for (int cardinality = 1; cardinality < 10_000; cardinality *= 2) { var hll = new CardinalityEstimator(b: b); - var nextMember = new byte[ElementSizeInBytes]; for (var i = 0; i < cardinality; i++) { @@ -400,31 +252,27 @@ public void CopyConstructorCorrectlyCopiesValues() } var hll2 = new CardinalityEstimator(hll); - Assert.Equal(hll, hll2); } } } /// - /// Generates random (or sequential) elements and adds them to CardinalityEstimators, then asserts that - /// the observed error rate is no more than + /// Generates random (or sequential) elements and adds them to CardinalityEstimators, then asserts that + /// the observed error rate is no more than /// - /// Expected standard error of the estimators (upper bound) - /// number of elements to generate in total - /// Maximum allowed error rate. Default is 4 times - /// Number of estimators to create. Generated elements will be assigned to one of the estimators at random - /// When false, elements will be generated at random. When true, elements will be 0,1,2... - /// When true, will disable using direct counting for estimators less than 100 elements. - private void RunTest(double stdError, long expectedCount, double? maxAcceptedError = null, int numHllInstances = 1, - bool sequential = false, bool disableDirectCount = false) + /// Expected standard error of the estimators (upper bound) + /// number of elements to generate in total + /// Maximum allowed error rate. Default is 4 times + /// Number of estimators to create. Generated elements will be assigned to one of the estimators at random + /// When false, elements will be generated at random. When true, elements will be 0,1,2... + /// When true, will disable using direct counting for estimators less than 100 elements. + private void RunTest(double stdError, long expectedCount, double? maxAcceptedError = null, int numHllInstances = 1, bool sequential = false, bool disableDirectCount = false) { maxAcceptedError ??= 10 * stdError; // should fail once in A LOT of runs int b = GetAccuracyInBits(stdError); - var runStopwatch = new Stopwatch(); long gcMemoryAtStart = GetGcMemory(); - // init HLLs var hlls = new CardinalityEstimator[numHllInstances]; for (var i = 0; i < numHllInstances; i++) @@ -451,12 +299,10 @@ private void RunTest(double stdError, long expectedCount, double? maxAcceptedErr runStopwatch.Stop(); ReportMemoryCost(gcMemoryAtStart, output); // done here so references can't be GC'ed yet - // Merge CardinalityEstimator mergedHll = CardinalityEstimator.Merge(hlls); output.WriteLine("Run time: {0}", runStopwatch.Elapsed); output.WriteLine("Expected {0}, got {1}", expectedCount, mergedHll.Count()); - double obsError = Math.Abs(mergedHll.Count() / (double)expectedCount - 1.0); output.WriteLine("StdErr: {0}. Observed error: {1}", stdError, obsError); Assert.True(obsError <= maxAcceptedError, string.Format("Observed error was {0}, over {1}, when adding {2} items", obsError, maxAcceptedError, expectedCount)); @@ -466,9 +312,9 @@ private void RunTest(double stdError, long expectedCount, double? maxAcceptedErr /// /// Gets the number of indexing bits required to produce a given standard error /// - /// + /// /// Standard error, which determines accuracy and memory consumption. For large cardinalities, the observed error is usually less than - /// 3 * . + /// 3 * . /// private static int GetAccuracyInBits(double stdError) { @@ -490,15 +336,1090 @@ private static void ReportMemoryCost(long gcMemoryAtStart, ITestOutputHelper out } /// - /// Returns the base-2 logarithm of . - /// This implementation is faster than as it avoids input checks + /// Returns the base-2 logarithm of . + /// This implementation is faster than as it avoids input checks /// - /// - /// The base-2 logarithm of + /// + /// The base-2 logarithm of private static double Log2(double x) { const double ln2 = 0.693147180559945309417232121458; return Math.Log(x) / ln2; } + + /// + /// Tests that the CardinalityEstimator constructor initializes CountAdditions to zero with various parameters. + /// + /// The accuracy parameter in the allowed range [4,16]. + /// Indicator for using direct counting. + [Theory] + [InlineData(4, true)] + [InlineData(14, true)] + [InlineData(16, true)] + [InlineData(4, false)] + [InlineData(14, false)] + [InlineData(16, false)] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void Constructor_WithParameters_InitializesCountAdditionsToZero(int b, bool useDirectCounting) + { + // Arrange & Act + CardinalityEstimator estimator = new CardinalityEstimator(null, b, useDirectCounting); + // Assert + Assert.Equal(0UL, estimator.CountAdditions); + } + + /// + /// Tests that the copy constructor creates a new instance with the same CountAdditions state as the original. + /// + [Fact] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void CopyConstructor_CopiesInitialCountAdditions() + { + // Arrange + CardinalityEstimator original = new CardinalityEstimator(null, 14, true); + // Note: As the Add methods' implementations are not provided, CountAdditions remains at its initial value. + // Act + CardinalityEstimator copy = new CardinalityEstimator(original); + // Assert + Assert.Equal(original.CountAdditions, copy.CountAdditions); + } + + /// + /// Tests that a new CardinalityEstimator instance created with valid parameters initializes CountAdditions to 0. + /// This test uses multiple boundary values for the 'b' parameter. + /// + /// The number of bits parameter determining accuracy and memory usage, valid in the range [4,16]. + [Theory] + [InlineData(4)] + [InlineData(14)] + [InlineData(16)] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void Constructor_ValidParameters_InitialCountAdditionsZero(int b) + { + // Arrange + GetHashCodeDelegate customHash = (byte[] bytes) => BitConverter.ToUInt64(new byte[8], 0); + bool useDirectCounting = true; + // Act + var estimator = new CardinalityEstimator(customHash, b, useDirectCounting); + // Assert + Assert.NotNull(estimator); + Assert.Equal(0UL, estimator.CountAdditions); + } + + /// + /// Tests that creating a CardinalityEstimator instance with a custom hash function does not throw and initializes correctly. + /// + [Fact] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void Constructor_CustomHashFunction_InstanceCreated() + { + // Arrange + GetHashCodeDelegate customHash = (byte[] bytes) => + { + // A simple custom hash function for testing purposes. + return (ulong)(bytes.Length); + }; + int b = 14; + bool useDirectCounting = true; + // Act + var estimator = new CardinalityEstimator(customHash, b, useDirectCounting); + // Assert + Assert.NotNull(estimator); + Assert.Equal(0UL, estimator.CountAdditions); + } + + /// + /// Tests that the copy constructor of CardinalityEstimator properly copies the state from the original instance. + /// Since internal state details are encapsulated, this test verifies that the copied instance is not null, + /// that its CountAdditions property matches, and that Equals yields true. + /// + [Fact] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void CopyConstructor_ValidEstimator_StateIsCopied() + { + // Arrange + GetHashCodeDelegate customHash = (byte[] bytes) => BitConverter.ToUInt64(new byte[8], 0); + int b = 14; + bool useDirectCounting = true; + var original = new CardinalityEstimator(customHash, b, useDirectCounting); + // In a full implementation, additional state modifications (e.g., via Add methods) would be applied here. + // Act + var copy = new CardinalityEstimator(original); + // Assert + Assert.NotNull(copy); + Assert.Equal(original.CountAdditions, copy.CountAdditions); + Assert.True(original.Equals(copy)); + } + + /// + /// Tests that creating a CardinalityEstimator with an invalid 'b' parameter (e.g., below 4) throws an exception. + /// This test is marked as skipped since the expected behavior for invalid 'b' values is not specified. + /// + [Fact(Skip = "Behavior for invalid 'b' parameter value is not specified; implement test when requirements are defined.")] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void Constructor_InvalidBValue_ThrowsException() + { + // Arrange + GetHashCodeDelegate customHash = (byte[] bytes) => BitConverter.ToUInt64(new byte[8], 0); + int invalidB = 3; // Invalid as the valid range is [4,16] + bool useDirectCounting = true; + // Act & Assert + // Uncomment and adjust the expected exception type when behavior is defined: + // Assert.Throws(() => new CardinalityEstimator(customHash, invalidB, useDirectCounting)); + } + + /// + /// Tests that the default constructor of CardinalityEstimator initializes CountAdditions to zero. + /// + [Fact] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void Constructor_DefaultValues_InitialCountAdditionsIsZero() + { + // Arrange & Act + var estimator = new CardinalityEstimator(); + // Assert + Assert.Equal(0UL, estimator.CountAdditions); + } + + /// + /// Tests that the constructor with custom parameters initializes CountAdditions to zero. + /// Parameterized over different values of b and useDirectCounting. + /// + /// The bits parameter for estimator accuracy. + /// Flag indicating whether to use direct counting. + [Theory] + [InlineData(4, true)] + [InlineData(4, false)] + [InlineData(14, true)] + [InlineData(14, false)] + [InlineData(16, true)] + [InlineData(16, false)] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void Constructor_CustomParameters_InitialCountAdditionsIsZero(int b, bool useDirectCounting) + { + // Arrange & Act + var estimator = new CardinalityEstimator(hashFunction: null, b: b, useDirectCounting: useDirectCounting); + // Assert + Assert.Equal(0UL, estimator.CountAdditions); + } + + /// + /// Tests that the copy constructor creates a new estimator with the same initial state as the original. + /// + [Fact] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void CopyConstructor_CreatesEquivalentInstance() + { + // Arrange + var original = new CardinalityEstimator(); + // As the Add methods implementations are not visible, we only assume initial CountAdditions remains 0. + Assert.Equal(0UL, original.CountAdditions); + // Act + var copy = new CardinalityEstimator(original); + // Assert + Assert.Equal(original.CountAdditions, copy.CountAdditions); + } + + /// + /// Tests that the constructor accepts a custom hash function delegate and does not throw. + /// + [Fact] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void Constructor_CustomHashFunction_DoesNotThrow() + { + // Arrange + GetHashCodeDelegate customHashFunction = (byte[] bytes) => + { + // A simple custom hash function returning a constant value for test purposes. + return 123UL; + }; + // Act + var estimator = new CardinalityEstimator(hashFunction: customHashFunction, b: 14, useDirectCounting: true); + // Assert + Assert.NotNull(estimator); + Assert.Equal(0UL, estimator.CountAdditions); + } + + /// + /// Tests that adding a new, unique string element returns true and increments CountAdditions. + /// Uses various representative string inputs. + /// + /// The string element to add. + [Theory] + [InlineData("test")] + [InlineData("")] + [InlineData(" ")] + [InlineData("special_!@#$%^&*()")] + [InlineData("A very long string that is intended to test the functionality of the Add method in CardinalityEstimator.")] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void Add_String_NewElement_ReturnsTrue_AndCountAdditionIncrements(string element) + { + // Arrange + var estimator = new CardinalityEstimator(hashFunction: TestHashFunction); + ulong initialCount = estimator.CountAdditions; + // Act + bool changed = estimator.Add(element); + // Assert + Assert.True(changed); + Assert.Equal(initialCount + 1, estimator.CountAdditions); + } + + /// + /// Tests that adding a duplicate string element returns false while still incrementing CountAdditions. + /// + /// The duplicate string element to add. + [Theory] + [InlineData("duplicate")] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void Add_String_DuplicateElement_ReturnsFalse_ButCountAdditionIncrements(string element) + { + // Arrange + var estimator = new CardinalityEstimator(hashFunction: TestHashFunction); + // Act + bool firstAdd = estimator.Add(element); + bool secondAdd = estimator.Add(element); + // Assert + Assert.True(firstAdd); + Assert.False(secondAdd); + Assert.Equal(2UL, estimator.CountAdditions); + } + + /// + /// A simple test hash function that deterministically converts a byte array into a ulong. + /// Pads the byte array to 8 bytes if necessary. + /// + /// Input byte array. + /// Deterministic ulong computed from the first 8 bytes. + private static ulong TestHashFunction(byte[] bytes) + { + byte[] padded = new byte[8]; + Array.Copy(bytes, padded, Math.Min(bytes.Length, 8)); + return BitConverter.ToUInt64(padded, 0); + } + + /// + /// Tests that adding a new integer element returns true and increments CountAdditions, + /// and that adding a duplicate element returns false while still incrementing CountAdditions. + /// + /// The integer value to test. + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(int.MinValue)] + [InlineData(int.MaxValue)] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void AddInt_DuplicateBehavior_UpdatesCountAdditionsAndReturnsExpected(int input) + { + // Arrange + var estimator = new CardinalityEstimator(TestHashFunction, 14, true); + ulong initialCountAdditions = estimator.CountAdditions; + // Act + bool firstAddResult = estimator.Add(input); + bool secondAddResult = estimator.Add(input); + ulong finalCountAdditions = estimator.CountAdditions; + // Assert + // First addition should modify state and return true. + Assert.True(firstAddResult); + // Second addition is a duplicate and should return false. + Assert.False(secondAddResult); + // CountAdditions increments irrespective of state change. + Assert.Equal(initialCountAdditions + 2, finalCountAdditions); + } + + /// + /// Tests that adding two distinct integer elements returns true for both additions + /// and that CountAdditions accurately reflects the number of addition attempts. + /// + [Fact] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void AddInt_DistinctElements_UpdatesCountAdditionsCorrectly() + { + // Arrange + var estimator = new CardinalityEstimator(TestHashFunction, 14, true); + ulong initialCountAdditions = estimator.CountAdditions; + int firstElement = 12345; + int secondElement = 54321; + // Act + bool firstAddResult = estimator.Add(firstElement); + bool secondAddResult = estimator.Add(secondElement); + ulong finalCountAdditions = estimator.CountAdditions; + // Assert + Assert.True(firstAddResult); + Assert.True(secondAddResult); + Assert.Equal(initialCountAdditions + 2, finalCountAdditions); + } + + /// + /// Tests the Add(uint) method for unique and duplicate elements. + /// Verifies that the estimator state is modified on the first call and not modified on a duplicate call, + /// and that CountAdditions is incremented for each Add call. + /// Uses a deterministic hash function for predictable behavior. + /// + /// The unsigned integer element to add. + [Theory] + [InlineData(0u)] + [InlineData(uint.MaxValue)] + [InlineData(12345u)] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void Add_UInt_UniqueAndDuplicateElements_ModifiesState(uint element) + { + // Arrange + // Use a simple deterministic hash function that converts the input bytes to UInt64. + GetHashCodeDelegate testHashFunction = (byte[] bytes) => BitConverter.ToUInt64(System.IO.Hashing.XxHash128.Hash(bytes)); + var estimator = new CardinalityEstimator(hashFunction: testHashFunction); + // Act + bool firstAddResult = estimator.Add(element); + bool secondAddResult = estimator.Add(element); + // Assert + Assert.True(firstAddResult); // First addition should modify state. + Assert.False(secondAddResult); // Duplicate addition should not modify state. + Assert.Equal(2UL, estimator.CountAdditions); // CountAdditions should increment on each call. + } + + /// + /// Placeholder test for Add(string) method. + /// This test is marked as skipped because the implementation content is stripped out. + /// + [Fact(Skip = "Not implemented due to stripped content")] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void Add_String_Test_NotImplemented() + { + // Arrange, Act, Assert to be implemented when method content is available. + } + + /// + /// Placeholder test for Add(int) method. + /// This test is marked as skipped because the implementation content is stripped out. + /// + [Fact(Skip = "Not implemented due to stripped content")] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void Add_Int_Test_NotImplemented() + { + // Arrange, Act, Assert to be implemented when method content is available. + } + + /// + /// Placeholder test for Add(long) method. + /// This test is marked as skipped because the implementation content is stripped out. + /// + [Fact(Skip = "Not implemented due to stripped content")] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void Add_Long_Test_NotImplemented() + { + // Arrange, Act, Assert to be implemented when method content is available. + } + + /// + /// Placeholder test for Add(ulong) method. + /// This test is marked as skipped because the implementation content is stripped out. + /// + [Fact(Skip = "Not implemented due to stripped content")] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void Add_ULong_Test_NotImplemented() + { + // Arrange, Act, Assert to be implemented when method content is available. + } + + /// + /// Placeholder test for Add(float) method. + /// This test is marked as skipped because the implementation content is stripped out. + /// + [Fact(Skip = "Not implemented due to stripped content")] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void Add_Float_Test_NotImplemented() + { + // Arrange, Act, Assert to be implemented when method content is available. + } + + /// + /// Placeholder test for Add(double) method. + /// This test is marked as skipped because the implementation content is stripped out. + /// + [Fact(Skip = "Not implemented due to stripped content")] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void Add_Double_Test_NotImplemented() + { + // Arrange, Act, Assert to be implemented when method content is available. + } + + /// + /// Placeholder test for Add(byte[]) method. + /// This test is marked as skipped because the implementation content is stripped out. + /// + [Fact(Skip = "Not implemented due to stripped content")] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void Add_ByteArray_Test_NotImplemented() + { + // Arrange, Act, Assert to be implemented when method content is available. + } + + /// + /// Tests that adding a new long element returns true and increments CountAdditions, + /// and that adding a duplicate element returns false while still incrementing CountAdditions. + /// + /// A long value to add to the estimator. + [Theory] + [InlineData(0L)] + [InlineData(long.MinValue)] + [InlineData(long.MaxValue)] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void Add_Long_UniqueAndDuplicate_ReturnsExpected(long value) + { + // Arrange: Create an estimator with a custom hash function for deterministic behavior. + CardinalityEstimator estimator = new CardinalityEstimator(hashFunction: (bytes) => + { + // Use BitConverter to obtain a ulong from the byte array. + return BitConverter.ToUInt64(bytes, 0); + }, b: 14, useDirectCounting: true); + // Act: Add the element for the first time. + bool firstAddResult = estimator.Add(value); + ulong countAfterFirstAdd = estimator.CountAdditions; + // Assert: The first addition should modify state (true) and CountAdditions becomes 1. + Assert.True(firstAddResult); + Assert.Equal(1UL, countAfterFirstAdd); + // Act: Add the same element again. + bool secondAddResult = estimator.Add(value); + ulong countAfterSecondAdd = estimator.CountAdditions; + // Assert: The second addition should not change the estimator's state (false) but CountAdditions is incremented. + Assert.False(secondAddResult); + Assert.Equal(2UL, countAfterSecondAdd); + } + + /// + /// Tests that adding two distinct long elements returns true for both additions and increments CountAdditions appropriately. + /// + /// The first long value to add. + /// The second distinct long value to add. + [Theory] + [InlineData(123L, 456L)] + [InlineData(0L, 1L)] + [InlineData(-100L, 100L)] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void Add_Long_DistinctValues_ReturnsTrue(long firstValue, long secondValue) + { + // Arrange: Create an estimator with a custom hash function. + CardinalityEstimator estimator = new CardinalityEstimator(hashFunction: (bytes) => + { + return BitConverter.ToUInt64(bytes, 0); + }, b: 14, useDirectCounting: true); + // Act: Add the first distinct element. + bool firstAddResult = estimator.Add(firstValue); + ulong countAfterFirstAdd = estimator.CountAdditions; + // Assert: First addition should return true. + Assert.True(firstAddResult); + Assert.Equal(1UL, countAfterFirstAdd); + // Act: Add the second distinct element. + bool secondAddResult = estimator.Add(secondValue); + ulong countAfterSecondAdd = estimator.CountAdditions; + // Assert: Second addition should also return true and CountAdditions equals 2. + Assert.True(secondAddResult); + Assert.Equal(2UL, countAfterSecondAdd); + } + + /// + /// Tests that adding a new ulong element returns true and increments CountAdditions. + /// Uses various edge case ulong values. + /// + /// The ulong element to add. + [Theory] + [InlineData(0UL)] + [InlineData(123UL)] + [InlineData(ulong.MaxValue)] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void AddUlong_NewElement_ReturnsTrueAndIncrementsCountAdditions(ulong element) + { + // Arrange: Use a deterministic hash function that returns the ulong represented by the byte array. + GetHashCodeDelegate testHashFunction = (byte[] bytes) => BitConverter.ToUInt64(System.IO.Hashing.XxHash128.Hash(bytes)); + var estimator = new CardinalityEstimator(testHashFunction); + ulong initialCountAdditions = estimator.CountAdditions; + // Act: add the element for the first time. + bool changed = estimator.Add(element); + // Assert: the element should be new, so changed is true. + Assert.True(changed); + Assert.Equal(initialCountAdditions + 1, estimator.CountAdditions); + } + + /// + /// Tests that adding a duplicate ulong element returns false but still increments CountAdditions. + /// Uses various edge case ulong values. + /// + /// The ulong element to add twice. + [Theory] + [InlineData(0UL)] + [InlineData(456UL)] + [InlineData(ulong.MaxValue)] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void AddUlong_DuplicateElement_ReturnsFalseButCountAdditionsIncrements(ulong element) + { + // Arrange: Use a deterministic hash function. + GetHashCodeDelegate testHashFunction = (byte[] bytes) => BitConverter.ToUInt64(System.IO.Hashing.XxHash128.Hash(bytes)); + var estimator = new CardinalityEstimator(testHashFunction); + // Act: Add the element the first time. + bool firstAdd = estimator.Add(element); + ulong countAfterFirst = estimator.CountAdditions; + // Act: Add the same element again. + bool secondAdd = estimator.Add(element); + ulong countAfterSecond = estimator.CountAdditions; + // Assert: first addition should modify state and return true. + Assert.True(firstAdd); + // Second addition should not modify the internal state, so returns false. + Assert.False(secondAdd); + // But CountAdditions is incremented on every add call. + Assert.Equal(countAfterFirst + 1, countAfterSecond); + } + + /// + /// Verifies that adding the same float element twice returns true the first time and false the second time, + /// and that CountAdditions is incremented for each addition. + /// + /// Test float value to add. + [Theory] + [InlineData(0f)] + [InlineData(1f)] + [InlineData(-1f)] + [InlineData(float.NaN)] + [InlineData(float.PositiveInfinity)] + [InlineData(float.NegativeInfinity)] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void AddFloat_SameElementTwice_ReturnsTrueThenFalse(float value) + { + // Arrange + var estimator = new CardinalityEstimator(); + // Act + bool firstAdd = estimator.Add(value); + bool secondAdd = estimator.Add(value); + // Assert + Assert.True(firstAdd); + Assert.False(secondAdd); + Assert.Equal(2UL, estimator.CountAdditions); + } + + /// + /// Verifies that adding two distinct float elements returns true for each addition, + /// and that CountAdditions correctly counts both additions. + /// + [Fact] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void AddFloat_DifferentElements_ReturnsTrueForEach() + { + // Arrange + var estimator = new CardinalityEstimator(); + float firstValue = 3.14f; + float secondValue = -2.71f; + // Act + bool firstResult = estimator.Add(firstValue); + bool secondResult = estimator.Add(secondValue); + // Assert + Assert.True(firstResult); + Assert.True(secondResult); + Assert.Equal(2UL, estimator.CountAdditions); + } + + /// + /// Tests that adding the same double element twice returns true on first add and false on duplicate add, + /// and that the CountAdditions property is incremented on each call. + /// + /// The double value to add, including edge cases such as NaN, infinities, and standard values. + [Theory] + [InlineData(0.0)] + [InlineData(1.23)] + [InlineData(-10.0)] + [InlineData(double.NaN)] + [InlineData(double.PositiveInfinity)] + [InlineData(double.NegativeInfinity)] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void Add_Double_SameElementTwice_ReturnsIdempotentResultAndIncrementsCountAdditions(double value) + { + // Arrange + // Provide a deterministic hash function based on BitConverter to ensure predictable behavior. + CardinalityEstimator estimator = new CardinalityEstimator(hashFunction: x => BitConverter.ToUInt64(System.IO.Hashing.XxHash128.Hash(x))); + // Act + bool firstAddResult = estimator.Add(value); + bool secondAddResult = estimator.Add(value); + // Assert + // The first addition should modify the estimator state. + Assert.True(firstAddResult); + // The duplicate addition should not modify the state. + Assert.False(secondAddResult); + // CountAdditions should be incremented on every call. + Assert.Equal(2UL, estimator.CountAdditions); + } + + /// + /// Tests that adding two distinct double elements returns true for both additions, + /// and that CountAdditions reflects the total number of addition attempts. + /// + [Fact] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void Add_Double_DistinctElements_ReturnsTrueAndIncrementsCountAdditions() + { + // Arrange + CardinalityEstimator estimator = new CardinalityEstimator(hashFunction: x => BitConverter.ToUInt64(System.IO.Hashing.XxHash128.Hash(x))); + double firstValue = 1.0; + double secondValue = 2.0; + // Act + bool firstAddResult = estimator.Add(firstValue); + bool secondAddResult = estimator.Add(secondValue); + // Assert + Assert.True(firstAddResult); + Assert.True(secondAddResult); + Assert.Equal(2UL, estimator.CountAdditions); + } + + /// + /// A fake hash function that computes a simple sum of the bytes. + /// This function simulates hashing such that duplicate byte arrays produce the same hash value. + /// + /// Input byte array. + /// A ulong hash computed as the sum of the bytes. + private static ulong FakeHash(byte[] data) + { + if (data == null) + { + throw new ArgumentNullException("data"); + } + + ulong sum = 0; + for (int i = 0; i < data.Length; i++) + { + sum += data[i]; + } + + return sum; + } + + /// + /// Tests that adding a new byte array element returns true and increments CountAdditions, + /// and that adding a duplicate byte array element returns false while still incrementing CountAdditions. + /// + [Fact] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void Add_ByteArray_NewAndDuplicateElements_ReturnsExpectedResult() + { + // Arrange + var estimator = new CardinalityEstimator(FakeHash); + byte[] element1 = new byte[] + { + 1, + 2, + 3 + }; // FakeHash returns 6 + byte[] elementDuplicate = new byte[] + { + 1, + 2, + 3 + }; // Same content, hash also 6 + byte[] elementDifferent = new byte[] + { + 1, + 2, + 4 + }; // FakeHash returns 7 + // Act + bool firstAdd = estimator.Add(element1); + bool duplicateAdd = estimator.Add(elementDuplicate); + bool differentAdd = estimator.Add(elementDifferent); + // Assert + // The first addition should modify the estimator's state. + Assert.True(firstAdd); + // Adding a duplicate element (same computed hash) should not modify the state. + Assert.False(duplicateAdd); + // Adding a distinct element should modify the state. + Assert.True(differentAdd); + // CountAdditions should reflect all Add calls. + Assert.Equal(3UL, estimator.CountAdditions); + } + + /// + /// Tests that adding an empty byte array works as expected. + /// Since FakeHash computes the sum of bytes (which will be 0 for an empty array), + /// adding multiple empty arrays should be treated as duplicates. + /// + [Fact] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void Add_ByteArray_EmptyArray_ReturnsExpectedResult() + { + // Arrange + var estimator = new CardinalityEstimator(FakeHash); + byte[] emptyArray = new byte[0]; + // Act + bool firstAdd = estimator.Add(emptyArray); + bool secondAdd = estimator.Add(emptyArray); + // Assert + // The first addition of an empty array should modify the state. + Assert.True(firstAdd); + // A subsequent addition should not modify the state. + Assert.False(secondAdd); + // CountAdditions should be incremented for each call. + Assert.Equal(2UL, estimator.CountAdditions); + } + + /// + /// Tests that adding a null byte array results in an ArgumentNullException. + /// This verifies that the hash function properly handles null input. + /// + [Fact] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void Add_ByteArray_NullElement_ThrowsArgumentNullException() + { + // Arrange + var estimator = new CardinalityEstimator(FakeHash); + byte[] nullArray = null; + // Act & Assert + Assert.Throws(() => estimator.Add(nullArray)); + } + + /// + /// Tests that a newly created CardinalityEstimator returns a count of zero. + /// This test verifies the behavior when no elements have been added. + /// + [Theory] + [InlineData(14, true)] + [InlineData(14, false)] + [InlineData(4, true)] + [InlineData(16, false)] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void Count_NewEstimator_ReturnsZero(int b, bool useDirectCounting) + { + // Arrange: Create a new estimator with given parameters. + var estimator = new CardinalityEstimator(hashFunction: null, b: b, useDirectCounting: useDirectCounting); + // Act: Get the count from the estimator. + ulong count = estimator.Count(); + // Assert: The count should be zero for a new estimator. + Assert.Equal(0UL, count); + } + + /// + /// Tests that the copy constructor produces an estimator with the same count as the original. + /// This test verifies that internal state related to count is correctly copied. + /// + [Theory] + [InlineData(14, true)] + [InlineData(14, false)] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void CopyConstructor_CopiesCountState(int b, bool useDirectCounting) + { + // Arrange: Create a new estimator and obtain its count. + var originalEstimator = new CardinalityEstimator(hashFunction: null, b: b, useDirectCounting: useDirectCounting); + ulong originalCount = originalEstimator.Count(); + // Act: Create a new estimator using the copy constructor. + var copiedEstimator = new CardinalityEstimator(originalEstimator); + ulong copiedCount = copiedEstimator.Count(); + // Assert: The copied estimator should have the same count as the original. + Assert.Equal(originalCount, copiedCount); + } + + /// + /// Tests that calling instance Merge with a null estimator throws an ArgumentNullException. + /// + [Fact] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void Merge_NullOther_ThrowsArgumentNullException() + { + // Arrange + var estimator = new CardinalityEstimator(); + // Act & Assert + Assert.Throws(() => estimator.Merge(null)); + } + + /// + /// Tests that merging two CardinalityEstimator instances with different accuracy settings throws an ArgumentOutOfRangeException. + /// + [Fact] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void Merge_IncompatibleEstimator_ThrowsArgumentOutOfRangeException() + { + // Arrange + // Create estimator with default b (14) and one with a different b (e.g., 10) + var estimator1 = new CardinalityEstimator(b: 14); + var estimator2 = new CardinalityEstimator(b: 10); + // Act & Assert + Assert.Throws(() => estimator1.Merge(estimator2)); + } + + /// + /// Tests that merging two estimators correctly sums up the CountAdditions property. + /// + [Fact] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void Merge_CountAdditions_SumsCorrectly() + { + // Arrange + var estimator1 = new CardinalityEstimator(); + // Simulate additions; assuming Add methods increment CountAdditions. + estimator1.Add(0); + estimator1.Add(0); + var estimator2 = new CardinalityEstimator(); + estimator2.Add(0); + // Act + estimator1.Merge(estimator2); + // Assert + // Expecting 3 total additions. + Assert.Equal(3UL, estimator1.CountAdditions); + } + + /// + /// Tests that the static Merge method returns null when passed a null parameter. + /// + [Fact] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void StaticMerge_NullParameter_ReturnsNull() + { + // Act + CardinalityEstimator merged = CardinalityEstimator.Merge(null); + // Assert + Assert.Null(merged); + } + + /// + /// Tests that merging a non-null CardinalityEstimator into another correctly combines the CountAdditions. + /// Arrange: Two estimators with specific additions are created. + /// Act: One estimator is merged into the other. + /// Assert: The merged estimator's CountAdditions equals the sum of both estimators' CountAdditions. + /// + [Fact] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void Merge_WithNonNullOther_AddsCountAdditions() + { + // Arrange + var estimator1 = new CardinalityEstimator(); + var estimator2 = new CardinalityEstimator(); + estimator1.Add(0); // Increments CountAdditions, expected to be 1. + estimator1.Add(0); // CountAdditions becomes 2. + estimator2.Add(0); // CountAdditions becomes 1. + // Act + estimator1.Merge(estimator2); + // Assert + Assert.Equal(3UL, estimator1.CountAdditions); + } + + /// + /// Tests that GetState returns the expected default state for a newly created estimator. + /// Verifies that: + /// - BitsPerIndex equals the provided b parameter. + /// - DirectCount is initialized as an empty collection. + /// - LookupSparse is initialized as an empty collection. + /// - LookupDense is null. + /// - CountAdditions is zero. + /// + [Fact] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void GetState_DefaultState_ReturnsExpectedState() + { + // Arrange + int bits = 10; + bool useDirectCounting = true; + CardinalityEstimator estimator = new CardinalityEstimator(b: bits, useDirectCounting: useDirectCounting); + // Act + var state = estimator.GetState(); + // Assert + Assert.NotNull(state); + Assert.Equal(bits, state.BitsPerIndex); + Assert.NotNull(state.DirectCount); + Assert.Empty(state.DirectCount); + Assert.NotNull(state.LookupSparse); + Assert.Empty(state.LookupSparse); + Assert.Null(state.LookupDense); + Assert.Equal(0UL, state.CountAdditions); + } + + /// + /// Tests that the copy constructor correctly reproduces the internal state. + /// After creating a copy, GetState should reflect the same values as in the original instance. + /// + [Fact] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void GetState_CopyConstructor_ReturnsIdenticalState() + { + // Arrange + int bits = 12; + bool useDirectCounting = false; + CardinalityEstimator estimator = new CardinalityEstimator(b: bits, useDirectCounting: useDirectCounting); + var originalState = estimator.GetState(); + // Act: create a copy using the copy constructor and retrieve its state. + CardinalityEstimator copyEstimator = new CardinalityEstimator(estimator); + var state = copyEstimator.GetState(); + // Assert + Assert.NotNull(state); + Assert.Equal(bits, state.BitsPerIndex); + Assert.Equal(estimator.CountAdditions, state.CountAdditions); + Assert.Equal(originalState.DirectCount, state.DirectCount); + Assert.NotNull(state.LookupSparse); + Assert.Empty(state.LookupSparse); + Assert.Null(state.LookupDense); + } + + /// + /// Tests the GetSigma method with various inputs to verify it returns the expected number of leading zeroes plus one. + /// + /// The hash value to test. + /// The number of bits to count. + /// The expected sigma value. + [Theory] + [InlineData(0UL, 50, 51)] + [InlineData(1UL, 50, 50)] + [InlineData(8UL, 50, 47)] + [InlineData(1125899906842623UL, 50, 1)] // (1UL << 50) - 1 + [InlineData(2251799813685248UL, 50, 51)] // (1UL << 51) with bitsToCount=50 results in masked=0 -> 64- (64-50)+1 = 51 + [InlineData(0UL, 64, 65)] + [InlineData(1UL, 64, 65)] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void GetSigma_VariousInputs_ReturnsExpectedSigma(ulong hash, byte bitsToCount, byte expectedSigma) + { + // Arrange is implicit + // Act + byte actualSigma = CardinalityEstimator.GetSigma(hash, bitsToCount); + // Assert + Assert.Equal(expectedSigma, actualSigma); + } + + /// + /// A dummy hash function returning a constant value. + /// + private static ulong DummyHash(byte[] bytes) + { + return 42UL; + } + + /// + /// A different dummy hash function returning a different constant value. + /// + private static ulong DifferentDummyHash(byte[] bytes) + { + return 84UL; + } + + /// + /// Tests that Equals returns false when the other instance is null. + /// + [Fact] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void Equals_Null_ReturnsFalse() + { + // Arrange + var estimator = new CardinalityEstimator(DummyHash, 14, true); + // Act + bool result = estimator.Equals(null); + // Assert + Assert.False(result); + } + + /// + /// Tests that Equals returns true when comparing an instance with itself. + /// + [Fact] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void Equals_SameInstance_ReturnsTrue() + { + // Arrange + var estimator = new CardinalityEstimator(DummyHash, 14, true); + // Act + bool result = estimator.Equals(estimator); + // Assert + Assert.True(result); + } + + /// + /// Tests that the copy constructor creates an instance equal to the original. + /// + [Fact] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void Equals_CopyConstructedInstances_ReturnTrue() + { + // Arrange + var original = new CardinalityEstimator(DummyHash, 14, true); + var copy = new CardinalityEstimator(original); + // Act + bool result = original.Equals(copy) && copy.Equals(original); + // Assert + Assert.True(result); + } + + /// + /// Tests that two instances with different configuration parameters are not equal. + /// + [Theory] + [InlineData(14, 15)] + [InlineData(15, 14)] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void Equals_DifferentConfiguration_ReturnsFalse(int b1, int b2) + { + // Arrange + var estimator1 = new CardinalityEstimator(DummyHash, b1, true); + var estimator2 = new CardinalityEstimator(DummyHash, b2, true); + // Act + bool result1 = estimator1.Equals(estimator2); + bool result2 = estimator2.Equals(estimator1); + // Assert + Assert.False(result1); + Assert.False(result2); + } + + /// + /// Tests that two instances with different hash functions are not equal. + /// + [Fact] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void Equals_DifferentHashFunction_ReturnsFalse() + { + // Arrange + var estimator1 = new CardinalityEstimator(DummyHash, 14, true); + var estimator2 = new CardinalityEstimator(DifferentDummyHash, 14, true); + // Act + bool result1 = estimator1.Equals(estimator2); + bool result2 = estimator2.Equals(estimator1); + // Assert + Assert.False(result1); + Assert.False(result2); + } } } \ No newline at end of file diff --git a/CardinalityEstimation.Test/Hash/Fnv1ATests.cs b/CardinalityEstimation.Test/Hash/Fnv1ATests.cs index ebd22dc..36bee31 100644 --- a/CardinalityEstimation.Test/Hash/Fnv1ATests.cs +++ b/CardinalityEstimation.Test/Hash/Fnv1ATests.cs @@ -1,4 +1,4 @@ -// /* +// /* // See https://github.com/saguiitay/CardinalityEstimation. // The MIT License (MIT) // @@ -23,11 +23,12 @@ // SOFTWARE. // */ +using System; +using CardinalityEstimation.Hash; +using Xunit; + namespace CardinalityEstimation.Test.Hash { - using CardinalityEstimation.Hash; - using Xunit; - public class Fnv1ATests { [Fact] @@ -38,5 +39,21 @@ public void Fnv1AProducesRightValues() Assert.Equal(1109817072422714760UL, Fnv1A.GetHashCode(new byte[] { 1, 2, 3, 4, 5 })); Assert.Equal(11047178588169845073UL, Fnv1A.GetHashCode(new byte[] { 255, 255, 255, 255 })); } + + /// + /// Tests that passing a null byte array to Fnv1A.GetHashCode throws a NullReferenceException. + /// Given that the method does not include null-checks, a null input should trigger a NullReferenceException. + /// + [Fact] + [Trait("Owner", "AI Testing Agent v0.1.0-alpha.25310.44+8471bbd")] + [Trait("Category", "auto-generated")] + public void GetHashCode_NullInput_ThrowsException() + { + // Arrange + byte[] nullBytes = null; + + // Act & Assert + Assert.Throws(() => Fnv1A.GetHashCode(nullBytes)); + } } } \ No newline at end of file diff --git a/CardinalityEstimation.Test/Hash/HashFunctionFactoryTests.cs b/CardinalityEstimation.Test/Hash/HashFunctionFactoryTests.cs deleted file mode 100644 index ee84526..0000000 --- a/CardinalityEstimation.Test/Hash/HashFunctionFactoryTests.cs +++ /dev/null @@ -1,47 +0,0 @@ -//// /* -//// See https://github.com/saguiitay/CardinalityEstimation. -//// The MIT License (MIT) -//// -//// Copyright (c) 2015 Microsoft -//// -//// Permission is hereby granted, free of charge, to any person obtaining a copy -//// of this software and associated documentation files (the "Software"), to deal -//// in the Software without restriction, including without limitation the rights -//// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//// copies of the Software, and to permit persons to whom the Software is -//// furnished to do so, subject to the following conditions: -//// -//// The above copyright notice and this permission notice shall be included in all -//// copies or substantial portions of the Software. -//// -//// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//// SOFTWARE. -//// */ - -//namespace CardinalityEstimation.Test.Hash -//{ -// using System; -// using System.Collections.Generic; -// using System.Linq; -// using CardinalityEstimation.Hash; -// using Xunit; - -// public class HashFunctionFactoryTests -// { -// [Fact] -// public void FactoryCanProduceAllHashFunctionTypes() -// { -// // Make sure factory can produce each HashFunctionId -// foreach (HashFunctionId hashFunctionId in Enum.GetValues(typeof (HashFunctionId))) -// { -// IHashFunction hashFunction = HashFunctionFactory.GetHashFunction(hashFunctionId); -// Assert.True(hashFunction != null, "Factory created a null hash function with ID" + hashFunctionId); -// } -// } -// } -//} \ No newline at end of file diff --git a/CardinalityEstimation.Test/Hash/Murmur3Tests.cs b/CardinalityEstimation.Test/Hash/Murmur3Tests.cs index 8906702..2952db6 100644 --- a/CardinalityEstimation.Test/Hash/Murmur3Tests.cs +++ b/CardinalityEstimation.Test/Hash/Murmur3Tests.cs @@ -1,4 +1,4 @@ -// /* +// /* // See https://github.com/saguiitay/CardinalityEstimation. // The MIT License (MIT) //