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,172 @@
|
||||
package jscriptx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// recorder 是个带状态的扩展,用来验证作用域之间有没有串。
|
||||
type recorder struct {
|
||||
mu sync.Mutex
|
||||
tag string
|
||||
seen []string
|
||||
}
|
||||
|
||||
func (*recorder) Name() string { return "rec" }
|
||||
func (*recorder) Module() (string, string) { return "", "" }
|
||||
func (r *recorder) Bindings() map[string]any {
|
||||
return map[string]any{
|
||||
"tag": func() string { return r.tag },
|
||||
"note": func(m string) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
r.seen = append(r.seen, m)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func scoped(t *testing.T, r *recorder) context.Context {
|
||||
t.Helper()
|
||||
return WithScope(context.Background(), ScopeExtensions(r))
|
||||
}
|
||||
|
||||
// Script.Call 走的是共享池,以前不注入作用域扩展,脚本顶层 import 直接
|
||||
// ReferenceError。现在带作用域就单造一个 VM。
|
||||
func TestScopePool_池化调用也能拿到扩展(t *testing.T) {
|
||||
e, _ := New(WithLogger(nil))
|
||||
defer e.Close()
|
||||
|
||||
s, err := e.Compile("s.ts", `
|
||||
rec.note("顶层:" + rec.tag())
|
||||
export default function () { return rec.tag() }`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
r := &recorder{tag: "A"}
|
||||
got, err := s.Call(scoped(t, r), "")
|
||||
if err != nil {
|
||||
t.Fatalf("带作用域的池化调用该能用扩展: %v", err)
|
||||
}
|
||||
if got != "A" {
|
||||
t.Errorf("拿到 %#v", got)
|
||||
}
|
||||
if len(r.seen) != 1 || r.seen[0] != "顶层:A" {
|
||||
t.Errorf("模块顶层没跑或没拿到扩展: %v", r.seen)
|
||||
}
|
||||
}
|
||||
|
||||
// 关键:带作用域的 VM 用完必须丢,不能回池——回池就把上一个作用域的扩展
|
||||
// 漏给下一个调用了。
|
||||
func TestScopePool_作用域之间不串(t *testing.T) {
|
||||
e, _ := New(WithLogger(nil))
|
||||
defer e.Close()
|
||||
|
||||
s, _ := e.Compile("s.ts", `export default function () { return rec.tag() }`)
|
||||
|
||||
for _, want := range []string{"A", "B", "C"} {
|
||||
r := &recorder{tag: want}
|
||||
got, err := s.Call(scoped(t, r), "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != want {
|
||||
t.Fatalf("拿到的是别的作用域的扩展: 想要 %q,拿到 %#v", want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 不带作用域的调用照旧走池,行为不变。
|
||||
func TestScopePool_无作用域仍然走池(t *testing.T) {
|
||||
e, _ := New(WithLogger(nil))
|
||||
defer e.Close()
|
||||
|
||||
s, _ := e.Compile("s.ts", `export default function () { return 1 }`)
|
||||
ctx := context.Background()
|
||||
|
||||
for range 5 {
|
||||
if _, err := s.Call(ctx, ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
// 第一次建一个,后面四次都该命中池
|
||||
if st := s.Stats(); st.Dropped != 0 {
|
||||
t.Errorf("不带作用域不该丢 VM,Dropped = %d", st.Dropped)
|
||||
}
|
||||
}
|
||||
|
||||
// 带作用域的 VM 每次都丢,不占池子。
|
||||
func TestScopePool_带作用域的VM不回池(t *testing.T) {
|
||||
e, _ := New(WithLogger(nil))
|
||||
defer e.Close()
|
||||
|
||||
s, _ := e.Compile("s.ts", `export default function () { return rec.tag() }`)
|
||||
|
||||
before := s.Stats().Dropped
|
||||
for range 3 {
|
||||
if _, err := s.Call(scoped(t, &recorder{tag: "X"}), ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if got := s.Stats().Dropped - before; got != 3 {
|
||||
t.Errorf("三次带作用域的调用该丢三个 VM,实际丢了 %d", got)
|
||||
}
|
||||
}
|
||||
|
||||
// 没有扩展、只有 key 的作用域也不能走池:脚本能读到 scope.key,
|
||||
// 池化 VM 会把上一个作用域的 key 漏给下一个调用。
|
||||
func TestScopePool_只有key的作用域也不走池(t *testing.T) {
|
||||
e, _ := New(WithLogger(nil))
|
||||
defer e.Close()
|
||||
|
||||
s, _ := e.Compile("s.ts", `export default function () { return scope.key }`)
|
||||
|
||||
for _, want := range []string{"k1", "k2", "k3"} {
|
||||
ctx := WithScope(context.Background(), ScopeKey(want))
|
||||
got, err := s.Call(ctx, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != want {
|
||||
t.Fatalf("拿到的是别的作用域的 key: 想要 %q,拿到 %#v", want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 并发下各作用域看到的必须是自己那份。
|
||||
func TestScopePool_并发不串(t *testing.T) {
|
||||
e, _ := New(WithLogger(nil))
|
||||
defer e.Close()
|
||||
|
||||
s, _ := e.Compile("s.ts", `export default function () { return rec.tag() }`)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
errs := make(chan string, 64)
|
||||
for i := range 32 {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
want := string(rune('a' + i%26))
|
||||
got, err := s.Call(scoped(t, &recorder{tag: want}), "")
|
||||
if err != nil {
|
||||
errs <- err.Error()
|
||||
return
|
||||
}
|
||||
if got != want {
|
||||
errs <- "串了: 想要 " + want + ",拿到 " + got.(string)
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
close(errs)
|
||||
|
||||
var bad []string
|
||||
for e := range errs {
|
||||
bad = append(bad, e)
|
||||
}
|
||||
if len(bad) > 0 {
|
||||
t.Errorf("%d 次出错:\n%s", len(bad), strings.Join(bad, "\n"))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user