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