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