diff --git a/include/proper.hrl b/include/proper.hrl index 803408ae..fff62bcd 100644 --- a/include/proper.hrl +++ b/include/proper.hrl @@ -46,10 +46,10 @@ %% Basic types %%------------------------------------------------------------------------------ --import(proper_types, [integer/2, float/2, atom/0, binary/0, binary/1, - bitstring/0, bitstring/1, list/1, vector/2, union/1, - weighted_union/1, tuple/1, loose_tuple/1, exactly/1, - fixed_list/1, function/2, map/2, any/0]). +-import(proper_types, [integer/2, float/2, atom/0, existing_atom/0, binary/0, + binary/1, bitstring/0, bitstring/1, list/1, vector/2, + union/1, weighted_union/1, tuple/1, loose_tuple/1, + exactly/1, fixed_list/1, function/2, map/2, any/0]). %%------------------------------------------------------------------------------ diff --git a/src/proper.erl b/src/proper.erl index 06ab14b9..60969728 100644 --- a/src/proper.erl +++ b/src/proper.erl @@ -297,6 +297,10 @@ %%%
`{stop_nodes, boolean()}'
%%%
Specifies whether parallel PropEr should stop the nodes after running a property %%% or not. Defaults to true.
+%%%
`{default_atom_generator, atom | existing_atom}'
+%%%
Declares the type of atom generator to use in the {@link proper_types:any/0. `any()'} +%%% generator to be either {@link proper_types:atom/0. `atom()'} or +%%% {@link proper_types:existing_atom/0. `existing_atom()'}.
%%% %%% %%% == Spec testing == @@ -539,6 +543,7 @@ | 'quiet' | 'verbose' | pos_integer() + | {'default_atom_generator', 'atom' | 'existing_atom'} | {'constraint_tries',pos_integer()} | {'false_positive_mfas',false_positive_mfas()} | {'max_shrinks',non_neg_integer()} @@ -569,6 +574,7 @@ constraint_tries = 50 :: pos_integer(), expect_fail = false :: boolean(), any_type :: {'type', proper_types:type()} | 'undefined', + default_atom_generator = atom :: 'atom' | 'existing_atom', spec_timeout = infinity :: timeout(), skip_mfas = [] :: [mfa()], false_positive_mfas :: false_positive_mfas(), @@ -744,8 +750,10 @@ global_state_init_size_seed(Size, Seed) -> -spec global_state_init(opts()) -> 'ok'. global_state_init(#opts{start_size = StartSize, constraint_tries = CTries, search_strategy = Strategy, search_steps = SearchSteps, - any_type = AnyType, seed = Seed, numworkers = NumWorkers} = Opts) -> + any_type = AnyType, seed = Seed, numworkers = NumWorkers, + default_atom_generator = DefaultAtomGen} = Opts) -> clean_garbage(), + put('$default_atom_generator', DefaultAtomGen), put('$size', StartSize - 1), put('$left', 0), put('$search_strategy', Strategy), @@ -772,6 +780,7 @@ global_state_reset(#opts{start_size = StartSize} = Opts) -> global_state_erase() -> proper_typeserver:stop(), proper_arith:rand_stop(), + erase('$default_atom_generator'), erase('$any_type'), erase('$constraint_tries'), erase('$left'), @@ -1039,6 +1048,8 @@ parse_opt(UserOpt, Opts) -> N when is_integer(N) -> ?VALIDATE_OPT(?POS_INTEGER(N), Opts#opts{numtests = N}); %% tuple options, sorted on tag + {default_atom_generator,G} -> + ?VALIDATE_OPT(G =:= atom orelse G =:= existing_atom, Opts#opts{default_atom_generator = G}); {constraint_tries,N} -> ?VALIDATE_OPT(?POS_INTEGER(N), Opts#opts{constraint_tries = N}); {false_positive_mfas,F} -> diff --git a/src/proper_gen.erl b/src/proper_gen.erl index 6f603917..b7c34e11 100644 --- a/src/proper_gen.erl +++ b/src/proper_gen.erl @@ -46,6 +46,8 @@ any_gen/1, native_type_gen/2, safe_weighted_union_gen/1, safe_union_gen/1]). +-export([existing_atom_gen/0]). + %% Public API types -export_type([instance/0, seed/0, size/0]). %% Internal types @@ -418,6 +420,27 @@ atom_gen(Size) -> atom_rev(Atom) -> {'$used', atom_to_list(Atom), Atom}. +%% @private +-spec existing_atom_gen() -> atom(). +%% We make sure we never clash with internal atoms by ignoring atoms starting with +%% the character '$'. +existing_atom_gen() -> + existing_atom_gen(10). + +existing_atom_gen(0) -> + ''; +existing_atom_gen(N) -> + Index = proper_arith:rand_int(0, erlang:system_info(atom_count) - 1), + Atom = get_existing_atom(Index), + case Atom =:= '' orelse hd(atom_to_list(Atom)) =/= $$ of + true -> Atom; + false -> existing_atom_gen(N - 1) + end. + +-define(ATOM_TERM_BIN(Index), <<131, 75, Index:24>>). +get_existing_atom(Index) -> + binary_to_term(?ATOM_TERM_BIN(Index)). + %% @private -spec binary_gen(size()) -> proper_types:type(). binary_gen(Size) -> @@ -594,7 +617,7 @@ any_gen(Size) -> -spec real_any_gen(size()) -> imm_instance(). real_any_gen(0) -> SimpleTypes = [proper_types:integer(), proper_types:float(), - proper_types:atom()], + proper_types:default_atom()], union_gen(SimpleTypes); real_any_gen(Size) -> FreqChoices = [{?ANY_SIMPLE_PROB,simple}, {?ANY_BINARY_PROB,binary}, diff --git a/src/proper_gen_next.erl b/src/proper_gen_next.erl index 569d47c4..cf176021 100644 --- a/src/proper_gen_next.erl +++ b/src/proper_gen_next.erl @@ -315,7 +315,7 @@ float_gen_sa({'$type', TypeProps}) -> %% List is_list_type(Type) -> - has_same_generator(Type, proper_types:list(proper_types:atom())). + has_same_generator(Type, proper_types:list(proper_types:default_atom())). list_choice(empty, Temp) -> C = ?RANDOM_MOD:uniform(), @@ -426,7 +426,7 @@ vector_gen_sa(Type) -> %% atom is_atom_type(Type) -> - has_same_generator(Type, proper_types:atom()). + has_same_generator(Type, proper_types:default_atom()). atom_gen_sa(_AtomType) -> StringType = proper_types:list(proper_types:integer(0, 255)), diff --git a/src/proper_types.erl b/src/proper_types.erl index 676aff22..1323c85f 100644 --- a/src/proper_types.erl +++ b/src/proper_types.erl @@ -139,7 +139,7 @@ -module(proper_types). -export([is_inst/2, is_inst/3]). --export([integer/2, float/2, atom/0, binary/0, binary/1, bitstring/0, +-export([integer/2, float/2, atom/0, existing_atom/0, binary/0, binary/1, bitstring/0, bitstring/1, list/1, vector/2, union/1, weighted_union/1, tuple/1, loose_tuple/1, exactly/1, fixed_list/1, function/2, map/0, map/2, any/0, shrink_list/1, safe_union/1, safe_weighted_union/1]). @@ -160,6 +160,7 @@ native_type/2, distlist/3, with_parameter/3, with_parameters/2, parameter/1, parameter/2]). -export([le/2]). +-export([default_atom/0]). %% Public API types -export_type([type/0, raw_type/0]). @@ -674,6 +675,28 @@ atom() -> {is_instance, fun atom_is_instance/1} ]). +%% @doc All existing atoms. All atoms used internally by PropEr start with a +%% '`$'', so such atoms will never be produced as instances of this type. You +%% should also refrain from using such atoms in your code, to avoid a potential +%% clash. +%% Instances do not shrink. +-spec existing_atom() -> proper_types:type(). +existing_atom() -> + ?BASIC([ + {generator, fun proper_gen:existing_atom_gen/0}, + {reverse_gen, fun proper_gen:atom_rev/1}, + {is_instance, fun atom_is_instance/1}, + {noshrink, true} + ]). + +%% @private +-spec default_atom() -> proper_types:type(). +default_atom() -> + case get('$default_atom_generator') of + existing_atom -> existing_atom(); + _ -> atom() + end. + atom_is_instance(X) -> is_atom(X) %% We return false for atoms starting with '$', since these are @@ -1127,7 +1150,7 @@ map(K, V) -> %% type if you are certain that you need it. -spec any() -> proper_types:type(). any() -> - AllTypes = [integer(),float(),atom(),bitstring(),?LAZY(loose_tuple(any())), + AllTypes = [integer(),float(),default_atom(),bitstring(),?LAZY(loose_tuple(any())), ?LAZY(list(any())), ?LAZY(map(any(), any()))], subtype([{generator, fun proper_gen:any_gen/1}], union(AllTypes)). diff --git a/src/proper_typeserver.erl b/src/proper_typeserver.erl index 4a59abbf..1b55970d 100644 --- a/src/proper_typeserver.erl +++ b/src/proper_typeserver.erl @@ -187,9 +187,9 @@ %% CAUTION: all these must be sorted -define(STD_TYPES_0, - [any,arity,atom,binary,bitstring,bool,boolean,byte,char,float,integer, - list,neg_integer,nil,non_neg_integer,number,pos_integer,string,term, - timeout]). + [any,arity,atom,binary,bitstring,bool,boolean,byte,char,existing_atom, + float,integer,list,neg_integer,nil,non_neg_integer,number,pos_integer, + string,term,timeout]). -define(HARD_ADTS, %% gb_trees:iterator and gb_sets:iterator are NOT hardcoded [{{array,0},array}, {{array,1},proper_array}, diff --git a/test/proper_tests.erl b/test/proper_tests.erl index eaa5f469..3df88a50 100644 --- a/test/proper_tests.erl +++ b/test/proper_tests.erl @@ -1398,6 +1398,20 @@ sampleshrink_test_() -> ?_shrinksTo([a], Gen)}, ?_test(proper_gen:sampleshrink(Gen))]}]. +existing_atom_test() -> + _ = proper_gen:pick(proper_types:existing_atom()), + N = erlang:system_info(atom_count), + {ok, Atom} = proper_gen:pick(proper_types:existing_atom()), + ?assert(erlang:is_atom(Atom)), + ?assertEqual(N, erlang:system_info(atom_count)). + +default_atom_test() -> + _ = proper:quickcheck(?FORALL(_, any(), true), + [1, {default_atom_generator, existing_atom}]), + N = erlang:system_info(atom_count), + ?assert(proper:quickcheck(?FORALL(_, any(), true), + [{default_atom_generator, existing_atom}])), + ?assertEqual(N, erlang:system_info(atom_count)). %%------------------------------------------------------------------------------ %% Performance tests