1
  • I want to unit test a non-deterministic predicate in Prolog with PL-Unit.

  • I want to "assert" that the solutions I specify in my tests are the only solutions for the predicate.

  • I'm not concerned about the order that each solution is specified in the test.

What the SWI-Prolog manual says:

The SWI-Prolog manual has a great section called testing non-deterministic predicates...

2.2.3 Testing non-deterministic predicates

Non-deterministic predicates succeed zero or more times. Their results are tested either using findall/3 or setof/3 followed by a value-check or using the all or set options. The following are equivalent tests:

test(member) :-
        findall(X, member(X, [a,b,c]), Xs),
        Xs == [a,b,c].

test(member, all(X == [a,b,c])) :-
        member(X, [a,b,c]).

This is almost exactly what I need. However, for these tests to pass, the output has to be specified in the correct order: [a, b, c]. I'd like to be able to specify it in any order and still have the test pass.

An example of what I'm trying:

Let's say I'm testing this (hypothetical) predicate that has two solutions.

specialNumber(X) :-
    X is 2;
    X is 4.

I could write a passing unit test like so...

% PASSES!
test(specialNumber, all(X == [2, 4])) :-
        specialNumber(X).

But it would fail if I swapped the order of the solutions...

% FAILS!
test(specialNumber, all(X == [4, 2])) :-
        specialNumber(X).

How can I make this test pass regardless of the order that the solutions are found?

Community
  • 1
  • 1
byxor
  • 5,930
  • 4
  • 27
  • 44
  • 1
    Use `setof/3` to collect *and* sort, or use `findall/3` and then `sort/2`. – mat Dec 16 '16 at 18:35
  • @mat Excellent idea. Would you have time to post an example? And would this only work for numeric lists? – byxor Dec 16 '16 at 18:36
  • Note: I still haven't been able to work it out. I don't understand how/where to use `setof/3`. – byxor Dec 16 '16 at 19:17

1 Answers1

1

Consider using setof/3 instead of findall/3 to collect and sort .

For example, given the slightly rewritten definition of your predicate:

special_number(X) :-
        (   X #= 2
        ;   X #= 4
        ).

We can define the following PlUnit test:

:- begin_tests(all).

test(solutions, Solutions = [2,4]) :-
        setof(X, special_number(X), Solutions).

:- end_tests(all).

This test case continues to work if you rewrite your predicate for example to:

special_number(X) :-
        (   X #= 4
        ;   X #= 2
        ).

In this case, we have:

?- findall(X, special_number(X), Ls).
Ls = [4, 2].

?- setof(X, special_number(X), Ls).
Ls = [2, 4].

As an alternative to setof/3, you can for example use:

  • findall/3
  • and then sort solutions manually with sort/2.

Related topics for you to read:

  • standard order of terms
  • bagof/3
  • (@<)/2

I leave adding the necessary libraries and directives that are necessary to run the example above as an exercise.

mat
  • 40,498
  • 3
  • 51
  • 78