返回 DeepSeek-Reasonix
snapshot_test.go
根目录 / internal / environment / snapshot_test.go
1 package environment
2
3 import (
4 "context"
5 "os"
6 "path/filepath"
7 "runtime"
8 "testing"
9 "time"
10 )
11
12 func TestProbeSnapshotServesAcrossRestartsWithoutReprobing(t *testing.T) {
13 resetProbeCacheForTest(t, time.Unix(1000, 0))
14 dir := t.TempDir()
15 snapDir := t.TempDir()
16 toolPath := writeProbeTool(t, filepath.Join(dir, "snaptool"), "snaptool 1.0")
17 opts := ProbeOptions{Overrides: map[string]string{"snaptool": toolPath}, SnapshotDir: snapDir}
18 commands := []string{"snaptool --version"}
19
20 first := RunProbesWithOptions(context.Background(), commands, opts)
21 if len(first) != 1 || !first[0].Found || first[0].Output != "snaptool 1.0" {
22 t.Fatalf("first run = %+v, want found snaptool 1.0", first)
23 }
24 sectionBefore := FormatSection(first, "test/os", "/bin/sh", nil)
25
26 // Simulate a process restart: in-memory cache gone, and the tool binary
27 // gone too — a live probe would now say "not found". The persisted
28 // snapshot must answer instead, byte-identically.
29 resetProbeCacheForTest(t, time.Unix(2000, 0))
30 if err := os.Remove(toolPath); err != nil {
31 t.Fatalf("remove tool: %v", err)
32 }
33 second := RunProbesWithOptions(context.Background(), commands, opts)
34 if len(second) != 1 || !second[0].Found || second[0].Output != "snaptool 1.0" {
35 t.Fatalf("post-restart run = %+v, want snapshot-served snaptool 1.0", second)
36 }
37 if sectionAfter := FormatSection(second, "test/os", "/bin/sh", nil); sectionAfter != sectionBefore {
38 t.Fatalf("environment section changed across restart:\nbefore: %s\nafter: %s", sectionBefore, sectionAfter)
39 }
40 }
41
42 func TestProbeSnapshotExpiresAndAdoptsDefinitiveChanges(t *testing.T) {
43 resetProbeCacheForTest(t, time.Unix(1000, 0))
44 dir := t.TempDir()
45 snapDir := t.TempDir()
46 toolPath := writeProbeTool(t, filepath.Join(dir, "exptool"), "exptool 1.0")
47 opts := ProbeOptions{Overrides: map[string]string{"exptool": toolPath}, SnapshotDir: snapDir}
48 commands := []string{"exptool --version"}
49
50 if first := RunProbesWithOptions(context.Background(), commands, opts); !first[0].Found {
51 t.Fatalf("first run = %+v, want found", first)
52 }
53
54 // Past the snapshot TTL a live probe runs again; a definitive change (the
55 // tool now reports a new version) is adopted and re-persisted.
56 resetProbeCacheForTest(t, time.Unix(1000, 0).Add(probeSnapshotTTL+time.Hour))
57 writeProbeTool(t, filepath.Join(dir, "exptool"), "exptool 2.0")
58 second := RunProbesWithOptions(context.Background(), commands, opts)
59 if !second[0].Found || second[0].Output != "exptool 2.0" {
60 t.Fatalf("post-TTL run = %+v, want refreshed exptool 2.0", second)
61 }
62
63 // The refreshed snapshot serves the new bytes across the next restart.
64 resetProbeCacheForTest(t, time.Unix(1000, 0).Add(probeSnapshotTTL+2*time.Hour))
65 if err := os.Remove(toolPath); err != nil {
66 t.Fatalf("remove tool: %v", err)
67 }
68 third := RunProbesWithOptions(context.Background(), commands, opts)
69 if !third[0].Found || third[0].Output != "exptool 2.0" {
70 t.Fatalf("post-refresh restart = %+v, want snapshot-served exptool 2.0", third)
71 }
72 }
73
74 func TestProbeSnapshotKeepsFoundOverTransientFailure(t *testing.T) {
75 resetProbeCacheForTest(t, time.Unix(1000, 0))
76 dir := t.TempDir()
77 snapDir := t.TempDir()
78 toolPath := writeProbeTool(t, filepath.Join(dir, "flaptool"), "flaptool 1.0")
79 opts := ProbeOptions{Overrides: map[string]string{"flaptool": toolPath}, SnapshotDir: snapDir}
80 commands := []string{"flaptool --version"}
81
82 if first := RunProbesWithOptions(context.Background(), commands, opts); !first[0].Found {
83 t.Fatalf("first run = %+v, want found", first)
84 }
85
86 // Past the TTL the live probe fails transiently (nonzero exit): the merge
87 // keeps the previous successful observation so the rendered section — and
88 // with it the cached prompt prefix — does not flap. Overwrite the tool in
89 // place so the fingerprint (command + override path) stays identical to
90 // the snapshot's.
91 resetProbeCacheForTest(t, time.Unix(1000, 0).Add(probeSnapshotTTL+time.Hour))
92 if err := os.Remove(toolPath); err != nil {
93 t.Fatalf("remove tool: %v", err)
94 }
95 body := "#!/bin/sh\nexit 1\n"
96 if runtime.GOOS == "windows" {
97 body = "@exit /b 1\r\n"
98 }
99 if err := os.WriteFile(toolPath, []byte(body), 0o755); err != nil {
100 t.Fatalf("write failing tool: %v", err)
101 }
102 second := RunProbesWithOptions(context.Background(), commands, opts)
103 if !second[0].Found || second[0].Output != "flaptool 1.0" {
104 t.Fatalf("transient failure run = %+v, want previous found result kept", second)
105 }
106
107 // A definitive "not found" (override pointing at a missing binary) is
108 // adopted — genuinely uninstalled tools must not be resurrected forever.
109 resetProbeCacheForTest(t, time.Unix(1000, 0).Add(2*probeSnapshotTTL+2*time.Hour))
110 if err := os.Remove(toolPath); err != nil {
111 t.Fatalf("remove tool: %v", err)
112 }
113 third := RunProbesWithOptions(context.Background(), commands, opts)
114 if third[0].Found || third[0].Error != "not found" {
115 t.Fatalf("definitive removal run = %+v, want not found adopted", third)
116 }
117 }
118
119 func TestMergeProbeSnapshotOnlyOverlaysTransientFailures(t *testing.T) {
120 previous := []ProbeResult{
121 {Command: "go version", Binary: "go", Found: true, Output: "go1.24"},
122 {Command: "docker --version", Binary: "docker", Found: true, Output: "Docker 27"},
123 {Command: "node --version", Binary: "node", Found: true, Output: "v22"},
124 }
125 fresh := []ProbeResult{
126 {Command: "go version", Binary: "go", Error: "timeout"},
127 {Command: "docker --version", Binary: "docker", Error: "exit exit status 1"},
128 {Command: "node --version", Binary: "node", Error: "not found"},
129 }
130 merged := mergeProbeSnapshot(previous, fresh)
131 byCommand := map[string]ProbeResult{}
132 for _, r := range merged {
133 byCommand[r.Command] = r
134 }
135 if r := byCommand["go version"]; !r.Found || r.Output != "go1.24" {
136 t.Fatalf("timeout overlay = %+v, want previous found kept", r)
137 }
138 if r := byCommand["docker --version"]; !r.Found || r.Output != "Docker 27" {
139 t.Fatalf("exit overlay = %+v, want previous found kept", r)
140 }
141 if r := byCommand["node --version"]; r.Found || r.Error != "not found" {
142 t.Fatalf("not-found overlay = %+v, want definitive result adopted", r)
143 }
144 }
145
145 lines GO