2019-05-22 21:16:55 +00:00
|
|
|
package mergeset
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"math/rand"
|
|
|
|
"os"
|
|
|
|
"sort"
|
2019-08-29 11:39:05 +00:00
|
|
|
"sync/atomic"
|
2019-05-22 21:16:55 +00:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestMain(m *testing.M) {
|
|
|
|
n := m.Run()
|
|
|
|
os.Exit(n)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestTableSearchSerial(t *testing.T) {
|
|
|
|
const path = "TestTableSearchSerial"
|
|
|
|
if err := os.RemoveAll(path); err != nil {
|
|
|
|
t.Fatalf("cannot remove %q: %s", path, err)
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
_ = os.RemoveAll(path)
|
|
|
|
}()
|
|
|
|
|
|
|
|
const itemsCount = 1e5
|
|
|
|
|
|
|
|
items := func() []string {
|
2023-01-24 03:43:39 +00:00
|
|
|
r := rand.New(rand.NewSource(1))
|
|
|
|
tb, items, err := newTestTable(r, path, itemsCount)
|
2019-05-22 21:16:55 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("cannot create test table: %s", err)
|
|
|
|
}
|
|
|
|
defer tb.MustClose()
|
|
|
|
if err := testTableSearchSerial(tb, items); err != nil {
|
|
|
|
t.Fatalf("unexpected error: %s", err)
|
|
|
|
}
|
|
|
|
return items
|
|
|
|
}()
|
|
|
|
|
|
|
|
func() {
|
|
|
|
// Re-open the table and verify the search works.
|
2024-02-23 21:29:23 +00:00
|
|
|
var isReadOnly atomic.Bool
|
2023-04-15 05:08:43 +00:00
|
|
|
tb := MustOpenTable(path, nil, nil, &isReadOnly)
|
2019-05-22 21:16:55 +00:00
|
|
|
defer tb.MustClose()
|
|
|
|
if err := testTableSearchSerial(tb, items); err != nil {
|
|
|
|
t.Fatalf("unexpected error: %s", err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestTableSearchConcurrent(t *testing.T) {
|
|
|
|
const path = "TestTableSearchConcurrent"
|
|
|
|
if err := os.RemoveAll(path); err != nil {
|
|
|
|
t.Fatalf("cannot remove %q: %s", path, err)
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
_ = os.RemoveAll(path)
|
|
|
|
}()
|
|
|
|
|
|
|
|
const itemsCount = 1e5
|
|
|
|
items := func() []string {
|
2023-01-24 03:43:39 +00:00
|
|
|
r := rand.New(rand.NewSource(2))
|
|
|
|
tb, items, err := newTestTable(r, path, itemsCount)
|
2019-05-22 21:16:55 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("cannot create test table: %s", err)
|
|
|
|
}
|
|
|
|
defer tb.MustClose()
|
|
|
|
if err := testTableSearchConcurrent(tb, items); err != nil {
|
|
|
|
t.Fatalf("unexpected error: %s", err)
|
|
|
|
}
|
|
|
|
return items
|
|
|
|
}()
|
|
|
|
|
|
|
|
// Re-open the table and verify the search works.
|
|
|
|
func() {
|
2024-02-23 21:29:23 +00:00
|
|
|
var isReadOnly atomic.Bool
|
2023-04-15 05:08:43 +00:00
|
|
|
tb := MustOpenTable(path, nil, nil, &isReadOnly)
|
2019-05-22 21:16:55 +00:00
|
|
|
defer tb.MustClose()
|
|
|
|
if err := testTableSearchConcurrent(tb, items); err != nil {
|
|
|
|
t.Fatalf("unexpected error: %s", err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
func testTableSearchConcurrent(tb *Table, items []string) error {
|
|
|
|
const goroutines = 5
|
|
|
|
ch := make(chan error, goroutines)
|
|
|
|
for i := 0; i < goroutines; i++ {
|
|
|
|
go func() {
|
|
|
|
ch <- testTableSearchSerial(tb, items)
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
for i := 0; i < goroutines; i++ {
|
|
|
|
select {
|
|
|
|
case err := <-ch:
|
|
|
|
if err != nil {
|
2020-06-30 19:58:18 +00:00
|
|
|
return fmt.Errorf("unexpected error: %w", err)
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
|
|
|
case <-time.After(time.Second * 5):
|
|
|
|
return fmt.Errorf("timeout")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func testTableSearchSerial(tb *Table, items []string) error {
|
|
|
|
var ts TableSearch
|
2024-11-04 13:29:14 +00:00
|
|
|
ts.Init(tb)
|
2019-05-22 21:16:55 +00:00
|
|
|
for _, key := range []string{
|
|
|
|
"",
|
|
|
|
"123",
|
|
|
|
"9",
|
|
|
|
"892",
|
|
|
|
"2384329",
|
|
|
|
"fdsjflfdf",
|
|
|
|
items[0],
|
|
|
|
items[len(items)-1],
|
|
|
|
items[len(items)/2],
|
|
|
|
} {
|
|
|
|
n := sort.Search(len(items), func(i int) bool {
|
|
|
|
return key <= items[i]
|
|
|
|
})
|
|
|
|
ts.Seek([]byte(key))
|
|
|
|
for n < len(items) {
|
|
|
|
item := items[n]
|
|
|
|
if !ts.NextItem() {
|
|
|
|
return fmt.Errorf("missing item %q at position %d when searching for %q", item, n, key)
|
|
|
|
}
|
|
|
|
if string(ts.Item) != item {
|
|
|
|
return fmt.Errorf("unexpected item found at position %d when searching for %q; got %q; want %q", n, key, ts.Item, item)
|
|
|
|
}
|
|
|
|
n++
|
|
|
|
}
|
|
|
|
if ts.NextItem() {
|
2020-11-24 10:41:34 +00:00
|
|
|
return fmt.Errorf("superfluous item found at position %d when searching for %q: %q", n, key, ts.Item)
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
|
|
|
if err := ts.Error(); err != nil {
|
2020-06-30 19:58:18 +00:00
|
|
|
return fmt.Errorf("unexpected error when searching for %q: %w", key, err)
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
ts.MustClose()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-01-24 03:43:39 +00:00
|
|
|
func newTestTable(r *rand.Rand, path string, itemsCount int) (*Table, []string, error) {
|
2024-02-23 21:29:23 +00:00
|
|
|
var flushes atomic.Uint64
|
2019-08-29 11:39:05 +00:00
|
|
|
flushCallback := func() {
|
2024-02-23 21:29:23 +00:00
|
|
|
flushes.Add(1)
|
2019-08-29 11:39:05 +00:00
|
|
|
}
|
2024-02-23 21:29:23 +00:00
|
|
|
var isReadOnly atomic.Bool
|
2023-04-15 05:08:43 +00:00
|
|
|
tb := MustOpenTable(path, flushCallback, nil, &isReadOnly)
|
2019-05-22 21:16:55 +00:00
|
|
|
items := make([]string, itemsCount)
|
|
|
|
for i := 0; i < itemsCount; i++ {
|
2023-01-24 03:43:39 +00:00
|
|
|
item := fmt.Sprintf("%d:%d", r.Intn(1e9), i)
|
2022-12-04 07:30:31 +00:00
|
|
|
tb.AddItems([][]byte{[]byte(item)})
|
2019-05-22 21:16:55 +00:00
|
|
|
items[i] = item
|
|
|
|
}
|
|
|
|
tb.DebugFlush()
|
2024-02-23 21:29:23 +00:00
|
|
|
if itemsCount > 0 && flushes.Load() == 0 {
|
2019-08-29 11:39:05 +00:00
|
|
|
return nil, nil, fmt.Errorf("unexpeted zero flushes for itemsCount=%d", itemsCount)
|
|
|
|
}
|
2019-05-22 21:16:55 +00:00
|
|
|
|
|
|
|
sort.Strings(items)
|
|
|
|
return tb, items, nil
|
|
|
|
}
|