1package commands 2 3import ( 4 "fmt" 5 "reflect" 6 7 "github.com/leanovate/gopter" 8 "github.com/leanovate/gopter/gen" 9) 10 11type shrinkableCommand struct { 12 command Command 13 shrinker gopter.Shrinker 14} 15 16func (s shrinkableCommand) shrink() gopter.Shrink { 17 return s.shrinker(s.command).Map(func(command Command) shrinkableCommand { 18 return shrinkableCommand{ 19 command: command, 20 shrinker: s.shrinker, 21 } 22 }) 23} 24 25func (s shrinkableCommand) String() string { 26 return fmt.Sprintf("%v", s.command) 27} 28 29type actions struct { 30 // initialStateProvider has to reset/recreate the initial state exactly the 31 // same every time. 32 initialStateProvider func() State 33 sequentialCommands []shrinkableCommand 34 // parallel commands will come later 35} 36 37func (a *actions) String() string { 38 return fmt.Sprintf("initialState=%v sequential=%s", a.initialStateProvider(), a.sequentialCommands) 39} 40 41func (a *actions) run(systemUnderTest SystemUnderTest) (*gopter.PropResult, error) { 42 state := a.initialStateProvider() 43 propResult := &gopter.PropResult{Status: gopter.PropTrue} 44 for _, shrinkableCommand := range a.sequentialCommands { 45 if !shrinkableCommand.command.PreCondition(state) { 46 return &gopter.PropResult{Status: gopter.PropFalse}, nil 47 } 48 result := shrinkableCommand.command.Run(systemUnderTest) 49 state = shrinkableCommand.command.NextState(state) 50 propResult = propResult.And(shrinkableCommand.command.PostCondition(state, result)) 51 } 52 return propResult, nil 53} 54 55type sizedCommands struct { 56 state State 57 commands []shrinkableCommand 58} 59 60func actionsShrinker(v interface{}) gopter.Shrink { 61 a := v.(*actions) 62 elementShrinker := gopter.Shrinker(func(v interface{}) gopter.Shrink { 63 return v.(shrinkableCommand).shrink() 64 }) 65 return gen.SliceShrinker(elementShrinker)(a.sequentialCommands).Map(func(v []shrinkableCommand) *actions { 66 return &actions{ 67 initialStateProvider: a.initialStateProvider, 68 sequentialCommands: v, 69 } 70 }) 71} 72 73func genActions(commands Commands) gopter.Gen { 74 genInitialState := commands.GenInitialState() 75 genInitialStateProvider := gopter.Gen(func(params *gopter.GenParameters) *gopter.GenResult { 76 seed := params.NextInt64() 77 return gopter.NewGenResult(func() State { 78 paramsWithSeed := params.CloneWithSeed(seed) 79 if initialState, ok := genInitialState(paramsWithSeed).Retrieve(); ok { 80 return initialState 81 } 82 return nil 83 }, gopter.NoShrinker) 84 }).SuchThat(func(initialStateProvoder func() State) bool { 85 state := initialStateProvoder() 86 return state != nil && commands.InitialPreCondition(state) 87 }) 88 return genInitialStateProvider.FlatMap(func(v interface{}) gopter.Gen { 89 initialStateProvider := v.(func() State) 90 return genSizedCommands(commands, initialStateProvider).Map(func(v sizedCommands) *actions { 91 return &actions{ 92 initialStateProvider: initialStateProvider, 93 sequentialCommands: v.commands, 94 } 95 }).SuchThat(func(actions *actions) bool { 96 state := actions.initialStateProvider() 97 for _, shrinkableCommand := range actions.sequentialCommands { 98 if !shrinkableCommand.command.PreCondition(state) { 99 return false 100 } 101 state = shrinkableCommand.command.NextState(state) 102 } 103 return true 104 }).WithShrinker(actionsShrinker) 105 }, reflect.TypeOf((*actions)(nil))) 106} 107 108func genSizedCommands(commands Commands, initialStateProvider func() State) gopter.Gen { 109 return func(genParams *gopter.GenParameters) *gopter.GenResult { 110 sizedCommandsGen := gen.Const(sizedCommands{ 111 state: initialStateProvider(), 112 commands: make([]shrinkableCommand, 0, genParams.MaxSize), 113 }) 114 for i := 0; i < genParams.MaxSize; i++ { 115 sizedCommandsGen = sizedCommandsGen.FlatMap(func(v interface{}) gopter.Gen { 116 prev := v.(sizedCommands) 117 return gen.RetryUntil(commands.GenCommand(prev.state), func(command Command) bool { 118 return command.PreCondition(prev.state) 119 }, 100).MapResult(func(result *gopter.GenResult) *gopter.GenResult { 120 value, ok := result.Retrieve() 121 if !ok { 122 return gopter.NewEmptyResult(reflect.TypeOf(sizedCommands{})) 123 } 124 command := value.(Command) 125 return gopter.NewGenResult( 126 sizedCommands{ 127 state: command.NextState(prev.state), 128 commands: append(prev.commands, shrinkableCommand{ 129 command: command, 130 shrinker: result.Shrinker, 131 }), 132 }, 133 gopter.NoShrinker, 134 ) 135 }) 136 }, reflect.TypeOf(sizedCommands{})) 137 } 138 return sizedCommandsGen(genParams) 139 } 140} 141