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  }