prolog: integration test suite (+20)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 40s

20 end-to-end tests via pl-query-* API: permission system, graph
reachability, quicksort, dynamic KB, fibonacci. Total 571/571.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-05 20:41:45 +00:00
parent e4eab6a309
commit ef736112ef
5 changed files with 184 additions and 7 deletions

View File

@@ -43,6 +43,7 @@ SUITES=(
"advanced:lib/prolog/tests/advanced.sx:pl-advanced-tests-run!"
"compiler:lib/prolog/tests/compiler.sx:pl-compiler-tests-run!"
"cross_validate:lib/prolog/tests/cross_validate.sx:pl-cross-validate-tests-run!"
"integration:lib/prolog/tests/integration.sx:pl-integration-tests-run!"
)
SCRIPT='(epoch 1)

View File

@@ -1,7 +1,7 @@
{
"total_passed": 517,
"total_passed": 571,
"total_failed": 0,
"total": 517,
"suites": {"parse":{"passed":25,"total":25,"failed":0},"unify":{"passed":47,"total":47,"failed":0},"clausedb":{"passed":14,"total":14,"failed":0},"solve":{"passed":62,"total":62,"failed":0},"operators":{"passed":19,"total":19,"failed":0},"dynamic":{"passed":11,"total":11,"failed":0},"findall":{"passed":11,"total":11,"failed":0},"term_inspect":{"passed":14,"total":14,"failed":0},"append":{"passed":6,"total":6,"failed":0},"reverse":{"passed":6,"total":6,"failed":0},"member":{"passed":7,"total":7,"failed":0},"nqueens":{"passed":6,"total":6,"failed":0},"family":{"passed":10,"total":10,"failed":0},"atoms":{"passed":34,"total":34,"failed":0},"query_api":{"passed":16,"total":16,"failed":0},"iso_predicates":{"passed":29,"total":29,"failed":0},"meta_predicates":{"passed":25,"total":25,"failed":0},"list_predicates":{"passed":33,"total":33,"failed":0},"meta_call":{"passed":15,"total":15,"failed":0},"set_predicates":{"passed":15,"total":15,"failed":0},"char_predicates":{"passed":27,"total":27,"failed":0},"io_predicates":{"passed":24,"total":24,"failed":0},"assert_rules":{"passed":15,"total":15,"failed":0},"string_agg":{"passed":25,"total":25,"failed":0},"advanced":{"passed":21,"total":21,"failed":0}},
"generated": "2026-04-25T14:12:52+00:00"
"total": 571,
"suites": {"parse":{"passed":25,"total":25,"failed":0},"unify":{"passed":47,"total":47,"failed":0},"clausedb":{"passed":14,"total":14,"failed":0},"solve":{"passed":62,"total":62,"failed":0},"operators":{"passed":19,"total":19,"failed":0},"dynamic":{"passed":11,"total":11,"failed":0},"findall":{"passed":11,"total":11,"failed":0},"term_inspect":{"passed":14,"total":14,"failed":0},"append":{"passed":6,"total":6,"failed":0},"reverse":{"passed":6,"total":6,"failed":0},"member":{"passed":7,"total":7,"failed":0},"nqueens":{"passed":6,"total":6,"failed":0},"family":{"passed":10,"total":10,"failed":0},"atoms":{"passed":34,"total":34,"failed":0},"query_api":{"passed":16,"total":16,"failed":0},"iso_predicates":{"passed":29,"total":29,"failed":0},"meta_predicates":{"passed":25,"total":25,"failed":0},"list_predicates":{"passed":33,"total":33,"failed":0},"meta_call":{"passed":15,"total":15,"failed":0},"set_predicates":{"passed":15,"total":15,"failed":0},"char_predicates":{"passed":27,"total":27,"failed":0},"io_predicates":{"passed":24,"total":24,"failed":0},"assert_rules":{"passed":15,"total":15,"failed":0},"string_agg":{"passed":25,"total":25,"failed":0},"advanced":{"passed":21,"total":21,"failed":0},"compiler":{"passed":17,"total":17,"failed":0},"cross_validate":{"passed":17,"total":17,"failed":0},"integration":{"passed":20,"total":20,"failed":0}},
"generated": "2026-05-05T20:36:53+00:00"
}

View File

