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}