AtlatestRepositorysigil-ffi
1
;;; FFI dlopen smoke tests2
;;;3
;;; Tests the full dynamic FFI path: compile a shared library, load it4
;;; via c-library (dlopen), look up symbols, and call functions with5
;;; various signatures including struct-by-value and callbacks.6
;;;7
;;; This test is critical for CI — it verifies that dyncall and dlopen8
;;; work correctly on each supported platform.9
;;;10
;;; On static musl builds, dlopen is not available. The test detects11
;;; 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 library37
;; ============================================================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
(cond49
((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 Marshaling66
;; ============================================================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 Marshaling82
;; ============================================================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 dlopen98
;; ============================================================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 dlopen113
;; ============================================================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 int32s122
(fill buf 5 10) ; fills with 10, 11, 12, 13, 14123
(assert-equal 60 (sum buf 5))124
;; Verify individual values125
(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-layout135
(c-struct-layout136
x: ffi/double137
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 = 25152
(assert-equal 25.0 (dist #{ x: 0.0 y: 0.0 }153
#{ x: 3.0 y: 4.0 })))))155
;; ============================================================156
;; Callbacks via dlopen157
;; ============================================================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/int164
(lambda (a b) (+ a b))))165
(mul-cb (c-callback (list ffi/int ffi/int) ffi/int166
(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 dlopen174
;; ============================================================176
(define-c-library test-math test-lib-so177
(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 library188
(when (file-exists? test-lib-so)189
(delete-file test-lib-so))191
(run-tests)193
) ;; end (when has-dlopen? ...)