v0.11.5 c4261b5

process_unix_test.go RawHistoryFolder

1//go:build !windows
2
3package subagents
4
5import (
6	"context"
7	"errors"
8	"fmt"
9	"io"
10	"os"
11	"os/exec"
12	"strconv"
13	"strings"
14	"syscall"
15	"testing"
16	"time"
17)
18
19func TestWorkerCancellationKillsProcessGroup(t *testing.T) {
20	shell, err := exec.LookPath("sh")
21	if err != nil {
22		t.Skip("sh is not available")
23	}
24
25	pidPath := t.TempDir() + "/child.pid"
26	ctx, cancel := context.WithCancel(context.Background())
27	defer cancel()
28
29	cmd := exec.CommandContext(ctx, shell, "-c", fmt.Sprintf("sleep 30 & echo $! > %q; wait", pidPath))
30	configureWorkerProcess(cmd)
31	if cmd.WaitDelay != workerProcessWaitDelay {
32		t.Fatalf("WaitDelay = %s, want %s", cmd.WaitDelay, workerProcessWaitDelay)
33	}
34	if err := cmd.Start(); err != nil {
35		t.Fatal(err)
36	}
37
38	childPID := waitForWorkerChildPID(t, pidPath)
39	cancel()
40
41	waitDone := make(chan error, 1)
42	go func() { waitDone <- cmd.Wait() }()
43	select {
44	case <-time.After(2 * time.Second):
45		t.Fatal("worker did not exit after context cancellation")
46	case err := <-waitDone:
47		if err == nil {
48			t.Fatal("worker exited successfully after context cancellation")
49		}
50	}
51
52	deadline := time.Now().Add(2 * time.Second)
53	for time.Now().Before(deadline) && processExists(childPID) {
54		time.Sleep(10 * time.Millisecond)
55	}
56	if processExists(childPID) {
57		t.Fatalf("worker descendant pid %d survived process-group cancellation", childPID)
58	}
59}
60
61func TestWorkerCancellationClosesOutputPipes(t *testing.T) {
62	cmd := exec.Command("sh")
63	stdout, err := cmd.StdoutPipe()
64	if err != nil {
65		t.Fatal(err)
66	}
67	stderr, err := cmd.StderrPipe()
68	if err != nil {
69		t.Fatal(err)
70	}
71	defer stdout.Close()
72	defer stderr.Close()
73
74	configureWorkerProcess(cmd, stdout, stderr)
75	if err := cmd.Cancel(); err != nil {
76		t.Fatal(err)
77	}
78	for name, pipe := range map[string]io.Reader{"stdout": stdout, "stderr": stderr} {
79		data := make([]byte, 1)
80		if _, err := pipe.Read(data); !errors.Is(err, os.ErrClosed) {
81			t.Errorf("%s read error = %v, want os.ErrClosed", name, err)
82		}
83	}
84}
85
86func waitForWorkerChildPID(t *testing.T, path string) int {
87	t.Helper()
88	deadline := time.Now().Add(2 * time.Second)
89	for time.Now().Before(deadline) {
90		data, err := os.ReadFile(path)
91		if err == nil {
92			pid, parseErr := strconv.Atoi(strings.TrimSpace(string(data)))
93			if parseErr == nil && pid > 0 {
94				return pid
95			}
96		}
97		time.Sleep(10 * time.Millisecond)
98	}
99	t.Fatalf("timed out waiting for child pid in %s", path)
100	return 0
101}
102
103func processExists(pid int) bool {
104	err := syscall.Kill(pid, 0)
105	return err == nil || errors.Is(err, syscall.EPERM)
106}

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 1

Tags 477

↑↓ moveenter openesc close