Files
gosentry/src/runner/seed_test.go
T
mix 2ef18e759c test: delete duplicate-coverage tests, fix TESTS.md drift, drop hand-rolled itoa
Items 1-3 of the 2026-08-04 test-suite review: TestCleanupLogsKeepsFilesWithinAgeLimit,
TestRunDueEmptyOverlapInheritsGlobal, and TestSameWindowsPathHandlesSpaces had
byte-identical coverage to an existing test and no assertion the survivor lacked.
storage.defaultJobs, the one accidental 0% coverage gap the review found, is now
covered and TESTS.md corrected to match. seed_test.go's itoa is replaced with
strconv.FormatInt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 22:30:27 +03:00

162 lines
5.4 KiB
Go

package runner
import (
"os"
"path/filepath"
"strconv"
"strings"
"testing"
"gitea.mixdep.ru/mix/gosentry/src/domain"
)
// writeTestLog writes a minimal log file in the format writeRunLog produces.
// Pass durationMS < 0 to omit the duration line (legacy log simulation).
// When jobID > 0 a job_id header line is included.
func writeTestLog(t *testing.T, dir, filename, state string, durationMS int64, jobID int) {
t.Helper()
var content strings.Builder
if jobID > 0 {
content.WriteString("job_id: ")
content.WriteString(strconv.Itoa(jobID))
content.WriteString("\n")
}
if durationMS >= 0 {
content.WriteString("state: " + state + "\nduration: " + strconv.FormatInt(durationMS, 10) + "\n\n")
} else {
content.WriteString("state: " + state + "\n\n")
}
if err := os.WriteFile(filepath.Join(dir, filename), []byte(content.String()), 0o644); err != nil {
t.Fatal(err)
}
}
func TestSeedStatsBasic(t *testing.T) {
dir := t.TempDir()
job := domain.Job{ID: 1, Name: "Build"}
name := sanitizeFileName(job.Name)
writeTestLog(t, dir, "20260601-100000_"+name+".log", "OK", 200, job.ID)
writeTestLog(t, dir, "20260601-110000_"+name+".log", "Failed", 400, job.ID)
writeTestLog(t, dir, "20260601-120000_"+name+".log", "OK", 600, job.ID)
result := SeedStats(dir, []domain.Job{job}, 0)
s, ok := result[job.ID]
if !ok {
t.Fatal("expected stats for job 1")
}
if s.RunCount != 3 {
t.Errorf("RunCount = %d, want 3", s.RunCount)
}
if s.FailCount != 1 {
t.Errorf("FailCount = %d, want 1", s.FailCount)
}
if s.LastDurationMS != 600 {
t.Errorf("LastDurationMS = %d, want 600", s.LastDurationMS)
}
if s.MaxDurationMS != 600 {
t.Errorf("MaxDurationMS = %d, want 600", s.MaxDurationMS)
}
// avg = (200+400+600)/3 = 400
if s.AvgDurationMS != 400 {
t.Errorf("AvgDurationMS = %d, want 400", s.AvgDurationMS)
}
}
// TestSeedStatsDurationLessLegacyLog verifies that a log without a duration
// line still contributes to RunCount/FailCount but is excluded from duration
// aggregates, so a missing duration cannot masquerade as a 0 ms run.
func TestSeedStatsDurationLessLegacyLog(t *testing.T) {
dir := t.TempDir()
job := domain.Job{ID: 2, Name: "Deploy"}
name := sanitizeFileName(job.Name)
// Legacy log (no duration line).
writeTestLog(t, dir, "20260601-080000_"+name+".log", "OK", -1, job.ID)
// Modern log with duration.
writeTestLog(t, dir, "20260601-090000_"+name+".log", "OK", 300, job.ID)
result := SeedStats(dir, []domain.Job{job}, 0)
s := result[job.ID]
if s.RunCount != 2 {
t.Errorf("RunCount = %d, want 2", s.RunCount)
}
if s.FailCount != 0 {
t.Errorf("FailCount = %d, want 0", s.FailCount)
}
// Only the modern log has a duration — avg/last/max must reflect that single entry.
if s.LastDurationMS != 300 {
t.Errorf("LastDurationMS = %d, want 300", s.LastDurationMS)
}
if s.AvgDurationMS != 300 {
t.Errorf("AvgDurationMS = %d, want 300", s.AvgDurationMS)
}
}
// TestSeedStatsMaxFilesHonoured verifies that only the newest N logs are
// parsed when maxFiles is positive.
func TestSeedStatsMaxFilesHonoured(t *testing.T) {
dir := t.TempDir()
job := domain.Job{ID: 3, Name: "Cleanup"}
name := sanitizeFileName(job.Name)
// Write 3 logs; only the 2 newest should be counted (maxFiles=2).
writeTestLog(t, dir, "20260601-060000_"+name+".log", "OK", 100, job.ID)
writeTestLog(t, dir, "20260601-070000_"+name+".log", "OK", 200, job.ID)
writeTestLog(t, dir, "20260601-080000_"+name+".log", "Failed", 300, job.ID)
result := SeedStats(dir, []domain.Job{job}, 2)
s := result[job.ID]
if s.RunCount != 2 {
t.Errorf("RunCount = %d, want 2 (maxFiles=2)", s.RunCount)
}
if s.FailCount != 1 {
t.Errorf("FailCount = %d, want 1", s.FailCount)
}
}
// TestSeedStatsMissingDir yields an empty map and does not panic.
func TestSeedStatsMissingDir(t *testing.T) {
result := SeedStats(filepath.Join(t.TempDir(), "no-such-dir"), []domain.Job{{ID: 1, Name: "J"}}, 0)
if len(result) != 0 {
t.Errorf("expected empty result for missing dir, got %v", result)
}
}
// TestSeedStatsUnknownJobProducesNoEntry verifies that log files not matching
// any known job are silently ignored.
func TestSeedStatsUnknownJobProducesNoEntry(t *testing.T) {
dir := t.TempDir()
writeTestLog(t, dir, "20260601-100000_UnknownJob.log", "OK", 100, 0)
result := SeedStats(dir, []domain.Job{{ID: 1, Name: "KnownJob"}}, 0)
if _, ok := result[1]; ok {
t.Error("expected no entry for a job with no matching log files")
}
}
// TestSeedStatsMatchesByJobID verifies that logs are associated by job_id even
// when sanitized job names would collide.
func TestSeedStatsMatchesByJobID(t *testing.T) {
dir := t.TempDir()
jobA := domain.Job{ID: 1, Name: "foo@bar"}
jobB := domain.Job{ID: 2, Name: "foo bar"}
colliding := sanitizeFileName(jobA.Name)
if colliding != sanitizeFileName(jobB.Name) {
t.Fatalf("test setup: expected colliding sanitized names, got %q and %q", sanitizeFileName(jobA.Name), sanitizeFileName(jobB.Name))
}
writeTestLog(t, dir, "20260601-100000_"+colliding+".log", "OK", 100, jobA.ID)
writeTestLog(t, dir, "20260601-110000_"+colliding+".log", "Failed", 200, jobB.ID)
result := SeedStats(dir, []domain.Job{jobA, jobB}, 0)
if result[jobA.ID].RunCount != 1 || result[jobA.ID].LastDurationMS != 100 {
t.Errorf("job A stats = %+v, want one OK run at 100 ms", result[jobA.ID])
}
if result[jobB.ID].RunCount != 1 || result[jobB.ID].FailCount != 1 || result[jobB.ID].LastDurationMS != 200 {
t.Errorf("job B stats = %+v, want one Failed run at 200 ms", result[jobB.ID])
}
}