func TestEngineBatch(t *testing.T) { defer leaktest.AfterTest(t)() runWithAllEngines(func(engine Engine, t *testing.T) { numShuffles := 100 key := mvccKey("a") // Those are randomized below. type data struct { key MVCCKey value []byte merge bool } batch := []data{ {key, appender("~ockroachDB"), false}, {key, appender("C~ckroachDB"), false}, {key, appender("Co~kroachDB"), false}, {key, appender("Coc~roachDB"), false}, {key, appender("C**k~oachDB"), false}, {key, appender("Cockr~achDB"), false}, {key, appender("Cockro~chDB"), false}, {key, appender("Cockroa~hDB"), false}, {key, appender("Cockroac~DB"), false}, {key, appender("Cockroach~B"), false}, {key, appender("CockroachD~"), false}, {key, nil, false}, {key, appender("C"), true}, {key, appender(" o"), true}, {key, appender(" c"), true}, {key, appender(" k"), true}, {key, appender("r"), true}, {key, appender(" o"), true}, {key, appender(" a"), true}, {key, appender(" c"), true}, {key, appender("h"), true}, {key, appender(" D"), true}, {key, appender(" B"), true}, } apply := func(eng ReadWriter, d data) error { if d.value == nil { return eng.Clear(d.key) } else if d.merge { return eng.Merge(d.key, d.value) } return eng.Put(d.key, d.value) } get := func(eng ReadWriter, key MVCCKey) []byte { b, err := eng.Get(key) if err != nil { t.Fatal(err) } var m enginepb.MVCCMetadata if err := proto.Unmarshal(b, &m); err != nil { t.Fatal(err) } if !m.IsInline() { return nil } valueBytes, err := MakeValue(m).GetBytes() if err != nil { t.Fatal(err) } return valueBytes } for i := 0; i < numShuffles; i++ { // In each run, create an array of shuffled operations. shuffledIndices := rand.Perm(len(batch)) currentBatch := make([]data, len(batch)) for k := range currentBatch { currentBatch[k] = batch[shuffledIndices[k]] } // Reset the key if err := engine.Clear(key); err != nil { t.Fatal(err) } // Run it once with individual operations and remember the result. for i, op := range currentBatch { if err := apply(engine, op); err != nil { t.Errorf("%d: op %v: %v", i, op, err) continue } } expectedValue := get(engine, key) // Run the whole thing as a batch and compare. b := engine.NewBatch() defer b.Close() if err := b.Clear(key); err != nil { t.Fatal(err) } for _, op := range currentBatch { if err := apply(b, op); err != nil { t.Fatal(err) } } // Try getting the value from the batch. actualValue := get(b, key) if !bytes.Equal(actualValue, expectedValue) { t.Errorf("%d: expected %s, but got %s", i, expectedValue, actualValue) } // Try using an iterator to get the value from the batch. iter := b.NewIterator(false) iter.Seek(key) if !iter.Valid() { if currentBatch[len(currentBatch)-1].value != nil { t.Errorf("%d: batch seek invalid", i) } } else if !iter.Key().Equal(key) { t.Errorf("%d: batch seek expected key %s, but got %s", i, key, iter.Key()) } else { var m enginepb.MVCCMetadata if err := iter.ValueProto(&m); err != nil { t.Fatal(err) } valueBytes, err := MakeValue(m).GetBytes() if err != nil { t.Fatal(err) } if !bytes.Equal(valueBytes, expectedValue) { t.Errorf("%d: expected %s, but got %s", i, expectedValue, valueBytes) } } iter.Close() // Commit the batch and try getting the value from the engine. if err := b.Commit(); err != nil { t.Errorf("%d: %v", i, err) continue } actualValue = get(engine, key) if !bytes.Equal(actualValue, expectedValue) { t.Errorf("%d: expected %s, but got %s", i, expectedValue, actualValue) } } }, t) }