Plunit and keeping "clauses together"

Some test code:

:- begin_tests(member).

   % Standard success

   test(t1, true, [nondet])  :- member(1,[1,2,3]).

   % Succeed twice

   test(t2, all(X == [1,1])) :- X=1, member(X,[1,2,3,1]).

   % Succeed even if the "list" is a non-list, as long as the prefix contains 1

   test(t3, true, [nondet])  :- L=[1,2,3|nope], \+is_list(L), member(1,L).

:- end_tests(member).

rt(member) :- run_tests(member).

When we load this:

?- [member_test].
Warning: /home/me/member_test.pl:13:
Warning:    Clauses of plunit_member:test/3 are not together in the source-file
Warning:    Earlier definition at /home/me/member_test.pl:5
Warning:    Current predicate: plunit_member:'unit body'/2
Warning:    Use :- discontiguous plunit_member:test/3. to suppress this message
true.

Is this is expected? It might be best to suppress this warning, right?

Alternatively, I can apparently write this, (second-guessing the documentation), inserting true before all:

test(t2, true, all(X == [1,1])) :- X=1, member(X,[1,2,3,1]).

Then everything is test/3 and the compiler does not warn.

Yes

You say tomato, I say tomato.

I have never seen someone calling that a taboo, but in test cases I would avoid doing that.


If you search the SWI-Prolog source code at GitHub for begin_tests you will see lots of examples and possibly get some other ideas. If you see discontiguous used with these test I would like to know for future reference. Also be aware that the source code has some older styles of test cases and those I personally do not use as examples with the newer style using begin_tests.

1 Like

Tests are declared with a head being either test(Name) or test(Name, Properties), i.e. test/1 or test/2. Anything else is just a predicate. No properties means true. A single property can be written without a list. Multiple need a list. So this would be

 test(t1, [true,nondet]) :-

Now, nondet is not the usual mode determinism declaration. In this context it means “succeeds, possibly with open choice points” and thus the true is not needed.

So, the intended way to write this is

test(t1, nondet) :- member(1,[1,2,3]).

Why the \+ is_list(L)? You know it is not a list. You are testing member/2 (I assume). If you were testing list you’d write

test(nonlist, fail) :- is_list([1,2,3|nope).
2 Likes

Thanks jan.

I see I have been deluding myself. test/3 is not called at all vi run_tests/1, indeed.

Indeed,

   test(t3, [nondet])  :- L=[1,2,3|nope], \+is_list(L), member(1,L).

Yes, this is a test for member. But I wanted to inform the reader/myself that this is a test for “not a list”.

One could even write:

   test(notalist_1, [nondet])  :- (L=[1,2,3|nope], \+is_list(L)) -> member(1,L).
   test(notalist_2, all(X == [1,2,3]))  :- (L=[1,2,3|nope], \+is_list(L)) -> member(X,L).

Thanks Eric. That’s a lot of code!