除了常规用例,有几组是专门钉住「引擎的既定事实」的,改动时会先红:
- 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":
定时器必须保持未定义,库里的特性探测才能正常降级,防止以后有人把桩加回来。
97 lines
2.7 KiB
Go
97 lines
2.7 KiB
Go
package jscriptx_test
|
||
|
||
import (
|
||
"context"
|
||
"testing"
|
||
|
||
"git.fsdpf.net/go/jscriptx"
|
||
)
|
||
|
||
// Go 的切片和 map 进到脚本里长什么样。
|
||
//
|
||
// 这不是 jscriptx 的行为,是 goja 的——但脚本作者是照着这个写代码的,一旦变了,
|
||
// 业务脚本会**静默**走错分支(比如查出来的记录突然 Array.isArray 为 false,
|
||
// 整段列表被当成空的),所以在这里钉住。
|
||
//
|
||
// 之所以走 jscriptx 而不是直接用 goja:注入前还经过一层 freeze,得连那层一起验。
|
||
func TestGo值到脚本里的形状(t *testing.T) {
|
||
e, err := jscriptx.New(jscriptx.WithLogger(nil))
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
defer e.Close()
|
||
|
||
// 照着资源查询的返回长的:[]map[string]any,里面混着切片、字符串和 nil
|
||
records := func() []map[string]any {
|
||
return []map[string]any{{
|
||
"role": "user",
|
||
"content": "你好",
|
||
"attempts": []any{map[string]any{"name": "a.pdf"}, map[string]any{"url": "http://x"}},
|
||
"tool_calls": `[{"id":"1"}]`, // 列里存的是 JSON 字符串
|
||
"reasoning": nil,
|
||
}}
|
||
}
|
||
|
||
s, err := e.Compile("shape.ts", `
|
||
export default class C {
|
||
Run() {
|
||
const rows = getRecords()
|
||
const r = rows[0]
|
||
return {
|
||
// 切片是真数组:length / for-of / map 都能用
|
||
rowsIsArray: Array.isArray(rows),
|
||
rowsLen: rows.length,
|
||
attemptsIsArray: Array.isArray(r.attempts),
|
||
attemptsLen: r.attempts.length,
|
||
mapped: r.attempts.map(a => a.name || a.url).join(","),
|
||
|
||
// map[string]any 的值按 Go 的类型各自转换,不会统一成字符串
|
||
contentType: typeof r.content,
|
||
toolCallsType: typeof r.tool_calls,
|
||
|
||
// Go 的 nil 是 null,不是 undefined——判空要用 == null 或 === null
|
||
reasoningIsNull: r.reasoning === null,
|
||
reasoningIsUndef: r.reasoning === undefined,
|
||
|
||
// 没有的列是 undefined,不会报错
|
||
missingIsUndef: r.nope === undefined,
|
||
}
|
||
}
|
||
}`)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
|
||
ctx := jscriptx.WithScope(context.Background(),
|
||
jscriptx.ScopeGlobals(map[string]any{"getRecords": records}))
|
||
|
||
obj, err := s.New(ctx)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
defer obj.Close()
|
||
|
||
var got map[string]any
|
||
if err := obj.CallInto(ctx, "Run", &got); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
|
||
want := map[string]any{
|
||
"rowsIsArray": true,
|
||
"rowsLen": int64(1),
|
||
"attemptsIsArray": true,
|
||
"attemptsLen": int64(2),
|
||
"mapped": "a.pdf,http://x",
|
||
"contentType": "string",
|
||
"toolCallsType": "string",
|
||
"reasoningIsNull": true,
|
||
"reasoningIsUndef": false,
|
||
"missingIsUndef": true,
|
||
}
|
||
for k, w := range want {
|
||
if got[k] != w {
|
||
t.Errorf("%s = %#v,该是 %#v", k, got[k], w)
|
||
}
|
||
}
|
||
}
|