227 lines
6.5 KiB
Erlang
227 lines
6.5 KiB
Erlang
%% This file tests that generators produce the right kind of data. It
|
|
%% also illustrates how to write a QuickSpec specification of a
|
|
%% datatype (such as the generator datatype).
|
|
|
|
-module(generators_eqc).
|
|
-include_lib("eqc/include/eqc.hrl").
|
|
|
|
-compile(export_all).
|
|
|
|
%% Generate most combinations of generator functions, as a symbolic
|
|
%% generator--for example:
|
|
%% {call,oneof,[{{call,bitstring,14},{call,elements,[15]}}]}
|
|
|
|
%% We control the size of the symbolic generator, by limiting the
|
|
%% nesting depth.
|
|
|
|
generator() ->
|
|
?SIZED(N,generator(N)).
|
|
|
|
generator(0) ->
|
|
% base case: no nested generators.
|
|
oneof([constant(),
|
|
binary,{call,binary,nat()},
|
|
bitstring,{call,bitstring,nat()},
|
|
bool,char,
|
|
?SUCHTHAT({call,choose,Lo,Hi},
|
|
{call,choose,int(),int()},
|
|
Lo=<Hi),
|
|
{call,elements,non_empty_list(constant())},
|
|
int,largeint,nat,real]);
|
|
generator(N) ->
|
|
% recursive case: reduce the size of nested compound generators.
|
|
N1 = N div 4,
|
|
?LAZY(oneof([generator(0),compound_generator(N1)])).
|
|
|
|
constant() ->
|
|
oneof([int(),atom()]).
|
|
|
|
atom() ->
|
|
elements([a,b,c,d]).
|
|
|
|
term() ->
|
|
?LET(G,generator(),generated_by(G)).
|
|
|
|
compound_generator(N) ->
|
|
Smaller = generator(N),
|
|
oneof([?LETSHRINK([Sm],[Smaller],?LET(Def,term(),{call,default,Sm,Def})),
|
|
?LETSHRINK(Gs,non_empty_list(Smaller),
|
|
{call,frequency,[{positive(),G} || G <- Gs]}),
|
|
?LETSHRINK([Sm],[Smaller],{call,list,Sm}),
|
|
?LETSHRINK([Sm],[Smaller],{call,orderedlist,Sm}),
|
|
?LETSHRINK([Sm],[?SUCHTHAT(Sm,Smaller,not often_empty(Sm))],
|
|
{call,non_empty,Sm}),
|
|
?LETSHRINK([Sm],[Smaller],{call,noshrink,Sm}),
|
|
?LETSHRINK(Gs,non_empty_list(Smaller),
|
|
{call,oneof,Gs}),
|
|
?LETSHRINK([Sm],[Smaller],{call,resize,choose(0,N),Sm}),
|
|
?LETSHRINK(L,list(constant()),{call,shuffle,L}),
|
|
?LETSHRINK(Gs,short_list(Smaller),
|
|
list_to_tuple(Gs)),
|
|
?LETSHRINK([Sm],[Smaller],{call,vector,choose(0,4),Sm})]).
|
|
|
|
%% To keep the size of generators under control, it is not enough to
|
|
%% restrict nesting depth. We also want lists of arguments to oneof,
|
|
%% frequency etc to be reasonably short.
|
|
|
|
short_list(G) ->
|
|
?SIZED(Size,
|
|
resize(Size div 3 + 1,
|
|
list(resize(Size,G)))).
|
|
|
|
positive() ->
|
|
?LET(N,nat(),N+1).
|
|
|
|
non_empty_list(G) ->
|
|
non_empty(short_list(G)).
|
|
|
|
%% When we generate {call,non_empty,G}, we need to know that G is
|
|
%% reasonably likely to produce a non-empty value... otherwise we may
|
|
%% loop when we try to use this generator!
|
|
|
|
often_empty([]) ->
|
|
true;
|
|
often_empty(<<>>) ->
|
|
true;
|
|
often_empty({call,vector,N,G}) ->
|
|
N==0;
|
|
often_empty({call,binary,N}) ->
|
|
N==0;
|
|
often_empty({call,bitstring,N}) ->
|
|
N==0;
|
|
often_empty({call,noshrink,G}) ->
|
|
often_empty(G);
|
|
often_empty({call,oneof,Gs}) ->
|
|
% conservative
|
|
lists:any(fun often_empty/1,Gs);
|
|
often_empty({call,frequency,WGs}) ->
|
|
% conservative
|
|
lists:any(fun often_empty/1, [G || {_,G} <- WGs]);
|
|
often_empty({call,default,G,V}) ->
|
|
often_empty(G) andalso often_empty(V);
|
|
often_empty({call,resize,N,G}) ->
|
|
N<4 orelse often_empty(G);
|
|
often_empty({call,shuffle,L}) ->
|
|
L==[];
|
|
often_empty(_) ->
|
|
false.
|
|
|
|
%% The values generated by a symbolic generator.
|
|
|
|
generated_by(A) when is_atom(A) ->
|
|
case erlang:function_exported(eqc_gen,A,0) of
|
|
true ->
|
|
eqc_gen:A();
|
|
false ->
|
|
A
|
|
end;
|
|
generated_by(T) when is_tuple(T), size(T)>0 ->
|
|
case tuple_to_list(T) of
|
|
[call,F|Args] ->
|
|
erlang:apply(eqc_gen,F,[generated_by(G) || G <- Args]);
|
|
Gs ->
|
|
list_to_tuple([generated_by(G) || G <- Gs])
|
|
end;
|
|
generated_by([H|T]) ->
|
|
[generated_by(H)|generated_by(T)];
|
|
generated_by(X) ->
|
|
X.
|
|
|
|
%% Check that a generated value corresponds to its generator.
|
|
|
|
is(binary,B) ->
|
|
is_binary(B);
|
|
is({call,binary,N},B) ->
|
|
is_binary(B) andalso size(B)==N;
|
|
is(bitstring,B) ->
|
|
is_bitstring(B);
|
|
is({call,bitstring,N},B) ->
|
|
is_bitstring(B) andalso size(B) == N div 8;
|
|
is(bool,B) ->
|
|
B==true orelse B==false;
|
|
is(char,C) ->
|
|
is_integer(C) andalso 0=<C andalso C=<255;
|
|
is({call,choose,Lo,Hi},N) ->
|
|
is_integer(N) andalso Lo =< N andalso N =< Hi;
|
|
is({call,default,G,D},X) ->
|
|
is(G,X) orelse X==D;
|
|
is({call,elements,L},X) ->
|
|
lists:member(X,L);
|
|
is({call,frequency,WGs},X) ->
|
|
lists:any(fun({_,G})->is(G,X) end,WGs);
|
|
is(int,N) ->
|
|
is_integer(N) andalso abs(N) =< 100;
|
|
is(largeint,N) ->
|
|
is_integer(N);
|
|
is(nat,N) ->
|
|
is_integer(N) andalso N>=0;
|
|
is(real,N) ->
|
|
is_float(N);
|
|
is({call,list,G},L) ->
|
|
is_list(L) andalso
|
|
lists:all(fun(X)->is(G,X) end,L);
|
|
is({call,orderedlist,G},L) ->
|
|
is_list(L) andalso
|
|
lists:all(fun(X)->is(G,X) end,L) andalso
|
|
lists:sort(L) == L;
|
|
is({call,non_empty,G},X) ->
|
|
is(G,X) andalso X/=[] andalso X/=<<>>;
|
|
is({call,noshrink,G},X) ->
|
|
is(G,X);
|
|
is({call,oneof,Gs},X) ->
|
|
lists:any(fun(G) -> is(G,X) end,Gs);
|
|
is({call,resize,_,G},X) ->
|
|
is(G,X);
|
|
is({call,shuffle,L},X) ->
|
|
is_list(X) andalso lists:sort(X) == lists:sort(L);
|
|
is({call,vector,N,G},V) ->
|
|
is_list(V) andalso length(V)==N andalso
|
|
lists:all(fun(X)->is(G,X) end,V);
|
|
is(GT,T) when is_tuple(GT) ->
|
|
is_tuple(T) andalso size(T)==size(GT) andalso
|
|
lists:all(fun({G,X})->is(G,X) end,
|
|
lists:zip(tuple_to_list(GT),
|
|
tuple_to_list(T)));
|
|
is(Const,X) when is_atom(Const); is_integer(Const) ->
|
|
Const == X.
|
|
|
|
%% The properties.
|
|
|
|
%% Generate symbolic generators, and report on the distribution of
|
|
%% generator functions used.
|
|
prop_generator() ->
|
|
?FORALL(G,generator(),
|
|
aggregate(generator_types(G),true)).
|
|
|
|
generator_types(G) when is_tuple(G) ->
|
|
case tuple_to_list(G) of
|
|
[call,frequency,WArgs] ->
|
|
[T || {_,G1} <- WArgs, T <- generator_types(G1)];
|
|
[call,F|Args] ->
|
|
[F|[T || A <- Args,
|
|
T <- generator_types(A)]];
|
|
L ->
|
|
[T || X <- L,
|
|
T <- generator_types(X)]
|
|
end;
|
|
generator_types(N) when is_integer(N) ->
|
|
[integer_constant];
|
|
generator_types(X) when is_float(X) ->
|
|
[float_constant];
|
|
generator_types(B) when is_binary(B) ->
|
|
[binary];
|
|
generator_types(B) when is_bitstring(B) ->
|
|
[bitstring];
|
|
generator_types(L) when is_list(L) ->
|
|
[list|[T || X <- L, T <- generator_types(X)]];
|
|
generator_types(X) ->
|
|
[X].
|
|
|
|
%% For each kind of generator, use it to generate a value, and check
|
|
%% that the value matches the generator. This tests the generators
|
|
%% (and our generator-generators!) pretty thoroughly.
|
|
|
|
prop_correct_types() ->
|
|
?FORALL(G,generator(),
|
|
?FORALL(X,generated_by(G),is(G,X))).
|
|
|