v0.3.0 da0887c

rawbus_test.go RawHistoryFolder

1package tray
2
3import (
4	"bufio"
5	"encoding/binary"
6	"io"
7	"net"
8	"path/filepath"
9	"testing"
10
11	"github.com/bnema/zerobus"
12)
13
14// wb builds D-Bus wire data by hand (little endian), so tests can send bodies
15// that zerobus's own encoder refuses to produce: truncated or inconsistent.
16type wb struct{ b []byte }
17
18func (w *wb) pad(n int) {
19	for len(w.b)%n != 0 {
20		w.b = append(w.b, 0)
21	}
22}
23
24func (w *wb) u32(v uint32) { w.pad(4); w.b = binary.LittleEndian.AppendUint32(w.b, v) }
25func (w *wb) i32(v int32)  { w.u32(uint32(v)) }
26
27func (w *wb) str(s string) {
28	w.u32(uint32(len(s)))
29	w.b = append(append(w.b, s...), 0)
30}
31
32func (w *wb) sig(s string) { w.b = append(append(w.b, byte(len(s))), append([]byte(s), 0)...) }
33
34// lenPrefixed writes an array whose data starts aligned to align and is filled by fn.
35func (w *wb) lenPrefixed(align int, fn func()) {
36	w.u32(0)
37	at := len(w.b) - 4
38	w.pad(align)
39	start := len(w.b)
40	fn()
41	binary.LittleEndian.PutUint32(w.b[at:], uint32(len(w.b)-start))
42}
43
44// layoutBody is the body of a GetLayout reply for root: the revision, then the tree.
45func layoutBody(root *tnode) []byte {
46	var w wb
47	w.u32(1)
48	w.node(root)
49	return w.b
50}
51
52func (w *wb) node(n *tnode) {
53	w.pad(8)
54	w.i32(n.id)
55	w.lenPrefixed(8, func() {
56		for _, p := range n.props {
57			w.pad(8)
58			w.str(p.k)
59			switch v := p.v.(type) {
60			case string:
61				w.sig("s")
62				w.str(v)
63			case bool:
64				w.sig("b")
65				if v {
66					w.u32(1)
67				} else {
68					w.u32(0)
69				}
70			case int32:
71				w.sig("i")
72				w.i32(v)
73			}
74		}
75	})
76	w.lenPrefixed(1, func() {
77		if n.junk {
78			w.sig("s")
79			w.str("not a node")
80		}
81		for i := range n.kids {
82			w.sig(layoutSig)
83			w.node(&n.kids[i])
84		}
85	})
86}
87
88// frame is a method return of the given serials, with signature sig and body.
89func frame(serial, replySerial uint32, sig string, body []byte) []byte {
90	// Header field 5 (REPLY_SERIAL, u) and 8 (SIGNATURE, g).
91	var fields wb
92	fields.pad(8)
93	fields.b = append(fields.b, 5)
94	fields.sig("u")
95	fields.u32(replySerial)
96	if sig != "" {
97		fields.pad(8)
98		fields.b = append(fields.b, 8)
99		fields.sig("g")
100		fields.sig(sig)
101	}
102	h := []byte{'l', 2, 0, 1}
103	h = binary.LittleEndian.AppendUint32(h, uint32(len(body)))
104	h = binary.LittleEndian.AppendUint32(h, serial)
105	h = binary.LittleEndian.AppendUint32(h, uint32(len(fields.b)))
106	h = append(h, fields.b...)
107	for len(h)%8 != 0 {
108		h = append(h, 0)
109	}
110	return append(h, body...)
111}
112
113// reply is what the raw bus answers to the next method call.
114type reply struct {
115	sig  string
116	body []byte
117}
118
119// rawBus is a bus with one client that answers every method call after Hello
120// with the next queued reply. It returns the address to dial.
121func rawBus(t testing.TB) (addr string, next chan<- reply) {
122	t.Helper()
123	sock := filepath.Join(t.TempDir(), "bus")
124	l, err := net.Listen("unix", sock)
125	if err != nil {
126		t.Fatal(err)
127	}
128	t.Cleanup(func() { _ = l.Close() })
129	ch := make(chan reply, 16)
130	go func() {
131		c, err := l.Accept()
132		if err != nil {
133			return
134		}
135		defer c.Close()
136		r := bufio.NewReader(c)
137		if _, err = r.ReadString('\n'); err != nil { // \0AUTH EXTERNAL ...
138			return
139		}
140		if _, err = io.WriteString(c, "OK 0123456789abcdef\r\n"); err != nil {
141			return
142		}
143		if _, err = r.ReadString('\n'); err != nil { // BEGIN
144			return
145		}
146		var hello wb
147		hello.str(":1.7")
148		serial := uint32(1)
149		first := true
150		for {
151			var hdr [16]byte
152			if _, err = io.ReadFull(r, hdr[:]); err != nil {
153				return
154			}
155			fields := binary.LittleEndian.Uint32(hdr[12:])
156			rest := (16+int(fields)+7)/8*8 - 16 + int(binary.LittleEndian.Uint32(hdr[4:]))
157			if _, err = io.CopyN(io.Discard, r, int64(rest)); err != nil {
158				return
159			}
160			callSerial := binary.LittleEndian.Uint32(hdr[8:])
161			var out []byte
162			if first {
163				first = false
164				out = frame(serial, callSerial, "s", hello.b)
165			} else {
166				rp, ok := <-ch
167				if !ok {
168					return
169				}
170				out = frame(serial, callSerial, rp.sig, rp.body)
171			}
172			serial++
173			if _, err = c.Write(out); err != nil {
174				return
175			}
176		}
177	}()
178	return "unix:path=" + sock, ch
179}
180
181// rawDial connects to a rawBus.
182func rawDial(t testing.TB, addr string) *zerobus.Conn {
183	t.Helper()
184	c, err := zerobus.Dial(addr)
185	if err != nil {
186		t.Fatal(err)
187	}
188	t.Cleanup(func() { _ = c.Close() })
189	return c
190}

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 5

Tags 4

↑↓ moveenter openesc close