2026-04-01 02:07:09 -03:00
|
|
|
package service
|
|
|
|
|
|
|
|
|
|
import (
|
2026-04-20 01:33:02 -03:00
|
|
|
"fmt"
|
2026-04-01 02:07:09 -03:00
|
|
|
"strings"
|
|
|
|
|
"testing"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestBuildStageHeartbeatProgresses(t *testing.T) {
|
|
|
|
|
p1, m1 := buildStageHeartbeat("titan-13", "titan-04", 10*time.Second)
|
|
|
|
|
p2, m2 := buildStageHeartbeat("titan-13", "titan-04", 3*time.Minute)
|
|
|
|
|
p3, m3 := buildStageHeartbeat("titan-13", "titan-04", 11*time.Minute)
|
|
|
|
|
|
|
|
|
|
if !(p1 > 8 && p1 < p2 && p2 < p3 && p3 <= 76) {
|
|
|
|
|
t.Fatalf("unexpected build progress values: %v %v %v", p1, p2, p3)
|
|
|
|
|
}
|
|
|
|
|
if !strings.Contains(m1, "Scheduling") {
|
|
|
|
|
t.Fatalf("expected scheduling message, got %q", m1)
|
|
|
|
|
}
|
|
|
|
|
if !strings.Contains(m2, "replacement image filesystem") {
|
|
|
|
|
t.Fatalf("expected filesystem message, got %q", m2)
|
|
|
|
|
}
|
|
|
|
|
if !strings.Contains(m3, "Harbor") {
|
|
|
|
|
t.Fatalf("expected Harbor message, got %q", m3)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestFlashStageHeartbeatProgresses(t *testing.T) {
|
|
|
|
|
p1, m1 := flashStageHeartbeat("titan-22", "registry.bstein.dev/metis/titan-13:latest", 5*time.Second)
|
|
|
|
|
p2, m2 := flashStageHeartbeat("titan-22", "registry.bstein.dev/metis/titan-13:latest", 20*time.Second)
|
|
|
|
|
p3, m3 := flashStageHeartbeat("titan-22", "registry.bstein.dev/metis/titan-13:latest", 90*time.Second)
|
|
|
|
|
|
|
|
|
|
if !(p1 > 84 && p1 < p2 && p2 < p3 && p3 <= 98) {
|
|
|
|
|
t.Fatalf("unexpected flash progress values: %v %v %v", p1, p2, p3)
|
|
|
|
|
}
|
|
|
|
|
if !strings.Contains(m1, "Pulling") {
|
|
|
|
|
t.Fatalf("expected pulling message, got %q", m1)
|
|
|
|
|
}
|
|
|
|
|
if !strings.Contains(m2, "Writing") {
|
|
|
|
|
t.Fatalf("expected writing message, got %q", m2)
|
|
|
|
|
}
|
|
|
|
|
if !strings.Contains(m3, "Flushing") {
|
|
|
|
|
t.Fatalf("expected flushing message, got %q", m3)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-20 01:33:02 -03:00
|
|
|
|
|
|
|
|
func TestParseRemoteProgressLogsFindsLatestMarker(t *testing.T) {
|
|
|
|
|
logs := strings.Join([]string{
|
|
|
|
|
"plain log line",
|
|
|
|
|
ProgressLogLine(RemoteProgressUpdate{Stage: "build", ProgressPct: 44, Message: "extracting"}),
|
|
|
|
|
ProgressLogLine(RemoteProgressUpdate{Stage: "build", ProgressPct: 72, Message: "uploading"}),
|
|
|
|
|
}, "\n")
|
|
|
|
|
|
|
|
|
|
update, ok := parseRemoteProgressLogs(logs)
|
|
|
|
|
if !ok {
|
|
|
|
|
t.Fatal("expected to parse remote progress logs")
|
|
|
|
|
}
|
|
|
|
|
if update.Stage != "build" || update.ProgressPct != 72 || update.Message != "uploading" {
|
|
|
|
|
t.Fatalf("unexpected update: %#v", update)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestApplyRemoteProgressUpdatesRunningJob(t *testing.T) {
|
|
|
|
|
app := newTestApp(t)
|
|
|
|
|
job := app.newJob("build", "titan-15", "", "")
|
|
|
|
|
app.setJob(job.ID, func(j *Job) {
|
|
|
|
|
j.Status = JobRunning
|
|
|
|
|
j.Stage = "build"
|
|
|
|
|
j.StageStartedAt = time.Now().Add(-10 * time.Second)
|
|
|
|
|
j.ProgressPct = 30
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
app.applyRemoteProgress(job.ID, RemoteProgressUpdate{
|
|
|
|
|
Stage: "flash",
|
|
|
|
|
ProgressPct: 95,
|
|
|
|
|
Message: "writing image",
|
|
|
|
|
WrittenBytes: 1024,
|
|
|
|
|
TotalBytes: 2048,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
got := app.job(job.ID)
|
|
|
|
|
if got.Stage != "flash" {
|
|
|
|
|
t.Fatalf("expected stage flash, got %q", got.Stage)
|
|
|
|
|
}
|
|
|
|
|
if got.ProgressPct != 95 {
|
|
|
|
|
t.Fatalf("expected progress 95, got %v", got.ProgressPct)
|
|
|
|
|
}
|
|
|
|
|
if got.Message != "writing image" {
|
|
|
|
|
t.Fatalf("expected progress message to update, got %q", got.Message)
|
|
|
|
|
}
|
|
|
|
|
if got.Written != 1024 || got.Total != 2048 {
|
|
|
|
|
t.Fatalf("expected byte counters to update, got written=%d total=%d", got.Written, got.Total)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestHeartbeatRemoteJobUsesActualFlashBytes(t *testing.T) {
|
|
|
|
|
app := newTestApp(t)
|
|
|
|
|
job := app.newJob("replace", "titan-15", "titan-22", "/dev/sdk")
|
|
|
|
|
app.setJob(job.ID, func(j *Job) {
|
|
|
|
|
j.Status = JobRunning
|
|
|
|
|
j.Stage = "flash"
|
|
|
|
|
j.StageStartedAt = time.Now().Add(-15 * time.Second)
|
|
|
|
|
j.ProgressPct = 88
|
|
|
|
|
j.Written = 512
|
|
|
|
|
j.Total = 1024
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
app.heartbeatRemoteJob(job.ID)
|
|
|
|
|
|
|
|
|
|
got := app.job(job.ID)
|
|
|
|
|
if got.ProgressPct <= 92 || got.ProgressPct > 98 {
|
|
|
|
|
t.Fatalf("expected actual write progress between 92 and 98, got %v", got.ProgressPct)
|
|
|
|
|
}
|
|
|
|
|
expected := fmt.Sprintf("Writing %s of %s on %s", humanBytes(512), humanBytes(1024), "titan-22")
|
|
|
|
|
if got.Message != expected {
|
|
|
|
|
t.Fatalf("expected %q, got %q", expected, got.Message)
|
|
|
|
|
}
|
|
|
|
|
}
|