Files
jscriptx/script_vm_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

226 lines
6.2 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):它要直接断言
// vmHandle.scoped 这个未导出字段。行为测试盖不住那条——borrowStatic 那条路
// 靠 finish 永远丢弃兜底,漏标 scoped 从外面看不出来,标志位本身才是真相。
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"))
}
}
// 取 VM 的每一条路径,带作用域时都必须把 scoped 标上。
//
// scoped 决定 release 会不会把 VM 放回池子。漏标的后果是**跨调用泄漏作用域扩展**:
// 下一个不带作用域的调用会看见上一个作用域的 store。
//
// 这条用内部测试直接断言标志位,而不是测「有没有漏」——borrowStatic 那条路今天
// 靠 staticTarget.finish 永远丢弃兜底,行为上看不出漏标,等于同一个约束靠两套
// 机制守。标志位本身才是唯一的真相。
func TestVM_每条取VM的路径都标了scoped(t *testing.T) {
e, err := New(WithLogger(nil))
if err != nil {
t.Fatal(err)
}
defer e.Close()
s, err := e.Compile("c.ts", `export default class C { static S() { return 1 } Run() { return 1 } }`)
if err != nil {
t.Fatal(err)
}
scoped := WithScope(context.Background(), ScopeKey("k"))
plain := context.Background()
for _, c := range []struct {
name string
get func(ctx context.Context) (*vmHandle, error)
}{
{"borrow(池化路径)", s.borrow},
{"borrowStatic(静态路径)", s.borrowStatic},
} {
vm, err := c.get(scoped)
if err != nil {
t.Fatalf("%s: %v", c.name, err)
}
if !vm.scoped {
t.Errorf("%s 在有作用域时没标 scoped——这个 VM 会被放回池子,"+
"把作用域的扩展漏给下一个调用", c.name)
}
vm, err = c.get(plain)
if err != nil {
t.Fatalf("%s(无作用域): %v", c.name, err)
}
if vm.scoped {
t.Errorf("%s 没有作用域却标了 scoped——白白丢弃可复用的 VM", c.name)
}
}
}