AtlatestRepositorysigil-ffi
1
;;; Tests for (sigil ffi)2
;;;3
;;; These tests require dynamic loading (dlopen) support. On static musl4
;;; builds, dlopen is stubbed out and c-library will fail. We detect this5
;;; at load time and skip all tests gracefully.7
(import (sigil core)8
(sigil test)9
(sigil ffi))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)))18
;; ============================================================19
;; Type Constants (these don't need dlopen)20
;; ============================================================22
(test-group "FFI type constants"23
(test "type constants are integers"24
(assert-true (integer? ffi/void))25
(assert-true (integer? ffi/int32))26
(assert-true (integer? ffi/double))27
(assert-true (integer? ffi/pointer))28
(assert-true (integer? ffi/string))29
(assert-true (integer? ffi/size-t)))31
(test "c-sizeof returns correct sizes"32
(assert-equal 8 (c-sizeof ffi/double))33
(assert-equal 4 (c-sizeof ffi/float))34
(assert-equal 4 (c-sizeof ffi/int32))35
(assert-equal 2 (c-sizeof ffi/int16))36
(assert-equal 1 (c-sizeof ffi/int8))37
(assert-equal 8 (c-sizeof ffi/int64)))39
(test "c-alignof returns correct alignments"40
(assert-equal 8 (c-alignof ffi/double))41
(assert-equal 4 (c-alignof ffi/float))42
(assert-equal 4 (c-alignof ffi/int32))43
(assert-equal 1 (c-alignof ffi/int8))))45
;; ============================================================46
;; Pointer Operations (don't need dlopen)47
;; ============================================================49
(test-group "Pointer operations"50
(test "null pointer"51
(assert-true (null-pointer? #f))52
(assert-true (not (null-pointer? (make-pointer 42)))))54
(test "make-pointer and pointer-address"55
(let ((ptr (make-pointer 12345)))56
(assert-true (pointer? ptr))57
(assert-equal 12345 (pointer-address ptr))))59
(test "pointer+"60
(let ((ptr (make-pointer 1000)))61
(assert-equal 1008 (pointer-address (pointer+ ptr 8)))))63
(test "c-alloc and pointer-ref/set!"64
(let ((ptr (c-alloc 32)))65
(assert-true (pointer? ptr))66
(assert-true (not (null-pointer? ptr)))68
;; Write and read int3269
(pointer-set! ptr ffi/int32 0 42)70
(assert-equal 42 (pointer-ref ptr ffi/int32 0))72
;; Write and read double73
(pointer-set! ptr ffi/double 8 3.14)74
(let ((val (pointer-ref ptr ffi/double 8)))75
(assert-true (< (abs (- val 3.14)) 0.001)))77
;; Write and read int878
(pointer-set! ptr ffi/int8 16 -127)79
(assert-equal -127 (pointer-ref ptr ffi/int8 16))81
;; Write and read uint1682
(pointer-set! ptr ffi/uint16 18 65535)83
(assert-equal 65535 (pointer-ref ptr ffi/uint16 18))85
(c-free ptr)))87
(test "c-alloc zeroes memory"88
(let ((ptr (c-alloc 64)))89
(assert-true (pointer? ptr))90
(assert-equal 0 (pointer-ref ptr ffi/int32 0))91
(c-free ptr))))93
;; ============================================================94
;; String Marshaling (don't need dlopen for basic ops)95
;; ============================================================97
(test-group "String marshaling"98
(test "string->pointer and pointer->string"99
(let* ((ptr (string->pointer "hello world"))100
(str (pointer->string ptr)))101
(assert-equal "hello world" str)102
(c-free ptr)))104
(test "string->pointer with explicit length"105
(let* ((ptr (string->pointer "hello\x00;world"))106
(str (pointer->string ptr 11)))107
;; With explicit length, we read past the embedded null108
(assert-equal 11 (string-length str))109
(c-free ptr))))111
;; ============================================================112
;; Struct Layouts (don't need dlopen)113
;; ============================================================115
(test-group "Struct layouts"116
(define point-layout117
(c-struct-layout118
x: ffi/double119
y: ffi/double120
z: ffi/double))122
(test "struct size"123
(assert-equal 24 (c-struct-size point-layout)))125
(test "struct offsets"126
(assert-equal 0 (c-struct-offset point-layout x:))127
(assert-equal 8 (c-struct-offset point-layout y:))128
(assert-equal 16 (c-struct-offset point-layout z:)))130
(test "struct ref and set"131
(let ((ptr (c-alloc-struct point-layout)))132
(c-struct-set! ptr point-layout x: 1.5)133
(c-struct-set! ptr point-layout y: 2.5)134
(c-struct-set! ptr point-layout z: 3.5)135
(assert-equal 1.5 (c-struct-ref ptr point-layout x:))136
(assert-equal 2.5 (c-struct-ref ptr point-layout y:))137
(assert-equal 3.5 (c-struct-ref ptr point-layout z:))138
(c-free ptr)))140
(test "struct->dict and dict->struct"141
(let* ((d (dict x: 10.0 y: 20.0 z: 30.0))142
(ptr (dict->c-struct d point-layout))143
(d2 (c-struct->dict ptr point-layout)))144
(assert-equal 10.0 (dict-ref d2 x:))145
(assert-equal 20.0 (dict-ref d2 y:))146
(assert-equal 30.0 (dict-ref d2 z:))147
(c-free ptr)))149
(test "struct with mixed types"150
(let ((layout (c-struct-layout151
id: ffi/int32152
value: ffi/double153
flags: ffi/uint8)))154
(assert-equal 0 (c-struct-offset layout id:))155
(assert-equal 8 (c-struct-offset layout value:))156
(assert-equal 16 (c-struct-offset layout flags:))158
(let ((ptr (c-alloc-struct layout)))159
(c-struct-set! ptr layout id: 42)160
(c-struct-set! ptr layout value: 3.14)161
(c-struct-set! ptr layout flags: 255)162
(assert-equal 42 (c-struct-ref ptr layout id:))163
(let ((val (c-struct-ref ptr layout value:)))164
(assert-true (< (abs (- val 3.14)) 0.001)))165
(assert-equal 255 (c-struct-ref ptr layout flags:))166
(c-free ptr))))168
(test "with-c-alloc"169
(let ((result (with-c-alloc point-layout170
(lambda (ptr)171
(c-struct-set! ptr point-layout x: 42.0)172
(c-struct-ref ptr point-layout x:)))))173
(assert-equal 42.0 result))))175
;; ============================================================176
;; Bytevector Bridge (don't need dlopen)177
;; ============================================================179
(test-group "Bytevector bridge"180
(test "pointer->bytevector copies data"181
(let ((ptr (c-alloc 4)))182
(pointer-set! ptr ffi/uint8 0 65) ; 'A'183
(pointer-set! ptr ffi/uint8 1 66) ; 'B'184
(pointer-set! ptr ffi/uint8 2 67) ; 'C'185
(pointer-set! ptr ffi/uint8 3 0)186
(let ((bv (pointer->bytevector ptr 3)))187
(assert-equal 3 (bytevector-length bv))188
(assert-equal 65 (bytevector-u8-ref bv 0))189
(assert-equal 66 (bytevector-u8-ref bv 1))190
(assert-equal 67 (bytevector-u8-ref bv 2)))191
(c-free ptr)))193
(test "bytevector->pointer"194
(let* ((bv (bytevector 1 2 3 4))195
(ptr (bytevector->pointer bv)))196
(assert-true (pointer? ptr))197
(assert-equal 1 (pointer-ref ptr ffi/uint8 0))198
(assert-equal 2 (pointer-ref ptr ffi/uint8 1))199
(assert-equal 3 (pointer-ref ptr ffi/uint8 2))200
(assert-equal 4 (pointer-ref ptr ffi/uint8 3)))))202
;; ============================================================203
;; Error Handling204
;; ============================================================206
(test-group "Error handling"207
(test "c-errno returns integer"208
(assert-true (integer? (c-errno))))210
(test "c-strerror returns string"211
(assert-true (string? (c-strerror 2))))) ; ENOENT = 2213
;; ============================================================214
;; Tests below require dynamic loading (dlopen)215
;; ============================================================217
(when has-dlopen?219
(test-group "Library loading"220
(test "load current process"221
(let ((libc (c-library #f)))222
(assert-true (c-library? libc))))224
(test "bad library name raises error"225
(assert-error (c-library "libnonexistent_12345"))))227
;; On Linux/glibc, math functions are available in the current process.228
(test-group "Math functions"229
(define libc (c-library #f))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))))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))))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))))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)))))253
(test-group "libc integer functions"254
(define libc (c-library #f))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))))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")))))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")))))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"))))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"))))))283
;; define-c-library must be used at top level — move to conditional block284
;; Note: define-c-library expands to top-level defines, so we test it285
;; only when dlopen is available.286
)288
;; define-c-library at top level, guarded289
(when has-dlopen?290
(define-c-library mathlib #f291
((math-sqrt "sqrt") (ffi/double) -> ffi/double)292
((math-pow "pow") (ffi/double ffi/double) -> ffi/double)))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)))299
(test "pow via define-c-library"300
(assert-equal 1024.0 (math-pow 2.0 10.0)))))302
;; ============================================================303
;; Callbacks (require dlopen for qsort test)304
;; ============================================================306
(when has-dlopen?307
(test-group "Callbacks"308
(define libc (c-library #f))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/int315
(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 integers320
(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
;; Sort326
(qsort-fn buf 5 4 cmp)327
;; Verify sorted order328
(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
;; Cleanup334
(c-callback-release cmp)335
(c-free buf)))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/int342
(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)))356
(test "callback-release frees the slot"357
(let ((cb (c-callback (list ffi/pointer ffi/pointer) ffi/int358
(lambda (a b) 0))))359
(assert-true (pointer? cb))360
(c-callback-release cb)))362
(test "multiple concurrent callbacks"363
(let* ((cb1 (c-callback (list ffi/pointer ffi/pointer) ffi/int364
(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/int368
(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 pointers374
(assert-true (not (= (pointer-address cb1) (pointer-address cb2))))375
(c-callback-release cb1)376
(c-callback-release cb2)))378
(test "void callback (thunk)"379
(let* ((called #f)380
(cb (c-callback '() ffi/void381
(lambda () (set! called #t)))))382
(assert-true (pointer? cb))383
(c-callback-release cb)))385
(test "double callback creates successfully"386
(let ((cb (c-callback (list ffi/double ffi/double) ffi/double387
(lambda (a b) (+ a b)))))388
(assert-true (pointer? cb))389
(c-callback-release cb)))))391
(run-tests)