github.com/leanovate/gopter@v0.2.9/commands/example_circularqueue_test.go (about) 1 package commands_test 2 3 import ( 4 "fmt" 5 6 "github.com/leanovate/gopter" 7 "github.com/leanovate/gopter/commands" 8 "github.com/leanovate/gopter/gen" 9 ) 10 11 // ***************************************** 12 // Production code (i.e. the implementation) 13 // ***************************************** 14 15 type Queue struct { 16 inp int 17 outp int 18 size int 19 buf []int 20 } 21 22 func New(n int) *Queue { 23 return &Queue{ 24 inp: 0, 25 outp: 0, 26 size: n + 1, 27 buf: make([]int, n+1), 28 } 29 } 30 31 func (q *Queue) Put(n int) int { 32 if q.inp == 4 && n > 0 { // Intentional spooky bug 33 q.buf[q.size-1] *= n 34 } 35 q.buf[q.inp] = n 36 q.inp = (q.inp + 1) % q.size 37 return n 38 } 39 40 func (q *Queue) Get() int { 41 ans := q.buf[q.outp] 42 q.outp = (q.outp + 1) % q.size 43 return ans 44 } 45 46 func (q *Queue) Size() int { 47 return (q.inp - q.outp + q.size) % q.size 48 } 49 50 func (q *Queue) Init() { 51 q.inp = 0 52 q.outp = 0 53 } 54 55 // ***************************************** 56 // Test code 57 // ***************************************** 58 59 // cbState holds the expected state (i.e. its the commands.State) 60 type cbState struct { 61 size int 62 elements []int 63 takenElement int 64 } 65 66 func (st *cbState) TakeFront() { 67 st.takenElement = st.elements[0] 68 st.elements = append(st.elements[:0], st.elements[1:]...) 69 } 70 71 func (st *cbState) PushBack(value int) { 72 st.elements = append(st.elements, value) 73 } 74 75 func (st *cbState) String() string { 76 return fmt.Sprintf("State(size=%d, elements=%v)", st.size, st.elements) 77 } 78 79 // Get command simply invokes the Get function on the queue and compares the 80 // result with the expected state. 81 var genGetCommand = gen.Const(&commands.ProtoCommand{ 82 Name: "Get", 83 RunFunc: func(q commands.SystemUnderTest) commands.Result { 84 return q.(*Queue).Get() 85 }, 86 NextStateFunc: func(state commands.State) commands.State { 87 state.(*cbState).TakeFront() 88 return state 89 }, 90 // The implementation implicitly assumes that Get is never called on an 91 // empty Queue, therefore the command requires a corresponding pre-condition 92 PreConditionFunc: func(state commands.State) bool { 93 return len(state.(*cbState).elements) > 0 94 }, 95 PostConditionFunc: func(state commands.State, result commands.Result) *gopter.PropResult { 96 if result.(int) != state.(*cbState).takenElement { 97 return &gopter.PropResult{Status: gopter.PropFalse} 98 } 99 return &gopter.PropResult{Status: gopter.PropTrue} 100 }, 101 }) 102 103 // Put command puts a value into the queue by using the Put function. Since 104 // the Put function has an int argument the Put command should have a 105 // corresponding parameter. 106 type putCommand int 107 108 func (value putCommand) Run(q commands.SystemUnderTest) commands.Result { 109 return q.(*Queue).Put(int(value)) 110 } 111 112 func (value putCommand) NextState(state commands.State) commands.State { 113 state.(*cbState).PushBack(int(value)) 114 return state 115 } 116 117 // The implementation implicitly assumes that that Put is never called if 118 // the capacity is exhausted, therefore the command requires a corresponding 119 // pre-condition. 120 func (putCommand) PreCondition(state commands.State) bool { 121 s := state.(*cbState) 122 return len(s.elements) < s.size 123 } 124 125 func (putCommand) PostCondition(state commands.State, result commands.Result) *gopter.PropResult { 126 st := state.(*cbState) 127 if result.(int) != st.elements[len(st.elements)-1] { 128 return &gopter.PropResult{Status: gopter.PropFalse} 129 } 130 return &gopter.PropResult{Status: gopter.PropTrue} 131 } 132 133 func (value putCommand) String() string { 134 return fmt.Sprintf("Put(%d)", value) 135 } 136 137 // We want to have a generator for put commands for arbitrary int values. 138 // In this case the command is actually shrinkable, e.g. if the property fails 139 // by putting a 1000, it might already fail for a 500 as well ... 140 var genPutCommand = gen.Int().Map(func(value int) commands.Command { 141 return putCommand(value) 142 }).WithShrinker(func(v interface{}) gopter.Shrink { 143 return gen.IntShrinker(int(v.(putCommand))).Map(func(value int) putCommand { 144 return putCommand(value) 145 }) 146 }) 147 148 // Size command is simple again, it just invokes the Size function and 149 // compares compares the result with the expected state. 150 // The Size function can be called any time, therefore this command does not 151 // require a pre-condition. 152 var genSizeCommand = gen.Const(&commands.ProtoCommand{ 153 Name: "Size", 154 RunFunc: func(q commands.SystemUnderTest) commands.Result { 155 return q.(*Queue).Size() 156 }, 157 PostConditionFunc: func(state commands.State, result commands.Result) *gopter.PropResult { 158 if result.(int) != len(state.(*cbState).elements) { 159 return &gopter.PropResult{Status: gopter.PropFalse} 160 } 161 return &gopter.PropResult{Status: gopter.PropTrue} 162 }, 163 }) 164 165 // cbCommands implements the command.Commands interface, i.e. is 166 // responsible for creating/destroying the system under test and generating 167 // commands and initial states (cbState) 168 var cbCommands = &commands.ProtoCommands{ 169 NewSystemUnderTestFunc: func(initialState commands.State) commands.SystemUnderTest { 170 s := initialState.(*cbState) 171 q := New(s.size) 172 for e := range s.elements { 173 q.Put(e) 174 } 175 return q 176 }, 177 DestroySystemUnderTestFunc: func(sut commands.SystemUnderTest) { 178 sut.(*Queue).Init() 179 }, 180 InitialStateGen: gen.IntRange(1, 30).Map(func(size int) *cbState { 181 return &cbState{ 182 size: size, 183 elements: make([]int, 0, size), 184 } 185 }), 186 InitialPreConditionFunc: func(state commands.State) bool { 187 s := state.(*cbState) 188 return len(s.elements) >= 0 && len(s.elements) <= s.size 189 }, 190 GenCommandFunc: func(state commands.State) gopter.Gen { 191 return gen.OneGenOf(genGetCommand, genPutCommand, genSizeCommand) 192 }, 193 } 194 195 // Kudos to @jamesd for providing this real world example. 196 // ... of course he did not implemented the bug, that was evil me 197 // 198 // The bug only occures on the following conditions: 199 // - the queue size has to be greater than 4 200 // - the queue has to be filled entirely once 201 // - Get operations have to be at least 5 elements behind put 202 // - The Put at the end of the queue and 5 elements later have to be non-zero 203 // 204 // Lets see what gopter has to say: 205 // 206 // The output of this example will be 207 // ! circular buffer: Falsified after 96 passed tests. 208 // ARG_0: initialState=State(size=7, elements=[]) sequential=[Put(0) Put(0) 209 // Get Put(0) Get Put(0) Put(0) Get Put(0) Get Put(0) Get Put(-1) Put(0) 210 // Put(0) Put(0) Put(0) Get Get Put(2) Get] 211 // ARG_0_ORIGINAL (85 shrinks): initialState=State(size=7, elements=[]) 212 // sequential=[Put(-1855365712) Put(-1591723498) Get Size Size 213 // Put(-1015561691) Get Put(397128011) Size Get Put(1943174048) Size 214 // Put(1309500770) Size Get Put(-879438231) Size Get Put(-1644094687) Get 215 // Put(-1818606323) Size Put(488620313) Size Put(-1219794505) 216 // Put(1166147059) Get Put(11390361) Get Size Put(-1407993944) Get Get Size 217 // Put(1393923085) Get Put(1222853245) Size Put(2070918543) Put(1741323168) 218 // Size Get Get Size Put(2019939681) Get Put(-170089451) Size Get Get Size 219 // Size Put(-49249034) Put(1229062846) Put(642598551) Get Put(1183453167) 220 // Size Get Get Get Put(1010460728) Put(6828709) Put(-185198587) Size Size 221 // Get Put(586459644) Get Size Put(-1802196502) Get Size Put(2097590857) Get 222 // Get Get Get Size Put(-474576011) Size Get Size Size Put(771190414) Size 223 // Put(-1509199920) Get Put(967212411) Size Get Put(578995532) Size Get Size 224 // Get] 225 // 226 // Though this is not the minimal possible combination of command, its already 227 // pretty close. 228 func Example_circularqueue() { 229 parameters := gopter.DefaultTestParametersWithSeed(1234) // Example should generate reproducible results, otherwise DefaultTestParameters() will suffice 230 231 properties := gopter.NewProperties(parameters) 232 233 properties.Property("circular buffer", commands.Prop(cbCommands)) 234 235 // When using testing.T you might just use: properties.TestingRun(t) 236 properties.Run(gopter.ConsoleReporter(false)) 237 // Output: 238 // ! circular buffer: Falsified after 96 passed tests. 239 // ARG_0: initialState=State(size=7, elements=[]) sequential=[Put(0) Put(0) 240 // Get Put(0) Get Put(0) Put(0) Get Put(0) Get Put(0) Get Put(-1) Put(0) 241 // Put(0) Put(0) Put(0) Get Get Put(2) Get] 242 // ARG_0_ORIGINAL (85 shrinks): initialState=State(size=7, elements=[]) 243 // sequential=[Put(-1855365712) Put(-1591723498) Get Size Size 244 // Put(-1015561691) Get Put(397128011) Size Get Put(1943174048) Size 245 // Put(1309500770) Size Get Put(-879438231) Size Get Put(-1644094687) Get 246 // Put(-1818606323) Size Put(488620313) Size Put(-1219794505) 247 // Put(1166147059) Get Put(11390361) Get Size Put(-1407993944) Get Get Size 248 // Put(1393923085) Get Put(1222853245) Size Put(2070918543) Put(1741323168) 249 // Size Get Get Size Put(2019939681) Get Put(-170089451) Size Get Get Size 250 // Size Put(-49249034) Put(1229062846) Put(642598551) Get Put(1183453167) 251 // Size Get Get Get Put(1010460728) Put(6828709) Put(-185198587) Size Size 252 // Get Put(586459644) Get Size Put(-1802196502) Get Size Put(2097590857) Get 253 // Get Get Get Size Put(-474576011) Size Get Size Size Put(771190414) Size 254 // Put(-1509199920) Get Put(967212411) Size Get Put(578995532) Size Get Size 255 // Get] 256 }