v0.5.0 ad674f5

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

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