AtlatestRepositorysigil-ffi

sigil-ffi / tree / testtest-ffi-dlopen.sgl

1;;; FFI dlopen smoke tests
2;;;
3;;; Tests the full dynamic FFI path: compile a shared library, load it
4;;; via c-library (dlopen), look up symbols, and call functions with
5;;; various signatures including struct-by-value and callbacks.
6;;;
7;;; This test is critical for CI — it verifies that dyncall and dlopen
8;;; work correctly on each supported platform.
9;;;
10;;; On static musl builds, dlopen is not available. The test detects
11;;; this and skips gracefully instead of failing with a LOAD ERROR.
13(import (sigil core)
14 (sigil test)
15 (sigil ffi)
16 (sigil path)
17 (sigil process)
18 (sigil fs)
19 (sigil string))
21;; Probe whether dynamic loading works on this platform.
22(define has-dlopen?
23 (guard (exn (#t #f))
24 (let ((lib (c-library #f)))
25 (c-library-close lib)
26 #t)))
28(unless has-dlopen?
29 (test-skip "dlopen not available — skipping all dlopen tests"
30 (assert-true #f))
31 (run-tests))
33(when has-dlopen?
35;; ============================================================
36;; Build the test shared library
37;; ============================================================
39(define test-dir "test")
40(define test-lib-src (path-join test-dir "ffi-test-lib.c"))
41(define test-lib-so (path-join test-dir "libffi-test.so"))
43;; Compile the shared library before tests run.
44;; Try zig cc first (sigil toolchain or PATH), then cc, then gcc.
45(define sigil-zig (path-join (or (getenv "HOME") "") ".sigil" "toolchain" "zig" "zig"))
47(define (find-c-compiler)
48 (cond
49 ((file-exists? sigil-zig) sigil-zig)
50 ((= 0 (process-run "sh" "-c" "command -v zig >/dev/null 2>&1")) "zig")
51 ((= 0 (process-run "sh" "-c" "command -v cc >/dev/null 2>&1")) "cc")
52 (else "gcc")))
54(let* ((compiler (find-c-compiler))
55 (use-zig? (string-contains? compiler "zig"))
56 (rc (if use-zig?
57 (process-run compiler "cc" "-shared" "-fPIC" "-o" test-lib-so test-lib-src)
58 (process-run compiler "-shared" "-fPIC" "-o" test-lib-so test-lib-src))))
59 (unless (= rc 0)
60 (error "Failed to compile ffi-test-lib.c")))
62(define lib (c-library test-lib-so))
64;; ============================================================
65;; Integer Marshaling
66;; ============================================================
68(test-group "dlopen: integer functions"
69 (test "add_ints (int int -> int)"
70 (let ((add (c-function lib "add_ints" (list ffi/int ffi/int) ffi/int)))
71 (assert-equal 5 (add 2 3))
72 (assert-equal 0 (add -5 5))
73 (assert-equal -10 (add -3 -7))))
75 (test "double_to_int (double -> int)"
76 (let ((d2i (c-function lib "double_to_int" (list ffi/double) ffi/int)))
77 (assert-equal 3 (d2i 3.7))
78 (assert-equal -5 (d2i -5.9)))))
80;; ============================================================
81;; Double Marshaling
82;; ============================================================
84(test-group "dlopen: double functions"
85 (test "multiply_doubles (double double -> double)"
86 (let ((mul (c-function lib "multiply_doubles"
87 (list ffi/double ffi/double) ffi/double)))
88 (assert-equal 6.0 (mul 2.0 3.0))
89 (assert-equal -15.0 (mul 5.0 -3.0))))
91 (test "int_to_double (int -> double)"
92 (let ((i2d (c-function lib "int_to_double" (list ffi/int) ffi/double)))
93 (assert-equal 25.0 (i2d 10))
94 (assert-equal -5.0 (i2d -2)))))
96;; ============================================================
97;; String Marshaling via dlopen
98;; ============================================================
100(test-group "dlopen: string functions"
101 (test "string_length (string -> int)"
102 (let ((slen (c-function lib "string_length" (list ffi/string) ffi/int)))
103 (assert-equal 5 (slen "hello"))
104 (assert-equal 0 (slen ""))
105 (assert-equal 13 (slen "hello, world!"))))
107 (test "greeting (void -> string)"
108 (let ((greet (c-function lib "greeting" '() ffi/string)))
109 (assert-equal "hello from ffi-test-lib" (greet)))))
111;; ============================================================
112;; Pointer Passing via dlopen
113;; ============================================================
115(test-group "dlopen: pointer functions"
116 (test "fill_buffer and sum_buffer"
117 (let ((fill (c-function lib "fill_buffer"
118 (list ffi/pointer ffi/int ffi/int) ffi/void))
119 (sum (c-function lib "sum_buffer"
120 (list ffi/pointer ffi/int) ffi/int))
121 (buf (c-alloc (* 5 4)))) ; 5 int32s
122 (fill buf 5 10) ; fills with 10, 11, 12, 13, 14
123 (assert-equal 60 (sum buf 5))
124 ;; Verify individual values
125 (assert-equal 10 (pointer-ref buf ffi/int32 0))
126 (assert-equal 14 (pointer-ref buf ffi/int32 16))
127 (c-free buf))))
129;; ============================================================
130;; Struct-by-Value (dyncall feature)
131;; ============================================================
133(test-group "dlopen: struct-by-value"
134 (define point-layout
135 (c-struct-layout
136 x: ffi/double
137 y: ffi/double))
139 (test "make_point returns struct by value"
140 (let ((mkpt (c-function lib "make_point"
141 (list ffi/double ffi/double)
142 point-layout)))
143 (let ((p (mkpt 3.0 4.0)))
144 (assert-equal 3.0 (dict-ref p x:))
145 (assert-equal 4.0 (dict-ref p y:)))))
147 (test "point_distance takes structs by value"
148 (let ((dist (c-function lib "point_distance"
149 (list point-layout point-layout)
150 ffi/double)))
151 ;; squared distance from (0,0) to (3,4) = 9 + 16 = 25
152 (assert-equal 25.0 (dist #{ x: 0.0 y: 0.0 }
153 #{ x: 3.0 y: 4.0 })))))
155;; ============================================================
156;; Callbacks via dlopen
157;; ============================================================
159(test-group "dlopen: callbacks"
160 (test "apply_fn calls Scheme callback"
161 (let* ((apply-fn (c-function lib "apply_fn"
162 (list ffi/pointer ffi/int ffi/int) ffi/int))
163 (add-cb (c-callback (list ffi/int ffi/int) ffi/int
164 (lambda (a b) (+ a b))))
165 (mul-cb (c-callback (list ffi/int ffi/int) ffi/int
166 (lambda (a b) (* a b)))))
167 (assert-equal 7 (apply-fn add-cb 3 4))
168 (assert-equal 12 (apply-fn mul-cb 3 4))
169 (c-callback-release add-cb)
170 (c-callback-release mul-cb))))
172;; ============================================================
173;; define-c-library with dlopen
174;; ============================================================
176(define-c-library test-math test-lib-so
177 (add-ints (ffi/int ffi/int) -> ffi/int)
178 ((mul-doubles "multiply_doubles") (ffi/double ffi/double) -> ffi/double))
180(test-group "dlopen: define-c-library"
181 (test "add-ints via macro"
182 (assert-equal 42 (add-ints 20 22)))
184 (test "mul-doubles via macro with rename"
185 (assert-equal 20.0 (mul-doubles 4.0 5.0))))
187;; Clean up the shared library
188(when (file-exists? test-lib-so)
189 (delete-file test-lib-so))
191(run-tests)
193) ;; end (when has-dlopen? ...)