1package client
2
3import (
4 "os"
5 "os/exec"
6 "path/filepath"
7 "strings"
8 "testing"
9
10 "github.com/bnema/go-wayland-bindings/spec"
11)
12
13func TestResolution(t *testing.T) {
14 p := filepath.Join(t.TempDir(), "fixture.xml")
15 xml := `<protocol name="sample"><interface name="wl_surface" version="1"><event name="done"><arg name="value" type="uint"/></event></interface><interface name="custom_thing" version="1"><request name="make"><arg name="id" type="new_id" interface="wl_surface"/><arg name="output" type="object" interface="wl_output"/></request></interface></protocol>`
16 if err := os.WriteFile(p, []byte(xml), 0600); err != nil {
17 t.Fatal(err)
18 }
19 s := NewScanner()
20 if err := s.ParseXML(p); err != nil {
21 t.Fatal(err)
22 }
23 s.CrossPackage = map[string]string{"wl_surface": "github.com/bnema/go-wayland-bindings/client/wayland", "wl_output": "github.com/bnema/go-wayland-bindings/client/wayland"}
24 b, err := s.Generate("fixture")
25 if err != nil {
26 t.Fatal(err)
27 }
28 text := string(b)
29 if !strings.Contains(text, "(*Surface, error)") || !strings.Contains(text, "output *cross_wl_output.Output") || strings.Contains(text, "cross_wl_surface") {
30 t.Fatalf("wrong resolution: %s", text)
31 }
32}
33func TestCoreGeneration(t *testing.T) {
34 data, err := spec.Open("wayland")
35 if err != nil {
36 t.Fatal(err)
37 }
38 s := NewScanner()
39 if err := s.Load(data, "wayland.xml"); err != nil {
40 t.Fatal(err)
41 }
42 b, err := s.Generate("wayland")
43 if err != nil {
44 t.Fatal(err)
45 }
46 for _, want := range []string{"type Keyboard struct", "type Subsurface struct", "type Buffer struct", "type Callback struct", "return \"uint,fd,uint,\", true", "child := &Keyboard{}"} {
47 if !strings.Contains(string(b), want) {
48 t.Errorf("missing %s", want)
49 }
50 }
51 if strings.Contains(string(b), "type Display struct") || strings.Contains(string(b), "type Registry struct") {
52 t.Fatal("bootstrap generated twice")
53 }
54}
55
56// Extension protocols use wl_buffer as both an event-created child and a request-created child.
57// Verify the generated code uses the public constructor instead of embedding
58// an inaccessible BaseProxy context field across package boundaries.
59func TestExternalChildFactory(t *testing.T) {
60 path := filepath.Join(t.TempDir(), "external.xml")
61 xml := `<protocol name="fixture"><interface name="ext_params" version="1"><request name="create"><arg name="id" type="new_id" interface="wl_buffer"/></request><event name="created"><arg name="id" type="new_id" interface="wl_buffer"/></event></interface></protocol>`
62 if err := os.WriteFile(path, []byte(xml), 0600); err != nil {
63 t.Fatal(err)
64 }
65 s := NewScanner()
66 if err := s.ParseXML(path); err != nil {
67 t.Fatal(err)
68 }
69 s.CrossPackage = map[string]string{"wl_buffer": "github.com/bnema/go-wayland-bindings/client/wayland"}
70 generated, err := s.Generate("ext")
71 if err != nil {
72 t.Fatal(err)
73 }
74 for _, part := range []string{"cross_wl_buffer.NewBuffer(o.Context())", "(*cross_wl_buffer.Buffer, error)", "idObject.SetID(idID)"} {
75 if !strings.Contains(string(generated), part) {
76 t.Errorf("generated code missing %q", part)
77 }
78 }
79 if strings.Contains(string(generated), "&cross_wl_buffer.Buffer{}") {
80 t.Fatal("foreign child was constructed without its package factory")
81 }
82}
83
84func TestUnresolvedExternalInterface(t *testing.T) {
85 path := filepath.Join(t.TempDir(), "extension.xml")
86 xml := `<protocol name="ext"><interface name="ext_parent" version="1"><request name="make"><arg name="id" type="new_id" interface="wl_surface"/></request></interface></protocol>`
87 if err := os.WriteFile(path, []byte(xml), 0600); err != nil {
88 t.Fatal(err)
89 }
90 s := NewScanner()
91 if err := s.ParseXML(path); err != nil {
92 t.Fatal(err)
93 }
94 if _, err := s.Generate("ext"); err == nil || !strings.Contains(err.Error(), "wl_surface") {
95 t.Fatalf("unmapped external reference: %v", err)
96 }
97}
98
99func TestMappedExtensionCompilesAndDispatchesChild(t *testing.T) {
100 goBin, err := exec.LookPath("go")
101 if err != nil {
102 t.Skip("go unavailable: generated extension compilation requires go")
103 }
104 dir := t.TempDir()
105 xml := `<protocol name="ext"><interface name="ext_parent" version="1"><event name="child"><arg name="id" type="new_id" interface="wl_buffer"/></event></interface></protocol>`
106 path := filepath.Join(dir, "ext.xml")
107 if err := os.WriteFile(path, []byte(xml), 0600); err != nil {
108 t.Fatal(err)
109 }
110 s := NewScanner()
111 if err := s.ParseXML(path); err != nil {
112 t.Fatal(err)
113 }
114 s.CrossPackage = map[string]string{"wl_buffer": "github.com/bnema/go-wayland-bindings/client/wayland"}
115 generated, err := s.Generate("ext")
116 if err != nil {
117 t.Fatal(err)
118 }
119 if err := os.WriteFile(filepath.Join(dir, "ext.go"), generated, 0600); err != nil {
120 t.Fatal(err)
121 }
122 writeTempModule(t, dir, "example.com/ext", true)
123 test := `package ext
124import (
125 "encoding/binary"
126 "net"
127 "os"
128 "testing"
129 "github.com/bnema/wlturbo"
130 "github.com/bnema/go-wayland-bindings/client/wayland"
131 "golang.org/x/sys/unix"
132)
133func TestTypedChild(t *testing.T) {
134 f, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM, 0); if err != nil { t.Fatal(err) }
135 a, b := os.NewFile(uintptr(f[0]), "a"), os.NewFile(uintptr(f[1]), "b")
136 c, err := net.FileConn(a); a.Close(); if err != nil { t.Fatal(err) }; defer c.Close()
137 p, err := net.FileConn(b); b.Close(); if err != nil { t.Fatal(err) }; defer p.Close()
138 d, err := wlturbo.ConnectFromConn(c); if err != nil { t.Fatal(err) }; defer d.Close()
139 parent := NewExtParent(d.Context()); parent.SetID(d.AllocateID()); d.Context().Register(parent)
140 var child *wayland.Buffer
141 parent.OnChild(func(b *wayland.Buffer) { child = b })
142 frame := make([]byte, 12); binary.LittleEndian.PutUint32(frame, parent.ID()); binary.LittleEndian.PutUint32(frame[4:], 12<<16); binary.LittleEndian.PutUint32(frame[8:], 0xff000001)
143 if _, err := p.Write(frame); err != nil { t.Fatal(err) }
144 if err := d.Dispatch(); err != nil { t.Fatal(err) }
145 if child == nil || child.ID() != 0xff000001 { t.Fatalf("child: %v", child) }
146 called := false; child.OnRelease(func(){ called = true })
147 frame = frame[:8]; binary.LittleEndian.PutUint32(frame, child.ID()); binary.LittleEndian.PutUint32(frame[4:], 8<<16)
148 if _, err := p.Write(frame); err != nil { t.Fatal(err) }
149 if err := d.Dispatch(); err != nil { t.Fatal(err) }
150 if !called { t.Fatal("typed child did not dispatch release") }
151}
152`
153 if err := os.WriteFile(filepath.Join(dir, "ext_test.go"), []byte(test), 0600); err != nil {
154 t.Fatal(err)
155 }
156 cmd := exec.Command(goBin, "test", "./...")
157 cmd.Dir = dir
158 cmd.Env = tempModuleEnv()
159 if out, err := cmd.CombinedOutput(); err != nil {
160 t.Fatalf("generated extension test: %v\n%s", err, out)
161 }
162}
163
164// An event object argument that names an interface nobody maps is an error,
165// as it is for requests, even though the event only reports the object ID.
166func TestUnresolvedEventObjectInterface(t *testing.T) {
167 xml := `<protocol name="ext"><interface name="ext_parent" version="1"><event name="focus"><arg name="surface" type="object" interface="wl_surfce"/></event></interface></protocol>`
168 s := NewScanner()
169 if err := s.Load([]byte(xml), "ext.xml"); err != nil {
170 t.Fatal(err)
171 }
172 if _, err := s.Generate("ext"); err == nil || !strings.Contains(err.Error(), "wl_surfce") {
173 t.Fatalf("unresolved event object interface: %v", err)
174 }
175 s.CrossPackage = map[string]string{"wl_surfce": spec.ModulePath + "/client/wayland"}
176 if _, err := s.Generate("ext"); err != nil {
177 t.Fatalf("mapped interface: %v", err)
178 }
179}