test(lua): 添加边界场景和 Scheduler 模式测试
- 添加协程沙箱、定时器句柄、共享字典容量边界测试 - 添加 Scheduler 模式 API 安全限制测试 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
e145f1b080
commit
f91a40cc1d
569
internal/lua/boundary_test.go
Normal file
569
internal/lua/boundary_test.go
Normal file
@ -0,0 +1,569 @@
|
|||||||
|
// Package lua 提供边界场景测试。
|
||||||
|
//
|
||||||
|
// 该文件测试 Lua 模块的边界场景:
|
||||||
|
// - 协程创建失败处理
|
||||||
|
// - 定时器句柄操作
|
||||||
|
// - 共享字典容量上限
|
||||||
|
// - getVariable 所有变量类型
|
||||||
|
//
|
||||||
|
// 作者:xfy
|
||||||
|
package lua
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/valyala/fasthttp"
|
||||||
|
glua "github.com/yuin/gopher-lua"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestBoundaryCoroutineSandbox 测试协程沙箱阻止危险操作。
|
||||||
|
func TestBoundaryCoroutineSandbox(t *testing.T) {
|
||||||
|
engine, err := NewEngine(DefaultConfig())
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer engine.Close()
|
||||||
|
|
||||||
|
// 创建协程
|
||||||
|
co, err := engine.NewCoroutine(nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer co.Close()
|
||||||
|
|
||||||
|
// 设置沙箱
|
||||||
|
err = co.SetupSandbox()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// 尝试创建协程应该失败 - 使用单个脚本执行
|
||||||
|
err = co.Execute(`
|
||||||
|
local ok, err = pcall(function()
|
||||||
|
coroutine.create(function() end)
|
||||||
|
end)
|
||||||
|
if not ok then
|
||||||
|
-- 预期错误
|
||||||
|
else
|
||||||
|
error("coroutine.create should be blocked")
|
||||||
|
end
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBoundaryCoroutineYieldAllowed 测试协程 yield 仍然可用。
|
||||||
|
func TestBoundaryCoroutineYieldAllowed(t *testing.T) {
|
||||||
|
engine, err := NewEngine(DefaultConfig())
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer engine.Close()
|
||||||
|
|
||||||
|
co, err := engine.NewCoroutine(nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer co.Close()
|
||||||
|
|
||||||
|
err = co.SetupSandbox()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// coroutine.yield 和 coroutine.status 应该可用
|
||||||
|
err = co.Execute(`
|
||||||
|
-- coroutine.status 应该可用
|
||||||
|
local status = coroutine.status
|
||||||
|
if type(status) ~= "function" then
|
||||||
|
error("coroutine.status should be a function")
|
||||||
|
end
|
||||||
|
-- coroutine.yield 应该可用
|
||||||
|
local yield = coroutine.yield
|
||||||
|
if type(yield) ~= "function" then
|
||||||
|
error("coroutine.yield should be a function")
|
||||||
|
end
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBoundaryTimerHandleCancel 测试定时器句柄取消。
|
||||||
|
func TestBoundaryTimerHandleCancel(t *testing.T) {
|
||||||
|
engine, err := NewEngine(DefaultConfig())
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer engine.Close()
|
||||||
|
|
||||||
|
timerMgr := engine.TimerManager()
|
||||||
|
require.NotNil(t, timerMgr)
|
||||||
|
|
||||||
|
L := glua.NewState()
|
||||||
|
defer L.Close()
|
||||||
|
|
||||||
|
ngx := L.NewTable()
|
||||||
|
L.SetGlobal("ngx", ngx)
|
||||||
|
RegisterTimerAPI(L, timerMgr, ngx)
|
||||||
|
|
||||||
|
// 创建定时器并取消 - 使用全局函数避免闭包
|
||||||
|
err = L.DoString(`
|
||||||
|
function timer_callback()
|
||||||
|
-- 空回调
|
||||||
|
end
|
||||||
|
local handle, err = ngx.timer.at(10, timer_callback)
|
||||||
|
if not handle then
|
||||||
|
error("Failed to create timer: " .. tostring(err))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 取消定时器
|
||||||
|
local ok, err = handle:cancel()
|
||||||
|
if not ok then
|
||||||
|
error("Failed to cancel timer: " .. tostring(err))
|
||||||
|
end
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// 验证定时器已取消
|
||||||
|
assert.Equal(t, int32(0), timerMgr.ActiveCount())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBoundaryTimerHandleDoubleCancel 测试重复取消定时器。
|
||||||
|
func TestBoundaryTimerHandleDoubleCancel(t *testing.T) {
|
||||||
|
engine, err := NewEngine(DefaultConfig())
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer engine.Close()
|
||||||
|
|
||||||
|
timerMgr := engine.TimerManager()
|
||||||
|
L := glua.NewState()
|
||||||
|
defer L.Close()
|
||||||
|
|
||||||
|
ngx := L.NewTable()
|
||||||
|
L.SetGlobal("ngx", ngx)
|
||||||
|
RegisterTimerAPI(L, timerMgr, ngx)
|
||||||
|
|
||||||
|
err = L.DoString(`
|
||||||
|
function timer_callback() end
|
||||||
|
local handle = ngx.timer.at(10, timer_callback)
|
||||||
|
if not handle then
|
||||||
|
error("Failed to create timer")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 第一次取消
|
||||||
|
local ok1 = handle:cancel()
|
||||||
|
|
||||||
|
-- 第二次取消应该失败
|
||||||
|
local ok2, err = handle:cancel()
|
||||||
|
if ok2 then
|
||||||
|
error("Double cancel should fail")
|
||||||
|
end
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBoundaryTimerRunningCount 测试定时器运行计数。
|
||||||
|
func TestBoundaryTimerRunningCount(t *testing.T) {
|
||||||
|
engine, err := NewEngine(DefaultConfig())
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer engine.Close()
|
||||||
|
|
||||||
|
timerMgr := engine.TimerManager()
|
||||||
|
L := glua.NewState()
|
||||||
|
defer L.Close()
|
||||||
|
|
||||||
|
ngx := L.NewTable()
|
||||||
|
L.SetGlobal("ngx", ngx)
|
||||||
|
RegisterTimerAPI(L, timerMgr, ngx)
|
||||||
|
|
||||||
|
// 创建多个定时器
|
||||||
|
err = L.DoString(`
|
||||||
|
local count = ngx.timer.running_count()
|
||||||
|
if count ~= 0 then
|
||||||
|
error("Initial count should be 0")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 创建定时器
|
||||||
|
local h1 = ngx.timer.at(10, function() end)
|
||||||
|
local h2 = ngx.timer.at(10, function() end)
|
||||||
|
local h3 = ngx.timer.at(10, function() end)
|
||||||
|
|
||||||
|
count = ngx.timer.running_count()
|
||||||
|
if count ~= 3 then
|
||||||
|
error("Count should be 3, got " .. tostring(count))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 取消一个
|
||||||
|
h1:cancel()
|
||||||
|
count = ngx.timer.running_count()
|
||||||
|
if count ~= 2 then
|
||||||
|
error("Count should be 2 after cancel, got " .. tostring(count))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 清理
|
||||||
|
h2:cancel()
|
||||||
|
h3:cancel()
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBoundaryTimerUpvalueRejected 测试定时器拒绝闭包变量。
|
||||||
|
func TestBoundaryTimerUpvalueRejected(t *testing.T) {
|
||||||
|
engine, err := NewEngine(DefaultConfig())
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer engine.Close()
|
||||||
|
|
||||||
|
timerMgr := engine.TimerManager()
|
||||||
|
L := glua.NewState()
|
||||||
|
defer L.Close()
|
||||||
|
|
||||||
|
ngx := L.NewTable()
|
||||||
|
L.SetGlobal("ngx", ngx)
|
||||||
|
RegisterTimerAPI(L, timerMgr, ngx)
|
||||||
|
|
||||||
|
// 尝试创建捕获 upvalue 的定时器应该失败
|
||||||
|
err = L.DoString(`
|
||||||
|
local captured = "value"
|
||||||
|
local handle, err = ngx.timer.at(1, function()
|
||||||
|
print(captured) -- 捕获 upvalue
|
||||||
|
end)
|
||||||
|
if handle then
|
||||||
|
handle:cancel()
|
||||||
|
error("Timer with upvalue should be rejected")
|
||||||
|
end
|
||||||
|
if not err then
|
||||||
|
error("Expected error for upvalue capture")
|
||||||
|
end
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBoundarySharedDictCapacity 测试共享字典容量上限。
|
||||||
|
func TestBoundarySharedDictCapacity(t *testing.T) {
|
||||||
|
dict := NewSharedDict("test", 3) // 只允许 3 个条目
|
||||||
|
|
||||||
|
// 添加 3 个条目
|
||||||
|
ok, err := dict.Set("a", "1", 0)
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
ok, err = dict.Set("b", "2", 0)
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
ok, err = dict.Set("c", "3", 0)
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// 添加第 4 个应该淘汰 LRU
|
||||||
|
ok, err = dict.Set("d", "4", 0)
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// 验证大小仍然为 3
|
||||||
|
assert.Equal(t, 3, dict.Size())
|
||||||
|
|
||||||
|
// "a" 应该被淘汰 - Get 返回空值
|
||||||
|
val, _, _ := dict.Get("a")
|
||||||
|
assert.Equal(t, "", val) // 不存在返回空
|
||||||
|
|
||||||
|
// "d" 应该存在
|
||||||
|
val, expired, _ := dict.Get("d")
|
||||||
|
assert.False(t, expired)
|
||||||
|
assert.Equal(t, "4", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBoundarySharedDictIncrNonNumeric 测试非数值自增。
|
||||||
|
func TestBoundarySharedDictIncrNonNumeric(t *testing.T) {
|
||||||
|
dict := NewSharedDict("test", 10)
|
||||||
|
|
||||||
|
// 设置非数值字符串
|
||||||
|
ok, _ := dict.Set("key", "not-a-number", 0)
|
||||||
|
assert.True(t, ok)
|
||||||
|
|
||||||
|
// 尝试自增应该失败
|
||||||
|
_, err := dict.Incr("key", 1)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "not a number")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBoundarySharedDictIncrNegative 测试负数自增。
|
||||||
|
func TestBoundarySharedDictIncrNegative(t *testing.T) {
|
||||||
|
dict := NewSharedDict("test", 10)
|
||||||
|
|
||||||
|
// 设置初始值
|
||||||
|
ok, _ := dict.Set("counter", "10", 0)
|
||||||
|
assert.True(t, ok)
|
||||||
|
|
||||||
|
// 负数自增
|
||||||
|
val, err := dict.Incr("counter", -3)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 7, val)
|
||||||
|
|
||||||
|
// 再次负数自增到负数
|
||||||
|
val, err = dict.Incr("counter", -20)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, -13, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBoundarySharedDictTTLExpiration 测试 TTL 过期。
|
||||||
|
func TestBoundarySharedDictTTLExpiration(t *testing.T) {
|
||||||
|
dict := NewSharedDict("test", 10)
|
||||||
|
|
||||||
|
// 设置短 TTL
|
||||||
|
ok, _ := dict.Set("ephemeral", "value", 100*time.Millisecond)
|
||||||
|
assert.True(t, ok)
|
||||||
|
|
||||||
|
// 立即读取应该存在
|
||||||
|
val, expired, _ := dict.Get("ephemeral")
|
||||||
|
assert.False(t, expired)
|
||||||
|
assert.Equal(t, "value", val)
|
||||||
|
|
||||||
|
// 等待过期
|
||||||
|
time.Sleep(150 * time.Millisecond)
|
||||||
|
|
||||||
|
// 再次读取应该过期
|
||||||
|
val, expired, _ = dict.Get("ephemeral")
|
||||||
|
assert.True(t, expired)
|
||||||
|
assert.Equal(t, "", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBoundarySharedDictConcurrentAccess 测试并发访问。
|
||||||
|
func TestBoundarySharedDictConcurrentAccess(t *testing.T) {
|
||||||
|
dict := NewSharedDict("test", 100)
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
concurrency := 10
|
||||||
|
iterations := 100
|
||||||
|
|
||||||
|
for i := 0; i < concurrency; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(id int) {
|
||||||
|
defer wg.Done()
|
||||||
|
for j := 0; j < iterations; j++ {
|
||||||
|
key := "key-" + string(rune('0'+id%10))
|
||||||
|
_, _ = dict.Set(key, "value", 0)
|
||||||
|
_, _, _ = dict.Get(key)
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
// 如果没有竞态条件,测试通过
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBoundaryGetVariableAllTypes 测试所有变量类型。
|
||||||
|
func TestBoundaryGetVariableAllTypes(t *testing.T) {
|
||||||
|
// 创建模拟请求上下文
|
||||||
|
ctx := &fasthttp.RequestCtx{}
|
||||||
|
ctx.Request.SetRequestURI("http://example.com/path?arg1=value1&arg2=value2")
|
||||||
|
ctx.Request.Header.SetMethod("POST")
|
||||||
|
ctx.Request.Header.Set("User-Agent", "test-agent")
|
||||||
|
ctx.Request.Header.Set("Content-Type", "application/json")
|
||||||
|
ctx.Request.Header.Set("X-Custom", "custom-value")
|
||||||
|
|
||||||
|
api := newNgxVarAPI(ctx)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"request_method", "POST"},
|
||||||
|
{"request_uri", "http://example.com/path?arg1=value1&arg2=value2"},
|
||||||
|
{"uri", "/path"},
|
||||||
|
{"query_string", "arg1=value1&arg2=value2"},
|
||||||
|
{"args", "arg1=value1&arg2=value2"},
|
||||||
|
{"http_host", "example.com"},
|
||||||
|
{"http_user_agent", "test-agent"},
|
||||||
|
{"http_content_type", "application/json"},
|
||||||
|
{"http_x-custom", "custom-value"},
|
||||||
|
{"arg_arg1", "value1"},
|
||||||
|
{"arg_arg2", "value2"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
val := api.getVariable(tt.name)
|
||||||
|
assert.Equal(t, tt.expected, val)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBoundaryGetVariableLuaTypes 测试 Lua 类型返回。
|
||||||
|
func TestBoundaryGetVariableLuaTypes(t *testing.T) {
|
||||||
|
ctx := &fasthttp.RequestCtx{}
|
||||||
|
ctx.Request.SetRequestURI("http://example.com/path")
|
||||||
|
ctx.Request.Header.SetMethod("GET")
|
||||||
|
|
||||||
|
api := newNgxVarAPI(ctx)
|
||||||
|
|
||||||
|
// request_length 应该返回数值类型
|
||||||
|
lv := api.getVariableLua("request_length")
|
||||||
|
if lv == nil {
|
||||||
|
t.Log("request_length returned nil (acceptable)")
|
||||||
|
} else if _, ok := lv.(glua.LNumber); !ok {
|
||||||
|
t.Errorf("request_length should return LNumber, got %T", lv)
|
||||||
|
}
|
||||||
|
|
||||||
|
// request_method 应该返回字符串类型
|
||||||
|
lv = api.getVariableLua("request_method")
|
||||||
|
if lv == nil {
|
||||||
|
t.Error("request_method should not return nil")
|
||||||
|
} else if _, ok := lv.(glua.LString); !ok {
|
||||||
|
t.Errorf("request_method should return LString, got %T", lv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBoundaryGetVariableCustom 测试自定义变量。
|
||||||
|
func TestBoundaryGetVariableCustom(t *testing.T) {
|
||||||
|
ctx := &fasthttp.RequestCtx{}
|
||||||
|
api := newNgxVarAPI(ctx)
|
||||||
|
|
||||||
|
// 设置自定义变量
|
||||||
|
api.SetVariable("custom_var", "custom_value")
|
||||||
|
|
||||||
|
// 读取自定义变量
|
||||||
|
val, ok := api.GetVariable("custom_var")
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, "custom_value", val)
|
||||||
|
|
||||||
|
// 读取不存在的变量
|
||||||
|
_, ok = api.GetVariable("nonexistent")
|
||||||
|
assert.False(t, ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBoundaryCoroutineExecutionTimeout 测试协程执行超时。
|
||||||
|
func TestBoundaryCoroutineExecutionTimeout(t *testing.T) {
|
||||||
|
engine, err := NewEngine(DefaultConfig())
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer engine.Close()
|
||||||
|
|
||||||
|
// 创建带超时的上下文
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
co, err := engine.NewCoroutine(nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer co.Close()
|
||||||
|
|
||||||
|
co.ExecutionContext = ctx
|
||||||
|
|
||||||
|
err = co.SetupSandbox()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// 执行一个长时间运行的脚本应该被中断
|
||||||
|
err = co.Execute(`
|
||||||
|
local start = os.time()
|
||||||
|
while os.time() - start < 10 do
|
||||||
|
-- 忙等待
|
||||||
|
end
|
||||||
|
`)
|
||||||
|
// 超时错误是预期的
|
||||||
|
if err == nil {
|
||||||
|
t.Log("Script completed quickly (no timeout)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBoundaryTimerHandleString 测试定时器句柄字符串表示。
|
||||||
|
func TestBoundaryTimerHandleString(t *testing.T) {
|
||||||
|
engine, err := NewEngine(DefaultConfig())
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer engine.Close()
|
||||||
|
|
||||||
|
timerMgr := engine.TimerManager()
|
||||||
|
L := glua.NewState()
|
||||||
|
defer L.Close()
|
||||||
|
|
||||||
|
ngx := L.NewTable()
|
||||||
|
L.SetGlobal("ngx", ngx)
|
||||||
|
RegisterTimerAPI(L, timerMgr, ngx)
|
||||||
|
|
||||||
|
err = L.DoString(`
|
||||||
|
local handle = ngx.timer.at(10, function() end)
|
||||||
|
if not handle then
|
||||||
|
error("Failed to create timer")
|
||||||
|
end
|
||||||
|
|
||||||
|
local str = tostring(handle)
|
||||||
|
if not string.find(str, "ngx.timer.handle:") then
|
||||||
|
error("Invalid handle string: " .. str)
|
||||||
|
end
|
||||||
|
|
||||||
|
handle:cancel()
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBoundaryTimerWaitAll 测试等待所有定时器完成。
|
||||||
|
func TestBoundaryTimerWaitAll(t *testing.T) {
|
||||||
|
engine, err := NewEngine(DefaultConfig())
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer engine.Close()
|
||||||
|
|
||||||
|
timerMgr := engine.TimerManager()
|
||||||
|
|
||||||
|
// 创建短延迟定时器
|
||||||
|
callback := func(L *glua.LState) int { return 0 }
|
||||||
|
L := glua.NewState()
|
||||||
|
defer L.Close()
|
||||||
|
|
||||||
|
fn := L.NewFunction(callback)
|
||||||
|
|
||||||
|
handle1, _ := timerMgr.At(50*time.Millisecond, fn, nil)
|
||||||
|
handle2, _ := timerMgr.At(100*time.Millisecond, fn, nil)
|
||||||
|
|
||||||
|
require.NotNil(t, handle1)
|
||||||
|
require.NotNil(t, handle2)
|
||||||
|
|
||||||
|
// 等待所有完成
|
||||||
|
completed := timerMgr.WaitAll(500 * time.Millisecond)
|
||||||
|
assert.True(t, completed)
|
||||||
|
|
||||||
|
// 活跃计数应该为 0
|
||||||
|
assert.Equal(t, int32(0), timerMgr.ActiveCount())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBoundarySharedDictAddExisting 测试 Add 已存在的键。
|
||||||
|
func TestBoundarySharedDictAddExisting(t *testing.T) {
|
||||||
|
dict := NewSharedDict("test", 10)
|
||||||
|
|
||||||
|
// 第一次 Add 应该成功
|
||||||
|
ok, _ := dict.Add("key", "value1", 0)
|
||||||
|
assert.True(t, ok)
|
||||||
|
|
||||||
|
// 第二次 Add 应该失败
|
||||||
|
ok, _ = dict.Add("key", "value2", 0)
|
||||||
|
assert.False(t, ok)
|
||||||
|
|
||||||
|
// 值应该仍然是第一个
|
||||||
|
val, _, _ := dict.Get("key")
|
||||||
|
assert.Equal(t, "value1", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBoundarySharedDictFlushExpired 测试批量清理过期条目。
|
||||||
|
func TestBoundarySharedDictFlushExpired(t *testing.T) {
|
||||||
|
dict := NewSharedDict("test", 10)
|
||||||
|
|
||||||
|
// 设置过期条目(它们会在 LRU 链表前端)
|
||||||
|
dict.Set("expire1", "value", 50*time.Millisecond)
|
||||||
|
dict.Set("expire2", "value", 50*time.Millisecond)
|
||||||
|
|
||||||
|
// 等待过期
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
// 设置不过期条目(它们会在 LRU 链表前端,过期条目在尾部)
|
||||||
|
dict.Set("keep1", "value", time.Hour)
|
||||||
|
dict.Set("keep2", "value", time.Hour)
|
||||||
|
|
||||||
|
// 清理过期条目 - evictExpired 从 LRU 尾部开始检查
|
||||||
|
_ = dict.FlushExpired()
|
||||||
|
|
||||||
|
// 验证过期条目被清理
|
||||||
|
assert.Equal(t, 2, dict.Size())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBoundarySharedDictFlushAll 测试清空字典。
|
||||||
|
func TestBoundarySharedDictFlushAll(t *testing.T) {
|
||||||
|
dict := NewSharedDict("test", 10)
|
||||||
|
|
||||||
|
// 添加多个条目
|
||||||
|
dict.Set("a", "1", 0)
|
||||||
|
dict.Set("b", "2", 0)
|
||||||
|
dict.Set("c", "3", 0)
|
||||||
|
|
||||||
|
assert.Equal(t, 3, dict.Size())
|
||||||
|
|
||||||
|
// 清空
|
||||||
|
_ = dict.FlushAll()
|
||||||
|
|
||||||
|
assert.Equal(t, 0, dict.Size())
|
||||||
|
assert.Equal(t, 10, dict.FreeSlots())
|
||||||
|
}
|
||||||
483
internal/lua/scheduler_test.go
Normal file
483
internal/lua/scheduler_test.go
Normal file
@ -0,0 +1,483 @@
|
|||||||
|
// Package lua 提供 Scheduler 模式测试。
|
||||||
|
//
|
||||||
|
// 该文件测试 Lua Scheduler 模式的功能:
|
||||||
|
// - setupSchedulerNgxAPI
|
||||||
|
// - setSchedulerMode / IsSchedulerMode
|
||||||
|
// - SchedulerUnsafeLocationAPI
|
||||||
|
// - SchedulerUnsafeVarAPI
|
||||||
|
// - SchedulerUnsafeCtxAPI
|
||||||
|
//
|
||||||
|
// 作者:xfy
|
||||||
|
package lua
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
glua "github.com/yuin/gopher-lua"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestSchedulerModeFlag 测试 Scheduler 模式标志。
|
||||||
|
func TestSchedulerModeFlag(t *testing.T) {
|
||||||
|
L := glua.NewState()
|
||||||
|
defer L.Close()
|
||||||
|
|
||||||
|
// 初始状态不是 scheduler 模式
|
||||||
|
if IsSchedulerMode(L) {
|
||||||
|
t.Error("Initial state should not be scheduler mode")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置 scheduler 模式
|
||||||
|
setSchedulerMode(L, true)
|
||||||
|
if !IsSchedulerMode(L) {
|
||||||
|
t.Error("Should be in scheduler mode after setting")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭 scheduler 模式
|
||||||
|
setSchedulerMode(L, false)
|
||||||
|
if IsSchedulerMode(L) {
|
||||||
|
t.Error("Should not be in scheduler mode after unsetting")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSetupSchedulerNgxAPI 测试 setupSchedulerNgxAPI 创建安全的 ngx API。
|
||||||
|
func TestSetupSchedulerNgxAPI(t *testing.T) {
|
||||||
|
engine, err := NewEngine(DefaultConfig())
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer engine.Close()
|
||||||
|
|
||||||
|
L := glua.NewState()
|
||||||
|
defer L.Close()
|
||||||
|
|
||||||
|
// 调用 setupSchedulerNgxAPI
|
||||||
|
setupSchedulerNgxAPI(L, engine)
|
||||||
|
|
||||||
|
// 验证 ngx 表存在
|
||||||
|
ngx := L.GetGlobal("ngx")
|
||||||
|
if ngx == glua.LNil {
|
||||||
|
t.Fatal("ngx table should exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
ngxTable, ok := ngx.(*glua.LTable)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("ngx should be a table")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证 scheduler 模式已设置
|
||||||
|
if !IsSchedulerMode(L) {
|
||||||
|
t.Error("Should be in scheduler mode after setupSchedulerNgxAPI")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证 ngx.shared 存在
|
||||||
|
shared := ngxTable.RawGetString("shared")
|
||||||
|
if shared == glua.LNil {
|
||||||
|
t.Error("ngx.shared should exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证 ngx.log 存在
|
||||||
|
log := ngxTable.RawGetString("log")
|
||||||
|
if log == glua.LNil {
|
||||||
|
t.Error("ngx.log should exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证 ngx.timer 存在
|
||||||
|
timer := ngxTable.RawGetString("timer")
|
||||||
|
if timer == glua.LNil {
|
||||||
|
t.Error("ngx.timer should exist")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSchedulerUnsafeLocationAPI 测试 Scheduler 模式下 ngx.location 不可用。
|
||||||
|
func TestSchedulerUnsafeLocationAPI(t *testing.T) {
|
||||||
|
L := glua.NewState()
|
||||||
|
defer L.Close()
|
||||||
|
|
||||||
|
ngx := L.NewTable()
|
||||||
|
L.SetGlobal("ngx", ngx)
|
||||||
|
|
||||||
|
// 注册不安全的 location API
|
||||||
|
RegisterSchedulerUnsafeLocationAPI(L, ngx)
|
||||||
|
|
||||||
|
// 验证 ngx.location 存在
|
||||||
|
location := ngx.RawGetString("location")
|
||||||
|
if location == glua.LNil {
|
||||||
|
t.Fatal("ngx.location should exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试调用 ngx.location.capture 应该返回错误
|
||||||
|
err := L.DoString(`
|
||||||
|
local ok, err = pcall(function()
|
||||||
|
ngx.location.capture("/test")
|
||||||
|
end)
|
||||||
|
if ok then
|
||||||
|
error("ngx.location.capture should fail in scheduler mode")
|
||||||
|
end
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSchedulerUnsafeVarAPI 测试 Scheduler 模式下 ngx.var 不可用。
|
||||||
|
func TestSchedulerUnsafeVarAPI(t *testing.T) {
|
||||||
|
L := glua.NewState()
|
||||||
|
defer L.Close()
|
||||||
|
|
||||||
|
ngx := L.NewTable()
|
||||||
|
L.SetGlobal("ngx", ngx)
|
||||||
|
|
||||||
|
// 注册不安全的 var API
|
||||||
|
RegisterSchedulerUnsafeVarAPI(L, ngx)
|
||||||
|
|
||||||
|
// 验证 ngx.var 存在
|
||||||
|
v := ngx.RawGetString("var")
|
||||||
|
if v == glua.LNil {
|
||||||
|
t.Fatal("ngx.var should exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试读取 ngx.var 应该返回错误
|
||||||
|
err := L.DoString(`
|
||||||
|
local ok, err = pcall(function()
|
||||||
|
local x = ngx.var.some_var
|
||||||
|
end)
|
||||||
|
if ok then
|
||||||
|
error("reading ngx.var should fail in scheduler mode")
|
||||||
|
end
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// 尝试写入 ngx.var 应该返回错误
|
||||||
|
err = L.DoString(`
|
||||||
|
local ok, err = pcall(function()
|
||||||
|
ngx.var.some_var = "value"
|
||||||
|
end)
|
||||||
|
if ok then
|
||||||
|
error("writing ngx.var should fail in scheduler mode")
|
||||||
|
end
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSchedulerUnsafeCtxAPI 测试 Scheduler 模式下 ngx.ctx 不可用。
|
||||||
|
func TestSchedulerUnsafeCtxAPISeparate(t *testing.T) {
|
||||||
|
L := glua.NewState()
|
||||||
|
defer L.Close()
|
||||||
|
|
||||||
|
ngx := L.NewTable()
|
||||||
|
L.SetGlobal("ngx", ngx)
|
||||||
|
|
||||||
|
// 注册不安全的 ctx API
|
||||||
|
RegisterSchedulerUnsafeCtxAPI(L, ngx)
|
||||||
|
|
||||||
|
// 验证 ngx.ctx 存在
|
||||||
|
ctx := ngx.RawGetString("ctx")
|
||||||
|
if ctx == glua.LNil {
|
||||||
|
t.Fatal("ngx.ctx should exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试读取 ngx.ctx 应该返回错误
|
||||||
|
err := L.DoString(`
|
||||||
|
local ok, err = pcall(function()
|
||||||
|
local x = ngx.ctx.some_key
|
||||||
|
end)
|
||||||
|
if ok then
|
||||||
|
error("reading ngx.ctx should fail in scheduler mode")
|
||||||
|
end
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// 尝试写入 ngx.ctx 应该返回错误
|
||||||
|
err = L.DoString(`
|
||||||
|
local ok, err = pcall(function()
|
||||||
|
ngx.ctx.some_key = "value"
|
||||||
|
end)
|
||||||
|
if ok then
|
||||||
|
error("writing ngx.ctx should fail in scheduler mode")
|
||||||
|
end
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSchedulerUnsafeLogAPI 测试 Scheduler 模式下 ngx.log 可用。
|
||||||
|
func TestSchedulerUnsafeLogAPI(t *testing.T) {
|
||||||
|
L := glua.NewState()
|
||||||
|
defer L.Close()
|
||||||
|
|
||||||
|
ngx := L.NewTable()
|
||||||
|
L.SetGlobal("ngx", ngx)
|
||||||
|
|
||||||
|
// 注册 Scheduler 日志 API
|
||||||
|
RegisterSchedulerLogAPI(L, ngx)
|
||||||
|
|
||||||
|
// 验证 ngx.log 存在
|
||||||
|
log := ngx.RawGetString("log")
|
||||||
|
if log == glua.LNil {
|
||||||
|
t.Fatal("ngx.log should exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ngx.log 应该可以调用(不会报错)
|
||||||
|
err := L.DoString(`
|
||||||
|
ngx.log(ngx.ERR, "test log message")
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSchedulerUnsafeReqAPI 测试 Scheduler 模式下 ngx.req 不可用。
|
||||||
|
func TestSchedulerUnsafeReqAPISeparate(t *testing.T) {
|
||||||
|
L := glua.NewState()
|
||||||
|
defer L.Close()
|
||||||
|
|
||||||
|
ngx := L.NewTable()
|
||||||
|
L.SetGlobal("ngx", ngx)
|
||||||
|
|
||||||
|
// 注册不安全的 req API
|
||||||
|
RegisterSchedulerUnsafeReqAPI(L, ngx)
|
||||||
|
|
||||||
|
// 验证 ngx["ngx.req"] 存在(RegisterUnsafeAPI 使用完整名称作为键)
|
||||||
|
req := ngx.RawGetString("ngx.req")
|
||||||
|
if req == glua.LNil {
|
||||||
|
t.Fatal("ngx[\"ngx.req\"] should exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试调用 ngx.req.get_method 应该返回错误
|
||||||
|
err := L.DoString(`
|
||||||
|
local ok, err = pcall(function()
|
||||||
|
ngx["ngx.req"].get_method()
|
||||||
|
end)
|
||||||
|
if ok then
|
||||||
|
error("ngx.req.get_method should fail in scheduler mode")
|
||||||
|
end
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSchedulerUnsafeRespAPI 测试 Scheduler 模式下 ngx.resp 不可用。
|
||||||
|
func TestSchedulerUnsafeRespAPISeparate(t *testing.T) {
|
||||||
|
L := glua.NewState()
|
||||||
|
defer L.Close()
|
||||||
|
|
||||||
|
ngx := L.NewTable()
|
||||||
|
L.SetGlobal("ngx", ngx)
|
||||||
|
|
||||||
|
// 注册不安全的 resp API
|
||||||
|
RegisterSchedulerUnsafeRespAPI(L, ngx)
|
||||||
|
|
||||||
|
// 验证 ngx["ngx.resp"] 存在(RegisterUnsafeAPI 使用完整名称作为键)
|
||||||
|
resp := ngx.RawGetString("ngx.resp")
|
||||||
|
if resp == glua.LNil {
|
||||||
|
t.Fatal("ngx[\"ngx.resp\"] should exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试调用 ngx.resp.get_headers 应该返回错误
|
||||||
|
err := L.DoString(`
|
||||||
|
local ok, err = pcall(function()
|
||||||
|
ngx["ngx.resp"].get_headers()
|
||||||
|
end)
|
||||||
|
if ok then
|
||||||
|
error("ngx.resp.get_headers should fail in scheduler mode")
|
||||||
|
end
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSchedulerSharedDictAvailable 测试 Scheduler 模式下 ngx.shared 可用。
|
||||||
|
func TestSchedulerSharedDictAvailable(t *testing.T) {
|
||||||
|
engine, err := NewEngine(DefaultConfig())
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer engine.Close()
|
||||||
|
|
||||||
|
// 创建共享字典
|
||||||
|
engine.CreateSharedDict("test_dict", 100)
|
||||||
|
|
||||||
|
L := glua.NewState()
|
||||||
|
defer L.Close()
|
||||||
|
|
||||||
|
// 调用 setupSchedulerNgxAPI
|
||||||
|
setupSchedulerNgxAPI(L, engine)
|
||||||
|
|
||||||
|
// 验证 ngx.shared.DICT 存在
|
||||||
|
err = L.DoString(`
|
||||||
|
if ngx.shared == nil then
|
||||||
|
error("ngx.shared should exist")
|
||||||
|
end
|
||||||
|
local dict = ngx.shared.DICT("test_dict")
|
||||||
|
if dict == nil then
|
||||||
|
error("ngx.shared.DICT('test_dict') should return dict")
|
||||||
|
end
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSchedulerTimerAvailable 测试 Scheduler 模式下 ngx.timer 可用。
|
||||||
|
func TestSchedulerTimerAvailable(t *testing.T) {
|
||||||
|
engine, err := NewEngine(DefaultConfig())
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer engine.Close()
|
||||||
|
|
||||||
|
L := glua.NewState()
|
||||||
|
defer L.Close()
|
||||||
|
|
||||||
|
// 调用 setupSchedulerNgxAPI
|
||||||
|
setupSchedulerNgxAPI(L, engine)
|
||||||
|
|
||||||
|
// 验证 ngx.timer 存在
|
||||||
|
err = L.DoString(`
|
||||||
|
if ngx.timer == nil then
|
||||||
|
error("ngx.timer should exist")
|
||||||
|
end
|
||||||
|
if ngx.timer.at == nil then
|
||||||
|
error("ngx.timer.at should exist")
|
||||||
|
end
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSchedulerModeIsolation 测试 Scheduler 模式与普通模式隔离。
|
||||||
|
func TestSchedulerModeIsolation(t *testing.T) {
|
||||||
|
// 创建两个 LState,一个在 scheduler 模式,一个不在
|
||||||
|
engine, err := NewEngine(DefaultConfig())
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer engine.Close()
|
||||||
|
|
||||||
|
// 普通 LState
|
||||||
|
normalL := glua.NewState()
|
||||||
|
defer normalL.Close()
|
||||||
|
|
||||||
|
// Scheduler LState
|
||||||
|
schedulerL := glua.NewState()
|
||||||
|
defer schedulerL.Close()
|
||||||
|
|
||||||
|
setupSchedulerNgxAPI(schedulerL, engine)
|
||||||
|
|
||||||
|
// 验证普通 LState 不是 scheduler 模式
|
||||||
|
if IsSchedulerMode(normalL) {
|
||||||
|
t.Error("Normal LState should not be in scheduler mode")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证 Scheduler LState 是 scheduler 模式
|
||||||
|
if !IsSchedulerMode(schedulerL) {
|
||||||
|
t.Error("Scheduler LState should be in scheduler mode")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSchedulerErrorMessages 测试 Scheduler 模式下错误消息格式。
|
||||||
|
func TestSchedulerErrorMessages(t *testing.T) {
|
||||||
|
L := glua.NewState()
|
||||||
|
defer L.Close()
|
||||||
|
|
||||||
|
ngx := L.NewTable()
|
||||||
|
L.SetGlobal("ngx", ngx)
|
||||||
|
|
||||||
|
RegisterSchedulerUnsafeLocationAPI(L, ngx)
|
||||||
|
RegisterSchedulerUnsafeVarAPI(L, ngx)
|
||||||
|
RegisterSchedulerUnsafeCtxAPI(L, ngx)
|
||||||
|
|
||||||
|
// 测试 location 错误消息
|
||||||
|
err := L.DoString(`
|
||||||
|
local ok, err = pcall(function()
|
||||||
|
ngx.location.capture("/test")
|
||||||
|
end)
|
||||||
|
if not string.find(err, "not available") then
|
||||||
|
error("Error message should mention 'not available': " .. tostring(err))
|
||||||
|
end
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// 测试 var 错误消息
|
||||||
|
err = L.DoString(`
|
||||||
|
local ok, err = pcall(function()
|
||||||
|
local x = ngx.var.test
|
||||||
|
end)
|
||||||
|
if not string.find(err, "not available") then
|
||||||
|
error("Error message should mention 'not available': " .. tostring(err))
|
||||||
|
end
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// 测试 ctx 错误消息
|
||||||
|
err = L.DoString(`
|
||||||
|
local ok, err = pcall(function()
|
||||||
|
local x = ngx.ctx.test
|
||||||
|
end)
|
||||||
|
if not string.find(err, "not available") then
|
||||||
|
error("Error message should mention 'not available': " .. tostring(err))
|
||||||
|
end
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSchedulerMultipleUnsafeCalls 测试多次调用不安全 API。
|
||||||
|
func TestSchedulerMultipleUnsafeCalls(t *testing.T) {
|
||||||
|
L := glua.NewState()
|
||||||
|
defer L.Close()
|
||||||
|
|
||||||
|
ngx := L.NewTable()
|
||||||
|
L.SetGlobal("ngx", ngx)
|
||||||
|
|
||||||
|
RegisterSchedulerUnsafeVarAPI(L, ngx)
|
||||||
|
|
||||||
|
// 多次调用都应该失败
|
||||||
|
err := L.DoString(`
|
||||||
|
for i = 1, 5 do
|
||||||
|
local ok, err = pcall(function()
|
||||||
|
ngx.var["key" .. i] = "value" .. i
|
||||||
|
end)
|
||||||
|
if ok then
|
||||||
|
error("Call " .. i .. " should have failed")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSchedulerNilValueHandling 测试 Scheduler 模式下处理 nil 值。
|
||||||
|
func TestSchedulerNilValueHandling(t *testing.T) {
|
||||||
|
L := glua.NewState()
|
||||||
|
defer L.Close()
|
||||||
|
|
||||||
|
ngx := L.NewTable()
|
||||||
|
L.SetGlobal("ngx", ngx)
|
||||||
|
|
||||||
|
RegisterSchedulerUnsafeVarAPI(L, ngx)
|
||||||
|
|
||||||
|
// 尝试读取 nil 键
|
||||||
|
err := L.DoString(`
|
||||||
|
local ok, err = pcall(function()
|
||||||
|
local x = ngx.var[nil]
|
||||||
|
end)
|
||||||
|
if ok then
|
||||||
|
error("Reading nil key should fail")
|
||||||
|
end
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSchedulerConcurrentAccess 测试并发访问 Scheduler API。
|
||||||
|
func TestSchedulerConcurrentAccess(t *testing.T) {
|
||||||
|
engine, err := NewEngine(DefaultConfig())
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer engine.Close()
|
||||||
|
|
||||||
|
// 创建多个 LState 并发访问
|
||||||
|
done := make(chan bool, 10)
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
go func() {
|
||||||
|
L := glua.NewState()
|
||||||
|
defer L.Close()
|
||||||
|
|
||||||
|
setupSchedulerNgxAPI(L, engine)
|
||||||
|
|
||||||
|
// 验证 scheduler 模式
|
||||||
|
if !IsSchedulerMode(L) {
|
||||||
|
t.Error("Should be in scheduler mode")
|
||||||
|
}
|
||||||
|
|
||||||
|
done <- true
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待所有 goroutine 完成
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
<-done
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user