Added eqcmini quickcheck library to lib/
This commit is contained in:
parent
8a574db360
commit
73194d7d28
21 changed files with 2075 additions and 0 deletions
227
lib/eqc/examples/generators_eqc.erl
Normal file
227
lib/eqc/examples/generators_eqc.erl
Normal file
|
@ -0,0 +1,227 @@
|
|||
%% 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))).
|
||||
|
Reference in a new issue