Log each line of build failure output as a separate structured log entry at WARN level, making output readable and queryable in Loki/Grafana. Add repo and rev fields to all build-related log entries. Add truncateOutputLines helper that returns a []string for per-line logging. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
165 lines
4.0 KiB
Go
165 lines
4.0 KiB
Go
package builder
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestTruncateOutput(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
keepLines int
|
|
wantLines int
|
|
wantOmit bool
|
|
}{
|
|
{
|
|
name: "short output unchanged",
|
|
input: "line1\nline2\nline3",
|
|
keepLines: 50,
|
|
wantLines: 3,
|
|
wantOmit: false,
|
|
},
|
|
{
|
|
name: "exactly at threshold unchanged",
|
|
input: strings.Join(makeLines(100), "\n"),
|
|
keepLines: 50,
|
|
wantLines: 100,
|
|
wantOmit: false,
|
|
},
|
|
{
|
|
name: "over threshold truncated",
|
|
input: strings.Join(makeLines(150), "\n"),
|
|
keepLines: 50,
|
|
wantLines: 103, // 50 + 1 (empty) + 1 (omitted msg) + 1 (empty) + 50
|
|
wantOmit: true,
|
|
},
|
|
{
|
|
name: "large output truncated",
|
|
input: strings.Join(makeLines(1000), "\n"),
|
|
keepLines: 50,
|
|
wantLines: 103,
|
|
wantOmit: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := truncateOutput(tt.input, tt.keepLines)
|
|
gotLines := strings.Split(got, "\n")
|
|
|
|
if len(gotLines) != tt.wantLines {
|
|
t.Errorf("got %d lines, want %d", len(gotLines), tt.wantLines)
|
|
}
|
|
|
|
hasOmit := strings.Contains(got, "lines omitted")
|
|
if hasOmit != tt.wantOmit {
|
|
t.Errorf("got omit marker = %v, want %v", hasOmit, tt.wantOmit)
|
|
}
|
|
|
|
if tt.wantOmit {
|
|
// Verify first and last lines are preserved
|
|
inputLines := strings.Split(tt.input, "\n")
|
|
firstLine := inputLines[0]
|
|
lastLine := inputLines[len(inputLines)-1]
|
|
if !strings.HasPrefix(got, firstLine+"\n") {
|
|
t.Errorf("first line not preserved, got prefix %q, want %q",
|
|
gotLines[0], firstLine)
|
|
}
|
|
if !strings.HasSuffix(got, lastLine) {
|
|
t.Errorf("last line not preserved, got suffix %q, want %q",
|
|
gotLines[len(gotLines)-1], lastLine)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func makeLines(n int) []string {
|
|
lines := make([]string, n)
|
|
for i := range lines {
|
|
lines[i] = "line " + strings.Repeat("x", i%80)
|
|
}
|
|
return lines
|
|
}
|
|
|
|
func TestTruncateOutputLines(t *testing.T) {
|
|
t.Run("short output returns all lines", func(t *testing.T) {
|
|
input := "line1\nline2\nline3"
|
|
got := truncateOutputLines(input, 50)
|
|
if len(got) != 3 {
|
|
t.Errorf("got %d lines, want 3", len(got))
|
|
}
|
|
if got[0] != "line1" || got[1] != "line2" || got[2] != "line3" {
|
|
t.Errorf("unexpected lines: %v", got)
|
|
}
|
|
})
|
|
|
|
t.Run("over threshold returns head + marker + tail", func(t *testing.T) {
|
|
lines := makeLines(200)
|
|
input := strings.Join(lines, "\n")
|
|
got := truncateOutputLines(input, 50)
|
|
|
|
// Should be 50 head + 1 marker + 50 tail = 101
|
|
if len(got) != 101 {
|
|
t.Errorf("got %d lines, want 101", len(got))
|
|
}
|
|
|
|
// Check first and last lines preserved
|
|
if got[0] != lines[0] {
|
|
t.Errorf("first line = %q, want %q", got[0], lines[0])
|
|
}
|
|
if got[len(got)-1] != lines[len(lines)-1] {
|
|
t.Errorf("last line = %q, want %q", got[len(got)-1], lines[len(lines)-1])
|
|
}
|
|
|
|
// Check omitted marker
|
|
marker := got[50]
|
|
expected := fmt.Sprintf("... (%d lines omitted) ...", 100)
|
|
if marker != expected {
|
|
t.Errorf("marker = %q, want %q", marker, expected)
|
|
}
|
|
})
|
|
|
|
t.Run("exactly at threshold returns all lines", func(t *testing.T) {
|
|
lines := makeLines(100)
|
|
input := strings.Join(lines, "\n")
|
|
got := truncateOutputLines(input, 50)
|
|
if len(got) != 100 {
|
|
t.Errorf("got %d lines, want 100", len(got))
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestTruncateOutputPreservesContent(t *testing.T) {
|
|
// Create input with distinct first and last lines
|
|
lines := make([]string, 200)
|
|
for i := range lines {
|
|
lines[i] = "middle"
|
|
}
|
|
lines[0] = "FIRST"
|
|
lines[49] = "LAST_OF_HEAD"
|
|
lines[150] = "FIRST_OF_TAIL"
|
|
lines[199] = "LAST"
|
|
|
|
input := strings.Join(lines, "\n")
|
|
got := truncateOutput(input, 50)
|
|
|
|
if !strings.Contains(got, "FIRST") {
|
|
t.Error("missing FIRST")
|
|
}
|
|
if !strings.Contains(got, "LAST_OF_HEAD") {
|
|
t.Error("missing LAST_OF_HEAD")
|
|
}
|
|
if !strings.Contains(got, "FIRST_OF_TAIL") {
|
|
t.Error("missing FIRST_OF_TAIL")
|
|
}
|
|
if !strings.Contains(got, "LAST") {
|
|
t.Error("missing LAST")
|
|
}
|
|
if !strings.Contains(got, "(100 lines omitted)") {
|
|
t.Errorf("wrong omitted count, got: %s", got)
|
|
}
|
|
}
|