test: 引擎核心的测试与基准
除了常规用例,有几组是专门钉住「引擎的既定事实」的,改动时会先红:
- vmstate_test goja 的硬限制:非原始值不是 goroutine 安全的、对象跨不了
Runtime。其中一条是哨兵——goja 哪天允许对象跨 Runtime 了,
它会失败,提醒我们可以简化设计。
- govalue_test Go 的切片/map 进到脚本里长什么样。这不是本库的行为而是
goja 的,但脚本作者照着它写代码,变了会静默走错分支。
- safety_test 并发下坏 VM 不会被别的 goroutine 捡到。
- bench_gobind 脚本碰 Go 对象的单次开销,README 性能一节的数据来源。
missing_global_test 里有一条断言 typeof setTimeout === "undefined":
定时器必须保持未定义,库里的特性探测才能正常降级,防止以后有人把桩加回来。
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
package jscriptx_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.fsdpf.net/go/jscriptx"
|
||||
)
|
||||
|
||||
// 脚本碰到 goja 没有的全局时,光一句 ReferenceError 会把人带偏——
|
||||
// 外部作者看到 "setTimeout is not defined" 会以为是打包漏了依赖,跑去装 polyfill。
|
||||
// 这组用例守着那句补充说明。
|
||||
func TestMissingGlobal_报错带上人话(t *testing.T) {
|
||||
cases := []struct {
|
||||
代码 string
|
||||
要点 []string
|
||||
}{
|
||||
{`setTimeout(() => {}, 10)`, []string{"没有事件循环", "Job / Cron"}},
|
||||
{`clearInterval(1)`, []string{"定时器用不了"}},
|
||||
{`structuredClone({})`, []string{"JSON.parse", "深拷贝"}},
|
||||
{`const 名 = "lodash"; require(名)`, []string{"ES 模块", "import"}}, // 动态名字,esbuild 打包期解析不了,才会漏到运行期
|
||||
{`process.env.X`, []string{"不跑在 Node"}},
|
||||
{`document.body`, []string{"没有 DOM"}},
|
||||
{`fetch("http://x")`, []string{"同步执行", "Go 侧"}},
|
||||
}
|
||||
|
||||
e, err := jscriptx.New(jscriptx.WithLogger(nil))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer e.Close()
|
||||
|
||||
for _, c := range cases {
|
||||
s, err := e.Compile("m.ts", "export default class C { Run() { "+c.代码+" } }")
|
||||
if err != nil {
|
||||
t.Fatalf("%s 编译失败: %v", c.代码, err)
|
||||
}
|
||||
ctx := context.Background()
|
||||
obj, err := s.New(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("%s 实例化失败: %v", c.代码, err)
|
||||
}
|
||||
_, err = obj.Call(ctx, "Run")
|
||||
obj.Close()
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("%s 该报错", c.代码)
|
||||
continue
|
||||
}
|
||||
for _, want := range c.要点 {
|
||||
if !strings.Contains(err.Error(), want) {
|
||||
t.Errorf("%s 的错误里该提到 %q\n实际: %v", c.代码, want, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 只认 goja 原样的那句,别把脚本自己抛的同名字符串也当成缺全局。
|
||||
func TestMissingGlobal_不误伤(t *testing.T) {
|
||||
e, err := jscriptx.New(jscriptx.WithLogger(nil))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer e.Close()
|
||||
|
||||
s, err := e.Compile("m.ts", `
|
||||
export default class C {
|
||||
Run() { throw new Error("ReferenceError: setTimeout is not defined") }
|
||||
}`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ctx := context.Background()
|
||||
obj, _ := s.New(ctx)
|
||||
defer obj.Close()
|
||||
|
||||
_, err = obj.Call(ctx, "Run")
|
||||
if err == nil {
|
||||
t.Fatal("该报错")
|
||||
}
|
||||
// 脚本自己抛的,goja 会加上 "Error: " 前缀,前缀对不上就不该补提示
|
||||
if strings.Contains(err.Error(), "没有事件循环") {
|
||||
t.Errorf("不该给脚本自己抛的异常补提示: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 定义成"一调就报错"的桩会破坏特性探测,所以这些全局必须保持 undefined。
|
||||
func TestMissingGlobal_特性探测仍然可用(t *testing.T) {
|
||||
e, err := jscriptx.New(jscriptx.WithLogger(nil))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer e.Close()
|
||||
|
||||
s, err := e.Compile("m.ts", `
|
||||
export default class C {
|
||||
Run() {
|
||||
// 库里常见的降级写法
|
||||
return typeof setTimeout === "undefined" ? "走降级分支" : "走定时器分支"
|
||||
}
|
||||
}`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ctx := context.Background()
|
||||
obj, _ := s.New(ctx)
|
||||
defer obj.Close()
|
||||
|
||||
got, err := obj.Call(ctx, "Run")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != "走降级分支" {
|
||||
t.Errorf("定时器该是 undefined,好让库能降级,拿到 %#v", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user