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}