1//go:build linux
2
3package syncobj
4
5import (
6 "errors"
7 "os"
8 "testing"
9 "unsafe"
10
11 "golang.org/x/sys/unix"
12)
13
14func TestUAPILayouts(t *testing.T) {
15 for _, tc := range []struct {
16 name string
17 got, want uintptr
18 }{
19 {"create", unsafe.Sizeof(createArg{}), 8},
20 {"destroy", unsafe.Sizeof(destroyArg{}), 8},
21 {"handle", unsafe.Sizeof(handleArg{}), 24},
22 {"transfer", unsafe.Sizeof(transferArg{}), 32},
23 {"wait", unsafe.Sizeof(timelineWaitArg{}), 48},
24 {"query", unsafe.Sizeof(queryArg{}), 24},
25 } {
26 if tc.got != tc.want {
27 t.Errorf("%s size: got %d want %d", tc.name, tc.got, tc.want)
28 }
29 }
30}
31
32func TestRenderNodeIoctls(t *testing.T) {
33 path := os.Getenv("NEFERGUI_RENDER_NODE")
34 if path == "" {
35 t.Skip("opt in with NEFERGUI_RENDER_NODE=/dev/dri/renderD... (matching feedback main_device)")
36 }
37 n, err := Open(path)
38 if err != nil {
39 t.Fatal(err)
40 }
41 defer n.Close()
42 acquire, err := n.Create()
43 if err != nil {
44 t.Fatalf("SYNCOBJ_CREATE acquire: %v", err)
45 }
46 defer n.Destroy(acquire)
47 release, err := n.Create()
48 if err != nil {
49 t.Fatalf("SYNCOBJ_CREATE release: %v", err)
50 }
51 defer n.Destroy(release)
52 fd, err := n.ExportTimeline(acquire)
53 if err != nil {
54 t.Fatalf("HANDLE_TO_FD timeline: %v", err)
55 }
56 defer unix.Close(fd)
57 // Import as another handle to prove this FD is a DRM syncobj, not a
58 // Vulkan OPAQUE_FD. FD_TO_HANDLE without IMPORT_SYNC_FILE does not consume fd.
59 a := handleArg{FD: int32(fd)}
60 if err = n.ioctl(ioctlFDToHandle, unsafe.Pointer(&a)); err != nil {
61 t.Fatalf("FD_TO_HANDLE timeline: %v", err)
62 }
63 defer n.Destroy(a.Handle)
64 if a.Handle == 0 {
65 t.Fatal("zero imported handle")
66 }
67 before, err := n.Query(release)
68 if err != nil {
69 t.Fatalf("QUERY release: %v", err)
70 }
71 t.Logf("render node %s: CREATE=%d,%d HANDLE_TO_FD=%d FD_TO_HANDLE=%d QUERY=%d", path, acquire, release, fd, a.Handle, before)
72 // An unsignaled point cannot be used for image recycling. TIMELINE_WAIT
73 // must time out without a CPU fence fallback.
74 ready, err := n.Signaled(release, 1)
75 if err != nil {
76 t.Fatalf("TIMELINE_WAIT: %v", err)
77 }
78 if ready {
79 t.Fatal("unsignaled point reported ready")
80 }
81 // TRANSFER may be rejected for a point not yet submitted. A valid
82 // SYNC_FD import is exercised by an actual Vulkan submission test.
83 if err = n.Transfer(acquire, 1, release, 2); err != nil {
84 t.Logf("TRANSFER unsubmitted point (expected kernel-dependent rejection): %v", err)
85 } else {
86 t.Log("TRANSFER accepted unsubmitted point")
87 }
88}
89
90func TestFailedImportRetainsFD(t *testing.T) {
91 // No render node needed: EBADF leaves the caller's descriptor owned.
92 fd, err := unix.Eventfd(0, unix.EFD_CLOEXEC)
93 if err != nil {
94 t.Fatal(err)
95 }
96 defer unix.Close(fd)
97 n := &Node{fd: -1}
98 if _, err = n.ImportFence(fd); !errors.Is(err, unix.EBADF) {
99 t.Fatalf("want EBADF, got %v", err)
100 }
101 if _, err = unix.FcntlInt(uintptr(fd), unix.F_GETFD, 0); err != nil {
102 t.Fatalf("lost caller FD after failed import: %v", err)
103 }
104}