package main import ( "os" "path/filepath" "strings" "testing" "git.dvdt.dev/david/ingot/internal/block" "git.dvdt.dev/david/ingot/internal/chunkenc" "git.dvdt.dev/david/ingot/labels" ) func makeChunk(t *testing.T, samples []struct{ t int64; v float64 }) []byte { t.Helper() c := chunkenc.NewXORChunk() a, err := c.Appender() if err != nil { t.Fatalf("unexpected error: %v", err) } for _, s := range samples { a.Append(s.t, s.v) } return append([]byte(nil), c.Bytes()...) } func setupTestData(t *testing.T) string { t.Helper() dir := t.TempDir() samples := []struct{ t int64; v float64 }{ {0, 1.0}, {15000, 2.0}, {30000, 3.0}, } chunk := makeChunk(t, samples) series := []block.SeriesFlush{ { Ref: 1, Labels: labels.FromStrings("__name__", "temp", "room", "office"), Chunks: []block.ChunkData{ {MinT: 0, MaxT: 30000, Data: chunk}, }, }, { Ref: 2, Labels: labels.FromStrings("__name__", "humidity", "room", "office"), Chunks: []block.ChunkData{ {MinT: 0, MaxT: 30000, Data: chunk}, }, }, } _, err := block.Flush(dir, series) if err != nil { t.Fatalf("unexpected error: %v", err) } return dir } // blockDir returns the first block directory inside dataDir. func blockDir(t *testing.T, dataDir string) string { t.Helper() entries, err := os.ReadDir(dataDir) if err != nil { t.Fatalf("unexpected error: %v", err) } for _, e := range entries { if e.IsDir() && e.Name() != "wal" { return filepath.Join(dataDir, e.Name()) } } t.Fatal("no block directory found") return "" } // captureStdout calls fn with stdout redirected and returns the output. func captureStdout(fn func()) string { old := os.Stdout r, w, _ := os.Pipe() os.Stdout = w fn() w.Close() os.Stdout = old out := make([]byte, 16384) n, _ := r.Read(out) return string(out[:n]) } func TestCmdBlocks(t *testing.T) { tests := []struct { name string args []string wantErr string // "" = no error expected wantOutputs []string }{ { name: "list_blocks", args: nil, // replaced in loop with setupTestData result wantErr: "", wantOutputs: []string{"ULID", "1 block(s) total"}, }, { name: "no_blocks", args: nil, // replaced with empty TempDir wantErr: "", wantOutputs: []string{"no blocks found"}, }, { name: "missing_args", args: []string{}, wantErr: "usage", wantOutputs: nil, }, } // Set up args that need dynamic dirs. dataDir := setupTestData(t) emptyDir := t.TempDir() tests[0].args = []string{dataDir} tests[1].args = []string{emptyDir} for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { var output string err := func() error { var cmdErr error output = captureStdout(func() { cmdErr = cmdBlocks(tc.args) }) return cmdErr }() if got := (err != nil); got != (tc.wantErr != "") { t.Errorf("error presence: got %v, want %v", got, tc.wantErr != "") } if !strings.Contains(errString(err), tc.wantErr) { t.Errorf("got %q, want substring %q", errString(err), tc.wantErr) } for _, want := range tc.wantOutputs { if !strings.Contains(output, want) { t.Errorf("got %q, want substring %q", output, want) } } }) } } func TestCmdInspect(t *testing.T) { dataDir := setupTestData(t) bd := blockDir(t, dataDir) tests := []struct { name string args []string wantErr string wantOutputs []string }{ { name: "valid_block", args: []string{bd}, wantErr: "", wantOutputs: []string{"Block Meta", "__name__", "Postings"}, }, { name: "missing_args", args: []string{}, wantErr: "usage", wantOutputs: nil, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { var output string err := func() error { var cmdErr error output = captureStdout(func() { cmdErr = cmdInspect(tc.args) }) return cmdErr }() if got := (err != nil); got != (tc.wantErr != "") { t.Errorf("error presence: got %v, want %v", got, tc.wantErr != "") } if !strings.Contains(errString(err), tc.wantErr) { t.Errorf("got %q, want substring %q", errString(err), tc.wantErr) } for _, want := range tc.wantOutputs { if !strings.Contains(output, want) { t.Errorf("got %q, want substring %q", output, want) } } }) } } func TestCmdChunks(t *testing.T) { dataDir := setupTestData(t) bd := blockDir(t, dataDir) tests := []struct { name string args []string wantErr string wantOutputs []string }{ { name: "valid_ref", args: []string{bd, "1"}, wantErr: "", wantOutputs: []string{"Series 1", "(3 samples)"}, }, { name: "ref_not_found", args: []string{bd, "999"}, wantErr: "not found", wantOutputs: nil, }, { name: "missing_ref_arg", args: []string{bd}, wantErr: "usage", wantOutputs: nil, }, { name: "missing_all_args", args: []string{}, wantErr: "usage", wantOutputs: nil, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { var output string err := func() error { var cmdErr error output = captureStdout(func() { cmdErr = cmdChunks(tc.args) }) return cmdErr }() if got := (err != nil); got != (tc.wantErr != "") { t.Errorf("error presence: got %v, want %v", got, tc.wantErr != "") } if !strings.Contains(errString(err), tc.wantErr) { t.Errorf("got %q, want substring %q", errString(err), tc.wantErr) } for _, want := range tc.wantOutputs { if !strings.Contains(output, want) { t.Errorf("got %q, want substring %q", output, want) } } }) } } func TestCmdFsck(t *testing.T) { dataDir := setupTestData(t) emptyDir := t.TempDir() tests := []struct { name string args []string wantErr string }{ { name: "valid_data", args: []string{dataDir}, wantErr: "", }, { name: "no_blocks", args: []string{emptyDir}, wantErr: "", }, { name: "missing_args", args: []string{}, wantErr: "usage", }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { err := func() error { var cmdErr error captureStdout(func() { cmdErr = cmdFsck(tc.args) }) return cmdErr }() if got := (err != nil); got != (tc.wantErr != "") { t.Errorf("error presence: got %v, want %v", got, tc.wantErr != "") } if !strings.Contains(errString(err), tc.wantErr) { t.Errorf("got %q, want substring %q", errString(err), tc.wantErr) } }) } } // errString returns the error message or "" for nil. func errString(err error) string { if err == nil { return "" } return strings.ToLower(err.Error()) }