Luigit
repositories / bugabinga.net

bugabinga.net

personal infrastructure for bugabinga!

owned by admin

services/luci/internal/history/history_test.go

Raw
package history

import (
	"errors"
	"io"
	"os"
	"strings"
	"testing"
	"time"

	"github.com/klauspost/compress/zstd"
)

func TestAppendBoundsUntrustedEventText(t *testing.T) {
	store := Store{DataDir: t.TempDir()}
	matrix := map[string]string{}
	for i := range 64 {
		matrix[string(rune('A'+i%26))+strings.Repeat("k", 1100)] = strings.Repeat("v", 5000)
	}
	if _, err := store.Append(Event{RunID: "large", ChildID: "run", Repo: "repo", Job: strings.Repeat("j", 70000), Status: "failed", Detail: strings.Repeat("x", 70000), Matrix: matrix}); err != nil {
		t.Fatal(err)
	}
	events, err := ReadAll(store.DataDir)
	if err != nil || len(events) != 1 || len(events[0].Job) > 4100 || len(events[0].Detail) > 8200 {
		t.Fatalf("events=%d err=%v", len(events), err)
	}
}

func TestReadAllEmptyAndInvalidFiles(t *testing.T) {
	dir := t.TempDir()
	events, err := ReadAll(dir)
	if err != nil {
		t.Fatalf("ReadAll empty error = %v", err)
	}
	if len(events) != 0 {
		t.Fatalf("empty events = %#v", events)
	}
	if err := os.MkdirAll(dir+"/events", 0o755); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(dir+"/events/bad.jsonl.zst", []byte("not zstd"), 0o644); err != nil {
		t.Fatal(err)
	}
	if _, err := ReadAll(dir); err == nil {
		t.Fatal("invalid zstd accepted")
	}
}

func TestReadAllRejectsBrokenInputs(t *testing.T) {
	dir := t.TempDir()
	if err := os.MkdirAll(dir+"/events", 0o755); err != nil {
		t.Fatal(err)
	}
	if err := os.Symlink("missing", dir+"/events/missing.jsonl.zst"); err != nil {
		t.Fatal(err)
	}
	if _, err := ReadAll(dir); err == nil {
		t.Fatal("broken symlink accepted")
	}
	if err := os.Remove(dir + "/events/missing.jsonl.zst"); err != nil {
		t.Fatal(err)
	}
	writeZstd(t, dir+"/events/bad-json.jsonl.zst", []byte("{\n"))
	if _, err := ReadAll(dir); err == nil {
		t.Fatal("bad JSON accepted")
	}
	if err := os.Remove(dir + "/events/bad-json.jsonl.zst"); err != nil {
		t.Fatal(err)
	}
	writeZstd(t, dir+"/events/huge.jsonl.zst", []byte(strings.Repeat("x", 70*1024)))
	if _, err := ReadAll(dir); err == nil {
		t.Fatal("huge scanner token accepted")
	}
}

func writeZstd(t *testing.T, path string, data []byte) {
	t.Helper()
	file, err := os.Create(path)
	if err != nil {
		t.Fatal(err)
	}
	encoder, err := zstd.NewWriter(file)
	if err != nil {
		t.Fatal(err)
	}
	if _, err := encoder.Write(data); err != nil {
		t.Fatal(err)
	}
	if err := encoder.Close(); err != nil {
		t.Fatal(err)
	}
	if err := file.Close(); err != nil {
		t.Fatal(err)
	}
}

func TestAppendRejectsBadDataDir(t *testing.T) {
	root := t.TempDir()
	bad := root + "/file"
	if err := os.WriteFile(bad, []byte("x"), 0o644); err != nil {
		t.Fatal(err)
	}
	_, err := Store{DataDir: bad, Now: func() time.Time { return time.Date(2026, 6, 18, 1, 2, 3, 0, time.UTC) }}.Append(Event{RunID: "run"})
	if err == nil {
		t.Fatal("append under file path accepted")
	}
}

