1package dbus
2
3import (
4	"context"
5	"testing"
6	"time"
7)
8
9type objectGoContextServer struct {
10	t     *testing.T
11	sleep time.Duration
12}
13
14func (o objectGoContextServer) Sleep() *Error {
15	o.t.Log("Got object call and sleeping for ", o.sleep)
16	time.Sleep(o.sleep)
17	o.t.Log("Completed sleeping for ", o.sleep)
18	return nil
19}
20
21func TestObjectGoWithContextTimeout(t *testing.T) {
22	bus, err := SessionBus()
23	if err != nil {
24		t.Fatalf("Unexpected error connecting to session bus: %s", err)
25	}
26
27	name := bus.Names()[0]
28	bus.Export(objectGoContextServer{t, time.Second}, "/org/dannin/DBus/Test", "org.dannin.DBus.Test")
29	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
30	defer cancel()
31	select {
32	case call := <-bus.Object(name, "/org/dannin/DBus/Test").GoWithContext(ctx, "org.dannin.DBus.Test.Sleep", 0, nil).Done:
33		if call.Err != ctx.Err() {
34			t.Fatal("Expected ", ctx.Err(), " but got ", call.Err)
35		}
36	case <-time.After(2 * time.Second):
37		t.Fatal("Expected call to not respond in time")
38	}
39}
40
41func TestObjectGoWithContext(t *testing.T) {
42	bus, err := SessionBus()
43	if err != nil {
44		t.Fatalf("Unexpected error connecting to session bus: %s", err)
45	}
46
47	name := bus.Names()[0]
48	bus.Export(objectGoContextServer{t, time.Millisecond}, "/org/dannin/DBus/Test", "org.dannin.DBus.Test")
49	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
50	defer cancel()
51	select {
52	case call := <-bus.Object(name, "/org/dannin/DBus/Test").GoWithContext(ctx, "org.dannin.DBus.Test.Sleep", 0, nil).Done:
53		if call.Err != ctx.Err() {
54			t.Fatal("Expected ", ctx.Err(), " but got ", call.Err)
55		}
56	case <-time.After(time.Second):
57		t.Fatal("Expected call to respond in 1 Millisecond")
58	}
59}
60
61type nopServer struct{}
62
63func (_ nopServer) Nop() *Error {
64	return nil
65}
66
67func fetchSignal(t *testing.T, ch chan *Signal, timeout time.Duration) *Signal {
68	select {
69	case sig := <-ch:
70		return sig
71	case <-time.After(timeout):
72		t.Fatalf("Failed to fetch signal in specified timeout %s", timeout)
73	}
74	return nil
75}
76
77func TestObjectSignalHandling(t *testing.T) {
78	bus, err := SessionBus()
79	if err != nil {
80		t.Fatalf("Unexpected error connecting to session bus: %s", err)
81	}
82
83	name := bus.Names()[0]
84	path := ObjectPath("/org/godbus/DBus/TestSignals")
85	otherPath := ObjectPath("/org/other-godbus/DBus/TestSignals")
86	iface := "org.godbus.DBus.TestSignals"
87	otherIface := "org.godbus.DBus.OtherTestSignals"
88	err = bus.Export(nopServer{}, path, iface)
89	if err != nil {
90		t.Fatalf("Unexpected error registering nop server: %v", err)
91	}
92
93	obj := bus.Object(name, path)
94	if err := bus.AddMatchSignal(
95		WithMatchInterface(iface),
96		WithMatchMember("Heartbeat"),
97		WithMatchObjectPath(path),
98	); err != nil {
99		t.Fatal(err)
100	}
101
102	ch := make(chan *Signal, 5)
103	bus.Signal(ch)
104
105	go func() {
106		defer func() {
107			if err := recover(); err != nil {
108				t.Errorf("Catched panic in emitter goroutine: %v", err)
109			}
110		}()
111
112		// desired signals
113		bus.Emit(path, iface+".Heartbeat", uint32(1))
114		bus.Emit(path, iface+".Heartbeat", uint32(2))
115		// undesired signals
116		bus.Emit(otherPath, iface+".Heartbeat", uint32(3))
117		bus.Emit(otherPath, otherIface+".Heartbeat", uint32(4))
118		bus.Emit(path, iface+".Updated", false)
119		// sentinel
120		bus.Emit(path, iface+".Heartbeat", uint32(5))
121
122		time.Sleep(100 * time.Millisecond)
123		bus.Emit(path, iface+".Heartbeat", uint32(6))
124	}()
125
126	checkSignal := func(sig *Signal, value uint32) {
127		if sig.Path != path {
128			t.Errorf("signal.Path mismatch: %s != %s", path, sig.Path)
129		}
130
131		name := iface + ".Heartbeat"
132		if sig.Name != name {
133			t.Errorf("signal.Name mismatch: %s != %s", name, sig.Name)
134		}
135
136		if len(sig.Body) != 1 {
137			t.Errorf("Invalid signal body length: %d", len(sig.Body))
138			return
139		}
140
141		if sig.Body[0] != interface{}(value) {
142			t.Errorf("signal value mismatch: %d != %d", value, sig.Body[0])
143		}
144	}
145
146	checkSignal(fetchSignal(t, ch, 50*time.Millisecond), 1)
147	checkSignal(fetchSignal(t, ch, 50*time.Millisecond), 2)
148	checkSignal(fetchSignal(t, ch, 50*time.Millisecond), 5)
149
150	obj.RemoveMatchSignal(iface, "Heartbeat", WithMatchObjectPath(obj.Path()))
151	select {
152	case sig := <-ch:
153		t.Errorf("Got signal after removing subscription: %v", sig)
154	case <-time.After(200 * time.Millisecond):
155	}
156}
157