Skip to content

Commit d8c8f95

Browse files
committed
introduce seed user option
1 parent 0365dd2 commit d8c8f95

File tree

2 files changed

+52
-0
lines changed

2 files changed

+52
-0
lines changed

src/proper.erl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,8 @@
239239
%%% <dd>This is equivalent to the {@link numtests/1} property wrapper. Any
240240
%%% {@link numtests/1} wrappers in the actual property will overwrite this
241241
%%% setting.</dd>
242+
%%% <dt>`{seed, {<Non_negative_integer>,<Non_negative_integer>,<Non_negative_integer>}}'</dt>
243+
%%% <dd>Pass a seed to the RNG so that random results can be reproduced.</dd>
242244
%%% <dt>`{start_size, <Size>}'</dt>
243245
%%% <dd>Specifies the initial value of the `size' parameter (default is 1), see
244246
%%% the documentation of the {@link proper_types} module for details.</dd>
@@ -489,6 +491,7 @@
489491
| {'search_steps',pos_integer()}
490492
| {'search_strategy',proper_target:strategy()}
491493
| pos_integer()
494+
| {'seed',proper_gen:seed()}
492495
| {'start_size',proper_gen:size()}
493496
| {'max_size',proper_gen:size()}
494497
| {'max_shrinks',non_neg_integer()}
@@ -924,6 +927,7 @@ parse_opt(UserOpt, Opts) ->
924927
{search_steps, N} -> Opts#opts{search_steps = N};
925928
{search_strategy, S} -> Opts#opts{search_strategy = S};
926929
N when is_integer(N) -> Opts#opts{numtests = N};
930+
{seed,Seed} -> Opts#opts{seed = Seed};
927931
{start_size,Size} -> Opts#opts{start_size = Size};
928932
{max_size,Size} -> Opts#opts{max_size = Size};
929933
{max_shrinks,N} -> Opts#opts{max_shrinks = N};

test/proper_tests.erl

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1119,6 +1119,54 @@ options_test_() ->
11191119
?FORALL(_,?SIZED(Size,integer(Size,Size)),false),
11201120
[{start_size,12}])].
11211121

1122+
seeded_test_() ->
1123+
Seed = os:timestamp(),
1124+
BaseOpts = [noshrink, {start_size,65536}, quiet],
1125+
Seeded = fun (Prop) ->
1126+
R = proper:counterexample(Prop, [{seed,Seed}|BaseOpts]),
1127+
proper:clean_garbage(),
1128+
R
1129+
end,
1130+
NoSeed = fun (Prop) ->
1131+
R = proper:counterexample(Prop, BaseOpts),
1132+
proper:clean_garbage(),
1133+
R
1134+
end,
1135+
ReSeeded = fun (Prop) ->
1136+
OtherSeed = os:timestamp(),
1137+
R = proper:counterexample(Prop, [{seed,OtherSeed}|BaseOpts]),
1138+
proper:clean_garbage(),
1139+
R
1140+
end,
1141+
[[?_assert(state_is_clean()),
1142+
?_assertMatch({Name,{_,Equals}} when Equals > 6, {Name,equaltimes(Seeded,Prop,Check,10)}),
1143+
?_assert(state_is_clean()),
1144+
?_assertMatch({Name,{_,Equals}} when Equals < 4, {Name,equaltimes(NoSeed,Prop,Check,10)}),
1145+
?_assert(state_is_clean()),
1146+
?_assertMatch({Name,{_,Equals}} when Equals < 4, {Name,equaltimes(ReSeeded,Prop,Check,10)}),
1147+
?_assert(state_is_clean())]
1148+
%% For each of these properties...
1149+
|| {Name,Prop} <- [{forall,?FORALL(_, integer(), false)},
1150+
{trapexit,?FORALL(_, integer(), ?TRAPEXIT(false))},
1151+
{targeted,?FORALL_TARGETED(I, integer(), begin ?MAXIMIZE(I),false end)}],
1152+
%% Ensure that, using a large enough size and at least 60% of the time:
1153+
%% * provided a seed, another run gives the same counterexample;
1154+
%% * when not provided a seed: run gives out differing results to the seeded one;
1155+
%% * and similarly when given a different seed.
1156+
Check <- [Seeded(Prop)]].
1157+
1158+
equaltimes(Runner, Prop, Expected, Max) ->
1159+
equaltimes(Runner, Prop, Expected, Max, Max, []).
1160+
equaltimes(_, _, _, Max, 0, Unexpecteds) ->
1161+
{Unexpecteds, Max - length(Unexpecteds)};
1162+
equaltimes(Runner, Prop, Expected, Max, N, Acc) ->
1163+
case Runner(Prop) of
1164+
Expected ->
1165+
equaltimes(Runner, Prop, Expected, Max, N-1, Acc);
1166+
Got ->
1167+
equaltimes(Runner, Prop, Expected, Max, N-1, [Got|Acc])
1168+
end.
1169+
11221170
setup_prop() ->
11231171
?SETUP(fun () ->
11241172
put(setup_token, true),

0 commit comments

Comments
 (0)