Examples (unittest)

Author(s): Edison Mera, Pedro López, Manuel Hermenegildo.

Stability: [beta] Most of the functionality is there but it is still missing some testing and/or verification.


Some examples of the use of test assertions.

test_examples.pl

:- module(test_examples,
    [
        p/1,
        display1/1,
        call_test10/1,
        cut_test5/0,
        display_fail/0
    ],
    [assertions, nativeprops, basicmodes, rtchecks, unittestdecls, hiord]).

:- doc(author, "Edison Mera").
:- doc(author, "Nataliia Stulova").

:- doc(module, "Some examples of unit tests.").

:- use_module(engine(io_basic)).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% ----------------------------------------------------------------------
% tests only with preconditions

:- test p(A) : (A = c)
    # "test should pass".
:- test p(A) : (A = d, A = a)
    # "test should fail (precondition)".
:- test p(A) : throw(exception)
    # "test should fail (throws exception in precondition)".
:- test p(A) : (A = h)
    # "test should abort".

% ----------------------------------------------------------------------
% tests with preconditions and computational properties

:- test p(A) : (A = a) + not_fails
    # "test should pass".
:- test p(A) : (A = b) + fails
    # "test should pass".
:- test p(A) : (A = c) + exception(error(c, _))
    # "test should pass".
:- test p(A) : (A = c) + fails
    # "test should fail".
:- test p(A) : (A = c) + not_fails
    # "test should fail".

% ----------------------------------------------------------------------
% tests with preconditions and postonditions

:- test p(Z) => (Z = d).

% ----------------------------------------------------------------------
% assertions that can be added to all unit tests for p/1 with the
% 'rtc_entry' unittest option
:- check comp p(X) : (X = a) + not_fails.
% :- check comp p(X) : (X = b) + not_fails.

p(a).                               % rule succeeds
p(b) :- fail.                       % rule fails
p(c) :- throw(error(c, 'error c')). % rule throws an exception
p(h) :- halt(1).                    % rule aborts the execution

% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

:- use_module(library(unittest/unittest_props), [times/2]).

% ----------------------------------------------------------------------
% this set of tests demonstrates the correct and incorrect uses of texec
% assertions, it only gives examples of passing tests
%
:- texec display1(A) : (A = hello)
    # "correct texecassertion, test should pass".
:- texec display1(A) : (A = hello) + times(1)
    # "correct texec assertion, test should pass".
:- texec display1(A) : (A = hello) + user_output("hello")
    # "incorrect texec assertion, test should pass".
% ----------------------------------------------------------------------

% ----------------------------------------------------------------------
% examples of the alternatives for the texec assertions above
%
:- test display1(A) : (A = hello)
    # "test should pass".
:- test display1(A) : (A = hello) + user_output("bye")
    # "test should fail".
% ----------------------------------------------------------------------

display1(A) :- display(A).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

:- test display_fail + (user_output("hello"), fails) # "Test OK".

display_fail :- display(hello), fail.

:- pred display_fail(A) + (user_output("hello2"), fails).
:- export(display_fail/1).

display_fail(_A) :- display(hello), fail.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

:- use_module(library(write), [write/1]).

:- test cut_test5 + (times(3), user_output("Cut disjunction"), fails) #
    "Test OK".

cut_test5 :- (! ; write('No')), write('Cut disjunction'), fail.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

:- test call_test10(X) : (X=(write(3), call(1)))
    + ( user_output("output"),
        exception(error(type_error(callable, 1), 'in metacall')) )
# "Wrong test".

:- meta_predicate call_test10(goal).

call_test10(X) :- call(X).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

:- export(qs/2).
:- pred qs(+list(int), -list(int)) + is_det.

qs(A, A).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% tests below cover most of scenarios of the unit test expansion
%% (missing: tests that take into account different values of prolog flags
%%  rtchecks_predloc and rtchecks_asrloc (see rtchecks_tr:combine_locators/6)
%%  for the reason behind).

%% tests for different uses of GP properties in texec assertions

:- use_module(library(unittest/unittest_props), [try_sols/2]).

:- export(case_tx/2).

% processing texec as texec

:- texec case_tx(A, AB) : (A = a)
     # "test should pass".
:- texec case_tx(A, AB) : (A = a) + ( times(2), try_sols(2) )
     # "test should pass".

% processing texec as test

:- texec case_tx(A, AB) : (A = a) + is_det
     # "test should fail, bug: true&false test".
:- texec case_tx(A, AB) : (A = a) + (try_sols(2), non_det)
     # "test should pass".

case_tx(a, a).
case_tx(a, b).

% ----------------------------------------------------------------------

%% tests for different uses of AP and GP properties in test assertions

