| package dbus |
| |
| import ( |
| "context" |
| "encoding/binary" |
| "fmt" |
| "io" |
| "io/ioutil" |
| "log" |
| "sync" |
| "testing" |
| "time" |
| ) |
| |
| func TestSessionBus(t *testing.T) { |
| oldConn, err := SessionBus() |
| if err != nil { |
| t.Error(err) |
| } |
| if err = oldConn.Close(); err != nil { |
| t.Fatal(err) |
| } |
| if oldConn.Connected() { |
| t.Fatal("Should be closed") |
| } |
| newConn, err := SessionBus() |
| if err != nil { |
| t.Error(err) |
| } |
| if newConn == oldConn { |
| t.Fatal("Should get a new connection") |
| } |
| } |
| |
| func TestSystemBus(t *testing.T) { |
| oldConn, err := SystemBus() |
| if err != nil { |
| t.Error(err) |
| } |
| if err = oldConn.Close(); err != nil { |
| t.Fatal(err) |
| } |
| if oldConn.Connected() { |
| t.Fatal("Should be closed") |
| } |
| newConn, err := SystemBus() |
| if err != nil { |
| t.Error(err) |
| } |
| if newConn == oldConn { |
| t.Fatal("Should get a new connection") |
| } |
| } |
| |
| func TestConnectSessionBus(t *testing.T) { |
| conn, err := ConnectSessionBus() |
| if err != nil { |
| t.Fatal(err) |
| } |
| if err = conn.Close(); err != nil { |
| t.Fatal(err) |
| } |
| if conn.Connected() { |
| t.Fatal("Should be closed") |
| } |
| } |
| |
| func TestConnectSystemBus(t *testing.T) { |
| conn, err := ConnectSystemBus() |
| if err != nil { |
| t.Fatal(err) |
| } |
| if err = conn.Close(); err != nil { |
| t.Fatal(err) |
| } |
| if conn.Connected() { |
| t.Fatal("Should be closed") |
| } |
| } |
| |
| func TestSend(t *testing.T) { |
| bus, err := ConnectSessionBus() |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer bus.Close() |
| |
| ch := make(chan *Call, 1) |
| msg := &Message{ |
| Type: TypeMethodCall, |
| Flags: 0, |
| Headers: map[HeaderField]Variant{ |
| FieldDestination: MakeVariant(bus.Names()[0]), |
| FieldPath: MakeVariant(ObjectPath("/org/freedesktop/DBus")), |
| FieldInterface: MakeVariant("org.freedesktop.DBus.Peer"), |
| FieldMember: MakeVariant("Ping"), |
| }, |
| } |
| call := bus.Send(msg, ch) |
| <-ch |
| if call.Err != nil { |
| t.Error(call.Err) |
| } |
| } |
| |
| func TestFlagNoReplyExpectedSend(t *testing.T) { |
| bus, err := ConnectSessionBus() |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer bus.Close() |
| |
| done := make(chan struct{}) |
| go func() { |
| bus.BusObject().Call("org.freedesktop.DBus.ListNames", FlagNoReplyExpected) |
| close(done) |
| }() |
| select { |
| case <-done: |
| case <-time.After(1 * time.Second): |
| t.Error("Failed to announce that the call was done") |
| } |
| } |
| |
| func TestRemoveSignal(t *testing.T) { |
| bus, err := NewConn(nil) |
| if err != nil { |
| t.Error(err) |
| } |
| signals := bus.signalHandler.(*defaultSignalHandler).signals |
| ch := make(chan *Signal) |
| ch2 := make(chan *Signal) |
| for _, ch := range []chan *Signal{ch, ch2, ch, ch2, ch2, ch} { |
| bus.Signal(ch) |
| } |
| signals = bus.signalHandler.(*defaultSignalHandler).signals |
| if len(signals) != 6 { |
| t.Errorf("remove signal: signals length not equal: got '%d', want '6'", len(signals)) |
| } |
| bus.RemoveSignal(ch) |
| signals = bus.signalHandler.(*defaultSignalHandler).signals |
| if len(signals) != 3 { |
| t.Errorf("remove signal: signals length not equal: got '%d', want '3'", len(signals)) |
| } |
| signals = bus.signalHandler.(*defaultSignalHandler).signals |
| for _, scd := range signals { |
| if scd.ch != ch2 { |
| t.Errorf("remove signal: removed signal present: got '%v', want '%v'", scd.ch, ch2) |
| } |
| } |
| } |
| |
| type rwc struct { |
| io.Reader |
| io.Writer |
| } |
| |
| func (rwc) Close() error { return nil } |
| |
| type fakeAuth struct { |
| } |
| |
| func (fakeAuth) FirstData() (name, resp []byte, status AuthStatus) { |
| return []byte("name"), []byte("resp"), AuthOk |
| } |
| |
| func (fakeAuth) HandleData(data []byte) (resp []byte, status AuthStatus) { |
| return nil, AuthOk |
| } |
| |
| func TestCloseBeforeSignal(t *testing.T) { |
| reader, pipewriter := io.Pipe() |
| defer pipewriter.Close() |
| defer reader.Close() |
| |
| bus, err := NewConn(rwc{Reader: reader, Writer: ioutil.Discard}) |
| if err != nil { |
| t.Fatal(err) |
| } |
| // give ch a buffer so sends won't block |
| ch := make(chan *Signal, 1) |
| bus.Signal(ch) |
| |
| go func() { |
| _, err := pipewriter.Write([]byte("REJECTED name\r\nOK myuuid\r\n")) |
| if err != nil { |
| t.Errorf("error writing to pipe: %v", err) |
| } |
| }() |
| |
| err = bus.Auth([]Auth{fakeAuth{}}) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| err = bus.Close() |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| msg := &Message{ |
| Type: TypeSignal, |
| Headers: map[HeaderField]Variant{ |
| FieldInterface: MakeVariant("foo.bar"), |
| FieldMember: MakeVariant("bar"), |
| FieldPath: MakeVariant(ObjectPath("/baz")), |
| }, |
| } |
| err = msg.EncodeTo(pipewriter, binary.LittleEndian) |
| if err != nil { |
| t.Fatal(err) |
| } |
| } |
| |
| func TestCloseChannelAfterRemoveSignal(t *testing.T) { |
| bus, err := NewConn(nil) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // Add an unbuffered signal channel |
| ch := make(chan *Signal) |
| bus.Signal(ch) |
| |
| // Send a signal |
| msg := &Message{ |
| Type: TypeSignal, |
| Headers: map[HeaderField]Variant{ |
| FieldInterface: MakeVariant("foo.bar"), |
| FieldMember: MakeVariant("bar"), |
| FieldPath: MakeVariant(ObjectPath("/baz")), |
| }, |
| } |
| bus.handleSignal(Sequence(1), msg) |
| |
| // Remove and close the signal channel |
| bus.RemoveSignal(ch) |
| close(ch) |
| } |
| |
| func TestAddAndRemoveMatchSignalContext(t *testing.T) { |
| conn, err := ConnectSessionBus() |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer conn.Close() |
| |
| sigc := make(chan *Signal, 1) |
| conn.Signal(sigc) |
| |
| ctx, cancel := context.WithCancel(context.Background()) |
| cancel() |
| // try to subscribe to a made up signal with an already canceled context |
| if err = conn.AddMatchSignalContext( |
| ctx, |
| WithMatchInterface("org.test"), |
| WithMatchMember("CtxTest"), |
| ); err == nil { |
| t.Fatal("call on canceled context did not fail") |
| } |
| |
| // subscribe to the signal with background context |
| if err = conn.AddMatchSignalContext( |
| context.Background(), |
| WithMatchInterface("org.test"), |
| WithMatchMember("CtxTest"), |
| ); err != nil { |
| t.Fatal(err) |
| } |
| |
| // try to unsubscribe with an already canceled context |
| if err = conn.RemoveMatchSignalContext( |
| ctx, |
| WithMatchInterface("org.test"), |
| WithMatchMember("CtxTest"), |
| ); err == nil { |
| t.Fatal("call on canceled context did not fail") |
| } |
| |
| // check that signal is still delivered |
| if err = conn.Emit("/", "org.test.CtxTest"); err != nil { |
| t.Fatal(err) |
| } |
| if sig := waitSignal(sigc, "org.test.CtxTest", time.Second); sig == nil { |
| t.Fatal("signal receive timed out") |
| } |
| |
| // unsubscribe from the signal |
| if err = conn.RemoveMatchSignalContext( |
| context.Background(), |
| WithMatchInterface("org.test"), |
| WithMatchMember("CtxTest"), |
| ); err != nil { |
| t.Fatal(err) |
| } |
| if err = conn.Emit("/", "org.test.CtxTest"); err != nil { |
| t.Fatal(err) |
| } |
| if sig := waitSignal(sigc, "org.test.CtxTest", time.Second); sig != nil { |
| t.Fatalf("unsubscribed from %q signal, but received %#v", "org.test.CtxTest", sig) |
| } |
| } |
| |
| func TestAddAndRemoveMatchSignal(t *testing.T) { |
| conn, err := ConnectSessionBus() |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer conn.Close() |
| |
| sigc := make(chan *Signal, 1) |
| conn.Signal(sigc) |
| |
| // subscribe to a made up signal name and emit one of the type |
| if err = conn.AddMatchSignal( |
| WithMatchInterface("org.test"), |
| WithMatchMember("Test"), |
| ); err != nil { |
| t.Fatal(err) |
| } |
| if err = conn.Emit("/", "org.test.Test"); err != nil { |
| t.Fatal(err) |
| } |
| if sig := waitSignal(sigc, "org.test.Test", time.Second); sig == nil { |
| t.Fatal("signal receive timed out") |
| } |
| |
| // unsubscribe from the signal and check that is not delivered anymore |
| if err = conn.RemoveMatchSignal( |
| WithMatchInterface("org.test"), |
| WithMatchMember("Test"), |
| ); err != nil { |
| t.Fatal(err) |
| } |
| if err = conn.Emit("/", "org.test.Test"); err != nil { |
| t.Fatal(err) |
| } |
| if sig := waitSignal(sigc, "org.test.Test", time.Second); sig != nil { |
| t.Fatalf("unsubscribed from %q signal, but received %#v", "org.test.Test", sig) |
| } |
| } |
| |
| func waitSignal(sigc <-chan *Signal, name string, timeout time.Duration) *Signal { |
| for { |
| select { |
| case sig := <-sigc: |
| if sig.Name == name { |
| return sig |
| } |
| case <-time.After(timeout): |
| return nil |
| } |
| } |
| } |
| |
| const ( |
| SCPPInterface = "org.godbus.DBus.StatefulTest" |
| SCPPPath = "/org/godbus/DBus/StatefulTest" |
| SCPPChangedSignalName = "Changed" |
| SCPPStateMethodName = "State" |
| ) |
| |
| func TestStateCachingProxyPattern(t *testing.T) { |
| srv, err := ConnectSessionBus() |
| defer srv.Close() |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| conn, err := ConnectSessionBus(WithSignalHandler(NewSequentialSignalHandler())) |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer conn.Close() |
| |
| serviceName := srv.Names()[0] |
| // message channel should have at least some buffering, to make sure Eavesdrop does not |
| // drop the message if nobody is currently trying to read from the channel. |
| messages := make(chan *Message, 1) |
| srv.Eavesdrop(messages) |
| |
| ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) |
| defer cancel() |
| |
| var wg sync.WaitGroup |
| wg.Add(2) |
| go func() { |
| defer wg.Done() |
| if err := serverProcess(ctx, srv, messages, t); err != nil { |
| t.Errorf("error in server process: %v", err) |
| cancel() |
| } |
| }() |
| go func() { |
| defer wg.Done() |
| if err := clientProcess(ctx, conn, serviceName, t); err != nil { |
| t.Errorf("error in client process: %v", err) |
| } |
| // Cancel the server process. |
| cancel() |
| }() |
| wg.Wait() |
| } |
| |
| func clientProcess(ctx context.Context, conn *Conn, serviceName string, t *testing.T) error { |
| // Subscribe to state changes on the remote object |
| if err := conn.AddMatchSignal( |
| WithMatchInterface(SCPPInterface), |
| WithMatchMember(SCPPChangedSignalName), |
| ); err != nil { |
| return err |
| } |
| channel := make(chan *Signal) |
| conn.Signal(channel) |
| t.Log("Subscribed to signals") |
| |
| // Simulate unfavourable OS scheduling leading to a delay between subscription |
| // and querying for the current state. |
| time.Sleep(30 * time.Millisecond) |
| |
| // Call .State() on the remote object to get its current state and store in observedStates[0]. |
| obj := conn.Object(serviceName, SCPPPath) |
| observedStates := make([]uint64, 1) |
| call := obj.CallWithContext(ctx, SCPPInterface+"."+SCPPStateMethodName, 0) |
| if err := call.Store(&observedStates[0]); err != nil { |
| return err |
| } |
| t.Logf("Queried current state, got %v", observedStates[0]) |
| |
| // Populate observedStates[1...49] based on the state change signals, |
| // ignoring signals with a sequence number less than call.ResponseSequence so that we ignore past signals. |
| signalsProcessed := 0 |
| readSignals: |
| for { |
| select { |
| case signal := <-channel: |
| signalsProcessed++ |
| if signal.Name == SCPPInterface+"."+SCPPChangedSignalName && signal.Sequence > call.ResponseSequence { |
| observedState := signal.Body[0].(uint64) |
| observedStates = append(observedStates, observedState) |
| // Observing at least 50 states gives us low probability that we received a contiguous subsequence of states 'by accident' |
| if len(observedStates) >= 50 { |
| break readSignals |
| } |
| } |
| case <-ctx.Done(): |
| t.Logf("Context cancelled, client processed %v signals", signalsProcessed) |
| return ctx.Err() |
| } |
| } |
| t.Logf("client processed %v signals", signalsProcessed) |
| |
| // Expect that we begun observing at least a few states in. This ensures the server was already emitting signals |
| // and makes it likely we simulated our race condition. |
| if observedStates[0] < 10 { |
| return fmt.Errorf("expected first state to be at least 10, got %v", observedStates[0]) |
| } |
| |
| t.Logf("Observed states: %v", observedStates) |
| |
| // The observable states of the remote object were [1 ... (infinity)] during this test. |
| // This loop is intended to assert that our observed states are a contiguous subgrange [n ... n+49] for some n, i.e. |
| // that we received a contiguous subsequence of the states of the remote object. For each run of the test, n |
| // may be slightly different due to scheduling effects. |
| for i := 0; i < len(observedStates); i++ { |
| expectedState := observedStates[0] + uint64(i) |
| if observedStates[i] != expectedState { |
| return fmt.Errorf("expected observed state %v to be %v, got %v", i, expectedState, observedStates[i]) |
| } |
| } |
| return nil |
| } |
| |
| func serverProcess(ctx context.Context, srv *Conn, messages <-chan *Message, t *testing.T) error { |
| state := uint64(0) |
| |
| process: |
| for { |
| select { |
| case msg, ok := <-messages: |
| if !ok { |
| t.Log("Message channel closed") |
| // Message channel closed. |
| break process |
| } |
| if msg.IsValid() != nil { |
| t.Log("Got invalid message, discarding") |
| continue process |
| } |
| name := msg.Headers[FieldMember].value.(string) |
| ifname := msg.Headers[FieldInterface].value.(string) |
| if ifname == SCPPInterface && name == SCPPStateMethodName { |
| t.Logf("Processing reply to .State(), returning state = %v", state) |
| reply := new(Message) |
| reply.Type = TypeMethodReply |
| reply.Headers = make(map[HeaderField]Variant) |
| reply.Headers[FieldDestination] = msg.Headers[FieldSender] |
| reply.Headers[FieldReplySerial] = MakeVariant(msg.serial) |
| reply.Body = make([]interface{}, 1) |
| reply.Body[0] = state |
| reply.Headers[FieldSignature] = MakeVariant(SignatureOf(reply.Body...)) |
| srv.sendMessageAndIfClosed(reply, nil) |
| } |
| case <-ctx.Done(): |
| t.Logf("Context cancelled, server emitted %v signals", state) |
| return nil |
| default: |
| state++ |
| if err := srv.Emit(SCPPPath, SCPPInterface+"."+SCPPChangedSignalName, state); err != nil { |
| return err |
| } |
| } |
| } |
| return nil |
| } |
| |
| type server struct{} |
| |
| func (server) Double(i int64) (int64, *Error) { |
| return 2 * i, nil |
| } |
| |
| func BenchmarkCall(b *testing.B) { |
| b.StopTimer() |
| b.ReportAllocs() |
| var s string |
| bus, err := ConnectSessionBus() |
| if err != nil { |
| b.Fatal(err) |
| } |
| defer bus.Close() |
| |
| name := bus.Names()[0] |
| obj := bus.BusObject() |
| b.StartTimer() |
| for i := 0; i < b.N; i++ { |
| err := obj.Call("org.freedesktop.DBus.GetNameOwner", 0, name).Store(&s) |
| if err != nil { |
| b.Fatal(err) |
| } |
| if s != name { |
| b.Errorf("got %s, wanted %s", s, name) |
| } |
| } |
| } |
| |
| func BenchmarkCallAsync(b *testing.B) { |
| b.StopTimer() |
| b.ReportAllocs() |
| bus, err := ConnectSessionBus() |
| if err != nil { |
| b.Fatal(err) |
| } |
| defer bus.Close() |
| |
| name := bus.Names()[0] |
| obj := bus.BusObject() |
| c := make(chan *Call, 50) |
| done := make(chan struct{}) |
| go func() { |
| for i := 0; i < b.N; i++ { |
| v := <-c |
| if v.Err != nil { |
| b.Error(v.Err) |
| } |
| s := v.Body[0].(string) |
| if s != name { |
| b.Errorf("got %s, wanted %s", s, name) |
| } |
| } |
| close(done) |
| }() |
| b.StartTimer() |
| for i := 0; i < b.N; i++ { |
| obj.Go("org.freedesktop.DBus.GetNameOwner", 0, c, name) |
| } |
| <-done |
| } |
| |
| func BenchmarkServe(b *testing.B) { |
| b.StopTimer() |
| srv, err := ConnectSessionBus() |
| if err != nil { |
| b.Fatal(err) |
| } |
| defer srv.Close() |
| |
| cli, err := ConnectSessionBus() |
| if err != nil { |
| b.Fatal(err) |
| } |
| defer cli.Close() |
| |
| benchmarkServe(b, srv, cli) |
| } |
| |
| func BenchmarkServeAsync(b *testing.B) { |
| b.StopTimer() |
| srv, err := ConnectSessionBus() |
| if err != nil { |
| b.Fatal(err) |
| } |
| defer srv.Close() |
| |
| cli, err := ConnectSessionBus() |
| if err != nil { |
| b.Fatal(err) |
| } |
| defer cli.Close() |
| |
| benchmarkServeAsync(b, srv, cli) |
| } |
| |
| func BenchmarkServeSameConn(b *testing.B) { |
| b.StopTimer() |
| bus, err := ConnectSessionBus() |
| if err != nil { |
| b.Fatal(err) |
| } |
| defer bus.Close() |
| |
| benchmarkServe(b, bus, bus) |
| } |
| |
| func BenchmarkServeSameConnAsync(b *testing.B) { |
| b.StopTimer() |
| bus, err := ConnectSessionBus() |
| if err != nil { |
| b.Fatal(err) |
| } |
| defer bus.Close() |
| |
| benchmarkServeAsync(b, bus, bus) |
| } |
| |
| func benchmarkServe(b *testing.B, srv, cli *Conn) { |
| var r int64 |
| var err error |
| dest := srv.Names()[0] |
| srv.Export(server{}, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test") |
| obj := cli.Object(dest, "/org/guelfey/DBus/Test") |
| b.StartTimer() |
| for i := 0; i < b.N; i++ { |
| err = obj.Call("org.guelfey.DBus.Test.Double", 0, int64(i)).Store(&r) |
| if err != nil { |
| b.Fatal(err) |
| } |
| if r != 2*int64(i) { |
| b.Errorf("got %d, wanted %d", r, 2*int64(i)) |
| } |
| } |
| } |
| |
| func benchmarkServeAsync(b *testing.B, srv, cli *Conn) { |
| dest := srv.Names()[0] |
| srv.Export(server{}, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test") |
| obj := cli.Object(dest, "/org/guelfey/DBus/Test") |
| c := make(chan *Call, 50) |
| done := make(chan struct{}) |
| go func() { |
| for i := 0; i < b.N; i++ { |
| v := <-c |
| if v.Err != nil { |
| b.Fatal(v.Err) |
| } |
| i, r := v.Args[0].(int64), v.Body[0].(int64) |
| if 2*i != r { |
| b.Errorf("got %d, wanted %d", r, 2*i) |
| } |
| } |
| close(done) |
| }() |
| b.StartTimer() |
| for i := 0; i < b.N; i++ { |
| obj.Go("org.guelfey.DBus.Test.Double", 0, c, int64(i)) |
| } |
| <-done |
| } |
| |
| func TestGetKey(t *testing.T) { |
| keys := "host=1.2.3.4,port=5678,family=ipv4" |
| if host := getKey(keys, "host"); host != "1.2.3.4" { |
| t.Error(`Expected "1.2.3.4", got`, host) |
| } |
| if port := getKey(keys, "port"); port != "5678" { |
| t.Error(`Expected "5678", got`, port) |
| } |
| if family := getKey(keys, "family"); family != "ipv4" { |
| t.Error(`Expected "ipv4", got`, family) |
| } |
| } |
| |
| func TestInterceptors(t *testing.T) { |
| conn, err := ConnectSessionBus( |
| WithIncomingInterceptor(func(msg *Message) { |
| log.Println("<", msg) |
| }), |
| WithOutgoingInterceptor(func(msg *Message) { |
| log.Println(">", msg) |
| }), |
| ) |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer conn.Close() |
| } |
| |
| func TestCloseCancelsConnectionContext(t *testing.T) { |
| bus, err := ConnectSessionBus() |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer bus.Close() |
| |
| // The context is not done at this point |
| ctx := bus.Context() |
| select { |
| case <-ctx.Done(): |
| t.Fatal("context should not be done") |
| default: |
| } |
| |
| err = bus.Close() |
| if err != nil { |
| t.Fatal(err) |
| } |
| select { |
| case <-ctx.Done(): |
| // expected |
| case <-time.After(5 * time.Second): |
| t.Fatal("context is not done after connection closed") |
| } |
| } |
| |
| func TestDisconnectCancelsConnectionContext(t *testing.T) { |
| reader, pipewriter := io.Pipe() |
| defer pipewriter.Close() |
| defer reader.Close() |
| |
| bus, err := NewConn(rwc{Reader: reader, Writer: ioutil.Discard}) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| go func() { |
| _, err := pipewriter.Write([]byte("REJECTED name\r\nOK myuuid\r\n")) |
| if err != nil { |
| t.Errorf("error writing to pipe: %v", err) |
| } |
| }() |
| err = bus.Auth([]Auth{fakeAuth{}}) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| ctx := bus.Context() |
| |
| pipewriter.Close() |
| select { |
| case <-ctx.Done(): |
| // expected |
| case <-time.After(5 * time.Second): |
| t.Fatal("context is not done after connection closed") |
| } |
| } |
| |
| func TestCancellingContextClosesConnection(t *testing.T) { |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
| |
| reader, pipewriter := io.Pipe() |
| defer pipewriter.Close() |
| defer reader.Close() |
| |
| bus, err := NewConn(rwc{Reader: reader, Writer: ioutil.Discard}, WithContext(ctx)) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| go func() { |
| _, err := pipewriter.Write([]byte("REJECTED name\r\nOK myuuid\r\n")) |
| if err != nil { |
| t.Errorf("error writing to pipe: %v", err) |
| } |
| }() |
| err = bus.Auth([]Auth{fakeAuth{}}) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // Cancel the connection's parent context and give time for |
| // other goroutines to schedule. |
| cancel() |
| time.Sleep(50 * time.Millisecond) |
| |
| err = bus.BusObject().Call("org.freedesktop.DBus.Peer.Ping", 0).Store() |
| if err != ErrClosed { |
| t.Errorf("expected connection to be closed, but got: %v", err) |
| } |
| } |