2026-06-14 07:57:01 +03:00
|
|
|
package worker
|
|
|
|
|
|
|
|
|
|
import (
|
2026-06-14 23:12:03 +03:00
|
|
|
"bytes"
|
2026-06-14 07:57:01 +03:00
|
|
|
"fmt"
|
2026-06-14 23:12:03 +03:00
|
|
|
"os"
|
|
|
|
|
"reflect"
|
2026-06-14 07:57:01 +03:00
|
|
|
"slices"
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
"gordenko.dev/dima/qb"
|
2026-06-14 23:12:03 +03:00
|
|
|
"gordenko.dev/dima/qb/enc"
|
2026-06-14 07:57:01 +03:00
|
|
|
"gordenko.dev/dima/qb/storage"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestWriteReadSnapshot(t *testing.T) {
|
2026-06-14 23:12:03 +03:00
|
|
|
storage.DataPageSize = 32
|
|
|
|
|
storage.DataPagePayloadSize = 20
|
|
|
|
|
storage.IndexPageSize = 16
|
|
|
|
|
|
|
|
|
|
buf := []byte{
|
|
|
|
|
// values
|
|
|
|
|
0x8f, // base value
|
|
|
|
|
0x80, // delta 0
|
|
|
|
|
0x01, // run (len = 3)
|
|
|
|
|
0x81, // delta 1
|
|
|
|
|
0x81, // literal (len = 2)
|
|
|
|
|
// empty space
|
|
|
|
|
0x00, 0x00, 0x00, 0x00,
|
|
|
|
|
0x00, 0x00, 0x00,
|
|
|
|
|
// timestamps
|
|
|
|
|
0x80, // h-byte (literal, len=1)
|
|
|
|
|
0x94, // delta 20
|
|
|
|
|
0x01, // h-byte (run, len=3)
|
|
|
|
|
0xbc, // delta 60
|
|
|
|
|
0x28, 0x80, 0x24, 0x6a, // since
|
|
|
|
|
// footer
|
|
|
|
|
0x00, 0x00, 0x00, 0x00,
|
|
|
|
|
0x00, 0x00, 0x00, 0x00,
|
|
|
|
|
0x00, 0x00, 0x00, 0x00,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
databuf := buf[:storage.DataPagePayloadSize]
|
|
|
|
|
timestampsSize := 8
|
|
|
|
|
valuesSize := 5
|
|
|
|
|
|
|
|
|
|
metrics := map[uint32]*Metric{
|
|
|
|
|
1: {
|
|
|
|
|
metricType: qb.Cumulative,
|
|
|
|
|
fracDigits: 3,
|
|
|
|
|
lastPageNo: 1000,
|
|
|
|
|
buffer: buf,
|
|
|
|
|
timestamps: enc.NewTimeDeltaCompressor(databuf, timestampsSize),
|
|
|
|
|
values: enc.NewValueDeltaCompressor(qb.Cumulative, 3, databuf, valuesSize),
|
|
|
|
|
indexLevelTails: []storage.IndexLevelTail{
|
|
|
|
|
{
|
|
|
|
|
Buffer: []byte{
|
|
|
|
|
0x01, 0x01, 0x01, 0x01,
|
|
|
|
|
0x02, 0x02, 0x02, 0x02,
|
|
|
|
|
0x00,
|
|
|
|
|
// footer
|
|
|
|
|
0x00, 0x00, 0x00, 0x00,
|
|
|
|
|
0x00, 0x00, 0x00,
|
|
|
|
|
},
|
|
|
|
|
RecordsCount: 1,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2026-06-14 07:57:01 +03:00
|
|
|
}
|
|
|
|
|
in := WriteSnapshotIn{
|
|
|
|
|
SnapshotNumber: 1,
|
|
|
|
|
WriteBufferSize: 8 * 1024 * 1024,
|
|
|
|
|
Metrics: metrics,
|
|
|
|
|
FrozenIndexPagesCount: 7,
|
|
|
|
|
IndexPageNumbers: []uint32{
|
|
|
|
|
1, 2, 3, 4, 5,
|
|
|
|
|
},
|
|
|
|
|
FrozenDataPagesCount: 8,
|
|
|
|
|
DataPageNumbers: []uint32{
|
|
|
|
|
6, 7, 8,
|
|
|
|
|
},
|
|
|
|
|
}
|
2026-06-14 23:12:03 +03:00
|
|
|
|
|
|
|
|
fileName := "test.snapshot"
|
|
|
|
|
|
|
|
|
|
err := writeSnapshot(fileName, in)
|
2026-06-14 07:57:01 +03:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("writeSnapshot: %s", err)
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-14 23:12:03 +03:00
|
|
|
out, err := ReadSnapshot(fileName)
|
2026-06-14 07:57:01 +03:00
|
|
|
if err != nil {
|
2026-06-14 23:12:03 +03:00
|
|
|
t.Fatal(err)
|
2026-06-14 07:57:01 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if out.FrozenIndexPagesCount != in.FrozenIndexPagesCount {
|
|
|
|
|
t.Fatalf("FrozenIndexPagesCount: got %d are not equal expected %d",
|
|
|
|
|
out.FrozenIndexPagesCount, in.FrozenIndexPagesCount)
|
|
|
|
|
}
|
|
|
|
|
if out.FrozenDataPagesCount != in.FrozenDataPagesCount {
|
|
|
|
|
t.Fatalf("FrozenDataPagesCount: got %d are not equal expected %d",
|
|
|
|
|
out.FrozenDataPagesCount, in.FrozenDataPagesCount)
|
|
|
|
|
}
|
|
|
|
|
if !slices.Equal(out.IndexPageNumbers, in.IndexPageNumbers) {
|
|
|
|
|
t.Fatalf("IndexPageNumbers: got %v are not equal expected %v",
|
|
|
|
|
out.IndexPageNumbers, in.IndexPageNumbers)
|
|
|
|
|
}
|
|
|
|
|
if !slices.Equal(out.DataPageNumbers, in.DataPageNumbers) {
|
|
|
|
|
t.Fatalf("DataPageNumbers: got %v are not equal expected %v",
|
|
|
|
|
out.DataPageNumbers, in.DataPageNumbers)
|
|
|
|
|
}
|
|
|
|
|
for metricID, replay := range out.Metrics {
|
|
|
|
|
origin, ok := metrics[metricID]
|
|
|
|
|
if !ok {
|
|
|
|
|
t.Fatalf("decoded metricID %d not found in metrics", metricID)
|
|
|
|
|
}
|
|
|
|
|
err = cmpMetric(replay, origin)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("metric %d: %s", metricID, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-06-14 23:12:03 +03:00
|
|
|
os.Remove(fileName)
|
2026-06-14 07:57:01 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func cmpMetric(replay *storage.ReplayMetric, origin *Metric) error {
|
|
|
|
|
if replay.MetricType != origin.metricType {
|
|
|
|
|
return fmt.Errorf("metricType: got %d are not equal expected %d",
|
|
|
|
|
replay.MetricType, origin.metricType)
|
|
|
|
|
}
|
|
|
|
|
if replay.FracDigits != origin.fracDigits {
|
|
|
|
|
return fmt.Errorf("fracDigits: got %d are not equal expected %d",
|
|
|
|
|
replay.FracDigits, origin.fracDigits)
|
|
|
|
|
}
|
|
|
|
|
if replay.LastPageNo != origin.lastPageNo {
|
|
|
|
|
return fmt.Errorf("lastPageNo: got %d are not equal expected %d",
|
|
|
|
|
replay.LastPageNo, origin.lastPageNo)
|
|
|
|
|
}
|
2026-06-14 23:12:03 +03:00
|
|
|
if !reflect.DeepEqual(replay.IndexLevelTails, origin.indexLevelTails) {
|
|
|
|
|
return fmt.Errorf("indexLevelTails: got %#v are not equal expected %#v",
|
|
|
|
|
replay.IndexLevelTails, origin.indexLevelTails)
|
|
|
|
|
}
|
|
|
|
|
if !bytes.Equal(replay.Buf, origin.buffer) {
|
|
|
|
|
return fmt.Errorf("buffer: got % x are not equal expected % x",
|
|
|
|
|
replay.Buf, origin.buffer)
|
|
|
|
|
}
|
|
|
|
|
if replay.TimestampsSize != origin.timestamps.Size() {
|
|
|
|
|
return fmt.Errorf("timestampsSize: got %d are not equal expected %d",
|
|
|
|
|
replay.TimestampsSize, origin.timestamps.Size())
|
|
|
|
|
}
|
|
|
|
|
if replay.ValuesSize != origin.values.Size() {
|
|
|
|
|
return fmt.Errorf("valuesSize: got %d are not equal expected %d",
|
|
|
|
|
replay.ValuesSize, origin.values.Size())
|
|
|
|
|
}
|
2026-06-14 07:57:01 +03:00
|
|
|
return nil
|
|
|
|
|
}
|