lolly/internal/lua/shared_dict_test.go
xfy e3e5b1fe83 feat(lua): 实现共享字典 API (ngx.shared.DICT)
添加共享内存字典实现,支持并发安全的 key-value 存储:
- SharedDictManager: 管理多个命名的 SharedDict 实例
- SharedDict: 带 LRU 汰出策略的内存字典
- 支持 set/get/add/incr/size/free_space 操作
- 支持带 TTL 的过期机制

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 11:21:17 +08:00

252 lines
5.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package lua 提供 Lua 脚本嵌入能力
package lua
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSharedDictGetSet(t *testing.T) {
dict := NewSharedDict("test", 100)
// Set
ok, err := dict.Set("key1", "value1", 0)
require.NoError(t, err)
assert.True(t, ok)
// Get
value, expired, err := dict.Get("key1")
require.NoError(t, err)
assert.False(t, expired)
assert.Equal(t, "value1", value)
// 不存在的 key
value, expired, err = dict.Get("notexist")
require.NoError(t, err)
assert.False(t, expired)
assert.Equal(t, "", value)
}
func TestSharedDictAdd(t *testing.T) {
dict := NewSharedDict("test", 100)
// Add 新 key
ok, err := dict.Add("key1", "value1", 0)
require.NoError(t, err)
assert.True(t, ok)
// Add 已存在的 key
ok, err = dict.Add("key1", "value2", 0)
require.NoError(t, err)
assert.False(t, ok) // 已存在,返回 false
// 验证值未被修改
value, _, _ := dict.Get("key1")
assert.Equal(t, "value1", value)
}
func TestSharedDictIncr(t *testing.T) {
dict := NewSharedDict("test", 100)
// Incr 不存在的 key从 0 开始
newValue, err := dict.Incr("counter", 5)
require.NoError(t, err)
assert.Equal(t, 5, newValue)
// Incr 已存在的 key
newValue, err = dict.Incr("counter", 3)
require.NoError(t, err)
assert.Equal(t, 8, newValue)
// 轮减
newValue, err = dict.Incr("counter", -2)
require.NoError(t, err)
assert.Equal(t, 6, newValue)
}
func TestSharedDictDelete(t *testing.T) {
dict := NewSharedDict("test", 100)
dict.Set("key1", "value1", 0)
dict.Delete("key1")
value, _, _ := dict.Get("key1")
assert.Equal(t, "", value)
// 删除不存在的 key 不会报错
dict.Delete("notexist")
}
func TestSharedDictTTL(t *testing.T) {
dict := NewSharedDict("test", 100)
// Set 带 TTL
ok, err := dict.Set("key1", "value1", 100*time.Millisecond)
require.NoError(t, err)
assert.True(t, ok)
// 立即获取应该成功
value, expired, err := dict.Get("key1")
require.NoError(t, err)
assert.False(t, expired)
assert.Equal(t, "value1", value)
// 等待过期
time.Sleep(150 * time.Millisecond)
// 过期后获取
value, expired, err = dict.Get("key1")
require.NoError(t, err)
assert.True(t, expired)
assert.Equal(t, "", value)
}
func TestSharedDictLRUEviction(t *testing.T) {
dict := NewSharedDict("test", 3) // 只有 3 个容量
// 添加 3 个条目
dict.Set("key1", "value1", 0)
dict.Set("key2", "value2", 0)
dict.Set("key3", "value3", 0)
// 使用 key1使其成为最近使用
dict.Get("key1")
// 添加第 4 个条目,应该淘汰 key2最久未使用
ok, err := dict.Set("key4", "value4", 0)
require.NoError(t, err)
assert.True(t, ok)
// key1 应该还在
value, _, _ := dict.Get("key1")
assert.Equal(t, "value1", value)
// key2 应该被淘汰
value, _, _ = dict.Get("key2")
assert.Equal(t, "", value)
}
func TestSharedDictFlushAll(t *testing.T) {
dict := NewSharedDict("test", 100)
dict.Set("key1", "value1", 0)
dict.Set("key2", "value2", 0)
dict.FlushAll()
assert.Equal(t, 0, dict.Size())
}
func TestSharedDictFlushExpired(t *testing.T) {
dict := NewSharedDict("test", 100)
dict.Set("key1", "value1", 100*time.Millisecond)
dict.Set("key2", "value2", 100*time.Millisecond)
dict.Set("key3", "value3", 0) // 不过期
// 立即清除应该返回 0
count := dict.FlushExpired()
assert.Equal(t, 0, count)
// 等待过期
time.Sleep(150 * time.Millisecond)
count = dict.FlushExpired()
assert.Equal(t, 2, count)
// key3 应该还在
assert.Equal(t, 1, dict.Size())
value, _, _ := dict.Get("key3")
assert.Equal(t, "value3", value)
}
func TestSharedDictSize(t *testing.T) {
dict := NewSharedDict("test", 100)
assert.Equal(t, 0, dict.Size())
assert.Equal(t, 100, dict.FreeSlots())
dict.Set("key1", "value1", 0)
assert.Equal(t, 1, dict.Size())
assert.Equal(t, 99, dict.FreeSlots())
}
func TestSharedDictManager(t *testing.T) {
manager := NewSharedDictManager()
// 创建字典
dict1 := manager.CreateDict("dict1", 100)
require.NotNil(t, dict1)
// 再次获取同一个字典
dict1Again := manager.GetDict("dict1")
assert.Equal(t, dict1, dict1Again)
// 创建另一个字典
dict2 := manager.CreateDict("dict2", 200)
require.NotNil(t, dict2)
// 获取不存在的字典
notexist := manager.GetDict("notexist")
assert.Nil(t, notexist)
// 关闭
manager.Close()
}
func TestSharedDictLuaAPI(t *testing.T) {
engine, err := NewEngine(DefaultConfig())
require.NoError(t, err)
defer engine.Close()
// 创建共享字典(通过 Lua API 测试,此处仅为初始化)
_ = engine.CreateSharedDict("mydict", 100)
// 测试 Lua 脚本
L := engine.L
// 手动注册 ngx.shared API用于测试
ngx := L.NewTable()
L.SetGlobal("ngx", ngx)
RegisterSharedDictAPI(L, engine.SharedDictManager(), ngx)
// 运行 Lua 脚本测试
err = L.DoString(`
local dict = ngx.shared.DICT("mydict")
-- 测试 set/get
dict:set("key1", "value1")
local val, flags = dict:get("key1")
assert(val == "value1")
-- 测试 add
local ok, err = dict:add("key2", "value2")
assert(ok == true)
-- 测试 add 已存在的 key
ok, err = dict:add("key2", "value3")
assert(ok == false)
assert(err == "exists")
-- 测试 incr
local new_val, err = dict:incr("counter", 10)
assert(new_val == 10)
new_val, err = dict:incr("counter", 5)
assert(new_val == 15)
-- 测试 size
local size = dict:size()
assert(size >= 3)
-- 测试 delete
dict:delete("key1")
local val, err = dict:get("key1")
assert(val == nil)
`)
require.NoError(t, err)
}