| 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 `<node><node name="com"></node></node>` |
| case "/com": |
| return `<node><node name="github"></node></node>` |
| case "/com/github": |
| return `<node><node name="godbus"></node></node>` |
| case "/com/github/godbus": |
| return `<node><node name="tester"></node></node>` |
| } |
| 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 `<node> |
| <interface name="org.freedesktop.DBus.Introspectable.Introspect"> |
| <method name="Introspect"> |
| <arg name="out" type="i" direction="out"> |
| </method> |
| </interface> |
| <interface name="com.github.godbus.dbus.Tester"> |
| <method name="Test"> |
| <arg name="in" type="i" direction="in"> |
| <arg name="out" type="i" direction="out"> |
| </method> |
| <signal name="sig1"> |
| <arg name="out" type="i" direction="out"> |
| </signal> |
| </interface> |
| </node>` |
| }), 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 := `<node> |
| <interface name="org.freedesktop.DBus.Introspectable.Introspect"> |
| <method name="Introspect"> |
| <arg name="out" type="i" direction="out"> |
| </method> |
| </interface> |
| <interface name="com.github.godbus.dbus.Tester"> |
| <method name="Test"> |
| <arg name="in" type="i" direction="in"> |
| <arg name="out" type="i" direction="out"> |
| </method> |
| <signal name="sig1"> |
| <arg name="out" type="i" direction="out"> |
| </signal> |
| </interface> |
| </node>` |
| 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 := `<node><node name="tester"></node></node>` |
| 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() |
| } |