v0.4.0 91c6a74

display_test.go RawHistoryFolder

1package server
2
3import (
4	"context"
5	"golang.org/x/sys/unix"
6	"os"
7	"testing"
8	"time"
9	"unsafe"
10)
11
12func TestNoHandlerClosesFDs(t *testing.T) {
13	for _, sig := range []string{"2?oh", "12uhu"} {
14		fds := []int{0, 0}
15		if err := unix.Pipe(fds); err != nil {
16			t.Fatal(err)
17		}
18		defer unix.Close(fds[1])
19		args := make([]Arg, argCount(sig))
20		i := 0
21		for _, ch := range sig {
22			if ch == '?' || (ch >= '0' && ch <= '9') {
23				continue
24			}
25			if ch == 'h' {
26				args[i] = Fd(fds[0])
27			}
28			i++
29		}
30		closeRequestFDs(sig, unsafe.Pointer(&args[0]))
31		if _, err := unix.FcntlInt(uintptr(fds[0]), unix.F_GETFD, 0); err != unix.EBADF {
32			t.Fatalf("signature %s: fd not closed: %v", sig, err)
33		}
34	}
35}
36
37func TestDoStopped(t *testing.T) {
38	d, err := NewDisplay()
39	if err != nil {
40		t.Fatal(err)
41	}
42	ctx, cancel := context.WithCancel(context.Background())
43	returned := make(chan error, 1)
44	go func() { returned <- d.Run(ctx) }()
45	ran := false
46	if !d.Do(func() { ran = true }) || !ran {
47		t.Fatal("call did not run")
48	}
49	cancel()
50	select {
51	case err := <-returned:
52		if err != nil {
53			t.Fatal(err)
54		}
55	case <-time.After(3 * time.Second):
56		t.Fatal("Run did not return")
57	}
58	select {
59	case <-d.Stopped():
60	default:
61		t.Fatal("Stopped not closed")
62	}
63	result := make(chan bool, 1)
64	go func() { result <- d.Do(func() { t.Error("call ran after stop") }) }()
65	select {
66	case ok := <-result:
67		if ok {
68			t.Fatal("call confirmed after stop")
69		}
70	case <-time.After(100 * time.Millisecond):
71		t.Fatal("Do blocked after stop")
72	}
73}
74
75func TestDoBlockedDuringCancellation(t *testing.T) {
76	d, err := NewDisplay()
77	if err != nil {
78		t.Fatal(err)
79	}
80	ctx, cancel := context.WithCancel(context.Background())
81	returned := make(chan error, 1)
82	go func() { returned <- d.Run(ctx) }()
83	entered := make(chan struct{})
84	release := make(chan struct{})
85	first := make(chan bool, 1)
86	go func() { first <- d.Do(func() { close(entered); <-release }) }()
87	select {
88	case <-entered:
89	case <-time.After(3 * time.Second):
90		t.Fatal("first call not started")
91	}
92	result := make(chan bool, 1)
93	go func() { result <- d.Do(func() {}) }()
94	cancel()
95	close(release)
96	select {
97	case <-first:
98	case <-time.After(3 * time.Second):
99		t.Fatal("first call blocked")
100	}
101	select {
102	case err := <-returned:
103		if err != nil {
104			t.Fatal(err)
105		}
106	case <-time.After(3 * time.Second):
107		t.Fatal("Run blocked")
108	}
109	select {
110	case <-result:
111	case <-time.After(100 * time.Millisecond):
112		t.Fatal("blocked Do leaked")
113	}
114}
115
116func TestCloseBeforeRun(t *testing.T) {
117	d, err := NewDisplay()
118	if err != nil {
119		t.Fatal(err)
120	}
121	fd, err := unix.Socket(unix.AF_UNIX, unix.SOCK_STREAM, 0)
122	if err != nil {
123		t.Fatal(err)
124	}
125	defer unix.Close(fd)
126	if err := unix.Bind(fd, &unix.SockaddrUnix{Name: "\x00purego-close-test"}); err != nil {
127		t.Fatal(err)
128	}
129	if err := unix.Listen(fd, 1); err != nil {
130		t.Fatal(err)
131	}
132	if err := d.AddSocketFD(fd); err != nil {
133		t.Fatal(err)
134	}
135	d.Close()
136	d.Close()
137}
138
139// Do must not wait for the dispatch timeout: a compositor sends one call per
140// pointer event, at up to 1000 per second.
141func TestDoLatency(t *testing.T) {
142	d, err := NewDisplay()
143	if err != nil {
144		t.Fatal(err)
145	}
146	ctx, cancel := context.WithCancel(context.Background())
147	returned := make(chan error, 1)
148	go func() { returned <- d.Run(ctx) }()
149	defer func() { cancel(); <-returned }()
150	start := time.Now()
151	for range 1000 {
152		if !d.Do(func() {}) {
153			t.Fatal("Do failed")
154		}
155	}
156	if elapsed := time.Since(start); elapsed > 500*time.Millisecond {
157		t.Fatalf("1000 calls took %v", elapsed)
158	}
159}
160
161func TestCloseReleasesFDs(t *testing.T) {
162	count := func() int {
163		es, err := os.ReadDir("/proc/self/fd")
164		if err != nil {
165			t.Skip(err)
166		}
167		return len(es)
168	}
169	d, err := NewDisplay()
170	if err != nil {
171		t.Fatal(err)
172	}
173	d.Close() // first display loads the library; measure the next ones
174	before := count()
175	for range 3 {
176		d, err := NewDisplay()
177		if err != nil {
178			t.Fatal(err)
179		}
180		d.Close()
181	}
182	if after := count(); after != before {
183		t.Fatalf("fd leak: %d -> %d", before, after)
184	}
185}

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 14

↑↓ moveenter openesc close