package main
import (
"bytes"
"os"
"path/filepath"
"strings"
"testing"
"github.com/bnema/go-wayland-bindings/spec"
)
const customXML = `
Test copyright.
`
func writeXML(t *testing.T, body string) string {
t.Helper()
p := filepath.Join(t.TempDir(), "custom.xml")
if err := os.WriteFile(p, []byte(body), 0o600); err != nil {
t.Fatal(err)
}
return p
}
func TestCustomResolvesFromManifest(t *testing.T) {
for side, want := range map[string]string{
"client": `"github.com/bnema/go-wayland-bindings/client/wayland"`,
"server": `wayland "github.com/bnema/go-wayland-bindings/server/wayland"`,
} {
t.Run(side, func(t *testing.T) {
out := filepath.Join(t.TempDir(), "x", "out.go")
args := []string{"-side", side, "-package", "custom", "-out", out, writeXML(t, customXML)}
if err := run(args, &bytes.Buffer{}); err != nil {
t.Fatal(err)
}
b, err := os.ReadFile(out)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(b), want) {
t.Errorf("output lacks import %s:\n%s", want, b)
}
if !strings.Contains(string(b), "Code generated by wlbgen") {
t.Error("header does not name wlbgen")
}
if !strings.Contains(string(b), "// Test copyright.\n") || strings.Contains(string(b), "SPDX") {
t.Errorf("custom output must keep the XML copyright and add no SPDX line:\n%s", b[:300])
}
})
}
}
func TestCustomImportOverride(t *testing.T) {
out := filepath.Join(t.TempDir(), "out.go")
args := []string{"-side", "server", "-package", "custom", "-out", out,
"-import", "wl_surface=example.com/mine/surfaces", writeXML(t, customXML)}
if err := run(args, &bytes.Buffer{}); err != nil {
t.Fatal(err)
}
b, _ := os.ReadFile(out)
if !strings.Contains(string(b), `surfaces "example.com/mine/surfaces"`) {
t.Errorf("override not used:\n%s", b)
}
}
func TestCustomUnknownInterfaceErrors(t *testing.T) {
xml := strings.ReplaceAll(customXML, "wl_surface", "no_such_iface")
for _, side := range []string{"client", "server"} {
t.Run(side, func(t *testing.T) {
out := filepath.Join(t.TempDir(), "out.go")
err := run([]string{"-side", side, "-package", "custom", "-out", out, writeXML(t, xml)}, &bytes.Buffer{})
if err == nil || !strings.Contains(err.Error(), "no_such_iface") {
t.Fatalf("want unresolved-interface error, got %v", err)
}
if _, statErr := os.Stat(out); statErr == nil {
t.Error("output written despite error")
}
})
}
}
func TestCustomBadSide(t *testing.T) {
out := filepath.Join(t.TempDir(), "out.go")
if err := run([]string{"-side", "both", "-package", "p", "-out", out, writeXML(t, customXML)}, &bytes.Buffer{}); err == nil {
t.Fatal("want error for bad -side")
}
}
// smallManifest builds a one-package manifest backed by customXML-like input.
func smallManifest() (*spec.Manifest, func(string) ([]byte, error)) {
xml := strings.ReplaceAll(customXML, ``, ``)
m := &spec.Manifest{Protocols: []spec.ProtocolEntry{{
Package: "custom", XML: "custom.xml", Path: "dir/custom-v1.xml", License: "MIT",
Interfaces: []spec.InterfaceEntry{{Name: "custom_thing", Version: 1}},
}}}
return m, func(string) ([]byte, error) { return []byte(xml), nil }
}
func TestAllDeterministic(t *testing.T) {
root := t.TempDir()
if err := generateAll(mustLoad(t), spec.Open, root); err != nil {
t.Fatal(err)
}
first := snapshot(t, root)
if len(first) != 2*len(mustLoad(t).Protocols) {
t.Fatalf("got %d files, want 2 per protocol", len(first))
}
if err := generateAll(mustLoad(t), spec.Open, root); err != nil {
t.Fatal(err)
}
second := snapshot(t, root)
for name, b := range first {
if !bytes.Equal(b, second[name]) {
t.Errorf("%s differs between runs", name)
}
}
if len(first) != len(second) {
t.Errorf("file count changed: %d -> %d", len(first), len(second))
}
}
func mustLoad(t *testing.T) *spec.Manifest {
t.Helper()
m, err := spec.Load()
if err != nil {
t.Fatal(err)
}
return m
}
func snapshot(t *testing.T, root string) map[string][]byte {
t.Helper()
out := map[string][]byte{}
err := filepath.WalkDir(root, func(p string, d os.DirEntry, err error) error {
if err != nil || d.IsDir() {
return err
}
b, err := os.ReadFile(p)
out[p] = b
return err
})
if err != nil {
t.Fatal(err)
}
return out
}
func TestAllSourceNameAndLicense(t *testing.T) {
root := t.TempDir()
m, open := smallManifest()
m.Protocols[0].License = "LGPL-2.1-or-later"
if err := generateAll(m, open, root); err != nil {
t.Fatal(err)
}
b, err := os.ReadFile(filepath.Join(root, "client", "custom", "custom_generated.go"))
if err != nil {
t.Fatal(err)
}
for _, want := range []string{"custom-v1.xml", "SPDX-License-Identifier: LGPL-2.1-or-later"} {
if !strings.Contains(string(b), want) {
t.Errorf("client header lacks %q", want)
}
}
sb, err := os.ReadFile(filepath.Join(root, "server", "custom", "custom_generated.go"))
if err != nil {
t.Fatal(err)
}
for _, want := range []string{"SPDX-License-Identifier: LGPL-2.1-or-later", "Upstream protocol copyright and license:", "// Test copyright."} {
if !strings.Contains(string(sb), want) {
t.Errorf("server header lacks %q", want)
}
}
}
func TestAllErrorLeavesTreeUntouched(t *testing.T) {
root := t.TempDir()
m, open := smallManifest()
m.Protocols = append(m.Protocols, spec.ProtocolEntry{Package: "broken", XML: "broken.xml", Path: "broken.xml"})
failing := func(pkg string) ([]byte, error) {
if pkg == "broken" {
return []byte(``), nil
}
return open(pkg)
}
stale := filepath.Join(root, "client", "gone")
if err := os.MkdirAll(stale, 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(stale, "gone_generated.go"), []byte("package gone\n"), 0o600); err != nil {
t.Fatal(err)
}
if err := generateAll(m, failing, root); err == nil {
t.Fatal("expected error")
}
got := snapshot(t, root)
if len(got) != 1 {
t.Fatalf("tree changed on error: %v", got)
}
}
func TestStaleGeneratedRemoved(t *testing.T) {
root := t.TempDir()
for _, side := range []string{"client", "server"} {
dir := filepath.Join(root, side, "gone")
if err := os.MkdirAll(dir, 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, "gone_generated.go"), []byte("package gone\n"), 0o600); err != nil {
t.Fatal(err)
}
}
// A directory without a generated file is not ours and stays.
keep := filepath.Join(root, "client", "handwritten")
if err := os.MkdirAll(keep, 0o755); err != nil {
t.Fatal(err)
}
m, open := smallManifest()
if err := generateAll(m, open, root); err != nil {
t.Fatal(err)
}
for _, side := range []string{"client", "server"} {
if _, err := os.Stat(filepath.Join(root, side, "gone")); !os.IsNotExist(err) {
t.Errorf("%s/gone not removed (err=%v)", side, err)
}
}
if _, err := os.Stat(keep); err != nil {
t.Errorf("unrelated dir removed: %v", err)
}
}
func TestStaleWithHandWrittenFileErrors(t *testing.T) {
root := t.TempDir()
dir := filepath.Join(root, "client", "gone")
if err := os.MkdirAll(dir, 0o755); err != nil {
t.Fatal(err)
}
for _, f := range []string{"gone_generated.go", "table.go"} {
if err := os.WriteFile(filepath.Join(dir, f), []byte("package gone\n"), 0o600); err != nil {
t.Fatal(err)
}
}
m, open := smallManifest()
err := generateAll(m, open, root)
if err == nil || !strings.Contains(err.Error(), filepath.Join("client", "gone", "table.go")) {
t.Fatalf("want error listing table.go, got %v", err)
}
if _, err := os.Stat(filepath.Join(dir, "gone_generated.go")); err != nil {
t.Error("generated file deleted despite error")
}
}