diff --git a/rueidisprob/slidingbloomfilter_test.go b/rueidisprob/slidingbloomfilter_test.go index f7b0e4f0..77627920 100644 --- a/rueidisprob/slidingbloomfilter_test.go +++ b/rueidisprob/slidingbloomfilter_test.go @@ -108,6 +108,60 @@ func TestNewSlidingBloomFilterError(t *testing.T) { t.Error("Error is not ErrFalsePositiveRateLessThanEqualZero") } }) + + t.Run("WindowSizeLessThanOneSecond", func(t *testing.T) { + client, flushAllAndClose, err := setupRedis7Cluster() + if err != nil { + t.Error(err) + } + defer func() { + err := flushAllAndClose() + if err != nil { + t.Error(err) + } + }() + + _, err = NewSlidingBloomFilter(client, "test", 100, 0.05, time.Millisecond) + if !errors.Is(err, ErrWindowSizeLessThanOneSecond) { + t.Error("Error is not ErrWindowSizeLessThanOneSecond") + } + }) + + t.Run("ZeroSize", func(t *testing.T) { + client, flushAllAndClose, err := setupRedis7Cluster() + if err != nil { + t.Error(err) + } + defer func() { + err := flushAllAndClose() + if err != nil { + t.Error(err) + } + }() + + _, err = NewSlidingBloomFilter(client, "test", 0, 0.05, time.Minute) + if !errors.Is(err, ErrBitsSizeZero) { + t.Error("Error is not ErrBitsSizeZero") + } + }) + + t.Run("TooLargeSize", func(t *testing.T) { + client, flushAllAndClose, err := setupRedis7Cluster() + if err != nil { + t.Error(err) + } + defer func() { + err := flushAllAndClose() + if err != nil { + t.Error(err) + } + }() + + _, err = NewSlidingBloomFilter(client, "test", 1<<33, 0.05, time.Minute) + if !errors.Is(err, ErrBitsSizeTooLarge) { + t.Error("Error is not ErrBitsSizeTooLarge") + } + }) } func TestSlidingBloomFilterAdd(t *testing.T) { @@ -655,3 +709,331 @@ func TestSlidingBloomFilterConcurrentRotation(t *testing.T) { t.Logf("Test ran for %v with %d clients", time.Since(startTime), numClients) } + +func TestSlidingBloomFilterCount(t *testing.T) { + t.Run("count exists", func(t *testing.T) { + client, flushAllAndClose, err := setupRedis7Cluster() + if err != nil { + t.Error(err) + } + defer func() { + err := flushAllAndClose() + if err != nil { + t.Error(err) + } + }() + + bf, err := NewSlidingBloomFilter(client, "test", 100, 0.05, time.Minute) + if err != nil { + t.Error(err) + } + + err = bf.Add(context.Background(), "1") + if err != nil { + t.Error(err) + } + + count, err := bf.Count(context.Background()) + if err != nil { + t.Error(err) + } + if count != 1 { + t.Error("Count is not 1") + } + }) + + t.Run("count does not exist", func(t *testing.T) { + client, flushAllAndClose, err := setupRedis7Cluster() + if err != nil { + t.Error(err) + } + defer func() { + err := flushAllAndClose() + if err != nil { + t.Error(err) + } + }() + + bf, err := NewSlidingBloomFilter(client, "test", 100, 0.05, time.Minute) + if err != nil { + t.Error(err) + } + + count, err := bf.Count(context.Background()) + if err != nil { + t.Error(err) + } + if count != 0 { + t.Error("Count is not 0") + } + }) + + t.Run("add multiple items", func(t *testing.T) { + client, flushAllAndClose, err := setupRedis7Cluster() + if err != nil { + t.Error(err) + } + defer func() { + err := flushAllAndClose() + if err != nil { + t.Error(err) + } + }() + + bf, err := NewSlidingBloomFilter(client, "test", 100, 0.05, time.Minute) + if err != nil { + t.Error(err) + } + + keys := []string{"1", "2", "3"} + err = bf.AddMulti(context.Background(), keys) + if err != nil { + t.Error(err) + } + + count, err := bf.Count(context.Background()) + if err != nil { + t.Error(err) + } + if count != 3 { + t.Error("Count is not 3") + } + }) +} + +func TestSlidingBloomFilterCountError(t *testing.T) { + t.Run("count error", func(t *testing.T) { + client, flushAllAndClose, err := setupRedis7Cluster() + if err != nil { + t.Error(err) + } + defer func() { + err := flushAllAndClose() + if err != nil { + t.Error(err) + } + }() + + bf, err := NewSlidingBloomFilter(client, "test", 100, 0.05, time.Minute) + if err != nil { + t.Error(err) + } + + ctx, cancel := context.WithCancel(context.Background()) + cancel() + _, err = bf.Count(ctx) + if !errors.Is(err, context.Canceled) { + t.Error("Error is not context.Canceled") + } + }) + + t.Run("counter key is corrupted", func(t *testing.T) { + client, flushAllAndClose, err := setupRedis7Cluster() + if err != nil { + t.Error(err) + } + defer func() { + err := flushAllAndClose() + if err != nil { + t.Error(err) + } + }() + + bf, err := NewSlidingBloomFilter(client, "test", 100, 0.05, time.Minute) + if err != nil { + t.Error(err) + } + + resp := client.Do( + context.Background(), + client.B(). + Set(). + Key("{test}:c"). + Value("not a number"). + Build(), + ) + if resp.Error() != nil { + t.Error(resp.Error()) + } + + _, err = bf.Count(context.Background()) + if !errors.Is(err, strconv.ErrSyntax) { + t.Error("Error is not strconv.ErrSyntax") + } + }) +} + +func TestSlidingBloomFilterReset(t *testing.T) { + t.Run("reset exists", func(t *testing.T) { + client, flushAllAndClose, err := setupRedis7Cluster() + if err != nil { + t.Error(err) + } + defer func() { + err := flushAllAndClose() + if err != nil { + t.Error(err) + } + }() + + bf, err := NewSlidingBloomFilter(client, "test", 100, 0.05, time.Minute) + if err != nil { + t.Error(err) + } + + // Reload scripts + sbf := bf.(*slidingBloomFilter) + if err := sbf.initialize(); err != nil { + t.Fatalf("failed to initialize scripts: %v", err) + } + + // Add an element first + err = bf.Add(context.Background(), "1") + if err != nil { + t.Error(err) + } + + // Verify the element was added + exists, err := bf.Exists(context.Background(), "1") + if err != nil { + t.Error(err) + } + if !exists { + t.Error("Key should exist before reset") + } + + // Wait a bit before resetting + time.Sleep(100 * time.Millisecond) + + // Clean up first + err = bf.Delete(context.Background()) + if err != nil { + t.Error(err) + } + + // Wait a bit after resetting + time.Sleep(100 * time.Millisecond) + + // Create a new filter (same name) + bf, err = NewSlidingBloomFilter(client, "test", 100, 0.05, time.Minute) + if err != nil { + t.Error(err) + } + + // Verify the element does not exist after reset + exists, err = bf.Exists(context.Background(), "1") + if err != nil && !rueidis.IsRedisNil(err) { + t.Error(err) + } + if exists { + t.Error("Key should not exist after reset") + } + + // Verify the count is 0 after reset + count, err := bf.Count(context.Background()) + if err != nil && !rueidis.IsRedisNil(err) { + t.Error(err) + } + if count != 0 { + t.Errorf("Count should be 0 after reset, got %d", count) + } + }) + + t.Run("reset does not exist", func(t *testing.T) { + client, flushAllAndClose, err := setupRedis7Cluster() + if err != nil { + t.Error(err) + } + defer func() { + err := flushAllAndClose() + if err != nil { + t.Error(err) + } + }() + + bf, err := NewSlidingBloomFilter(client, "test", 100, 0.05, time.Minute) + if err != nil { + t.Error(err) + } + + // Reload scripts + err = bf.(*slidingBloomFilter).initialize() + if err != nil { + t.Fatalf("failed to initialize: %v", err) + } + + // Wait a bit before resetting + time.Sleep(100 * time.Millisecond) + + // Reset on empty filter + err = bf.Reset(context.Background()) + if err != nil && !rueidis.IsRedisNil(err) { + t.Error(err) + } + + // Wait a bit after resetting + time.Sleep(100 * time.Millisecond) + + // Verify the count is 0 after reset + count, err := bf.Count(context.Background()) + if err != nil && !rueidis.IsRedisNil(err) { + t.Error(err) + } + if count != 0 { + t.Errorf("Count should be 0 after reset, got %d", count) + } + }) +} + +func TestSlidingBloomFilterResetError(t *testing.T) { + client, flushAllAndClose, err := setupRedis7Cluster() + if err != nil { + t.Error(err) + } + defer func() { + err := flushAllAndClose() + if err != nil { + t.Error(err) + } + }() + + bf, err := NewSlidingBloomFilter(client, "test", 100, 0.05, time.Minute) + if err != nil { + t.Error(err) + } + + ctx, cancel := context.WithCancel(context.Background()) + cancel() + err = bf.Reset(ctx) + if !errors.Is(err, context.Canceled) { + t.Error("Error is not context.Canceled") + } +} + +func TestSlidingBloomFilterExistsMulti(t *testing.T) { + t.Run("error case", func(t *testing.T) { + client, flushAllAndClose, err := setupRedis7Cluster() + if err != nil { + t.Error(err) + } + defer func() { + err := flushAllAndClose() + if err != nil { + t.Error(err) + } + }() + + bf, err := NewSlidingBloomFilter(client, "test", 100, 0.05, time.Minute) + if err != nil { + t.Error(err) + } + + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + _, err = bf.ExistsMulti(ctx, []string{"1", "2", "3"}) + if !errors.Is(err, context.Canceled) { + t.Error("Expected context.Canceled error") + } + }) +}