1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
|
package commands_test
import (
"github.com/leanovate/gopter"
"github.com/leanovate/gopter/commands"
"github.com/leanovate/gopter/gen"
)
type BuggyCounter struct {
n int
}
func (c *BuggyCounter) Inc() {
c.n++
}
func (c *BuggyCounter) Dec() {
if c.n > 3 {
// Intentional error
c.n -= 2
} else {
c.n--
}
}
func (c *BuggyCounter) Get() int {
return c.n
}
func (c *BuggyCounter) Reset() {
c.n = 0
}
var GetBuggyCommand = &commands.ProtoCommand{
Name: "GET",
RunFunc: func(systemUnderTest commands.SystemUnderTest) commands.Result {
return systemUnderTest.(*BuggyCounter).Get()
},
PostConditionFunc: func(state commands.State, result commands.Result) *gopter.PropResult {
if state.(int) != result.(int) {
return &gopter.PropResult{Status: gopter.PropFalse}
}
return &gopter.PropResult{Status: gopter.PropTrue}
},
}
var IncBuggyCommand = &commands.ProtoCommand{
Name: "INC",
RunFunc: func(systemUnderTest commands.SystemUnderTest) commands.Result {
systemUnderTest.(*BuggyCounter).Inc()
return nil
},
NextStateFunc: func(state commands.State) commands.State {
return state.(int) + 1
},
}
var DecBuggyCommand = &commands.ProtoCommand{
Name: "DEC",
RunFunc: func(systemUnderTest commands.SystemUnderTest) commands.Result {
systemUnderTest.(*BuggyCounter).Dec()
return nil
},
NextStateFunc: func(state commands.State) commands.State {
return state.(int) - 1
},
}
var ResetBuggyCommand = &commands.ProtoCommand{
Name: "RESET",
RunFunc: func(systemUnderTest commands.SystemUnderTest) commands.Result {
systemUnderTest.(*BuggyCounter).Reset()
return nil
},
NextStateFunc: func(state commands.State) commands.State {
return 0
},
}
var buggyCounterCommands = &commands.ProtoCommands{
NewSystemUnderTestFunc: func(initialState commands.State) commands.SystemUnderTest {
return &BuggyCounter{}
},
InitialStateGen: gen.Const(0),
InitialPreConditionFunc: func(state commands.State) bool {
return state.(int) == 0
},
GenCommandFunc: func(state commands.State) gopter.Gen {
return gen.OneConstOf(GetBuggyCommand, IncBuggyCommand, DecBuggyCommand, ResetBuggyCommand)
},
}
// Demonstrates the usage of the commands package to find a bug in a counter
// implementation that only occurs if the counter is above 3.
//
// The output of this example will be
// ! buggy counter: Falsified after 45 passed tests.
// ARG_0: initial=0 sequential=[INC INC INC INC DEC GET]
// ARG_0_ORIGINAL (9 shrinks): initial=0 sequential=[DEC RESET GET GET GET
// RESET DEC DEC INC INC RESET RESET DEC INC RESET INC INC GET INC INC DEC
// DEC GET RESET INC INC DEC INC INC INC RESET RESET INC INC GET INC DEC GET
// DEC GET INC RESET INC INC RESET]
// I.e. gopter found an invalid state with a rather long sequence of arbitrary
// commands/function calls, and then shrank that sequence down to
// INC INC INC INC DEC GET
// which is indeed the minimal set of commands one has to perform to find the
// bug.
func Example_buggyCounter() {
parameters := gopter.DefaultTestParameters()
parameters.Rng.Seed(1234) // Just for this example to generate reproducible results
properties := gopter.NewProperties(parameters)
properties.Property("buggy counter", commands.Prop(buggyCounterCommands))
// When using testing.T you might just use: properties.TestingRun(t)
properties.Run(gopter.ConsoleReporter(false))
// Output:
// ! buggy counter: Falsified after 43 passed tests.
// ARG_0: initialState=0 sequential=[INC INC INC INC DEC GET]
// ARG_0_ORIGINAL (8 shrinks): initialState=0 sequential=[RESET GET GET GET
// RESET DEC DEC INC INC RESET RESET DEC INC RESET INC INC GET INC INC DEC
// DEC GET RESET INC INC DEC INC INC INC RESET RESET INC INC GET INC DEC GET
// DEC GET INC RESET INC INC]
}
|