这章回答:文档里展示的 Python 程序,在实际 CLI 工具清单下走普通执行和 PLM,是否仍得到同一个结果?
测试调用 cmd/demo 使用的 demoTools(),读取真实 dist/spine.wasm,创建一个 Runner,然后把 examples/ 下的程序逐个交给它。输入初始值是 item=book、quantity=2、delivery=false;最终观察 Output.Value 和 Output.Transformed,不把示例测试误当成性能实验。
测试固定了什么局面
cmd/demo/main_test.go · L1–L26 · 打开完整源码
package main
import (
"context"
"encoding/json"
"os"
"reflect"
"testing"
"time"
spine "pysolate-spine"
)
// Run the documented source files using the actual CLI tool registry.
func TestWalkthroughPrograms(t *testing.T) {
wasm, err := os.ReadFile("../../dist/spine.wasm")
if err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
runner, err := spine.New(ctx, wasm, demoTools())
if err != nil {
t.Fatal(err)
}
defer runner.Close(context.Background())
TestWalkthroughPrograms 先读取 artifact。context.WithTimeout 给整组运行设置一分钟上限,defer cancel 和 defer runner.Close 负责测试生命周期。测试没有重新实现工具函数,而是直接使用 CLI 的真实注册表;因此示例与用户实际运行入口共享同一套具名 Python 函数。
表中的五个程序分别覆盖独立读取、结果依赖、分支、错误交付和不适合提前改写的循环。want 存的是 JSON 文本,transformed 表示这个程序在 PLM 下是否预期产生真实的 AST 渲染结果。lookup.py 是 CLI 的默认最小示例,带读正文另行覆盖它;这张表集中验证其余 walkthrough 程序。
每个程序跑两种模式
cmd/demo/main_test.go · L27–L70 · 打开完整源码
inputs := map[string]any{"item": "book", "quantity": 2, "delivery": false}
cases := []struct {
name, file, want string
transformed bool
}{
{"independent", "order.py", `{"item":"book","total":47}`, true},
{"dependency", "dependency.py", `{"item":"pen","total":11}`, true},
{"branch", "branch.py", `42`, true},
{"error delivery", "error_delivery.py", `{"price":21,"caught":true}`, true},
{"fallback", "fallback.py", `24`, false},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
code, err := os.ReadFile("../../examples/" + tc.file)
if err != nil {
t.Fatal(err)
}
var want any
if err = json.Unmarshal([]byte(tc.want), &want); err != nil {
t.Fatal(err)
}
for _, plm := range []bool{false, true} {
execute := runner.Run
if plm {
execute = runner.RunPLM
}
out, err := execute(ctx, string(code), inputs)
if err != nil {
t.Fatal(err)
}
var got any
if err = json.Unmarshal(out.Value, &got); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("plm=%v: got %s, want %s", plm, out.Value, tc.want)
}
if (out.Transformed != "") != (plm && tc.transformed) {
t.Fatalf("plm=%v: unexpected rewrite state: %q", plm, out.Transformed)
}
}
})
}
}
子测试从文件系统读取当前示例,再把期望 JSON 解码成 Go 值。内层循环依次选择 runner.Run 和 runner.RunPLM,两者都使用同一段源代码和同一个输入局面。运行必须无错误;out.Value 解码后用 reflect.DeepEqual 与期望值比较,所以断言的是结构和值,不是 JSON 的排版。
第二个断言看 out.Transformed:
- 普通模式一定没有 transformed source。
- PLM 模式只有表中标为
true的程序才应有实际改写。 - 测试不比较改写文本的完整字符串,因此不会把 AST 渲染的无关格式绑定成 API。
这正好把两个问题分开:程序语义在两种模式下都必须相同;PLM 是否确实执行了改写,则由 Output.Transformed 单独核对。error_delivery.py 的 caught error 也走成功结果断言,说明“异常被 Python 捕获后形成结果”与“Run 返回错误”是两种不同边界。
断言维护的设计边界
测试没有断言调用顺序、延迟、工具内部 map 的实现或完整日志内容,因为这些不是 CLI 示例对外交付的合同。它只锁定:真实文件可读、真实 Guest 可执行、具名工具得到预期数据、普通/PLM 的结果一致,以及应改写的程序确实留下 transformed source。
同一 Runner 串行执行所有 case,代码没有使用 t.Parallel,也没有用睡眠推测调度。测试超时是兜底,不是同步手段;每次 Run 自己返回后才开始下一个模式。
继续阅读
先看 六个用户程序 对照每个源文件的控制流,再回到 PLM:沿 AST 找安全提前点。读完本章应能解释:为什么结果相等还不足以证明 PLM 发生了改写,以及测试为何比较解码后的 JSON 而非原始字符串。