mirror of
https://github.com/langgenius/dify-plugin-daemon.git
synced 2026-07-23 10:15:22 -04:00
8380c1d6fa
- Introduced a new test case `TestLock` to validate the behavior of the Redis locking mechanism under concurrent access. - Enhanced the `Lock` function to improve error handling and ensure proper locking behavior. - Utilized `sync.WaitGroup` and atomic operations to measure wait times during lock acquisition, ensuring the lock behaves as expected under high concurrency.
355 lines
6.8 KiB
Go
355 lines
6.8 KiB
Go
package cache
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/redis/go-redis/v9"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
const (
|
|
TEST_PREFIX = "test"
|
|
)
|
|
|
|
func getRedisConnection() error {
|
|
return InitRedisClient("0.0.0.0:6379", "", "difyai123456", false, 0)
|
|
}
|
|
|
|
func TestRedisConnection(t *testing.T) {
|
|
// get redis connection
|
|
if err := getRedisConnection(); err != nil {
|
|
t.Errorf("get redis connection failed: %v", err)
|
|
return
|
|
}
|
|
|
|
// close
|
|
if err := Close(); err != nil {
|
|
t.Errorf("close redis client failed: %v", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestRedisTransaction(t *testing.T) {
|
|
// get redis connection
|
|
if err := getRedisConnection(); err != nil {
|
|
t.Errorf("get redis connection failed: %v", err)
|
|
return
|
|
}
|
|
defer Close()
|
|
|
|
// test transaction
|
|
err := Transaction(func(p redis.Pipeliner) error {
|
|
// set key
|
|
if err := Store(
|
|
strings.Join([]string{TEST_PREFIX, "key"}, ":"),
|
|
"value",
|
|
time.Second,
|
|
p,
|
|
); err != nil {
|
|
t.Errorf("store key failed: %v", err)
|
|
return err
|
|
}
|
|
|
|
return errors.New("test transaction error")
|
|
})
|
|
|
|
if err == nil {
|
|
t.Errorf("transaction should return error")
|
|
return
|
|
}
|
|
|
|
// get key
|
|
value, err := GetString(
|
|
strings.Join([]string{TEST_PREFIX, "key"}, ":"),
|
|
)
|
|
|
|
if err != ErrNotFound {
|
|
t.Errorf("key should not exist")
|
|
return
|
|
}
|
|
|
|
if value != "" {
|
|
t.Errorf("value should be empty")
|
|
return
|
|
}
|
|
|
|
// test success transaction
|
|
err = Transaction(func(p redis.Pipeliner) error {
|
|
// set key
|
|
if err := Store(
|
|
strings.Join([]string{TEST_PREFIX, "key"}, ":"),
|
|
"value",
|
|
time.Second,
|
|
p,
|
|
); err != nil {
|
|
t.Errorf("store key failed: %v", err)
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
t.Errorf("transaction should not return error")
|
|
return
|
|
}
|
|
|
|
defer Del(strings.Join([]string{TEST_PREFIX, "key"}, ":"))
|
|
|
|
// get key
|
|
value, err = GetString(
|
|
strings.Join([]string{TEST_PREFIX, "key"}, ":"),
|
|
)
|
|
|
|
if err != nil {
|
|
t.Errorf("get key failed: %v", err)
|
|
return
|
|
}
|
|
|
|
if value != "value" {
|
|
t.Errorf("value should be value")
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestRedisScanMap(t *testing.T) {
|
|
// get redis connection
|
|
if err := getRedisConnection(); err != nil {
|
|
t.Errorf("get redis connection failed: %v", err)
|
|
return
|
|
}
|
|
defer Close()
|
|
|
|
type s struct {
|
|
Field string `json:"field"`
|
|
}
|
|
|
|
err := SetMapOneField(strings.Join([]string{TEST_PREFIX, "map"}, ":"), "key1", s{Field: "value1"})
|
|
if err != nil {
|
|
t.Errorf("set map failed: %v", err)
|
|
return
|
|
}
|
|
defer Del(strings.Join([]string{TEST_PREFIX, "map"}, ":"))
|
|
err = SetMapOneField(strings.Join([]string{TEST_PREFIX, "map"}, ":"), "key2", s{Field: "value2"})
|
|
if err != nil {
|
|
t.Errorf("set map failed: %v", err)
|
|
return
|
|
}
|
|
err = SetMapOneField(strings.Join([]string{TEST_PREFIX, "map"}, ":"), "key3", s{Field: "value3"})
|
|
if err != nil {
|
|
t.Errorf("set map failed: %v", err)
|
|
return
|
|
}
|
|
err = SetMapOneField(strings.Join([]string{TEST_PREFIX, "map"}, ":"), "4", s{Field: "value4"})
|
|
if err != nil {
|
|
t.Errorf("set map failed: %v", err)
|
|
return
|
|
}
|
|
|
|
data, err := ScanMap[s](strings.Join([]string{TEST_PREFIX, "map"}, ":"), "key*")
|
|
if err != nil {
|
|
t.Errorf("scan map failed: %v", err)
|
|
return
|
|
}
|
|
|
|
if len(data) != 3 {
|
|
t.Errorf("scan map should return 3")
|
|
return
|
|
}
|
|
|
|
if data["key1"].Field != "value1" {
|
|
t.Errorf("scan map should return value1")
|
|
return
|
|
}
|
|
|
|
if data["key2"].Field != "value2" {
|
|
t.Errorf("scan map should return value2")
|
|
return
|
|
}
|
|
|
|
if data["key3"].Field != "value3" {
|
|
t.Errorf("scan map should return value3")
|
|
return
|
|
}
|
|
|
|
err = ScanMapAsync[s](strings.Join([]string{TEST_PREFIX, "map"}, ":"), "4", func(m map[string]s) error {
|
|
if len(m) != 1 {
|
|
t.Errorf("scan map async should return 1")
|
|
return errors.New("scan map async should return 1")
|
|
}
|
|
|
|
if m["4"].Field != "value4" {
|
|
t.Errorf("scan map async should return value4")
|
|
return errors.New("scan map async should return value4")
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
t.Errorf("scan map async failed: %v", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestRedisP2PPubsub(t *testing.T) {
|
|
// get redis connection
|
|
if err := getRedisConnection(); err != nil {
|
|
t.Errorf("get redis connection failed: %v", err)
|
|
return
|
|
}
|
|
defer Close()
|
|
|
|
ch := "test-channel"
|
|
|
|
type s struct{}
|
|
|
|
sub, cancel := Subscribe[s](ch)
|
|
defer cancel()
|
|
|
|
wg := sync.WaitGroup{}
|
|
wg.Add(1)
|
|
|
|
go func() {
|
|
<-sub
|
|
wg.Done()
|
|
}()
|
|
|
|
// test pubsub
|
|
err := Publish(ch, s{})
|
|
if err != nil {
|
|
t.Errorf("publish failed: %v", err)
|
|
return
|
|
}
|
|
|
|
wg.Wait()
|
|
}
|
|
|
|
func TestRedisP2ARedis(t *testing.T) {
|
|
// get redis connection
|
|
if err := getRedisConnection(); err != nil {
|
|
t.Errorf("get redis connection failed: %v", err)
|
|
return
|
|
}
|
|
defer Close()
|
|
|
|
ch := "test-channel-p2a"
|
|
|
|
type s struct{}
|
|
|
|
wg := sync.WaitGroup{}
|
|
wg.Add(3)
|
|
|
|
swg := sync.WaitGroup{}
|
|
swg.Add(3)
|
|
|
|
for i := 0; i < 3; i++ {
|
|
go func() {
|
|
sub, cancel := Subscribe[s](ch)
|
|
swg.Done()
|
|
defer cancel()
|
|
<-sub
|
|
wg.Done()
|
|
}()
|
|
}
|
|
|
|
swg.Wait()
|
|
|
|
// test pubsub
|
|
err := Publish(ch, s{})
|
|
if err != nil {
|
|
t.Errorf("publish failed: %v", err)
|
|
return
|
|
}
|
|
|
|
wg.Wait()
|
|
}
|
|
|
|
func TestGetRedisOptions(t *testing.T) {
|
|
opts := getRedisOptions("dummy:6379", "", "password", false, 0)
|
|
if opts.TLSConfig != nil {
|
|
t.Errorf("TLSConfig should not be set")
|
|
return
|
|
}
|
|
|
|
opts = getRedisOptions("dummy:6379", "", "password", true, 0)
|
|
if opts.TLSConfig == nil {
|
|
t.Errorf("TLSConfig should be set")
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestSetAndGet(t *testing.T) {
|
|
if err := InitRedisClient("127.0.0.1:6379", "", "difyai123456", false, 0); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer Close()
|
|
|
|
m := map[string]string{
|
|
"key": "hello",
|
|
}
|
|
|
|
err := Store(strings.Join([]string{TEST_PREFIX, "get-test"}, ":"), m, time.Minute)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
val, err := Get[map[string]string](strings.Join([]string{TEST_PREFIX, "get-test"}, ":"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if (*val)["key"] != "hello" {
|
|
t.Fatalf("Get[\"key\"] should be \"hello\"")
|
|
}
|
|
_, err = Del(strings.Join([]string{TEST_PREFIX, "get-test"}, ":"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
val, err = Get[map[string]string](strings.Join([]string{TEST_PREFIX, "get-test"}, ":"))
|
|
if err != ErrNotFound {
|
|
t.Fatalf("Get[\"key\"] should be ErrNotFound")
|
|
}
|
|
}
|
|
|
|
func TestLock(t *testing.T) {
|
|
if err := InitRedisClient("127.0.0.1:6379", "", "difyai123456", false, 0); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer Close()
|
|
|
|
const CONCURRENCY = 10
|
|
const SINGLE_TURN_TIME = 100
|
|
|
|
wg := sync.WaitGroup{}
|
|
wg.Add(CONCURRENCY)
|
|
|
|
waitMilliseconds := int32(0)
|
|
|
|
foo := func() {
|
|
Lock("test-lock", SINGLE_TURN_TIME*time.Millisecond*1000, SINGLE_TURN_TIME*time.Millisecond*1000)
|
|
started := time.Now()
|
|
time.Sleep(SINGLE_TURN_TIME * time.Millisecond)
|
|
defer func() {
|
|
Unlock("test-lock")
|
|
atomic.AddInt32(&waitMilliseconds, int32(time.Since(started).Milliseconds()))
|
|
wg.Done()
|
|
}()
|
|
}
|
|
|
|
for range CONCURRENCY {
|
|
go foo()
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
fmt.Println("waitSeconds", waitMilliseconds)
|
|
|
|
assert.GreaterOrEqual(t, waitMilliseconds, int32(100*CONCURRENCY))
|
|
}
|