;; lib/datalog/tests/eval.sx — naive evaluation + safety analysis tests. (define dl-et-pass 0) (define dl-et-fail 0) (define dl-et-failures (list)) ;; Same deep-equal helper used in other suites. (define dl-et-deep=? (fn (a b) (cond ((and (list? a) (list? b)) (and (= (len a) (len b)) (dl-et-deq-l? a b 0))) ((and (dict? a) (dict? b)) (let ((ka (keys a)) (kb (keys b))) (and (= (len ka) (len kb)) (dl-et-deq-d? a b ka 0)))) ((and (number? a) (number? b)) (= a b)) (else (equal? a b))))) (define dl-et-deq-l? (fn (a b i) (cond ((>= i (len a)) true) ((not (dl-et-deep=? (nth a i) (nth b i))) false) (else (dl-et-deq-l? a b (+ i 1)))))) (define dl-et-deq-d? (fn (a b ka i) (cond ((>= i (len ka)) true) ((let ((k (nth ka i))) (not (dl-et-deep=? (get a k) (get b k)))) false) (else (dl-et-deq-d? a b ka (+ i 1)))))) ;; Set-equality on lists (order-independent, uses dl-et-deep=?). (define dl-et-set=? (fn (a b) (and (= (len a) (len b)) (dl-et-subset? a b) (dl-et-subset? b a)))) (define dl-et-subset? (fn (xs ys) (cond ((= (len xs) 0) true) ((not (dl-et-contains? ys (first xs))) false) (else (dl-et-subset? (rest xs) ys))))) (define dl-et-contains? (fn (xs target) (cond ((= (len xs) 0) false) ((dl-et-deep=? (first xs) target) true) (else (dl-et-contains? (rest xs) target))))) (define dl-et-test! (fn (name got expected) (if (dl-et-deep=? got expected) (set! dl-et-pass (+ dl-et-pass 1)) (do (set! dl-et-fail (+ dl-et-fail 1)) (append! dl-et-failures (str name "\n expected: " expected "\n got: " got)))))) (define dl-et-test-set! (fn (name got expected) (if (dl-et-set=? got expected) (set! dl-et-pass (+ dl-et-pass 1)) (do (set! dl-et-fail (+ dl-et-fail 1)) (append! dl-et-failures (str name "\n expected (set): " expected "\n got: " got)))))) (define dl-et-throws? (fn (thunk) (let ((threw false)) (do (guard (e (#t (set! threw true))) (thunk)) threw)))) (define dl-et-run-all! (fn () (do (dl-et-test-set! "fact lookup any" (dl-query (dl-program "parent(tom, bob). parent(bob, ann).") (list (quote parent) (quote X) (quote Y))) (list {:X (quote tom) :Y (quote bob)} {:X (quote bob) :Y (quote ann)})) (dl-et-test-set! "fact lookup constant arg" (dl-query (dl-program "parent(tom, bob). parent(tom, liz). parent(bob, ann).") (list (quote parent) (quote tom) (quote Y))) (list {:Y (quote bob)} {:Y (quote liz)})) (dl-et-test-set! "no match" (dl-query (dl-program "parent(tom, bob).") (list (quote parent) (quote nobody) (quote X))) (list)) (dl-et-test-set! "ancestor closure" (dl-query (dl-program "parent(tom, bob). parent(bob, ann). parent(ann, pat).\n ancestor(X, Y) :- parent(X, Y).\n ancestor(X, Z) :- parent(X, Y), ancestor(Y, Z).") (list (quote ancestor) (quote tom) (quote X))) (list {:X (quote bob)} {:X (quote ann)} {:X (quote pat)})) (dl-et-test-set! "sibling" (dl-query (dl-program "parent(tom, bob). parent(tom, liz). parent(jane, bob). parent(jane, liz).\n sibling(X, Y) :- parent(P, X), parent(P, Y).") (list (quote sibling) (quote bob) (quote Y))) (list {:Y (quote bob)} {:Y (quote liz)})) (dl-et-test-set! "same-generation" (dl-query (dl-program "parent(tom, bob). parent(tom, liz). parent(bob, ann). parent(liz, joe).\n person(tom). person(bob). person(liz). person(ann). person(joe).\n sg(X, X) :- person(X).\n sg(X, Y) :- parent(P1, X), sg(P1, P2), parent(P2, Y).") (list (quote sg) (quote ann) (quote X))) (list {:X (quote ann)} {:X (quote joe)})) (dl-et-test! "ancestor count" (let ((db (dl-program "parent(a, b). parent(b, c). parent(c, d).\n ancestor(X, Y) :- parent(X, Y).\n ancestor(X, Z) :- parent(X, Y), ancestor(Y, Z)."))) (do (dl-saturate! db) (len (dl-relation db "ancestor")))) 6) (dl-et-test-set! "grandparent" (dl-query (dl-program "parent(a, b). parent(b, c). parent(c, d).\n grandparent(X, Z) :- parent(X, Y), parent(Y, Z).") (list (quote grandparent) (quote X) (quote Y))) (list {:X (quote a) :Y (quote c)} {:X (quote b) :Y (quote d)})) (dl-et-test! "no recursion infinite loop" (let ((db (dl-program "edge(1, 2). edge(2, 3). edge(3, 1).\n reach(X, Y) :- edge(X, Y).\n reach(X, Z) :- edge(X, Y), reach(Y, Z)."))) (do (dl-saturate! db) (len (dl-relation db "reach")))) 9) (dl-et-test! "unsafe head var" (dl-et-throws? (fn () (dl-program "p(X, Y) :- q(X)."))) true) (dl-et-test! "unsafe — empty body" (dl-et-throws? (fn () (dl-program "p(X) :- ."))) true) (dl-et-test! "underscore var ok" (dl-et-throws? (fn () (dl-program "p(X, _) :- q(X)."))) false) (dl-et-test! "var only in head — unsafe" (dl-et-throws? (fn () (dl-program "p(X, Y) :- q(Z)."))) true) (dl-et-test! "head var bound by body" (dl-et-throws? (fn () (dl-program "p(X) :- q(X)."))) false) (dl-et-test! "head subset of body" (dl-et-throws? (fn () (dl-program "edge(a,b). edge(b,c). reach(X, Z) :- edge(X, Y), edge(Y, Z)."))) false)))) (define dl-eval-tests-run! (fn () (do (set! dl-et-pass 0) (set! dl-et-fail 0) (set! dl-et-failures (list)) (dl-et-run-all!) {:failures dl-et-failures :total (+ dl-et-pass dl-et-fail) :passed dl-et-pass :failed dl-et-fail})))