erlang: extract ffi test suite (637/637, ffi 14/14)

This commit is contained in:
2026-05-14 20:21:51 +00:00
parent 29fd70f17a
commit 6636f9c170
6 changed files with 128 additions and 55 deletions

View File

@@ -36,6 +36,7 @@ SUITES=(
"bank|er-bank-test-pass|er-bank-test-count"
"echo|er-echo-test-pass|er-echo-test-count"
"fib|er-fib-test-pass|er-fib-test-count"
"ffi|er-ffi-test-pass|er-ffi-test-count"
)
cat > "$TMPFILE" << 'EPOCHS'
@@ -56,6 +57,7 @@ cat > "$TMPFILE" << 'EPOCHS'
(load "lib/erlang/tests/programs/bank.sx")
(load "lib/erlang/tests/programs/echo.sx")
(load "lib/erlang/tests/programs/fib_server.sx")
(load "lib/erlang/tests/ffi.sx")
(epoch 100)
(eval "(list er-test-pass er-test-count)")
(epoch 101)
@@ -74,6 +76,8 @@ cat > "$TMPFILE" << 'EPOCHS'
(eval "(list er-echo-test-pass er-echo-test-count)")
(epoch 108)
(eval "(list er-fib-test-pass er-fib-test-count)")
(epoch 109)
(eval "(list er-ffi-test-pass er-ffi-test-count)")
EPOCHS
timeout 600 "$SX_SERVER" < "$TMPFILE" > "$OUTFILE" 2>&1

View File

@@ -1,16 +1,17 @@
{
"language": "erlang",
"total_pass": 633,
"total": 633,
"total_pass": 637,
"total": 637,
"suites": [
{"name":"tokenize","pass":62,"total":62,"status":"ok"},
{"name":"parse","pass":52,"total":52,"status":"ok"},
{"name":"eval","pass":395,"total":395,"status":"ok"},
{"name":"eval","pass":385,"total":385,"status":"ok"},
{"name":"runtime","pass":93,"total":93,"status":"ok"},
{"name":"ring","pass":4,"total":4,"status":"ok"},
{"name":"ping-pong","pass":4,"total":4,"status":"ok"},
{"name":"bank","pass":8,"total":8,"status":"ok"},
{"name":"echo","pass":7,"total":7,"status":"ok"},
{"name":"fib","pass":8,"total":8,"status":"ok"}
{"name":"fib","pass":8,"total":8,"status":"ok"},
{"name":"ffi","pass":14,"total":14,"status":"ok"}
]
}

View File

@@ -1,18 +1,19 @@
# Erlang-on-SX Scoreboard
**Total: 633 / 633 tests passing**
**Total: 637 / 637 tests passing**
| | Suite | Pass | Total |
|---|---|---|---|
| ✅ | tokenize | 62 | 62 |
| ✅ | parse | 52 | 52 |
| ✅ | eval | 395 | 395 |
| ✅ | eval | 385 | 385 |
| ✅ | runtime | 93 | 93 |
| ✅ | ring | 4 | 4 |
| ✅ | ping-pong | 4 | 4 |
| ✅ | bank | 8 | 8 |
| ✅ | echo | 7 | 7 |
| ✅ | fib | 8 | 8 |
| ✅ | ffi | 14 | 14 |
Generated by `lib/erlang/conformance.sh`.

View File

@@ -1341,54 +1341,6 @@
(get (nth (get er-rt-cap-result :elements) 4) :name) "true")
;; ── Phase 8: file module BIFs ───────────────────────────────────
(er-modules-reset!)
;; write + read round-trip
(er-eval-test "file:write_file ok"
(nm (ev "file:write_file(\"/tmp/er-test-1.txt\", \"hello\")"))
"ok")
(er-eval-test "file:read_file ok tag"
(nm (ev "element(1, file:read_file(\"/tmp/er-test-1.txt\"))"))
"ok")
(er-eval-test "file:read_file payload is binary"
(ev "case file:read_file(\"/tmp/er-test-1.txt\") of {ok, B} -> is_binary(B) end")
(er-mk-atom "true"))
(er-eval-test "file:read_file content bytes"
(ev "case file:read_file(\"/tmp/er-test-1.txt\") of {ok, B} -> byte_size(B) end")
5)
;; missing file → {error, enoent}
(er-eval-test "file:read_file missing tag"
(nm (ev "element(1, file:read_file(\"/tmp/er-no-such-file-xyz\"))"))
"error")
(er-eval-test "file:read_file missing reason"
(nm (ev "element(2, file:read_file(\"/tmp/er-no-such-file-xyz\"))"))
"enoent")
;; delete
(er-eval-test "file:delete ok"
(nm (ev "file:write_file(\"/tmp/er-test-del.txt\", \"x\"), file:delete(\"/tmp/er-test-del.txt\")"))
"ok")
(er-eval-test "file:read_file after delete"
(nm (ev "file:write_file(\"/tmp/er-test-del2.txt\", \"x\"), file:delete(\"/tmp/er-test-del2.txt\"), element(2, file:read_file(\"/tmp/er-test-del2.txt\"))"))
"enoent")
;; write to inaccessible dir → {error, enoent}
(er-eval-test "file:write_file bad path"
(nm (ev "element(2, file:write_file(\"/tmp/no-such-dir-xyz/x\", \"y\"))"))
"enoent")
;; binary input round-trip (the bytes go through write)
(er-eval-test "file:write_file binary payload round-trip"
(ev "file:write_file(\"/tmp/er-test-2.bin\", <<1, 2, 3, 4, 5>>), case file:read_file(\"/tmp/er-test-2.bin\") of {ok, B} -> byte_size(B) end")
5)
(define
er-eval-test-summary
(str "eval " er-eval-test-pass "/" er-eval-test-count))