func TestAppendRejectsExistingDirectoryLogPath(t *testing.T) {
	store := Store{DataDir: t.TempDir(), Now: func() time.Time { return time.Date(2026, 6, 18, 1, 2, 3, 0, time.UTC) }}
	if err := os.MkdirAll(store.DataDir+"/events/2026-06.jsonl.zst", 0o755); err != nil {
		t.Fatal(err)
	}
	if _, err := store.Append(Event{RunID: "run"}); err == nil {
		t.Fatal("append accepted directory log path")
	}
}

func TestReadAllInjectedReaderFailure(t *testing.T) {
	dir := t.TempDir()
	if err := os.MkdirAll(dir+"/events", 0o755); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(dir+"/events/x.jsonl.zst", []byte("x"), 0o644); err != nil {
		t.Fatal(err)
	}
	old := newReader
	newReader = func(io.Reader) (zstdReader, error) { return nil, errors.New("reader boom") }
	defer func() { newReader = old }()
	if _, err := ReadAll(dir); err == nil {
		t.Fatal("reader failure hidden")
	}
}

func TestAppendInjectedFailures(t *testing.T) {
	store := Store{DataDir: t.TempDir(), Now: func() time.Time { return time.Date(2026, 6, 18, 1, 2, 3, 0, time.UTC) }}
	boom := errors.New("boom")

	oldWriter := newWriter
	newWriter = func(io.Writer) (zstdWriter, error) { return nil, boom }
	if _, err := store.Append(Event{RunID: "run"}); !errors.Is(err, boom) {
		t.Fatalf("writer err = %v", err)
	}
	newWriter = oldWriter

	oldMarshal := marshal
	marshal = func(any) ([]byte, error) { return nil, boom }
	if _, err := store.Append(Event{RunID: "run"}); !errors.Is(err, boom) {
		t.Fatalf("marshal err = %v", err)
	}
	marshal = oldMarshal

	oldWriter = newWriter
	newWriter = func(io.Writer) (zstdWriter, error) { return failWriter{writeErr: boom}, nil }
	if _, err := store.Append(Event{RunID: "run"}); !errors.Is(err, boom) {
		t.Fatalf("write err = %v", err)
	}
	newWriter = oldWriter

	newWriter = func(io.Writer) (zstdWriter, error) { return failWriter{closeErr: boom}, nil }
	if _, err := store.Append(Event{RunID: "run"}); !errors.Is(err, boom) {
		t.Fatalf("close err = %v", err)
	}
	newWriter = oldWriter

	oldOpen := openAppend
	openAppend = func(string) (appendFile, error) { return failFile{closeErr: boom}, nil }
	if _, err := store.Append(Event{RunID: "run"}); !errors.Is(err, boom) {
		t.Fatalf("file close err = %v", err)
	}
	openAppend = oldOpen
}

type failWriter struct{ writeErr, closeErr error }

func (w failWriter) Write([]byte) (int, error) { return 0, w.writeErr }
func (w failWriter) Close() error              { return w.closeErr }

type failFile struct{ closeErr error }

type partialFile struct {
	*os.File
	err error
}

func (f partialFile) Write(p []byte) (int, error) {
	if len(p) == 0 {
		return 0, f.err
	}
	n, _ := f.File.Write(p[:max(1, len(p)/2)])
	return n, f.err
}

func (f failFile) Write(p []byte) (int, error) { return len(p), nil }
func (f failFile) Close() error                { return f.closeErr }

func TestFailedAppendLeavesPreviousHistoryReadable(t *testing.T) {
	store := Store{DataDir: t.TempDir(), Now: func() time.Time { return time.Date(2026, 6, 18, 1, 2, 3, 0, time.UTC) }}
	if _, err := store.Append(Event{RunID: "first", ChildID: "run", Repo: "repo", Job: "check", Status: "success"}); err != nil {
		t.Fatal(err)
	}
	boom := errors.New("disk full")
	oldOpen := openAppend
	t.Cleanup(func() { openAppend = oldOpen })
	openAppend = func(path string) (appendFile, error) {
		file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
		if err != nil {
			return nil, err
		}
		return partialFile{File: file, err: boom}, nil
	}
	if _, err := store.Append(Event{RunID: "second", ChildID: "run", Repo: "repo", Job: "check", Status: "failed"}); !errors.Is(err, boom) {
		t.Fatalf("append err=%v", err)
	}
	openAppend = oldOpen
	events, err := ReadAll(store.DataDir)
	if err != nil || len(events) != 1 || events[0].RunID != "first" {
		t.Fatalf("events=%#v err=%v", events, err)
	}
}

