1package server
2
3import (
4 "context"
5 "os"
6 "testing"
7 "unsafe"
8
9 "github.com/bnema/purego"
10 "golang.org/x/sys/unix"
11)
12
13// hotResource creates a real wl_client/resource on the display thread without
14// needing a separate client event loop.
15func hotResource(t testing.TB, d *Display, iface *Interface, h Handler) *Resource {
16 t.Helper()
17 lib, err := purego.Dlopen("libwayland-server.so.0", purego.RTLD_NOW)
18 if err != nil {
19 t.Fatal(err)
20 }
21 create, err := purego.Dlsym(lib, "wl_client_create")
22 if err != nil {
23 t.Fatal(err)
24 }
25 fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0)
26 if err != nil {
27 t.Fatal(err)
28 }
29 t.Cleanup(func() { _ = unix.Close(fds[1]) })
30 var r *Resource
31 if !d.Do(func() {
32 client, _, _ := purego.SyscallN(create, d.c, uintptr(fds[0]))
33 if client == 0 {
34 _ = unix.Close(fds[0])
35 return
36 }
37 r, err = (Client{client}).CreateResource(iface, 1, 2, h)
38 }) {
39 t.Fatal("display stopped")
40 }
41 if err != nil || r == nil {
42 t.Fatalf("create resource: %v", err)
43 }
44 return r
45}
46
47func hotDisplay(t testing.TB, iface *Interface, h Handler) (*Display, *Resource) {
48 t.Helper()
49 if err := NewInterfaces(iface); err != nil {
50 t.Fatal(err)
51 }
52 d, err := NewDisplay()
53 if err != nil {
54 t.Fatal(err)
55 }
56 ctx, cancel := context.WithCancel(context.Background())
57 done := make(chan error, 1)
58 go func() { done <- d.Run(ctx) }()
59 t.Cleanup(func() {
60 cancel()
61 if err := <-done; err != nil {
62 t.Error(err)
63 }
64 })
65 return d, hotResource(t, d, iface, h)
66}
67
68func TestServerAllocatedResourceID(t *testing.T) {
69 iface := &Interface{Name: "server_allocated_id_test", Version: 1}
70 d, existing := hotDisplay(t, iface, nil)
71 if !d.Do(func() {
72 r, err := (Client{existing.client}).CreateResource(iface, 1, 0, nil)
73 if err != nil {
74 t.Errorf("create server-side resource: %v", err)
75 return
76 }
77 if r.ID() == 0 || r.ID() != wlResourceGetID(r.c) {
78 t.Errorf("resource ID = %d, libwayland ID = %d", r.ID(), wlResourceGetID(r.c))
79 }
80 }) {
81 t.Fatal("display stopped")
82 }
83}
84
85func requestMessage(iface *Interface) unsafe.Pointer {
86 return *(*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(&tables.mem[0]), iface.c-tables.base+16))
87}
88
89var hotSink uint32
90
91func hotHandler(_ *Resource, _ uint32, args []Arg) { hotSink = args[0].Uint() }
92
93func TestHotPathAllocs(t *testing.T) {
94 iface := &Interface{Name: "hot_path_test", Version: 1, Requests: []Message{{Name: "request", Signature: "uuu"}}, Events: []Message{{Name: "event", Signature: "uuu"}}}
95 d, r := hotDisplay(t, iface, hotHandler)
96 var post, dispatchAllocs float64
97 if !d.Do(func() {
98 post = testing.AllocsPerRun(100, func() { r.PostEvent(0, Uint(1), Uint(2), Uint(3)) })
99 a := [3]Arg{Uint(1), Uint(2), Uint(3)}
100 dispatchAllocs = testing.AllocsPerRun(100, func() { dispatch(0, r.c, 0, requestMessage(iface), unsafe.Pointer(&a[0])) })
101 }) {
102 t.Fatal("display stopped")
103 }
104 if post != 0 || dispatchAllocs != 0 {
105 t.Fatalf("allocs: PostEvent=%g dispatch=%g, want zero", post, dispatchAllocs)
106 }
107}
108
109func BenchmarkDispatch(b *testing.B) {
110 iface := &Interface{Name: "hot_dispatch_bench", Version: 1, Requests: []Message{{Name: "request", Signature: "uuu"}}}
111 d, r := hotDisplay(b, iface, hotHandler)
112 a := [3]Arg{Uint(1), Uint(2), Uint(3)}
113 if !d.Do(func() {
114 b.ReportAllocs()
115 b.ResetTimer()
116 for i := 0; i < b.N; i++ {
117 dispatch(0, r.c, 0, requestMessage(iface), unsafe.Pointer(&a[0]))
118 }
119 b.StopTimer()
120 }) {
121 b.Fatal("display stopped")
122 }
123}
124func BenchmarkPostEvent(b *testing.B) {
125 iface := &Interface{Name: "hot_event_bench", Version: 1, Events: []Message{{Name: "event", Signature: "uuu"}}}
126 d, r := hotDisplay(b, iface, nil)
127 if !d.Do(func() {
128 b.ReportAllocs()
129 b.ResetTimer()
130 for i := 0; i < b.N; i++ {
131 r.PostEvent(0, Uint(1), Uint(2), Uint(3))
132 }
133 b.StopTimer()
134 }) {
135 b.Fatal("display stopped")
136 }
137}
138
139func TestClientCredentials(t *testing.T) {
140 iface := &Interface{Name: "cred_test", Version: 1, Requests: []Message{{Name: "request", Signature: "uuu"}}, Events: []Message{{Name: "event", Signature: "uuu"}}}
141 d, r := hotDisplay(t, iface, hotHandler)
142 var (
143 got, zero Credentials
144 err, zerr error
145 allocs float64
146 pid int
147 )
148 c := r.Client()
149 if !d.Do(func() {
150 got, err = c.Credentials()
151 zero, zerr = Client{}.Credentials()
152 pid = c.PID()
153 allocs = testing.AllocsPerRun(100, func() { _, _ = c.Credentials() })
154 }) {
155 t.Fatal("display stopped")
156 }
157 if err != nil {
158 t.Fatal(err)
159 }
160 if got.PID != os.Getpid() || got.UID != uint32(os.Geteuid()) || got.GID != uint32(os.Getegid()) || pid != got.PID {
161 t.Fatalf("credentials %+v pid %d, want pid=%d uid=%d gid=%d", got, pid, os.Getpid(), os.Geteuid(), os.Getegid())
162 }
163 if zerr == nil || zero != (Credentials{}) || (Client{}).PID() != 0 {
164 t.Fatalf("invalid client: %+v, %v", zero, zerr)
165 }
166 if allocs != 0 {
167 t.Fatalf("Credentials allocs = %g, want 0", allocs)
168 }
169}