1package zerobus
2
3import (
4 "bufio"
5 "errors"
6 "os/exec"
7 "strings"
8 "testing"
9 "time"
10)
11
12// privateBus starts a dbus-daemon for the test and returns its address. The
13// test is skipped when dbus-daemon is not installed.
14func privateBus(t *testing.T) string {
15 t.Helper()
16 path, err := exec.LookPath("dbus-daemon")
17 if err != nil {
18 t.Skip("dbus-daemon not installed")
19 }
20 cmd := exec.Command(path, "--session", "--nofork", "--print-address")
21 out, err := cmd.StdoutPipe()
22 if err != nil {
23 t.Fatal(err)
24 }
25 if err := cmd.Start(); err != nil {
26 t.Fatal(err)
27 }
28 t.Cleanup(func() {
29 _ = cmd.Process.Kill()
30 _ = cmd.Wait()
31 })
32 line := make(chan string, 1)
33 go func() {
34 s, _ := bufio.NewReader(out).ReadString('\n')
35 line <- strings.TrimSpace(s)
36 }()
37 select {
38 case addr := <-line:
39 if addr == "" {
40 t.Fatal("dbus-daemon printed no address")
41 }
42 return addr
43 case <-time.After(5 * time.Second):
44 t.Fatal("dbus-daemon did not start")
45 }
46 return ""
47}
48
49func dial(t *testing.T, addr string) *Conn {
50 t.Helper()
51 c, err := Dial(addr)
52 if err != nil {
53 t.Fatal(err)
54 }
55 t.Cleanup(func() { _ = c.Close() })
56 return c
57}
58
59func TestHelloAndListNames(t *testing.T) {
60 c := dial(t, privateBus(t))
61 if !strings.HasPrefix(c.UniqueName(), ":") {
62 t.Fatalf("unique name %q", c.UniqueName())
63 }
64 c.NewCall(busName, busPath, busName, "ListNames", "")
65 m, err := c.Call()
66 if err != nil {
67 t.Fatal(err)
68 }
69 r := m.Body()
70 found := false
71 end := r.Array('s')
72 for r.More(end) {
73 if r.Str() == c.UniqueName() {
74 found = true
75 }
76 }
77 if !r.Done() || !found {
78 t.Fatalf("own name not listed: %v", r.Err())
79 }
80}
81
82func TestErrorReply(t *testing.T) {
83 c := dial(t, privateBus(t))
84 c.NewCall("org.example.Missing", "/", "org.example.I", "M", "").SetFlags(FlagNoAutoStart)
85 _, err := c.Call()
86 var de *Error
87 // ServiceUnknown or NameHasNoOwner, depending on the bus.
88 if !errors.As(err, &de) || !strings.HasPrefix(de.Name, "org.freedesktop.DBus.Error.") || de.Message == "" {
89 t.Fatalf("err = %v", err)
90 }
91 // The connection stays usable after an error reply.
92 if _, err := c.RequestName("org.example.Zerobus", NameFlagDoNotQueue); err != nil {
93 t.Fatal(err)
94 }
95}
96
97func TestSignalAndMethodBetweenPeers(t *testing.T) {
98 addr := privateBus(t)
99 server := dial(t, addr)
100 client := dial(t, addr)
101
102 if code, err := server.RequestName("org.example.Echo", NameFlagDoNotQueue); err != nil || code != NamePrimaryOwner {
103 t.Fatalf("RequestName = %d, %v", code, err)
104 }
105 if err := client.AddMatch("type='signal',interface='org.example.Echo'"); err != nil {
106 t.Fatal(err)
107 }
108
109 // The server answers one Echo call, then emits a signal.
110 done := make(chan error, 1)
111 go func() {
112 for {
113 m, err := server.ReadMessage()
114 if err != nil {
115 done <- err
116 return
117 }
118 if m.Type != TypeMethodCall || m.Member != "Echo" {
119 continue // NameAcquired and similar
120 }
121 s := m.Body().Str()
122 server.NewReply(m, "s").Str(strings.ToUpper(s))
123 if _, err := server.Send(); err != nil {
124 done <- err
125 return
126 }
127 server.NewSignal("/org/example/Echo", "org.example.Echo", "Echoed", "s").Str(s)
128 _, err = server.Send()
129 done <- err
130 return
131 }
132 }()
133
134 client.NewCall("org.example.Echo", "/org/example/Echo", "org.example.Echo", "Echo", "s").Str("hi")
135 m, err := client.Call()
136 if err != nil {
137 t.Fatal(err)
138 }
139 if got := m.Body().Str(); got != "HI" {
140 t.Fatalf("reply %q", got)
141 }
142 if err := <-done; err != nil {
143 t.Fatal(err)
144 }
145 for {
146 m, err := client.ReadMessage()
147 if err != nil {
148 t.Fatal(err)
149 }
150 if m.Type == TypeSignal && m.Member == "Echoed" {
151 if s := m.Body().Str(); s != "hi" || m.Sender != server.UniqueName() {
152 t.Fatalf("signal %q from %q", s, m.Sender)
153 }
154 return
155 }
156 }
157}
158
159func TestHandleDuringCall(t *testing.T) {
160 addr := privateBus(t)
161 c := dial(t, addr)
162 peer := dial(t, addr)
163 if err := c.AddMatch("type='signal',interface='org.example.Ping'"); err != nil {
164 t.Fatal(err)
165 }
166 peer.NewSignal("/a", "org.example.Ping", "Ping", "")
167 if _, err := peer.Send(); err != nil {
168 t.Fatal(err)
169 }
170 // A round trip from the peer proves the bus has routed the signal.
171 peer.NewCall(busName, busPath, busName, "GetId", "")
172 if _, err := peer.Call(); err != nil {
173 t.Fatal(err)
174 }
175
176 var got string
177 var nested error
178 c.Handle = func(m *Message) {
179 if m.Member == "Ping" {
180 got = strings.Clone(m.Member)
181 _, nested = c.ReadMessage()
182 // Sending from Handle is allowed.
183 c.NewSignal("/a", "org.example.Pong", "Pong", "")
184 if _, err := c.Send(); err != nil {
185 t.Error(err)
186 }
187 }
188 }
189 // The signal is queued before the reply, so Call passes it to Handle.
190 c.NewCall(busName, busPath, busName, "GetId", "")
191 if _, err := c.Call(); err != nil {
192 t.Fatal(err)
193 }
194 if got != "Ping" || !errors.Is(nested, ErrNested) {
195 t.Fatalf("handled %q, nested read: %v", got, nested)
196 }
197}
198
199func TestCallRejectsNoReply(t *testing.T) {
200 c := dial(t, privateBus(t))
201 c.NewCall(busName, busPath, busName, "GetId", "").SetFlags(FlagNoReplyExpected)
202 if _, err := c.Call(); !errors.Is(err, ErrInvalid) {
203 t.Fatalf("err = %v, want ErrInvalid", err)
204 }
205 c.NewSignal("/a", "a.b", "C", "")
206 if _, err := c.Call(); !errors.Is(err, ErrInvalid) {
207 t.Fatalf("Call on a signal: %v, want ErrInvalid", err)
208 }
209 // The connection still works.
210 c.NewCall(busName, busPath, busName, "GetId", "")
211 if _, err := c.Call(); err != nil {
212 t.Fatal(err)
213 }
214}
215
216func TestCloseUnblocksRead(t *testing.T) {
217 c := dial(t, privateBus(t))
218 errc := make(chan error, 1)
219 go func() {
220 for {
221 if _, err := c.ReadMessage(); err != nil {
222 errc <- err
223 return
224 }
225 }
226 }()
227 time.Sleep(50 * time.Millisecond)
228 if err := c.Close(); err != nil {
229 t.Fatal(err)
230 }
231 select {
232 case err := <-errc:
233 if !errors.Is(err, ErrClosed) {
234 t.Fatalf("err = %v, want ErrClosed", err)
235 }
236 case <-time.After(5 * time.Second):
237 t.Fatal("ReadMessage did not return after Close")
238 }
239}
240
241func TestCallNoAllocs(t *testing.T) {
242 c := dial(t, privateBus(t))
243 call := func() {
244 c.NewCall(busName, busPath, busName, "GetNameOwner", "s").Str(busName)
245 m, err := c.Call()
246 if err != nil {
247 t.Fatal(err)
248 }
249 if m.Body().Str() == "" {
250 t.Fatal("empty owner")
251 }
252 }
253 call() // grow the buffers once
254 if allocs := testing.AllocsPerRun(200, call); allocs != 0 {
255 t.Fatalf("%v allocations per call, want 0", allocs)
256 }
257}
258
259func TestLargeMessageGrowsBuffer(t *testing.T) {
260 addr := privateBus(t)
261 a := dial(t, addr)
262 b := dial(t, addr)
263 if err := b.AddMatch("type='signal',interface='org.example.Big'"); err != nil {
264 t.Fatal(err)
265 }
266 payload := make([]byte, 3*defaultBufSz)
267 for i := range payload {
268 payload[i] = byte(i)
269 }
270 a.NewSignal("/a", "org.example.Big", "Blob", "ay").ByteArray(payload)
271 if _, err := a.Send(); err != nil {
272 t.Fatal(err)
273 }
274 for {
275 m, err := b.ReadMessage()
276 if err != nil {
277 t.Fatal(err)
278 }
279 if m.Member == "Blob" {
280 got := m.Body().ByteArray()
281 if len(got) != len(payload) || got[len(got)-1] != payload[len(payload)-1] {
282 t.Fatalf("payload of %d bytes", len(got))
283 }
284 return
285 }
286 }
287}
288
289func TestDialErrors(t *testing.T) {
290 for _, addr := range []string{"", "tcp:host=localhost,port=1", "unix:guid=abc", "unix:path=/nonexistent/zerobus"} {
291 if c, err := Dial(addr); err == nil {
292 _ = c.Close()
293 t.Errorf("Dial(%q) succeeded", addr)
294 }
295 }
296}
297
298func TestUnescape(t *testing.T) {
299 got, err := unescape("/tmp/a%20b%2c")
300 if err != nil || got != "/tmp/a b," {
301 t.Fatalf("got %q, %v", got, err)
302 }
303 if _, err := unescape("%zz"); err == nil {
304 t.Fatal("bad escape accepted")
305 }
306}