detached 8d86d44

fix(obs): isolate role XDG directories

commit
8d86d44339a6cdb432a69eeb1d76e367b2cc888e
author
brice · · 2026-07-13 02:25 UTC
parent
89cf334
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +54 -1
M cmd/vev-perf-harness/main.go +1 -1
@@ -287,7 +287,7 @@ func (l *cliLauncher) Launch(m processMapping, role roleCommand) (launchedProces
287287 // a role working directory: daemon-owned subprocess cleanup may otherwise
288288 // treat the evidence directory as its working tree and remove a preallocated
289289 // trace while a later repetition is being merged.
290- cmd.Env = append(withoutEnv(os.Environ(), "VEV", "VEV_PERF_TRACE", "VEV_PERF_PROCESS_ID", "VEV_PERF_SCENARIO", "VEV_PERF_RUN", "VEV_REMOTE_TRANSPORT"), traceEnvironment(m)...)
290+ cmd.Env = append(withoutEnv(os.Environ(), "VEV", "VEV_PERF_TRACE", "VEV_PERF_PROCESS_ID", "VEV_PERF_SCENARIO", "VEV_PERF_RUN", "VEV_REMOTE_TRANSPORT", "XDG_RUNTIME_DIR", "XDG_STATE_HOME"), traceEnvironment(m)...)
291291 cmd.Env = append(cmd.Env, "XDG_RUNTIME_DIR="+runtimeDir,
292292 "XDG_STATE_HOME="+filepath.Join(runDir, "state"), "TERM=xterm-256color")
293293 if m.Role == "client" {
@@ -287,7 +287,7 @@ func (l *cliLauncher) Launch(m processMapping, role roleCommand) (launchedProces
287 // a role working directory: daemon-owned subprocess cleanup may otherwise287 // a role working directory: daemon-owned subprocess cleanup may otherwise
288 // treat the evidence directory as its working tree and remove a preallocated288 // treat the evidence directory as its working tree and remove a preallocated
289 // trace while a later repetition is being merged.289 // trace while a later repetition is being merged.
290- cmd.Env = append(withoutEnv(os.Environ(), "VEV", "VEV_PERF_TRACE", "VEV_PERF_PROCESS_ID", "VEV_PERF_SCENARIO", "VEV_PERF_RUN", "VEV_REMOTE_TRANSPORT"), traceEnvironment(m)...)290+ cmd.Env = append(withoutEnv(os.Environ(), "VEV", "VEV_PERF_TRACE", "VEV_PERF_PROCESS_ID", "VEV_PERF_SCENARIO", "VEV_PERF_RUN", "VEV_REMOTE_TRANSPORT", "XDG_RUNTIME_DIR", "XDG_STATE_HOME"), traceEnvironment(m)...)
291 cmd.Env = append(cmd.Env, "XDG_RUNTIME_DIR="+runtimeDir,291 cmd.Env = append(cmd.Env, "XDG_RUNTIME_DIR="+runtimeDir,
292 "XDG_STATE_HOME="+filepath.Join(runDir, "state"), "TERM=xterm-256color")292 "XDG_STATE_HOME="+filepath.Join(runDir, "state"), "TERM=xterm-256color")
293 if m.Role == "client" {293 if m.Role == "client" {
M cmd/vev-perf-harness/main_test.go +53 -0
@@ -130,6 +130,59 @@ func TestCLIProcessWaitReadyRequiresDaemonSocket(t *testing.T) {
130130 }
131131 }
132132
133+func TestCLILauncherIsolatesRoleXDGEnvironmentAcrossRepetitions(t *testing.T) {
134+ captureDir := t.TempDir()
135+ t.Setenv("CAPTURE_DIR", captureDir)
136+ t.Setenv("XDG_RUNTIME_DIR", filepath.Join(t.TempDir(), "inherited-runtime"))
137+ t.Setenv("XDG_STATE_HOME", filepath.Join(t.TempDir(), "inherited-state"))
138+
139+ bin := filepath.Join(t.TempDir(), "capture-env")
140+ if err := os.WriteFile(bin, []byte("#!/bin/sh\nenv > \"$CAPTURE_DIR/$VEV_PERF_PROCESS_ID\"\n"), 0o700); err != nil {
141+ t.Fatal(err)
142+ }
143+ launcher := &cliLauncher{bin: bin}
144+ for run := 1; run <= 3; run++ {
145+ runDir := filepath.Join(t.TempDir(), fmt.Sprintf("run-%d", run))
146+ if err := os.MkdirAll(runDir, 0o700); err != nil {
147+ t.Fatal(err)
148+ }
149+ roles := []string{"daemon", "client"}
150+ for _, role := range roles {
151+ id := fmt.Sprintf("run-%d-%s", run, role)
152+ p, err := launcher.Launch(processMapping{
153+ Role: role, TracePath: filepath.Join(runDir, role+".jsonl"), ProcessID: id,
154+ }, roleCommand{Args: []string{"test"}})
155+ if err != nil {
156+ t.Fatalf("run %d %s launch: %v", run, role, err)
157+ }
158+ runtimeDir := launcher.runtimes[runDir]
159+ if err := p.Close(); err != nil {
160+ t.Fatalf("run %d %s close: %v", run, role, err)
161+ }
162+
163+ env, err := os.ReadFile(filepath.Join(captureDir, id))
164+ if err != nil {
165+ t.Fatalf("run %d %s capture: %v", run, role, err)
166+ }
167+ for _, want := range []string{
168+ "XDG_RUNTIME_DIR=" + runtimeDir,
169+ "XDG_STATE_HOME=" + filepath.Join(runDir, "state"),
170+ } {
171+ name := strings.SplitN(want, "=", 2)[0]
172+ if count := bytes.Count(env, []byte(name+"=")); count != 1 {
173+ t.Fatalf("run %d %s %s entries = %d, want exactly one; env:\n%s", run, role, name, count, env)
174+ }
175+ if !bytes.Contains(env, []byte(want+"\n")) {
176+ t.Fatalf("run %d %s missing %q; env:\n%s", run, role, want, env)
177+ }
178+ }
179+ }
180+ if err := launcher.releaseRuntime(runDir); err != nil {
181+ t.Fatalf("run %d release runtime: %v", run, err)
182+ }
183+ }
184+}
185+
133186 func TestCLIProcessCloseUsesPublicDaemonShutdown(t *testing.T) {
134187 wait := make(chan error, 1)
135188 wait <- nil
@@ -130,6 +130,59 @@ func TestCLIProcessWaitReadyRequiresDaemonSocket(t *testing.T) {
130 }130 }
131 }131 }
132 132
133+func TestCLILauncherIsolatesRoleXDGEnvironmentAcrossRepetitions(t *testing.T) {
134+ captureDir := t.TempDir()
135+ t.Setenv("CAPTURE_DIR", captureDir)
136+ t.Setenv("XDG_RUNTIME_DIR", filepath.Join(t.TempDir(), "inherited-runtime"))
137+ t.Setenv("XDG_STATE_HOME", filepath.Join(t.TempDir(), "inherited-state"))
138+
139+ bin := filepath.Join(t.TempDir(), "capture-env")
140+ if err := os.WriteFile(bin, []byte("#!/bin/sh\nenv > \"$CAPTURE_DIR/$VEV_PERF_PROCESS_ID\"\n"), 0o700); err != nil {
141+ t.Fatal(err)
142+ }
143+ launcher := &cliLauncher{bin: bin}
144+ for run := 1; run <= 3; run++ {
145+ runDir := filepath.Join(t.TempDir(), fmt.Sprintf("run-%d", run))
146+ if err := os.MkdirAll(runDir, 0o700); err != nil {
147+ t.Fatal(err)
148+ }
149+ roles := []string{"daemon", "client"}
150+ for _, role := range roles {
151+ id := fmt.Sprintf("run-%d-%s", run, role)
152+ p, err := launcher.Launch(processMapping{
153+ Role: role, TracePath: filepath.Join(runDir, role+".jsonl"), ProcessID: id,
154+ }, roleCommand{Args: []string{"test"}})
155+ if err != nil {
156+ t.Fatalf("run %d %s launch: %v", run, role, err)
157+ }
158+ runtimeDir := launcher.runtimes[runDir]
159+ if err := p.Close(); err != nil {
160+ t.Fatalf("run %d %s close: %v", run, role, err)
161+ }
162+
163+ env, err := os.ReadFile(filepath.Join(captureDir, id))
164+ if err != nil {
165+ t.Fatalf("run %d %s capture: %v", run, role, err)
166+ }
167+ for _, want := range []string{
168+ "XDG_RUNTIME_DIR=" + runtimeDir,
169+ "XDG_STATE_HOME=" + filepath.Join(runDir, "state"),
170+ } {
171+ name := strings.SplitN(want, "=", 2)[0]
172+ if count := bytes.Count(env, []byte(name+"=")); count != 1 {
173+ t.Fatalf("run %d %s %s entries = %d, want exactly one; env:\n%s", run, role, name, count, env)
174+ }
175+ if !bytes.Contains(env, []byte(want+"\n")) {
176+ t.Fatalf("run %d %s missing %q; env:\n%s", run, role, want, env)
177+ }
178+ }
179+ }
180+ if err := launcher.releaseRuntime(runDir); err != nil {
181+ t.Fatalf("run %d release runtime: %v", run, err)
182+ }
183+ }
184+}
185+
133 func TestCLIProcessCloseUsesPublicDaemonShutdown(t *testing.T) {186 func TestCLIProcessCloseUsesPublicDaemonShutdown(t *testing.T) {
134 wait := make(chan error, 1)187 wait := make(chan error, 1)
135 wait <- nil188 wait <- nil

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 12

Tags 11

↑↓ moveenter openesc close