Remove testify dependency

This commit is contained in:
2026-07-04 18:51:08 -04:00
parent 199e555e3d
commit 4a8b1768a1
20 changed files with 1335 additions and 495 deletions
+16 -7
View File
@@ -3,11 +3,10 @@ package wal
import (
"encoding/binary"
"math"
"reflect"
"testing"
"git.dvdt.dev/david/ingot/labels"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSeriesRecord(t *testing.T) {
@@ -140,8 +139,12 @@ func TestSeriesRecord(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
rec, err := DecodeSeriesRecord(tc.data)
assert.Equal(t, tc.want.rec, rec, "record")
assert.Equal(t, tc.want.err, err, "error")
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)
}
})
}
}
@@ -260,10 +263,16 @@ func TestSamplesRecord(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
samples, err := DecodeSamplesRecord(tc.data)
got := samplesToBits(samples)
assert.Equal(t, tc.want.err, err, "error")
require.Equal(t, len(tc.want.samples), len(got), "sample count")
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 {
assert.Equal(t, tc.want.samples[i], got[i], "sample %d", i)
if got[i] != tc.want.samples[i] {
t.Errorf("sample %d: got %v, want %v", i, got[i], tc.want.samples[i])
}
}
})
}
+28 -11
View File
@@ -3,9 +3,8 @@ package wal
import (
"encoding/binary"
"hash/crc32"
"reflect"
"testing"
"github.com/stretchr/testify/assert"
)
func TestRecord(t *testing.T) {
@@ -138,10 +137,18 @@ func TestRecord(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
typ, payload, consumed, err := DecodeRecord(tc.data)
assert.Equal(t, tc.want.typ, typ, "type")
assert.Equal(t, tc.want.payload, payload, "payload")
assert.Equal(t, tc.want.consumed, consumed, "consumed")
assert.Equal(t, tc.want.err, err, "error")
if typ != tc.want.typ {
t.Errorf("type: got %v, want %v", typ, tc.want.typ)
}
if !reflect.DeepEqual(payload, tc.want.payload) {
t.Errorf("payload: got %v, want %v", payload, tc.want.payload)
}
if consumed != tc.want.consumed {
t.Errorf("consumed: got %v, want %v", consumed, tc.want.consumed)
}
if err != tc.want.err {
t.Errorf("error: got %v, want %v", err, tc.want.err)
}
})
}
}
@@ -149,11 +156,17 @@ func TestRecord(t *testing.T) {
func TestEncodeRecordAppendsToExisting(t *testing.T) {
prefix := []byte("existing")
result := EncodeRecord(prefix, RecordSeries, []byte{0x01})
assert.Equal(t, []byte("existing"), result[:8])
if !reflect.DeepEqual([]byte("existing"), result[:8]) {
t.Errorf("prefix: got %v, want %v", result[:8], []byte("existing"))
}
_, payload, _, err := DecodeRecord(result[8:])
assert.Equal(t, []byte{0x01}, payload)
assert.Equal(t, nil, err)
if !reflect.DeepEqual([]byte{0x01}, payload) {
t.Errorf("payload: got %v, want %v", payload, []byte{0x01})
}
if err != nil {
t.Errorf("error: got %v, want nil", err)
}
}
func TestRecordSize(t *testing.T) {
@@ -167,7 +180,9 @@ func TestRecordSize(t *testing.T) {
}
for _, tc := range tests {
assert.Equal(t, tc.want, RecordSize(tc.payloadLen))
if got := RecordSize(tc.payloadLen); got != tc.want {
t.Errorf("RecordSize(%d): got %v, want %v", tc.payloadLen, got, tc.want)
}
}
}
@@ -179,5 +194,7 @@ func TestEncodeCRCCoversHeaderAndPayload(t *testing.T) {
headerAndPayload := rec[:recordHeaderSize+len(payload)]
want := crc32.Checksum(headerAndPayload, castagnoliTable)
got := binary.BigEndian.Uint32(rec[recordHeaderSize+len(payload):])
assert.Equal(t, want, got)
if got != want {
t.Errorf("CRC: got %v, want %v", got, want)
}
}
+186 -69
View File
@@ -3,25 +3,28 @@ package wal
import (
"os"
"path/filepath"
"reflect"
"testing"
"git.dvdt.dev/david/ingot/labels"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// collectRecords replays all records from a WAL directory.
func collectRecords(t *testing.T, dir string) []Record {
t.Helper()
r, err := NewReader(dir)
require.NoError(t, err)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
defer r.Close()
var recs []Record
for r.Next() {
recs = append(recs, r.Record())
}
require.NoError(t, r.Err())
if r.Err() != nil {
t.Fatalf("unexpected error: %v", r.Err())
}
return recs
}
@@ -36,15 +39,23 @@ func TestWAL(t *testing.T) {
name: "write_and_replay",
setup: func(t *testing.T, dir string) {
w, err := Open(dir, Options{})
require.NoError(t, err)
require.NoError(t, w.LogSeries([]SeriesRecord{
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := w.LogSeries([]SeriesRecord{
{Ref: 1, Labels: []labels.Label{{Name: "__name__", Value: "temp"}, {Name: "room", Value: "office"}}},
}))
require.NoError(t, w.LogSamples([]RefSample{
}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := w.LogSamples([]RefSample{
{Ref: 1, T: 1000, V: 71.3},
{Ref: 1, T: 1015, V: 71.4},
}))
require.NoError(t, w.Close())
}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := w.Close(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
},
wantRecords: 2,
wantMinSegs: 1,
@@ -53,8 +64,12 @@ func TestWAL(t *testing.T) {
name: "empty",
setup: func(t *testing.T, dir string) {
w, err := Open(dir, Options{})
require.NoError(t, err)
require.NoError(t, w.Close())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := w.Close(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
},
wantRecords: 0,
wantMinSegs: 1,
@@ -63,14 +78,26 @@ func TestWAL(t *testing.T) {
name: "reopen_and_append",
setup: func(t *testing.T, dir string) {
w, err := Open(dir, Options{})
require.NoError(t, err)
require.NoError(t, w.LogSamples([]RefSample{{Ref: 1, T: 1000, V: 1.0}}))
require.NoError(t, w.Close())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := w.LogSamples([]RefSample{{Ref: 1, T: 1000, V: 1.0}}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := w.Close(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
w, err = Open(dir, Options{})
require.NoError(t, err)
require.NoError(t, w.LogSamples([]RefSample{{Ref: 2, T: 2000, V: 2.0}}))
require.NoError(t, w.Close())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := w.LogSamples([]RefSample{{Ref: 2, T: 2000, V: 2.0}}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := w.Close(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
},
wantRecords: 2,
wantMinSegs: 1,
@@ -79,11 +106,17 @@ func TestWAL(t *testing.T) {
name: "segment_rotation",
setup: func(t *testing.T, dir string) {
w, err := Open(dir, Options{SegmentMaxSize: 50})
require.NoError(t, err)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
for i := 0; i < 10; i++ {
require.NoError(t, w.LogSamples([]RefSample{{Ref: uint64(i), T: int64(i * 1000), V: float64(i)}}))
if err := w.LogSamples([]RefSample{{Ref: uint64(i), T: int64(i * 1000), V: float64(i)}}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
if err := w.Close(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
require.NoError(t, w.Close())
},
wantRecords: 10,
wantMinSegs: 2,
@@ -92,13 +125,21 @@ func TestWAL(t *testing.T) {
name: "truncate_old_segments",
setup: func(t *testing.T, dir string) {
w, err := Open(dir, Options{SegmentMaxSize: 50})
require.NoError(t, err)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
for i := 0; i < 10; i++ {
require.NoError(t, w.LogSamples([]RefSample{{Ref: uint64(i), T: int64(i), V: float64(i)}}))
if err := w.LogSamples([]RefSample{{Ref: uint64(i), T: int64(i), V: float64(i)}}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
lastSeg := w.LastSegment()
require.NoError(t, w.Truncate(lastSeg))
require.NoError(t, w.Close())
if err := w.Truncate(lastSeg); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := w.Close(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
},
wantRecords: 1, // only the last segment's record(s) survive
wantMinSegs: 1,
@@ -107,25 +148,42 @@ func TestWAL(t *testing.T) {
name: "recovery_truncates_trailing_garbage",
setup: func(t *testing.T, dir string) {
w, err := Open(dir, Options{})
require.NoError(t, err)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
for i := 0; i < 3; i++ {
require.NoError(t, w.LogSamples([]RefSample{{Ref: uint64(i), T: int64(i), V: float64(i)}}))
if err := w.LogSamples([]RefSample{{Ref: uint64(i), T: int64(i), V: float64(i)}}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
if err := w.Close(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
require.NoError(t, w.Close())
// Append garbage after valid records.
segs, err := listSegments(dir)
require.NoError(t, err)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
f, err := os.OpenFile(segmentPath(dir, segs[0]), os.O_WRONLY|os.O_APPEND, 0644)
require.NoError(t, err)
_, err = f.Write([]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF})
require.NoError(t, err)
require.NoError(t, f.Close())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if _, err := f.Write([]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := f.Close(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
// Reopen triggers recovery.
w, err = Open(dir, Options{})
require.NoError(t, err)
require.NoError(t, w.Close())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := w.Close(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
},
wantRecords: 3,
wantMinSegs: 1,
@@ -134,25 +192,42 @@ func TestWAL(t *testing.T) {
name: "recovery_truncates_corrupt_mid_record",
setup: func(t *testing.T, dir string) {
w, err := Open(dir, Options{})
require.NoError(t, err)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
for i := 0; i < 3; i++ {
require.NoError(t, w.LogSamples([]RefSample{{Ref: uint64(i), T: int64(i), V: float64(i)}}))
if err := w.LogSamples([]RefSample{{Ref: uint64(i), T: int64(i), V: float64(i)}}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
if err := w.Close(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
require.NoError(t, w.Close())
// Write a valid header but truncated payload (looks like a torn write).
segs, err := listSegments(dir)
require.NoError(t, err)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
f, err := os.OpenFile(segmentPath(dir, segs[0]), os.O_WRONLY|os.O_APPEND, 0644)
require.NoError(t, err)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// type=1, len=100 (big), but no payload follows.
_, err = f.Write([]byte{0x01, 0x00, 0x00, 0x00, 0x64})
require.NoError(t, err)
require.NoError(t, f.Close())
if _, err := f.Write([]byte{0x01, 0x00, 0x00, 0x00, 0x64}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := f.Close(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
w, err = Open(dir, Options{})
require.NoError(t, err)
require.NoError(t, w.Close())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := w.Close(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
},
wantRecords: 3,
wantMinSegs: 1,
@@ -165,11 +240,17 @@ func TestWAL(t *testing.T) {
tc.setup(t, dir)
recs := collectRecords(t, dir)
assert.Equal(t, tc.wantRecords, len(recs), "record count")
if len(recs) != tc.wantRecords {
t.Errorf("record count: got %v, want %v", len(recs), tc.wantRecords)
}
segs, err := listSegments(dir)
require.NoError(t, err)
assert.GreaterOrEqual(t, len(segs), tc.wantMinSegs, "segment count")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !(len(segs) >= tc.wantMinSegs) {
t.Errorf("segment count: got %v, want >= %v", len(segs), tc.wantMinSegs)
}
})
}
}
@@ -187,17 +268,25 @@ func TestTornWriteRecovery(t *testing.T) {
name: "single_segment_mixed_records",
opts: Options{},
recs: func(t *testing.T, w *WAL) {
require.NoError(t, w.LogSeries([]SeriesRecord{
if err := w.LogSeries([]SeriesRecord{
{Ref: 1, Labels: []labels.Label{{Name: "__name__", Value: "temp"}}},
}))
require.NoError(t, w.LogSamples([]RefSample{{Ref: 1, T: 1000, V: 71.3}}))
require.NoError(t, w.LogSeries([]SeriesRecord{
}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := w.LogSamples([]RefSample{{Ref: 1, T: 1000, V: 71.3}}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := w.LogSeries([]SeriesRecord{
{Ref: 2, Labels: []labels.Label{{Name: "__name__", Value: "humidity"}, {Name: "room", Value: "lab"}}},
}))
require.NoError(t, w.LogSamples([]RefSample{
}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := w.LogSamples([]RefSample{
{Ref: 1, T: 1015, V: 71.4},
{Ref: 2, T: 1000, V: 55.0},
}))
}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
},
},
{
@@ -205,7 +294,9 @@ func TestTornWriteRecovery(t *testing.T) {
opts: Options{SegmentMaxSize: 50},
recs: func(t *testing.T, w *WAL) {
for i := 0; i < 10; i++ {
require.NoError(t, w.LogSamples([]RefSample{{Ref: uint64(i), T: int64(i * 1000), V: float64(i)}}))
if err := w.LogSamples([]RefSample{{Ref: uint64(i), T: int64(i * 1000), V: float64(i)}}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
},
},
@@ -216,21 +307,31 @@ func TestTornWriteRecovery(t *testing.T) {
// Write the reference WAL.
srcDir := filepath.Join(t.TempDir(), "src")
w, err := Open(srcDir, tc.opts)
require.NoError(t, err)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
tc.recs(t, w)
require.NoError(t, w.Close())
if err := w.Close(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
origRecs := collectRecords(t, srcDir)
require.Greater(t, len(origRecs), 0)
if !(len(origRecs) > 0) {
t.Fatalf("got %v records, want > 0", len(origRecs))
}
segs, err := listSegments(srcDir)
require.NoError(t, err)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// Read all segment data.
segData := make(map[int][]byte)
for _, idx := range segs {
data, err := os.ReadFile(segmentPath(srcDir, idx))
require.NoError(t, err)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
segData[idx] = data
}
@@ -240,25 +341,41 @@ func TestTornWriteRecovery(t *testing.T) {
for cutoff := 0; cutoff <= len(lastData); cutoff++ {
walDir := filepath.Join(t.TempDir(), "wal")
require.NoError(t, os.MkdirAll(walDir, 0755))
if err := os.MkdirAll(walDir, 0755); err != nil {
t.Fatalf("cutoff=%d: unexpected error: %v", cutoff, err)
}
// Copy earlier segments intact.
for _, idx := range segs[:len(segs)-1] {
require.NoError(t, os.WriteFile(segmentPath(walDir, idx), segData[idx], 0644))
if err := os.WriteFile(segmentPath(walDir, idx), segData[idx], 0644); err != nil {
t.Fatalf("cutoff=%d: unexpected error: %v", cutoff, err)
}
}
// Write truncated last segment.
require.NoError(t, os.WriteFile(segmentPath(walDir, lastSeg), lastData[:cutoff], 0644))
if err := os.WriteFile(segmentPath(walDir, lastSeg), lastData[:cutoff], 0644); err != nil {
t.Fatalf("cutoff=%d: unexpected error: %v", cutoff, err)
}
w2, err := Open(walDir, tc.opts)
require.NoError(t, err, "cutoff=%d", cutoff)
if err != nil {
t.Fatalf("cutoff=%d: unexpected error: %v", cutoff, err)
}
recovered := collectRecords(t, walDir)
require.NoError(t, w2.Close(), "cutoff=%d", cutoff)
if err := w2.Close(); err != nil {
t.Fatalf("cutoff=%d: unexpected error: %v", cutoff, err)
}
// Must be a valid prefix.
assert.LessOrEqual(t, len(recovered), len(origRecs), "cutoff=%d count", cutoff)
if !(len(recovered) <= len(origRecs)) {
t.Errorf("cutoff=%d: got %d records, want <= %d", cutoff, len(recovered), len(origRecs))
}
for i, rec := range recovered {
assert.Equal(t, origRecs[i].Type, rec.Type, "cutoff=%d rec=%d type", cutoff, i)
assert.Equal(t, origRecs[i].Data, rec.Data, "cutoff=%d rec=%d data", cutoff, i)
if origRecs[i].Type != rec.Type {
t.Errorf("cutoff=%d rec=%d type: got %v, want %v", cutoff, i, rec.Type, origRecs[i].Type)
}
if !reflect.DeepEqual(origRecs[i].Data, rec.Data) {
t.Errorf("cutoff=%d rec=%d data: got %v, want %v", cutoff, i, rec.Data, origRecs[i].Data)
}
}
}
})