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
41// Objects libwayland created itself (wl_registry) come back as bare handles.
42func TestArgResourceUntracked(t *testing.T) {
43 if Arg(0).Resource() != nil {
44 t.Fatal("null object is not nil")
45 }
46 r := Arg(0x1234).Resource()
47 if r == nil || r.c != 0x1234 || r.handler != nil || !r.bare || !r.Alive() {
48 t.Fatalf("untracked object %+v", r)
49 }
50}
51
52func TestAckGlobalRemoveNilResources(t *testing.T) {
53 if err := load(); err != nil {
54 t.Skip(err)
55 }
56 // A dead or missing resource is ignored, whatever the library version.
57 supported := AckGlobalRemove(nil, nil, 1)
58 if supported != (symFixesAckGlobalRemove != 0) {
59 t.Fatalf("supported %v with symbol %#x", supported, symFixesAckGlobalRemove)
60 }
61}
62
63func TestDoStopped(t *testing.T) {
64 d, err := NewDisplay()
65 if err != nil {
66 t.Fatal(err)
67 }
68 ctx, cancel := context.WithCancel(context.Background())
69 returned := make(chan error, 1)
70 go func() { returned <- d.Run(ctx) }()
71 ran := false
72 if !d.Do(func() { ran = true }) || !ran {
73 t.Fatal("call did not run")
74 }
75 cancel()
76 select {
77 case err := <-returned:
78 if err != nil {
79 t.Fatal(err)
80 }
81 case <-time.After(3 * time.Second):
82 t.Fatal("Run did not return")
83 }
84 select {
85 case <-d.Stopped():
86 default:
87 t.Fatal("Stopped not closed")
88 }
89 result := make(chan bool, 1)
90 go func() { result <- d.Do(func() { t.Error("call ran after stop") }) }()
91 select {
92 case ok := <-result:
93 if ok {
94 t.Fatal("call confirmed after stop")
95 }
96 case <-time.After(100 * time.Millisecond):
97 t.Fatal("Do blocked after stop")
98 }
99}
100
101func TestDoBlockedDuringCancellation(t *testing.T) {
102 d, err := NewDisplay()
103 if err != nil {
104 t.Fatal(err)
105 }
106 ctx, cancel := context.WithCancel(context.Background())
107 returned := make(chan error, 1)
108 go func() { returned <- d.Run(ctx) }()
109 entered := make(chan struct{})
110 release := make(chan struct{})
111 first := make(chan bool, 1)
112 go func() { first <- d.Do(func() { close(entered); <-release }) }()
113 select {
114 case <-entered:
115 case <-time.After(3 * time.Second):
116 t.Fatal("first call not started")
117 }
118 result := make(chan bool, 1)
119 go func() { result <- d.Do(func() {}) }()
120 cancel()
121 close(release)
122 select {
123 case <-first:
124 case <-time.After(3 * time.Second):
125 t.Fatal("first call blocked")
126 }
127 select {
128 case err := <-returned:
129 if err != nil {
130 t.Fatal(err)
131 }
132 case <-time.After(3 * time.Second):
133 t.Fatal("Run blocked")
134 }
135 select {
136 case <-result:
137 case <-time.After(100 * time.Millisecond):
138 t.Fatal("blocked Do leaked")
139 }
140}
141
142func TestCloseBeforeRun(t *testing.T) {
143 d, err := NewDisplay()
144 if err != nil {
145 t.Fatal(err)
146 }
147 fd, err := unix.Socket(unix.AF_UNIX, unix.SOCK_STREAM, 0)
148 if err != nil {
149 t.Fatal(err)
150 }
151 defer unix.Close(fd)
152 if err := unix.Bind(fd, &unix.SockaddrUnix{Name: "\x00purego-close-test"}); err != nil {
153 t.Fatal(err)
154 }
155 if err := unix.Listen(fd, 1); err != nil {
156 t.Fatal(err)
157 }
158 if err := d.AddSocketFD(fd); err != nil {
159 t.Fatal(err)
160 }
161 d.Close()
162 d.Close()
163}
164
165// Do must not wait for the dispatch timeout: a compositor sends one call per
166// pointer event, at up to 1000 per second.
167func TestDoLatency(t *testing.T) {
168 d, err := NewDisplay()
169 if err != nil {
170 t.Fatal(err)
171 }
172 ctx, cancel := context.WithCancel(context.Background())
173 returned := make(chan error, 1)
174 go func() { returned <- d.Run(ctx) }()
175 defer func() { cancel(); <-returned }()
176 start := time.Now()
177 for range 1000 {
178 if !d.Do(func() {}) {
179 t.Fatal("Do failed")
180 }
181 }
182 if elapsed := time.Since(start); elapsed > 500*time.Millisecond {
183 t.Fatalf("1000 calls took %v", elapsed)
184 }
185}
186
187func TestCloseReleasesFDs(t *testing.T) {
188 count := func() int {
189 es, err := os.ReadDir("/proc/self/fd")
190 if err != nil {
191 t.Skip(err)
192 }
193 return len(es)
194 }
195 d, err := NewDisplay()
196 if err != nil {
197 t.Fatal(err)
198 }
199 d.Close() // first display loads the library; measure the next ones
200 before := count()
201 for range 3 {
202 d, err := NewDisplay()
203 if err != nil {
204 t.Fatal(err)
205 }
206 d.Close()
207 }
208 if after := count(); after != before {
209 t.Fatalf("fd leak: %d -> %d", before, after)
210 }
211}