Skip to content

Commit 5a0b55b

Browse files
committed
login1: Use AddMatchSignalContext instead of BusObject.Call directly
Closes #388. Signed-off-by: Mateusz Gozdek <[email protected]>
1 parent 6ebe89f commit 5a0b55b

File tree

2 files changed

+173
-14
lines changed

2 files changed

+173
-14
lines changed

login1/dbus.go

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,7 @@ type Connection interface {
4646
Object(string, dbus.ObjectPath) dbus.BusObject
4747
Signal(ch chan<- *dbus.Signal)
4848
Connected() bool
49-
// TODO: This should be replaced with AddMatchSignal.
50-
// See https://github.com/coreos/go-systemd/issues/388 for details.
51-
BusObject() dbus.BusObject
49+
AddMatchSignalContext(ctx context.Context, options ...dbus.MatchOption) error
5250
}
5351

5452
// connectionManager explicitly wraps dependencies on established D-Bus connection.
@@ -394,14 +392,23 @@ func (c *Conn) Inhibit(what, who, why, mode string) (*os.File, error) {
394392
return os.NewFile(uintptr(fd), "inhibit"), nil
395393
}
396394

397-
// Subscribe to signals on the logind dbus
398-
func (c *Conn) Subscribe(members ...string) chan *dbus.Signal {
395+
// SubscribeWithContext to signals on the logind dbus.
396+
func (c *Conn) SubscribeWithContext(ctx context.Context, members ...string) (chan *dbus.Signal, error) {
399397
for _, member := range members {
400-
c.conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
401-
fmt.Sprintf("type='signal',interface='org.freedesktop.login1.Manager',member='%s'", member))
398+
if err := c.conn.AddMatchSignalContext(ctx, dbus.WithMatchInterface(dbusManagerInterface), dbus.WithMatchMember(member)); err != nil {
399+
return nil, fmt.Errorf("adding match for signal %s: %w", member, err)
400+
}
402401
}
403402
ch := make(chan *dbus.Signal, 10)
404403
c.conn.Signal(ch)
404+
return ch, nil
405+
}
406+
407+
// Subscribe to signals on the logind dbus.
408+
//
409+
// Deprecated: use SubscribeWithContext instead.
410+
func (c *Conn) Subscribe(members ...string) chan *dbus.Signal {
411+
ch, _ := c.SubscribeWithContext(context.Background(), members...)
405412
return ch
406413
}
407414

login1/dbus_test.go

Lines changed: 159 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -245,9 +245,162 @@ func Test_Creating_new_connection_with_custom_connection(t *testing.T) {
245245
})
246246
}
247247

