Permalink
Browse files

Merge pull request #33 from spawngrid/allow-seeding

Allow reproducible runs through RNG seeding
  • Loading branch information...
2 parents dd682ea + 7c96423 commit 017ca3d060814a6bbe20ff8d8446b645e836f93d @kostis kostis committed Apr 16, 2012
Showing with 45 additions and 12 deletions.
  1. +1 −0 include/proper_internal.hrl
  2. +16 −3 src/proper.erl
  3. +4 −4 src/proper_arith.erl
  4. +10 −5 src/proper_gen.erl
  5. +14 −0 test/proper_tests.erl
@@ -66,6 +66,7 @@
-type length() :: non_neg_integer().
-type position() :: pos_integer().
-type frequency() :: pos_integer().
+-type seed() :: {non_neg_integer(), non_neg_integer(), non_neg_integer()}.
%% TODO: Replace these with the appropriate types from stdlib.
-type abs_form() :: term().
View
@@ -343,7 +343,8 @@
-export([counterexample/0, counterexamples/0]).
-export([clean_garbage/0, global_state_erase/0]).
--export([get_size/1, global_state_init_size/1, report_error/2]).
+-export([get_size/1, global_state_init_size/1,
+ global_state_init_size_seed/2,report_error/2]).
-export([pure_check/1, pure_check/2]).
-export([forall/2, implies/2, whenfail/2, trapexit/1, timeout/2]).
@@ -467,6 +468,7 @@
long_result = false :: boolean(),
numtests = 100 :: pos_integer(),
start_size = 1 :: size(),
+ seed :: seed(),
max_size = 42 :: size(),
max_shrinks = 500 :: non_neg_integer(),
noshrink = false :: boolean(),
@@ -593,16 +595,27 @@ get_size(Type) ->
global_state_init_size(Size) ->
global_state_init(#opts{start_size = Size}).
+%% @private
+-spec global_state_init_size_seed(size(), seed()) -> 'ok'.
+global_state_init_size_seed(Size, Seed) ->
+ global_state_init(#opts{start_size = Size, seed = Seed}).
+
-spec global_state_init(opts()) -> 'ok'.
global_state_init(#opts{start_size = StartSize, constraint_tries = CTries,
- any_type = AnyType} = Opts) ->
+ any_type = AnyType, seed = Seed} = Opts) ->
clean_garbage(),
put('$size', StartSize - 1),
put('$left', 0),
grow_size(Opts),
put('$constraint_tries', CTries),
put('$any_type',AnyType),
- proper_arith:rand_start(),
+ S = case Seed of
+ undefined ->
+ now();
+ _ ->
+ Seed
+ end,
+ proper_arith:rand_start(S),
proper_typeserver:start(),
ok.
@@ -30,7 +30,7 @@
safe_any/2, safe_zip/2, tuple_map/2, cut_improper_tail/1,
head_length/1, find_first/2, filter/2, partition/2, remove/2, insert/3,
unflatten/2]).
--export([rand_start/0, rand_reseed/0, rand_stop/0,
+-export([rand_start/1, rand_reseed/0, rand_stop/0,
rand_int/1, rand_int/2, rand_non_neg_int/1,
rand_float/1, rand_float/2, rand_non_neg_float/1,
distribute/2, jumble/1, rand_choose/1, freq_choose/1]).
@@ -211,9 +211,9 @@ remove_n(N, {List,Acc}) ->
%% @doc Seeds the random number generator. This function should be run before
%% calling any random function from this module.
--spec rand_start() -> 'ok'.
-rand_start() ->
- _ = random:seed(now()),
+-spec rand_start(seed()) -> 'ok'.
+rand_start(Seed) ->
+ _ = random:seed(Seed),
%% TODO: read option for RNG bijections here
ok.
View
@@ -30,7 +30,7 @@
%%% meant for demonstration purposes only.
-module(proper_gen).
--export([pick/1, pick/2, sample/1, sample/3, sampleshrink/1, sampleshrink/2]).
+-export([pick/1, pick/2, pick/3, sample/1, sample/3, sampleshrink/1, sampleshrink/2]).
-export([safe_generate/1]).
-export([generate/1, normal_gen/1, alt_gens/1, clean_instance/1,
@@ -179,11 +179,16 @@ generate(Type, TriesLeft, Fallback) ->
pick(RawType) ->
pick(RawType, 10).
-%% @doc Generates a random instance of `Type', of size `Size'.
--spec pick(Type::proper_types:raw_type(), size()) ->
- {'ok',instance()} | 'error'.
+%% @equiv pick(Type, Size, now())
+-spec pick(Type::proper_types:raw_type(), size()) -> {'ok', instance()} | 'error'.
pick(RawType, Size) ->
- proper:global_state_init_size(Size),
+ pick(RawType, Size, now()).
+
+%% @doc Generates a random instance of `Type', of size `Size' with seed `Seed'.
+-spec pick(Type::proper_types:raw_type(), size(), seed()) ->
+ {'ok',instance()} | 'error'.
+pick(RawType, Size, Seed) ->
+ proper:global_state_init_size_seed(Size, Seed),
case clean_instance(safe_generate(RawType)) of
{ok,Instance} = Result ->
Msg = "WARNING: Some garbage has been left in the process registry "
View
@@ -261,6 +261,16 @@ try_generate(Type, Size, CheckIsInstance) ->
false -> ok
end.
+assert_seeded_runs_return_same_result(Type) ->
+ lists:foreach(fun(Size) -> try_generate_seeded(Type, Size) end,
+ [1, 2, 5, 10, 20, 40, 50]).
+
+try_generate_seeded(Type, Size) ->
+ Seed = now(),
+ {ok, Instance1} = proper_gen:pick(Type, Size, Seed),
+ {ok, Instance2} = proper_gen:pick(Type, Size, Seed),
+ ?assert(Instance1 =:= Instance2).
+
assert_native_can_generate(Mod, TypeStr, CheckIsInstance) ->
assert_can_generate(assert_can_translate(Mod,TypeStr), CheckIsInstance).
@@ -1012,6 +1022,10 @@ can_generate_parallel_commands1_test_() ->
false))
|| Mod <- [ets_counter]]}.
+seeded_runs_return_same_result_test_() ->
+ [?_test(assert_seeded_runs_return_same_result(proper_statem:commands(Mod)))
+ || Mod <- [pdict_statem]].
+
run_valid_commands_test_() ->
[?_assertMatch({_H,DynState,ok}, setup_run_commands(Mod, Cmds, Env))
|| {Mod,_,Cmds,_,DynState,Env} <- valid_command_sequences()].

0 comments on commit 017ca3d

Please sign in to comment.