gitmote

gitmote / internal / ci / livelogs_test.go  ·  history  ·  branches

3263 bytes · raw

package ci

import (
	"strings"
	"testing"
	"time"
)

func TestLiveLogsAppendAndRead(t *testing.T) {
	l := NewLiveLogs()
	now := time.Unix(0, 0)
	l.Append(7, []byte("hello "), now)
	l.Append(7, []byte("world"), now)

	data, next, done, ok := l.Read(7, 0)
	if !ok || done {
		t.Fatalf("Read = ok %v done %v, want ok=true done=false", ok, done)
	}
	if string(data) != "hello world" || next != 11 {
		t.Fatalf("Read = %q next %d, want %q next 11", data, next, "hello world")
	}
	// A follow-up read from the last offset returns only the new bytes.
	l.Append(7, []byte("!"), now)
	data, next, _, _ = l.Read(7, next)
	if string(data) != "!" || next != 12 {
		t.Fatalf("incremental Read = %q next %d, want %q next 12", data, next, "!")
	}
}

func TestLiveLogsReadUnknownJob(t *testing.T) {
	// The failure path: no buffer for the job → ok=false so the caller falls back
	// to the durable log rather than showing an empty live tail forever.
	data, next, done, ok := NewLiveLogs().Read(99, 3)
	if ok || done || data != nil || next != 3 {
		t.Fatalf("Read unknown = data %q next %d done %v ok %v, want nil/3/false/false", data, next, done, ok)
	}
}

func TestLiveLogsReadClampsOffset(t *testing.T) {
	l := NewLiveLogs()
	l.Append(1, []byte("abc"), time.Unix(0, 0))
	// An over-long offset (stale client) clamps to the end, never panics.
	if data, next, _, ok := l.Read(1, 999); !ok || len(data) != 0 || next != 3 {
		t.Fatalf("clamped Read = %q next %d ok %v, want empty/3/true", data, next, ok)
	}
	// A negative offset clamps to 0.
	if data, _, _, _ := l.Read(1, -5); string(data) != "abc" {
		t.Fatalf("negative-offset Read = %q, want abc", data)
	}
}

func TestLiveLogsFinish(t *testing.T) {
	l := NewLiveLogs()
	now := time.Unix(0, 0)
	l.Append(2, []byte("x"), now)
	if _, _, done, _ := l.Read(2, 0); done {
		t.Fatal("job reported done before Finish")
	}
	l.Finish(2, now)
	if _, _, done, ok := l.Read(2, 0); !ok || !done {
		t.Fatalf("after Finish: ok %v done %v, want both true", ok, done)
	}
	l.Finish(404, now) // unknown job is a no-op, not a panic
}

func TestLiveLogsCapTruncatesOnce(t *testing.T) {
	l := &LiveLogs{jobs: map[int64]*liveLog{}, cap: 8}
	now := time.Unix(0, 0)
	l.Append(1, []byte("0123456"), now) // 7 bytes, under cap 8
	l.Append(1, []byte("789abc"), now)  // crosses the cap
	l.Append(1, []byte("more"), now)    // dropped (already truncated)

	data, _, _, _ := l.Read(1, 0)
	if !strings.HasPrefix(string(data), "01234567") {
		t.Fatalf("data = %q, want the first 8 bytes kept", data)
	}
	if !strings.Contains(string(data), logTruncationMarker) {
		t.Errorf("data = %q, want an explicit truncation marker", data)
	}
	if strings.Contains(string(data), "more") {
		t.Errorf("data = %q, want post-cap appends dropped", data)
	}
}

func TestLiveLogsSweep(t *testing.T) {
	l := NewLiveLogs()
	start := time.Unix(1000, 0)
	l.Append(1, []byte("old"), start)
	l.Append(2, []byte("fresh"), start.Add(9*time.Minute))

	// Sweep at start+10m with a 5m TTL: job 1 (idle 10m) goes, job 2 (idle 1m) stays.
	l.Sweep(start.Add(10*time.Minute), 5*time.Minute)
	if _, _, _, ok := l.Read(1, 0); ok {
		t.Error("job 1 should have been swept (idle past TTL)")
	}
	if _, _, _, ok := l.Read(2, 0); !ok {
		t.Error("job 2 should survive (idle within TTL)")
	}
}