:- export(case_ts/2).

:- test case_ts(X, Y) : (X = a)
    # "test should pass".
:- test case_ts(X, Y) : (X = a) + try_sols(1)
    # "test should pass".
:- test case_ts(X, Y) : (X = b) + is_det
    # "test should pass".
:- test case_ts(X, Y) : (X = a) => (Y = b) + try_sols(1)
    # "test should fail".
:- test case_ts(X, Y) : (X = b) => (Y = a ; Y = b) + is_det
    # "test should pass".

% ----------------------------------------------------------------------

%% checks for the properties in the assertions are added to the unit
%% tests for case_ts/2 iff this module does not include the rtchecks
%% package. To see the effects of this assertions remove the rtchecks
%% package from the package list and run the tests for this module
%% wuth the [rtc_entry] test option. If you do not remove the
%% package, the assertions below will be simply turned into rtchecks.

% :- pred case_ts(X,Y) : atm(X) => int(Y) # "makes unit tests fail".
% :- pred case_ts(X,Y) : atm(X) => gnd(Y) # "does not affect unit tests".

case_ts(a, a).
case_ts(a, b).
case_ts(b, b).

ceval1.pl

:- module(ceval1, [ceval/2], [assertions, regtypes, nativeprops]).

:- doc(author, "Edison Mera").

