1 package dbus
2
3 import (
4 "fmt"
5 "sync"
6 "sync/atomic"
7 "testing"
8 "time"
9 )
10
11 type tester struct {
12 conn *Conn
13 sigs chan *Signal
14
15 subSigsMu sync.Mutex
16 subSigs map[string]map[string]struct{}
17
18 serial uint32
19 }
20
21 type intro struct {
22 path ObjectPath
23 }
24
25 func (i *intro) introspectPath(path ObjectPath) string {
26 switch path {
27 case "/":
28 return `<node><node name="com"></node></node>`
29 case "/com":
30 return `<node><node name="github"></node></node>`
31 case "/com/github":
32 return `<node><node name="godbus"></node></node>`
33 case "/com/github/godbus":
34 return `<node><node name="tester"></node></node>`
35 }
36 return ""
37 }
38
39 func (i *intro) LookupInterface(name string) (Interface, bool) {
40 if name == "org.freedesktop.DBus.Introspectable" {
41 return i, true
42 }
43 return nil, false
44 }
45
46 func (i *intro) LookupMethod(name string) (Method, bool) {
47 if name == "Introspect" {
48 return intro_fn(func() string {
49 return i.introspectPath(i.path)
50 }), true
51 }
52 return nil, false
53 }
54
55 func newIntro(path ObjectPath) *intro {
56 return &intro{path}
57 }
58
59
60 func (t *tester) LookupObject(path ObjectPath) (ServerObject, bool) {
61 if path == "/com/github/godbus/tester" {
62 return t, true
63 }
64 return newIntro(path), true
65 }
66
67
68 func (t *tester) LookupInterface(name string) (Interface, bool) {
69 switch name {
70 case "com.github.godbus.dbus.Tester":
71 return t, true
72 case "org.freedesktop.DBus.Introspectable":
73 return t, true
74 }
75
76 return nil, false
77 }
78
79
80 func (t *tester) LookupMethod(name string) (Method, bool) {
81 switch name {
82 case "Test":
83 return t, true
84 case "Error":
85 return terrfn(func(in string) error {
86 return fmt.Errorf(in)
87 }), true
88 case "Introspect":
89 return intro_fn(func() string {
90 return `<node>
91 <interface name="org.freedesktop.DBus.Introspectable.Introspect">
92 <method name="Introspect">
93 <arg name="out" type="i" direction="out">
94 </method>
95 </interface>
96 <interface name="com.github.godbus.dbus.Tester">
97 <method name="Test">
98 <arg name="in" type="i" direction="in">
99 <arg name="out" type="i" direction="out">
100 </method>
101 <signal name="sig1">
102 <arg name="out" type="i" direction="out">
103 </signal>
104 </interface>
105 </node>`
106 }), true
107 }
108 return nil, false
109 }
110
111
112 func (t *tester) Call(args ...interface{}) ([]interface{}, error) {
113 return args, nil
114 }
115
116 func (t *tester) NumArguments() int {
117 return 1
118 }
119
120 func (t *tester) NumReturns() int {
121 return 1
122 }
123
124 func (t *tester) ArgumentValue(position int) interface{} {
125 return ""
126 }
127
128 func (t *tester) ReturnValue(position int) interface{} {
129 return ""
130 }
131
132 type terrfn func(in string) error
133
134 func (t terrfn) Call(args ...interface{}) ([]interface{}, error) {
135 return nil, t(*args[0].(*string))
136 }
137
138 func (t terrfn) NumArguments() int {
139 return 1
140 }
141
142 func (t terrfn) NumReturns() int {
143 return 0
144 }
145
146 func (t terrfn) ArgumentValue(position int) interface{} {
147 return ""
148 }
149
150 func (t terrfn) ReturnValue(position int) interface{} {
151 return ""
152 }
153
154
155 func (t *tester) DeliverSignal(iface, name string, signal *Signal) {
156 t.subSigsMu.Lock()
157 intf, ok := t.subSigs[iface]
158 t.subSigsMu.Unlock()
159 if !ok {
160 return
161 }
162 if _, ok := intf[name]; !ok {
163 return
164 }
165 t.sigs <- signal
166 }
167
168 func (t *tester) AddSignal(iface, name string) error {
169 t.subSigsMu.Lock()
170 if i, ok := t.subSigs[iface]; ok {
171 i[name] = struct{}{}
172 } else {
173 t.subSigs[iface] = make(map[string]struct{})
174 t.subSigs[iface][name] = struct{}{}
175 }
176 t.subSigsMu.Unlock()
177 return t.conn.AddMatchSignal(WithMatchInterface(iface), WithMatchMember(name))
178 }
179
180 func (t *tester) Close() {
181 t.conn.Close()
182 close(t.sigs)
183 }
184
185 func (t *tester) Name() string {
186 return t.conn.Names()[0]
187 }
188
189 func (t *tester) GetSerial() uint32 {
190 return atomic.AddUint32(&t.serial, 1)
191 }
192
193 func (t *tester) RetireSerial(serial uint32) {}
194
195 type intro_fn func() string
196
197 func (intro intro_fn) Call(args ...interface{}) ([]interface{}, error) {
198 return []interface{}{intro()}, nil
199 }
200
201 func (_ intro_fn) NumArguments() int {
202 return 0
203 }
204
205 func (_ intro_fn) NumReturns() int {
206 return 1
207 }
208
209 func (_ intro_fn) ArgumentValue(position int) interface{} {
210 return nil
211 }
212
213 func (_ intro_fn) ReturnValue(position int) interface{} {
214 return ""
215 }
216
217 func newTester() (*tester, error) {
218 tester := &tester{
219 sigs: make(chan *Signal),
220 subSigs: make(map[string]map[string]struct{}),
221 }
222 conn, err := ConnectSessionBus(
223 WithHandler(tester),
224 WithSignalHandler(tester),
225 WithSerialGenerator(tester),
226 )
227 if err != nil {
228 return nil, err
229 }
230 tester.conn = conn
231 return tester, nil
232 }
233
234 func TestHandlerCall(t *testing.T) {
235 tester, err := newTester()
236 if err != nil {
237 t.Errorf("Unexpected error: %s", err)
238 }
239 conn, err := ConnectSessionBus()
240 if err != nil {
241 t.Errorf("Unexpected error: %s", err)
242 }
243 defer conn.Close()
244 obj := conn.Object(tester.Name(), "/com/github/godbus/tester")
245 var out string
246 in := "foo"
247 err = obj.Call("com.github.godbus.dbus.Tester.Test", 0, in).Store(&out)
248 if err != nil {
249 t.Errorf("Unexpected error: %s", err)
250 }
251 if out != in {
252 t.Errorf("Unexpected error: got %s, expected %s", out, in)
253 }
254 tester.Close()
255 }
256
257 func TestHandlerCallGenericError(t *testing.T) {
258 tester, err := newTester()
259 if err != nil {
260 t.Errorf("Unexpected error: %s", err)
261 }
262 conn, err := ConnectSessionBus()
263 if err != nil {
264 t.Errorf("Unexpected error: %s", err)
265 }
266 defer conn.Close()
267 obj := conn.Object(tester.Name(), "/com/github/godbus/tester")
268 var out string
269 in := "foo"
270 err = obj.Call("com.github.godbus.dbus.Tester.Error", 0, in).Store(&out)
271 if err != nil && err.(Error).Body[0].(string) != "foo" {
272 t.Errorf("Unexpected error: %s", err)
273 }
274
275 tester.Close()
276 }
277
278 func TestHandlerCallNonExistent(t *testing.T) {
279 tester, err := newTester()
280 if err != nil {
281 t.Errorf("Unexpected error: %s", err)
282 }
283 conn, err := ConnectSessionBus()
284 if err != nil {
285 t.Errorf("Unexpected error: %s", err)
286 }
287 defer conn.Close()
288 obj := conn.Object(tester.Name(), "/com/github/godbus/tester/nonexist")
289 var out string
290 in := "foo"
291 err = obj.Call("com.github.godbus.dbus.Tester.Test", 0, in).Store(&out)
292 if err != nil {
293 if err.Error() != "Object does not implement the interface 'com.github.godbus.dbus.Tester'" {
294 t.Errorf("Unexpected error: %s", err)
295 }
296 }
297 tester.Close()
298 }
299
300 func TestHandlerInvalidFunc(t *testing.T) {
301 tester, err := newTester()
302 if err != nil {
303 t.Errorf("Unexpected error: %s", err)
304 }
305 conn, err := ConnectSessionBus()
306 if err != nil {
307 t.Errorf("Unexpected error: %s", err)
308 }
309 defer conn.Close()
310 obj := conn.Object(tester.Name(), "/com/github/godbus/tester")
311 var out string
312 in := "foo"
313 err = obj.Call("com.github.godbus.dbus.Tester.Notexist", 0, in).Store(&out)
314 if err == nil {
315 t.Errorf("didn't get expected error")
316 }
317 tester.Close()
318 }
319
320 func TestHandlerInvalidNumArg(t *testing.T) {
321 tester, err := newTester()
322 if err != nil {
323 t.Errorf("Unexpected error: %s", err)
324 }
325 conn, err := ConnectSessionBus()
326 if err != nil {
327 t.Errorf("Unexpected error: %s", err)
328 }
329 defer conn.Close()
330 obj := conn.Object(tester.Name(), "/com/github/godbus/tester")
331 var out string
332 err = obj.Call("com.github.godbus.dbus.Tester.Test", 0).Store(&out)
333 if err == nil {
334 t.Errorf("didn't get expected error")
335 }
336 tester.Close()
337 }
338
339 func TestHandlerInvalidArgType(t *testing.T) {
340 tester, err := newTester()
341 if err != nil {
342 t.Errorf("Unexpected error: %s", err)
343 }
344 conn, err := ConnectSessionBus()
345 if err != nil {
346 t.Errorf("Unexpected error: %s", err)
347 }
348 defer conn.Close()
349 obj := conn.Object(tester.Name(), "/com/github/godbus/tester")
350 var out string
351 err = obj.Call("com.github.godbus.dbus.Tester.Test", 0, 2.10).Store(&out)
352 if err == nil {
353 t.Errorf("didn't get expected error")
354 }
355 tester.Close()
356 }
357
358 func TestHandlerIntrospect(t *testing.T) {
359 tester, err := newTester()
360 if err != nil {
361 t.Errorf("Unexpected error: %s", err)
362 }
363 conn, err := ConnectSessionBus()
364 if err != nil {
365 t.Errorf("Unexpected error: %s", err)
366 }
367 defer conn.Close()
368 obj := conn.Object(tester.Name(), "/com/github/godbus/tester")
369 var out string
370 err = obj.Call("org.freedesktop.DBus.Introspectable.Introspect", 0).Store(&out)
371 if err != nil {
372 t.Errorf("Unexpected error: %s", err)
373 }
374 expected := `<node>
375 <interface name="org.freedesktop.DBus.Introspectable.Introspect">
376 <method name="Introspect">
377 <arg name="out" type="i" direction="out">
378 </method>
379 </interface>
380 <interface name="com.github.godbus.dbus.Tester">
381 <method name="Test">
382 <arg name="in" type="i" direction="in">
383 <arg name="out" type="i" direction="out">
384 </method>
385 <signal name="sig1">
386 <arg name="out" type="i" direction="out">
387 </signal>
388 </interface>
389 </node>`
390 if out != expected {
391 t.Errorf("didn't get expected return value, expected %s got %s", expected, out)
392 }
393 tester.Close()
394 }
395
396 func TestHandlerIntrospectPath(t *testing.T) {
397 tester, err := newTester()
398 if err != nil {
399 t.Errorf("Unexpected error: %s", err)
400 }
401 conn, err := ConnectSessionBus()
402 if err != nil {
403 t.Errorf("Unexpected error: %s", err)
404 }
405 defer conn.Close()
406 obj := conn.Object(tester.Name(), "/com/github/godbus")
407 var out string
408 err = obj.Call("org.freedesktop.DBus.Introspectable.Introspect", 0).Store(&out)
409 if err != nil {
410 t.Errorf("Unexpected error: %s", err)
411 }
412 expected := `<node><node name="tester"></node></node>`
413 if out != expected {
414 t.Errorf("didn't get expected return value, expected %s got %s", expected, out)
415 }
416 tester.Close()
417 }
418
419 func TestHandlerSignal(t *testing.T) {
420 tester, err := newTester()
421 if err != nil {
422 t.Errorf("Unexpected error: %s", err)
423 }
424 conn, err := ConnectSessionBus()
425 if err != nil {
426 t.Errorf("Unexpected error: %s", err)
427 }
428 defer conn.Close()
429 if err = tester.AddSignal("com.github.godbus.dbus.Tester", "sig1"); err != nil {
430 t.Fatal(err)
431 }
432 if err = conn.Emit(
433 "/com/github/godbus/tester",
434 "com.github.godbus.dbus.Tester.sig1",
435 "foo",
436 ); err != nil {
437 t.Fatal(err)
438 }
439 select {
440 case sig := <-tester.sigs:
441 if sig.Body[0] != "foo" {
442 t.Errorf("Unexpected signal got %s, expected %s", sig.Body[0], "foo")
443 }
444 case <-time.After(time.Second * 10):
445 t.Errorf("Didn't receive a signal after 10 seconds")
446 }
447 tester.Close()
448 }
449
450 type X struct {
451 }
452
453 func (x *X) Method1() *Error {
454 return nil
455 }
456
457 func TestRaceInExport(t *testing.T) {
458 const (
459 dbusPath = "/org/example/godbus/test1"
460 dbusInterface = "org.example.godbus.test1"
461 )
462
463 bus, err := ConnectSessionBus()
464 if err != nil {
465 t.Fatal(err)
466 }
467 defer bus.Close()
468
469 var x X
470
471 var wg sync.WaitGroup
472 wg.Add(2)
473 go func() {
474 err = bus.Export(&x, dbusPath, dbusInterface)
475 if err != nil {
476 t.Fatal(err)
477 }
478 wg.Done()
479 }()
480
481 go func() {
482 obj := bus.Object(bus.Names()[0], dbusPath)
483 obj.Call(dbusInterface+".Method1", 0)
484 wg.Done()
485 }()
486 wg.Wait()
487 }
488
View as plain text