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:
2026-09-05 22:13:57 +08:00
parent 6ce9b483fd
commit 7f484d7a71
20 changed files with 4660 additions and 0 deletions
+419
View File
@@ -0,0 +1,419 @@
package jscriptx
import (
"errors"
"fmt"
"strings"
"testing"
)
// srcLoader 是个最简单的内存 Loader,用来搭叠层测试。
type srcLoader struct {
src map[string]string
ver map[string]string
prepared bool
names []string
rebuilt int
fail error // 非 ErrScriptNotFound 的故障
}
func (l *srcLoader) Load(name string) (string, string, error) {
if l.fail != nil {
return "", "", l.fail
}
s, ok := l.src[name]
if !ok {
return "", "", fmt.Errorf("%w: %s", ErrScriptNotFound, name)
}
return s, l.ver[name], nil
}
func (l *srcLoader) Prepared() bool { return l.prepared }
func (l *srcLoader) Names() []string { return l.names }
func (l *srcLoader) Rebuild() error { l.rebuilt++; return nil }
func clsOf(v string) string {
return "export default class C { V() { return " + `"` + v + `"` + " } }"
}
func TestOverlay_后面的盖前面的(t *testing.T) {
base := &srcLoader{src: map[string]string{"A": clsOf("base-A"), "B": clsOf("base-B")}}
over := &srcLoader{src: map[string]string{"A": clsOf("over-A"), "C": clsOf("over-C")}}
o, err := Overlay(base, over)
if err != nil {
t.Fatal(err)
}
for _, tc := range []struct{ name, want string }{
{"A", "over-A"}, // 两层都有,取后面那层
{"B", "base-B"}, // 只有前面那层有
{"C", "over-C"}, // 只有后面那层有
} {
src, _, err := o.Load(tc.name)
if err != nil {
t.Fatalf("%s: %v", tc.name, err)
}
if !strings.Contains(src, tc.want) {
t.Errorf("%s 取到的不是 %s: %s", tc.name, tc.want, src)
}
}
}
func TestOverlay_都没有时报脚本不存在(t *testing.T) {
o, err := Overlay(&srcLoader{}, &srcLoader{})
if err != nil {
t.Fatal(err)
}
_, _, err = o.Load("X")
if !errors.Is(err, ErrScriptNotFound) {
t.Fatalf("要能被 ErrScriptNotFound 匹配上: %v", err)
}
if !strings.Contains(err.Error(), "X") {
t.Errorf("报错里要带脚本名: %v", err)
}
}
// 某一层出故障(不是"脚本不存在")时要立刻报出来,
// 不能被下面那层的结果悄悄盖掉——否则线上换了实现都不知道。
func TestOverlay_故障不被下层掩盖(t *testing.T) {
boom := errors.New("数据库连不上")
base := &srcLoader{src: map[string]string{"A": clsOf("base-A")}}
over := &srcLoader{fail: boom}
o, _ := Overlay(base, over)
_, _, err := o.Load("A")
if !errors.Is(err, boom) {
t.Fatalf("故障被吞了: %v", err)
}
}
// 版本号要带上层号:覆盖层撤掉后落回下层,两层版本号万一撞上,
// 不带层号引擎就看不出脚本已经换了人。
func TestOverlay_版本号带层号(t *testing.T) {
base := &srcLoader{
src: map[string]string{"A": clsOf("base-A")},
ver: map[string]string{"A": "v1"},
}
over := &srcLoader{
src: map[string]string{"A": clsOf("over-A")},
ver: map[string]string{"A": "v1"}, // 故意跟 base 撞
}
o, _ := Overlay(base, over)
_, v1, _ := o.Load("A")
delete(over.src, "A") // 撤掉覆盖
_, v2, _ := o.Load("A")
if v1 == v2 {
t.Fatalf("落回下层后版本号没变(都是 %q),引擎会继续用旧的编译结果", v1)
}
}
// 成员不给版本号时要原样透传空串,让引擎去按源码算哈希。
func TestOverlay_空版本号原样透传(t *testing.T) {
o, _ := Overlay(&srcLoader{src: map[string]string{"A": clsOf("a")}})
if _, v, _ := o.Load("A"); v != "" {
t.Errorf("版本号该是空的,拿到 %q", v)
}
}
func TestOverlay_Prepared必须一致(t *testing.T) {
yes := &srcLoader{prepared: true}
no := &srcLoader{prepared: false}
if _, err := Overlay(yes, no); err == nil {
t.Error("混着来该报错")
}
if _, err := Overlay(yes, yes); err != nil {
t.Errorf("都是打好包的该通过: %v", err)
}
o, err := Overlay(no, no)
if err != nil {
t.Fatalf("都是原始源码该通过: %v", err)
}
if o.Prepared() {
t.Error("Prepared 该跟成员一致")
}
}
func TestOverlay_参数校验(t *testing.T) {
if _, err := Overlay(); err == nil {
t.Error("一个都不给该报错")
}
if _, err := Overlay(&srcLoader{}, nil); err == nil {
t.Error("给 nil 该报错")
}
}
func TestOverlay_Names去重排序(t *testing.T) {
base := &srcLoader{names: []string{"B/b", "A/a"}}
over := &srcLoader{names: []string{"A/a", "C/c"}}
plain := LoaderFunc(func(string) (string, string, error) { return "", "", nil }) // 没有 Names
o, _ := Overlay(base, over, plain)
got := strings.Join(o.Names(), ",")
if got != "A/a,B/b,C/c" {
t.Errorf("Names() = %s", got)
}
}
func TestOverlay_Rebuild走遍每一层(t *testing.T) {
base, over := &srcLoader{}, &srcLoader{}
o, _ := Overlay(base, over, LoaderFunc(func(string) (string, string, error) { return "", "", nil }))
if err := o.Rebuild(); err != nil {
t.Fatal(err)
}
if base.rebuilt != 1 || over.rebuilt != 1 {
t.Errorf("每层都该重建一次: base=%d over=%d", base.rebuilt, over.rebuilt)
}
}
// WithLoader 给多个时自动叠起来,跟 Overlay 的语义一致。
func TestWithLoader_多个自动叠层(t *testing.T) {
base := &srcLoader{src: map[string]string{"A": clsOf("base-A"), "B": clsOf("base-B")}}
over := &srcLoader{src: map[string]string{"A": clsOf("over-A")}}
e, err := New(WithLoader(base, over))
if err != nil {
t.Fatal(err)
}
defer e.Close()
mustCall := func(name, want string) {
t.Helper()
s, err := e.Script(name)
if err != nil {
t.Fatal(err)
}
got, err := s.Call(t.Context(), "V")
if err != nil {
t.Fatal(err)
}
if got != want {
t.Errorf("%s = %v, 想要 %s", name, got, want)
}
}
mustCall("A", "over-A")
mustCall("B", "base-B")
}
// 多次调用 WithLoader 继续往后叠,效果跟一次传多个一样。
func TestWithLoader_多次调用继续叠(t *testing.T) {
base := &srcLoader{src: map[string]string{"A": clsOf("base-A")}}
over := &srcLoader{src: map[string]string{"A": clsOf("over-A")}}
e, err := New(WithLoader(base), WithLoader(over))
if err != nil {
t.Fatal(err)
}
defer e.Close()
s, err := e.Script("A")
if err != nil {
t.Fatal(err)
}
if got, _ := s.Call(t.Context(), "V"); got != "over-A" {
t.Errorf("后面那次该盖住前面的,拿到 %v", got)
}
}
// 只给一个时不该套上 Overlay,Prepared 之类的行为要原样保留。
func TestWithLoader_单个不套壳(t *testing.T) {
l := &srcLoader{prepared: true}
e, err := New(WithLoader(l))
if err != nil {
t.Fatal(err)
}
defer e.Close()
if e.loader != Loader(l) {
t.Errorf("单个 Loader 该原样用,拿到 %T", e.loader)
}
}
func TestWithLoader_层间Prepared不一致时New报错(t *testing.T) {
_, err := New(WithLoader(&srcLoader{prepared: true}, &srcLoader{prepared: false}))
if err == nil {
t.Fatal("该报错")
}
if !strings.Contains(err.Error(), "Prepared") {
t.Errorf("报错要说清楚是 Prepared 不一致: %v", err)
}
}
// tagged 是带版本标签的内存 Loader。
type tagged struct {
*srcLoader
ver string
}
func (t *tagged) Version() string { return t.ver }
func TestVersion_点名要哪一层(t *testing.T) {
v1 := &tagged{&srcLoader{src: map[string]string{"A": clsOf("v1-A"), "OnlyV1": clsOf("x")}}, "v1"}
v2 := &tagged{&srcLoader{src: map[string]string{"A": clsOf("v2-A")}}, "v2"}
o, err := Overlay(v1, v2)
if err != nil {
t.Fatal(err)
}
for _, tc := range []struct{ name, want string }{
{"A", "v2-A"}, // 不点名:上层盖下层
{"A@v2", "v2-A"}, // 点名上层
{"A@v1", "v1-A"}, // 点名下层
{"OnlyV1@v1", "x"}, // 点名的那层独有的
} {
src, _, err := o.Load(tc.name)
if err != nil {
t.Fatalf("%s: %v", tc.name, err)
}
if !strings.Contains(src, tc.want) {
t.Errorf("%s 该拿到 %s: %s", tc.name, tc.want, src)
}
}
}
// 点名是"只认这一层":那层没有就报错,不能掉到别的层去。
func TestVersion_点名后不回落(t *testing.T) {
v1 := &tagged{&srcLoader{src: map[string]string{"A": clsOf("v1-A")}}, "v1"}
v2 := &tagged{&srcLoader{}, "v2"} // 空的
o, _ := Overlay(v1, v2)
_, _, err := o.Load("A@v2")
if !errors.Is(err, ErrScriptNotFound) {
t.Fatalf("v2 层没有 A,该报不存在而不是回落到 v1: %v", err)
}
}
func TestVersion_点名不存在的版本(t *testing.T) {
v1 := &tagged{&srcLoader{src: map[string]string{"A": clsOf("a")}}, "v1"}
o, _ := Overlay(v1)
_, _, err := o.Load("A@v9")
if !errors.Is(err, ErrScriptNotFound) {
t.Fatalf("该报不存在: %v", err)
}
if !strings.Contains(err.Error(), "v1") {
t.Errorf("报错该提示有哪些版本: %v", err)
}
}
// 点名不同的层,版本号里的层号要跟着变,两份实现才能各自缓存。
func TestVersion_点名时版本号带层号(t *testing.T) {
v1 := &tagged{&srcLoader{
src: map[string]string{"A": clsOf("v1-A")},
ver: map[string]string{"A": "same"},
}, "v1"}
v2 := &tagged{&srcLoader{
src: map[string]string{"A": clsOf("v2-A")},
ver: map[string]string{"A": "same"}, // 故意撞
}, "v2"}
o, _ := Overlay(v1, v2)
_, a, _ := o.Load("A@v1")
_, b, _ := o.Load("A@v2")
if a == b {
t.Fatalf("两层的版本号该区分开,都是 %q", a)
}
}
func TestVersion_标签校验(t *testing.T) {
a := &tagged{&srcLoader{}, "v1"}
b := &tagged{&srcLoader{}, "v1"}
if _, err := Overlay(a, b); err == nil {
t.Error("标签重名该报错")
}
bad := &tagged{&srcLoader{}, "v@1"}
if _, err := Overlay(bad); err == nil {
t.Error("标签里带分隔符该报错")
}
}
func TestVersion_Tag给任意Loader贴标签(t *testing.T) {
plain := &srcLoader{src: map[string]string{"A": clsOf("plain-A")}}
other := &srcLoader{src: map[string]string{"A": clsOf("other-A")}}
o, err := Overlay(plain, Tag("hotfix", other))
if err != nil {
t.Fatal(err)
}
src, _, err := o.Load("A@hotfix")
if err != nil {
t.Fatal(err)
}
if !strings.Contains(src, "other-A") {
t.Errorf("该拿到贴了标签那层的: %s", src)
}
if got := strings.Join(o.Versions(), ","); got != "hotfix" {
t.Errorf("Versions() = %s", got)
}
}
// 贴标签不该改变底下 Loader 的 Prepared,否则叠层的一致性校验会误判。
func TestVersion_Tag透传Prepared(t *testing.T) {
if !isPrepared(Tag("v1", &srcLoader{prepared: true})) {
t.Error("底下是 prepared 的,贴完标签该还是")
}
if isPrepared(Tag("v1", &srcLoader{prepared: false})) {
t.Error("底下不是 prepared 的,贴完标签不该变成是")
}
// 一层贴了标签一层没贴,Prepared 一致就该能叠
if _, err := Overlay(&srcLoader{prepared: true}, Tag("v1", &srcLoader{prepared: true})); err != nil {
t.Errorf("该能叠: %v", err)
}
}
func TestVersion_拆名字(t *testing.T) {
for _, tc := range []struct {
in, bare, ver string
ok bool
}{
{"Foo/Bar@v1", "Foo/Bar", "v1", true},
{"Foo/Bar", "Foo/Bar", "", false},
{"@v1", "@v1", "", false}, // 左边空着,不算点名
{"Foo@", "Foo@", "", false}, // 右边空着,不算点名
{"a@b@v1", "a@b", "v1", true}, // 用最后一个分隔符
} {
bare, ver, ok := splitVersion(tc.in)
if bare != tc.bare || ver != tc.ver || ok != tc.ok {
t.Errorf("splitVersion(%q) = %q,%q,%v;想要 %q,%q,%v",
tc.in, bare, ver, ok, tc.bare, tc.ver, tc.ok)
}
}
}
// 走完整链路:从 Engine 点名要某个版本。
func TestVersion_从Engine点名(t *testing.T) {
v1 := &tagged{&srcLoader{src: map[string]string{"A": clsOf("v1-A")}}, "v1"}
v2 := &tagged{&srcLoader{src: map[string]string{"A": clsOf("v2-A")}}, "v2"}
e, err := New(WithLoader(v1, v2))
if err != nil {
t.Fatal(err)
}
defer e.Close()
call := func(name string) any {
t.Helper()
s, err := e.Script(name)
if err != nil {
t.Fatal(err)
}
got, err := s.Call(t.Context(), "V")
if err != nil {
t.Fatal(err)
}
return got
}
if got := call("A"); got != "v2-A" {
t.Errorf("不点名该拿到上层: %v", got)
}
if got := call("A@v1"); got != "v1-A" {
t.Errorf("点名 v1 该拿到 v1: %v", got)
}
// 两个版本各自缓存,互不干扰
if got := call("A@v2"); got != "v2-A" {
t.Errorf("点名 v2 该拿到 v2: %v", got)
}
}