1//go:build linux
2
3package syncobj
4
5import (
6 "errors"
7 "os"
8 "strings"
9 "testing"
10 "time"
11
12 "golang.org/x/sys/unix"
13)
14
15func TestWaitResultMapping(t *testing.T) {
16 if ok, err := waitResult(0); !ok || err != nil {
17 t.Fatalf("success: got %v, %v", ok, err)
18 }
19 for _, e := range []unix.Errno{unix.ETIME, unix.EBUSY} {
20 if ok, err := waitResult(e); ok || err != nil {
21 t.Fatalf("%v: want (false,nil), got %v, %v", e, ok, err)
22 }
23 }
24 for _, e := range []unix.Errno{unix.EINTR, unix.EBADF, unix.EINVAL, unix.ENOENT, unix.ENOTTY, unix.EFAULT} {
25 ok, err := waitResult(e)
26 if ok || err == nil {
27 t.Fatalf("%v: want error, got %v, %v", e, ok, err)
28 }
29 if !errors.Is(err, e) {
30 t.Errorf("%v: error does not wrap errno: %v", e, err)
31 }
32 if !strings.Contains(err.Error(), "DRM syncobj ioctl 0xc03064ca") {
33 t.Errorf("%v: lost request context: %v", e, err)
34 }
35 }
36}
37
38func TestWaitResultIdleDoesNotAllocate(t *testing.T) {
39 for _, e := range []unix.Errno{unix.ETIME, unix.EBUSY} {
40 if n := testing.AllocsPerRun(1000, func() { _, _ = waitResult(e) }); n != 0 {
41 t.Errorf("%v: %v allocs per idle result, want 0", e, n)
42 }
43 }
44}
45
46func TestWaitPointBadFDKeepsRealError(t *testing.T) {
47 for _, fd := range []int{-1, 1 << 30} {
48 n := &Node{fd: fd}
49 ok, err := n.WaitPoint(1, 1, 20*time.Millisecond)
50 if ok || !errors.Is(err, unix.EBADF) {
51 t.Fatalf("fd %d: want (false, EBADF), got %v, %v", fd, ok, err)
52 }
53 if !strings.Contains(err.Error(), "DRM syncobj ioctl") {
54 t.Errorf("fd %d: lost context: %v", fd, err)
55 }
56 }
57 // Closed Node (fd -1 after Close) must also surface EBADF.
58 n := &Node{fd: -1}
59 if _, err := n.Signaled(1, 1); !errors.Is(err, unix.EBADF) {
60 t.Fatalf("Signaled on closed node: %v", err)
61 }
62}
63
64func TestWaitPointNonDRMFDIsNotSwallowed(t *testing.T) {
65 fd, err := unix.Eventfd(0, unix.EFD_CLOEXEC)
66 if err != nil {
67 t.Fatal(err)
68 }
69 defer unix.Close(fd)
70 n := &Node{fd: fd}
71 if ok, err := n.WaitPoint(1, 1, 0); ok || !errors.Is(err, unix.ENOTTY) {
72 t.Fatalf("want (false, ENOTTY), got %v, %v", ok, err)
73 }
74}
75
76func TestWaitPointTimeoutBounds(t *testing.T) {
77 n := &Node{fd: -1}
78 for _, d := range []time.Duration{-1, time.Second + 1} {
79 if _, err := n.WaitPoint(1, 1, d); err == nil || errors.Is(err, unix.EBADF) {
80 t.Errorf("timeout %s: want bounds error before ioctl, got %v", d, err)
81 }
82 }
83 // Boundaries stay accepted (fail later at the ioctl with EBADF).
84 for _, d := range []time.Duration{0, time.Second} {
85 if _, err := n.WaitPoint(1, 1, d); !errors.Is(err, unix.EBADF) {
86 t.Errorf("timeout %s: want EBADF, got %v", d, err)
87 }
88 }
89}
90
91// openBenchNode opts in to a real render node, like the other hardware tests.
92func openBenchNode(tb testing.TB) *Node {
93 path := os.Getenv("NEFERGUI_RENDER_NODE")
94 if path == "" {
95 tb.Skip("set NEFERGUI_RENDER_NODE=/dev/dri/renderD... for real idle-wait measurement")
96 }
97 n, err := Open(path)
98 if err != nil {
99 tb.Fatal(err)
100 }
101 tb.Cleanup(func() { _ = n.Close() })
102 return n
103}
104
105func TestWaitPointIdleTimeoutRealNodeNoAllocs(t *testing.T) {
106 n := openBenchNode(t)
107 h, err := n.Create()
108 if err != nil {
109 t.Fatal(err)
110 }
111 defer n.Destroy(h)
112 ready, err := n.WaitPoint(h, 1, 0)
113 if ready || err != nil {
114 t.Fatalf("idle unsignaled point: got %v, %v", ready, err)
115 }
116 if a := testing.AllocsPerRun(200, func() { _, _ = n.WaitPoint(h, 1, 0) }); a != 0 {
117 t.Errorf("idle WaitPoint allocs/op = %v, want 0", a)
118 }
119}
120
121func BenchmarkWaitResultIdle(b *testing.B) {
122 b.ReportAllocs()
123 for i := 0; i < b.N; i++ {
124 _, _ = waitResult(unix.ETIME)
125 }
126}
127
128// BenchmarkWaitPointIdle measures the presentation waiter's idle path on a
129// real render node: an unsignaled point that times out (ETIME/EBUSY).
130func BenchmarkWaitPointIdle(b *testing.B) {
131 n := openBenchNode(b)
132 h, err := n.Create()
133 if err != nil {
134 b.Fatal(err)
135 }
136 defer n.Destroy(h)
137 b.ReportAllocs()
138 b.ResetTimer()
139 for i := 0; i < b.N; i++ {
140 if ready, err := n.WaitPoint(h, 1, 20*time.Millisecond); ready || err != nil {
141 b.Fatalf("got %v, %v", ready, err)
142 }
143 }
144}