Commit65911679Recorded4 Apr 2026Repositorysigil-ffi

Skip FFI tests gracefully when dlopen is unavailable

Message

On static musl builds, dlopen is stubbed out and c-library calls fail with "Dynamic loading not supported". Previously this caused LOAD ERRORs that counted as test failures, creating noise in the test results.

test-ffi.sgl: Probe for dlopen support at load time and only register dlopen-dependent tests when available. Non-dlopen tests (type constants, pointer ops, struct layouts, bytevector bridge) now always run.

test-ffi-dlopen.sgl: Skip entirely with a single skip marker when dlopen is unavailable, since all tests in this file require it.

Changed
 test/test-ffi-dlopen.sgl |  19 +++++++++
 test/test-ffi.sgl        | 372 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------------------------------------------------------------
 2 files changed, 211 insertions(+), 180 deletions(-)
Diff
test/test-ffi-dlopen.sglmodified
@@ -6,6 +6,9 @@
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.
12
13
(import (sigil core)
14
(sigil test)
@@ -15,6 +18,20 @@
18
(sigil fs)
19
(sigil string))
20
+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)))
+27
+28
(unless has-dlopen?
+29
(test-skip "dlopen not available — skipping all dlopen tests"
+30
(assert-true #f))
+31
(run-tests))
+32
+33
(when has-dlopen?
+34
35
;; ============================================================
36
;; Build the test shared library
37
;; ============================================================
@@ -172,3 +189,5 @@
189
(delete-file test-lib-so))
190
191
(run-tests)
+192
+193
) ;; end (when has-dlopen? ...)
test/test-ffi.sglmodified
@@ -1,11 +1,22 @@
1
;;; Tests for (sigil ffi)
+2
;;;
+3
;;; These tests require dynamic loading (dlopen) support. On static musl
+4
;;; builds, dlopen is stubbed out and c-library will fail. We detect this
+5
;;; at load time and skip all tests gracefully.
6
7
(import (sigil core)
8
(sigil test)
9
(sigil ffi))
10
+11
;; Probe whether dynamic loading works on this platform.
+12
(define has-dlopen?
+13
(guard (exn (#t #f))
+14
(let ((lib (c-library #f)))
+15
(c-library-close lib)
+16
#t)))
+17
18
;; ============================================================
8
;; Type Constants
+19
;; Type Constants (these don't need dlopen)
20
;; ============================================================
21
22
(test-group "FFI type constants"
@@ -32,69 +43,7 @@
43
(assert-equal 1 (c-alignof ffi/int8))))
44
45
;; ============================================================
35
;; Library Loading
36
;; ============================================================
37
38
(test-group "Library loading"
39
(test "load current process"
40
(let ((libc (c-library #f)))
41
(assert-true (c-library? libc))))
42
43
(test "bad library name raises error"
44
(assert-error (c-library "libnonexistent_12345"))))
45
46
;; ============================================================
47
;; Math Functions (via current process)
48
;; ============================================================
49
50
;; On Linux/glibc, math functions are available in the current process.
51
;; On other platforms, load libm explicitly.
52
(test-group "Math functions"
53
(define libc (c-library #f))
54
55
(test "sqrt (double -> double)"
56
(let ((sqrt-fn (c-function libc "sqrt" (list ffi/double) ffi/double)))
57
(assert-equal 3.0 (sqrt-fn 9.0))
58
(assert-equal 12.0 (sqrt-fn 144.0))
59
(assert-equal 0.0 (sqrt-fn 0.0))))
60
61
(test "pow (double double -> double)"
62
(let ((pow-fn (c-function libc "pow" (list ffi/double ffi/double) ffi/double)))
63
(assert-equal 8.0 (pow-fn 2.0 3.0))
64
(assert-equal 1024.0 (pow-fn 2.0 10.0))))
65
66
(test "floor and ceil (double -> double)"
67
(let ((floor-fn (c-function libc "floor" (list ffi/double) ffi/double))
68
(ceil-fn (c-function libc "ceil" (list ffi/double) ffi/double)))
69
(assert-equal 3.0 (floor-fn 3.7))
70
(assert-equal 4.0 (ceil-fn 3.2))))
71
72
(test "fabs (double -> double)"
73
(let ((fabs-fn (c-function libc "fabs" (list ffi/double) ffi/double)))
74
(assert-equal 5.0 (fabs-fn -5.0))
75
(assert-equal 0.0 (fabs-fn 0.0)))))
76
77
;; ============================================================
78
;; Integer Functions (libc)
79
;; ============================================================
80
81
(test-group "libc integer functions"
82
(define libc (c-library #f))
83
84
(test "abs (int -> int)"
85
(let ((c-abs (c-function libc "abs" (list ffi/int) ffi/int)))
86
(assert-equal 5 (c-abs -5))
87
(assert-equal 0 (c-abs 0))
88
(assert-equal 42 (c-abs 42))))
89
90
(test "strlen (string -> size_t)"
91
(let ((c-strlen (c-function libc "strlen" (list ffi/string) ffi/size-t)))
92
(assert-equal 5 (c-strlen "hello"))
93
(assert-equal 0 (c-strlen ""))
94
(assert-equal 11 (c-strlen "hello world")))))
95
96
;; ============================================================
97
;; Pointer Operations
+46
;; Pointer Operations (don't need dlopen)
47
;; ============================================================
48
49
(test-group "Pointer operations"
@@ -142,7 +91,7 @@
91
(c-free ptr))))
92
93
;; ============================================================
145
;; String Marshaling
+94
;; String Marshaling (don't need dlopen for basic ops)
95
;; ============================================================
96
97
(test-group "String marshaling"
@@ -157,15 +106,10 @@
106
(str (pointer->string ptr 11)))
107
;; With explicit length, we read past the embedded null
108
(assert-equal 11 (string-length str))
160
(c-free ptr)))
161
162
(test "ffi/string auto-marshaling"
163
(let* ((libc (c-library #f))
164
(c-strlen (c-function libc "strlen" (list ffi/string) ffi/size-t)))
165
(assert-equal 3 (c-strlen "abc")))))
+109
(c-free ptr))))
110
111
;; ============================================================
168
;; Struct Layouts
+112
;; Struct Layouts (don't need dlopen)
113
;; ============================================================
114
115
(test-group "Struct layouts"
@@ -229,23 +173,7 @@
173
(assert-equal 42.0 result))))
174
175
;; ============================================================
232
;; define-c-library Macro
233
;; ============================================================
234
235
;; define-c-library must be used at top level (not inside test-group)
236
(define-c-library mathlib #f
237
((math-sqrt "sqrt") (ffi/double) -> ffi/double)
238
((math-pow "pow") (ffi/double ffi/double) -> ffi/double))
239
240
(test-group "define-c-library"
241
(test "sqrt via define-c-library"
242
(assert-equal 5.0 (math-sqrt 25.0)))
243
244
(test "pow via define-c-library"
245
(assert-equal 1024.0 (math-pow 2.0 10.0))))
246
247
;; ============================================================
248
;; Bytevector Bridge
+176
;; Bytevector Bridge (don't need dlopen)
177
;; ============================================================
178
179
(test-group "Bytevector bridge"
@@ -280,100 +208,184 @@
208
(assert-true (integer? (c-errno))))
209
210
(test "c-strerror returns string"
283
(assert-true (string? (c-strerror 2)))) ; ENOENT = 2
+211
(assert-true (string? (c-strerror 2))))) ; ENOENT = 2
212
285
(test "c-symbol raises error on missing symbol"
286
(let ((libc (c-library #f)))
287
(assert-error (c-symbol libc "nonexistent_symbol_12345"))))
+213
;; ============================================================
+214
;; Tests below require dynamic loading (dlopen)
+215
;; ============================================================
216
289
(test "c-symbol? returns #f for missing symbol"
290
(let ((libc (c-library #f)))
291
(assert-true (not (c-symbol? libc "nonexistent_symbol_12345"))))))
+217
(when has-dlopen?
+218
+219
(test-group "Library loading"
+220
(test "load current process"
+221
(let ((libc (c-library #f)))
+222
(assert-true (c-library? libc))))
+223
+224
(test "bad library name raises error"
+225
(assert-error (c-library "libnonexistent_12345"))))
+226
+227
;; On Linux/glibc, math functions are available in the current process.
+228
(test-group "Math functions"
+229
(define libc (c-library #f))
+230
+231
(test "sqrt (double -> double)"
+232
(let ((sqrt-fn (c-function libc "sqrt" (list ffi/double) ffi/double)))
+233
(assert-equal 3.0 (sqrt-fn 9.0))
+234
(assert-equal 12.0 (sqrt-fn 144.0))
+235
(assert-equal 0.0 (sqrt-fn 0.0))))
+236
+237
(test "pow (double double -> double)"
+238
(let ((pow-fn (c-function libc "pow" (list ffi/double ffi/double) ffi/double)))
+239
(assert-equal 8.0 (pow-fn 2.0 3.0))
+240
(assert-equal 1024.0 (pow-fn 2.0 10.0))))
+241
+242
(test "floor and ceil (double -> double)"
+243
(let ((floor-fn (c-function libc "floor" (list ffi/double) ffi/double))
+244
(ceil-fn (c-function libc "ceil" (list ffi/double) ffi/double)))
+245
(assert-equal 3.0 (floor-fn 3.7))
+246
(assert-equal 4.0 (ceil-fn 3.2))))
+247
+248
(test "fabs (double -> double)"
+249
(let ((fabs-fn (c-function libc "fabs" (list ffi/double) ffi/double)))
+250
(assert-equal 5.0 (fabs-fn -5.0))
+251
(assert-equal 0.0 (fabs-fn 0.0)))))
+252
+253
(test-group "libc integer functions"
+254
(define libc (c-library #f))
+255
+256
(test "abs (int -> int)"
+257
(let ((c-abs (c-function libc "abs" (list ffi/int) ffi/int)))
+258
(assert-equal 5 (c-abs -5))
+259
(assert-equal 0 (c-abs 0))
+260
(assert-equal 42 (c-abs 42))))
+261
+262
(test "strlen (string -> size_t)"
+263
(let ((c-strlen (c-function libc "strlen" (list ffi/string) ffi/size-t)))
+264
(assert-equal 5 (c-strlen "hello"))
+265
(assert-equal 0 (c-strlen ""))
+266
(assert-equal 11 (c-strlen "hello world")))))
+267
+268
(test-group "String auto-marshaling"
+269
(test "ffi/string auto-marshaling"
+270
(let* ((libc (c-library #f))
+271
(c-strlen (c-function libc "strlen" (list ffi/string) ffi/size-t)))
+272
(assert-equal 3 (c-strlen "abc")))))
+273
+274
(test-group "Symbol lookup"
+275
(test "c-symbol raises error on missing symbol"
+276
(let ((libc (c-library #f)))
+277
(assert-error (c-symbol libc "nonexistent_symbol_12345"))))
+278
+279
(test "c-symbol? returns #f for missing symbol"
+280
(let ((libc (c-library #f)))
+281
(assert-true (not (c-symbol? libc "nonexistent_symbol_12345"))))))
+282
+283
;; define-c-library must be used at top level — move to conditional block
+284
;; Note: define-c-library expands to top-level defines, so we test it
+285
;; only when dlopen is available.
+286
)
+287
+288
;; define-c-library at top level, guarded
+289
(when has-dlopen?
+290
(define-c-library mathlib #f
+291
((math-sqrt "sqrt") (ffi/double) -> ffi/double)
+292
((math-pow "pow") (ffi/double ffi/double) -> ffi/double)))
+293
+294
(when has-dlopen?
+295
(test-group "define-c-library"
+296
(test "sqrt via define-c-library"
+297
(assert-equal 5.0 (math-sqrt 25.0)))
+298
+299
(test "pow via define-c-library"
+300
(assert-equal 1024.0 (math-pow 2.0 10.0)))))
301
302
;; ============================================================
294
;; Callbacks (C → Scheme)
+303
;; Callbacks (require dlopen for qsort test)
304
;; ============================================================
305
297
(test-group "Callbacks"
298
(define libc (c-library #f))
299
300
(test "qsort with integer comparison callback"
301
(let* ((qsort-fn (c-function libc "qsort"
302
(list ffi/pointer ffi/size-t ffi/size-t ffi/pointer)
303
ffi/void))
304
(cmp (c-callback (list ffi/pointer ffi/pointer) ffi/int
305
(lambda (a b)
306
(- (pointer-ref a ffi/int32 0)
307
(pointer-ref b ffi/int32 0)))))
308
(buf (c-alloc 20)))
309
;; Fill with unsorted integers
310
(pointer-set! buf ffi/int32 0 5)
311
(pointer-set! buf ffi/int32 4 3)
312
(pointer-set! buf ffi/int32 8 1)
313
(pointer-set! buf ffi/int32 12 4)
314
(pointer-set! buf ffi/int32 16 2)
315
;; Sort
316
(qsort-fn buf 5 4 cmp)
317
;; Verify sorted order
318
(assert-equal 1 (pointer-ref buf ffi/int32 0))
319
(assert-equal 2 (pointer-ref buf ffi/int32 4))
320
(assert-equal 3 (pointer-ref buf ffi/int32 8))
321
(assert-equal 4 (pointer-ref buf ffi/int32 12))
322
(assert-equal 5 (pointer-ref buf ffi/int32 16))
323
;; Cleanup
324
(c-callback-release cmp)
325
(c-free buf)))
326
327
(test "qsort descending order"
328
(let* ((qsort-fn (c-function libc "qsort"
329
(list ffi/pointer ffi/size-t ffi/size-t ffi/pointer)
330
ffi/void))
331
(cmp-desc (c-callback (list ffi/pointer ffi/pointer) ffi/int
332
(lambda (a b)
333
(- (pointer-ref b ffi/int32 0)
334
(pointer-ref a ffi/int32 0)))))
335
(buf (c-alloc 12)))
336
(pointer-set! buf ffi/int32 0 1)
337
(pointer-set! buf ffi/int32 4 3)
338
(pointer-set! buf ffi/int32 8 2)
339
(qsort-fn buf 3 4 cmp-desc)
340
(assert-equal 3 (pointer-ref buf ffi/int32 0))
341
(assert-equal 2 (pointer-ref buf ffi/int32 4))
342
(assert-equal 1 (pointer-ref buf ffi/int32 8))
343
(c-callback-release cmp-desc)
344
(c-free buf)))
345
346
(test "callback-release frees the slot"
347
(let ((cb (c-callback (list ffi/pointer ffi/pointer) ffi/int
348
(lambda (a b) 0))))
349
(assert-true (pointer? cb))
350
(c-callback-release cb)))
351
352
(test "multiple concurrent callbacks"
353
(let* ((cb1 (c-callback (list ffi/pointer ffi/pointer) ffi/int
354
(lambda (a b)
355
(- (pointer-ref a ffi/int32 0)
356
(pointer-ref b ffi/int32 0)))))
357
(cb2 (c-callback (list ffi/pointer ffi/pointer) ffi/int
358
(lambda (a b)
359
(- (pointer-ref b ffi/int32 0)
360
(pointer-ref a ffi/int32 0))))))
361
(assert-true (pointer? cb1))
362
(assert-true (pointer? cb2))
363
;; Different function pointers
364
(assert-true (not (= (pointer-address cb1) (pointer-address cb2))))
365
(c-callback-release cb1)
366
(c-callback-release cb2)))
367
368
(test "void callback (thunk)"
369
(let* ((called #f)
370
(cb (c-callback '() ffi/void
371
(lambda () (set! called #t)))))
372
(assert-true (pointer? cb))
373
(c-callback-release cb)))
374
375
(test "double callback creates successfully"
376
(let ((cb (c-callback (list ffi/double ffi/double) ffi/double
377
(lambda (a b) (+ a b)))))
378
(assert-true (pointer? cb))
379
(c-callback-release cb))))
+306
(when has-dlopen?
+307
(test-group "Callbacks"
+308
(define libc (c-library #f))
+309
+310
(test "qsort with integer comparison callback"
+311
(let* ((qsort-fn (c-function libc "qsort"
+312
(list ffi/pointer ffi/size-t ffi/size-t ffi/pointer)
+313
ffi/void))
+314
(cmp (c-callback (list ffi/pointer ffi/pointer) ffi/int
+315
(lambda (a b)
+316
(- (pointer-ref a ffi/int32 0)
+317
(pointer-ref b ffi/int32 0)))))
+318
(buf (c-alloc 20)))
+319
;; Fill with unsorted integers
+320
(pointer-set! buf ffi/int32 0 5)
+321
(pointer-set! buf ffi/int32 4 3)
+322
(pointer-set! buf ffi/int32 8 1)
+323
(pointer-set! buf ffi/int32 12 4)
+324
(pointer-set! buf ffi/int32 16 2)
+325
;; Sort
+326
(qsort-fn buf 5 4 cmp)
+327
;; Verify sorted order
+328
(assert-equal 1 (pointer-ref buf ffi/int32 0))
+329
(assert-equal 2 (pointer-ref buf ffi/int32 4))
+330
(assert-equal 3 (pointer-ref buf ffi/int32 8))
+331
(assert-equal 4 (pointer-ref buf ffi/int32 12))
+332
(assert-equal 5 (pointer-ref buf ffi/int32 16))
+333
;; Cleanup
+334
(c-callback-release cmp)
+335
(c-free buf)))
+336
+337
(test "qsort descending order"
+338
(let* ((qsort-fn (c-function libc "qsort"
+339
(list ffi/pointer ffi/size-t ffi/size-t ffi/pointer)
+340
ffi/void))
+341
(cmp-desc (c-callback (list ffi/pointer ffi/pointer) ffi/int
+342
(lambda (a b)
+343
(- (pointer-ref b ffi/int32 0)
+344
(pointer-ref a ffi/int32 0)))))
+345
(buf (c-alloc 12)))
+346
(pointer-set! buf ffi/int32 0 1)
+347
(pointer-set! buf ffi/int32 4 3)
+348
(pointer-set! buf ffi/int32 8 2)
+349
(qsort-fn buf 3 4 cmp-desc)
+350
(assert-equal 3 (pointer-ref buf ffi/int32 0))
+351
(assert-equal 2 (pointer-ref buf ffi/int32 4))
+352
(assert-equal 1 (pointer-ref buf ffi/int32 8))
+353
(c-callback-release cmp-desc)
+354
(c-free buf)))
+355
+356
(test "callback-release frees the slot"
+357
(let ((cb (c-callback (list ffi/pointer ffi/pointer) ffi/int
+358
(lambda (a b) 0))))
+359
(assert-true (pointer? cb))
+360
(c-callback-release cb)))
+361
+362
(test "multiple concurrent callbacks"
+363
(let* ((cb1 (c-callback (list ffi/pointer ffi/pointer) ffi/int
+364
(lambda (a b)
+365
(- (pointer-ref a ffi/int32 0)
+366
(pointer-ref b ffi/int32 0)))))
+367
(cb2 (c-callback (list ffi/pointer ffi/pointer) ffi/int
+368
(lambda (a b)
+369
(- (pointer-ref b ffi/int32 0)
+370
(pointer-ref a ffi/int32 0))))))
+371
(assert-true (pointer? cb1))
+372
(assert-true (pointer? cb2))
+373
;; Different function pointers
+374
(assert-true (not (= (pointer-address cb1) (pointer-address cb2))))
+375
(c-callback-release cb1)
+376
(c-callback-release cb2)))
+377
+378
(test "void callback (thunk)"
+379
(let* ((called #f)
+380
(cb (c-callback '() ffi/void
+381
(lambda () (set! called #t)))))
+382
(assert-true (pointer? cb))
+383
(c-callback-release cb)))
+384
+385
(test "double callback creates successfully"
+386
(let ((cb (c-callback (list ffi/double ffi/double) ffi/double
+387
(lambda (a b) (+ a b)))))
+388
(assert-true (pointer? cb))
+389
(c-callback-release cb)))))
+390
+391
(run-tests)