package dbus import ( "fmt" "sync" "sync/atomic" "testing" "time" ) type tester struct { conn *Conn sigs chan *Signal subSigsMu sync.Mutex subSigs map[string]map[string]struct{} serial uint32 } type intro struct { path ObjectPath } func (i *intro) introspectPath(path ObjectPath) string { switch path { case "/": return `` case "/com": return `` case "/com/github": return `` case "/com/github/godbus": return `` } return "" } func (i *intro) LookupInterface(name string) (Interface, bool) { if name == "org.freedesktop.DBus.Introspectable" { return i, true } return nil, false } func (i *intro) LookupMethod(name string) (Method, bool) { if name == "Introspect" { return intro_fn(func() string { return i.introspectPath(i.path) }), true } return nil, false } func newIntro(path ObjectPath) *intro { return &intro{path} } //Handler func (t *tester) LookupObject(path ObjectPath) (ServerObject, bool) { if path == "/com/github/godbus/tester" { return t, true } return newIntro(path), true } //ServerObject func (t *tester) LookupInterface(name string) (Interface, bool) { switch name { case "com.github.godbus.dbus.Tester": return t, true case "org.freedesktop.DBus.Introspectable": return t, true } return nil, false } //Interface func (t *tester) LookupMethod(name string) (Method, bool) { switch name { case "Test": return t, true case "Error": return terrfn(func(in string) error { return fmt.Errorf(in) }), true case "Introspect": return intro_fn(func() string { return ` ` }), true } return nil, false } //Method func (t *tester) Call(args ...interface{}) ([]interface{}, error) { return args, nil } func (t *tester) NumArguments() int { return 1 } func (t *tester) NumReturns() int { return 1 } func (t *tester) ArgumentValue(position int) interface{} { return "" } func (t *tester) ReturnValue(position int) interface{} { return "" } type terrfn func(in string) error func (t terrfn) Call(args ...interface{}) ([]interface{}, error) { return nil, t(*args[0].(*string)) } func (t terrfn) NumArguments() int { return 1 } func (t terrfn) NumReturns() int { return 0 } func (t terrfn) ArgumentValue(position int) interface{} { return "" } func (t terrfn) ReturnValue(position int) interface{} { return "" } //SignalHandler func (t *tester) DeliverSignal(iface, name string, signal *Signal) { t.subSigsMu.Lock() intf, ok := t.subSigs[iface] t.subSigsMu.Unlock() if !ok { return } if _, ok := intf[name]; !ok { return } t.sigs <- signal } func (t *tester) AddSignal(iface, name string) { t.subSigsMu.Lock() if i, ok := t.subSigs[iface]; ok { i[name] = struct{}{} } else { t.subSigs[iface] = make(map[string]struct{}) t.subSigs[iface][name] = struct{}{} } t.subSigsMu.Unlock() t.conn.BusObject().(*Object).AddMatchSignal( iface, name) } func (t *tester) Close() { t.conn.Close() close(t.sigs) } func (t *tester) Name() string { return t.conn.Names()[0] } func (t *tester) GetSerial() uint32 { return atomic.AddUint32(&t.serial, 1) } func (t *tester) RetireSerial(serial uint32) {} type intro_fn func() string func (intro intro_fn) Call(args ...interface{}) ([]interface{}, error) { return []interface{}{intro()}, nil } func (_ intro_fn) NumArguments() int { return 0 } func (_ intro_fn) NumReturns() int { return 1 } func (_ intro_fn) ArgumentValue(position int) interface{} { return nil } func (_ intro_fn) ReturnValue(position int) interface{} { return "" } func newTester() (*tester, error) { tester := &tester{ sigs: make(chan *Signal), subSigs: make(map[string]map[string]struct{}), } conn, err := SessionBusPrivate( WithHandler(tester), WithSignalHandler(tester), WithSerialGenerator(tester), ) if err != nil { return nil, err } err = conn.Auth(nil) if err != nil { conn.Close() return nil, err } err = conn.Hello() if err != nil { conn.Close() return nil, err } tester.conn = conn return tester, nil } func TestHandlerCall(t *testing.T) { tester, err := newTester() if err != nil { t.Errorf("Unexpected error: %s", err) } conn, err := SessionBus() if err != nil { t.Errorf("Unexpected error: %s", err) } obj := conn.Object(tester.Name(), "/com/github/godbus/tester") var out string in := "foo" err = obj.Call("com.github.godbus.dbus.Tester.Test", 0, in).Store(&out) if err != nil { t.Errorf("Unexpected error: %s", err) } if out != in { t.Errorf("Unexpected error: got %s, expected %s", out, in) } tester.Close() } func TestHandlerCallGenericError(t *testing.T) { tester, err := newTester() if err != nil { t.Errorf("Unexpected error: %s", err) } conn, err := SessionBus() if err != nil { t.Errorf("Unexpected error: %s", err) } obj := conn.Object(tester.Name(), "/com/github/godbus/tester") var out string in := "foo" err = obj.Call("com.github.godbus.dbus.Tester.Error", 0, in).Store(&out) if err != nil && err.(Error).Body[0].(string) != "foo" { t.Errorf("Unexpected error: %s", err) } tester.Close() } func TestHandlerCallNonExistent(t *testing.T) { tester, err := newTester() if err != nil { t.Errorf("Unexpected error: %s", err) } conn, err := SessionBus() if err != nil { t.Errorf("Unexpected error: %s", err) } obj := conn.Object(tester.Name(), "/com/github/godbus/tester/nonexist") var out string in := "foo" err = obj.Call("com.github.godbus.dbus.Tester.Test", 0, in).Store(&out) if err != nil { if err.Error() != "Object does not implement the interface" { t.Errorf("Unexpected error: %s", err) } } tester.Close() } func TestHandlerInvalidFunc(t *testing.T) { tester, err := newTester() if err != nil { t.Errorf("Unexpected error: %s", err) } conn, err := SessionBus() if err != nil { t.Errorf("Unexpected error: %s", err) } obj := conn.Object(tester.Name(), "/com/github/godbus/tester") var out string in := "foo" err = obj.Call("com.github.godbus.dbus.Tester.Notexist", 0, in).Store(&out) if err == nil { t.Errorf("didn't get expected error") } tester.Close() } func TestHandlerInvalidNumArg(t *testing.T) { tester, err := newTester() if err != nil { t.Errorf("Unexpected error: %s", err) } conn, err := SessionBus() if err != nil { t.Errorf("Unexpected error: %s", err) } obj := conn.Object(tester.Name(), "/com/github/godbus/tester") var out string err = obj.Call("com.github.godbus.dbus.Tester.Test", 0).Store(&out) if err == nil { t.Errorf("didn't get expected error") } tester.Close() } func TestHandlerInvalidArgType(t *testing.T) { tester, err := newTester() if err != nil { t.Errorf("Unexpected error: %s", err) } conn, err := SessionBus() if err != nil { t.Errorf("Unexpected error: %s", err) } obj := conn.Object(tester.Name(), "/com/github/godbus/tester") var out string err = obj.Call("com.github.godbus.dbus.Tester.Test", 0, 2.10).Store(&out) if err == nil { t.Errorf("didn't get expected error") } tester.Close() } func TestHandlerIntrospect(t *testing.T) { tester, err := newTester() if err != nil { t.Errorf("Unexpected error: %s", err) } conn, err := SessionBus() if err != nil { t.Errorf("Unexpected error: %s", err) } obj := conn.Object(tester.Name(), "/com/github/godbus/tester") var out string err = obj.Call("org.freedesktop.DBus.Introspectable.Introspect", 0).Store(&out) expected := ` ` if out != expected { t.Errorf("didn't get expected return value, expected %s got %s", expected, out) } tester.Close() } func TestHandlerIntrospectPath(t *testing.T) { tester, err := newTester() if err != nil { t.Errorf("Unexpected error: %s", err) } conn, err := SessionBus() if err != nil { t.Errorf("Unexpected error: %s", err) } obj := conn.Object(tester.Name(), "/com/github/godbus") var out string err = obj.Call("org.freedesktop.DBus.Introspectable.Introspect", 0).Store(&out) expected := `` if out != expected { t.Errorf("didn't get expected return value, expected %s got %s", expected, out) } tester.Close() } func TestHandlerSignal(t *testing.T) { tester, err := newTester() if err != nil { t.Errorf("Unexpected error: %s", err) } conn, err := SessionBus() if err != nil { t.Errorf("Unexpected error: %s", err) } tester.AddSignal("com.github.godbus.dbus.Tester", "sig1") conn.Emit("/com/github/godbus/tester", "com.github.godbus.dbus.Tester.sig1", "foo") select { case sig := <-tester.sigs: if sig.Body[0] != "foo" { t.Errorf("Unexpected signal got %s, expected %s", sig.Body[0], "foo") } case <-time.After(time.Second * 10): //overly generous timeout t.Errorf("Didn't receive a signal after 10 seconds") } tester.Close() } type X struct { } func (x *X) Method1() *Error { return nil } func TestRaceInExport(t *testing.T) { const ( dbusPath = "/org/example/godbus/test1" dbusInterface = "org.example.godbus.test1" ) bus, err := SessionBus() if err != nil { t.Fatal(err) } var x X var wg sync.WaitGroup wg.Add(2) go func() { err = bus.Export(&x, dbusPath, dbusInterface) if err != nil { t.Fatal(err) } wg.Done() }() go func() { obj := bus.Object(bus.Names()[0], dbusPath) obj.Call(dbusInterface+".Method1", 0) wg.Done() }() wg.Wait() }