main 5004296

requestargs_test.go RawHistoryFolder

1package client
2
3import (
4	"os"
5	"os/exec"
6	"path/filepath"
7	"strings"
8	"testing"
9)
10
11const requestArgsXML = `<protocol name="argsfix">
12<interface name="args_obj" version="1">
13 <request name="mixed">
14  <arg name="serial" type="uint"/><arg name="dx" type="int"/><arg name="fx" type="fixed"/>
15  <arg name="name" type="string"/><arg name="blob" type="array"/>
16  <arg name="peer" type="object" interface="args_obj" allow-null="true"/>
17 </request>
18 <request name="make"><arg name="id" type="new_id" interface="args_obj"/><arg name="n" type="uint"/></request>
19 <request name="give"><arg name="tag" type="string"/><arg name="fd" type="fd"/><arg name="size" type="uint"/></request>
20 <request name="destroy" type="destructor"/>
21</interface></protocol>`
22
23func generateArgsFixture(t *testing.T) string {
24	t.Helper()
25	path := filepath.Join(t.TempDir(), "argsfix.xml")
26	if err := os.WriteFile(path, []byte(requestArgsXML), 0600); err != nil {
27		t.Fatal(err)
28	}
29	s := NewScanner()
30	if err := s.ParseXML(path); err != nil {
31		t.Fatal(err)
32	}
33	b, err := s.Generate("argsfix")
34	if err != nil {
35		t.Fatal(err)
36	}
37	return string(b)
38}
39
40// Generated requests must use typed arguments only: no interface{} boxing
41// path and no uintptr placeholder for descriptors.
42func TestGeneratedRequestsUseTypedArgs(t *testing.T) {
43	text := generateArgsFixture(t)
44	for _, want := range []string{
45		"RequestArgs(wl.Request{Proxy: o, Opcode: 0, Name: \"args_obj.mixed\"}, wl.ArgUint(serial), wl.ArgInt(dx), wl.ArgFixed(fx), wl.ArgString(name), wl.ArgArray(blob), wl.ArgObject(arg5))",
46		"Child: child}, wl.ArgObject(child), wl.ArgUint(n))",
47		"FDs: []int{fd}}, wl.ArgString(tag), wl.ArgFD(), wl.ArgUint(size))",
48		"Destructor: true})",
49	} {
50		if !strings.Contains(text, want) {
51			t.Errorf("generated code missing %q\n%s", want, text)
52		}
53	}
54	for _, bad := range []string{".Request(wl.Request", "uintptr("} {
55		if strings.Contains(text, bad) {
56			t.Errorf("generated code still contains %q", bad)
57		}
58	}
59}
60
61// The generated requests compile against the transport, put the exact wire
62// bytes on the socket, and do not allocate for realistic numeric values.
63func TestGeneratedRequestsWireAndAllocations(t *testing.T) {
64	goBin, err := exec.LookPath("go")
65	if err != nil {
66		t.Skip("go unavailable")
67	}
68	dir := t.TempDir()
69	write := func(name, content string) {
70		if err := os.WriteFile(filepath.Join(dir, name), []byte(content), 0600); err != nil {
71			t.Fatal(err)
72		}
73	}
74	write("args.go", generateArgsFixture(t))
75	writeTempModule(t, dir, "example.com/argsfix", false)
76	write("args_test.go", `package argsfix
77
78import (
79	"bytes"
80	"net"
81	"os"
82	"testing"
83	"time"
84
85	"github.com/bnema/wlturbo"
86	"golang.org/x/sys/unix"
87)
88
89type sink struct{ last []byte }
90
91func (c *sink) Read([]byte) (int, error) { return 0, net.ErrClosed }
92func (c *sink) Write(p []byte) (int, error) {
93	c.last = append(c.last[:0], p...)
94	return len(p), nil
95}
96func (c *sink) Close() error                     { return nil }
97func (c *sink) LocalAddr() net.Addr              { return a{} }
98func (c *sink) RemoteAddr() net.Addr             { return a{} }
99func (c *sink) SetDeadline(time.Time) error      { return nil }
100func (c *sink) SetReadDeadline(time.Time) error  { return nil }
101func (c *sink) SetWriteDeadline(time.Time) error { return nil }
102
103type a struct{}
104
105func (a) Network() string { return "t" }
106func (a) String() string  { return "t" }
107
108func setup(t testing.TB) (*wlturbo.Display, *sink, *ArgsObj) {
109	c := &sink{}
110	d, err := wlturbo.ConnectFromConn(c)
111	if err != nil {
112		t.Fatal(err)
113	}
114	o := NewArgsObj(d.Context())
115	o.SetID(d.AllocateID())
116	d.Context().Register(o)
117	return d, c, o
118}
119
120func TestMixedWire(t *testing.T) {
121	_, c, o := setup(t)
122	peer := NewArgsObj(o.Context())
123	peer.SetID(0x1234)
124	if err := o.Mixed(1000, -2, wlturbo.Fixed(512), "ab", []byte{9, 8, 7}, peer); err != nil {
125		t.Fatal(err)
126	}
127	want := []byte{
128		byte(o.ID()), 0, 0, 0, 0, 0, 40, 0,
129		0xe8, 3, 0, 0,
130		0xfe, 0xff, 0xff, 0xff,
131		0, 2, 0, 0,
132		3, 0, 0, 0, 'a', 'b', 0, 0,
133		3, 0, 0, 0, 9, 8, 7, 0,
134		0x34, 0x12, 0, 0,
135	}
136	if !bytes.Equal(c.last, want) {
137		t.Fatalf("got %x want %x", c.last, want)
138	}
139	if err := o.Mixed(1, 2, 3, "", nil, nil); err != nil {
140		t.Fatal(err)
141	}
142	if got := c.last[len(c.last)-4:]; !bytes.Equal(got, []byte{0, 0, 0, 0}) {
143		t.Fatalf("nil object = %x", got)
144	}
145}
146
147func TestMixedAllocFree(t *testing.T) {
148	_, _, o := setup(t)
149	blob := []byte("blob")
150	n := testing.AllocsPerRun(200, func() {
151		if err := o.Mixed(123456, -70000, wlturbo.Fixed(1<<20), "hello", blob, nil); err != nil {
152			t.Fatal(err)
153		}
154	})
155	if n != 0 {
156		t.Fatalf("Mixed: %v allocs/op, want 0", n)
157	}
158}
159
160func TestChildAndDestructor(t *testing.T) {
161	d, c, o := setup(t)
162	kid, err := o.Make(77777)
163	if err != nil {
164		t.Fatal(err)
165	}
166	if kid.ID() == 0 || c.last[8] != byte(kid.ID()) {
167		t.Fatalf("child id %d not on the wire: %x", kid.ID(), c.last)
168	}
169	if err := o.Destroy(); err != nil {
170		t.Fatal(err)
171	}
172	n := len(c.last)
173	c.last = c.last[:0]
174	if err := o.Destroy(); err == nil {
175		t.Fatal("second destructor succeeded")
176	}
177	if len(c.last) != 0 || n == 0 {
178		t.Fatal("wrote after destructor")
179	}
180	_ = d
181}
182
183func TestFDRequest(t *testing.T) {
184	sp, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0)
185	if err != nil {
186		t.Fatal(err)
187	}
188	fa, fb := os.NewFile(uintptr(sp[0]), "a"), os.NewFile(uintptr(sp[1]), "b")
189	ca, err := net.FileConn(fa)
190	fa.Close()
191	if err != nil {
192		fb.Close()
193		t.Fatal(err)
194	}
195	defer ca.Close()
196	cb, err := net.FileConn(fb)
197	fb.Close()
198	if err != nil {
199		t.Fatal(err)
200	}
201	defer cb.Close()
202	d, err := wlturbo.ConnectFromConn(ca)
203	if err != nil {
204		t.Fatal(err)
205	}
206	defer d.Close()
207	o := NewArgsObj(d.Context())
208	o.SetID(d.AllocateID())
209	d.Context().Register(o)
210	fd, err := unix.Open(os.DevNull, unix.O_RDONLY|unix.O_CLOEXEC, 0)
211	if err != nil {
212		t.Fatal(err)
213	}
214	if err := o.Give("t", fd, 5); err != nil {
215		t.Fatal(err)
216	}
217	if _, err := unix.FcntlInt(uintptr(fd), unix.F_GETFD, 0); err == nil {
218		t.Fatal("fd not closed after send")
219	}
220	// The connection first sends wl_display.get_registry (12 bytes).
221	buf := make([]byte, 12+8+8+4)
222	oob := make([]byte, unix.CmsgSpace(4))
223	n, oobn, _, _, err := cb.(*net.UnixConn).ReadMsgUnix(buf, oob)
224	if err != nil || n != len(buf) || oobn == 0 {
225		t.Fatalf("n=%d oobn=%d err=%v", n, oobn, err)
226	}
227	// The descriptor has no body word: string(8) + uint(4) only.
228	if buf[12+4] != 2 || buf[12+8+8] != 5 || buf[12+6] != 20 {
229		t.Fatalf("message = %x", buf[12:])
230	}
231}
232`)
233	cmd := exec.Command(goBin, "test", "./...")
234	cmd.Dir = dir
235	cmd.Env = tempModuleEnv()
236	if out, err := cmd.CombinedOutput(); err != nil {
237		t.Fatalf("generated request test: %v\n%s", err, out)
238	}
239}

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 1

Tags 1

↑↓ moveenter openesc close