forked from cockroachdb/pebble
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
meta: add state machine for generating sequences of operations
Currently, sequences of operations for metamorphic tests are generated for certain specific cases (e.g. a SINGLEDEL follows a key that has been SET once). Additional test sequences require maintaining the "state" of the sequence in the `generator`, which clutters the struct with various fields required for the state management. Add a `sequenceGenerator` struct, which uses a "transition map" (a mapping from a current state to next state, along with a corresponding output for the transition) to model a state machine. The state machine can be used to generate random sequences of operations that adhere to certain rules (e.g. output a GET following a SET for a given key). A `generator` can be constructed containing one or more `sequenceGenerator`s that, when selected by the random number generator (i.e. the "deck"), generate the next operation in the sequence governed by the state machine and place the operation into the operation log. Related to cockroachdb/cockroach#69414.
- Loading branch information
Showing
4 changed files
with
146 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package metamorphic | ||
|
||
import "golang.org/x/exp/rand" | ||
|
||
// transitionFn is a node in the state machine graph. The node is a function | ||
// that generates the op resulting from moving to this node and the next opType | ||
// representing the next state in the state machine. | ||
type transitionFn func(rng *rand.Rand) (current op, next opType) | ||
|
||
// transitionMap is a blueprint for generating sequences of ops. It functions as | ||
// a graph representing a state machine. Each node in the graph is a function | ||
// returning the current op and the next opType to transition to. | ||
type transitionMap map[opType]transitionFn | ||
|
||
// stateDone is the terminal state for the state machine. | ||
var stateDone opType = -1 | ||
|
||
// sequenceGenerator generates a sequence of ops from an transitionMap | ||
// corresponding to a specific sequenceGenerator. | ||
type sequenceGenerator struct { | ||
tMap transitionMap | ||
nextState opType | ||
steps int | ||
} | ||
|
||
// newSequenceGenerator constructs a new sequenceGenerator with the given | ||
// transition map, post-ops, and the initial state of the sequence. | ||
func newSequenceGenerator(m transitionMap, initial opType) *sequenceGenerator { | ||
return &sequenceGenerator{ | ||
tMap: m, | ||
nextState: initial, | ||
} | ||
} | ||
|
||
// next generates the next ops from the transitionMap state machine, and | ||
// transitions the transitionMap to the next state. | ||
func (g *sequenceGenerator) next(rng *rand.Rand) op { | ||
if g.nextState == stateDone { | ||
return nil | ||
} | ||
|
||
// Randomly pick the next state to transition into. Generate the op from | ||
// this transition and move to the next state. | ||
var o op | ||
o, g.nextState = g.tMap[g.nextState](rng) | ||
g.steps++ | ||
|
||
return o | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package metamorphic | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
"golang.org/x/exp/rand" | ||
) | ||
|
||
func TestSequenceGenerator(t *testing.T) { | ||
n := 10 | ||
key, value := []byte("foo"), []byte("bar") | ||
|
||
// A simple state machine that alternates states between GET and SET n times | ||
// before transitioning into a terminal state. | ||
tMap := transitionMap{ | ||
readerGet: func(rng *rand.Rand) (current op, next opType) { | ||
current = &getOp{key: key, readerID: makeObjID(dbTag, 0)} | ||
n-- | ||
if n > 0 { | ||
next = writerSet | ||
} else { | ||
next = stateDone | ||
} | ||
return | ||
}, | ||
writerSet: func(rng *rand.Rand) (current op, next opType) { | ||
current = &setOp{key: key, value: value, writerID: makeObjID(dbTag, 0)} | ||
n-- | ||
if n > 0 { | ||
next = readerGet | ||
} else { | ||
next = stateDone | ||
} | ||
return | ||
}, | ||
} | ||
|
||
rng := rand.New(rand.NewSource(0)) | ||
g := newSequenceGenerator(tMap, readerGet) | ||
|
||
// Generate the sequence from the state machine, counting GETs and SETs. | ||
var nGet, nSet int | ||
var last opType | ||
for { | ||
op := g.next(rng) | ||
if op == nil { | ||
break // Terminal state. | ||
} | ||
switch op.(type) { | ||
case *getOp: | ||
nGet++ | ||
if last > 0 { | ||
require.Equal(t, writerSet, last) | ||
} | ||
last = readerGet | ||
case *setOp: | ||
nSet++ | ||
require.Equal(t, readerGet, last) | ||
last = writerSet | ||
default: | ||
t.Fatalf("unknown op type: %s", op) | ||
} | ||
} | ||
|
||
require.Equal(t, nGet, 5) | ||
require.Equal(t, nSet, 5) | ||
} |