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
挪成外部测试,挪不动。
This commit is contained in:
-190
@@ -3,8 +3,6 @@ package jscriptx_test
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -27,51 +25,6 @@ const deviceScript = `
|
||||
}
|
||||
`
|
||||
|
||||
// 实例独占 VM,this.xxx 在它活着期间跨调用保持。
|
||||
func TestInstance_状态跨调用保持(t *testing.T) {
|
||||
e := newEngine(t)
|
||||
s := mustCompile(t, e, "device.ts", deviceScript)
|
||||
ctx := context.Background()
|
||||
|
||||
obj, err := s.New(ctx, "device-A", "温控器")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer obj.Close()
|
||||
|
||||
for i, want := range []string{"device-A#1:m1", "device-A#2:m2", "device-A#3:m3"} {
|
||||
got, err := obj.Call(ctx, "onMessage", fmt.Sprintf("m%d", i+1))
|
||||
if err != nil {
|
||||
t.Fatalf("第 %d 条失败: %v", i+1, err)
|
||||
}
|
||||
if got != want {
|
||||
t.Errorf("第 %d 条 = %#v, want %q", i+1, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 不同实例互不相干,这是并发的基础。
|
||||
func TestInstance_互相隔离(t *testing.T) {
|
||||
e := newEngine(t)
|
||||
s := mustCompile(t, e, "device.ts", deviceScript)
|
||||
ctx := context.Background()
|
||||
|
||||
a, _ := s.New(ctx, "device-A", "型号A")
|
||||
defer a.Close()
|
||||
b, _ := s.New(ctx, "device-B", "型号B")
|
||||
defer b.Close()
|
||||
|
||||
a.Call(ctx, "onMessage", "x")
|
||||
a.Call(ctx, "onMessage", "x")
|
||||
got, err := b.Call(ctx, "onMessage", "x")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != "device-B#1:x" {
|
||||
t.Errorf("B 被 A 污染: %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
// 作用域的核心用途:同一个 ctx 下的多个脚本共享扩展。
|
||||
func TestScope_多脚本共享扩展(t *testing.T) {
|
||||
e := newEngine(t)
|
||||
@@ -289,149 +242,6 @@ func TestScope_取回扩展(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// 同一个实例的调用串行,保护 this.xxx 不错乱。
|
||||
func TestInstance_调用串行(t *testing.T) {
|
||||
e := newEngine(t)
|
||||
s := mustCompile(t, e, "counter.ts", `
|
||||
export default class C {
|
||||
constructor() { this.n = 0 }
|
||||
bump() { this.n++; return this.n }
|
||||
total() { return this.n }
|
||||
}
|
||||
`)
|
||||
|
||||
obj, err := s.New(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer obj.Close()
|
||||
|
||||
const goroutines, per = 8, 100
|
||||
var wg sync.WaitGroup
|
||||
for g := 0; g < goroutines; g++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < per; i++ {
|
||||
if _, err := obj.Call(context.Background(), "bump"); err != nil {
|
||||
t.Errorf("%v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
got, _ := obj.Call(context.Background(), "total")
|
||||
if got.(int64) != goroutines*per {
|
||||
t.Errorf("串行保证失效, total = %#v, want %d", got, goroutines*per)
|
||||
}
|
||||
}
|
||||
|
||||
// 多个实例并发跑,各自的状态正确。
|
||||
func TestInstance_多实例并发(t *testing.T) {
|
||||
e := newEngine(t)
|
||||
s := mustCompile(t, e, "device.ts", deviceScript)
|
||||
|
||||
const devices, messages = 16, 30
|
||||
var wg sync.WaitGroup
|
||||
for d := 0; d < devices; d++ {
|
||||
id := fmt.Sprintf("device-%d", d)
|
||||
wg.Add(1)
|
||||
go func(id string) {
|
||||
defer wg.Done()
|
||||
obj, err := s.New(context.Background(), id, "型号")
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
return
|
||||
}
|
||||
defer obj.Close()
|
||||
for m := 0; m < messages; m++ {
|
||||
got, err := obj.Call(context.Background(), "onMessage", "m")
|
||||
if err != nil {
|
||||
t.Errorf("%s: %v", id, err)
|
||||
return
|
||||
}
|
||||
want := fmt.Sprintf("%s#%d:m", id, m+1)
|
||||
if got != want {
|
||||
t.Errorf("%s 第 %d 条 = %#v, want %q", id, m+1, got, want)
|
||||
return
|
||||
}
|
||||
}
|
||||
}(id)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// 脚本出错会丢 this 上的状态,但扩展里的东西还在。
|
||||
func TestInstance_出错后状态归零(t *testing.T) {
|
||||
e := newEngine(t, jscriptx.WithTimeout(80*time.Millisecond))
|
||||
s := mustCompile(t, e, "mix.ts", `
|
||||
export default class M {
|
||||
constructor() { this.n = 0 }
|
||||
bump() { store.Incr("kept", 1); return ++this.n }
|
||||
spin() { while (true) {} }
|
||||
}
|
||||
`)
|
||||
|
||||
st := testext.New()
|
||||
ctx := jscriptx.WithScope(context.Background(), jscriptx.ScopeExtensions(st))
|
||||
obj, err := s.New(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer obj.Close()
|
||||
|
||||
obj.Call(ctx, "bump")
|
||||
obj.Call(ctx, "bump")
|
||||
|
||||
if _, err := obj.Call(ctx, "spin"); !errors.Is(err, jscriptx.ErrTimeout) {
|
||||
t.Fatalf("want ErrTimeout, got %v", err)
|
||||
}
|
||||
if obj.Resets() != 1 {
|
||||
t.Errorf("Resets = %d, want 1", obj.Resets())
|
||||
}
|
||||
|
||||
got, err := obj.Call(ctx, "bump")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.(int64) != 1 {
|
||||
t.Errorf("this 上的状态应该归零, got %#v", got)
|
||||
}
|
||||
// 扩展在 Go 侧,不受 VM 重建影响
|
||||
if n := toNumber(t, st.Get("kept")); n != 3 {
|
||||
t.Errorf("扩展里的状态不该丢, got %v want 3", n)
|
||||
}
|
||||
}
|
||||
|
||||
// 脚本关闭后实例失效。
|
||||
func TestInstance_脚本关闭后失效(t *testing.T) {
|
||||
e := newEngine(t)
|
||||
s := mustCompile(t, e, "device.ts", deviceScript)
|
||||
obj, _ := s.New(context.Background(), "d", "m")
|
||||
|
||||
s.Close()
|
||||
if _, err := obj.Call(context.Background(), "onMessage", "x"); !errors.Is(err, jscriptx.ErrClosed) {
|
||||
t.Errorf("want ErrClosed, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// toNumber 把脚本返回的数字统一成 float64:goja 会按数值是否为整数
|
||||
// 导出成 int64 或 float64,测试里不必纠结这个差异。
|
||||
func toNumber(t *testing.T, v any) float64 {
|
||||
t.Helper()
|
||||
switch n := v.(type) {
|
||||
case int64:
|
||||
return float64(n)
|
||||
case float64:
|
||||
return n
|
||||
default:
|
||||
t.Fatalf("不是数字: %#v", v)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// 作用域实例重建 VM 时,要回到它**创建时**那个作用域。
|
||||
//
|
||||
// Instance 的 VM 超时后会被丢弃,下次调用重建(见 Resets)。重建走的是 i.scope,
|
||||
|
||||
Reference in New Issue
Block a user