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,96 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user