This repository has been archived on 2025-08-18. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.
GGS/lib/eqc/examples/generators_eqc.erl
2011-05-05 02:46:21 +02:00

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))).