v0.33.1 9577fee

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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close