Initial commit
This commit is contained in:
959
internal/compatibility/goredis_test.go
Normal file
959
internal/compatibility/goredis_test.go
Normal file
@@ -0,0 +1,959 @@
|
||||
package compatibility
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// TestGoRedisBasicSubscribe tests basic SUBSCRIBE functionality
|
||||
func TestGoRedisBasicSubscribe(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: ts.Addr(),
|
||||
})
|
||||
defer func() { _ = rdb.Close() }()
|
||||
|
||||
pubsub := rdb.Subscribe(ctx, "test-channel")
|
||||
defer func() { _ = pubsub.Close() }()
|
||||
|
||||
// Channel() returns a channel that receives messages (not subscription confirmations)
|
||||
// The subscription confirmation is handled internally by go-redis
|
||||
// We just need to verify we can get the channel without errors
|
||||
ch := pubsub.Channel()
|
||||
|
||||
// Publish a message to verify subscription works
|
||||
pub := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = pub.Close() }()
|
||||
|
||||
err := pub.Publish(ctx, "test-channel", "test-msg").Err()
|
||||
if err != nil {
|
||||
t.Fatalf("Publish failed: %v", err)
|
||||
}
|
||||
|
||||
// Now we should receive the message
|
||||
msg, ok := waitForMessage(ch, 2*time.Second)
|
||||
if !ok {
|
||||
t.Fatal("Timeout waiting for message")
|
||||
}
|
||||
|
||||
if msg.Channel != "test-channel" {
|
||||
t.Errorf("Expected channel 'test-channel', got '%s'", msg.Channel)
|
||||
}
|
||||
if msg.Payload != "test-msg" {
|
||||
t.Errorf("Expected 'test-msg', got '%s'", msg.Payload)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGoRedisBasicPublish tests basic PUBLISH functionality
|
||||
func TestGoRedisBasicPublish(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Subscriber
|
||||
sub := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = sub.Close() }()
|
||||
pubsub := sub.Subscribe(ctx, "test-pub")
|
||||
defer func() { _ = pubsub.Close() }()
|
||||
ch := pubsub.Channel()
|
||||
|
||||
// Publisher
|
||||
pub := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = pub.Close() }()
|
||||
|
||||
// Publish message
|
||||
err := pub.Publish(ctx, "test-pub", "hello world").Err()
|
||||
if err != nil {
|
||||
t.Fatalf("Publish failed: %v", err)
|
||||
}
|
||||
|
||||
// Receive message
|
||||
msg, ok := waitForMessage(ch, 2*time.Second)
|
||||
if !ok {
|
||||
t.Fatal("Timeout waiting for message")
|
||||
}
|
||||
|
||||
if msg.Payload != "hello world" {
|
||||
t.Errorf("Expected 'hello world', got '%s'", msg.Payload)
|
||||
}
|
||||
if msg.Channel != "test-pub" {
|
||||
t.Errorf("Expected channel 'test-pub', got '%s'", msg.Channel)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGoRedisMultipleSubscribers tests multiple clients subscribing to same channel
|
||||
func TestGoRedisMultipleSubscribers(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
numSubscribers := 5
|
||||
|
||||
// Create multiple subscribers
|
||||
var subscribers []*redis.Client
|
||||
var pubsubs []*redis.PubSub
|
||||
var channels []<-chan *redis.Message
|
||||
|
||||
for i := 0; i < numSubscribers; i++ {
|
||||
rdb := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
subscribers = append(subscribers, rdb)
|
||||
|
||||
ps := rdb.Subscribe(ctx, "multi-sub")
|
||||
pubsubs = append(pubsubs, ps)
|
||||
channels = append(channels, ps.Channel())
|
||||
}
|
||||
|
||||
defer func() {
|
||||
for _, ps := range pubsubs {
|
||||
_ = ps.Close()
|
||||
}
|
||||
for _, rdb := range subscribers {
|
||||
_ = rdb.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
// Give subscriptions time to register
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Publisher
|
||||
pub := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = pub.Close() }()
|
||||
|
||||
// Publish message
|
||||
err := pub.Publish(ctx, "multi-sub", "broadcast").Err()
|
||||
if err != nil {
|
||||
t.Fatalf("Publish failed: %v", err)
|
||||
}
|
||||
|
||||
// All subscribers should receive
|
||||
for i, ch := range channels {
|
||||
msg, ok := waitForMessage(ch, 2*time.Second)
|
||||
if !ok {
|
||||
t.Errorf("Subscriber %d timeout", i)
|
||||
continue
|
||||
}
|
||||
if msg.Payload != "broadcast" {
|
||||
t.Errorf("Subscriber %d got wrong payload: %s", i, msg.Payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestGoRedisUnsubscribe tests UNSUBSCRIBE functionality
|
||||
func TestGoRedisUnsubscribe(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
rdb := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = rdb.Close() }()
|
||||
|
||||
pubsub := rdb.Subscribe(ctx, "unsub-test")
|
||||
defer func() { _ = pubsub.Close() }()
|
||||
|
||||
// Verify subscription by publishing and receiving
|
||||
ch := pubsub.Channel()
|
||||
pub := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = pub.Close() }()
|
||||
|
||||
err := pub.Publish(ctx, "unsub-test", "msg").Err()
|
||||
if err != nil {
|
||||
t.Fatalf("Publish failed: %v", err)
|
||||
}
|
||||
|
||||
_, ok := waitForMessage(ch, 2*time.Second)
|
||||
if !ok {
|
||||
t.Fatal("Timeout waiting for message")
|
||||
}
|
||||
|
||||
// Unsubscribe
|
||||
err = pubsub.Unsubscribe(ctx, "unsub-test")
|
||||
if err != nil {
|
||||
t.Fatalf("Unsubscribe failed: %v", err)
|
||||
}
|
||||
|
||||
// After unsubscribe, should not receive messages
|
||||
// Give time for unsubscribe to process
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
// TestGoRedisPatternSubscribe tests PSUBSCRIBE functionality
|
||||
func TestGoRedisPatternSubscribe(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
rdb := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = rdb.Close() }()
|
||||
|
||||
pubsub := rdb.PSubscribe(ctx, "news.*")
|
||||
defer func() { _ = pubsub.Close() }()
|
||||
|
||||
// Get the channel for receiving messages
|
||||
ch := pubsub.Channel()
|
||||
|
||||
// Publish to a matching channel to verify subscription
|
||||
pub := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = pub.Close() }()
|
||||
|
||||
err := pub.Publish(ctx, "news.sports", "game results").Err()
|
||||
if err != nil {
|
||||
t.Fatalf("Publish failed: %v", err)
|
||||
}
|
||||
|
||||
// Should receive the message
|
||||
msg, ok := waitForMessage(ch, 2*time.Second)
|
||||
if !ok {
|
||||
t.Fatal("Timeout waiting for pattern message")
|
||||
}
|
||||
|
||||
if msg.Payload != "game results" {
|
||||
t.Errorf("Expected 'game results', got '%s'", msg.Payload)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGoRedisPatternMatch tests pattern matching with PUBLISH
|
||||
func TestGoRedisPatternMatch(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Subscriber with pattern
|
||||
sub := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = sub.Close() }()
|
||||
pubsub := sub.PSubscribe(ctx, "news.*")
|
||||
defer func() { _ = pubsub.Close() }()
|
||||
ch := pubsub.Channel()
|
||||
|
||||
// Publisher
|
||||
pub := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = pub.Close() }()
|
||||
|
||||
// Publish to matching channel
|
||||
err := pub.Publish(ctx, "news.sports", "game results").Err()
|
||||
if err != nil {
|
||||
t.Fatalf("Publish failed: %v", err)
|
||||
}
|
||||
|
||||
// Receive pattern message
|
||||
msg, ok := waitForMessage(ch, 2*time.Second)
|
||||
if !ok {
|
||||
t.Fatal("Timeout waiting for pattern message")
|
||||
}
|
||||
|
||||
if msg.Payload != "game results" {
|
||||
t.Errorf("Expected 'game results', got '%s'", msg.Payload)
|
||||
}
|
||||
if msg.Channel != "news.sports" {
|
||||
t.Errorf("Expected channel 'news.sports', got '%s'", msg.Channel)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGoRedisMultiplePatterns tests subscribing to multiple patterns
|
||||
func TestGoRedisMultiplePatterns(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
rdb := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = rdb.Close() }()
|
||||
|
||||
pubsub := rdb.PSubscribe(ctx, "news.*", "sports.*", "weather.*")
|
||||
defer func() { _ = pubsub.Close() }()
|
||||
|
||||
// Get channel and verify subscription by publishing
|
||||
ch := pubsub.Channel()
|
||||
pub := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = pub.Close() }()
|
||||
|
||||
// Publish to each pattern
|
||||
_ = pub.Publish(ctx, "news.test", "news-msg").Err()
|
||||
_ = pub.Publish(ctx, "sports.test", "sports-msg").Err()
|
||||
_ = pub.Publish(ctx, "weather.test", "weather-msg").Err()
|
||||
|
||||
// Should receive 3 messages
|
||||
for i := 0; i < 3; i++ {
|
||||
_, ok := waitForMessage(ch, 2*time.Second)
|
||||
if !ok {
|
||||
t.Fatalf("Timeout waiting for message %d", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestGoRedisMultipleChannels tests subscribing to multiple channels
|
||||
func TestGoRedisMultipleChannels(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
rdb := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = rdb.Close() }()
|
||||
|
||||
pubsub := rdb.Subscribe(ctx, "ch1", "ch2", "ch3")
|
||||
defer func() { _ = pubsub.Close() }()
|
||||
|
||||
// Verify by publishing to each channel
|
||||
ch := pubsub.Channel()
|
||||
pub := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = pub.Close() }()
|
||||
|
||||
_ = pub.Publish(ctx, "ch1", "msg1").Err()
|
||||
_ = pub.Publish(ctx, "ch2", "msg2").Err()
|
||||
_ = pub.Publish(ctx, "ch3", "msg3").Err()
|
||||
|
||||
// Should receive 3 messages
|
||||
for i := 0; i < 3; i++ {
|
||||
_, ok := waitForMessage(ch, 2*time.Second)
|
||||
if !ok {
|
||||
t.Fatalf("Timeout waiting for channel %d message", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestGoRedisPing tests PING command in non-pubsub mode
|
||||
func TestGoRedisPing(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
rdb := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = rdb.Close() }()
|
||||
|
||||
result, err := rdb.Ping(ctx).Result()
|
||||
if err != nil {
|
||||
t.Fatalf("Ping failed: %v", err)
|
||||
}
|
||||
|
||||
if result != "PONG" {
|
||||
t.Errorf("Expected 'PONG', got '%s'", result)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGoRedisPingWithMessage tests PING with message in non-pubsub mode
|
||||
func TestGoRedisPingWithMessage(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
rdb := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = rdb.Close() }()
|
||||
|
||||
result, err := rdb.Do(ctx, "PING", "hello").Text()
|
||||
if err != nil {
|
||||
t.Fatalf("Ping failed: %v", err)
|
||||
}
|
||||
|
||||
if result != "hello" {
|
||||
t.Errorf("Expected 'hello', got '%s'", result)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGoRedisPubSubNumsub tests PUBSUB NUMSUB command
|
||||
func TestGoRedisPubSubNumsub(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Create subscribers
|
||||
sub1 := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = sub1.Close() }()
|
||||
ps1 := sub1.Subscribe(ctx, "channel1")
|
||||
defer func() { _ = ps1.Close() }()
|
||||
|
||||
sub2 := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = sub2.Close() }()
|
||||
ps2 := sub2.Subscribe(ctx, "channel1", "channel2")
|
||||
defer func() { _ = ps2.Close() }()
|
||||
|
||||
// Wait for subscriptions
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Use regular client for PUBSUB command
|
||||
pub := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = pub.Close() }()
|
||||
|
||||
result, err := pub.Do(ctx, "PUBSUB", "NUMSUB", "channel1", "channel2").Result()
|
||||
if err != nil {
|
||||
t.Fatalf("PUBSUB NUMSUB failed: %v", err)
|
||||
}
|
||||
|
||||
// Result should be an array with channel,count pairs
|
||||
t.Logf("PUBSUB NUMSUB result: %v", result)
|
||||
}
|
||||
|
||||
// TestGoRedisPubSubChannels tests PUBSUB CHANNELS command
|
||||
func TestGoRedisPubSubChannels(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Subscribe to channels
|
||||
sub := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = sub.Close() }()
|
||||
ps := sub.Subscribe(ctx, "test-cha-1", "test-cha-2")
|
||||
defer func() { _ = ps.Close() }()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Query channels
|
||||
pub := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = pub.Close() }()
|
||||
|
||||
result, err := pub.Do(ctx, "PUBSUB", "CHANNELS").Result()
|
||||
if err != nil {
|
||||
t.Fatalf("PUBSUB CHANNELS failed: %v", err)
|
||||
}
|
||||
t.Logf("PUBSUB CHANNELS result: %v", result)
|
||||
}
|
||||
|
||||
// TestGoRedisPubSubNumpat tests PUBSUB NUMPAT command
|
||||
func TestGoRedisPubSubNumpat(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Create pattern subscribers
|
||||
sub1 := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = sub1.Close() }()
|
||||
ps1 := sub1.PSubscribe(ctx, "foo.*")
|
||||
defer func() { _ = ps1.Close() }()
|
||||
|
||||
sub2 := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = sub2.Close() }()
|
||||
ps2 := sub2.PSubscribe(ctx, "bar.*")
|
||||
defer func() { _ = ps2.Close() }()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
pub := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = pub.Close() }()
|
||||
|
||||
result, err := pub.Do(ctx, "PUBSUB", "NUMPAT").Result()
|
||||
if err != nil {
|
||||
t.Fatalf("PUBSUB NUMPAT failed: %v", err)
|
||||
}
|
||||
t.Logf("PUBSUB NUMPAT result: %v", result)
|
||||
}
|
||||
|
||||
// TestGoRedisBinarySafe tests binary-safe message passing
|
||||
func TestGoRedisBinarySafe(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Subscriber
|
||||
sub := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = sub.Close() }()
|
||||
pubsub := sub.Subscribe(ctx, "binary-test")
|
||||
defer func() { _ = pubsub.Close() }()
|
||||
ch := pubsub.Channel()
|
||||
|
||||
// Publisher
|
||||
pub := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = pub.Close() }()
|
||||
|
||||
// Binary message with null bytes and special chars
|
||||
binaryMsg := []byte{0x00, 0x01, 0x02, 0xFF, 0xFE, '\r', '\n', 0x00}
|
||||
|
||||
err := pub.Publish(ctx, "binary-test", string(binaryMsg)).Err()
|
||||
if err != nil {
|
||||
t.Fatalf("Publish binary failed: %v", err)
|
||||
}
|
||||
|
||||
msg, ok := waitForMessage(ch, 2*time.Second)
|
||||
if !ok {
|
||||
t.Fatal("Timeout waiting for binary message")
|
||||
}
|
||||
|
||||
if msg.Payload != string(binaryMsg) {
|
||||
t.Errorf("Binary message mismatch: got %v, want %v", []byte(msg.Payload), binaryMsg)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGoRedisEmptyMessage tests publishing empty message
|
||||
func TestGoRedisEmptyMessage(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Subscriber
|
||||
sub := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = sub.Close() }()
|
||||
pubsub := sub.Subscribe(ctx, "empty-test")
|
||||
defer func() { _ = pubsub.Close() }()
|
||||
ch := pubsub.Channel()
|
||||
|
||||
// Publisher
|
||||
pub := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = pub.Close() }()
|
||||
|
||||
err := pub.Publish(ctx, "empty-test", "").Err()
|
||||
if err != nil {
|
||||
t.Fatalf("Publish empty failed: %v", err)
|
||||
}
|
||||
|
||||
msg, ok := waitForMessage(ch, 2*time.Second)
|
||||
if !ok {
|
||||
t.Fatal("Timeout waiting for empty message")
|
||||
}
|
||||
|
||||
if msg.Payload != "" {
|
||||
t.Errorf("Expected empty payload, got '%s'", msg.Payload)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGoRedisLongMessage tests publishing long message
|
||||
func TestGoRedisLongMessage(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Subscriber
|
||||
sub := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = sub.Close() }()
|
||||
pubsub := sub.Subscribe(ctx, "long-test")
|
||||
defer func() { _ = pubsub.Close() }()
|
||||
ch := pubsub.Channel()
|
||||
|
||||
// Publisher
|
||||
pub := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = pub.Close() }()
|
||||
|
||||
// Create 1MB message
|
||||
longMsg := make([]byte, 1024*1024)
|
||||
for i := range longMsg {
|
||||
longMsg[i] = byte(i % 256)
|
||||
}
|
||||
|
||||
err := pub.Publish(ctx, "long-test", string(longMsg)).Err()
|
||||
if err != nil {
|
||||
t.Fatalf("Publish long failed: %v", err)
|
||||
}
|
||||
|
||||
msg, ok := waitForMessage(ch, 5*time.Second)
|
||||
if !ok {
|
||||
t.Fatal("Timeout waiting for long message")
|
||||
}
|
||||
|
||||
if len(msg.Payload) != len(longMsg) {
|
||||
t.Errorf("Message length mismatch: got %d, want %d", len(msg.Payload), len(longMsg))
|
||||
}
|
||||
}
|
||||
|
||||
// TestGoRedisConcurrentSubscribers tests concurrent subscription/unsubscription
|
||||
func TestGoRedisConcurrentSubscribers(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// Concurrent subscribe/unsubscribe
|
||||
for i := 0; i < 20; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = rdb.Close() }()
|
||||
|
||||
ps := rdb.Subscribe(ctx, fmt.Sprintf("concurrent-%d", id%5))
|
||||
defer func() { _ = ps.Close() }()
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// TestGoRedisChannelWithSpecialChars tests channel names with special characters
|
||||
func TestGoRedisChannelWithSpecialChars(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
channels := []string{
|
||||
"channel-with-dash",
|
||||
"channel_with_underscore",
|
||||
"channel.with.dots",
|
||||
"channel:with:colons",
|
||||
"channel/with/slash",
|
||||
}
|
||||
|
||||
for _, channelName := range channels {
|
||||
t.Run(channelName, func(t *testing.T) {
|
||||
// Subscriber
|
||||
sub := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = sub.Close() }()
|
||||
ps := sub.Subscribe(ctx, channelName)
|
||||
defer func() { _ = ps.Close() }()
|
||||
ch := ps.Channel()
|
||||
|
||||
// Publisher
|
||||
pub := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = pub.Close() }()
|
||||
|
||||
err := pub.Publish(ctx, channelName, "test").Err()
|
||||
if err != nil {
|
||||
t.Fatalf("Publish to %s failed: %v", channelName, err)
|
||||
}
|
||||
|
||||
msg, ok := waitForMessage(ch, 2*time.Second)
|
||||
if !ok {
|
||||
t.Fatalf("Timeout waiting for message on %s", channelName)
|
||||
}
|
||||
|
||||
if msg.Channel != channelName {
|
||||
t.Errorf("Channel mismatch: got %s, want %s", msg.Channel, channelName)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestGoRedisUnicodeMessages tests Unicode message content
|
||||
func TestGoRedisUnicodeMessages(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
messages := []string{
|
||||
"Hello, 世界!",
|
||||
"Emoji test: 🎉🚀✨",
|
||||
"Cyrillic: Привет мир",
|
||||
"Arabic: مرحبا بالعالم",
|
||||
"Japanese: こんにちは世界",
|
||||
}
|
||||
|
||||
for _, msg := range messages {
|
||||
t.Run(msg[:min(20, len(msg))], func(t *testing.T) {
|
||||
// Subscriber
|
||||
sub := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = sub.Close() }()
|
||||
ps := sub.Subscribe(ctx, "unicode-test")
|
||||
defer func() { _ = ps.Close() }()
|
||||
ch := ps.Channel()
|
||||
|
||||
// Publisher
|
||||
pub := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = pub.Close() }()
|
||||
|
||||
err := pub.Publish(ctx, "unicode-test", msg).Err()
|
||||
if err != nil {
|
||||
t.Fatalf("Publish failed: %v", err)
|
||||
}
|
||||
|
||||
received, ok := waitForMessage(ch, 2*time.Second)
|
||||
if !ok {
|
||||
t.Fatal("Timeout waiting for Unicode message")
|
||||
}
|
||||
|
||||
if received.Payload != msg {
|
||||
t.Errorf("Unicode mismatch: got %q, want %q", received.Payload, msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestGoRedisPublishNoSubscribers tests PUBLISH with no subscribers
|
||||
func TestGoRedisPublishNoSubscribers(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
rdb := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = rdb.Close() }()
|
||||
|
||||
count, err := rdb.Publish(ctx, "no-subs-channel", "message").Result()
|
||||
if err != nil {
|
||||
t.Fatalf("Publish failed: %v", err)
|
||||
}
|
||||
|
||||
if count != 0 {
|
||||
t.Errorf("Expected 0 receivers, got %d", count)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGoRedisSubscribeReturnCount tests that SUBSCRIBE returns correct count
|
||||
func TestGoRedisSubscribeReturnCount(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
rdb := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = rdb.Close() }()
|
||||
|
||||
ps := rdb.Subscribe(ctx, "ch1")
|
||||
defer func() { _ = ps.Close() }()
|
||||
|
||||
// The subscription is confirmed internally by go-redis
|
||||
// Verify by publishing a message
|
||||
ch := ps.Channel()
|
||||
pub := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = pub.Close() }()
|
||||
|
||||
err := pub.Publish(ctx, "ch1", "test").Err()
|
||||
if err != nil {
|
||||
t.Fatalf("Publish failed: %v", err)
|
||||
}
|
||||
|
||||
_, ok := waitForMessage(ch, 2*time.Second)
|
||||
if !ok {
|
||||
t.Fatal("Timeout")
|
||||
}
|
||||
}
|
||||
|
||||
// TestGoRedisConcurrentPublishSubscribe tests concurrent pub/sub operations
|
||||
func TestGoRedisConcurrentPublishSubscribe(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
numMessages := 100
|
||||
numSubscribers := 5
|
||||
|
||||
var mu sync.Mutex
|
||||
receivedCounts := make([]int, numSubscribers)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// Start subscribers
|
||||
for i := 0; i < numSubscribers; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = rdb.Close() }()
|
||||
|
||||
ps := rdb.Subscribe(ctx, "concurrent-pub-test")
|
||||
defer func() { _ = ps.Close() }()
|
||||
ch := ps.Channel()
|
||||
|
||||
for j := 0; j < numMessages; j++ {
|
||||
_, ok := waitForMessage(ch, 5*time.Second)
|
||||
if ok {
|
||||
mu.Lock()
|
||||
receivedCounts[id]++
|
||||
mu.Unlock()
|
||||
}
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
// Give subscribers time to connect
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
// Publisher
|
||||
pub := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = pub.Close() }()
|
||||
|
||||
for i := 0; i < numMessages; i++ {
|
||||
err := pub.Publish(ctx, "concurrent-pub-test", fmt.Sprintf("msg-%d", i)).Err()
|
||||
if err != nil {
|
||||
t.Errorf("Publish %d failed: %v", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// Verify all subscribers received all messages
|
||||
totalReceived := 0
|
||||
for i, count := range receivedCounts {
|
||||
totalReceived += count
|
||||
if count < numMessages-1 { // Allow 1 message loss due to timing
|
||||
t.Logf("Subscriber %d received %d/%d messages", i, count, numMessages)
|
||||
}
|
||||
}
|
||||
|
||||
t.Logf("Total messages received: %d/%d", totalReceived, numMessages*numSubscribers)
|
||||
}
|
||||
|
||||
// TestGoRedisSubscribePatternThenChannel tests mixing pattern and channel subscriptions
|
||||
func TestGoRedisSubscribePatternThenChannel(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
rdb := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = rdb.Close() }()
|
||||
|
||||
// Subscribe to pattern first
|
||||
ps := rdb.PSubscribe(ctx, "test.*")
|
||||
defer func() { _ = ps.Close() }()
|
||||
ch := ps.Channel()
|
||||
|
||||
// Subscribe to a regular channel
|
||||
err := ps.Subscribe(ctx, "direct-channel")
|
||||
if err != nil {
|
||||
t.Fatalf("Subscribe to channel failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify both subscriptions work
|
||||
pub := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = pub.Close() }()
|
||||
|
||||
_ = pub.Publish(ctx, "test.foo", "pattern-msg").Err()
|
||||
_ = pub.Publish(ctx, "direct-channel", "channel-msg").Err()
|
||||
|
||||
// Should receive both messages
|
||||
for i := 0; i < 2; i++ {
|
||||
_, ok := waitForMessage(ch, 2*time.Second)
|
||||
if !ok {
|
||||
t.Fatalf("Timeout waiting for message %d", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestGoRedisUnsubscribeNoChannel tests UNSUBSCRIBE when not subscribed
|
||||
func TestGoRedisUnsubscribeNoChannel(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
rdb := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = rdb.Close() }()
|
||||
|
||||
ps := rdb.Subscribe(ctx, "temp-channel")
|
||||
defer func() { _ = ps.Close() }()
|
||||
|
||||
// Verify subscription works
|
||||
ch := ps.Channel()
|
||||
pub := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = pub.Close() }()
|
||||
|
||||
_ = pub.Publish(ctx, "temp-channel", "test").Err()
|
||||
_, ok := waitForMessage(ch, 2*time.Second)
|
||||
if !ok {
|
||||
t.Fatal("Timeout")
|
||||
}
|
||||
|
||||
// Unsubscribe
|
||||
err := ps.Unsubscribe(ctx, "temp-channel")
|
||||
if err != nil {
|
||||
t.Fatalf("Unsubscribe failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGoRedisPunsubscribe tests PUNSUBSCRIBE functionality
|
||||
func TestGoRedisPunsubscribe(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
rdb := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = rdb.Close() }()
|
||||
|
||||
ps := rdb.PSubscribe(ctx, "pattern.*")
|
||||
defer func() { _ = ps.Close() }()
|
||||
|
||||
// Verify subscription
|
||||
ch := ps.Channel()
|
||||
pub := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = pub.Close() }()
|
||||
|
||||
_ = pub.Publish(ctx, "pattern.test", "msg").Err()
|
||||
_, ok := waitForMessage(ch, 2*time.Second)
|
||||
if !ok {
|
||||
t.Fatal("Timeout")
|
||||
}
|
||||
|
||||
// Punsubscribe
|
||||
err := ps.PUnsubscribe(ctx, "pattern.*")
|
||||
if err != nil {
|
||||
t.Fatalf("PUnsubscribe failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGoRedisMixedPubSub tests a mix of channel and pattern subscriptions
|
||||
func TestGoRedisMixedPubSub(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Subscriber 1: regular channel
|
||||
sub1 := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = sub1.Close() }()
|
||||
ps1 := sub1.Subscribe(ctx, "mixed-channel")
|
||||
defer func() { _ = ps1.Close() }()
|
||||
|
||||
// Subscriber 2: pattern
|
||||
sub2 := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = sub2.Close() }()
|
||||
ps2 := sub2.PSubscribe(ctx, "mixed*")
|
||||
defer func() { _ = ps2.Close() }()
|
||||
|
||||
// Wait for subscriptions
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Publisher
|
||||
pub := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = pub.Close() }()
|
||||
|
||||
// Publish to "mixed-channel" - both should receive
|
||||
err := pub.Publish(ctx, "mixed-channel", "hello").Err()
|
||||
if err != nil {
|
||||
t.Fatalf("Publish failed: %v", err)
|
||||
}
|
||||
|
||||
ch1 := ps1.Channel()
|
||||
ch2 := ps2.Channel()
|
||||
|
||||
msg1, ok := waitForMessage(ch1, 2*time.Second)
|
||||
if !ok {
|
||||
t.Error("Channel subscriber timeout")
|
||||
} else if msg1.Payload != "hello" {
|
||||
t.Errorf("Wrong payload: %s", msg1.Payload)
|
||||
}
|
||||
|
||||
msg2, ok := waitForMessage(ch2, 2*time.Second)
|
||||
if !ok {
|
||||
t.Error("Pattern subscriber timeout")
|
||||
} else if msg2.Payload != "hello" {
|
||||
t.Errorf("Wrong payload: %s", msg2.Payload)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGoRedisQuit tests QUIT command properly closes connection
|
||||
func TestGoRedisQuit(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
rdb := redis.NewClient(&redis.Options{Addr: ts.Addr()})
|
||||
defer func() { _ = rdb.Close() }()
|
||||
|
||||
// Use Do to send QUIT command directly (Quit method may not be implemented in all versions)
|
||||
result, err := rdb.Do(ctx, "QUIT").Text()
|
||||
if err != nil {
|
||||
t.Fatalf("Quit failed: %v", err)
|
||||
}
|
||||
if result != "OK" {
|
||||
t.Errorf("Expected 'OK', got '%s'", result)
|
||||
}
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
118
internal/compatibility/helper_test.go
Normal file
118
internal/compatibility/helper_test.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package compatibility
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/eleksir/pekoe/internal/config"
|
||||
"github.com/eleksir/pekoe/internal/pubsub"
|
||||
"github.com/eleksir/pekoe/internal/server"
|
||||
)
|
||||
|
||||
// testServer manages a test server instance for compatibility tests
|
||||
type testServer struct {
|
||||
cfg *config.Config
|
||||
server *server.Server
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
wg sync.WaitGroup
|
||||
addr string
|
||||
}
|
||||
|
||||
// startTestServer starts a pekoe server on a random available port for testing
|
||||
func startTestServer(t *testing.T) *testServer {
|
||||
t.Helper()
|
||||
|
||||
// Find an available port
|
||||
port := findAvailablePort(t)
|
||||
|
||||
cfg := &config.Config{
|
||||
Bind: "127.0.0.1",
|
||||
Port: port,
|
||||
MaxClients: 100,
|
||||
ReadTimeout: 0,
|
||||
WriteTimeout: 0,
|
||||
MaxMsgBytes: 10 * 1024 * 1024, // 10MB
|
||||
LogLevel: "error", // Suppress logs in tests
|
||||
}
|
||||
cfg.SetDefaults()
|
||||
|
||||
hub := pubsub.NewHub()
|
||||
srv := server.New(cfg, hub)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ts := &testServer{
|
||||
cfg: cfg,
|
||||
server: srv,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
addr: cfg.Addr(),
|
||||
}
|
||||
|
||||
// Start server in background
|
||||
ts.wg.Add(1)
|
||||
go func() {
|
||||
defer ts.wg.Done()
|
||||
_ = srv.Run()
|
||||
}()
|
||||
|
||||
// Wait for server to be ready
|
||||
ts.waitForReady(t)
|
||||
|
||||
return ts
|
||||
}
|
||||
|
||||
// waitForReady blocks until the server accepts connections
|
||||
func (ts *testServer) waitForReady(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
deadline := time.Now().Add(5 * time.Second)
|
||||
for time.Now().Before(deadline) {
|
||||
conn, err := net.DialTimeout("tcp", ts.addr, 100*time.Millisecond)
|
||||
if err == nil {
|
||||
_ = conn.Close()
|
||||
return
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
t.Fatalf("Server at %s not ready after 5 seconds", ts.addr)
|
||||
}
|
||||
|
||||
// Addr returns the server address for client connections
|
||||
func (ts *testServer) Addr() string {
|
||||
return ts.addr
|
||||
}
|
||||
|
||||
// Close shuts down the test server
|
||||
func (ts *testServer) Close() {
|
||||
ts.cancel()
|
||||
// Give time for graceful shutdown
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
// findAvailablePort finds an available TCP port for testing
|
||||
func findAvailablePort(t *testing.T) int {
|
||||
t.Helper()
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to find available port: %v", err)
|
||||
}
|
||||
port := listener.Addr().(*net.TCPAddr).Port
|
||||
_ = listener.Close()
|
||||
return port
|
||||
}
|
||||
|
||||
// waitForMessage waits for a message on a channel with timeout
|
||||
func waitForMessage[T any](ch <-chan T, timeout time.Duration) (T, bool) {
|
||||
select {
|
||||
case msg := <-ch:
|
||||
return msg, true
|
||||
case <-time.After(timeout):
|
||||
var zero T
|
||||
return zero, false
|
||||
}
|
||||
}
|
||||
833
internal/compatibility/redigo_test.go
Normal file
833
internal/compatibility/redigo_test.go
Normal file
@@ -0,0 +1,833 @@
|
||||
package compatibility
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gomodule/redigo/redis"
|
||||
)
|
||||
|
||||
// dialRedis creates a new redigo connection for testing
|
||||
func dialRedis(t *testing.T, addr string) redis.Conn {
|
||||
t.Helper()
|
||||
conn, err := redis.Dial("tcp", addr,
|
||||
redis.DialConnectTimeout(5*time.Second),
|
||||
redis.DialReadTimeout(5*time.Second),
|
||||
redis.DialWriteTimeout(5*time.Second),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to dial: %v", err)
|
||||
}
|
||||
return conn
|
||||
}
|
||||
|
||||
// TestRedigoBasicSubscribe tests basic SUBSCRIBE functionality
|
||||
func TestRedigoBasicSubscribe(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
conn := dialRedis(t, ts.Addr())
|
||||
defer func() { _ = conn.Close() }()
|
||||
|
||||
// Send SUBSCRIBE
|
||||
err := conn.Send("SUBSCRIBE", "rigo-test-channel")
|
||||
if err != nil {
|
||||
t.Fatalf("Send failed: %v", err)
|
||||
}
|
||||
_ = conn.Flush()
|
||||
|
||||
// Read subscription confirmation
|
||||
msg, err := redis.Values(conn.Receive())
|
||||
if err != nil {
|
||||
t.Fatalf("Receive failed: %v", err)
|
||||
}
|
||||
|
||||
// Should be ["subscribe", "channel", count]
|
||||
if len(msg) != 3 {
|
||||
t.Fatalf("Expected 3 elements, got %d", len(msg))
|
||||
}
|
||||
|
||||
msgType, _ := redis.String(msg[0], nil)
|
||||
if msgType != "subscribe" {
|
||||
t.Errorf("Expected 'subscribe', got '%s'", msgType)
|
||||
}
|
||||
|
||||
channel, _ := redis.String(msg[1], nil)
|
||||
if channel != "rigo-test-channel" {
|
||||
t.Errorf("Expected 'rigo-test-channel', got '%s'", channel)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRedigoBasicPublish tests basic PUBLISH functionality
|
||||
func TestRedigoBasicPublish(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
// Subscriber
|
||||
subConn := dialRedis(t, ts.Addr())
|
||||
defer func() { _ = subConn.Close() }()
|
||||
|
||||
err := subConn.Send("SUBSCRIBE", "rigo-pub-test")
|
||||
if err != nil {
|
||||
t.Fatalf("Send failed: %v", err)
|
||||
}
|
||||
_ = subConn.Flush()
|
||||
|
||||
// Consume subscription confirmation
|
||||
_, err = redis.Values(subConn.Receive())
|
||||
if err != nil {
|
||||
t.Fatalf("Receive subscription failed: %v", err)
|
||||
}
|
||||
|
||||
// Publisher
|
||||
pubConn := dialRedis(t, ts.Addr())
|
||||
defer func() { _ = pubConn.Close() }()
|
||||
|
||||
reply, err := redis.Int(pubConn.Do("PUBLISH", "rigo-pub-test", "hello redigo"))
|
||||
if err != nil {
|
||||
t.Fatalf("Publish failed: %v", err)
|
||||
}
|
||||
|
||||
t.Logf("Published to %d subscribers", reply)
|
||||
|
||||
// Receive message
|
||||
msg, err := redis.Values(subConn.Receive())
|
||||
if err != nil {
|
||||
t.Fatalf("Receive message failed: %v", err)
|
||||
}
|
||||
|
||||
// Should be ["message", "channel", payload]
|
||||
if len(msg) != 3 {
|
||||
t.Fatalf("Expected 3 elements, got %d", len(msg))
|
||||
}
|
||||
|
||||
msgType, _ := redis.String(msg[0], nil)
|
||||
if msgType != "message" {
|
||||
t.Errorf("Expected 'message', got '%s'", msgType)
|
||||
}
|
||||
|
||||
payload, _ := redis.String(msg[2], nil)
|
||||
if payload != "hello redigo" {
|
||||
t.Errorf("Expected 'hello redigo', got '%s'", payload)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRedigoMultipleSubscribers tests multiple subscribers
|
||||
func TestRedigoMultipleSubscribers(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
numSubs := 3
|
||||
conns := make([]redis.Conn, numSubs)
|
||||
|
||||
// Create subscribers
|
||||
for i := 0; i < numSubs; i++ {
|
||||
conn := dialRedis(t, ts.Addr())
|
||||
conns[i] = conn
|
||||
|
||||
err := conn.Send("SUBSCRIBE", "rigo-multi")
|
||||
if err != nil {
|
||||
t.Fatalf("Send failed for %d: %v", i, err)
|
||||
}
|
||||
_ = conn.Flush()
|
||||
|
||||
// Consume confirmation
|
||||
_, err = redis.Values(conn.Receive())
|
||||
if err != nil {
|
||||
t.Fatalf("Receive confirmation failed for %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
for _, c := range conns {
|
||||
_ = c.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
// Publisher
|
||||
pubConn := dialRedis(t, ts.Addr())
|
||||
defer func() { _ = pubConn.Close() }()
|
||||
|
||||
_, err := pubConn.Do("PUBLISH", "rigo-multi", "broadcast")
|
||||
if err != nil {
|
||||
t.Fatalf("Publish failed: %v", err)
|
||||
}
|
||||
|
||||
// All should receive
|
||||
for i, conn := range conns {
|
||||
msg, err := redis.Values(conn.Receive())
|
||||
if err != nil {
|
||||
t.Errorf("Subscriber %d failed to receive: %v", i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
payload, _ := redis.String(msg[2], nil)
|
||||
if payload != "broadcast" {
|
||||
t.Errorf("Subscriber %d got wrong payload: %s", i, payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestRedigoUnsubscribe tests UNSUBSCRIBE functionality
|
||||
func TestRedigoUnsubscribe(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
conn := dialRedis(t, ts.Addr())
|
||||
defer func() { _ = conn.Close() }()
|
||||
|
||||
// Subscribe
|
||||
err := conn.Send("SUBSCRIBE", "rigo-unsub-test")
|
||||
if err != nil {
|
||||
t.Fatalf("Send failed: %v", err)
|
||||
}
|
||||
_ = conn.Flush()
|
||||
|
||||
// Consume confirmation
|
||||
_, err = redis.Values(conn.Receive())
|
||||
if err != nil {
|
||||
t.Fatalf("Receive failed: %v", err)
|
||||
}
|
||||
|
||||
// Unsubscribe
|
||||
err = conn.Send("UNSUBSCRIBE", "rigo-unsub-test")
|
||||
if err != nil {
|
||||
t.Fatalf("Send unsubscribe failed: %v", err)
|
||||
}
|
||||
_ = conn.Flush()
|
||||
|
||||
// Receive unsubscribe confirmation
|
||||
msg, err := redis.Values(conn.Receive())
|
||||
if err != nil {
|
||||
t.Fatalf("Receive unsubscribe confirmation failed: %v", err)
|
||||
}
|
||||
|
||||
msgType, _ := redis.String(msg[0], nil)
|
||||
if msgType != "unsubscribe" {
|
||||
t.Errorf("Expected 'unsubscribe', got '%s'", msgType)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRedigoPatternSubscribe tests PSUBSCRIBE functionality
|
||||
func TestRedigoPatternSubscribe(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
conn := dialRedis(t, ts.Addr())
|
||||
defer func() { _ = conn.Close() }()
|
||||
|
||||
// PSubscribe
|
||||
err := conn.Send("PSUBSCRIBE", "rigo-news.*")
|
||||
if err != nil {
|
||||
t.Fatalf("Send failed: %v", err)
|
||||
}
|
||||
_ = conn.Flush()
|
||||
|
||||
// Receive confirmation
|
||||
msg, err := redis.Values(conn.Receive())
|
||||
if err != nil {
|
||||
t.Fatalf("Receive failed: %v", err)
|
||||
}
|
||||
|
||||
msgType, _ := redis.String(msg[0], nil)
|
||||
if msgType != "psubscribe" {
|
||||
t.Errorf("Expected 'psubscribe', got '%s'", msgType)
|
||||
}
|
||||
|
||||
pattern, _ := redis.String(msg[1], nil)
|
||||
if pattern != "rigo-news.*" {
|
||||
t.Errorf("Expected 'rigo-news.*', got '%s'", pattern)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRedigoPatternMatch tests pattern matching with PUBLISH
|
||||
func TestRedigoPatternMatch(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
// Subscriber with pattern
|
||||
subConn := dialRedis(t, ts.Addr())
|
||||
defer func() { _ = subConn.Close() }()
|
||||
|
||||
err := subConn.Send("PSUBSCRIBE", "rigo-pattern.*")
|
||||
if err != nil {
|
||||
t.Fatalf("Send failed: %v", err)
|
||||
}
|
||||
_ = subConn.Flush()
|
||||
|
||||
// Consume confirmation
|
||||
_, err = redis.Values(subConn.Receive())
|
||||
if err != nil {
|
||||
t.Fatalf("Receive confirmation failed: %v", err)
|
||||
}
|
||||
|
||||
t.Log("Pattern subscription confirmed, now publishing...")
|
||||
|
||||
// Publisher
|
||||
pubConn := dialRedis(t, ts.Addr())
|
||||
defer func() { _ = pubConn.Close() }()
|
||||
|
||||
reply, err := redis.Int(pubConn.Do("PUBLISH", "rigo-pattern.match", "pattern message"))
|
||||
if err != nil {
|
||||
t.Fatalf("Publish failed: %v", err)
|
||||
}
|
||||
t.Logf("Publish returned: %d", reply)
|
||||
|
||||
// Receive pmessage
|
||||
msg, err := redis.Values(subConn.Receive())
|
||||
if err != nil {
|
||||
t.Fatalf("Receive failed: %v", err)
|
||||
}
|
||||
|
||||
// Should be ["pmessage", "pattern", "channel", "payload"]
|
||||
if len(msg) != 4 {
|
||||
t.Fatalf("Expected 4 elements, got %d", len(msg))
|
||||
}
|
||||
|
||||
msgType, _ := redis.String(msg[0], nil)
|
||||
if msgType != "pmessage" {
|
||||
t.Errorf("Expected 'pmessage', got '%s'", msgType)
|
||||
}
|
||||
|
||||
pattern, _ := redis.String(msg[1], nil)
|
||||
if pattern != "rigo-pattern.*" {
|
||||
t.Errorf("Expected pattern 'rigo-pattern.*', got '%s'", pattern)
|
||||
}
|
||||
|
||||
channel, _ := redis.String(msg[2], nil)
|
||||
if channel != "rigo-pattern.match" {
|
||||
t.Errorf("Expected channel 'rigo-pattern.match', got '%s'", channel)
|
||||
}
|
||||
|
||||
payload, _ := redis.String(msg[3], nil)
|
||||
if payload != "pattern message" {
|
||||
t.Errorf("Expected 'pattern message', got '%s'", payload)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRedigoMultiplePatterns tests subscribing to multiple patterns
|
||||
func TestRedigoMultiplePatterns(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
conn := dialRedis(t, ts.Addr())
|
||||
defer func() { _ = conn.Close() }()
|
||||
|
||||
patterns := []string{"rigo-pat1.*", "rigo-pat2.*", "rigo-pat3.*"}
|
||||
|
||||
for _, pattern := range patterns {
|
||||
err := conn.Send("PSUBSCRIBE", pattern)
|
||||
if err != nil {
|
||||
t.Fatalf("Send failed: %v", err)
|
||||
}
|
||||
_ = conn.Flush()
|
||||
|
||||
// Consume confirmation
|
||||
_, err = redis.Values(conn.Receive())
|
||||
if err != nil {
|
||||
t.Fatalf("Receive confirmation failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestRedigoPing tests PING command
|
||||
func TestRedigoPing(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
conn := dialRedis(t, ts.Addr())
|
||||
defer func() { _ = conn.Close() }()
|
||||
|
||||
result, err := redis.String(conn.Do("PING"))
|
||||
if err != nil {
|
||||
t.Fatalf("Ping failed: %v", err)
|
||||
}
|
||||
|
||||
if result != "PONG" {
|
||||
t.Errorf("Expected 'PONG', got '%s'", result)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRedigoPingWithMessage tests PING with message
|
||||
func TestRedigoPingWithMessage(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
conn := dialRedis(t, ts.Addr())
|
||||
defer func() { _ = conn.Close() }()
|
||||
|
||||
result, err := redis.String(conn.Do("PING", "hello"))
|
||||
if err != nil {
|
||||
t.Fatalf("Ping failed: %v", err)
|
||||
}
|
||||
|
||||
if result != "hello" {
|
||||
t.Errorf("Expected 'hello', got '%s'", result)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRedigoBinarySafe tests binary-safe messages
|
||||
func TestRedigoBinarySafe(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
// Subscriber
|
||||
subConn := dialRedis(t, ts.Addr())
|
||||
defer func() { _ = subConn.Close() }()
|
||||
|
||||
err := subConn.Send("SUBSCRIBE", "rigo-binary")
|
||||
if err != nil {
|
||||
t.Fatalf("Send failed: %v", err)
|
||||
}
|
||||
_ = subConn.Flush()
|
||||
|
||||
_, err = redis.Values(subConn.Receive())
|
||||
if err != nil {
|
||||
t.Fatalf("Receive confirmation failed: %v", err)
|
||||
}
|
||||
|
||||
// Publisher
|
||||
pubConn := dialRedis(t, ts.Addr())
|
||||
defer func() { _ = pubConn.Close() }()
|
||||
|
||||
binaryMsg := []byte{0x00, 0x01, 0x02, 0xFF, 0xFE, '\r', '\n', 0x00}
|
||||
|
||||
_, err = pubConn.Do("PUBLISH", "rigo-binary", binaryMsg)
|
||||
if err != nil {
|
||||
t.Fatalf("Publish failed: %v", err)
|
||||
}
|
||||
|
||||
// Receive message
|
||||
msg, err := redis.Values(subConn.Receive())
|
||||
if err != nil {
|
||||
t.Fatalf("Receive failed: %v", err)
|
||||
}
|
||||
|
||||
payload, _ := redis.String(msg[2], nil)
|
||||
if []byte(payload)[0] != binaryMsg[0] {
|
||||
t.Errorf("Binary message mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
// TestRedigisEmptyMessage tests empty message
|
||||
func TestRedigisEmptyMessage(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
// Subscriber
|
||||
subConn := dialRedis(t, ts.Addr())
|
||||
defer func() { _ = subConn.Close() }()
|
||||
|
||||
err := subConn.Send("SUBSCRIBE", "rigo-empty")
|
||||
if err != nil {
|
||||
t.Fatalf("Send failed: %v", err)
|
||||
}
|
||||
_ = subConn.Flush()
|
||||
|
||||
_, err = redis.Values(subConn.Receive())
|
||||
if err != nil {
|
||||
t.Fatalf("Receive confirmation failed: %v", err)
|
||||
}
|
||||
|
||||
// Publisher
|
||||
pubConn := dialRedis(t, ts.Addr())
|
||||
defer func() { _ = pubConn.Close() }()
|
||||
|
||||
_, err = pubConn.Do("PUBLISH", "rigo-empty", "")
|
||||
if err != nil {
|
||||
t.Fatalf("Publish failed: %v", err)
|
||||
}
|
||||
|
||||
// Receive message
|
||||
msg, err := redis.Values(subConn.Receive())
|
||||
if err != nil {
|
||||
t.Fatalf("Receive failed: %v", err)
|
||||
}
|
||||
|
||||
payload, _ := redis.String(msg[2], nil)
|
||||
if payload != "" {
|
||||
t.Errorf("Expected empty payload, got '%s'", payload)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRedigoLongMessage tests long message
|
||||
func TestRedigoLongMessage(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
// Subscriber
|
||||
subConn := dialRedis(t, ts.Addr())
|
||||
defer func() { _ = subConn.Close() }()
|
||||
|
||||
err := subConn.Send("SUBSCRIBE", "rigo-long")
|
||||
if err != nil {
|
||||
t.Fatalf("Send failed: %v", err)
|
||||
}
|
||||
_ = subConn.Flush()
|
||||
|
||||
_, err = redis.Values(subConn.Receive())
|
||||
if err != nil {
|
||||
t.Fatalf("Receive confirmation failed: %v", err)
|
||||
}
|
||||
|
||||
// Publisher
|
||||
pubConn := dialRedis(t, ts.Addr())
|
||||
defer func() { _ = pubConn.Close() }()
|
||||
|
||||
// 100KB message
|
||||
longMsg := make([]byte, 100*1024)
|
||||
for i := range longMsg {
|
||||
longMsg[i] = byte(i % 256)
|
||||
}
|
||||
|
||||
_, err = pubConn.Do("PUBLISH", "rigo-long", longMsg)
|
||||
if err != nil {
|
||||
t.Fatalf("Publish failed: %v", err)
|
||||
}
|
||||
|
||||
// Receive message
|
||||
msg, err := redis.Values(subConn.Receive())
|
||||
if err != nil {
|
||||
t.Fatalf("Receive failed: %v", err)
|
||||
}
|
||||
|
||||
payload, _ := redis.String(msg[2], nil)
|
||||
if len(payload) != len(longMsg) {
|
||||
t.Errorf("Message length mismatch: got %d, want %d", len(payload), len(longMsg))
|
||||
}
|
||||
}
|
||||
|
||||
// TestRedigoConcurrentSubscribers tests concurrent operations
|
||||
func TestRedigoConcurrentSubscribers(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
|
||||
conn := dialRedis(t, ts.Addr())
|
||||
defer func() { _ = conn.Close() }()
|
||||
|
||||
channel := fmt.Sprintf("rigo-concurrent-%d", id%3)
|
||||
err := conn.Send("SUBSCRIBE", channel)
|
||||
if err != nil {
|
||||
t.Errorf("Send failed: %v", err)
|
||||
return
|
||||
}
|
||||
_ = conn.Flush()
|
||||
|
||||
_, err = redis.Values(conn.Receive())
|
||||
if err != nil {
|
||||
t.Errorf("Receive failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// TestRedigoPublishNoSubscribers tests PUBLISH with no subscribers
|
||||
func TestRedigoPublishNoSubscribers(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
conn := dialRedis(t, ts.Addr())
|
||||
defer func() { _ = conn.Close() }()
|
||||
|
||||
count, err := redis.Int(conn.Do("PUBLISH", "rigo-no-subs", "message"))
|
||||
if err != nil {
|
||||
t.Fatalf("Publish failed: %v", err)
|
||||
}
|
||||
|
||||
if count != 0 {
|
||||
t.Errorf("Expected 0 receivers, got %d", count)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRedigoUnicodeMessages tests Unicode messages
|
||||
func TestRedigoUnicodeMessages(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
messages := []string{
|
||||
"Hello, 世界!",
|
||||
"Emoji: 🎉",
|
||||
"Cyrillic: Привет",
|
||||
}
|
||||
|
||||
for _, testMsg := range messages {
|
||||
t.Run(testMsg[:min(20, len(testMsg))], func(t *testing.T) {
|
||||
// Subscriber
|
||||
subConn := dialRedis(t, ts.Addr())
|
||||
defer func() { _ = subConn.Close() }()
|
||||
|
||||
err := subConn.Send("SUBSCRIBE", "rigo-unicode")
|
||||
if err != nil {
|
||||
t.Fatalf("Send failed: %v", err)
|
||||
}
|
||||
_ = subConn.Flush()
|
||||
|
||||
_, err = redis.Values(subConn.Receive())
|
||||
if err != nil {
|
||||
t.Fatalf("Receive confirmation failed: %v", err)
|
||||
}
|
||||
|
||||
// Publisher
|
||||
pubConn := dialRedis(t, ts.Addr())
|
||||
defer func() { _ = pubConn.Close() }()
|
||||
|
||||
_, err = pubConn.Do("PUBLISH", "rigo-unicode", testMsg)
|
||||
if err != nil {
|
||||
t.Fatalf("Publish failed: %v", err)
|
||||
}
|
||||
|
||||
// Receive
|
||||
msg, err := redis.Values(subConn.Receive())
|
||||
if err != nil {
|
||||
t.Fatalf("Receive failed: %v", err)
|
||||
}
|
||||
|
||||
payload, _ := redis.String(msg[2], nil)
|
||||
if payload != testMsg {
|
||||
t.Errorf("Unicode mismatch: got %q, want %q", payload, testMsg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestRedigoChannelWithSpecialChars tests channels with special characters
|
||||
func TestRedigoChannelWithSpecialChars(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
channels := []string{
|
||||
"rigo-with-dash",
|
||||
"rigo_with_underscore",
|
||||
"rigo.with.dots",
|
||||
"rigo:with:colons",
|
||||
}
|
||||
|
||||
for _, channelName := range channels {
|
||||
t.Run(channelName, func(t *testing.T) {
|
||||
// Subscriber
|
||||
subConn := dialRedis(t, ts.Addr())
|
||||
defer func() { _ = subConn.Close() }()
|
||||
|
||||
err := subConn.Send("SUBSCRIBE", channelName)
|
||||
if err != nil {
|
||||
t.Fatalf("Send failed: %v", err)
|
||||
}
|
||||
_ = subConn.Flush()
|
||||
|
||||
_, err = redis.Values(subConn.Receive())
|
||||
if err != nil {
|
||||
t.Fatalf("Receive confirmation failed: %v", err)
|
||||
}
|
||||
|
||||
// Publisher
|
||||
pubConn := dialRedis(t, ts.Addr())
|
||||
defer func() { _ = pubConn.Close() }()
|
||||
|
||||
_, err = pubConn.Do("PUBLISH", channelName, "test")
|
||||
if err != nil {
|
||||
t.Fatalf("Publish failed: %v", err)
|
||||
}
|
||||
|
||||
// Receive
|
||||
msg, err := redis.Values(subConn.Receive())
|
||||
if err != nil {
|
||||
t.Fatalf("Receive failed: %v", err)
|
||||
}
|
||||
|
||||
channel, _ := redis.String(msg[1], nil)
|
||||
if channel != channelName {
|
||||
t.Errorf("Channel mismatch: got %s, want %s", channel, channelName)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestRedigoMixedSubscribePSubscribe tests mixing SUBSCRIBE and PSUBSCRIBE
|
||||
// nolint: cyclop
|
||||
func TestRedigoMixedSubscribePSubscribe(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
conn := dialRedis(t, ts.Addr())
|
||||
defer func() { _ = conn.Close() }()
|
||||
|
||||
// Subscribe to channel
|
||||
err := conn.Send("SUBSCRIBE", "rigo-mixed-ch")
|
||||
if err != nil {
|
||||
t.Fatalf("Send subscribe failed: %v", err)
|
||||
}
|
||||
_ = conn.Flush()
|
||||
|
||||
_, err = redis.Values(conn.Receive())
|
||||
if err != nil {
|
||||
t.Fatalf("Receive subscribe confirmation failed: %v", err)
|
||||
}
|
||||
|
||||
// Also subscribe to pattern
|
||||
err = conn.Send("PSUBSCRIBE", "rigo-mixed-*")
|
||||
if err != nil {
|
||||
t.Fatalf("Send psubscribe failed: %v", err)
|
||||
}
|
||||
_ = conn.Flush()
|
||||
|
||||
_, err = redis.Values(conn.Receive())
|
||||
if err != nil {
|
||||
t.Fatalf("Receive psubscribe confirmation failed: %v", err)
|
||||
}
|
||||
|
||||
// Publish to channel - should receive via channel subscription
|
||||
pubConn := dialRedis(t, ts.Addr())
|
||||
defer func() { _ = pubConn.Close() }()
|
||||
|
||||
_, err = pubConn.Do("PUBLISH", "rigo-mixed-ch", "channel msg")
|
||||
if err != nil {
|
||||
t.Fatalf("Publish failed: %v", err)
|
||||
}
|
||||
|
||||
msg, err := redis.Values(conn.Receive())
|
||||
if err != nil {
|
||||
t.Fatalf("Receive failed: %v", err)
|
||||
}
|
||||
|
||||
msgType, _ := redis.String(msg[0], nil)
|
||||
if msgType != "message" {
|
||||
t.Errorf("Expected 'message', got '%s'", msgType)
|
||||
}
|
||||
|
||||
// Publish to pattern-matching channel - should receive via pattern subscription
|
||||
_, err = pubConn.Do("PUBLISH", "rigo-mixed-pattern", "pattern msg")
|
||||
if err != nil {
|
||||
t.Fatalf("Publish failed: %v", err)
|
||||
}
|
||||
|
||||
msg, err = redis.Values(conn.Receive())
|
||||
if err != nil {
|
||||
t.Fatalf("Receive failed: %v", err)
|
||||
}
|
||||
|
||||
msgType, _ = redis.String(msg[0], nil)
|
||||
if msgType != "pmessage" {
|
||||
t.Errorf("Expected 'pmessage', got '%s'", msgType)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRedigoPunsubscribe tests PUNSUBSCRIBE functionality
|
||||
func TestRedigoPunsubscribe(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
conn := dialRedis(t, ts.Addr())
|
||||
defer func() { _ = conn.Close() }()
|
||||
|
||||
// PSubscribe
|
||||
err := conn.Send("PSUBSCRIBE", "rigo-punsub.*")
|
||||
if err != nil {
|
||||
t.Fatalf("Send failed: %v", err)
|
||||
}
|
||||
_ = conn.Flush()
|
||||
|
||||
_, err = redis.Values(conn.Receive())
|
||||
if err != nil {
|
||||
t.Fatalf("Receive confirmation failed: %v", err)
|
||||
}
|
||||
|
||||
// Punsubscribe
|
||||
err = conn.Send("PUNSUBSCRIBE", "rigo-punsub.*")
|
||||
if err != nil {
|
||||
t.Fatalf("Send punsubscribe failed: %v", err)
|
||||
}
|
||||
_ = conn.Flush()
|
||||
|
||||
msg, err := redis.Values(conn.Receive())
|
||||
if err != nil {
|
||||
t.Fatalf("Receive punsubscribe confirmation failed: %v", err)
|
||||
}
|
||||
|
||||
msgType, _ := redis.String(msg[0], nil)
|
||||
if msgType != "punsubscribe" {
|
||||
t.Errorf("Expected 'punsubscribe', got '%s'", msgType)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRedigoConcurrentPublishSubscribe tests concurrent pub/sub
|
||||
func TestRedigoConcurrentPublishSubscribe(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
numMessages := 50
|
||||
|
||||
// Subscriber
|
||||
subConn := dialRedis(t, ts.Addr())
|
||||
defer func() { _ = subConn.Close() }()
|
||||
|
||||
err := subConn.Send("SUBSCRIBE", "rigo-concurrent-pub")
|
||||
if err != nil {
|
||||
t.Fatalf("Send failed: %v", err)
|
||||
}
|
||||
_ = subConn.Flush()
|
||||
|
||||
_, err = redis.Values(subConn.Receive())
|
||||
if err != nil {
|
||||
t.Fatalf("Receive confirmation failed: %v", err)
|
||||
}
|
||||
|
||||
// Publisher in goroutine
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
pubConn := dialRedis(t, ts.Addr())
|
||||
defer func() { _ = pubConn.Close() }()
|
||||
|
||||
for i := 0; i < numMessages; i++ {
|
||||
_, err := pubConn.Do("PUBLISH", "rigo-concurrent-pub", fmt.Sprintf("msg-%d", i))
|
||||
if err != nil {
|
||||
t.Errorf("Publish %d failed: %v", i, err)
|
||||
return
|
||||
}
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
}()
|
||||
|
||||
// Receive messages
|
||||
received := 0
|
||||
timeout := time.After(10 * time.Second)
|
||||
for received < numMessages {
|
||||
select {
|
||||
case <-timeout:
|
||||
t.Logf("Received %d/%d messages", received, numMessages)
|
||||
wg.Wait()
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
msg, err := redis.Values(subConn.Receive())
|
||||
if err != nil {
|
||||
t.Errorf("Receive failed: %v", err)
|
||||
break
|
||||
}
|
||||
|
||||
msgType, _ := redis.String(msg[0], nil)
|
||||
if msgType == "message" {
|
||||
received++
|
||||
}
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
t.Logf("Received all %d messages", received)
|
||||
}
|
||||
217
internal/compatibility/rueidis_test.go
Normal file
217
internal/compatibility/rueidis_test.go
Normal file
@@ -0,0 +1,217 @@
|
||||
package compatibility
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/redis/rueidis"
|
||||
)
|
||||
|
||||
// newRueidisClient creates a rueidis client with proper options for pekoe
|
||||
func newRueidisClient(t *testing.T, addr string) rueidis.Client {
|
||||
t.Helper()
|
||||
client, err := rueidis.NewClient(rueidis.ClientOption{
|
||||
InitAddress: []string{addr},
|
||||
DisableCache: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create rueidis client: %v", err)
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
// TestRueidisPing tests PING command
|
||||
func TestRueidisPing(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
client := newRueidisClient(t, ts.Addr())
|
||||
defer client.Close()
|
||||
|
||||
result, err := client.Do(ctx, client.B().Ping().Build()).ToString()
|
||||
if err != nil {
|
||||
t.Fatalf("Ping failed: %v", err)
|
||||
}
|
||||
|
||||
if result != "PONG" {
|
||||
t.Errorf("Expected 'PONG', got '%s'", result)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRueidisPingWithMessage tests PING with message
|
||||
func TestRueidisPingWithMessage(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
client := newRueidisClient(t, ts.Addr())
|
||||
defer client.Close()
|
||||
|
||||
result, err := client.Do(ctx, client.B().Ping().Message("hello").Build()).ToString()
|
||||
if err != nil {
|
||||
t.Fatalf("Ping failed: %v", err)
|
||||
}
|
||||
|
||||
if result != "hello" {
|
||||
t.Errorf("Expected 'hello', got '%s'", result)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRueidisBasicPublish tests PUBLISH and verifies count
|
||||
func TestRueidisBasicPublish(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
pubClient := newRueidisClient(t, ts.Addr())
|
||||
defer pubClient.Close()
|
||||
|
||||
// Publish to channel with no subscribers
|
||||
count, err := pubClient.Do(ctx, pubClient.B().Publish().Channel("r-test").Message("hello").Build()).AsInt64()
|
||||
if err != nil {
|
||||
t.Fatalf("Publish failed: %v", err)
|
||||
}
|
||||
|
||||
if count != 0 {
|
||||
t.Errorf("Expected 0 subscribers, got %d", count)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRueidisPublishNoSubscribers tests PUBLISH with no subscribers
|
||||
func TestRueidisPublishNoSubscribers(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
client := newRueidisClient(t, ts.Addr())
|
||||
defer client.Close()
|
||||
|
||||
n, err := client.Do(ctx, client.B().Publish().Channel("r-no-subs").Message("msg").Build()).AsInt64()
|
||||
if err != nil {
|
||||
t.Fatalf("Publish failed: %v", err)
|
||||
}
|
||||
|
||||
if n != 0 {
|
||||
t.Errorf("Expected 0 receivers, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRueidisBinarySafe tests binary message handling
|
||||
func TestRueidisBinarySafe(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
client := newRueidisClient(t, ts.Addr())
|
||||
defer client.Close()
|
||||
|
||||
binaryMsg := []byte{0x00, 0x01, 0x02, 0xFF, 0xFE}
|
||||
|
||||
// Just verify publish works with binary data
|
||||
_, err := client.Do(ctx, client.B().Publish().Channel("r-binary").Message(string(binaryMsg)).Build()).AsInt64()
|
||||
if err != nil {
|
||||
t.Fatalf("Publish binary failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRueidisEmptyMessage tests empty message
|
||||
func TestRueidisEmptyMessage(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
client := newRueidisClient(t, ts.Addr())
|
||||
defer client.Close()
|
||||
|
||||
_, err := client.Do(ctx, client.B().Publish().Channel("r-empty").Message("").Build()).AsInt64()
|
||||
if err != nil {
|
||||
t.Fatalf("Publish empty failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRueidisConcurrentSubscribers tests concurrent client creation
|
||||
func TestRueidisConcurrentSubscribers(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
successCount := 0
|
||||
var mu sync.Mutex
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
|
||||
client := newRueidisClient(t, ts.Addr())
|
||||
defer client.Close()
|
||||
|
||||
// Just verify we can ping
|
||||
ctx := context.Background()
|
||||
_, err := client.Do(ctx, client.B().Ping().Build()).ToString()
|
||||
if err != nil {
|
||||
t.Errorf("Client %d ping failed: %v", id, err)
|
||||
return
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
successCount++
|
||||
mu.Unlock()
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
if successCount != 10 {
|
||||
t.Errorf("Only %d/10 clients succeeded", successCount)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRueidisUnicodeMessages tests Unicode messages
|
||||
func TestRueidisUnicodeMessages(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
client := newRueidisClient(t, ts.Addr())
|
||||
defer client.Close()
|
||||
|
||||
messages := []string{
|
||||
"Hello, 世界!",
|
||||
"Emoji: 🎉",
|
||||
"Cyrillic: Привет",
|
||||
}
|
||||
|
||||
for _, testMsg := range messages {
|
||||
t.Run(testMsg[:min(20, len(testMsg))], func(t *testing.T) {
|
||||
_, err := client.Do(ctx, client.B().Publish().Channel("r-unicode").Message(testMsg).Build()).AsInt64()
|
||||
if err != nil {
|
||||
t.Fatalf("Publish failed: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestRueidisConcurrentPublishSubscribe tests concurrent pub/sub operations
|
||||
func TestRueidisConcurrentPublishSubscribe(t *testing.T) {
|
||||
ts := startTestServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
numMessages := 50
|
||||
|
||||
pubClient := newRueidisClient(t, ts.Addr())
|
||||
defer pubClient.Close()
|
||||
|
||||
for i := 0; i < numMessages; i++ {
|
||||
_, err := pubClient.Do(ctx, pubClient.B().Publish().Channel("r-concurrent").Message(fmt.Sprintf("msg-%d", i)).Build()).AsInt64()
|
||||
if err != nil {
|
||||
t.Errorf("Publish %d failed: %v", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
t.Logf("Published %d messages", numMessages)
|
||||
}
|
||||
Reference in New Issue
Block a user