PPysolate SpineREAD THE CODE · UNDERSTAND THE SYSTEM
课程与章节
CODE READING / 08

CLI 示例的测试

cmd/demo/main_test.go

本章目录

这章回答:文档里展示的 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 而非原始字符串。