@@ -1,7 +1,7 @@
# Prolog scoreboard
**517 / 517 passing** (0 failure(s)).
Generated 2026-04-25T14:12:52+00:00.
**571 / 571 passing** (0 failure(s)).
Generated 2026-05-05T20:36:53+00:00.
| Suite | Passed | Total | Status |
|-------|--------|-------|--------|
@@ -30,6 +30,9 @@ Generated 2026-04-25T14:12:52+00:00.
| assert_rules | 15 | 15 | ok |
| string_agg | 25 | 25 | ok |
| advanced | 21 | 21 | ok |
| compiler | 17 | 17 | ok |
| cross_validate | 17 | 17 | ok |
| integration | 20 | 20 | ok |
Run `bash lib/prolog/conformance.sh` to refresh. Override the binary
with `SX_SERVER=path/to/sx_server.exe bash …`.

View File

@@ -0,0 +1,172 @@
;; lib/prolog/tests/integration.sx — end-to-end integration tests via pl-query-* API
;;
;; Tests the full source→parse→load→solve pipeline with real programs.
;; Covers: permission system, graph reachability, quicksort, fibonacci, dynamic KB.
(define pl-int-test-count 0)
(define pl-int-test-pass 0)
(define pl-int-test-fail 0)
(define pl-int-test-failures (list))
(define
pl-int-test!
(fn
(name got expected)
(begin
(set! pl-int-test-count (+ pl-int-test-count 1))
(if
(= got expected)
(set! pl-int-test-pass (+ pl-int-test-pass 1))
(begin
(set! pl-int-test-fail (+ pl-int-test-fail 1))
(append!
pl-int-test-failures
(str name "\n expected: " expected "\n got: " got)))))))
;; ── Permission system ──
;; role/2 + permission/2 facts, allowed/2 rule
(define
pl-int-perm-src
"role(alice, admin). role(bob, editor). role(charlie, viewer). permission(admin, read). permission(admin, write). permission(admin, delete). permission(editor, read). permission(editor, write). permission(viewer, read). allowed(U, A) :- role(U, R), permission(R, A).")
(define pl-int-perm-db (pl-load pl-int-perm-src))
(pl-int-test!
"alice can read"
(len (pl-query-all pl-int-perm-db "allowed(alice, read)"))
1)
(pl-int-test!
"alice can delete"
(len (pl-query-all pl-int-perm-db "allowed(alice, delete)"))
1)
(pl-int-test!
"charlie cannot write"
(len (pl-query-all pl-int-perm-db "allowed(charlie, write)"))
0)
(pl-int-test!
"alice has 3 permissions"
(len (pl-query-all pl-int-perm-db "allowed(alice, A)"))
3)
(pl-int-test!
"only one user can delete"
(len (pl-query-all pl-int-perm-db "allowed(U, delete)"))
1)
(pl-int-test!
"the deleter is alice"
(dict-get (first (pl-query-all pl-int-perm-db "allowed(U, delete)")) "U")
"alice")
;; ── Graph reachability ──
;; Directed edges; path/2 transitive closure via two clauses
(define
pl-int-graph-src
"edge(a, b). edge(b, c). edge(c, d). edge(b, d). path(X, Y) :- edge(X, Y). path(X, Y) :- edge(X, Z), path(Z, Y).")
(define pl-int-graph-db (pl-load pl-int-graph-src))
(pl-int-test!
"direct edge a→b is a path"
(len (pl-query-all pl-int-graph-db "path(a, b)"))
1)
(pl-int-test!
"transitive path a→c"
(len (pl-query-all pl-int-graph-db "path(a, c)"))
1)
(pl-int-test!
"no path d→a (no back-edges)"
(len (pl-query-all pl-int-graph-db "path(d, a)"))
0)
(pl-int-test!
"4 derivations from a (b,c,d via two routes to d)"
(len (pl-query-all pl-int-graph-db "path(a, Y)"))
4)
;; ── Quicksort ──
;; Partition-and-recurse; uses its own append/3 to avoid DB pollution
(define
pl-int-qs-src
"partition(_, [], [], []). partition(Piv, [H|T], [H|Less], Greater) :- H =< Piv, !, partition(Piv, T, Less, Greater). partition(Piv, [H|T], Less, [H|Greater]) :- partition(Piv, T, Less, Greater). append([], L, L). append([H|T], L, [H|R]) :- append(T, L, R). quicksort([], []). quicksort([H|T], Sorted) :- partition(H, T, Less, Greater), quicksort(Less, SL), quicksort(Greater, SG), append(SL, [H|SG], Sorted).")
(define pl-int-qs-db (pl-load pl-int-qs-src))
(pl-int-test!
"quicksort([]) = [] (ground check)"
(len (pl-query-all pl-int-qs-db "quicksort([], [])"))
1)
(pl-int-test!
"quicksort([3,1,2]) = [1,2,3] (ground check)"
(len (pl-query-all pl-int-qs-db "quicksort([3,1,2], [1,2,3])"))
1)
(pl-int-test!
"quicksort([5,3,1,4,2]) = [1,2,3,4,5] (ground check)"
(len (pl-query-all pl-int-qs-db "quicksort([5,3,1,4,2], [1,2,3,4,5])"))
1)
(pl-int-test!
"quicksort([3,1,2], [3,1,2]) fails — unsorted order rejected"
(len (pl-query-all pl-int-qs-db "quicksort([3,1,2], [3,1,2])"))
0)
;; ── Fibonacci ──
;; Naive recursive; ground checks avoid list-format uncertainty
(define
pl-int-fib-src
"fib(0, 0). fib(1, 1). fib(N, F) :- N > 1, N1 is N - 1, N2 is N - 2, fib(N1, F1), fib(N2, F2), F is F1 + F2.")
(define pl-int-fib-db (pl-load pl-int-fib-src))
(pl-int-test!
"fib(0, 0) succeeds"
(len (pl-query-all pl-int-fib-db "fib(0, 0)"))
1)
(pl-int-test!
"fib(5, 5) succeeds"
(len (pl-query-all pl-int-fib-db "fib(5, 5)"))
1)
(pl-int-test!
"fib(7, 13) succeeds"
(len (pl-query-all pl-int-fib-db "fib(7, 13)"))
1)
;; ── Dynamic knowledge base ──
;; Assert and retract facts; the DB dict is mutable so mutations persist
(define pl-int-dyn-src "color(red). color(green). color(blue).")
(define pl-int-dyn-db (pl-load pl-int-dyn-src))
(pl-int-test!
"initial KB: 3 colors"
(len (pl-query-all pl-int-dyn-db "color(X)"))
3)
(pl-int-test!
"after assert(color(yellow)): 4 colors"
(begin
(pl-query-all pl-int-dyn-db "assert(color(yellow))")
(len (pl-query-all pl-int-dyn-db "color(X)")))
4)
(pl-int-test!
"after retract(color(red)): back to 3 colors"
(begin
(pl-query-all pl-int-dyn-db "retract(color(red))")
(len (pl-query-all pl-int-dyn-db "color(X)")))
3)
(define pl-integration-tests-run! (fn () {:failed pl-int-test-fail :passed pl-int-test-pass :total pl-int-test-count :failures pl-int-test-failures}))

