改完之后「某个行为在哪测」就是查同名文件:
lazyglobal_test.go → engine_globals_test.go
static_test.go → script_static_test.go
scope_pool_test.go → script_vm_test.go 它测的是 VM 池,不是作用域
esmwrap_test.go → bundle_finalize_test.go
missing_global_test.go → errors_hints_test.go
entry_test.go → bundle_entry_test.go 并收下 bundle_test.go 里
那三个 TestEntry_*
删掉 5 组复制粘贴。其中 TestNew_同实例调用串行 和 TestInstance_调用串行 **函数体
完全一致**,只差 mustCompile 的文件名参数 counter.js / counter.ts。Instance 的
测试全部归 instance_test.go 并统一前缀成 TestInstance_,scope_test.go 只留作用域。
vmstate_test.go + govalue_test.go → goja_facts_test.go。这两个测的都是 **goja
本身**,不是这个库:Program 能不能给多个 Runtime 共用、顶层变量跟不跟 Runtime 走、
Go 的切片进到脚本里是不是真数组。它们是「为什么这个库要这么设计」的实验记录,
goja 行为变了会先红。原来那个名字看不出这层定位。
helper 集中到 helper_test.go:newEngine / mustCompile 被十几个文件依赖,原来住在
script_test.go 里,那个名字暗示「测 Script」,找不到。
caller_example_test.go 并进 caller_test.go——同一个类型没必要两个文件。
overlay_test.go 和 script_vm_test.go 保持内部测试,各写了一句为什么:前者要读
Engine.loader 和 isPrepared,后者要直接断言 vmHandle.scoped。我试过把 overlay
挪成外部测试,挪不动。
424 lines
12 KiB
Go
424 lines
12 KiB
Go
package jscriptx
|
||
|
||
// 这是**内部测试**(package jscriptx,不是 jscriptx_test):有几条要读
|
||
// Engine.loader 这个未导出字段、调 isPrepared 这个未导出函数,验的是"多层
|
||
// Loader 合成之后引擎实际拿到的是什么",从外面看不见。
|
||
|
||
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)
|
||
}
|
||
}
|