139 lines
3.9 KiB
Go
139 lines
3.9 KiB
Go
package mods
|
||
|
||
import (
|
||
"os"
|
||
"path/filepath"
|
||
"testing"
|
||
"time"
|
||
)
|
||
|
||
// TestLuaRegister 脚本注册方块/物品/配方与事件(Mod开发指南.md §4)。
|
||
func TestLuaRegister(t *testing.T) {
|
||
reg := NewRegistry()
|
||
m := NewLuaMod("test_mod", reg)
|
||
script := `
|
||
api.register_block{ id = "ruby_ore", hardness = 3.0, light = 0 }
|
||
api.register_item{ id = "ruby", stack = 64 }
|
||
api.register_recipe{ type = "crafting_shaped", pattern = {"RRR","R R","RRR"}, result = {item = "ruby_block"} }
|
||
`
|
||
if err := m.Load(script); err != nil {
|
||
t.Fatalf("加载失败: %v", err)
|
||
}
|
||
entries := reg.Entries()
|
||
if len(entries) != 3 {
|
||
t.Fatalf("注册条目期望 3,实际 %d", len(entries))
|
||
}
|
||
if reg.Gen("test_mod") != 1 {
|
||
t.Fatalf("代数期望 1,实际 %d", reg.Gen("test_mod"))
|
||
}
|
||
// 方块数据完整性
|
||
found := false
|
||
for _, e := range entries {
|
||
if e.Key == "block:ruby_ore" {
|
||
found = true
|
||
if d, ok := e.Data.(map[string]any); !ok || d["hardness"] != 3.0 {
|
||
t.Fatalf("方块数据异常: %+v", e.Data)
|
||
}
|
||
}
|
||
}
|
||
if !found {
|
||
t.Fatal("缺少 block:ruby_ore 注册")
|
||
}
|
||
}
|
||
|
||
// TestHotReload 热重载:新脚本替换旧注册(generation 机制,热更新.md §4.1)。
|
||
func TestHotReload(t *testing.T) {
|
||
reg := NewRegistry()
|
||
m := NewLuaMod("hot_mod", reg)
|
||
if err := m.Load(`api.register_block{ id = "old_block" }`); err != nil {
|
||
t.Fatalf("首次加载失败: %v", err)
|
||
}
|
||
// 热重载:注册新方块
|
||
if err := m.Load(`api.register_block{ id = "new_block" }`); err != nil {
|
||
t.Fatalf("重载失败: %v", err)
|
||
}
|
||
if reg.Gen("hot_mod") != 2 {
|
||
t.Fatalf("重载后代数期望 2,实际 %d", reg.Gen("hot_mod"))
|
||
}
|
||
for _, e := range reg.Entries() {
|
||
if e.Key == "block:old_block" {
|
||
t.Fatal("旧代注册未回收")
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestReloadRollback 坏脚本回滚:保留旧版本(热更新.md §4.2)。
|
||
func TestReloadRollback(t *testing.T) {
|
||
reg := NewRegistry()
|
||
m := NewLuaMod("rollback_mod", reg)
|
||
if err := m.Load(`api.register_block{ id = "good_block" }`); err != nil {
|
||
t.Fatalf("首次加载失败: %v", err)
|
||
}
|
||
// 语法错误脚本:重载失败,旧版保留
|
||
if err := m.Load(`api.register_block{ id = `); err == nil {
|
||
t.Fatal("坏脚本应报错")
|
||
}
|
||
if reg.Gen("rollback_mod") != 1 {
|
||
t.Fatalf("回滚后代数应保持 1,实际 %d", reg.Gen("rollback_mod"))
|
||
}
|
||
found := false
|
||
for _, e := range reg.Entries() {
|
||
if e.Key == "block:good_block" {
|
||
found = true
|
||
}
|
||
}
|
||
if !found {
|
||
t.Fatal("回滚后旧注册丢失")
|
||
}
|
||
}
|
||
|
||
// TestSandbox 沙箱:os/io 库不可用(Mod开发指南.md §6)。
|
||
func TestSandbox(t *testing.T) {
|
||
reg := NewRegistry()
|
||
m := NewLuaMod("sandbox_mod", reg)
|
||
if err := m.Load(`os.exit(0)`); err == nil {
|
||
t.Fatal("沙箱应禁止 os 库")
|
||
}
|
||
}
|
||
|
||
// TestWatcher 文件监听热重载(热更新.md §4.5 防抖)。
|
||
func TestWatcher(t *testing.T) {
|
||
dir := t.TempDir()
|
||
path := filepath.Join(dir, "init.lua")
|
||
if err := os.WriteFile(path, []byte(`api.register_block{ id = "v1" }`), 0o644); err != nil {
|
||
t.Fatalf("写脚本失败: %v", err)
|
||
}
|
||
reg := NewRegistry()
|
||
m := NewLuaMod("watched_mod", reg)
|
||
reload := func() error {
|
||
b, err := os.ReadFile(path)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return m.Load(string(b))
|
||
}
|
||
w, err := NewWatcher()
|
||
if err != nil {
|
||
t.Fatalf("创建监听器失败: %v", err)
|
||
}
|
||
defer w.Close()
|
||
if err := w.WatchFile(path, reload); err != nil {
|
||
t.Fatalf("监听失败: %v", err)
|
||
}
|
||
go w.Run()
|
||
if err := reload(); err != nil {
|
||
t.Fatalf("初始加载失败: %v", err)
|
||
}
|
||
// 修改文件 → 防抖后自动重载
|
||
if err := os.WriteFile(path, []byte(`api.register_block{ id = "v2" }`), 0o644); err != nil {
|
||
t.Fatalf("写脚本失败: %v", err)
|
||
}
|
||
deadline := time.Now().Add(5 * time.Second)
|
||
for reg.Gen("watched_mod") != 2 && time.Now().Before(deadline) {
|
||
time.Sleep(50 * time.Millisecond)
|
||
}
|
||
if reg.Gen("watched_mod") != 2 {
|
||
t.Fatalf("文件监听未触发重载(代数 %d)", reg.Gen("watched_mod"))
|
||
}
|
||
}
|