View File

@@ -74,7 +74,7 @@ Representation choices (finalise in phase 1, document here):
### Phase 5 — Hyperscript integration
- [x] `prolog-query` primitive callable from SX/Hyperscript
- [ ] Hyperscript DSL: `when allowed(user, :edit) then …`**blocked** (needs `lib/hyperscript/**`, out of scope)
- [ ] Integration suite
- [x] Integration suite
### Phase 6 — ISO conformance
- [x] Vendor Hirst's conformance tests
@@ -88,6 +88,7 @@ Representation choices (finalise in phase 1, document here):
_Newest first. Agent appends on every commit._
- 2026-05-05 — Integration test suite (`tests/integration.sx`): 20 end-to-end tests via `pl-query-*` API covering permission system (6), graph reachability (4), quicksort (4), fibonacci (3), dynamic KB (3). Suite added to conformance harness. Total **571** (+20).
- 2026-04-25 — `pl-compiled-matches-interp?` cross-validator in `compiler.sx`: loads source into both a plain and a compiled DB, runs the same goal, returns true iff solution counts match. `tests/cross_validate.sx` applies this to 17 goals across append/member/ancestor/cut/arithmetic/if-then-else, locking the interpreter as the reference against which any future compiler change must agree. Total **551** (+17).
- 2026-04-25 — Clause compiler (`lib/prolog/compiler.sx`): `pl-compile-clause` converts parse-AST clauses to SX closures `(fn (goal trail db cut-box k) bool)`. Pre-collects var names at compile time; `pl-cmp-build-term` reconstructs fresh runtime terms per call. `pl-compile-db!` compiles all clauses in a DB and stores them in `:compiled` table. `pl-solve-user!` in runtime.sx auto-dispatches to compiled lambdas when present, falls back to interpreted. `pl-try-compiled-clauses!` mirrors `pl-try-clauses!` cut semantics. 17 tests in `tests/compiler.sx`. Total **534** (+17).
- 2026-04-25 — `predsort/3` (insertion-sort with 3-arg comparator predicate, deduplicates `=` pairs), `term_variables/2` (collect unbound vars left-to-right, dedup by id), arithmetic extensions (`floor/1`, `ceiling/1`, `truncate/1`, `round/1`, `sign/1`, `sqrt/1`, `pow/2`, `**/2`, `^/2`, `integer/1`, `float/1`, `float_integer_part/1`, `float_fractional_part/1`). 21 tests in `tests/advanced.sx`. Total **517** (+21).