1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
3
4package purego_test
5
6import (
7 "fmt"
8 "os"
9 "runtime"
10 "testing"
11 "unsafe"
12
13 "github.com/bnema/purego"
14 "github.com/bnema/purego/internal/load"
15)
16
17func TestOS(t *testing.T) {
18 // set and unset an environment variable since this calls into fakecgo.
19 err := os.Setenv("TESTING", "SOMETHING")
20 if err != nil {
21 t.Errorf("failed to Setenv: %s", err)
22 }
23 err = os.Unsetenv("TESTING")
24 if err != nil {
25 t.Errorf("failed to Unsetenv: %s", err)
26 }
27}
28
29func TestErrno(t *testing.T) {
30 if runtime.GOOS != "darwin" {
31 t.Skip("platform does not support returning errno from syscall")
32 }
33
34 libc, err := load.OpenLibrary("/usr/lib/libSystem.B.dylib")
35 if err != nil {
36 t.Fatal(err)
37 }
38
39 openSym, err := load.OpenSymbol(libc, "open")
40 if err != nil {
41 t.Fatal(err)
42 }
43
44 r1, _, errno := purego.SyscallN(openSym, uintptr(unsafe.Pointer(&[]byte("_file_that_does_not_exist_\x00")[0])), uintptr(os.O_RDWR))
45 if int32(r1) != -1 {
46 t.Errorf("open returned %d, wanted -1", r1)
47 }
48
49 var strerror func(int32) string
50 purego.RegisterLibFunc(&strerror, libc, "strerror")
51
52 const expected = "No such file or directory"
53 got := strerror(int32(errno))
54 if got != expected {
55 t.Errorf("strerror returned %q, wanted \"%s\"", got, expected)
56 }
57}
58
59func TestSyscallSelf(t *testing.T) {
60 // Test that SyscallSelf(fn, self, args...) produces the same result as
61 // SyscallN(fn, self, args...) for various argument counts.
62
63 // sum2 takes (self, a1) and returns self + a1
64 sum2 := purego.NewCallback(func(self, a1 uintptr) uintptr {
65 return self + a1
66 })
67 // sum4 takes (self, a1, a2, a3) and returns self + a1 + a2 + a3
68 sum4 := purego.NewCallback(func(self, a1, a2, a3 uintptr) uintptr {
69 return self + a1 + a2 + a3
70 })
71
72 tests := []struct {
73 name string
74 fn uintptr
75 self uintptr
76 args []uintptr
77 want uintptr
78 }{
79 {"no extra args", sum2, 10, []uintptr{20}, 30},
80 {"zero self", sum2, 0, []uintptr{42}, 42},
81 {"multiple args", sum4, 1, []uintptr{2, 3, 4}, 10},
82 {"no variadic args",
83 purego.NewCallback(func(self uintptr) uintptr { return self * 3 }),
84 7, nil, 21},
85 }
86
87 for _, tc := range tests {
88 t.Run(tc.name, func(t *testing.T) {
89 r1, _, _ := purego.SyscallSelf(tc.fn, tc.self, tc.args...)
90 if r1 != tc.want {
91 t.Errorf("SyscallSelf returned %d, want %d", r1, tc.want)
92 }
93
94 // Verify it matches SyscallN with manually prepended self
95 allArgs := make([]uintptr, 0, 1+len(tc.args))
96 allArgs = append(allArgs, tc.self)
97 allArgs = append(allArgs, tc.args...)
98 r1n, _, _ := purego.SyscallN(tc.fn, allArgs...)
99 if r1 != r1n {
100 t.Errorf("SyscallSelf result %d differs from SyscallN result %d", r1, r1n)
101 }
102 })
103 }
104}
105
106func TestSyscallSelfPanics(t *testing.T) {
107 t.Run("nil fn", func(t *testing.T) {
108 defer func() {
109 if r := recover(); r == nil {
110 t.Error("expected panic for nil fn")
111 }
112 }()
113 purego.SyscallSelf(0, 1)
114 })
115
116 t.Run("too many args", func(t *testing.T) {
117 defer func() {
118 r := recover()
119 if r == nil {
120 t.Error("expected panic for too many args")
121 }
122 msg := fmt.Sprint(r)
123 if msg != "purego: too many arguments to SyscallSelf" {
124 t.Errorf("unexpected panic message: %s", msg)
125 }
126 }()
127 // Create a valid callback so we don't panic on nil fn
128 fn := purego.NewCallback(func(uintptr) uintptr { return 0 })
129 // Pass more args than maxArgs-1 allows (maxArgs is 15 on 64-bit)
130 args := make([]uintptr, 15)
131 purego.SyscallSelf(fn, 1, args...)
132 })
133}