Files
jscriptx/scope_pool_test.go
T
what 7f484d7a71 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":
定时器必须保持未定义,库里的特性探测才能正常降级,防止以后有人把桩加回来。
2026-09-05 22:13:57 +08:00

173 lines
4.4 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
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("不带作用域不该丢 VMDropped = %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"))
}
}