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:
2026-09-10 15:39:25 +08:00
parent 7e1893b246
commit 7c9a70712e
16 changed files with 427 additions and 572 deletions
+280
View File
@@ -0,0 +1,280 @@
package jscriptx_test
import (
"context"
"log/slog"
"strings"
"sync/atomic"
"testing"
"git.fsdpf.net/go/jscriptx"
)
// countExt 记录自己的 Bindings 被取用过几次——用来验证"没访问就不 freeze"。
type countExt struct {
name string
calls *atomic.Int64
}
func (e countExt) Name() string { return e.name }
func (countExt) Module() (string, string) { return "", "" }
func (e countExt) Bindings() map[string]any {
return map[string]any{
"hit": func() string {
e.calls.Add(1)
return e.name
},
}
}
// 全局是惰性的:装的是访问器属性,值要等第一次读才算出来。
//
// 直接查属性描述符——惰性注入是 {get, set},急切注入是 {value, writable}。
// 这是两种实现唯一可靠的观测点:数"方法被调了几次"不行,因为急切注入只是
// **包装**函数、并不调用它,两边都是 0。
func TestLazyGlobal_是惰性的访问器(t *testing.T) {
e, err := jscriptx.New(jscriptx.WithLogger(nil))
if err != nil {
t.Fatal(err)
}
defer e.Close()
s, err := e.Compile("s.ts", `
export default function () {
const d = Object.getOwnPropertyDescriptor(globalThis, "unused")
if (typeof d.get !== "function") return "急切: 装的是数据属性"
if (d.value !== undefined) return "急切: 有 value"
// 读一次,值要能正常算出来
return typeof unused.hit === "function" ? "惰性" : "惰性但值不对"
}`)
if err != nil {
t.Fatal(err)
}
var usedCalls, unusedCalls atomic.Int64
ctx := jscriptx.WithScope(context.Background(), jscriptx.ScopeExtensions(
countExt{"used", &usedCalls},
countExt{"unused", &unusedCalls},
))
got, err := s.Call(ctx, "")
if err != nil {
t.Fatal(err)
}
if got != "惰性" {
t.Errorf("全局该是惰性的访问器属性,结果 %#v", got)
}
}
// 用得上的扩展照常能用——惰性不能把功能弄丢。
func TestLazyGlobal_用得上的照常(t *testing.T) {
e, err := jscriptx.New(jscriptx.WithLogger(nil))
if err != nil {
t.Fatal(err)
}
defer e.Close()
s, err := e.Compile("s.ts", `export default function () { return used.hit() }`)
if err != nil {
t.Fatal(err)
}
var usedCalls, unusedCalls atomic.Int64
ctx := jscriptx.WithScope(context.Background(), jscriptx.ScopeExtensions(
countExt{"used", &usedCalls},
countExt{"unused", &unusedCalls},
))
got, err := s.Call(ctx, "")
if err != nil {
t.Fatal(err)
}
if got != "used" {
t.Errorf("拿到 %#v", got)
}
if usedCalls.Load() != 1 {
t.Errorf("used.hit 该被调一次,实际 %d 次", usedCalls.Load())
}
if unusedCalls.Load() != 0 {
t.Error("没用到的扩展,它的方法不该被调用")
}
}
// 读两次只 freeze 一次——getter 里的缓存要生效。
func TestLazyGlobal_只转换一次(t *testing.T) {
e, err := jscriptx.New(jscriptx.WithLogger(nil))
if err != nil {
t.Fatal(err)
}
defer e.Close()
// 每次 freeze 都会重新包装 Bindings 里的函数,所以两次读到的
// 如果是同一个对象,就说明只转换了一次
s, err := e.Compile("s.ts", `
export default function () { return globalThis.ext === globalThis.ext }`)
if err != nil {
t.Fatal(err)
}
var calls atomic.Int64
ctx := jscriptx.WithScope(context.Background(),
jscriptx.ScopeExtensions(countExt{"ext", &calls}))
got, err := s.Call(ctx, "")
if err != nil {
t.Fatal(err)
}
if got != true {
t.Error("两次读该拿到同一个对象,说明 freeze 只跑了一次")
}
}
// 只读性不能变:脚本赋值不生效。
//
// 注意断言的是"值没变"而不是"抛错"——打包产物**没有** "use strict"
// 脚本跑在非严格模式下,给只读属性赋值是静默失败的。
func TestLazyGlobal_仍然只读(t *testing.T) {
e, err := jscriptx.New(jscriptx.WithLogger(nil))
if err != nil {
t.Fatal(err)
}
defer e.Close()
s, err := e.Compile("s.ts", `
export default function () {
globalThis.ext = 1
return typeof ext === "object" ? "没被改掉" : "被改成了 " + typeof ext
}`)
if err != nil {
t.Fatal(err)
}
var calls atomic.Int64
ctx := jscriptx.WithScope(context.Background(),
jscriptx.ScopeExtensions(countExt{"ext", &calls}))
got, err := s.Call(ctx, "")
if err != nil {
t.Fatal(err)
}
if got != "没被改掉" {
t.Errorf("扩展全局该是只读的,结果 %#v", got)
}
}
// 删不掉、重定义不了(configurable=false)。
func TestLazyGlobal_不可删除(t *testing.T) {
e, err := jscriptx.New(jscriptx.WithLogger(nil))
if err != nil {
t.Fatal(err)
}
defer e.Close()
s, err := e.Compile("s.ts", `
export default function () {
delete globalThis.ext
return typeof ext === "object" ? "还在" : "被删了"
}`)
if err != nil {
t.Fatal(err)
}
var calls atomic.Int64
ctx := jscriptx.WithScope(context.Background(),
jscriptx.ScopeExtensions(countExt{"ext", &calls}))
got, err := s.Call(ctx, "")
if err != nil {
t.Fatal(err)
}
if got != "还在" {
t.Errorf("configurable 该是 false,删不掉,结果 %#v", got)
}
}
// 每个 VM 各转各的——goja.Value 跨不了 Runtime,缓存绝不能共享。
func TestLazyGlobal_每个VM各一份(t *testing.T) {
e, err := jscriptx.New(jscriptx.WithLogger(nil))
if err != nil {
t.Fatal(err)
}
defer e.Close()
s, err := e.Compile("s.ts", `export default function () { return ext.hit() }`)
if err != nil {
t.Fatal(err)
}
var calls atomic.Int64
// 每次都是新作用域 → 新 VM(带作用域的调用不走池)
for range 5 {
ctx := jscriptx.WithScope(context.Background(),
jscriptx.ScopeExtensions(countExt{"ext", &calls}))
if _, err := s.Call(ctx, ""); err != nil {
t.Fatal(err)
}
}
if got := calls.Load(); got != 5 {
t.Errorf("5 个 VM 各调一次,该是 5 次,实际 %d 次", got)
}
}
// 白名单全局(WithGlobals)同样是惰性的,行为跟以前一致。
func TestLazyGlobal_白名单也惰性(t *testing.T) {
e, err := jscriptx.New(
jscriptx.WithLogger(nil),
jscriptx.WithGlobals(map[string]any{
"cfg": map[string]any{"name": "线上", "port": 8080},
}),
)
if err != nil {
t.Fatal(err)
}
defer e.Close()
s, err := e.Compile("s.ts", `export default function () { return cfg.name + ":" + cfg.port }`)
if err != nil {
t.Fatal(err)
}
got, err := s.Call(context.Background(), "")
if err != nil {
t.Fatal(err)
}
if got != "线上:8080" {
t.Errorf("拿到 %#v", got)
}
// 嵌套的对象也该是只读的
s2, _ := e.Compile("s2.ts", `
export default function () { cfg.name = "改了"; return cfg.name }`)
if got, _ := s2.Call(context.Background(), ""); got != "线上" {
t.Errorf("嵌套字段该只读、改不动,结果 %#v", got)
}
}
// newTestLogger 造一个把输出写进 buf 的 slog,用来验 console。
func newTestLogger(buf *strings.Builder) *slog.Logger {
return slog.New(slog.NewTextHandler(buf, &slog.HandlerOptions{Level: slog.LevelDebug}))
}
// console 也走惰性注入,用法不变。
func TestLazyGlobal_console照常(t *testing.T) {
var buf strings.Builder
e, err := jscriptx.New(jscriptx.WithLogger(newTestLogger(&buf)))
if err != nil {
t.Fatal(err)
}
defer e.Close()
s, err := e.Compile("s.ts", `export default function () { console.log("来自脚本"); return 1 }`)
if err != nil {
t.Fatal(err)
}
if _, err := s.Call(context.Background(), ""); err != nil {
t.Fatal(err)
}
if !strings.Contains(buf.String(), "来自脚本") {
t.Errorf("console.log 该照常落到日志,拿到 %q", buf.String())
}
}