%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et %% %% This file is part of Triq - Trifork QuickCheck %% %% Copyright (c) 2010-2013 by Trifork %% Copyright 2013-2018 Triq authors %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. -module(triq_dom). -include_lib("eunit/include/eunit.hrl"). %% the name of te special record for domains -define(DOM,'@'). -define(DEFAULT_PROP_PREFIX, "prop_"). %% must correspond to the definitions in triq.hrl, but we don't want to %% include that file. -define(DELAY(X), fun()->X end). -define(SIZED(Size,Gen), sized(fun(Size) -> Gen end)). %% How many times we try to pick or shrink a value in order to satisfy a %% ?SUCHTHAT property. -define(SUCHTHAT_LOOPS,100). %% how many times we try to shrink a value before we bail out -define(SHRINK_LOOPS,100). %% A number large enough to trigger the Erlang bignum implementation -define(BIGNUM, trunc(math:pow(2, 65) * 2)). %% @type pick_fun(T). Picks members of the `domain(T)'. %% Return pair of `{domain(T),T}'; the "output domain" is what will %% be used for shrinking the value. -type pick_fun(T) :: fun( (domain(T),integer()) -> {domain(T),T} | no_return() ) | undefined. %% @type shrink_fun(T). Shrinks members of the `domain(T)'. %% Return pair of `{domain(T),T}'; the "output domain" is what will %% be used for further shrinking the value. -type shrink_fun(T) :: fun( (domain(T),T) -> {domain(T),T} | no_return() ) | none | undefined. -type domrec(T) :: {?DOM, atom() | tuple(), pick_fun(T), shrink_fun(T), boolean()}. -define(BOX,'@box'). -record(?BOX, {dom :: domain(T), value :: T}). %%-------------------------------------- %% @doc A box(T) contains a value of type T, along with %% information needed to shrink T. %% %% For example, not all integers shrink the same. If the %% integer was generated to produce only even numbers for %% instance, then that knowledge needs to be kept with the %% value. %% %% @end %%-------------------------------------- -type box(T) :: #?BOX{dom:: domain(T), value :: T}. %% @type domain(T). Domain of values of type T. %% -type domain(T) :: domrec(T) | T. %% @type uchar(). Valid unicode code point. -type uchar() :: 0..16#D7FF | 16#E000..16#10FFFF. -type uchars() :: unicode:unicode_characters(). -record(?DOM, {kind :: atom() | tuple(), pick = fun error_pick/2 :: pick_fun(T), shrink = fun error_shrink/2 :: shrink_fun(T) | none, empty_ok = true :: boolean() }). -record(list, {elem}). -record(tuple, {elem}). -record(vector,{size, elem}). -record(binary,{size}). -record(bitstring,{size}). -record(atom, {size}). -record(oneof, {size, elems=[]}). -record(resize,{size, dom}). -record(bind, {dom, body}). -record(sized, {body}). -record(noshrink, {dom}). -record(suchthat,{dom,pred}). -record(bound_domain,{dom1,val1,dom2,fun2,size}). -record(choose,{min,max,shrinkto}). -record(elements,{elems,size,picked=none}). -record(seal,{dom,seed}). -record(unicode_binary, {size, encoding = utf8}). %% generators -export([list/1, tuple/1, int/0, int/1, int/2, largeint/0, byte/0, real/0, float/0, sized/1, elements/1, any/0, atom/0, atom/1, choose/2, oneof/1, frequency/1, bool/0, char/0, return/1, vector/2, binary/1, binary/0, bitstring/0, bitstring/1, non_empty/1, resize/2, noshrink/1, non_neg_integer/0, pos_integer/0]). %% Unicode -export([unicode_char/0, unicode_string/0, unicode_string/1, unicode_binary/0, unicode_binary/1, unicode_binary/2, unicode_characters/0, unicode_characters/1]). %% using a generator -export([bind/2, bindshrink/2, is_shrinkable/1, suchthat/2, pick/2, shrink/2, sample/1, sampleshrink/1, seal/1, open/1, peek/1, domain/3, shrink_without_duplicates/1]). %% %% Default values for pic/shrink in ?DOM records %% error_pick(#?DOM{kind=Kind},_) -> erlang:error({pick,Kind}). error_shrink(#?DOM{kind=Kind},_) -> erlang:error({shrink,Kind}). %% %% @doc The heart of the random structure generator; pick a value from the %% domain. %% Returns a pair of `{domain(T), T}' where the first component describes %% the structure of the picked value. %% @spec pick(domain(T), pos_integer()) -> {domain(T), T} %% -spec pick(domain(T), pos_integer()) -> {domain(T), T}. pick(Dom=#?DOM{pick=PickFun}, SampleSize) when SampleSize > 0, is_integer(SampleSize) -> PickFun(Dom,SampleSize); %% %% A tuple is generated by generating each element %% pick({}=Empty, _) -> {Empty,Empty}; pick(T,SampleSize) when is_tuple(T), SampleSize > 0, is_integer(SampleSize) -> {DomList,List} = pick(tuple_to_list(T), SampleSize), {list_to_tuple(DomList), list_to_tuple(List)}; %% %% For lists, we traverse down the list and generate each head %% pick([], _) -> {[],[]}; pick([H|T], SampleSize) when SampleSize > 0, is_integer(SampleSize) -> {HDom,HVal} = pick(H,SampleSize), {TDom,TVal} = pick(T,SampleSize), {[HDom|TDom], [HVal|TVal]}; %% %% Lazy elements... %% pick(F,SampleSize) when erlang:is_function(F,0) -> pick(F(),SampleSize); %% %% Simple values that generate themselves %% pick(V,_) -> {V,V}. pick_test() -> case pick(int(), 10) of {Dom, Val} when Val >= -5, Val =< 5 -> Dom = int() end. pick_tuple_test() -> case pick({int(),int()}, 10) of {Dom, {Val1,Val2}} when is_integer(Val1), is_integer(Val2) -> Dom = {int(), int()} end. pick_lazy_test() -> case pick(?DELAY(int()), 10) of {_, Val} when Val >= -5, Val =< 5 -> ok end. pick_list_test() -> case pick([int(),int()], 10) of {Dom, [Val1,Val2]} when is_integer(Val1), is_integer(Val2) -> Dom = [int(), int()] end. pick_pair_test() -> repeat( fun() -> case pick([choose(0,10), {choose(0,10)}], 10) of {Dom, [Int1, {Int2}]=Val} when is_integer(Int1), is_integer(Int2) -> case shrink(Dom, Val) of {_, [SInt1, {SInt2}]} when SInt1 ok; {_, [_, {_}]} when Int1==0;Int2==0 -> ok end end end, 20). shrink({Domain,Value}) -> shrink(Domain,Value). %% @doc The shrinking step function used internally in Triq. %% %% Performs one single step of shrinking. If unsuccessful, %% i.e. value cound not be shrunk, the output is equal to the input. %% %% Takes a `Domain' and a `Value' from said domain, and shrinks %% the value within the constraints of the domain. The result is %% a tuple of a (possibly smaller) output domain, and the %% shrunken value. %% %% @spec shrink(Domain::domain(T),Value::T) -> {domain(T), T} -spec shrink(domain(T),T) -> {domain(T), T}. shrink(Dom=#?DOM{shrink=none}, Value) -> {Dom, Value}; shrink(Domain=#?DOM{shrink=SFun}, Value) -> SFun(Domain,Value); shrink(TupDom,Tup) when is_tuple(TupDom), is_tuple(Tup), tuple_size(TupDom) =:= tuple_size(Tup) -> shrink_tuple_samesize(TupDom, Tup, 10); %% %% well-formed lists are shrunk using this case. %% the "length(X)>=0 tests if it is well-formed list" %% shrink(ListDom, List) when is_list(ListDom), is_list(List), length(List) >= 0 -> ?assert(length(ListDom) == length(List)), shrink_list_samesize(ListDom, List, length(List), 10); %% %% other non-well-formed lists [cons pairs] use this clause %% shrink([_|_]=ListDom, [_|_]=List) -> shrink_pair(ListDom,List, 10); %% finally, if the generator is the value itself, it simplifies to itself shrink(Any,Any) -> {Any,Any}. %% @doc %% Support functions for the generic shrinking %% @private %% @end -spec shrink_pair([domain(H)|domain(T)], [H|T], non_neg_integer()) -> {[domain(H)|domain(T)],[H|T]}. shrink_pair(ListDom,List,0) -> {ListDom,List}; shrink_pair([HDom|TDom]=ListDom, [H|T]=List, NAttempts) -> %% choose if we shrink the head or the tail ShrinkHead = (triq_rnd:uniform(2) =:= 1), ShrinkTail = (triq_rnd:uniform(2) =:= 1), %% then do it case %% shrink head and/or tail { case ShrinkHead of true -> shrink(HDom,H); false -> {HDom,H} end , case ShrinkTail of true -> shrink(TDom,T); false -> {TDom,T} end } of %% it did not shrink {{_,H}, {_,T}} -> shrink_pair(ListDom,List,NAttempts-1); %% either H or T changed {{HSDom,HS}, {TSDom,TS}} -> {[HSDom|TSDom], [HS|TS]} end. %% %% We shrink tuples by turning it into a list and shrinking that... %% shrink_tuple_samesize(TupDom, Tup, NAttempts) -> ?assert(tuple_size(TupDom) =:= tuple_size(Tup)), ListDom = tuple_to_list(TupDom), List = tuple_to_list(Tup), {SDom,SList} = shrink_list_samesize(ListDom, List, tuple_size(Tup), NAttempts), { list_to_tuple(SDom), list_to_tuple(SList) }. %% %% Shrink a list by simplifying one or more of the elements. %% -spec shrink_list_samesize([domain(T)],[T],non_neg_integer(), non_neg_integer()) -> {[domain(T)],[T]}. shrink_list_samesize([],[],_,_) -> {[],[]}; shrink_list_samesize(ListDom,List,_,0) -> {ListDom,List}; shrink_list_samesize(ListDom,List,Length,NAttempts) when is_list(List) -> ?assert(length(ListDom) == length(List)), HowManyToShrink = shrink_members(Length), case shrink_list_members(ListDom, List, Length, HowManyToShrink) of %% it did not shrink, try again {_,List} -> shrink_list_samesize(ListDom, List, Length, NAttempts-1); %% else, we got a simpler list {_,_}=Result -> Result end. %% %% Given a list, shrink HowMany of it's elements, %% but don't reduce the list length. %% -spec shrink_list_members([domain(T)],[T],non_neg_integer(), non_neg_integer()) -> {[domain(T)],[T]}. shrink_list_members(ListDom, List, _, 0) -> {ListDom,List}; shrink_list_members(ListDom, List, Len, HowMany) when is_list(List), is_list(ListDom) -> ?assert(Len == length(List)), %% %% replace element at RemIdx with simplified one %% RemIdx = triq_rnd:uniform(Len), Elm = lists:nth(RemIdx, List), ElmDom = lists:nth(RemIdx, ListDom), {NextDom,NextList} = case shrink(ElmDom,Elm) of {_,Elm} -> {ListDom,List}; {SElmDom,SElm} -> Dom2 = lists:sublist(ListDom,RemIdx-1) ++ [SElmDom] ++ lists:sublist(ListDom,RemIdx+1,Len), List2 = lists:sublist(List,RemIdx-1) ++ [SElm] ++ lists:sublist(List,RemIdx+1,Len), {Dom2, List2} end, shrink_list_members(NextDom, NextList, Len, HowMany-1). %%------------------------------------------------------------------- %% %% Now, the specific domains %% %%------------------------------------------------------------------- %%-------------------------------------------------------------------- %% @doc %% Returns the domain of lists of the argument. %% For example, `list(int())' yields the domain of lists of integers. %% %% @spec list( domain(T) ) -> domain([T]) %% @end %%-------------------------------------------------------------------- -spec list(domain(T)) -> domrec([T]). list(ElemDom) -> #?DOM{kind=#list{elem=ElemDom}, pick=fun list_pick/2 }. list_pick(#?DOM{kind=#list{elem=ElemDom},empty_ok=EmptyOK}, SampleSize) -> OutLen = if EmptyOK =:= false -> triq_rnd:uniform(SampleSize); EmptyOK =:= true -> triq_rnd:uniform(SampleSize)-1 end, %% %% TODO: if ElemDom is "simple" no need to build template %% %% generate template Domain and corresponding List {ListDom,List} = foldn(fun({Dom,T}) -> {EDom,E} = pick(ElemDom,SampleSize), {[EDom|Dom], [E|T]} end, {[],[]}, OutLen), shrinkable_list(ListDom, List, OutLen, EmptyOK). %% oops, if length==1 and EmptyOK=false; just return the fixed list shrinkable_list(ListDom, List, 1, false) -> {ListDom, List}; shrinkable_list(_, [], 0, _) -> {[], []}; shrinkable_list(ListDom, List, Len, EmptyOK) -> ?assert(length(List) == length(ListDom)), ?assert(length(List) == Len), SDom = #?DOM{kind={shrinkable_list, ListDom, Len}, shrink=fun list_shrink/2, empty_ok=EmptyOK}, {SDom,List}. list_shrink(#?DOM{kind={shrinkable_list, ListDom, Len}, empty_ok=EmptyOK}, List) -> ?assert(length(List) == Len), SmallerOK = ((EmptyOK and (Len>0)) or (Len>1)), case SmallerOK and (triq_rnd:uniform(5) == 1) of true -> shorter_list(ListDom,List,Len,EmptyOK); false -> case shrink(ListDom,List) of {_, List} when SmallerOK -> shorter_list(ListDom,List,Len,EmptyOK); {ShrunkenListDom,ShrunkenList} -> shrinkable_list(ShrunkenListDom, ShrunkenList, Len, EmptyOK) end end. shorter_list(ListDom,List,Len,EmptyOK) -> {ShorterListDom, ShorterList, ShorterLen} = case triq_rnd:uniform(3) of 1 -> %% Remove one element. RemIdx = triq_rnd:uniform(Len), {without(RemIdx, ListDom), without(RemIdx, List), Len-1}; 2 -> %% Remove or keep a random sublist. Idx1 = triq_rnd:uniform(Len), Idx2 = triq_rnd:uniform(Len), if Idx1 < Idx2 -> %% Remove the sublist [Idx1;Idx2] {without(Idx1,Idx2, ListDom), without(Idx1,Idx2, List), Len-(Idx2-Idx1)}; true -> %% Remove all but the sublist [Idx2;Idx1] ShorterLen1 = Idx1-Idx2+1, {lists:sublist(ListDom, Idx2, ShorterLen1), lists:sublist(List, Idx2, ShorterLen1), ShorterLen1} end; 3 -> %% Remove a random sublist. Zipped = lists:zip(ListDom, List), TrueTreshold = triq_rnd:uniform(), FalseTreshold = triq_rnd:uniform(), %% This may happen to be the original list again. Pruned = markov_prune_list(Zipped, TrueTreshold, FalseTreshold, false), {ListDom2,List2} = lists:unzip(Pruned), {ListDom2, List2, length(Pruned)} end, {NewListDom, NewList, NewLen} = case {EmptyOK, ShorterLen} of {false, 0} -> {ListDom, List, Len}; _ -> {ShorterListDom, ShorterList, ShorterLen} end, shrinkable_list(NewListDom, NewList, NewLen, EmptyOK). markov_prune_list([], _,_,_) -> []; markov_prune_list([H|T], TrueTreshold, FalseTreshold, Prev) -> Rnd = triq_rnd:uniform(), Threshold = if Prev -> TrueTreshold; true -> FalseTreshold end, Include = Rnd > Threshold, NewTail = markov_prune_list(T, TrueTreshold, FalseTreshold, Include), if Include -> [H|NewTail]; true -> NewTail end. %% %% Generator for tuples %% %% @spec tuple(domain(ElemType::any())) -> domain(tuple(ElemType)) -spec tuple(domain(any())) -> domrec(tuple()). tuple(ElemDom) -> #?DOM{kind=#tuple{elem=ElemDom}, pick=fun tuple_pick/2 }. tuple_pick(#?DOM{kind=#tuple{elem=ElemDom},empty_ok=EmptyOK}, SampleSize) -> OutLen = if EmptyOK =:= false -> triq_rnd:uniform(SampleSize); EmptyOK =:= true -> triq_rnd:uniform(SampleSize)-1 end, %% %% TODO: if ElemDom is "simple" no need to build template %% %% generate template Domain and corresponding Tuple {ListDom,List} = foldn(fun({Dom,T}) -> {EDom,E} = pick(ElemDom,SampleSize), {[EDom|Dom], [E|T]} end, {[],[]}, OutLen), shrinkable_tuple(list_to_tuple(ListDom), list_to_tuple(List), EmptyOK). %% If length==1 and EmptyOK=false; just return the fixed tuple shrinkable_tuple(TupleDom, Tuple, false) when tuple_size(Tuple) =:= 1 -> {TupleDom, Tuple}; shrinkable_tuple(TupleDom, Tuple, EmptyOK) -> ?assert(tuple_size(Tuple) == tuple_size(TupleDom)), SDom = #?DOM{kind={shrinkable_tuple, TupleDom}, shrink=fun tuple_shrink/2, empty_ok=EmptyOK}, {SDom,Tuple}. tuple_shrink(#?DOM{kind={shrinkable_tuple, TupleDom}, empty_ok=EmptyOK}, Tuple) -> AllowSmaller = allow_smaller(tuple_size(Tuple), any, EmptyOK), case shrink(TupleDom,Tuple) of {_, Tuple} when AllowSmaller -> RemIdx = triq_rnd:uniform(tuple_size(Tuple)), shrinkable_tuple(without(RemIdx, TupleDom), without(RemIdx, Tuple), EmptyOK); Result -> Result end. %% @doc The domain of integers. %% @spec int() -> domain(integer()) -spec int() -> domrec(integer()). int() -> #?DOM{kind=int, shrink=fun(Dom,Val) when Val>0 -> {Dom,Val-1}; (Dom,Val) when Val<0 -> {Dom,Val+1}; (Dom,0) -> {Dom,0} end, pick=fun(Dom,SampleSize) -> {Dom, triq_rnd:uniform(SampleSize) - (SampleSize div 2)} end }. int(Max) -> choose(0, Max). int(Min, Max) -> choose(Min, Max). -spec byte() -> domrec(integer()). byte() -> int(0, 255). %% @doc The domain of non-negative integers. %% @spec non_neg_integer() -> domain(non_neg_integer()) -spec non_neg_integer() -> domrec(non_neg_integer()). non_neg_integer() -> #?DOM{ kind=int, shrink=fun(Dom,Val) when Val>0 -> {Dom,Val-1}; (Dom,0) -> {Dom,0} end, pick=fun(Dom,SampleSize) -> {Dom, triq_rnd:uniform(SampleSize) - 1} end }. %% @doc The domain of positive integers. %% @spec pos_integer() -> domain(pos_integer()) -spec pos_integer() -> domrec(pos_integer()). pos_integer() -> #?DOM{ kind=int, shrink=fun(Dom,Val) when Val>1 -> {Dom,Val-1}; (Dom,1) -> {Dom,1} end, pick=fun(Dom,SampleSize) -> {Dom, abs(triq_rnd:uniform(SampleSize)) + 1} end }. %% @doc The domain of "big" integers. %% %% Note, this is sized to ensure it remains a big integer, even on 64 %% bit implementations. %% @spec largeint() -> domrec(largeint()) largeint() -> #?DOM{ kind=largeint, shrink=fun(Dom,0) -> {Dom,0}; (Dom,Val) -> {Dom,Val div 10} end, pick=fun(Dom,SampleSize) -> Val = trunc(?BIGNUM * SampleSize * triq_rnd:uniform()), Sign = case triq_rnd:uniform(2) of 2 -> -1; 1 -> 1 end, {Dom, Sign * Val} end }. -spec(float() ->domrec(float())). float() -> real(). %% @doc The domain of floats. %% @spec real() -> domain(float()) -spec real() -> domrec(float()). real() -> #?DOM{ kind=real, pick=fun(Dom,SampleSize) -> {Dom, (triq_rnd:uniform()*SampleSize) - (SampleSize / 2)} end, shrink=fun(Dom,Val) -> {Dom, Val/2.0} end }. %% @doc The domain of booleans. Shrinks to false. %% @spec bool() -> domain( true | false ) bool() -> #?DOM{ kind=boolean, pick=fun(Dom,_) -> {Dom, triq_rnd:uniform(2)==1} end, shrink=fun(_,_) -> {false, false} end }. -spec char() -> domrec(32..126). char() -> #?DOM{ kind=char, pick=fun(Dom,_) -> {Dom, $a + triq_rnd:uniform($z - $a + 1)-1} end, shrink=fun(Dom,V) when V =< $c -> {Dom,V}; (Dom,N) when N > $c, N =< $z -> {Dom,N - triq_rnd:uniform(3)} end }. -spec binary() -> domrec(binary()). binary() -> #?DOM{kind=#binary{size=any}, pick=fun binary_pick/2, shrink=fun binary_shrink/2}. -spec binary(Size::non_neg_integer()) -> domrec(binary()). binary(Size) -> #?DOM{kind=#binary{size=Size}, pick=fun binary_pick/2, shrink=fun binary_shrink/2}. binary_pick(#?DOM{kind=#binary{size=Size}, empty_ok=EmptyOK}=BinDom, SampleSize) -> Sz = case Size of any -> case EmptyOK of true -> triq_rnd:uniform(SampleSize)-1; false -> triq_rnd:uniform(SampleSize) end; Size -> Size end, BinValue = list_to_binary(foldn(fun(T) -> [triq_rnd:uniform(256)-1 | T] end, [], Sz)), {BinDom, BinValue}. allow_smaller(Len,any,true) when Len>0 -> true; allow_smaller(Len,any,false) when Len>1 -> true; allow_smaller(_,_,_) -> false. binary_shrink(#?DOM{kind=#binary{size=Size}, empty_ok=EmptyOK}=BinDom, BinValue) -> List = binary_to_list(BinValue), Length = byte_size(BinValue), AllowSmaller = allow_smaller(Length,Size,EmptyOK) , case shrink_list_with_elemdom(int(), List, Length, AllowSmaller) of List -> {BinDom, BinValue}; NewList -> {BinDom, list_to_binary(NewList)} end. %% @doc The domain of bitstrings %% @spec bitstring() -> domain(bitstring()) -spec bitstring() -> domrec(bitstring()). bitstring() -> #?DOM{kind=#bitstring{size=any}, pick=fun bitstring_pick/2, shrink=fun bitstring_shrink/2}. -spec bitstring(Size::non_neg_integer()) -> domrec(bitstring()). bitstring(Size) -> #?DOM{kind=#bitstring{size=Size}, pick=fun bitstring_pick/2, shrink=fun bitstring_shrink/2}. bitstring_pick(#?DOM{kind=#bitstring{size=Size}, empty_ok=EmptyOK}=BinDom, SampleSize) -> Sz = case Size of any -> case EmptyOK of true -> triq_rnd:uniform(SampleSize)-1; false -> triq_rnd:uniform(SampleSize) end; Size -> Size end, BinValue = list_to_bitstring(foldn(fun(T) -> Int = triq_rnd:uniform(256) - 1, Bit = triq_rnd:uniform(8), [<> | T] end, [], Sz)), {BinDom, BinValue}. bitstring_shrink(#?DOM{kind=#bitstring{size=Size}, empty_ok=EmptyOK}=BinDom, BinValue) -> List = bitstring_to_list(BinValue), Length = byte_size(BinValue), AllowSmaller = allow_smaller(Length,Size,EmptyOK), case shrink_list_with_elemdom(int(), List, Length, AllowSmaller) of List -> {BinDom, BinValue}; NewList -> {BinDom, list_to_bitstring(NewList)} end. %% @doc The domain of atoms %% @spec atom() -> domain(integer()) -spec atom() -> domrec(atom()). atom() -> #?DOM{kind=#atom{size=any}, pick=fun atom_pick/2, shrink=fun atom_shrink/2}. -spec atom(non_neg_integer()) -> domrec(atom()). atom(Size) -> #?DOM{kind=#atom{size=Size}, pick=fun atom_pick/2, shrink=fun atom_shrink/2}. atom_pick(#?DOM{kind=#atom{size=Size}, empty_ok=EmptyOK}=AtomDom, SampleSize) -> Sz = case Size of any -> case EmptyOK of true -> triq_rnd:uniform(xmin(SampleSize,256))-1; false -> triq_rnd:uniform(xmin(SampleSize,256)) end; Size -> Size end, CharDom = char(), Fun=fun(T) -> {_,Char} = pick(CharDom,SampleSize), [Char | T] end, AtomValue = list_to_atom(foldn(Fun, [], Sz)), {AtomDom, AtomValue}. xmin(A,B) when A A; xmin(A,B) when B B; xmin(A,B) when A==B -> A. atom_shrink(#?DOM{kind=#atom{size=Size}, empty_ok=EmptyOK}=AtomDom, AtomValue) -> List = atom_to_list(AtomValue), Length = length(List), AllowSmaller = allow_smaller(Length,Size,EmptyOK) , case shrink_list_with_elemdom(char(), List, Length, AllowSmaller) of List -> {AtomDom, AtomValue}; NewList -> {AtomDom, list_to_atom(NewList)} end. vector(Size,ElemDom) -> #?DOM{kind=#vector{size=Size,elem=ElemDom}, pick=fun vector_pick/2}. vector_pick(#?DOM{kind=#vector{size=Size,elem=ElemDom}}, SampleSize) -> foldn(fun({TDom,T}) -> {HDom,H} = pick(ElemDom, SampleSize), {[HDom|TDom], [H|T]} end, {[], []}, Size). %% %% @doc Shrink `List' where all elements have the same domain `ElemDom'. %% If parameter `AllowSmaller' is true, then we may also make the list %% shorter. %% @end shrink_list_with_elemdom(_,List,0,_) -> List; shrink_list_with_elemdom(ElemDom,List,Length,AllowSmaller) -> %% 1/5 of the time, try shrinking by removing an elemet case AllowSmaller andalso shrink_smaller(Length) of true -> RemoveIdx = triq_rnd:uniform(Length), without(RemoveIdx, List); false -> HowManyToShrink = shrink_members(Length), case shrink_list_members_generic(ElemDom, List, Length, HowManyToShrink) of %% can we remove an element? List when AllowSmaller -> RemIdx = triq_rnd:uniform(Length), without(RemIdx, List); %% it changed! ShrunkenList -> ShrunkenList end end. %% decide if something of size `Length' should be shrunk by removing an element shrink_smaller(_Length) -> triq_rnd:uniform(5)==1. %% decide how many of shrink_members(0) -> 0; shrink_members(Length) when Length>0 -> case triq_rnd:uniform(5) of 1 -> triq_rnd:uniform(5); _ -> 1 end. %% %% Same, but when component element is fixed %% (also returns List, not {Dom,List})! %% -spec shrink_list_members_generic(domain(T), [T], non_neg_integer(), integer()) -> [T]. shrink_list_members_generic(_, List, _, 0) -> List; shrink_list_members_generic(#?DOM{}=ElemDom, List, Len, HowMany) -> NextList = shrink_list_N(ElemDom,List,Len, ?SHRINK_LOOPS), shrink_list_members_generic(ElemDom, NextList, Len, HowMany-1). shrink_list_N(_, List, _, 0) -> List; shrink_list_N(#?DOM{}=ElemDom, List, Len, N) -> ?assert(Len == length(List)), %% %% replace element at RemIdx with simplified one %% RemIdx = triq_rnd:uniform(Len), Elm = lists:nth(RemIdx, List), %% io:format("shrinking elem ~p (~p) of ~p~n", [RemIdx,Elm,List]), case shrink(ElemDom,Elm) of {_,Elm} -> shrink_list_N(ElemDom, List, Len, N-1); {_,SElm} -> lists:sublist(List,RemIdx-1) ++ [SElm] ++ lists:sublist(List,RemIdx+1,Len) end. non_empty(#?DOM{}=Dom) -> Dom#?DOM{empty_ok=false}. %% @doc Tell whether the domain can possibly be shrinked. %% @private -spec is_shrinkable(domain(_)) -> boolean(). is_shrinkable(#?DOM{shrink=none}) -> false; is_shrinkable(_) -> true. %% @doc Support function for the `?LET(Vars,Dom1,Dom2)' macro. %% @private %% @spec bind(domain(T), fun( (T) -> domain(D) )) -> domain(D) -spec bind(domain(T::any()), fun( (T::any()) -> domain(D) )) -> domain(D). bind(Gen1,FG2) -> #?DOM{kind=#bind{dom=Gen1,body=FG2}, pick = fun bind_pick/2 }. -spec bind_pick(domain(T),pos_integer()) -> T. bind_pick(#?DOM{kind=#bind{dom=Dom,body=Fun}}, SampleSize) -> {Dom1,Val1} = pick(Dom, SampleSize), {Dom2,Val2} = pick( Fun(Val1), SampleSize), { bound_domain(Dom1,Val1,Dom2,Fun, SampleSize), Val2 }. bound_domain(Dom1,Val1,Dom2,Fun,SampleSize) -> #?DOM{kind=#bound_domain{dom1=Dom1,val1=Val1,dom2=Dom2,fun2=Fun, size=SampleSize}, shrink= fun bound_shrink/2 }. bound_shrink(#?DOM{kind=#bound_domain{dom1=Dom1,val1=Val1,dom2=Dom2,fun2=Fun, size=SampleSize}}, Val2) -> case shrink(Dom1,Val1) of %% it did not shrink val1 {_,Val1} -> %% try to shrink the secondary domain shrink(Dom2,Val2); %% Val1 did shrink! {SDom1,SVal1} -> %% pick a new value from the secondary domain {SDom2,SVal2} = pick( Fun(SVal1), SampleSize), %% and return the new bound domain { bound_domain(SDom1,SVal1,SDom2,Fun, SampleSize), SVal2 } end. %% @doc support function for `?LETSHRINK([X,...],[domain(),...],domain())' %% @private bindshrink(Dom,_Fun) when not is_list(Dom) -> error(argument_to_LETSHRINK_macro_must_be_lists); bindshrink(Dom,Fun) when is_function(Fun,1) -> domain(letshrink, fun(_,SampleSize) -> Box1 = {_,List1} = pick(Dom, SampleSize), ?assert(is_list(List1) and (length(List1)>0) ), Box2 = {_,List2} = pick(Fun(List1), SampleSize), { bindshrink2(Box1,Box1,Box2,Fun,SampleSize), List2 } end, undefined). bindshrink2(OrigBox1,Box1,Box2,Fun,SampleSize) -> domain(letshrink2, undefined, fun(_,_) -> {Dom2,Val2}=Box2, case shrink(Dom2,Val2) of {_,Val2} -> case shrink({_,List1}=Box1) of {_, List1} -> {OrigDom1,OrigList1}=OrigBox1, Index = triq_rnd:uniform(length(OrigList1)), { lists:nth(Index,OrigDom1), lists:nth(Index,OrigList1) }; {_,NewList1}=NewBox1 -> NewBox2 = pick(Fun(NewList1), SampleSize), {_,NewVal2} = NewBox2, { bindshrink2(OrigBox1, NewBox1, NewBox2, Fun, SampleSize), NewVal2 } end; {_, NewVal2}=NewBox2 -> { bindshrink2(OrigBox1, Box1, NewBox2, Fun, SampleSize), NewVal2 } end end). %% @doc Support function for the ?SUCHTHAT macro. %% @private %% @spec suchthat(domain(T),fun((T) -> boolean())) -> domain(T) -spec suchthat(domain(T),fun((T) -> boolean())) -> domain(T). suchthat(Dom,Predicate) -> #?DOM{kind=#suchthat{dom=Dom,pred=Predicate}, pick=fun suchthat_pick/2, shrink=fun suchthat_shrink/2}. suchthat_pick(#?DOM{kind=#suchthat{dom=Dom,pred=Pred}},SampleSize) -> suchthat_pick_loop(?SUCHTHAT_LOOPS,Dom,Pred,SampleSize). suchthat_shrink(#?DOM{kind=#suchthat{dom=Dom,pred=Pred}},Val) -> suchthat_shrink_loop(?SUCHTHAT_LOOPS, Dom, Pred, Val). suchthat_pick_loop(0,_,_,_) -> erlang:exit(suchthat_failed); suchthat_pick_loop(N,Dom,Pred,SampleSize) -> {ValDom,Val} = pick(Dom,SampleSize), case Pred(Val) of true -> {suchthat(ValDom, Pred), Val}; %% If we failed, we make another try with a higher sample size, %% as there may be not enough variance within our current sample %% size. _ -> suchthat_pick_loop(N - 1, Dom, Pred, SampleSize + 2) end. suchthat_shrink_loop(0, Dom, Pred, Val) -> {suchthat(Dom, Pred), Val}; suchthat_shrink_loop(N, Dom, Pred, Val) -> case shrink(Dom, Val) of {Dom, Val} -> {suchthat(Dom, Pred), Val}; {ShrinkedDom, ShrinkedVal} -> case Pred(ShrinkedVal) of true -> {suchthat(ShrinkedDom, Pred), ShrinkedVal}; _ -> suchthat_shrink_loop(N - 1, Dom, Pred, Val) end end. smaller(Domain) -> ?SIZED(SZ, resize(triq_rnd:uniform((SZ div 2)+1), Domain)). -spec any() -> domain(any()). any() -> oneof([int(), real(), bool(), atom(), [smaller(?DELAY(any())), smaller(?DELAY(any()))], %% list(any()), but with a size in the range 1..GenSize list(smaller(?DELAY(any()))), tuple(smaller(?DELAY(any()))) ]). -spec oneof([domain(T)]) -> domain(T). oneof(DomList) when is_list(DomList) -> #?DOM{kind=#oneof{elems=DomList, size=length(DomList)}, pick=fun oneof_pick/2 }. oneof_pick(#?DOM{kind=#oneof{elems=DomList, size=Length}}, SampleSize) -> Dom = lists:nth(triq_rnd:uniform(Length), DomList), pick(Dom, SampleSize). %% -------------------------------------------------------------- %% @doc Choose domain from list [{Weight, Domain}, ...] %% @end %% -------------------------------------------------------------- frequency(GenList) when is_list(GenList) -> Sum = lists:foldl(fun({Freq, _}, Acc) -> Freq + Acc end, 0, GenList), domain(frequency, fun(_,SampleSize) -> Limit = triq_rnd:uniform(Sum), {ok,Gen} = lists:foldl( fun (_, {ok, _}=Acc) -> Acc; ({Freq,Generator}, {AccSum, none}) -> NextAcc = AccSum+Freq, if NextAcc >= Limit -> {ok, Generator}; true -> {NextAcc, none} end end, {0, none}, GenList), pick(Gen, SampleSize) end, undefined). %% @doc Returns the domain containing exactly `Value'. %% Triq uses internally records of type `@'; and so to avoid %% interpretation of such values you can wrap it with this. This would %% be the case if you have constants in your domains contain the atom `@'. %% I.e., the following would break because Triq tries to interpret the `@': %%
?FORALL(X, [int(), {'@', 4}],
%%   [IntVal, {'@', 4}] = X
%%)
%%To fix it, do like this: %%
?FORALL(X, [int(), return({'@', 4})],
%%   [IntVal, {'@', 4}] = X
%%)
%% @spec return(Value::Type) -> domain(Type) -spec return(Value::Type) -> domain(Type). return(Val) -> domain(return, fun(Self,_) -> {noshrink(Self),Val} end, fun error_shrink/2). %% @doc Support function for the ?SIZED macro. %% @spec sized( fun((integer()) -> domain(T)) ) -> domain(T) -spec sized( fun((integer()) -> domain(T)) ) -> domain(T). sized(Fun) -> #?DOM{kind=#sized{body=Fun}, pick=fun(#?DOM{kind=#sized{body=F}},GS) -> pick(F(GS),GS) end }. resize(Sz,Dom) -> #?DOM{kind=#resize{dom=Dom,size=Sz}, pick=fun(#?DOM{kind=#resize{dom=D,size=SampleSize}},_) -> pick(D,SampleSize) end }. noshrink_pick(#?DOM{kind=#noshrink{dom=InnerDom}}, Size) -> {Dom, Val} = pick(InnerDom, Size), {noshrink(Dom), Val}. -spec noshrink(domain(T)) -> domain(T). noshrink(Dom) -> #?DOM{ kind=#noshrink{dom=Dom}, pick=fun noshrink_pick/2, shrink=none}. -spec choose(M::integer(), N::integer()) -> domrec(integer()). choose(M,N) when is_integer(M), is_integer(N), M= ShrinkTo = case {M>=0, N>=0} of {true, true} -> M; {false, true} -> 0; {false, false} -> N end, #?DOM{kind=#choose{min=M,max=N,shrinkto=ShrinkTo}, pick = fun choose_pick/2, shrink = fun choose_shrink/2 }. choose_pick(#?DOM{kind=#choose{min=M,max=N}}=Dom, _) -> Value = triq_rnd:uniform(N-M+1) - 1 + M, {Dom,Value}. choose_shrink(#?DOM{kind=#choose{shrinkto=Value}}=Dom, Value) -> {Dom, Value}; choose_shrink(#?DOM{kind=#choose{shrinkto=ShrinkTo}}=Dom, Value) -> DecrementProb = 2 * math:exp(-0.1 * abs(Value - ShrinkTo)), case triq_rnd:uniform() < DecrementProb of true -> {Dom, choose_shrink_by_decrement(Value, ShrinkTo)}; false -> {Dom, choose_shrink_by_half(Value, ShrinkTo)} end. choose_shrink_by_half(Value, ShrinkTo) -> Mid = (Value - ShrinkTo) div 2, ShrinkTo + Mid. choose_shrink_by_decrement(Value, ShrinkTo) when Value > ShrinkTo -> Value - 1; choose_shrink_by_decrement(Value, ShrinkTo) when Value < ShrinkTo -> Value + 1; choose_shrink_by_decrement(Value, Value) -> Value. %% @doc Generates a member of the list `L'. Shrinks towards the first element %% of the list. %% @spec elements([any()]) -> domain(any()) %% @end -spec elements([T,...]) -> domain(T). elements(L) when is_list(L), length(L)>0 -> #?DOM{kind=#elements{elems=L,size=length(L)}, pick = fun elements_pick/2, shrink = fun elements_shrink/2 }. -spec elements_pick(domain(T), pos_integer()) -> {domain(T), T}. elements_pick(#?DOM{kind=#elements{elems=Elems,size=Length}=Kind}=Dom, _) -> Picked = triq_rnd:uniform(Length), Value = lists:nth(Picked,Elems), { Dom#?DOM{kind=Kind#elements{picked=Picked}}, Value }. elements_shrink(#?DOM{kind=#elements{elems=Elems,picked=Picked}=Kind}=Dom, _) when Picked > 1 -> Value = lists:nth(Picked-1,Elems), { Dom#?DOM{kind=Kind#elements{picked=Picked-1}}, Value }; elements_shrink(Dom,Value) -> { Dom, Value }. shrink_without_duplicates(Dom) -> domain(shrink_without_duplicates1, fun(_,GS) -> {Dom,Val} = pick(Dom,GS), Tested = gb_sets:add(Val, gb_sets:new()), {shrink_without_duplicates(Dom,Tested), Val} end, undefined). shrink_without_duplicates(Dom,Tested) -> domain(shrink_without_duplicates2, undefined, fun(_,Val) -> shrink_without_duplicates_loop(Dom,Val,Tested,?SHRINK_LOOPS) end). shrink_without_duplicates_loop(_,Val,_,0) -> Val; shrink_without_duplicates_loop(Dom,Val,Tested,Tries) -> {Dom2,Val2} = shrink(Dom,Val), case gb_sets:is_member(Val2, Tested) of true -> shrink_without_duplicates_loop(Dom,Val,Tested,Tries-1); false -> {shrink_without_duplicates(Dom2, gb_sets:add(Val, Tested)), Val2} end. %%-------------------------------------------------------------------- %% @doc Generate a sample of output values from a generator. %% This should not be used except for REPL testing purposes; it will %% only ever generate fairly small-valued samples. %% %% @spec sample( domain(T) ) -> [T] %% @end %%-------------------------------------------------------------------- sample(Dom) -> foldn(fun(T) -> {_,Val} = pick(Dom, 20 + triq_rnd:uniform(10) ), [Val|T] end, [], 11). %%------------------------------------------------------------------- %% @doc Get the domain of boxes of T %% @spec seal(domain(T)) -> domain(box(T)) %% @end %%------------------------------------------------------------------- -spec seal(Dom::domain(T)) -> domrec(box(T)). seal(Dom) -> Seed = triq_rnd:seed(), triq_rnd:seed(Seed), #?DOM{kind=#seal{dom=Dom,seed=Seed}, pick=fun seal_pick/2}. seal_pick(#?DOM{kind=#seal{dom=Dom,seed=Seed}}, SampleSize) -> OldSeed = triq_rnd:seed(Seed), {BoxDom,BoxValue} = pick(Dom,SampleSize), triq_rnd:seed(OldSeed), #?BOX{dom=BoxDom,value=BoxValue}. %%------------------------------------------------------------------- %% @doc Open a box, yielding a domain which always generates the same value. %% @spec open(box(T)) -> domain(T) %% @end %%------------------------------------------------------------------- -spec open(Box::box(T)) -> domain(T). open(#?BOX{}=Box) -> #?DOM{kind=Box, pick=fun box_pick/2}. box_pick(#?DOM{kind=#?BOX{dom=Dom,value=Value}}, _) -> {Dom,Value}. -spec peek(box(T)) -> T. peek(#?BOX{value=Value}) -> Value. %%------------------------------------------------------------------ %% @doc Print a value generated by `Domain', followed by a sample of shrinkings. %% For each line of successive output, it prints up to five samples of %% shrinking. The first value on each like is used as the target for the next %% round of shrinking. %% %%
 1> sampleshrink(list(int())).