:- doc(module, "This example illustrates how the unified approach for
   static verification, run-time checking, and unit testing works. It
   captures the situation in which the programmer changes the main
   functor of a data structure. In this case, the regular type complex
   c(A, B) is renamed to cm(A, B).  This file contains the initial
   program.").

:- entry ceval/2 : gnd * var.

:- regtype complex/1.
:- export(complex/1).

complex(c(A, B)) :-
    num(A),
    num(B).

:- test ceval(A, B) : (A = c(3, 4) + c(1, 2) - c(2, 3))
    => (B = c(2, 3)) + (not_fails, is_det).

:- test ceval(A, B) : (A = c(3, 4) * c(1, 2) / c(1, 2))
    => (B = c(3.0, 4.0)) + (not_fails, is_det).

:- check pred ceval/2 : gnd * term => gnd * complex.

ceval(A,   A) :- complex(A), !.
ceval(A+B, C) :- ceval(A, CA), ceval(B, CB), add(CA, CB, C).
ceval(A-B, C) :- ceval(A, CA), ceval(B, CB), sub(CA, CB, C).
ceval(A*B, C) :- ceval(A, CA), ceval(B, CB), mul(CA, CB, C).
ceval(A/B, C) :- ceval(A, CA), ceval(B, CB), div(CA, CB, C).

:- pred add/3 : complex * complex * var => complex * complex * complex.
add(c(A1, B1), c(A2, B2), c(A, B)) :-
    A is A1 + A2,
    B is B1 + B2.

:- pred sub/3 : complex * complex * var => complex * complex * complex.
sub(c(A1, B1), c(A2, B2), c(A, B)) :-
    A is A1 - A2,
    B is B1 - B2.

:- pred mul/3 : complex * complex * var => complex * complex * complex.
mul(c(A1, B1), c(A2, B2), c(A, B)) :-
    A is A1*A2 - B1*B2,
    B is A1*B2 + B1*A2.

:- pred div/3 : complex * complex * var => complex * complex * complex.
div(c(A1, B1), c(A2, B2), c(A, B)) :-
    D is A2*A2 + B2*B2,
    A is (A1*A2 + B1*B2) / D,
    B is (A2*B1 - A1*B2) / D.

ceval2.pl

:- module(ceval2, [ceval/2], [assertions, regtypes, nativeprops]).

:- doc(author, "Edison Mera").

:- doc(module, "This example illustrates how the unified approach for
   static verification, run-time checking, and unit testing works. It
   captures the situation in which the programmer changes the main
   functor of a data structure. In this case, the regular type complex
   c(A, B) is renamed to cm(A, B).  This file contains the renamed
   program (with a bug).").

:- entry ceval/2 : gnd * var.

:- regtype complex/1.
:- export(complex/1).

complex(cm(A, B)) :-
    num(A),
    num(B).

:- false test ceval(A, B) : (A = cm(3, 4) + cm(1, 2) - cm(2, 3))
    => (B = cm(2, 3)) + (not_fails, is_det).

:- test ceval(A, B) : (A = cm(3, 4) * cm(1, 2) / cm(1, 2))
    => (B = cm(3.0, 4.0)).

:- check pred ceval/2 : gnd * term => gnd * complex.

ceval(A,   A) :- complex(A), !.
ceval(A+B, C) :- ceval(A, CA), ceval(B, CB), add(CA, CB, C).
ceval(A-B, C) :- ceval(A, CA), ceval(B, CB), sub(CA, CB, C).
ceval(A*B, C) :- ceval(A, CA), ceval(B, CB), mul(CA, CB, C).
ceval(A/B, C) :- ceval(A, CA), ceval(B, CB), div(CA, CB, C).

:- pred add/3 : complex * complex * var => complex * complex * complex.
add(cm(A1, B1), cm(A2, B2), cm(A, B)) :-
    A is A1 + A2,
    B is B1 + B2.

:- pred sub/3 : complex * complex * var => complex * complex * complex.
sub(cm(A1, B1), cm(A2, B2), c(A, B)) :- % You forgot to rename c to cm!
    A is A1 - A2,
    B is B1 - B2.

:- pred mul/3 : complex * complex * var => complex * complex * complex.
mul(cm(A1, B1), cm(A2, B2), cm(A, B)) :-
    A is A1*A2 - B1*B2,
    B is A1*B2 + B1*A2.

:- pred div/3 : complex * complex * var => complex * complex * complex.
div(cm(A1, B1), cm(A2, B2), cm(A, B)) :-
    D is A2*A2 + B2*B2,
    A is (A1*A2 + B1*B2) / D,
    B is (A2*B1 - A1*B2) / D.

length.pl

:- module(length, [length/2, concat/3],
        [assertions, isomodes, metatypes, hiord, nativeprops]).

:- doc(author, "Alvaro Sevilla San Mateo").

length([],    0).
length([_|T], N) :- length(T, N0), N is N0 + 1.

concat([],     L,  L).
concat([X|L1], L2, [X|L3]) :- concat(L1, L2, L3).

:- test length(X, Y) : (X = [1, 2, 3], Y = 3) + not_fails.
:- test length(X, Y) : (X = [a, b, c, d, e], Y = 5) + not_fails.
:- test length(X, Y) : (X = [], Y = 0) + not_fails.
:- test length(X, Y) : (X = [a, b, c, d, e], Y = 5) + not_fails.
:- test length(X, Y) : (X = [a, b, c], Y = 5) + fails.
:- test length(X, Y) : (X = [a, b, c, d], Y = 5) + fails.
:- test concat(A, B, C) : (A = [1, 2], B = [3], C = [1, 2, 3]) + not_fails.
:- test concat(A, B, C) : (A = [1, 2], B = [3], var(C)) => (C=[1, 2, 3])
    + not_fails.

civil_registry.pl

:- module(civil_registry, [profession/1, surname/1, sevillian/1,
            family_income/1, married/2, salary/2],
        [assertions, isomodes, metatypes, hiord, nativeprops]).

:- doc(author, "Alvaro Sevilla San Mateo").

family_information(husband(name(antonio, garcia, fernandez),
            profession(architect), salary(300000)),
        wife(name(ana, ruiz, lopez),
            profession(teacher), salary(120000)),
        address(sevilla)).

family_information(husband(name(luis, alvarez, garcia),
            profession(architect), salary(400000)),
        wife(name(ana, romero, soler),
            profession(housewife), salary(0)),
        address(sevilla)).

family_information(husband(name(bernardo, bueno, martinez),
            profession(teacher), salary(120000)),
        wife(name(laura, rodriguez, millan),
            profession(doctor), salary(250000)),
        address(cuenca)).


family_information(husband(name(miguel, gonzalez, ruiz),
            profession(businessman), salary(400000)),
        wife(name(belen, salguero, cuevas),
            profession(housewife), salary(0)),
        address(dos_hermanas)).

profession(X) :- family_information(husband(_,profession(X),_),_,_).
profession(X) :- family_information(_,wife(_,profession(X),_),_).

:- test profession(X) : (X = doctor) + not_fails.
:- test profession(X) : (X = businessman) + not_fails.
:- test profession(X) : (X = computer_scientist) + fails.

surname(X) :-
    family_information(husband(name(_, X, _), _, _), _, _).
surname(X) :-
    family_information(_, wife(name(_, X, _), _, _), _).

:- test surname(X) : (X = alvarez) + not_fails.
:- test surname(X) : (X = salguero) + not_fails.
:- test surname(X) : (X = sanchez) + fails.

sevillian(X) :-
    family_information(husband(X, _, _), _, address(sevilla)).
sevillian(X) :-
    family_information(_, wife(X, _, _), address(sevilla)).

:- test sevillian(X) : (X = name(miguel, gonzalez, ruiz)) + fails.
:- test sevillian(X) : (X = name(belen, salguero, cuevas)) + fails.
:- test sevillian(X) : (X = name(pedro, sanchez, rodriguez)) + fails.
:- test sevillian(X) : (X = name(antonio, garcia, fernandez)) + not_fails.

family_income(X) :-
    family_information(
        husband(_, _, salary(N1)),
        wife(_, _, salary(N2)), _),
    X is N1+N2.

:- test family_income(X) : (X = 400000) + not_fails.
:- test family_income(X) : (X = 370000) + not_fails.
:- test family_income(X) : (X = 100) + fails.

married(X, Y) :-
    family_information(
        husband(name(X, _, _), _, _),
        wife(name(Y, _, _), _, _), _).

:- test married(X, Y) : (X = antonio, Y = ana) + not_fails.
:- test married(X, Y) : (X = miguel, Y = belen) + not_fails.
:- test married(X, Y) : (X = antonio, Y = maria) + fails.

salary(X, Y) :-
    family_information(husband(X, _, salary(Y)), _, _).
salary(X, Y) :-
    family_information(_, wife(X, _, salary(Y)), _).

:- test salary(X, Y) : (X = name(luis, alvarez, garcia), Y = 400000)
    + not_fails.
:- test salary(X, Y) : (X = name(laura, rodriguez, millan), Y = 250000)
    + not_fails.
:- test salary(X, Y) : (X = ana, Y = 100) + fails.

qsort.pl

:- module(_, [qsort/2], [assertions, nativeprops]).

:- doc(author, "Edison Mera").

:- doc(module, "This example illustrates how the unified approach
    for static verification, run-time checking and unit testing
    works. In this case, a bug have been introduced: in the call
    to append/3 by qsort/2, variables R1 and R2 are swapped.  Then
    we prove statically part of the assertions, for the part that
    cannot be verified statically we generate run-time checks,
    and finally, the program also has a test attached that
    exercises the run-time checks.").

:- prop sorted_num_list/1.
:- export(sorted_num_list/1).

sorted_num_list([]).
sorted_num_list([X]) :- number(X).
sorted_num_list([X, Y|Z]) :-
    number(X), number(Y), X<Y, sorted_num_list([Y|Z]).

:- calls partition(A, B, C, D) : (ground(A), ground(B)).
:- comp partition(A, B, C, D) : (list(num, A)) + (not_fails, is_det).
:- success partition(A, B, C, D) => (ground(C), list(num, D)).

partition([],    _B, [],        []).
partition([E|R], C,  [E|Left1], Right) :-
    E < C, !, partition(R, C, Left1, Right).
partition([E|R], C, Left, [E|Right1]) :-
    E >= C, partition(R, C, Left, Right1).

:- calls append(A, B, C) : (list(num, A), list(num, B)).

append([],    X, X).
append([H|X], Y, [H|Z]) :- append(X, Y, Z).

:- entry qsort(A, B) : (list(num, A), ground(A)).

:- test qsort(A, B) : (A = [5, 7, 2, 4, 3]) => (B = [2, 3, 4, 5, 7]).

:- calls qsort(A, B) : list(num, A).
:- comp qsort(A, B) : (list(num, A), ground(A), var(B)) + is_det.
:- success qsort(A, B) => (ground(B), sorted_num_list(B)).

qsort([X|L], R) :-
    partition(L, X, L1, L2),
    qsort(L2, R2), qsort(L1, R1),
    append(R2, [X|R1], R). % There is a bug here!!!
qsort([], []).

relationships.pl

:- module(relationships,
        [father/2, son/2, grandfather/2, brother/2, fsb/2],
        [assertions, isomodes, metatypes, hiord, nativeprops]).

:- doc(author, "Alvaro Sevilla San Mateo").

father('juan',   'maria'). % juan is the father of maria
father('pablo',  'juan').  % pablo is the father of juan
father('pablo',  'marcela').
father('carlos', 'debora').

% A is the son of B if B the father of A
son(A, B) :- father(B, A).

% A is the grandfather of B if A is the father of C and C is the father of B 
grandfather(A, B) :-
    father(A, C),
    father(C, B).

% A and B are brothers if the father of A is also the father of B and A and B are different persons
brother(A, B) :-
    father(C, A),
    father(C, B),
    A \== B.


% A and B are fsb if A is the father, son, or brother of B
fsb(A, B) :-
    father(A, B).
fsb(A, B) :-
    son(A, B).
fsb(A, B) :-
    brother(A, B).

% Tests
:- test fsb(A, B) : (A = 'juan', B = 'marcela') + not_fails.
:- test fsb(A, B) : (A = 'pedro', B = 'pedro') + not_fails.
:- test fsb(A, B) : (A = 'debora', B = 'carlos') + fails.
:- test fsb(A, B) : (A = 'debora', B = 'nadie') + not_fails.
:- test brother(A, B) : (A = 'debora', B = 'carlos') + not_fails.