From f9c0a5b558d76f483b73b605a3979e168294a26d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Mon, 5 Oct 2026 14:03:18 -0400 Subject: [PATCH] Snapshots render in a fresh child process: pixels no longer depend on test order --- AGENTS.md | 2 +- docs/upstream.md | 2 +- snapshot_test.go | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index c1f959a..376e0de 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -54,7 +54,7 @@ testdata/snapshots/ /-.png - `ui.NewTester` drives click, keys, typing. Assert values, not existence. - Every component: one `snapshot(t, name, w, h, view)` call (light and dark). Refresh with `go test -run -update .` - Run `go vet ./... && go test ./...` before reporting done. -- Snapshots assume the default test order. MyGo shares its path-mask cache across a process, and its own chevrons are not snapped, so `-shuffle` can shift a few pixels (docs/upstream.md). +- Each snapshot test renders in a fresh child process (snapshot_test.go), because MyGo's path-mask cache is process-wide. Order and neighbours never change pixels; `-run -update` is safe. ## Gallery page diff --git a/docs/upstream.md b/docs/upstream.md index 565e3e4..beea964 100644 --- a/docs/upstream.md +++ b/docs/upstream.md @@ -35,4 +35,4 @@ PRD 第 2 节要求公开接口不足时向上游提交扩展。以下缺口在 | `SelectBase` 高亮无设置接口 | `ui/base.go:260` | Select | | `TextInputBase` 无只读开关与占位符 | `ui/editor.go:139,734,811` | TextInput 只读模式、CommandPalette | | `ui.Stepper` 禁用时仍可步进,固定 9 位取整 | `ui/indicators.go:216`、`ui/widgets.go` `snap` | NumberInput(已改为自绘) | -| 路径遮罩缓存按四分之一像素取键,进程内共享 | `ui/path.go:189-210` | 自绘路径以 `internal.Snap` 对齐;MyGo 自绘的箭头(Outline 等)无法对齐,快照依赖测试运行顺序,`-shuffle` 下 TreeTable 快照会出现 6 像素差异 | +| 路径遮罩缓存按四分之一像素取键,进程内共享 | `ui/path.go:189-210` | 自绘路径以 `internal.Snap` 对齐;MyGo 自绘的箭头(Outline 等)无法对齐;快照测试因此在独立子进程中渲染,结果与测试顺序无关 | diff --git a/snapshot_test.go b/snapshot_test.go index acab2e2..49940ac 100644 --- a/snapshot_test.go +++ b/snapshot_test.go @@ -6,9 +6,12 @@ import ( "image" "image/png" "os" + "os/exec" "path/filepath" + "regexp" "runtime" "strings" + "sync" "testing" "github.com/egoist/mygo/ui" @@ -22,9 +25,18 @@ func snapshot(t *testing.T, name string, w, h int, view func(c *ui.Context)) { snapshotAfter(t, name, w, h, view, nil) } +// snapshotChild marks a process that renders snapshots itself. +const snapshotChild = "MUJICA_SNAPSHOT_CHILD" + +var snapshotRan sync.Map + // snapshotAfter is snapshot with drive sending input before the capture. func snapshotAfter(t *testing.T, name string, w, h int, view func(c *ui.Context), drive func(tt *ui.Tester)) { t.Helper() + if os.Getenv(snapshotChild) == "" { + snapshotInChild(t) + return + } for _, m := range []Mode{Light, Dark} { ready := false tt := ui.NewTester(func(c *ui.Context) { @@ -64,6 +76,27 @@ func snapshotAfter(t *testing.T, name string, w, h int, view func(c *ui.Context) } } +// snapshotInChild reruns the test alone in a fresh process; MyGo's path cache is process-wide. +func snapshotInChild(t *testing.T) { + t.Helper() + if _, done := snapshotRan.LoadOrStore(t.Name(), true); done { + return + } + parts := strings.Split(t.Name(), "/") + for i, p := range parts { + parts[i] = "^" + regexp.QuoteMeta(p) + "$" + } + args := []string{"-test.run=" + strings.Join(parts, "/"), "-test.count=1"} + if *update { + args = append(args, "-update") + } + cmd := exec.Command(os.Args[0], args...) + cmd.Env = append(os.Environ(), snapshotChild+"=1") + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("snapshot child failed: %v\n%s", err, out) + } +} + func encode(t *testing.T, img image.Image) []byte { t.Helper() var b bytes.Buffer