2019-05-22 23:16:55 +02:00
|
|
|
package mergeset
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"math/rand"
|
|
|
|
"sort"
|
2024-02-23 22:29:23 +01:00
|
|
|
"sync/atomic"
|
2019-05-22 23:16:55 +02:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestPartSearch(t *testing.T) {
|
2023-01-24 04:43:39 +01:00
|
|
|
r := rand.New(rand.NewSource(1))
|
|
|
|
p, items, err := newTestPart(r, 10, 4000)
|
2019-05-22 23:16:55 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("cannot create test part: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
t.Run("serial", func(t *testing.T) {
|
2023-01-24 04:43:39 +01:00
|
|
|
if err := testPartSearchSerial(r, p, items); err != nil {
|
2019-05-22 23:16:55 +02:00
|
|
|
t.Fatalf("error in serial part search test: %s", err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("concurrent", func(t *testing.T) {
|
|
|
|
if err := testPartSearchConcurrent(p, items); err != nil {
|
|
|
|
t.Fatalf("error in concurrent part search test: %s", err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func testPartSearchConcurrent(p *part, items []string) error {
|
|
|
|
const goroutinesCount = 5
|
|
|
|
ch := make(chan error, goroutinesCount)
|
|
|
|
for i := 0; i < goroutinesCount; i++ {
|
2023-01-24 04:43:39 +01:00
|
|
|
go func(n int) {
|
|
|
|
rLocal := rand.New(rand.NewSource(int64(n)))
|
|
|
|
ch <- testPartSearchSerial(rLocal, p, items)
|
|
|
|
}(i)
|
2019-05-22 23:16:55 +02:00
|
|
|
}
|
|
|
|
for i := 0; i < goroutinesCount; i++ {
|
|
|
|
select {
|
|
|
|
case err := <-ch:
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
case <-time.After(time.Second * 5):
|
|
|
|
return fmt.Errorf("timeout")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-01-24 04:43:39 +01:00
|
|
|
func testPartSearchSerial(r *rand.Rand, p *part, items []string) error {
|
2019-05-22 23:16:55 +02:00
|
|
|
var ps partSearch
|
|
|
|
|
2024-11-04 14:29:14 +01:00
|
|
|
ps.Init(p)
|
2019-05-22 23:16:55 +02:00
|
|
|
var k []byte
|
|
|
|
|
|
|
|
// Search for the item smaller than the items[0]
|
|
|
|
k = append(k[:0], items[0]...)
|
|
|
|
if len(k) > 0 {
|
|
|
|
k = k[:len(k)-1]
|
|
|
|
}
|
|
|
|
ps.Seek(k)
|
|
|
|
for i, item := range items {
|
|
|
|
if !ps.NextItem() {
|
|
|
|
return fmt.Errorf("missing item at position %d", i)
|
|
|
|
}
|
|
|
|
if string(ps.Item) != item {
|
|
|
|
return fmt.Errorf("unexpected item found at position %d; got %X; want %X", i, ps.Item, item)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ps.NextItem() {
|
|
|
|
return fmt.Errorf("unexpected item found past the end of all the items: %X", ps.Item)
|
|
|
|
}
|
|
|
|
if err := ps.Error(); err != nil {
|
2020-06-30 21:58:18 +02:00
|
|
|
return fmt.Errorf("unexpected error: %w", err)
|
2019-05-22 23:16:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Search for the item bigger than the items[len(items)-1]
|
|
|
|
k = append(k[:0], items[len(items)-1]...)
|
|
|
|
k = append(k, "tail"...)
|
|
|
|
ps.Seek(k)
|
|
|
|
if ps.NextItem() {
|
|
|
|
return fmt.Errorf("unexpected item found: %X; want nothing", ps.Item)
|
|
|
|
}
|
|
|
|
if err := ps.Error(); err != nil {
|
2020-06-30 21:58:18 +02:00
|
|
|
return fmt.Errorf("unexpected error when searching past the last item: %w", err)
|
2019-05-22 23:16:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Search for inner items
|
|
|
|
for loop := 0; loop < 100; loop++ {
|
2023-01-24 04:43:39 +01:00
|
|
|
idx := r.Intn(len(items))
|
2019-05-22 23:16:55 +02:00
|
|
|
k = append(k[:0], items[idx]...)
|
|
|
|
ps.Seek(k)
|
|
|
|
n := sort.Search(len(items), func(i int) bool {
|
|
|
|
return string(k) <= string(items[i])
|
|
|
|
})
|
|
|
|
for i := n; i < len(items); i++ {
|
|
|
|
if !ps.NextItem() {
|
|
|
|
return fmt.Errorf("missing item at position %d for idx %d on the loop %d", i, n, loop)
|
|
|
|
}
|
|
|
|
if string(ps.Item) != items[i] {
|
|
|
|
return fmt.Errorf("unexpected item found at position %d for idx %d out of %d items; loop %d; key=%X; got %X; want %X",
|
|
|
|
i, n, len(items), loop, k, ps.Item, items[i])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ps.NextItem() {
|
|
|
|
return fmt.Errorf("unexpected item found past the end of all the items for idx %d out of %d items; loop %d: got %X", n, len(items), loop, ps.Item)
|
|
|
|
}
|
|
|
|
if err := ps.Error(); err != nil {
|
2020-06-30 21:58:18 +02:00
|
|
|
return fmt.Errorf("unexpected error on loop %d: %w", loop, err)
|
2019-05-22 23:16:55 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Search for sorted items
|
|
|
|
for i, item := range items {
|
|
|
|
ps.Seek([]byte(item))
|
|
|
|
if !ps.NextItem() {
|
|
|
|
return fmt.Errorf("cannot find items[%d]=%X", i, item)
|
|
|
|
}
|
|
|
|
if string(ps.Item) != item {
|
|
|
|
return fmt.Errorf("unexpected item found at position %d: got %X; want %X", i, ps.Item, item)
|
|
|
|
}
|
|
|
|
if err := ps.Error(); err != nil {
|
2020-06-30 21:58:18 +02:00
|
|
|
return fmt.Errorf("unexpected error when searching for items[%d]=%X: %w", i, item, err)
|
2019-05-22 23:16:55 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Search for reversely sorted items
|
|
|
|
for i := 0; i < len(items); i++ {
|
|
|
|
item := items[len(items)-i-1]
|
|
|
|
ps.Seek([]byte(item))
|
|
|
|
if !ps.NextItem() {
|
|
|
|
return fmt.Errorf("cannot find items[%d]=%X", i, item)
|
|
|
|
}
|
|
|
|
if string(ps.Item) != item {
|
|
|
|
return fmt.Errorf("unexpected item found at position %d: got %X; want %X", i, ps.Item, item)
|
|
|
|
}
|
|
|
|
if err := ps.Error(); err != nil {
|
2020-06-30 21:58:18 +02:00
|
|
|
return fmt.Errorf("unexpected error when searching for items[%d]=%X: %w", i, item, err)
|
2019-05-22 23:16:55 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-01-24 04:43:39 +01:00
|
|
|
func newTestPart(r *rand.Rand, blocksCount, maxItemsPerBlock int) (*part, []string, error) {
|
|
|
|
bsrs, items := newTestInmemoryBlockStreamReaders(r, blocksCount, maxItemsPerBlock)
|
2019-05-22 23:16:55 +02:00
|
|
|
|
2024-02-23 22:29:23 +01:00
|
|
|
var itemsMerged atomic.Uint64
|
2019-05-22 23:16:55 +02:00
|
|
|
var ip inmemoryPart
|
|
|
|
var bsw blockStreamWriter
|
2023-04-15 00:46:09 +02:00
|
|
|
bsw.MustInitFromInmemoryPart(&ip, -3)
|
2020-07-30 18:57:25 +02:00
|
|
|
if err := mergeBlockStreams(&ip.ph, &bsw, bsrs, nil, nil, &itemsMerged); err != nil {
|
2020-06-30 21:58:18 +02:00
|
|
|
return nil, nil, fmt.Errorf("cannot merge blocks: %w", err)
|
2019-05-22 23:16:55 +02:00
|
|
|
}
|
2024-02-23 22:29:23 +01:00
|
|
|
if n := itemsMerged.Load(); n != uint64(len(items)) {
|
|
|
|
return nil, nil, fmt.Errorf("unexpected itemsMerged; got %d; want %d", n, len(items))
|
2019-05-22 23:16:55 +02:00
|
|
|
}
|
2019-07-04 18:09:40 +02:00
|
|
|
size := ip.size()
|
2023-04-15 00:46:09 +02:00
|
|
|
p := newPart(&ip.ph, "partName", size, ip.metaindexData.NewReader(), &ip.indexData, &ip.itemsData, &ip.lensData)
|
2019-05-22 23:16:55 +02:00
|
|
|
return p, items, nil
|
|
|
|
}
|