Files
ingot/internal/wal/codec_test.go
T
2026-07-04 18:51:08 -04:00

280 lines
6.7 KiB
Go

package wal
import (
"encoding/binary"
"math"
"reflect"
"testing"
"git.dvdt.dev/david/ingot/labels"
)
func TestSeriesRecord(t *testing.T) {
type result struct {
rec SeriesRecord
err error
}
tests := []struct {
name string
data []byte
want result
}{
{
name: "single_label",
data: EncodeSeriesRecord(nil, SeriesRecord{
Ref: 42,
Labels: []labels.Label{{Name: "__name__", Value: "temp"}},
}),
want: result{SeriesRecord{
Ref: 42,
Labels: []labels.Label{{Name: "__name__", Value: "temp"}},
}, nil},
},
{
name: "multiple_labels",
data: EncodeSeriesRecord(nil, SeriesRecord{
Ref: 1,
Labels: []labels.Label{
{Name: "__name__", Value: "cpu_usage"},
{Name: "host", Value: "web-01"},
{Name: "region", Value: "us-east"},
},
}),
want: result{SeriesRecord{
Ref: 1,
Labels: []labels.Label{
{Name: "__name__", Value: "cpu_usage"},
{Name: "host", Value: "web-01"},
{Name: "region", Value: "us-east"},
},
}, nil},
},
{
name: "zero_labels",
data: EncodeSeriesRecord(nil, SeriesRecord{Ref: 99, Labels: nil}),
want: result{SeriesRecord{Ref: 99, Labels: []labels.Label{}}, nil},
},
{
name: "unicode_labels",
data: EncodeSeriesRecord(nil, SeriesRecord{
Ref: 7,
Labels: []labels.Label{{Name: "名前", Value: "温度"}},
}),
want: result{SeriesRecord{
Ref: 7,
Labels: []labels.Label{{Name: "名前", Value: "温度"}},
}, nil},
},
{
name: "empty_label_strings",
data: EncodeSeriesRecord(nil, SeriesRecord{
Ref: 1,
Labels: []labels.Label{{Name: "", Value: ""}},
}),
want: result{SeriesRecord{
Ref: 1,
Labels: []labels.Label{{Name: "", Value: ""}},
}, nil},
},
// Error cases.
{
name: "nil",
data: nil,
want: result{SeriesRecord{}, ErrShortPayload},
},
{
name: "truncated_ref",
data: make([]byte, 6),
want: result{SeriesRecord{}, ErrShortPayload},
},
{
name: "truncated_nlabels",
data: make([]byte, 10),
want: result{SeriesRecord{}, ErrShortPayload},
},
{
name: "truncated_name_len",
data: func() []byte {
d := make([]byte, 13) // ref(8) + nlabels=1(4) + 1 byte (short)
binary.BigEndian.PutUint32(d[8:], 1)
return d
}(),
want: result{SeriesRecord{}, ErrShortPayload},
},
{
name: "truncated_name_data",
data: func() []byte {
d := make([]byte, 16) // ref(8) + nlabels=1(4) + namelen=10(2) + 2 bytes
binary.BigEndian.PutUint32(d[8:], 1)
binary.BigEndian.PutUint16(d[12:], 10) // claims 10 bytes, only 2 available
return d
}(),
want: result{SeriesRecord{}, ErrShortPayload},
},
{
name: "truncated_value_len",
data: func() []byte {
d := make([]byte, 15) // ref(8) + nlabels=1(4) + namelen=0(2) + 1 byte
binary.BigEndian.PutUint32(d[8:], 1)
binary.BigEndian.PutUint16(d[12:], 0) // 0-length name
return d
}(),
want: result{SeriesRecord{}, ErrShortPayload},
},
{
name: "truncated_value_data",
data: func() []byte {
d := make([]byte, 18) // ref(8) + nlabels=1(4) + namelen=0(2) + vallen=5(2) + 2 bytes
binary.BigEndian.PutUint32(d[8:], 1)
binary.BigEndian.PutUint16(d[12:], 0)
binary.BigEndian.PutUint16(d[14:], 5) // claims 5 bytes, only 2 available
return d
}(),
want: result{SeriesRecord{}, ErrShortPayload},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
rec, err := DecodeSeriesRecord(tc.data)
if !reflect.DeepEqual(rec, tc.want.rec) {
t.Errorf("record: got %v, want %v", rec, tc.want.rec)
}
if err != tc.want.err {
t.Errorf("error: got %v, want %v", err, tc.want.err)
}
})
}
}
// refSampleBits holds a RefSample with the value stored as raw bits
// so NaN and negative zero compare correctly.
type refSampleBits struct {
Ref uint64
T int64
VBits uint64
}
func toBits(s RefSample) refSampleBits {
return refSampleBits{s.Ref, s.T, math.Float64bits(s.V)}
}
func samplesToBits(ss []RefSample) []refSampleBits {
out := make([]refSampleBits, len(ss))
for i, s := range ss {
out[i] = toBits(s)
}
return out
}
func TestSamplesRecord(t *testing.T) {
type result struct {
samples []refSampleBits
err error
}
tests := []struct {
name string
data []byte
want result
}{
{
name: "single_sample",
data: EncodeSamplesRecord(nil, []RefSample{
{Ref: 1, T: 1000, V: 71.3},
}),
want: result{samplesToBits([]RefSample{{Ref: 1, T: 1000, V: 71.3}}), nil},
},
{
name: "multiple_samples",
data: EncodeSamplesRecord(nil, []RefSample{
{Ref: 1, T: 1000, V: 71.3},
{Ref: 1, T: 1015, V: 71.4},
{Ref: 2, T: 1000, V: 0},
}),
want: result{samplesToBits([]RefSample{
{Ref: 1, T: 1000, V: 71.3},
{Ref: 1, T: 1015, V: 71.4},
{Ref: 2, T: 1000, V: 0},
}), nil},
},
{
name: "zero_samples",
data: EncodeSamplesRecord(nil, nil),
want: result{samplesToBits([]RefSample{}), nil},
},
{
name: "special_float_values",
data: EncodeSamplesRecord(nil, []RefSample{
{Ref: 1, T: 0, V: math.NaN()},
{Ref: 2, T: 0, V: math.Inf(1)},
{Ref: 3, T: 0, V: math.Inf(-1)},
{Ref: 4, T: 0, V: math.Copysign(0, -1)},
}),
want: result{samplesToBits([]RefSample{
{Ref: 1, T: 0, V: math.NaN()},
{Ref: 2, T: 0, V: math.Inf(1)},
{Ref: 3, T: 0, V: math.Inf(-1)},
{Ref: 4, T: 0, V: math.Copysign(0, -1)},
}), nil},
},
{
name: "negative_timestamp",
data: EncodeSamplesRecord(nil, []RefSample{
{Ref: 1, T: -5000, V: 1.5},
}),
want: result{samplesToBits([]RefSample{{Ref: 1, T: -5000, V: 1.5}}), nil},
},
// Error cases.
{
name: "nil",
data: nil,
want: result{nil, ErrShortPayload},
},
{
name: "truncated_count",
data: []byte{0, 0},
want: result{nil, ErrShortPayload},
},
{
name: "truncated_mid_sample",
data: func() []byte {
d := make([]byte, 20) // nsamples=1(4) + 16 bytes (need 24)
binary.BigEndian.PutUint32(d, 1)
return d
}(),
want: result{nil, ErrShortPayload},
},
{
name: "count_exceeds_data",
data: func() []byte {
d := make([]byte, 28) // nsamples=2(4) + 24 bytes (only 1 sample)
binary.BigEndian.PutUint32(d, 2)
return d
}(),
want: result{nil, ErrShortPayload},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
samples, err := DecodeSamplesRecord(tc.data)
got := samplesToBits(samples)
if err != tc.want.err {
t.Errorf("error: got %v, want %v", err, tc.want.err)
}
if len(got) != len(tc.want.samples) {
t.Fatalf("sample count: got %d, want %d", len(got), len(tc.want.samples))
}
for i := range tc.want.samples {
if got[i] != tc.want.samples[i] {
t.Errorf("sample %d: got %v, want %v", i, got[i], tc.want.samples[i])
}
}
})
}
}