Files
jscriptx/overlay_test.go
T
what 7c9a70712e test: 测试文件跟源文件同名,删掉复制粘贴出来的重复
改完之后「某个行为在哪测」就是查同名文件:

  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
挪成外部测试,挪不动。
2026-09-10 15:39:25 +08:00

424 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}
}