From 63409ef59a63c49fcfd71c0c0cf723fd7aa3a14b Mon Sep 17 00:00:00 2001 From: Corin Lawson Date: Fri, 19 Oct 2018 09:36:21 +1100 Subject: [PATCH] Ensure that gen.Struct is reproducible for a seed Prior to this change, `gen.Struct` produced a value by ranging over items in a map, which in turn draws values from the `gopter.Gen`s from the items, which in turn presumedly makes calls to the PRNG in the `gopter.GenParameters`. Ranging over items in a map is intentionally non-deterministic, therefore the calls to the PRNG is also non-deterministic. This change extracts the keys from the map, sorts them and then ranges over the keys. This ensures that the calls to PRNG is deterministic. fixes #44 Signed-off-by: Corin Lawson --- gen/struct.go | 11 ++++++++++- gen/struct_test.go | 48 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/gen/struct.go b/gen/struct.go index d57a829..e2f22b8 100644 --- a/gen/struct.go +++ b/gen/struct.go @@ -2,6 +2,7 @@ package gen import ( "reflect" + "sort" "github.com/leanovate/gopter" ) @@ -20,7 +21,15 @@ func Struct(rt reflect.Type, gens map[string]gopter.Gen) gopter.Gen { return func(genParams *gopter.GenParameters) *gopter.GenResult { result := reflect.New(rt) - for name, gen := range gens { + names := make([]string, len(gens)) + i := 0 + for name := range gens { + names[i] = name + i++ + } + sort.Strings(names) + for _, name := range names { + gen := gens[name] field, ok := rt.FieldByName(name) if !ok { continue diff --git a/gen/struct_test.go b/gen/struct_test.go index 62ea546..516f09f 100644 --- a/gen/struct_test.go +++ b/gen/struct_test.go @@ -35,6 +35,54 @@ func TestStruct(t *testing.T) { } } +func TestStructDeterminism(t *testing.T) { + structGen := gen.Struct(reflect.TypeOf(&testStruct{}), map[string]gopter.Gen{ + "Value1": gen.Identifier(), + "Value2": gen.Int64(), + "Value3": gen.SliceOf(gen.Int8()), + }) + for i := 0; i < 100; i++ { + parameters := gopter.DefaultGenParameters().CloneWithSeed(1234) + for _, expected := range []testStruct{ + testStruct{ + Value1: "hUeNzDbtiF4xxkidfvLaiczgpwsqfyvbbuhrjjoez4jtewulIKwzMguttazo3qwi5ufIfi6izpqT4evzrmgtmk1gQo", + Value2: -2282921689139609493, + Value3: []int8{-93, -96, -23, -58, 65, -108, 56, 63, -64, 26, -69, 62, 61, -93, -107, 52, -95}, + }, + testStruct{ + Value1: "ubJrJEawwnoh63jv1lxd7xhtaqqrEnjawudgiixhhkw6sdmqdgxbabyoxcoE0uviwDupccvYvxcqOv0z8opjk", + Value2: -1611599231975617329, + Value3: []int8{15, -41, -106, 37, 3, 76, -65, -87, 113, -115, 76, 61, 41, 65, 11, -90, -4, 43, 110, -121, 65, 112, -128, 51, -86, 50, 30, 33, -73, -88, 94, 101, 63, -113, 45, 110, 46, 21, 115, 78, -58, 47, -110, 7, -14, -18, 2, -26, 63, -33, 77, 82, -52, -57, -105}, + }, + testStruct{ + Value1: "axnbggD6Hgsxyxd6ZwcZ4Bn1uM7hzd0azvsuLvj3wvfvoramjcltivmditt5qhmHYfn0egagcFpuAffzaWxvalEaniojczez", + Value2: -345052727922296584, + Value3: []int8{-61, 94, 67, 9, 39, 119, 23, 1, 57, -66, 57, -94, 38, -122, 16, 82, -119, 21, -74, -66, -111, 55, -96, 8, -79, 13, -41, 124, 71, -63, 56, 16, 62, 55, -13, -35, -27, 68, -82, 22, -63, -76, 96, 60, -89, -10, -65, -102, -97, 45, 124, 117, -37, 21, 58, -87, 116, 60, -111, 27, 102, -102, -81, -123, -86, -95}, + }, + testStruct{ + Value1: "lr", + Value2: -6442088894944465291, + Value3: []int8{-20, 38, 25, -76, -110, -98, -61, 65, -14, 52, -47, 22, -90}, + }, + testStruct{ + Value1: "un23mggozHs4txZtydz6mIBymnxjxklkjyNzf", + Value2: -26686468742269553, + Value3: []int8{78, 91, -22, -126, -93, 35, -14, 67, -97, 13, -25, 73, -111, 26, 14, -67, 50, -23, -15, -63, -40, -103, 126, 60, -63, -83, -126, 64, 52, -50, 86, -25, -1, 108, 7, 62, 79, 89, 45, -73, 52, -7, -85, -111, -120, -21, 116, -8, -22, 34, 85, 36, 124, 12, -111, -114, -115, 91, -94, 82, -3, -46, 94, -73, 62, -117, -7, 84, -94, 13, 71, -5, 21, 32, 106, -44, 46}, + }, + } { + value, ok := structGen(parameters).Retrieve() + + if !ok { + t.Errorf("Invalid value: %#v", value) + } + v, ok := value.(testStruct) + if !reflect.DeepEqual(expected, v) { + t.Errorf("Invalid value: %#v; expected: %#v", v, expected) + } + } + } +} + func TestStructPropageEmpty(t *testing.T) { fail := gen.Struct(reflect.TypeOf(&testStruct{}), map[string]gopter.Gen{ "Value1": gen.Identifier().SuchThat(func(str string) bool {