113
lib/erlang/tests/ffi.sx Normal file
View File

@@ -0,0 +1,113 @@
;; Phase 8 FFI BIF tests — one round-trip per BIF.
;; Each BIF lives in lib/erlang/runtime.sx (registered with
;; er-bif-registry) and wraps an SX-host primitive.
(define er-ffi-test-count 0)
(define er-ffi-test-pass 0)
(define er-ffi-test-fails (list))
(define
er-ffi-test
(fn
(name actual expected)
(set! er-ffi-test-count (+ er-ffi-test-count 1))
(if
(= actual expected)
(set! er-ffi-test-pass (+ er-ffi-test-pass 1))
(append! er-ffi-test-fails {:name name :expected expected :actual actual}))))
(define ffi-ev erlang-eval-ast)
(define ffi-nm (fn (v) (get v :name)))
;; ── file:read_file/1 + file:write_file/2 ────────────────────────
(er-ffi-test
"file:write_file ok"
(ffi-nm (ffi-ev "file:write_file(\"/tmp/er-ffi-1.txt\", \"hello\")"))
"ok")
(er-ffi-test
"file:read_file ok tag"
(ffi-nm (ffi-ev "element(1, file:read_file(\"/tmp/er-ffi-1.txt\"))"))
"ok")
(er-ffi-test
"file:read_file payload is binary"
(ffi-nm
(ffi-ev
"case file:read_file(\"/tmp/er-ffi-1.txt\") of {ok, B} -> is_binary(B) end"))
"true")
(er-ffi-test
"file:read_file content byte_size"
(ffi-ev
"case file:read_file(\"/tmp/er-ffi-1.txt\") of {ok, B} -> byte_size(B) end")
5)
(er-ffi-test
"file:read_file missing enoent"
(ffi-nm (ffi-ev "element(2, file:read_file(\"/tmp/er-ffi-no-such-xyz\"))"))
"enoent")
(er-ffi-test
"file:write_file bad path enoent"
(ffi-nm
(ffi-ev "element(2, file:write_file(\"/tmp/er-ffi-no-dir-xyz/x\", \"y\"))"))
"enoent")
(er-ffi-test
"file:write_file binary payload"
(ffi-ev
"file:write_file(\"/tmp/er-ffi-2.bin\", <<1, 2, 3, 4, 5>>), case file:read_file(\"/tmp/er-ffi-2.bin\") of {ok, B} -> byte_size(B) end")
5)
;; ── file:delete/1 ────────────────────────────────────────────────
(er-ffi-test
"file:delete ok"
(ffi-nm
(ffi-ev
"file:write_file(\"/tmp/er-ffi-del.txt\", \"x\"), file:delete(\"/tmp/er-ffi-del.txt\")"))
"ok")
(er-ffi-test
"file:read_file after delete enoent"
(ffi-nm
(ffi-ev
"file:write_file(\"/tmp/er-ffi-del2.txt\", \"x\"), file:delete(\"/tmp/er-ffi-del2.txt\"), element(2, file:read_file(\"/tmp/er-ffi-del2.txt\"))"))
"enoent")
;; ── Blocked BIFs (placeholder asserts so the suite documents intent) ──
;; crypto:hash/2, cid:from_bytes/1, cid:to_string/1, file:list_dir/1,
;; httpc:request/4, sqlite:* — documented in plans/erlang-on-sx.md
;; under Blockers. When the host runtime gains the underlying primitive,
;; the wrappers land in runtime.sx and tests appear here. For now we
;; assert each is NOT registered, so a future iteration that adds them
;; without updating this file fails fast.
(er-ffi-test
"crypto:hash unregistered"
(er-lookup-bif "crypto" "hash" 2)
nil)
(er-ffi-test
"cid:from_bytes unregistered"
(er-lookup-bif "cid" "from_bytes" 1)
nil)
(er-ffi-test
"file:list_dir unregistered"
(er-lookup-bif "file" "list_dir" 1)
nil)
(er-ffi-test
"httpc:request unregistered"
(er-lookup-bif "httpc" "request" 4)
nil)
(er-ffi-test
"sqlite:exec unregistered"
(er-lookup-bif "sqlite" "exec" 2)
nil)
(define
er-ffi-test-summary
(str "ffi " er-ffi-test-pass "/" er-ffi-test-count))