package chunker_test import ( "compress/gzip" "io" "os" "path/filepath" "strings" "testing" "workshop.perforce.com/p4lf/internal/chunker" "workshop.perforce.com/p4lf/internal/config" "workshop.perforce.com/p4lf/internal/tailer" ) type nopLogger struct{} func (nopLogger) Infof(string, ...interface{}) {} func (nopLogger) Warnf(string, ...interface{}) {} func (nopLogger) Debugf(string, ...interface{}) {} func (nopLogger) Errorf(string, ...interface{}) {} func testConfig(t *testing.T) config.Config { t.Helper() dir := t.TempDir() cfg := config.Defaults() cfg.LogChunksDir = dir cfg.P4LogFile = "/dev/null" cfg.StateFile = filepath.Join(dir, "p4lf.state") return cfg } func TestWrite_CreatesGzipFile(t *testing.T) { cfg := testConfig(t) c, err := chunker.New(cfg, nopLogger{}) if err != nil { t.Fatalf("New: %v", err) } chunk := &tailer.Chunk{ Data: []byte("line1\nline2\nline3\n"), StartOffset: 0, EndOffset: 18, } ok, err := c.Write(chunk) if err != nil { t.Fatalf("Write: %v", err) } if !ok { t.Fatal("Write returned false (skipped), expected true") } // Filename now includes a date: log..0.18.gz matches, err := filepath.Glob(filepath.Join(cfg.LogChunksDir, "log.*.0.18.gz")) if err != nil { t.Fatalf("Glob error: %v", err) } if len(matches) == 0 { t.Fatalf("expected chunk file log.*.0.18.gz not found in %s", cfg.LogChunksDir) } // Verify the gzip content. f, err := os.Open(matches[0]) if err != nil { t.Fatalf("opening chunk: %v", err) } defer f.Close() gz, err := gzip.NewReader(f) if err != nil { t.Fatalf("gzip.NewReader: %v", err) } content, err := io.ReadAll(gz) if err != nil { t.Fatalf("reading gzip content: %v", err) } if string(content) != string(chunk.Data) { t.Errorf("chunk content: got %q, want %q", content, chunk.Data) } } func TestWrite_MaxLogChunks(t *testing.T) { cfg := testConfig(t) cfg.MaxLogChunks = 2 c, err := chunker.New(cfg, nopLogger{}) if err != nil { t.Fatalf("New: %v", err) } // Write 3 chunks with MaxLogChunks=2; the third write should delete the // oldest file to stay within the limit. chunks := []*tailer.Chunk{ {Data: []byte("aaa"), StartOffset: 0, EndOffset: 3}, {Data: []byte("bbb"), StartOffset: 3, EndOffset: 6}, {Data: []byte("ccc"), StartOffset: 6, EndOffset: 9}, } for i, chunk := range chunks { ok, err := c.Write(chunk) if err != nil { t.Fatalf("Write[%d]: err=%v", i, err) } if !ok { t.Fatalf("Write[%d]: expected write to succeed (delete-oldest, not skip)", i) } } // After 3 writes with MaxLogChunks=2, we should have exactly 2 files. matches, err := filepath.Glob(filepath.Join(cfg.LogChunksDir, "log.*.gz")) if err != nil { t.Fatalf("Glob: %v", err) } if len(matches) != 2 { t.Errorf("expected 2 chunk files after enforcing MaxLogChunks=2, got %d", len(matches)) } } func TestWrite_NoPanicOnEmptyDir(t *testing.T) { cfg := testConfig(t) cfg.MaxLogChunks = 5 c, err := chunker.New(cfg, nopLogger{}) if err != nil { t.Fatalf("New: %v", err) } chunk := &tailer.Chunk{Data: []byte("x"), StartOffset: 100, EndOffset: 101} ok, err := c.Write(chunk) if err != nil { t.Fatalf("Write: %v", err) } if !ok { t.Error("expected write to succeed") } } func TestWrite_ContentIsCorrect(t *testing.T) { cfg := testConfig(t) c, err := chunker.New(cfg, nopLogger{}) if err != nil { t.Fatalf("New: %v", err) } payload := strings.Repeat("Perforce log line\n", 100) chunk := &tailer.Chunk{ Data: []byte(payload), StartOffset: 1000, EndOffset: 1000 + int64(len(payload)), } if _, err := c.Write(chunk); err != nil { t.Fatalf("Write: %v", err) } matches, err := filepath.Glob(filepath.Join(cfg.LogChunksDir, "log.*.gz")) if err != nil || len(matches) == 0 { t.Fatalf("no chunk files found in %s", cfg.LogChunksDir) } f, _ := os.Open(matches[0]) defer f.Close() gz, _ := gzip.NewReader(f) got, _ := io.ReadAll(gz) if string(got) != payload { t.Errorf("content mismatch: got %d bytes, want %d bytes", len(got), len(payload)) } }