blob: e56acd12bed00a6e36356d594a71509698aea819 [file] [log] [blame]
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package storage
import (
"fmt"
"slices"
"sync"
"testing"
"time"
"golang.org/x/oscar/internal/testutil"
"rsc.io/ordered"
)
// TestDB runs basic tests on db.
// It should be empty when TestDB is called.
// To run tests on Lock and Unlock, also call [TestDBLock].
func TestDB(t *testing.T, db DB) {
db.Set([]byte("key"), []byte("value"))
if val, ok := db.Get([]byte("key")); string(val) != "value" || ok != true {
// unreachable except for bad db
t.Fatalf("Get(key) = %q, %v, want %q, true", val, ok, "value")
}
if val, ok := db.Get([]byte("missing")); val != nil || ok != false {
// unreachable except for bad db
t.Fatalf("Get(missing) = %v, %v, want nil, false", val, ok)
}
testutil.StopPanic(func() {
db.Set(nil, []byte{0})
t.Fatal("Set with nil key did not panic")
})
db.Delete([]byte("key"))
if val, ok := db.Get([]byte("key")); val != nil || ok != false {
// unreachable except for bad db
t.Fatalf("Get(key) after delete = %v, %v, want nil, false", val, ok)
}
b := db.Batch()
for i := range 10 {
b.Set(ordered.Encode(i), []byte(fmt.Sprint(i)))
b.MaybeApply()
}
b.Apply()
testutil.StopPanic(func() {
b.Set(nil, []byte{0})
t.Fatal("Set with nil key did not panic")
})
collect := func(min, max, stop int) []int {
t.Helper()
var list []int
for key, val := range db.Scan(ordered.Encode(min), ordered.Encode(max)) {
var i int
if err := ordered.Decode(key, &i); err != nil {
// unreachable except for bad db
t.Fatalf("db.Scan malformed key %v", Fmt(key))
}
if sv, want := string(val()), fmt.Sprint(i); sv != want {
// unreachable except for bad db
t.Fatalf("db.Scan key %v val=%q, want %q", i, sv, want)
}
list = append(list, i)
if i == stop {
break
}
}
return list
}
if scan, want := collect(3, 6, -1), []int{3, 4, 5, 6}; !slices.Equal(scan, want) {
// unreachable except for bad db
t.Fatalf("Scan(3, 6) = %v, want %v", scan, want)
}
if scan, want := collect(3, 6, 5), []int{3, 4, 5}; !slices.Equal(scan, want) {
// unreachable except for bad db
t.Fatalf("Scan(3, 6) with break at 5 = %v, want %v", scan, want)
}
// Passing a zero-length value for end to Scan will return an empty sequence.
something := false
for range db.Scan(nil, nil) {
something = true
break
}
if something {
t.Fatal("Scan(nil, nil) returned a non-empty sequence, want an empty one")
}
db.DeleteRange(ordered.Encode(4), ordered.Encode(7))
if scan, want := collect(-1, 11, -1), []int{0, 1, 2, 3, 8, 9}; !slices.Equal(scan, want) {
// unreachable except for bad db
t.Fatalf("Scan(-1, 11) after Delete(4, 7) = %v, want %v", scan, want)
}
b = db.Batch()
for i := range 5 {
b.Delete(ordered.Encode(i))
b.Set(ordered.Encode(2*i), []byte(fmt.Sprint(2*i)))
}
b.DeleteRange(ordered.Encode(0), ordered.Encode(0))
b.Apply()
if scan, want := collect(-1, 11, -1), []int{6, 8, 9}; !slices.Equal(scan, want) {
// unreachable except for bad db
t.Fatalf("Scan(-1, 11) after batch Delete+Set = %v, want %v", scan, want)
}
// Check that batch.Apply clears the batch.
k := ordered.Encode("a")
b = db.Batch()
b.Set(k, []byte{0})
b.Apply()
db.Delete(k)
b.Apply() // should be a no-op
if _, ok := db.Get(k); ok {
t.Fatalf("empty Apply should be no-op, but got previous value")
}
// Can't test much, but check that it doesn't crash.
db.Flush()
}
type locker interface {
Lock(string)
Unlock(string)
}
// TestDBLock verifies that Lock behaves correctly.
// It is separate from [TestDB] because it can't be used
// with a recorder/replayer, thanks to its sensitivity
// to time.
func TestDBLock(t *testing.T, db locker) {
db.Lock("abc")
c := make(chan struct{})
var wg sync.WaitGroup
wg.Add(1)
go func() {
db.Lock("abc")
close(c)
db.Unlock("abc")
wg.Done()
}()
// The db.Lock in the goroutine should block, since the lock is already held.
select {
case <-c:
t.Fatal("Lock did not wait")
case <-time.After(1 * time.Second):
}
db.Unlock("abc")
// Now the db.Lock in the goroutine should return.
<-c
wg.Wait()
testutil.StopPanic(func() {
db.Unlock("def")
t.Errorf("Unlock never-locked key did not panic")
})
}