detached 5004296

main_test.go RawHistoryFolder

1package main
2
3import (
4	"bytes"
5	"os"
6	"path/filepath"
7	"strings"
8	"testing"
9
10	"github.com/bnema/go-wayland-bindings/spec"
11)
12
13const customXML = `<?xml version="1.0" encoding="UTF-8"?>
14<protocol name="custom_thing">
15  <copyright>Test copyright.</copyright>
16  <interface name="custom_thing" version="1">
17    <request name="attach">
18      <arg name="surface" type="object" interface="wl_surface"/>
19    </request>
20    <event name="done"/>
21  </interface>
22</protocol>
23`
24
25func writeXML(t *testing.T, body string) string {
26	t.Helper()
27	p := filepath.Join(t.TempDir(), "custom.xml")
28	if err := os.WriteFile(p, []byte(body), 0o600); err != nil {
29		t.Fatal(err)
30	}
31	return p
32}
33
34func TestCustomResolvesFromManifest(t *testing.T) {
35	for side, want := range map[string]string{
36		"client": `"github.com/bnema/go-wayland-bindings/client/wayland"`,
37		"server": `wayland "github.com/bnema/go-wayland-bindings/server/wayland"`,
38	} {
39		t.Run(side, func(t *testing.T) {
40			out := filepath.Join(t.TempDir(), "x", "out.go")
41			args := []string{"-side", side, "-package", "custom", "-out", out, writeXML(t, customXML)}
42			if err := run(args, &bytes.Buffer{}); err != nil {
43				t.Fatal(err)
44			}
45			b, err := os.ReadFile(out)
46			if err != nil {
47				t.Fatal(err)
48			}
49			if !strings.Contains(string(b), want) {
50				t.Errorf("output lacks import %s:\n%s", want, b)
51			}
52			if !strings.Contains(string(b), "Code generated by wlbgen") {
53				t.Error("header does not name wlbgen")
54			}
55			if !strings.Contains(string(b), "// Test copyright.\n") || strings.Contains(string(b), "SPDX") {
56				t.Errorf("custom output must keep the XML copyright and add no SPDX line:\n%s", b[:300])
57			}
58		})
59	}
60}
61
62func TestCustomImportOverride(t *testing.T) {
63	out := filepath.Join(t.TempDir(), "out.go")
64	args := []string{"-side", "server", "-package", "custom", "-out", out,
65		"-import", "wl_surface=example.com/mine/surfaces", writeXML(t, customXML)}
66	if err := run(args, &bytes.Buffer{}); err != nil {
67		t.Fatal(err)
68	}
69	b, _ := os.ReadFile(out)
70	if !strings.Contains(string(b), `surfaces "example.com/mine/surfaces"`) {
71		t.Errorf("override not used:\n%s", b)
72	}
73}
74
75func TestCustomUnknownInterfaceErrors(t *testing.T) {
76	xml := strings.ReplaceAll(customXML, "wl_surface", "no_such_iface")
77	for _, side := range []string{"client", "server"} {
78		t.Run(side, func(t *testing.T) {
79			out := filepath.Join(t.TempDir(), "out.go")
80			err := run([]string{"-side", side, "-package", "custom", "-out", out, writeXML(t, xml)}, &bytes.Buffer{})
81			if err == nil || !strings.Contains(err.Error(), "no_such_iface") {
82				t.Fatalf("want unresolved-interface error, got %v", err)
83			}
84			if _, statErr := os.Stat(out); statErr == nil {
85				t.Error("output written despite error")
86			}
87		})
88	}
89}
90
91func TestCustomBadSide(t *testing.T) {
92	out := filepath.Join(t.TempDir(), "out.go")
93	if err := run([]string{"-side", "both", "-package", "p", "-out", out, writeXML(t, customXML)}, &bytes.Buffer{}); err == nil {
94		t.Fatal("want error for bad -side")
95	}
96}
97
98// smallManifest builds a one-package manifest backed by customXML-like input.
99func smallManifest() (*spec.Manifest, func(string) ([]byte, error)) {
100	xml := strings.ReplaceAll(customXML, `<arg name="surface" type="object" interface="wl_surface"/>`, ``)
101	m := &spec.Manifest{Protocols: []spec.ProtocolEntry{{
102		Package: "custom", XML: "custom.xml", Path: "dir/custom-v1.xml", License: "MIT",
103		Interfaces: []spec.InterfaceEntry{{Name: "custom_thing", Version: 1}},
104	}}}
105	return m, func(string) ([]byte, error) { return []byte(xml), nil }
106}
107
108func TestAllDeterministic(t *testing.T) {
109	root := t.TempDir()
110	if err := generateAll(mustLoad(t), spec.Open, root); err != nil {
111		t.Fatal(err)
112	}
113	first := snapshot(t, root)
114	if len(first) != 2*len(mustLoad(t).Protocols) {
115		t.Fatalf("got %d files, want 2 per protocol", len(first))
116	}
117	if err := generateAll(mustLoad(t), spec.Open, root); err != nil {
118		t.Fatal(err)
119	}
120	second := snapshot(t, root)
121	for name, b := range first {
122		if !bytes.Equal(b, second[name]) {
123			t.Errorf("%s differs between runs", name)
124		}
125	}
126	if len(first) != len(second) {
127		t.Errorf("file count changed: %d -> %d", len(first), len(second))
128	}
129}
130
131func mustLoad(t *testing.T) *spec.Manifest {
132	t.Helper()
133	m, err := spec.Load()
134	if err != nil {
135		t.Fatal(err)
136	}
137	return m
138}
139
140func snapshot(t *testing.T, root string) map[string][]byte {
141	t.Helper()
142	out := map[string][]byte{}
143	err := filepath.WalkDir(root, func(p string, d os.DirEntry, err error) error {
144		if err != nil || d.IsDir() {
145			return err
146		}
147		b, err := os.ReadFile(p)
148		out[p] = b
149		return err
150	})
151	if err != nil {
152		t.Fatal(err)
153	}
154	return out
155}
156
157func TestAllSourceNameAndLicense(t *testing.T) {
158	root := t.TempDir()
159	m, open := smallManifest()
160	m.Protocols[0].License = "LGPL-2.1-or-later"
161	if err := generateAll(m, open, root); err != nil {
162		t.Fatal(err)
163	}
164	b, err := os.ReadFile(filepath.Join(root, "client", "custom", "custom_generated.go"))
165	if err != nil {
166		t.Fatal(err)
167	}
168	for _, want := range []string{"custom-v1.xml", "SPDX-License-Identifier: LGPL-2.1-or-later"} {
169		if !strings.Contains(string(b), want) {
170			t.Errorf("client header lacks %q", want)
171		}
172	}
173	sb, err := os.ReadFile(filepath.Join(root, "server", "custom", "custom_generated.go"))
174	if err != nil {
175		t.Fatal(err)
176	}
177	for _, want := range []string{"SPDX-License-Identifier: LGPL-2.1-or-later", "Upstream protocol copyright and license:", "// Test copyright."} {
178		if !strings.Contains(string(sb), want) {
179			t.Errorf("server header lacks %q", want)
180		}
181	}
182}
183
184func TestAllErrorLeavesTreeUntouched(t *testing.T) {
185	root := t.TempDir()
186	m, open := smallManifest()
187	m.Protocols = append(m.Protocols, spec.ProtocolEntry{Package: "broken", XML: "broken.xml", Path: "broken.xml"})
188	failing := func(pkg string) ([]byte, error) {
189		if pkg == "broken" {
190			return []byte(`<protocol name="broken"><interface name="b" version="1"><event name="e"><arg name="o" type="object" interface="nowhere"/></event></interface></protocol>`), nil
191		}
192		return open(pkg)
193	}
194	stale := filepath.Join(root, "client", "gone")
195	if err := os.MkdirAll(stale, 0o755); err != nil {
196		t.Fatal(err)
197	}
198	if err := os.WriteFile(filepath.Join(stale, "gone_generated.go"), []byte("package gone\n"), 0o600); err != nil {
199		t.Fatal(err)
200	}
201	if err := generateAll(m, failing, root); err == nil {
202		t.Fatal("expected error")
203	}
204	got := snapshot(t, root)
205	if len(got) != 1 {
206		t.Fatalf("tree changed on error: %v", got)
207	}
208}
209
210func TestStaleGeneratedRemoved(t *testing.T) {
211	root := t.TempDir()
212	for _, side := range []string{"client", "server"} {
213		dir := filepath.Join(root, side, "gone")
214		if err := os.MkdirAll(dir, 0o755); err != nil {
215			t.Fatal(err)
216		}
217		if err := os.WriteFile(filepath.Join(dir, "gone_generated.go"), []byte("package gone\n"), 0o600); err != nil {
218			t.Fatal(err)
219		}
220	}
221	// A directory without a generated file is not ours and stays.
222	keep := filepath.Join(root, "client", "handwritten")
223	if err := os.MkdirAll(keep, 0o755); err != nil {
224		t.Fatal(err)
225	}
226	m, open := smallManifest()
227	if err := generateAll(m, open, root); err != nil {
228		t.Fatal(err)
229	}
230	for _, side := range []string{"client", "server"} {
231		if _, err := os.Stat(filepath.Join(root, side, "gone")); !os.IsNotExist(err) {
232			t.Errorf("%s/gone not removed (err=%v)", side, err)
233		}
234	}
235	if _, err := os.Stat(keep); err != nil {
236		t.Errorf("unrelated dir removed: %v", err)
237	}
238}
239
240func TestStaleWithHandWrittenFileErrors(t *testing.T) {
241	root := t.TempDir()
242	dir := filepath.Join(root, "client", "gone")
243	if err := os.MkdirAll(dir, 0o755); err != nil {
244		t.Fatal(err)
245	}
246	for _, f := range []string{"gone_generated.go", "table.go"} {
247		if err := os.WriteFile(filepath.Join(dir, f), []byte("package gone\n"), 0o600); err != nil {
248			t.Fatal(err)
249		}
250	}
251	m, open := smallManifest()
252	err := generateAll(m, open, root)
253	if err == nil || !strings.Contains(err.Error(), filepath.Join("client", "gone", "table.go")) {
254		t.Fatalf("want error listing table.go, got %v", err)
255	}
256	if _, err := os.Stat(filepath.Join(dir, "gone_generated.go")); err != nil {
257		t.Error("generated file deleted despite error")
258	}
259}

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