func TestAppendAndReadAllCompressedJSONL(t *testing.T) {
	store := Store{DataDir: t.TempDir(), Now: func() time.Time { return time.Date(2026, 6, 18, 1, 2, 3, 0, time.UTC) }}
	path, err := store.Append(Event{RunID: "run1", Repo: "repo", Job: "check", Rev: "abc", Status: "success"})
	if err != nil {
		t.Fatalf("Append() error = %v", err)
	}
	if _, err := store.Append(Event{RunID: "run2", Repo: "repo", Job: "test", Rev: "def", Status: "failed"}); err != nil {
		t.Fatalf("Append() second error = %v", err)
	}
	file, err := os.Open(path)
	if err != nil {
		t.Fatal(err)
	}
	decoder, err := zstd.NewReader(file)
	if err != nil {
		t.Fatal(err)
	}
	data, err := io.ReadAll(decoder)
	decoder.Close()
	file.Close()
	if err != nil {
		t.Fatal(err)
	}
	text := string(data)
	if !strings.Contains(text, `"run_id":"run1"`) || !strings.Contains(text, `"status":"success"`) || !strings.HasSuffix(text, "\n") {
		t.Fatalf("jsonl = %q", text)
	}
	events, err := ReadAll(store.DataDir)
	if err != nil {
		t.Fatalf("ReadAll() error = %v", err)
	}
	if len(events) != 2 || events[0].RunID != "run1" || events[1].RunID != "run2" {
		t.Fatalf("events = %#v", events)
	}
}

func TestAppendOnceUsesRunAndChildIdentity(t *testing.T) {
	now := time.Date(2026, 8, 5, 1, 2, 3, 4, time.UTC)
	store := Store{DataDir: t.TempDir(), Now: func() time.Time { return now }}
	event := Event{RunID: "42", ChildID: "run", Repo: "smith", Job: "push", Rev: "abc", Ref: "refs/heads/trunk", Trigger: "push", Status: "success", StartedAt: now.Add(-time.Second)}
	appended, err := store.AppendOnce(event)
	if err != nil || !appended {
		t.Fatalf("first append=%v err=%v", appended, err)
	}
	appended, err = store.AppendOnce(event)
	if err != nil || appended {
		t.Fatalf("second append=%v err=%v", appended, err)
	}
	events, err := ReadAll(store.DataDir)
	if err != nil || len(events) != 1 || events[0].ChildID != "run" || events[0].Ref != "refs/heads/trunk" || events[0].Trigger != "push" || !events[0].StartedAt.Equal(now.Add(-time.Second)) {
		t.Fatalf("events=%#v err=%v", events, err)
	}
	found, err := store.Contains("42", "run")
	if err != nil || !found {
		t.Fatalf("found=%v err=%v", found, err)
	}
	found, err = store.Contains("42", "001")
	if err != nil || found {
		t.Fatalf("absent=%v err=%v", found, err)
	}
}

func TestLegacyEventRemainsReadable(t *testing.T) {
	dir := t.TempDir()
	if err := os.MkdirAll(dir+"/events", 0o755); err != nil {
		t.Fatal(err)
	}
	writeZstd(t, dir+"/events/legacy.jsonl.zst", []byte(`{"run_id":"old","repo":"repo","job":"check","rev":"abc","status":"success","time":"2026-08-05T00:00:00Z"}`+"\n"))
	events, err := ReadAll(dir)
	if err != nil || len(events) != 1 {
		t.Fatalf("events=%#v err=%v", events, err)
	}
	got := events[0]
	if got.ChildID != "" || got.Ref != "" || got.Trigger != "" || !got.StartedAt.IsZero() {
		t.Fatalf("legacy fields=%#v", got)
	}
}

func TestReadAllRejectsEventsFile(t *testing.T) {
	dir := t.TempDir()
	if err := os.WriteFile(dir+"/events", []byte("x"), 0o644); err != nil {
		t.Fatal(err)
	}
	if _, err := ReadAll(dir); err == nil {
		t.Fatal("events file accepted as empty history")
	}
}