:- module(test_con, [ test_con/0, % Run all tests test_con/1, % +Test (+)/1, % Assert (-)/1, % Retract v/1, % Visible s/2, % +Id, ?Subject p/2, % +Id, ?Predicate o/2, % +Id, ?Object u/1, % InVisible l/0, % List r/0, % reset {}/1, % transaction (@@)/2, % Action @ Context (Synchronous) (@@)/1, % Action in snapshot j/0, % Join helper threads j/1, % Join a specific helper jf/1, % Join a specific helper, failing snap/1, % -Snapshot op(200, xfx, @@), op(200, xf, @@), op(200, xfy, <=) ]). :- include(local_test). :- use_module(library(semweb/rdf_db)). :- use_module(library(aggregate)). /** RDF test language */ :- meta_predicate true(0), false(0), {}(0), @@(:,?), @@(:). :- thread_local triple/2. %! + Pattern % % Assert a triple, optionally giving it a name. + Name^rdf(S,P,O) :- !, mk_spo(S,P,O), rdf_assert(S,P,O), ( var(Name) -> Name = rdf(S,P,O) ; assertz(triple(Name, rdf(S,P,O))) ). + rdf(S,P,O) :- !, mk_spo(S,P,O), rdf_assert(S,P,O). + Sub<=Super :- mk(p,Sub), mk(p,Super), rdf_assert(Sub, rdfs:subPropertyOf, Super). mk_spo(S,P,O) :- mk(s, S), mk(p, P), mk(o, O). mk(_, R) :- atom(R), !. mk(Prefix, R) :- gensym(Prefix, R). %! - Pattern % % Retract a triple, normally referenced by name. - Name^rdf(S,P,O) :- !, rdf_retractall(S,P,O), ( var(Name) -> Name = rdf(S,P,O) ; assertz(triple(Name, rdf(S,P,O))) ). - rdf(S,P,O) :- !, rdf_retractall(S,P,O). - rdf(S,P,O) :- !, rdf_retractall(S,P,O). - Name :- ground(Name), triple(Name, Triple), -(Triple). %! v(+Id) % % True if triple Id is visible. v(+rdf(S,P,O)) :- !, v(rdf_has(S,P,O)). v(rdf(S,P,O)) :- !, true((rdf(S,P,O))), true((rdf(S,P,O2), O == O2)), true((rdf(S,P2,O), P == P2)), true((rdf(S,P2,O2), P == P2, O == O2)), true((rdf(S2,P,O), S2 == S)), true((rdf(S2,P,O2), S2 == S, O == O2)), true((rdf(S2,P2,O), S2 == S, P == P2)), true((rdf(S2,P2,O2), S2 == S, P == P2, O == O2)). v(rdf_has(S,P,O)) :- !, true((rdf_has(S,P,O))), true((rdf_has(S,P,O2), O == O2)), true((rdf_has(S2,P,O), S2 == S)), true((rdf_has(S2,P,O2), S2 == S, O == O2)). v(Name) :- ground(Name), triple(Name, Triple), v(Triple). %! u(+Id) % % True if triple Id is not visible. u(rdf(S,P,O)) :- !, false((rdf(S,P,O))). u(+rdf(S,P,O)) :- !, false((rdf_has(S,P,O))). u(Name) :- ground(Name), triple(Name, Triple), u(Triple). %! s(Id, Subject) is semidet. %! p(Id, Predicate) is semidet. %! o(Id, Object) is semidet. s(rdf(S,_,_), T) :- !, S = T. s(Name, T) :- ground(Name), triple(Name, Triple), s(Triple, T). p(rdf(_,P,_), T) :- !, P = T. p(Name, T) :- ground(Name), triple(Name, Triple), p(Triple, T). o(rdf(_,_,O), T) :- !, O = T. o(Name, T) :- ground(Name), triple(Name, Triple), o(Triple, T). true(G) :- G, !. true(G) :- print_message(error, false(G)), backtrace(5), throw(test_failed). false(G) :- G, !, print_message(error, true(G)), backtrace(5), throw(test_failed). false(_). %! {G} % % Run G in an RDF transaction. {}(G) :- rdf_transaction(G). %! snap(-Snapshot) is det. % % Create a snapshot. snap(X) :- rdf_snapshot(X). %! @@(:Goal, +Context) % % Run Goal (as once/1) in Context. Context is either a snapshot or % a separate thread. If Context is a thread, wait for its % completion (synchronous execution). The construct % % {G} @@ {T} % % runs the helper thread as a transaction. :- dynamic helper/1. (M:{}(G)) @@ Snapshot :- nonvar(Snapshot), rdf_current_snapshot(Snapshot), !, rdf_transaction(M:G, _Id, [snapshot(Snapshot)]). (M:{}(G)) @@ T :- ( var(T) -> thread_create(helper, T, []), assert(helper(T)), Helper = T ; T = {Helper} -> ( var(Helper) -> thread_create(rdf_transaction(helper), Helper, []), assert(helper(Helper)) ; true ) ; Helper = T ), thread_self(Me), thread_send_message(Helper, run(M:G, Me)), thread_get_message(Reply), ( Reply = true(X) -> X = M:G ; Reply = exception(E) -> throw(E) ). %! @@(:Goal) % % Run Goal in a snapshot. ((M:{}(G)) @@) :- rdf_transaction(M:G, _Id, [snapshot(true)]). %! j % % Join all helper threads. j :- forall(retract(helper(Id)), ( thread_send_message(Id, done), thread_join(Id, true) )). %! j(+Id) % % Join the specific helper thread, terminating its transaction % with success. j(Id) :- retract(helper(Id)), !, thread_send_message(Id, done), thread_join(Id, true). jf(Id) :- retract(helper(Id)), !, thread_send_message(Id, fail), thread_join(Id, false). helper :- thread_get_message(M), ( M = run(G, Sender) -> run(G,Result), thread_send_message(Sender, Result), helper ; M == done ). run(G, Result) :- catch(G, E, true), !, ( var(E) -> Result = true(G) ; Result = exception(E) ). run(_, false). %! r % % Reset the RDF database, helper threads, etc. r :- j, retractall(triple(_,_)), rdf_reset_db. %! l % % List content of RDF database. l :- forall(rdf(S,P,O), format('<~q, ~q, ~q>~n', [S,P,O])). db(RDF) :- findall(rdf(S,P,O), rdf(S,P,O), RDF0), sort(RDF0, RDF). /******************************* * TESTS * *******************************/ :- op(1000, fx, test). :- discontiguous (test)/1. term_expansion((test Head :- Body), [ test(Head), (Head :- Body) ]). test t1 :- % asserted triple in failed ( { + a^_, % transaction disappears fail } ; true ), u(a). test t2 :- % asserted triple in transaction { + a^_, % is visible inside and outside v(a) }, v(a). test t3 :- { + a^_, { v(a), + b^_, v(b) }, v(b) }, v(a). test t4 :- + a^_, { v(a) }. test t5 :- + a^_, { - a, u(a) }, u(a). test t6 :- + a^_, { - a, u(a) }, u(a). test t7 :- + a^_, ( { - a, u(a), fail } ; true ), v(a). % property handling tests test p1 :- + rdf(s,p,_), + B^rdf(s,p,_), rdf(s,p,O), - B, o(B, O). test p2 :- + rdf(s,p,_), + B^rdf(s,p,_), rdf(s,p,O), {- B} @@ H, {u(B)} @@ H, u(B), o(B, O). test p3 :- + B^rdf(s,p,_), {-B}@@_, u(B). % snapshot tests test s1 :- + a^_, snap(S), + b^_, { u(b) }@@S. test s2 :- + a^_, snap(S), { + b^_ }@@S, u(b). test s3 :- + a^_, snap(S), { - a }@@S, v(a). test s4 :- + a^_, { - a }@@, v(a). test s5 :- % snap inside a transaction { + a^_, snap(S), + b^_, { u(b) }@@S }. /* subProperty tests */ test sp1 :- + rdf(S1,P1,O1), + P1<=P2, v(+rdf(S1,P2,O1)). test sp2 :- + rdf(S1,P1,O1), { + P1<=P2 } @@ _, v(+rdf(S1,P2,O1)). test sp3 :- + rdf(S1,P1,O1), { + P1<=P2 } @@ {T}, u(+rdf(S1,P2,O1)), j(T), v(+rdf(S1,P2,O1)). test sp3b :- + rdf(S1,P1,O1), { { + P1<=P2 } } @@ {T}, u(+rdf(S1,P2,O1)), j(T), v(+rdf(S1,P2,O1)). test sp4 :- + rdf(S1,P1,O1), { + P1<=P2 } @@ {T}, u(+rdf(S1,P2,O1)), jf(T), u(+rdf(S1,P2,O1)). test sp5 :- % join two non-empty clouds + rdf(S1,P1,O1), + P1 <= SP1, v(+rdf(S1,SP1,O1)), + rdf(S2,P2,O2), + P2 <= SP2, v(+rdf(S2,SP2,O2)), + SP1 <= Root, + SP2 <= Root, v(+rdf(S1,Root,O1)), v(+rdf(S2,Root,O2)). /* Logical updates */ r2(R, R2) :- atom_concat(R, '^', R2). test lu1 :- + _, findall(x, (rdf(S,P,O), r2(O, O2), rdf_assert(S,P,O2)), [x]), findall(x, (rdf(S,P,O), r2(S, S2), rdf_assert(S2,P,O)), [x,x]). test lu2 :- + rdf(S1,P1,_), + rdf(S2,P2,_), + P1<=Root, findall(x, ( rdf_has(_,Root,_), + P2<=Root ), [x]), findall(S, rdf_has(S, Root, _), [S1,S2]). /* duplicate handling */ test dup1 :- + X, + X, findall(x, rdf(_,_,_), [x]). test dup2 :- + X, + X, - X, findall(x, rdf(_,_,_), []). test dup3 :- + X, \+ { + X, fail }, findall(x, rdf(_,_,_), [x]). /******************************* * TEST DRIVER * *******************************/ :- dynamic passed/1, failed/1. %! test_con % % Run all tests test_con :- retractall(passed(_)), retractall(failed(_)), forall(test(Head), test_con(Head)), aggregate_all(count, passed(_), Passed), aggregate_all(count, failed(_), Failed), ( Failed =:= 0 -> format('~NAll ~D tests passed~n', [Passed]) ; format('~N~D tests passed; ~D failed~n', [Passed, Failed]), fail ). %! test_con(+Test) % % Run one individual test. test_con(Head) :- r, catch(Head, E, true), !, j, ( var(E) -> assert(passed(Head)), write(user_error, '.') ; assert(failed(Head)), ( E == test_failed -> print_message(error, test_failed(Head)) ; print_message(error, test_failed(Head, E)) ) ). test_con(Head) :- j, assert(failed(Head)), print_message(error, test_failed(Head)). /******************************* * MESSAGES * *******************************/ prolog:message(test_failed) --> [ 'Test failed' ]. prolog:message(test_failed(Head)) --> [ 'Test failed: ~q'-[Head] ]. prolog:message(test_failed(Head, Error)) --> [ 'Test failed: ~q: '-[Head] ], '$messages':translate_message(Error). prolog:message(false(Goal)) --> [ 'Unexpected failure: ~q'-[Goal] ]. prolog:message(true(Goal)) --> [ 'Unexpected success: ~q'-[Goal] ].