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)