248+
func Test_Subscribing_to_signals(t *testing.T) {
249+
t.Parallel()
250+
251+
ctx := context.Background()
252+
253+
t.Run("subscribes_to", func(t *testing.T) {
254+
t.Parallel()
255+
256+
t.Run("login1_interface", func(t *testing.T) {
257+
t.Parallel()
258+
259+
addMatchCalled := false
260+
261+
connectionWithInterfaceCheck := &mockConnection{
262+
AddMatchSignalContextF: func(ctx context.Context, options ...dbus.MatchOption) error {
263+
addMatchCalled = true
264+
if len(options) < 2 {
265+
t.Fatalf("Expected at least 2 match options (interface and member)")
266+
}
267+
return nil
268+
},
269+
}
270+
271+
conn, err := login1.NewWithConnection(connectionWithInterfaceCheck)
272+
if err != nil {
273+
t.Fatalf("Unexpected error creating connection: %v", err)
274+
}
275+
276+
if _, err := conn.SubscribeWithContext(ctx, "SessionNew"); err != nil {
277+
t.Fatalf("Unexpected error subscribing to signals: %v", err)
278+
}
279+
280+
if !addMatchCalled {
281+
t.Fatalf("Expected AddMatchSignalContext to be called")
282+
}
283+
})
284+
285+
t.Run("for_all_given_members", func(t *testing.T) {
286+
t.Parallel()
287+
288+
callCount := 0
289+
290+
connectionWithMemberCheck := &mockConnection{
291+
AddMatchSignalContextF: func(ctx context.Context, options ...dbus.MatchOption) error {
292+
callCount++
293+
return nil
294+
},
295+
}
296+
297+
conn, err := login1.NewWithConnection(connectionWithMemberCheck)
298+
if err != nil {
299+
t.Fatalf("Unexpected error creating connection: %v", err)
300+
}
301+
302+
expectedMembers := []string{"SessionNew", "SessionRemoved", "UserNew"}
303+
if _, err := conn.SubscribeWithContext(ctx, expectedMembers...); err != nil {
304+
t.Fatalf("Unexpected error subscribing to signals: %v", err)
305+
}
306+
307+
if callCount != len(expectedMembers) {
308+
t.Fatalf("Expected AddMatchSignalContext to be called %d times, got %d", len(expectedMembers), callCount)
309+
}
310+
})
311+
})
312+
313+
t.Run("passes_received_signals_to_channel", func(t *testing.T) {
314+
t.Parallel()
315+
316+
signalChannelProvided := false
317+
318+
connectionWithSignalCheck := &mockConnection{
319+
SignalF: func(ch chan<- *dbus.Signal) {
320+
signalChannelProvided = ch != nil
321+
// Send a test signal to verify the channel works
322+
go func() {
323+
ch <- &dbus.Signal{
324+
Sender: "org.freedesktop.login1",
325+
Path: "/org/freedesktop/login1",
326+
Name: "org.freedesktop.login1.Manager.SessionNew",
327+
Body: []any{"session1", dbus.ObjectPath("/org/freedesktop/login1/session/session1")},
328+
}
329+
}()
330+
},
331+
}
332+
333+
conn, err := login1.NewWithConnection(connectionWithSignalCheck)
334+
if err != nil {
335+
t.Fatalf("Unexpected error creating connection: %v", err)
336+
}
337+
338+
ch, err := conn.SubscribeWithContext(ctx, "SessionNew")
339+
if err != nil {
340+
t.Fatalf("Unexpected error subscribing to signals: %v", err)
341+
}
342+
343+
if ch == nil {
344+
t.Fatalf("Expected signal channel to be returned")
345+
}
346+
347+
if !signalChannelProvided {
348+
t.Fatalf("Expected signal channel to be passed to connection")
349+
}
350+
351+
// Verify we can receive signals through the channel
352+
ctx, cancel := context.WithTimeout(ctx, time.Second*3)
353+
defer cancel()
354+
355+
select {
356+
case sig := <-ch:
357+
if sig == nil {
358+
t.Fatalf("Received nil signal")
359+
}
360+
if sig.Name != "org.freedesktop.login1.Manager.SessionNew" {
361+
t.Fatalf("Expected signal name %q, got %q", "org.freedesktop.login1.Manager.SessionNew", sig.Name)
362+
}
363+
case <-ctx.Done():
364+
t.Fatalf("Timeout waiting for signal")
365+
}
366+
})
367+
368+
t.Run("returns_error_when_adding_match_signal_fails", func(t *testing.T) {
369+
t.Parallel()
370+
371+
expectedError := fmt.Errorf("failed to add match")
372+
373+
connectionWithError := &mockConnection{
374+
AddMatchSignalContextF: func(ctx context.Context, options ...dbus.MatchOption) error {
375+
return expectedError
376+
},
377+
}
378+
379+
conn, err := login1.NewWithConnection(connectionWithError)
380+
if err != nil {
381+
t.Fatalf("Unexpected error creating connection: %v", err)
382+
}
383+
384+
_, err = conn.SubscribeWithContext(ctx, "SessionNew")
385+
if err == nil {
386+
t.Fatalf("Expected error when adding match signal fails")
387+
}
388+
})
389+
}
390+
248391
// mockConnection is a test helper for mocking dbus.Conn.
249392
type mockConnection struct {
250-
ObjectF func(string, dbus.ObjectPath) dbus.BusObject
393+
ObjectF func(string, dbus.ObjectPath) dbus.BusObject
394+
AddMatchSignalContextF func(context.Context, ...dbus.MatchOption) error
395+
SignalF func(chan<- *dbus.Signal)
396+
}
397+
398+
// AddMatchSignalContext ...
399+
func (m *mockConnection) AddMatchSignalContext(ctx context.Context, options ...dbus.MatchOption) error {
400+
if m.AddMatchSignalContextF != nil {
401+
return m.AddMatchSignalContextF(ctx, options...)
402+
}
403+
return nil
251404
}
252405

253406
// Auth ...
@@ -261,7 +414,11 @@ func (m *mockConnection) Hello() error {
261414
}
262415

263416
// Signal ...
264-
func (m *mockConnection) Signal(ch chan<- *dbus.Signal) {}
417+
func (m *mockConnection) Signal(ch chan<- *dbus.Signal) {
418+
if m.SignalF != nil {
419+
m.SignalF(ch)
420+
}
421+
}
265422

266423
// Object ...
267424
func (m *mockConnection) Object(dest string, path dbus.ObjectPath) dbus.BusObject {
@@ -277,11 +434,6 @@ func (m *mockConnection) Close() error {
277434
return nil
278435
}
279436

280-
// BusObject ...
281-
func (m *mockConnection) BusObject() dbus.BusObject {
282-
return nil
283-
}
284-
285437
// Connected ...
286438
func (m *mockConnection) Connected() bool {
287439
return true

0 commit comments

Comments
 (0)