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") } }