%%[-2,-8,2]
%%[[-1,-8,2],[0,-8,2],[-1,-7,2],[-2,-8,1],[-1,-8,1]]
%%[[0,-8,2],[0,-6,1],[-1,-7,2],[0,-7,2]]
%%[[0,-8,0],[0,-7,0],[0,-7,2],[0,-8,1],[0,-5,2],[0,-7,1]]
%%[[0,-7,0],[0,-5,0]]
%%[[0,-5,0],[0,-6,0]]
%%[[0,-4,0],[0,-3,0]]
%%[[0,-2,0],[0,-3,0],[0,-1,0]]
%%[[0,-1,0]]
%%[[0,0,0]]
%%[[0,0]]
%%[[0]]
%%[[]]
%%ok
%% @spec sampleshrink(domain(any())) -> ok %% @end %%------------------------------------------------------------------ -spec sampleshrink(domain(any())) -> ok. sampleshrink(Domain) -> {Dom2,Value} = pick(Domain, 20), io:format("~p~n", [Value]), sampleshrink_loop(Dom2,Value). sampleshrink_loop(Dom,Val) -> case shrink(Dom,Val) of {_,Val} -> ok; {Dom2,Val2} -> Samples = foldn(fun(T) -> {_,V} = shrink(Dom,Val), case lists:member(V,T) of true -> T; false -> [V|T] end end, [Val2], 5), io:format("~p~n", [lists:reverse(Samples)]), sampleshrink_loop(Dom2,Val2) end. %%------------------------------------------------------------------------ %% @doc Create custom domain. %% This function allows you to create a custom domain with it's own %% shrinking logic. For instance, the even numbers can be specified thus: %% %%
even() ->
%%    domain(even,
%%      fun(Self,Size) ->
%%            Value = (triq_rnd:uniform(Size) * 2) div 2,
%%            {Self, Value}
%%      end,
%%      fun(Self,Value) when Value>0 ->
%%            {Self, Value-2};
%%         (Self,_,0) ->
%%            {0, 0}
%%      end).
%% %% The domain itself (`Self' in the above code) is passed as the first argument %% to each invocation of both the picking and the shrinking functions. %% %% Both the picking and the shrinking function must return a 2-tuple of %% the domain of the resulting value, and the value itself. %% %% @spec domain(Name::any(), %% PickFun :: pick_fun(T), %% ShrinkFun :: shrink_fun(T)) -> domain(T) %% @end %%------------------------------------------------------------------------ -spec domain(Name::atom(), PickFun::pick_fun(T), ShrinkFun::shrink_fun(T)) -> domain(T). domain(Name,PickFun,ShrinkFun) -> #?DOM{kind=Name, pick=PickFun, shrink=ShrinkFun}. %% %% Utility functions %% foldn(_,Acc,0) -> Acc; foldn(Fun,Acc,Count) when Count > 0 -> foldn(Fun, Fun(Acc), Count-1). %% remove the RemIdx'th element of List [1-indexed] without(RemIdx,List) when is_list(List) -> {First,Rest} = lists:split(RemIdx-1,List), First ++ tl(Rest); without(RemIdx,Tup) when is_tuple(Tup) -> list_to_tuple(without(RemIdx, tuple_to_list(Tup))). %% remove the RemIdx1 through RemIdx2-1'th element of List [1-indexed] without(RemIdx1, RemIdx2, List) when is_list(List) -> {First,Tail} = lists:split(RemIdx1-1,List), {_Middle,Rest} = lists:split(RemIdx2-RemIdx1,Tail), First ++ Rest. repeat(_,0) -> ok; repeat(Fun,N) -> Fun(), repeat(Fun,N-1). %% Code points in the range U+D800..U+DBFF (1,024 code points) are known as %% high-surrogate code points, and code points in the range U+DC00..U+DFFF %% (1,024 code points) are known as low-surrogate code points. %% A high-surrogate code point (also known as a leading surrogate) followed %% by a low-surrogate code point (also known as a trailing surrogate) %% together form a surrogate pair used in UTF-16 to represent 1,048,576 %% code points outside BMP. %% High and low surrogate code points are not valid by themselves. Thus the %% range of code points that are available for use as characters is %% U+0000..U+D7FF and U+E000..U+10FFFF (1,112,064 code points). %% The value of these code points (i.e. excluding surrogates) is sometimes %% referred to as the character's scalar value. -define(UNICODE_CHAR_SHRINK_STEP, 3). -spec unicode_char() -> domrec(uchar()). unicode_char() -> P = fun(Dom,_) -> {Dom, random_unicode_char()} end, S = fun(Dom,V) -> NewV = case (V - triq_rnd:uniform(?UNICODE_CHAR_SHRINK_STEP)) of X when X < 0 -> V; X when X >= 16#D800, X =< 16#DFFF -> %% skip surrogates. 16#D799; X when X =:= 16#FFFF; X =:= 16#FFFE -> 16#FFFD; X -> X end, {Dom, NewV} end, #?DOM{ kind=unicode_char, pick=P, shrink=S}. -spec random_unicode_char() -> uchar(). random_unicode_char() -> case (triq_rnd:uniform(16#10FFFF + 1) - 1) of C when C >= 16#D800 andalso C =< 16#DFFF -> %% surrogates random_unicode_char(); 16#FFFF -> random_unicode_char(); 16#FFFE -> random_unicode_char(); C -> C end. %% @doc Generate a list of unicode code points. -spec unicode_string() -> domrec([uchar()]). unicode_string() -> list(unicode_char()). %% @doc Generate a list of unicode code points of length `Size'. -spec unicode_string(non_neg_integer()) -> domrec([uchar()]). unicode_string(Size) -> vector(Size, unicode_char()). -spec unicode_binary() -> domrec(binary()). unicode_binary() -> unicode_binary(any, utf8). %% @doc Generate an unicode binary binary. -spec unicode_binary(Size | Encoding) -> domrec(binary()) when Size :: non_neg_integer(), Encoding :: unicode:encoding(). unicode_binary(Size) when is_integer(Size) -> unicode_binary(Size, utf8); unicode_binary(Encoding) -> unicode_binary(any, Encoding). %% @doc Generate an unicode binary. -spec unicode_binary(Size, Encoding) -> domrec(binary()) when Size :: non_neg_integer() | 'any', Encoding :: unicode:encoding(). unicode_binary(Size, Encoding) -> #?DOM{kind=#unicode_binary{size=Size, encoding=Encoding}, pick=fun unicode_binary_pick/2, shrink=fun unicode_binary_shrink/2}. unicode_binary_pick(#?DOM{kind=#unicode_binary{size=Size, encoding=Encoding}, empty_ok=EmptyOK}=BinDom, SampleSize) when SampleSize > 1 -> Sz = case Size of any -> case EmptyOK of true -> triq_rnd:uniform(SampleSize)-1; false -> triq_rnd:uniform(SampleSize) end; Size -> Size end, CharList = foldn(fun(T) -> [random_unicode_char() | T] end, [], Sz), BinValue = unicode:characters_to_binary(CharList, unicode, Encoding), {BinDom, BinValue}. unicode_binary_shrink(#?DOM{kind=#unicode_binary{size=Size, encoding=Encoding}, empty_ok=EmptyOK}=BinDom, BinValue) -> List = unicode:characters_to_list_int(BinValue, utf8), Length = strlen(List), AllowSmaller = allow_smaller(Length,Size,EmptyOK), case shrink_list_with_elemdom(unicode_char(), List, Length, AllowSmaller) of List -> {BinDom, BinValue}; NewList -> NewBin = unicode:characters_to_binary(NewList, unicode, Encoding), {BinDom, NewBin} end. strlen(L) -> case erlang:function_exported(string, length, 1) of true -> string:length(L); false -> apply(string, len, [L]) end. -spec unicode_characters() -> domrec(uchars()). unicode_characters() -> unicode_characters(unicode). %% `unicode_characters()' should not return a single `unicode_char()'. -spec unicode_characters(Encoding) -> domrec(uchars()) when Encoding :: unicode:encoding(). unicode_characters(Encoding) -> ?SIZED(Size, frequency([{1, unicode_string()}, {1, unicode_binary(Encoding)}, {5, ?DELAY(resize(Size div 2, unicode_characters1(Encoding)))} ])). unicode_characters1(Encoding) -> ?SIZED(Size, unicode_characters1(Size, Encoding)). unicode_characters1(0, _Encoding) -> list(unicode_char()); unicode_characters1(Size, Encoding) -> Chars = ?DELAY(resize(Size, unicode_characters(Encoding))), %% TODO: Unicode characters can be of type `maybe_improper_list()'. list(frequency([{10,unicode_char()}, {1, Chars}])).