first_presentation_collector_test.go RawHistoryFolder
1package cef
2
3import (
4 "crypto/sha256"
5 "encoding/hex"
6 "encoding/json"
7 "fmt"
8 "os"
9 "os/exec"
10 "path/filepath"
11 "strings"
12 "testing"
13
14 "github.com/stretchr/testify/require"
15)
16
17const validFirstPresentationLog = `{"message":"startup_trace: milestone","milestone":"process_entry","t_ms":0,"delta_ms":0,"time":"2026-07-14T09:00:00+02:00","machine_path":"/home/alice/private"}
18{"message":"startup_trace: milestone","milestone":"config_complete","t_ms":1,"delta_ms":1}
19{"message":"startup_trace: milestone","milestone":"cef_library_load_begin","t_ms":2,"delta_ms":1}
20{"message":"startup_trace: milestone","milestone":"cef_initialized","t_ms":3,"delta_ms":1}
21{"message":"startup_trace: milestone","milestone":"browser_create_requested","t_ms":4,"delta_ms":1}
22{"message":"startup_trace: milestone","milestone":"first_accelerated_paint_received","t_ms":5,"delta_ms":1}
23{"message":"startup_trace: milestone","milestone":"first_dmabuf_texture_swap","t_ms":6,"delta_ms":1}
24{"message":"startup_trace: milestone","milestone":"first_gtk_presentation","t_ms":7,"delta_ms":1}
25{"message":"startup_trace: first presentation","backend":"gdk-dmabuf","incomplete_reason":"","total_ms":7,"host":"alice"}`
26
27const (
28 testUpstreamVersion = "v0.8.5-0.20300102030405-bbd397409ebe"
29 testUpstreamRevision = "bbd397409ebed75a5979c1e4566a2ef319f6a484"
30 testSourceRevision = "0123456789abcdef0123456789abcdef01234567"
31)
32
33func TestFirstPresentationCollectorNeverRecursivelyDeletesCallerOutput(t *testing.T) {
34 repoRoot, err := filepath.Abs(filepath.Join("..", "..", ".."))
35 require.NoError(t, err)
36 script, err := os.ReadFile(filepath.Join(repoRoot, "scripts", "collect_first_presentation.sh"))
37 require.NoError(t, err)
38 require.NotContains(t, string(script), "rm -rf \"$output\"")
39 require.NotContains(t, string(script), "rm -rf -- \"$output\"")
40}
41
42func TestFirstPresentationCollectorHasNoHardcodedRuntimeDefaults(t *testing.T) {
43 repoRoot, err := filepath.Abs(filepath.Join("..", "..", ".."))
44 require.NoError(t, err)
45 script, err := os.ReadFile(filepath.Join(repoRoot, "scripts", "collect_first_presentation.sh"))
46 require.NoError(t, err)
47 require.NotContains(t, string(script), "cef-147")
48 require.NotContains(t, string(script), `"version": "147"`)
49 require.NotContains(t, string(script), "go list -m")
50 require.NotContains(t, string(script), "go mod download")
51 require.NotContains(t, string(script), "-mod=mod")
52 require.Contains(t, string(script), "DUMBER_CEF_DIR must be set")
53 require.Contains(t, string(script), "cef_runtime_probe.py")
54 require.Contains(t, string(script), `CEF_DIR="$selected_cef_dir"`)
55}
56
57func TestFirstPresentationCollectorRejectsUnsafeOutputPaths(t *testing.T) {
58 repoRoot, err := filepath.Abs(filepath.Join("..", "..", ".."))
59 require.NoError(t, err)
60 temp := t.TempDir()
61 runtime := collectorFakeCEFRuntime(t, 150)
62 binary := filepath.Join(temp, "dumber")
63 require.NoError(t, os.WriteFile(binary, collectorTestBinary(validFirstPresentationLog), 0o755))
64 home := filepath.Join(temp, "home")
65 require.NoError(t, os.Mkdir(home, 0o755))
66
67 unrelated := filepath.Join(temp, "unrelated")
68 require.NoError(t, os.Mkdir(unrelated, 0o755))
69 unrelatedSentinel := filepath.Join(unrelated, "keep")
70 require.NoError(t, os.WriteFile(unrelatedSentinel, []byte("do not delete"), 0o600))
71
72 safeParent := filepath.Join(temp, "safe")
73 require.NoError(t, os.Mkdir(safeParent, 0o755))
74 parentSentinel := filepath.Join(safeParent, "keep")
75 require.NoError(t, os.WriteFile(parentSentinel, []byte("do not delete"), 0o600))
76
77 external := filepath.Join(temp, "external")
78 require.NoError(t, os.Mkdir(external, 0o755))
79 externalSentinel := filepath.Join(external, "keep")
80 require.NoError(t, os.WriteFile(externalSentinel, []byte("do not delete"), 0o600))
81 link := filepath.Join(temp, "link")
82 require.NoError(t, os.Symlink(external, link))
83
84 for _, test := range []struct {
85 name string
86 output string
87 verify func(t *testing.T)
88 }{
89 {name: "empty", output: ""},
90 {name: "root", output: "/"},
91 {name: "home", output: home},
92 {name: "existing unrelated directory", output: unrelated, verify: func(t *testing.T) {
93 requireFileContents(t, unrelatedSentinel, "do not delete")
94 }},
95 {name: "parent traversal", output: safeParent + "/../escaped", verify: func(t *testing.T) {
96 requireFileContents(t, parentSentinel, "do not delete")
97 require.NoFileExists(t, filepath.Join(temp, "escaped"))
98 }},
99 {name: "symlink escape", output: filepath.Join(link, "escaped"), verify: func(t *testing.T) {
100 requireFileContents(t, externalSentinel, "do not delete")
101 require.NoFileExists(t, filepath.Join(external, "escaped"))
102 }},
103 } {
104 t.Run(test.name, func(t *testing.T) {
105 cmd := exec.Command(filepath.Join(repoRoot, "scripts", "collect_first_presentation.sh"))
106 // An isolated directory prevents a regression from reaching the tracked
107 // default artifact path while this test proves output validation is first.
108 cmd.Dir = temp
109 cmd.Env = append(os.Environ(),
110 "HOME="+home,
111 "DISPLAY=:test",
112 "DUMBER_CEF_DIR="+runtime,
113 "DUMBER_FIRST_PRESENTATION_BIN="+binary,
114 "DUMBER_FIRST_PRESENTATION_OUTPUT="+test.output,
115 )
116 result, err := cmd.CombinedOutput()
117 require.Errorf(t, err, "collector accepted unsafe output %q: %s", test.output, result)
118 require.Contains(t, string(result), "unsafe output path")
119 if test.verify != nil {
120 test.verify(t)
121 }
122 })
123 }
124}
125
126func requireFileContents(t *testing.T, path, want string) {
127 t.Helper()
128 contents, err := os.ReadFile(path)
129 require.NoError(t, err)
130 require.Equal(t, want, string(contents))
131}
132
133func TestFirstPresentationCollectorSanitizesMachineLocalValues(t *testing.T) {
134 repoRoot, err := filepath.Abs(filepath.Join("..", "..", ".."))
135 require.NoError(t, err)
136 temp := t.TempDir()
137 runtime := collectorFakeCEFRuntime(t, 150)
138 binary := filepath.Join(temp, "dumber")
139 require.NoError(t, os.WriteFile(binary, collectorTestBinary(validFirstPresentationLog), 0o755))
140
141 goBin := collectorFakeGo(t, temp, testUpstreamVersion, testSourceRevision, false)
142 manifest := collectorManifest(t, temp, binary, testSourceRevision, testUpstreamVersion, testUpstreamRevision)
143 output := filepath.Join(temp, "artifacts")
144 cmd := exec.Command(filepath.Join(repoRoot, "scripts", "collect_first_presentation.sh"))
145 cmd.Dir = repoRoot
146 cmd.Env = append(os.Environ(),
147 "DISPLAY=:test",
148 "WAYLAND_DISPLAY=",
149 "DUMBER_CEF_DIR="+runtime,
150 "DUMBER_FIRST_PRESENTATION_BIN="+binary,
151 "DUMBER_BUILD_MANIFEST="+manifest,
152 "DUMBER_FIRST_PRESENTATION_OUTPUT="+output,
153 "DUMBER_FIRST_PRESENTATION_TIMEOUT_SECONDS=1",
154 "DUMBER_MACHINE_GPU_PROFILE=integrated-gpu",
155 "PATH="+filepath.Dir(goBin)+":"+os.Getenv("PATH"),
156 )
157 result, err := cmd.CombinedOutput()
158 require.NoErrorf(t, err, "collector failed: %s", result)
159
160 var artifacts strings.Builder
161 for _, name := range append([]string{"metadata.json", "baseline.json"}, runArtifactNames()...) {
162 artifactContents, readErr := os.ReadFile(filepath.Join(output, name))
163 require.NoError(t, readErr)
164 artifacts.Write(artifactContents)
165 }
166 for _, forbidden := range []string{"/home/", temp, "alice", "machine_path", `"time"`, runtime, "libcef.so"} {
167 require.NotContainsf(t, artifacts.String(), forbidden, "committed artifact leaked %q", forbidden)
168 }
169 for _, required := range []string{
170 `"measured_source_revision"`,
171 `"version": "` + testUpstreamVersion + `"`,
172 `"tag": "v0.8.5"`,
173 `"revision": "` + testUpstreamRevision + `"`,
174 `"chrome_major": 150`,
175 `"libcef_sha256"`,
176 } {
177 require.Contains(t, artifacts.String(), required)
178 }
179
180 var metadata struct {
181 Comparison struct {
182 OS string `json:"os"`
183 Architecture string `json:"architecture"`
184 DisplayProtocol string `json:"display_protocol"`
185 MachineGPUProfile string `json:"machine_gpu_profile"`
186 } `json:"comparison"`
187 }
188 contents, err := os.ReadFile(filepath.Join(output, "metadata.json"))
189 require.NoError(t, err)
190 require.NoError(t, json.Unmarshal(contents, &metadata))
191 require.NotEmpty(t, metadata.Comparison.OS)
192 require.NotEmpty(t, metadata.Comparison.Architecture)
193 require.Equal(t, "x11", metadata.Comparison.DisplayProtocol)
194 require.Equal(t, "integrated-gpu", metadata.Comparison.MachineGPUProfile)
195}
196
197func TestFirstPresentationCollectorDerivesSelectedImmutableModuleProvenance(t *testing.T) {
198 repoRoot, err := filepath.Abs(filepath.Join("..", "..", ".."))
199 require.NoError(t, err)
200 temp := t.TempDir()
201 runtime := collectorFakeCEFRuntime(t, 150)
202 binary := filepath.Join(temp, "dumber")
203 require.NoError(t, os.WriteFile(binary, collectorTestBinary(validFirstPresentationLog), 0o755))
204
205 goBin := collectorFakeGo(t, temp, testUpstreamVersion, testSourceRevision, false)
206 manifest := collectorManifest(t, temp, binary, testSourceRevision, testUpstreamVersion, testUpstreamRevision)
207 output := filepath.Join(temp, "artifacts")
208 cmd := exec.Command(filepath.Join(repoRoot, "scripts", "collect_first_presentation.sh"))
209 cmd.Dir = repoRoot
210 cmd.Env = append(os.Environ(),
211 "DISPLAY=:test",
212 "DUMBER_CEF_DIR="+runtime,
213 "DUMBER_FIRST_PRESENTATION_BIN="+binary,
214 "DUMBER_BUILD_MANIFEST="+manifest,
215 "DUMBER_FIRST_PRESENTATION_OUTPUT="+output,
216 "DUMBER_FIRST_PRESENTATION_TIMEOUT_SECONDS=1",
217 "PATH="+filepath.Dir(goBin)+":"+os.Getenv("PATH"),
218 )
219 result, err := cmd.CombinedOutput()
220 require.NoErrorf(t, err, "collector failed: %s", result)
221
222 var metadata struct {
223 Upstream struct {
224 Module string `json:"module"`
225 Version string `json:"version"`
226 Tag string `json:"tag"`
227 Revision string `json:"revision"`
228 } `json:"upstream"`
229 MeasuredSourceRevision string `json:"measured_source_revision"`
230 }
231 contents, err := os.ReadFile(filepath.Join(output, "metadata.json"))
232 require.NoError(t, err)
233 require.NoError(t, json.Unmarshal(contents, &metadata))
234 require.Equal(t, "github.com/bnema/purego-cef2gtk", metadata.Upstream.Module)
235 require.Equal(t, testUpstreamVersion, metadata.Upstream.Version)
236 require.Equal(t, "v0.8.5", metadata.Upstream.Tag)
237 require.Equal(t, testUpstreamRevision, metadata.Upstream.Revision)
238 require.Equal(t, testSourceRevision, metadata.MeasuredSourceRevision)
239
240 script, err := os.ReadFile(filepath.Join(repoRoot, "scripts", "collect_first_presentation.sh"))
241 require.NoError(t, err)
242 require.NotContains(t, string(script), "f217ece342dea3ef2a3f98671fcd16a39ad0037d")
243}
244
245func TestFirstPresentationCollectorRejectsNonImmutableModuleProvenanceWithoutLeaks(t *testing.T) {
246 repoRoot, err := filepath.Abs(filepath.Join("..", "..", ".."))
247 require.NoError(t, err)
248
249 for _, test := range []struct {
250 name, version string
251 }{
252 {name: "branch selector", version: "main"},
253 {name: "devel version", version: "(devel)"},
254 {name: "missing patch", version: "v0.8"},
255 } {
256 t.Run(test.name, func(t *testing.T) {
257 temp := t.TempDir()
258 runtime := collectorFakeCEFRuntime(t, 150)
259 binary := filepath.Join(temp, "dumber")
260 require.NoError(t, os.WriteFile(binary, collectorTestBinary(validFirstPresentationLog), 0o755))
261
262 goBin := collectorFakeGo(t, temp, test.version, testSourceRevision, false)
263 manifest := collectorManifest(t, temp, binary, testSourceRevision, testUpstreamVersion, testUpstreamRevision)
264 output := filepath.Join(temp, "artifacts")
265 cmd := exec.Command(filepath.Join(repoRoot, "scripts", "collect_first_presentation.sh"))
266 cmd.Dir = repoRoot
267 cmd.Env = append(os.Environ(),
268 "DISPLAY=:test",
269 "DUMBER_CEF_DIR="+runtime,
270 "DUMBER_FIRST_PRESENTATION_BIN="+binary,
271 "DUMBER_BUILD_MANIFEST="+manifest,
272 "DUMBER_FIRST_PRESENTATION_OUTPUT="+output,
273 "PATH="+filepath.Dir(goBin)+":"+os.Getenv("PATH"),
274 )
275 result, runErr := cmd.CombinedOutput()
276 require.Errorf(t, runErr, "collector accepted nonimmutable module provenance: %s", result)
277 require.Contains(t, string(result), "immutable module provenance is unavailable")
278 require.NotContains(t, string(result), temp)
279 require.NoFileExists(t, filepath.Join(output, "metadata.json"))
280 for _, name := range runArtifactNames() {
281 require.NoFileExists(t, filepath.Join(output, name))
282 }
283 })
284 }
285}
286
287func TestFirstPresentationCollectorRequiresExplicitRuntimeDir(t *testing.T) {
288 repoRoot, err := filepath.Abs(filepath.Join("..", "..", ".."))
289 require.NoError(t, err)
290 temp := t.TempDir()
291 binary := filepath.Join(temp, "dumber")
292 require.NoError(t, os.WriteFile(binary, collectorTestBinary(validFirstPresentationLog), 0o755))
293
294 cmd := exec.Command(filepath.Join(repoRoot, "scripts", "collect_first_presentation.sh"))
295 cmd.Dir = repoRoot
296 env := envWithout("DUMBER_CEF_DIR")
297 env = append(env,
298 "DISPLAY=:test",
299 "DUMBER_FIRST_PRESENTATION_BIN="+binary,
300 "DUMBER_FIRST_PRESENTATION_OUTPUT="+filepath.Join(temp, "artifacts"),
301 )
302 cmd.Env = env
303 result, err := cmd.CombinedOutput()
304 require.Errorf(t, err, "collector accepted missing runtime dir: %s", result)
305 require.Contains(t, string(result), "DUMBER_CEF_DIR must be set")
306}
307
308func TestFirstPresentationCollectorIgnoresConflictingParentCEFDir(t *testing.T) {
309 repoRoot, err := filepath.Abs(filepath.Join("..", "..", ".."))
310 require.NoError(t, err)
311 temp := t.TempDir()
312 runtime := collectorFakeCEFRuntime(t, 150)
313 bogus := filepath.Join(temp, "bogus-runtime")
314 require.NoError(t, os.Mkdir(bogus, 0o755))
315 binary := filepath.Join(temp, "dumber")
316 require.NoError(t, os.WriteFile(binary, collectorTestBinary(validFirstPresentationLog), 0o755))
317
318 goBin := collectorFakeGo(t, temp, testUpstreamVersion, testSourceRevision, false)
319 manifest := collectorManifest(t, temp, binary, testSourceRevision, testUpstreamVersion, testUpstreamRevision)
320 output := filepath.Join(temp, "artifacts")
321 cmd := exec.Command(filepath.Join(repoRoot, "scripts", "collect_first_presentation.sh"))
322 cmd.Dir = repoRoot
323 cmd.Env = append(os.Environ(),
324 "DISPLAY=:test",
325 "CEF_DIR="+bogus,
326 "DUMBER_CEF_DIR="+runtime,
327 "DUMBER_FIRST_PRESENTATION_BIN="+binary,
328 "DUMBER_BUILD_MANIFEST="+manifest,
329 "DUMBER_FIRST_PRESENTATION_OUTPUT="+output,
330 "DUMBER_FIRST_PRESENTATION_TIMEOUT_SECONDS=1",
331 "PATH="+filepath.Dir(goBin)+":"+os.Getenv("PATH"),
332 )
333 result, err := cmd.CombinedOutput()
334 require.NoErrorf(t, err, "collector failed with conflicting parent CEF_DIR: %s", result)
335 require.FileExists(t, filepath.Join(output, "metadata.json"))
336}
337
338func TestFirstPresentationCollectorRejectsProbeFailure(t *testing.T) {
339 repoRoot, err := filepath.Abs(filepath.Join("..", "..", ".."))
340 require.NoError(t, err)
341 temp := t.TempDir()
342 runtime := filepath.Join(temp, "empty-runtime")
343 require.NoError(t, os.Mkdir(runtime, 0o755))
344 binary := filepath.Join(temp, "dumber")
345 require.NoError(t, os.WriteFile(binary, collectorTestBinary(validFirstPresentationLog), 0o755))
346
347 goBin := collectorFakeGo(t, temp, testUpstreamVersion, testSourceRevision, false)
348 manifest := collectorManifest(t, temp, binary, testSourceRevision, testUpstreamVersion, testUpstreamRevision)
349 cmd := exec.Command(filepath.Join(repoRoot, "scripts", "collect_first_presentation.sh"))
350 cmd.Dir = repoRoot
351 cmd.Env = append(os.Environ(),
352 "DISPLAY=:test",
353 "DUMBER_CEF_DIR="+runtime,
354 "DUMBER_FIRST_PRESENTATION_BIN="+binary,
355 "DUMBER_BUILD_MANIFEST="+manifest,
356 "DUMBER_FIRST_PRESENTATION_OUTPUT="+filepath.Join(temp, "artifacts"),
357 "PATH="+filepath.Dir(goBin)+":"+os.Getenv("PATH"),
358 )
359 result, err := cmd.CombinedOutput()
360 require.Errorf(t, err, "collector accepted missing runtime library: %s", result)
361 require.Contains(t, string(result), "CEF runtime probe failed")
362}
363
364func TestFirstPresentationCollectorRejectsUnsupportedRuntime(t *testing.T) {
365 repoRoot, err := filepath.Abs(filepath.Join("..", "..", ".."))
366 require.NoError(t, err)
367 temp := t.TempDir()
368 runtime := collectorFakeCEFRuntime(t, 140)
369 binary := filepath.Join(temp, "dumber")
370 require.NoError(t, os.WriteFile(binary, collectorTestBinary(validFirstPresentationLog), 0o755))
371
372 goBin := collectorFakeGo(t, temp, testUpstreamVersion, testSourceRevision, false)
373 manifest := collectorManifest(t, temp, binary, testSourceRevision, testUpstreamVersion, testUpstreamRevision)
374 cmd := exec.Command(filepath.Join(repoRoot, "scripts", "collect_first_presentation.sh"))
375 cmd.Dir = repoRoot
376 cmd.Env = append(os.Environ(),
377 "DISPLAY=:test",
378 "DUMBER_CEF_DIR="+runtime,
379 "DUMBER_FIRST_PRESENTATION_BIN="+binary,
380 "DUMBER_BUILD_MANIFEST="+manifest,
381 "DUMBER_FIRST_PRESENTATION_OUTPUT="+filepath.Join(temp, "artifacts"),
382 "PATH="+filepath.Dir(goBin)+":"+os.Getenv("PATH"),
383 )
384 result, err := cmd.CombinedOutput()
385 require.Errorf(t, err, "collector accepted unsupported runtime: %s", result)
386 require.Contains(t, string(result), "unsupported CEF runtime")
387}
388
389func TestFirstPresentationCollectorRequiresManifestWithoutEmbeddedVCS(t *testing.T) {
390 repoRoot, err := filepath.Abs(filepath.Join("..", "..", ".."))
391 require.NoError(t, err)
392 temp := t.TempDir()
393 runtime := collectorFakeCEFRuntime(t, 150)
394 binary := filepath.Join(temp, "dumber")
395 require.NoError(t, os.WriteFile(binary, collectorTestBinary(validFirstPresentationLog), 0o755))
396
397 goBin := collectorFakeGo(t, temp, testUpstreamVersion, "", false)
398 cmd := exec.Command(filepath.Join(repoRoot, "scripts", "collect_first_presentation.sh"))
399 cmd.Dir = repoRoot
400 cmd.Env = append(os.Environ(),
401 "DISPLAY=:test",
402 "DUMBER_CEF_DIR="+runtime,
403 "DUMBER_FIRST_PRESENTATION_BIN="+binary,
404 "DUMBER_BUILD_MANIFEST="+filepath.Join(temp, "missing-manifest.json"),
405 "DUMBER_FIRST_PRESENTATION_OUTPUT="+filepath.Join(temp, "artifacts"),
406 "PATH="+filepath.Dir(goBin)+":"+os.Getenv("PATH"),
407 )
408 result, err := cmd.CombinedOutput()
409 require.Errorf(t, err, "collector accepted binary without VCS or manifest: %s", result)
410 require.Contains(t, string(result), "build manifest is required")
411}
412
413func TestFirstPresentationCollectorRejectsManifestMismatch(t *testing.T) {
414 repoRoot, err := filepath.Abs(filepath.Join("..", "..", ".."))
415 require.NoError(t, err)
416
417 for _, test := range []struct {
418 name string
419 sourceRevision string
420 depVersion string
421 depRevision string
422 mutateBinary bool
423 }{
424 {name: "binary checkout mismatch", sourceRevision: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", depVersion: testUpstreamVersion, depRevision: testUpstreamRevision},
425 {name: "dependency version mismatch", sourceRevision: testSourceRevision, depVersion: "v0.9.3", depRevision: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"},
426 {name: "dependency revision mismatch", sourceRevision: testSourceRevision, depVersion: testUpstreamVersion, depRevision: "cccccccccccccccccccccccccccccccccccccccc"},
427 } {
428 t.Run(test.name, func(t *testing.T) {
429 temp := t.TempDir()
430 runtime := collectorFakeCEFRuntime(t, 150)
431 binary := filepath.Join(temp, "dumber")
432 require.NoError(t, os.WriteFile(binary, collectorTestBinary(validFirstPresentationLog), 0o755))
433
434 goBin := collectorFakeGo(t, temp, testUpstreamVersion, testSourceRevision, false)
435 manifest := collectorManifest(t, temp, binary, test.sourceRevision, test.depVersion, test.depRevision)
436 if test.mutateBinary {
437 require.NoError(t, os.WriteFile(binary, collectorTestBinary(validFirstPresentationLog+"extra"), 0o755))
438 }
439 cmd := exec.Command(filepath.Join(repoRoot, "scripts", "collect_first_presentation.sh"))
440 cmd.Dir = repoRoot
441 cmd.Env = append(os.Environ(),
442 "DISPLAY=:test",
443 "DUMBER_CEF_DIR="+runtime,
444 "DUMBER_FIRST_PRESENTATION_BIN="+binary,
445 "DUMBER_BUILD_MANIFEST="+manifest,
446 "DUMBER_FIRST_PRESENTATION_OUTPUT="+filepath.Join(temp, "artifacts"),
447 "PATH="+filepath.Dir(goBin)+":"+os.Getenv("PATH"),
448 )
449 result, err := cmd.CombinedOutput()
450 require.Errorf(t, err, "collector accepted mismatched manifest: %s", result)
451 require.Contains(t, string(result), "build manifest does not match")
452 })
453 }
454}
455
456func TestFirstPresentationCollectorRejectsReplacementContamination(t *testing.T) {
457 repoRoot, err := filepath.Abs(filepath.Join("..", "..", ".."))
458 require.NoError(t, err)
459 temp := t.TempDir()
460 runtime := collectorFakeCEFRuntime(t, 150)
461 binary := filepath.Join(temp, "dumber")
462 require.NoError(t, os.WriteFile(binary, collectorTestBinary(validFirstPresentationLog), 0o755))
463
464 goBin := collectorFakeGo(t, temp, testUpstreamVersion, testSourceRevision, true)
465 manifest := collectorManifest(t, temp, binary, testSourceRevision, testUpstreamVersion, testUpstreamRevision)
466 cmd := exec.Command(filepath.Join(repoRoot, "scripts", "collect_first_presentation.sh"))
467 cmd.Dir = repoRoot
468 cmd.Env = append(os.Environ(),
469 "DISPLAY=:test",
470 "DUMBER_CEF_DIR="+runtime,
471 "DUMBER_FIRST_PRESENTATION_BIN="+binary,
472 "DUMBER_BUILD_MANIFEST="+manifest,
473 "DUMBER_FIRST_PRESENTATION_OUTPUT="+filepath.Join(temp, "artifacts"),
474 "PATH="+filepath.Dir(goBin)+":"+os.Getenv("PATH"),
475 )
476 result, err := cmd.CombinedOutput()
477 require.Errorf(t, err, "collector accepted replacement contamination: %s", result)
478 require.Contains(t, string(result), "immutable module provenance is unavailable")
479}
480
481// collectorFakeGo fakes `go version -m <binary>` for the measured binary.
482// It never consults the current checkout module graph.
483func collectorFakeGo(t *testing.T, temp, depVersion, vcsRevision string, replacement bool) string {
484 t.Helper()
485 binDir := filepath.Join(temp, "gobin")
486 require.NoError(t, os.MkdirAll(binDir, 0o755))
487 goBin := filepath.Join(binDir, "go")
488 depLine := fmt.Sprintf("dep\tgithub.com/bnema/purego-cef2gtk\t%s", depVersion)
489 if replacement {
490 depLine += " => ./local-override"
491 }
492 buildLine := ""
493 if vcsRevision != "" {
494 // Real `go version -m` format is "build vcs.revision=<hex>".
495 buildLine = fmt.Sprintf("build\tvcs.revision=%s", vcsRevision)
496 }
497 script := fmt.Sprintf(`#!/bin/sh
498if [ "$1" = "version" ] && [ "$2" = "-m" ]; then
499 printf '%%s\n' 'path\tgithub.com/bnema/dumber/cmd/dumber' 'mod\tgithub.com/bnema/dumber\t(devel)' '%s' '%s'
500 exit 0
501fi
502exit 1
503`, depLine, buildLine)
504 require.NoError(t, os.WriteFile(goBin, []byte(script), 0o755))
505 return goBin
506}
507
508func collectorManifest(t *testing.T, temp, binary, sourceRevision, depVersion, depRevision string) string {
509 t.Helper()
510 contents, err := os.ReadFile(binary)
511 require.NoError(t, err)
512 sum := sha256.Sum256(contents)
513 manifest := map[string]any{
514 "binary_sha256": hex.EncodeToString(sum[:]),
515 "source_revision": sourceRevision,
516 "modules": map[string]any{
517 "github.com/bnema/purego-cef2gtk": map[string]string{
518 "version": depVersion,
519 "revision": depRevision,
520 },
521 },
522 }
523 raw, err := json.Marshal(manifest)
524 require.NoError(t, err)
525 path := filepath.Join(temp, "build-manifest.json")
526 require.NoError(t, os.WriteFile(path, raw, 0o600))
527 return path
528}
529
530// collectorFakeCEFRuntime builds a tiny shared library exposing the
531// header-verified cef_version_info(int) entries with a controlled Chrome
532// major. It exercises the real ctypes probe path without copying a full
533// multi-hundred-megabyte runtime per test.
534func collectorFakeCEFRuntime(t *testing.T, chromeMajor int) string {
535 t.Helper()
536 runtime := t.TempDir()
537 source := fmt.Sprintf(`int cef_version_info(int entry) {
538 switch (entry) {
539 case 0: return 150;
540 case 1: return 0;
541 case 2: return 0;
542 case 3: return 0;
543 case 4: return %d;
544 case 5: return 0;
545 case 6: return 0;
546 case 7: return 0;
547 default: return 0;
548 }
549}
550`, chromeMajor)
551 src := filepath.Join(runtime, "fake_cef.c")
552 require.NoError(t, os.WriteFile(src, []byte(source), 0o600))
553 lib := filepath.Join(runtime, "libcef.so")
554 cmd := exec.Command("gcc", "-shared", "-fPIC", "-o", lib, src)
555 out, err := cmd.CombinedOutput()
556 require.NoErrorf(t, err, "failed to build fake libcef: %s", out)
557 return runtime
558}
559
560func TestFirstPresentationCollectorDefaultsToXDGStateEvidenceDirectory(t *testing.T) {
561 repoRoot, err := filepath.Abs(filepath.Join("..", "..", ".."))
562 require.NoError(t, err)
563 temp := t.TempDir()
564 runtime := collectorFakeCEFRuntime(t, 150)
565 binary := filepath.Join(temp, "dumber")
566 require.NoError(t, os.WriteFile(binary, collectorTestBinary(validFirstPresentationLog), 0o755))
567 stateHome := filepath.Join(temp, "state")
568 goBin := collectorFakeGo(t, temp, testUpstreamVersion, testSourceRevision, false)
569 manifest := collectorManifest(t, temp, binary, testSourceRevision, testUpstreamVersion, testUpstreamRevision)
570
571 cmd := exec.Command(filepath.Join(repoRoot, "scripts", "collect_first_presentation.sh"))
572 cmd.Dir = repoRoot
573 cmd.Env = append(envWithout("DUMBER_FIRST_PRESENTATION_OUTPUT"),
574 "DISPLAY=:test",
575 "WAYLAND_DISPLAY=",
576 "DUMBER_CEF_DIR="+runtime,
577 "DUMBER_FIRST_PRESENTATION_BIN="+binary,
578 "DUMBER_BUILD_MANIFEST="+manifest,
579 "DUMBER_FIRST_PRESENTATION_TIMEOUT_SECONDS=1",
580 "DUMBER_MACHINE_GPU_PROFILE=integrated-gpu",
581 "XDG_STATE_HOME="+stateHome,
582 "PATH="+filepath.Dir(goBin)+":"+os.Getenv("PATH"),
583 )
584 result, err := cmd.CombinedOutput()
585 require.NoErrorf(t, err, "collector failed: %s", result)
586
587 evidenceRoot := filepath.Join(stateHome, "dumber", "roadmap-evidence")
588 entries, err := os.ReadDir(evidenceRoot)
589 require.NoError(t, err)
590 require.Len(t, entries, 1)
591 require.True(t, entries[0].IsDir())
592 require.FileExists(t, filepath.Join(evidenceRoot, entries[0].Name(), "metadata.json"))
593 require.NotContains(t, string(result), filepath.Join(repoRoot, "phase1"))
594}
595
596func envWithout(name string) []string {
597 prefix := name + "="
598 var environment []string
599 for _, entry := range os.Environ() {
600 if !strings.HasPrefix(entry, prefix) {
601 environment = append(environment, entry)
602 }
603 }
604 return environment
605}
606
607func TestFirstPresentationCollectorBindsBinaryNotCheckoutGit(t *testing.T) {
608 repoRoot, err := filepath.Abs(filepath.Join("..", "..", ".."))
609 require.NoError(t, err)
610 temp := t.TempDir()
611 runtime := collectorFakeCEFRuntime(t, 150)
612 binary := filepath.Join(temp, "dumber")
613 require.NoError(t, os.WriteFile(binary, collectorTestBinary(validFirstPresentationLog), 0o755))
614 goBin := collectorFakeGo(t, temp, testUpstreamVersion, testSourceRevision, false)
615 manifest := collectorManifest(t, temp, binary, testSourceRevision, testUpstreamVersion, testUpstreamRevision)
616 gitDir := filepath.Join(temp, "gitbin")
617 require.NoError(t, os.Mkdir(gitDir, 0o755))
618 git := filepath.Join(gitDir, "git")
619 require.NoError(t, os.WriteFile(git, []byte(`#!/bin/sh
620echo "fatal: detected dubious ownership in repository" >&2
621exit 128
622`), 0o755))
623
624 output := filepath.Join(temp, "artifacts")
625 cmd := exec.Command(filepath.Join(repoRoot, "scripts", "collect_first_presentation.sh"))
626 cmd.Dir = repoRoot
627 cmd.Env = append(os.Environ(),
628 "DISPLAY=:test",
629 "DUMBER_CEF_DIR="+runtime,
630 "DUMBER_FIRST_PRESENTATION_BIN="+binary,
631 "DUMBER_BUILD_MANIFEST="+manifest,
632 "DUMBER_FIRST_PRESENTATION_OUTPUT="+output,
633 "DUMBER_FIRST_PRESENTATION_TIMEOUT_SECONDS=1",
634 "PATH="+gitDir+":"+filepath.Dir(goBin)+":"+os.Getenv("PATH"),
635 )
636 result, err := cmd.CombinedOutput()
637 require.NoErrorf(t, err, "collector depends on checkout git: %s", result)
638
639 var metadata struct {
640 MeasuredSourceRevision string `json:"measured_source_revision"`
641 }
642 contents, err := os.ReadFile(filepath.Join(output, "metadata.json"))
643 require.NoError(t, err)
644 require.NoError(t, json.Unmarshal(contents, &metadata))
645 require.Equal(t, testSourceRevision, metadata.MeasuredSourceRevision)
646}
647
648func TestFirstPresentationCollectorRejectsInconsistentTiming(t *testing.T) {
649 repoRoot, err := filepath.Abs(filepath.Join("..", "..", ".."))
650 require.NoError(t, err)
651
652 for _, test := range []struct {
653 name string
654 old string
655 new string
656 }{
657 {name: "non-integer delta", old: `"delta_ms":1}`, new: `"delta_ms":"1"}`},
658 {name: "incorrect delta", old: `"delta_ms":1}`, new: `"delta_ms":2}`},
659 {name: "non-integer summary total", old: `"total_ms":7`, new: `"total_ms":7.5`},
660 {name: "incorrect summary total", old: `"total_ms":7`, new: `"total_ms":8`},
661 } {
662 t.Run(test.name, func(t *testing.T) {
663 temp := t.TempDir()
664 runtime := collectorFakeCEFRuntime(t, 150)
665 binary := filepath.Join(temp, "dumber")
666 log := strings.Replace(validFirstPresentationLog, test.old, test.new, 1)
667 require.NoError(t, os.WriteFile(binary, collectorTestBinary(log), 0o755))
668 goBin := collectorFakeGo(t, temp, testUpstreamVersion, testSourceRevision, false)
669 manifest := collectorManifest(t, temp, binary, testSourceRevision, testUpstreamVersion, testUpstreamRevision)
670
671 cmd := exec.Command(filepath.Join(repoRoot, "scripts", "collect_first_presentation.sh"))
672 cmd.Dir = repoRoot
673 cmd.Env = append(os.Environ(),
674 "DISPLAY=:test",
675 "DUMBER_CEF_DIR="+runtime,
676 "DUMBER_FIRST_PRESENTATION_BIN="+binary,
677 "DUMBER_BUILD_MANIFEST="+manifest,
678 "DUMBER_FIRST_PRESENTATION_OUTPUT="+filepath.Join(temp, "artifacts"),
679 "DUMBER_FIRST_PRESENTATION_TIMEOUT_SECONDS=1",
680 "PATH="+filepath.Dir(goBin)+":"+os.Getenv("PATH"),
681 )
682 result, err := cmd.CombinedOutput()
683 require.Errorf(t, err, "collector accepted malformed timing artifact: %s", result)
684 require.Contains(t, string(result), "invalid or incomplete non-DMABUF timeline")
685 })
686 }
687}
688
689func collectorTestBinary(log string) []byte {
690 return []byte("#!/bin/sh\ncat <<'EOF'\n" + log + "\nEOF\nexit 139\n")
691}
692
693func runArtifactNames() []string {
694 return []string{"run-01.json", "run-02.json", "run-03.json", "run-04.json", "run-05.json"}
695}