;; 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}))