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 SET -> DELETE -> SINGLEDEL sequence generator
Add a `sequenceGenerator` instance with a transition map that generates the following sequence of operations, similar to the problematic sequence generated for cockroachdb/cockroach#69414: ``` ((SET -> GET)+ -> DELETE -> GET)+ -> SINGLEDEL -> (GET)+ ``` See also cockroachdb/cockroach#69891.
- Loading branch information
Showing
3 changed files
with
171 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
package metamorphic | ||
|
||
import ( | ||
"github.com/cockroachdb/pebble/internal/randvar" | ||
"golang.org/x/exp/rand" | ||
) | ||
|
||
// makeDelToSingleDelSequence returns a sequenceGenerator that will generate | ||
// sequences of the form: | ||
// | ||
// (SET -> GET)+ -> DELETE -> GET -> SET -> GET -> SINGLEDEL -> GET+ | ||
// | ||
// i.e. one or more SETs, followed by a DELETE, followed by a single SET. This | ||
// SET is then removed via a SINGLEDEL. GETs are interspersed between all | ||
// operations. The sequence completes with a fixed number of GETs, to simulate | ||
// fetching the key after it has been single-deleted. | ||
// | ||
// This sequenceGenerator is regression test for cockroachdb/cockroach#69414. | ||
func makeDelToSingleDelSequence(key []byte, valueFn func() []byte) *sequenceGenerator { | ||
reader, writer := makeObjID(dbTag, 0), makeObjID(dbTag, 0) | ||
|
||
var statePrev opType | ||
var doneDel, doneSingleDel bool | ||
var setCount, trailingGetCount int | ||
return newSequenceGenerator( | ||
transitionMap{ | ||
writerSet: func(rng *rand.Rand) (current op, next opType) { | ||
setCount++ | ||
current = &setOp{writerID: writer, key: key, value: valueFn()} | ||
statePrev = writerSet | ||
next = readerGet | ||
return | ||
}, | ||
writerDelete: func(rng *rand.Rand) (current op, next opType) { | ||
doneDel = true | ||
current = &deleteOp{writerID: writer, key: key} | ||
statePrev = writerDelete | ||
next = readerGet | ||
return | ||
}, | ||
writerSingleDelete: func(rng *rand.Rand) (current op, next opType) { | ||
doneSingleDel = true | ||
current = &singleDeleteOp{writerID: writer, key: key} | ||
statePrev = writerSingleDelete | ||
next = readerGet | ||
return | ||
}, | ||
readerGet: func(rng *rand.Rand) (current op, next opType) { | ||
switch statePrev { | ||
case writerSet: | ||
if doneDel && setCount > 1 { | ||
// Transition to SINGLEDEL only when we've performed a | ||
// DEL followed by a single SET. | ||
next = writerSingleDelete | ||
} else { | ||
// Else, bias towards performing more sets. | ||
r := randvar.NewWeighted(rng, 0.8, 0.2) | ||
next = []opType{writerSet, writerDelete}[r.Int()] | ||
} | ||
case writerDelete: | ||
// Transition back to SET after performing a DELETE. | ||
next = writerSet | ||
case writerSingleDelete, readerGet: | ||
// Perform a fixed number of trailing GETs to fetch the key | ||
// after it has been single-deleted. | ||
if doneSingleDel && trailingGetCount == 10 { | ||
next = stateDone | ||
return | ||
} | ||
trailingGetCount++ | ||
next = readerGet | ||
} | ||
|
||
current = &getOp{readerID: reader, key: key} | ||
statePrev = readerGet | ||
return | ||
}, | ||
}, | ||
// Start with a SET. | ||
writerSet, | ||
) | ||
} |
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,86 @@ | ||
package metamorphic | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/require" | ||
"golang.org/x/exp/rand" | ||
) | ||
|
||
func TestDelToSingleDel(t *testing.T) { | ||
rng := rand.New(rand.NewSource(uint64(time.Now().UnixNano()))) | ||
g := newGenerator(rng) | ||
|
||
key := g.randValue(4, 12) | ||
valueFn := func() []byte { return g.randValue(4, 12) } | ||
s := makeDelToSingleDelSequence(key, valueFn) | ||
|
||
// Track the positions of each operation in the sequence. | ||
var sets, gets, dels, singleDels []int | ||
var i int | ||
for { | ||
op := s.next(rng) | ||
if op == nil { | ||
break | ||
} | ||
fmt.Println(op) | ||
switch op.(type) { | ||
case *setOp: | ||
sets = append(sets, i) | ||
case *getOp: | ||
gets = append(gets, i) | ||
case *deleteOp: | ||
dels = append(dels, i) | ||
case *singleDeleteOp: | ||
singleDels = append(singleDels, i) | ||
} | ||
i++ | ||
} | ||
|
||
// All ops occurred at least once. | ||
if sets == nil || gets == nil || dels == nil || singleDels == nil { | ||
t.Fatalf("expected each operation to occur at least once") | ||
} | ||
|
||
// Exactly one SINGLEDEL. | ||
require.Len(t, singleDels, 1) | ||
|
||
// A single SET in between the DELETE and SINGLEDEL. | ||
idxSingleDel := singleDels[0] | ||
idxLastDel := dels[len(dels)-1] | ||
cnt := countOps(sets, func(i int) bool { | ||
return i > idxLastDel && i < idxSingleDel | ||
}) | ||
require.Equal(t, 1, cnt) | ||
|
||
// No SETs or DELs after the SINGLEDEL. | ||
idxLastSet := sets[len(sets)-1] | ||
require.Less(t, idxLastSet, idxSingleDel) | ||
require.Less(t, idxLastDel, idxSingleDel) | ||
|
||
// A single GET follows every SET and DEL. | ||
cnt = countOps(gets, func(i int) bool { | ||
return i < idxSingleDel && i%2 == 1 /* odd numbered indices */ | ||
}) | ||
require.Equal(t, cnt, len(sets)+len(dels)) | ||
|
||
// A fixed number of GETS (10) following the SINGLEDEL. | ||
cnt = countOps(gets, func(i int) bool { | ||
return i > idxSingleDel | ||
}) | ||
require.Equal(t, 10, cnt) | ||
} | ||
|
||
// countOps returns the number of operations with indices satisfying the given | ||
// predicate. | ||
func countOps(is []int, predicate func(i int) bool) int { | ||
var count int | ||
for _, idx := range is { | ||
if predicate(idx) { | ||
count++ | ||
} | ||
} | ||
return count | ||
} |