fix(builder): truncate large error output to prevent log overflow

Build errors from nix can be very large (100k+ chars). This truncates
error output to the first 50 and last 50 lines when it exceeds 100
lines, preventing journal and NATS message overflow.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-11 00:42:13 +01:00
parent a8aab16d0e
commit c13914bf5a
3 changed files with 130 additions and 2 deletions

View File

@@ -0,0 +1,115 @@
package builder
import (
"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 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)
}
}