main f2d166b

bus_test.go RawHistoryFolder

1package atspi
2
3import (
4	"bufio"
5	"os"
6	"os/exec"
7	"strings"
8	"testing"
9	"time"
10
11	"github.com/bnema/zerobus"
12	"github.com/stretchr/testify/require"
13)
14
15// startBus starts a private dbus-daemon, so the test never touches the
16// desktop's buses. It skips when the daemon is not installed.
17func startBus(t *testing.T) string {
18	t.Helper()
19	bin, err := exec.LookPath("dbus-daemon")
20	if err != nil {
21		if os.Getenv("CI") != "" {
22			t.Fatal("dbus-daemon not installed")
23		}
24		t.Skip("dbus-daemon not installed")
25	}
26	cmd := exec.Command(bin, "--session", "--nofork", "--nopidfile", "--print-address=1")
27	out, err := cmd.StdoutPipe()
28	require.NoError(t, err)
29	require.NoError(t, cmd.Start())
30	t.Cleanup(func() { _ = cmd.Process.Kill(); _ = cmd.Wait() })
31	line, err := bufio.NewReader(out).ReadString('\n')
32	require.NoError(t, err)
33	return strings.TrimSpace(line)
34}
35
36// TestDoDecodesMatchesPipelined runs Do against an app on a real bus: the
37// GetMatches reply is decoded, and replies to several calls sent before any
38// is read are matched by serial.
39func TestDoDecodesMatchesPipelined(t *testing.T) {
40	addr := startBus(t)
41	app, err := zerobus.Dial(addr)
42	require.NoError(t, err)
43	t.Cleanup(func() { _ = app.Close() })
44	_, err = app.RequestName("org.test.App", 0)
45	require.NoError(t, err)
46
47	done := make(chan struct{})
48	go func() {
49		defer close(done)
50		for {
51			m, err := app.ReadMessage()
52			if err != nil {
53				return
54			}
55			if m.Type != zerobus.TypeMethodCall {
56				continue
57			}
58			req := &zerobus.Message{Sender: strings.Clone(m.Sender), Serial: m.Serial}
59			switch m.Member {
60			case "GetMatches":
61				// Check the rule: struct, then sort, count, traverse.
62				r := m.Body()
63				r.Struct()
64				end := r.Array('i')
65				var states []int32
66				for r.More(end) {
67					states = append(states, r.Int32())
68				}
69				_ = r.Int32()
70				ad := r.Array('{')
71				_ = ad
72				_ = r.Int32()
73				end = r.Array('i')
74				var roles []int32
75				for r.More(end) {
76					roles = append(roles, r.Int32())
77				}
78				ok := r.Err() == nil && len(states) == 2 && states[0] == 1<<25|1<<30 && len(roles) == 5 && uint32(roles[2]) == 1<<24|1<<18|1<<31
79				if !ok {
80					app.NewError(req, "org.test.Error.BadRule", "s").Str("bad rule")
81					_, _ = app.Send()
82					continue
83				}
84				e := app.NewReply(req, "a(so)")
85				a := e.BeginArray('(')
86				for _, p := range []string{"/one", "/two"} {
87					e.Struct()
88					e.Str(":1.99")
89					e.ObjectPath(p)
90				}
91				e.EndArray(a)
92			case "GetExtents":
93				e := app.NewReply(req, "(iiii)")
94				e.Struct()
95				e.Int32(1)
96				e.Int32(2)
97				e.Int32(30)
98				e.Int32(40)
99			case "GetRole":
100				app.NewReply(req, "u").Uint32(roleLink)
101			case "GetState":
102				e := app.NewReply(req, "au")
103				a := e.BeginArray('u')
104				e.Uint32(stShowing)
105				e.Uint32(0)
106				e.EndArray(a)
107			default:
108				app.NewError(req, "org.freedesktop.DBus.Error.UnknownMethod", "s").Str("no")
109			}
110			_, _ = app.Send()
111		}
112	}()
113
114	cc, err := zerobus.Dial(addr)
115	require.NoError(t, err)
116	b, err := newConn(cc)
117	require.NoError(t, err)
118	t.Cleanup(func() { _ = b.Close() })
119
120	calls := []call{
121		{op: opMatches, name: "org.test.App", path: "/frame", rule: matchRule{linksOnly: true}},
122		{op: opExtents, name: "org.test.App", path: "/one", arg: coordWindow},
123		{op: opRole, name: "org.test.App", path: "/one"},
124		{op: opState, name: "org.test.App", path: "/one"},
125		{op: opChildren, name: "org.test.App", path: "/one"},
126	}
127	rep := b.Do(time.Now().Add(2*time.Second), time.Second, calls)
128	require.Len(t, rep, 5)
129	require.NoError(t, rep[0].err)
130	require.Equal(t, []ref{{":1.99", "/one"}, {":1.99", "/two"}}, rep[0].refs)
131	require.NoError(t, rep[1].err)
132	require.Equal(t, [4]int32{1, 2, 30, 40}, rep[1].rect)
133	require.Equal(t, uint32(roleLink), rep[2].u)
134	require.Equal(t, uint64(stShowing), rep[3].state)
135	var be *busError
136	require.ErrorAs(t, rep[4].err, &be)
137	require.Equal(t, "org.freedesktop.DBus.Error.UnknownMethod", be.name)
138
139	// A call to a name nobody owns fails fast, others still succeed.
140	rep = b.Do(time.Now().Add(2*time.Second), time.Second, []call{
141		{op: opRole, name: "org.test.Nobody", path: "/x"},
142		{op: opRole, name: "org.test.App", path: "/x"},
143	})
144	require.Error(t, rep[0].err)
145	require.NoError(t, rep[1].err)
146}
147
148func TestDoTimesOutOnSilentApp(t *testing.T) {
149	addr := startBus(t)
150	app, err := zerobus.Dial(addr)
151	require.NoError(t, err)
152	t.Cleanup(func() { _ = app.Close() })
153	_, err = app.RequestName("org.test.Mute", 0)
154	require.NoError(t, err)
155
156	cc, err := zerobus.Dial(addr)
157	require.NoError(t, err)
158	b, err := newConn(cc)
159	require.NoError(t, err)
160	t.Cleanup(func() { _ = b.Close() })
161
162	t0 := time.Now()
163	rep := b.Do(time.Now().Add(time.Second), 50*time.Millisecond, []call{{op: opRole, name: "org.test.Mute", path: "/x"}})
164	require.ErrorIs(t, rep[0].err, ErrTimeout)
165	require.Less(t, time.Since(t0), 500*time.Millisecond)
166}
167
168// A call left unanswered for idle gets ErrTimeout without breaking the
169// connection; a batch that hits its deadline breaks it, and Do returns near
170// the deadline even though the idle timeout is longer.
171func TestDoDeadlineBreaksConnection(t *testing.T) {
172	addr := startBus(t)
173	app, err := zerobus.Dial(addr)
174	require.NoError(t, err)
175	t.Cleanup(func() { _ = app.Close() })
176	_, err = app.RequestName("org.test.Mute", 0)
177	require.NoError(t, err)
178
179	cc, err := zerobus.Dial(addr)
180	require.NoError(t, err)
181	b, err := newConn(cc)
182	require.NoError(t, err)
183	t.Cleanup(func() { _ = b.Close() })
184
185	mute := []call{{op: opRole, name: "org.test.Mute", path: "/x"}}
186	rep := b.Do(time.Now().Add(time.Second), 40*time.Millisecond, mute)
187	require.ErrorIs(t, rep[0].err, ErrTimeout)
188	require.NotErrorIs(t, rep[0].err, ErrBroken)
189
190	t0 := time.Now()
191	rep = b.Do(time.Now().Add(60*time.Millisecond), time.Second, mute)
192	require.ErrorIs(t, rep[0].err, ErrBroken)
193	require.Less(t, time.Since(t0), 300*time.Millisecond)
194	// The socket is closed: the next batch fails at once.
195	rep = b.Do(time.Now().Add(time.Second), time.Second, mute)
196	require.ErrorIs(t, rep[0].err, ErrBroken)
197}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 25

Tags 10

↑↓ moveenter openesc close