454 lines
13 KiB
Go
454 lines
13 KiB
Go
package kubedog
|
|
|
|
import (
|
|
"bytes"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/gookit/color"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/werf/kubedog/pkg/tracker/daemonset"
|
|
"github.com/werf/kubedog/pkg/tracker/deployment"
|
|
"github.com/werf/kubedog/pkg/tracker/job"
|
|
"github.com/werf/kubedog/pkg/tracker/pod"
|
|
"github.com/werf/kubedog/pkg/tracker/statefulset"
|
|
)
|
|
|
|
// TestMain forces ANSI color output so that tests asserting on escape codes
|
|
// pass in non-TTY environments such as CI runners.
|
|
func TestMain(m *testing.M) {
|
|
color.ForceColor()
|
|
os.Exit(m.Run())
|
|
}
|
|
|
|
// --- formatResourceCaption ---
|
|
|
|
func TestFormatResourceCaption_Ready(t *testing.T) {
|
|
result := formatResourceCaption("deploy/myapp", true, false)
|
|
assert.Contains(t, result, "deploy/myapp")
|
|
// Green ANSI escape should be present
|
|
assert.Contains(t, result, "\033[")
|
|
}
|
|
|
|
func TestFormatResourceCaption_Failed(t *testing.T) {
|
|
result := formatResourceCaption("deploy/myapp", false, true)
|
|
assert.Contains(t, result, "deploy/myapp")
|
|
assert.Contains(t, result, "\033[")
|
|
}
|
|
|
|
func TestFormatResourceCaption_InProgress(t *testing.T) {
|
|
result := formatResourceCaption("deploy/myapp", false, false)
|
|
assert.Contains(t, result, "deploy/myapp")
|
|
// Yellow for in-progress
|
|
assert.Contains(t, result, "\033[")
|
|
}
|
|
|
|
func TestFormatResourceCaption_ReadyTakesPrecedence(t *testing.T) {
|
|
// isReady=true should win over isFailed=true
|
|
resultReady := formatResourceCaption("x", true, false)
|
|
resultFailed := formatResourceCaption("x", false, true)
|
|
// Colors should differ
|
|
assert.NotEqual(t, resultReady, resultFailed)
|
|
}
|
|
|
|
// --- formatPodResourceCaption ---
|
|
|
|
func TestFormatPodResourceCaption_NotNew(t *testing.T) {
|
|
result := formatPodResourceCaption("my-pod-abc", true, false, false)
|
|
// Not a new pod: no coloring applied, just the plain name
|
|
assert.Equal(t, "my-pod-abc", result)
|
|
}
|
|
|
|
func TestFormatPodResourceCaption_NewAndReady(t *testing.T) {
|
|
result := formatPodResourceCaption("my-pod-abc", true, false, true)
|
|
assert.Contains(t, result, "my-pod-abc")
|
|
assert.Contains(t, result, "\033[")
|
|
}
|
|
|
|
func TestFormatPodResourceCaption_NewAndFailed(t *testing.T) {
|
|
result := formatPodResourceCaption("my-pod-abc", false, true, true)
|
|
assert.Contains(t, result, "my-pod-abc")
|
|
assert.Contains(t, result, "\033[")
|
|
}
|
|
|
|
func TestFormatPodResourceCaption_NewInProgress(t *testing.T) {
|
|
result := formatPodResourceCaption("my-pod-abc", false, false, true)
|
|
assert.Contains(t, result, "my-pod-abc")
|
|
assert.Contains(t, result, "\033[")
|
|
}
|
|
|
|
// --- formatResourceError / formatResourceWarning ---
|
|
|
|
func TestFormatResourceError(t *testing.T) {
|
|
result := formatResourceError("CrashLoopBackOff")
|
|
assert.Contains(t, result, "error:")
|
|
assert.Contains(t, result, "CrashLoopBackOff")
|
|
}
|
|
|
|
func TestFormatResourceWarning(t *testing.T) {
|
|
result := formatResourceWarning("PodNotScheduled")
|
|
assert.Contains(t, result, "warning:")
|
|
assert.Contains(t, result, "PodNotScheduled")
|
|
}
|
|
|
|
// --- termWidth ---
|
|
|
|
func TestTermWidth_ReturnsPositive(t *testing.T) {
|
|
w := termWidth()
|
|
assert.Greater(t, w, 0)
|
|
}
|
|
|
|
// --- displayDeploymentStatusProgress ---
|
|
|
|
func TestDisplayDeploymentStatusProgress_ZeroStatus(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
caption := formatResourceCaption("deploy/myapp", false, false)
|
|
var prev deployment.DeploymentStatus
|
|
status := deployment.DeploymentStatus{}
|
|
|
|
// Must not panic and must produce some output
|
|
assert.NotPanics(t, func() {
|
|
displayDeploymentStatusProgress(&buf, caption, status, &prev)
|
|
})
|
|
out := buf.String()
|
|
assert.NotEmpty(t, out)
|
|
assert.Contains(t, out, "DEPLOYMENT")
|
|
}
|
|
|
|
func TestDisplayDeploymentStatusProgress_Failed(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
caption := formatResourceCaption("deploy/myapp", false, true)
|
|
var prev deployment.DeploymentStatus
|
|
status := deployment.DeploymentStatus{
|
|
IsFailed: true,
|
|
FailedReason: "ImagePullBackOff",
|
|
}
|
|
|
|
assert.NotPanics(t, func() {
|
|
displayDeploymentStatusProgress(&buf, caption, status, &prev)
|
|
})
|
|
out := buf.String()
|
|
assert.Contains(t, out, "error:")
|
|
assert.Contains(t, out, "ImagePullBackOff")
|
|
}
|
|
|
|
func TestDisplayDeploymentStatusProgress_WithWaitingMessage(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
caption := formatResourceCaption("deploy/myapp", false, false)
|
|
var prev deployment.DeploymentStatus
|
|
// WaitingForMessages is only rendered when there are pods
|
|
status := deployment.DeploymentStatus{
|
|
StatusGeneration: 1,
|
|
WaitingForMessages: []string{"up-to-date 1->3"},
|
|
Pods: map[string]pod.PodStatus{
|
|
"myapp-pod-abc": {ReadyContainers: 1, TotalContainers: 1},
|
|
},
|
|
}
|
|
|
|
assert.NotPanics(t, func() {
|
|
displayDeploymentStatusProgress(&buf, caption, status, &prev)
|
|
})
|
|
out := buf.String()
|
|
assert.Contains(t, out, "Waiting for:")
|
|
assert.Contains(t, out, "up-to-date 1->3")
|
|
}
|
|
|
|
func TestDisplayDeploymentStatusProgress_WithPods(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
caption := formatResourceCaption("deploy/myapp", false, false)
|
|
prev := deployment.DeploymentStatus{}
|
|
status := deployment.DeploymentStatus{
|
|
StatusGeneration: 1,
|
|
Pods: map[string]pod.PodStatus{
|
|
"myapp-abc-123": {ReadyContainers: 1, TotalContainers: 1},
|
|
},
|
|
NewPodsNames: []string{"myapp-abc-123"},
|
|
}
|
|
|
|
assert.NotPanics(t, func() {
|
|
displayDeploymentStatusProgress(&buf, caption, status, &prev)
|
|
})
|
|
out := buf.String()
|
|
assert.Contains(t, out, "POD")
|
|
assert.Contains(t, out, "myapp-abc-123")
|
|
}
|
|
|
|
// --- displayStatefulSetStatusProgress ---
|
|
|
|
func TestDisplayStatefulSetStatusProgress_ZeroStatus(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
caption := formatResourceCaption("sts/myapp", false, false)
|
|
var prev statefulset.StatefulSetStatus
|
|
status := statefulset.StatefulSetStatus{}
|
|
|
|
assert.NotPanics(t, func() {
|
|
displayStatefulSetStatusProgress(&buf, caption, status, &prev)
|
|
})
|
|
out := buf.String()
|
|
assert.Contains(t, out, "STATEFULSET")
|
|
}
|
|
|
|
func TestDisplayStatefulSetStatusProgress_WithWarnings(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
caption := formatResourceCaption("sts/myapp", false, false)
|
|
var prev statefulset.StatefulSetStatus
|
|
status := statefulset.StatefulSetStatus{
|
|
WarningMessages: []string{"PodNotScheduled: insufficient resources"},
|
|
}
|
|
|
|
assert.NotPanics(t, func() {
|
|
displayStatefulSetStatusProgress(&buf, caption, status, &prev)
|
|
})
|
|
out := buf.String()
|
|
assert.Contains(t, out, "warning:")
|
|
assert.Contains(t, out, "PodNotScheduled")
|
|
}
|
|
|
|
func TestDisplayStatefulSetStatusProgress_Failed(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
caption := formatResourceCaption("sts/myapp", false, true)
|
|
var prev statefulset.StatefulSetStatus
|
|
status := statefulset.StatefulSetStatus{
|
|
IsFailed: true,
|
|
FailedReason: "timeout waiting for ready",
|
|
}
|
|
|
|
assert.NotPanics(t, func() {
|
|
displayStatefulSetStatusProgress(&buf, caption, status, &prev)
|
|
})
|
|
out := buf.String()
|
|
assert.Contains(t, out, "error:")
|
|
assert.Contains(t, out, "timeout waiting for ready")
|
|
}
|
|
|
|
// --- displayDaemonSetStatusProgress ---
|
|
|
|
func TestDisplayDaemonSetStatusProgress_ZeroStatus(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
caption := formatResourceCaption("ds/myapp", false, false)
|
|
var prev daemonset.DaemonSetStatus
|
|
status := daemonset.DaemonSetStatus{}
|
|
|
|
assert.NotPanics(t, func() {
|
|
displayDaemonSetStatusProgress(&buf, caption, status, &prev)
|
|
})
|
|
out := buf.String()
|
|
assert.Contains(t, out, "DAEMONSET")
|
|
}
|
|
|
|
func TestDisplayDaemonSetStatusProgress_Failed(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
caption := formatResourceCaption("ds/myapp", false, true)
|
|
var prev daemonset.DaemonSetStatus
|
|
status := daemonset.DaemonSetStatus{
|
|
IsFailed: true,
|
|
FailedReason: "node not ready",
|
|
}
|
|
|
|
assert.NotPanics(t, func() {
|
|
displayDaemonSetStatusProgress(&buf, caption, status, &prev)
|
|
})
|
|
out := buf.String()
|
|
assert.Contains(t, out, "error:")
|
|
assert.Contains(t, out, "node not ready")
|
|
}
|
|
|
|
// --- displayJobStatusProgress ---
|
|
|
|
func TestDisplayJobStatusProgress_ZeroStatus(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
caption := formatResourceCaption("job/myjob", false, false)
|
|
var prev job.JobStatus
|
|
status := job.JobStatus{}
|
|
|
|
assert.NotPanics(t, func() {
|
|
displayJobStatusProgress(&buf, caption, status, &prev)
|
|
})
|
|
out := buf.String()
|
|
assert.Contains(t, out, "JOB")
|
|
}
|
|
|
|
func TestDisplayJobStatusProgress_Active(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
caption := formatResourceCaption("job/myjob", false, false)
|
|
var prev job.JobStatus
|
|
status := job.JobStatus{
|
|
StatusGeneration: 1,
|
|
}
|
|
|
|
assert.NotPanics(t, func() {
|
|
displayJobStatusProgress(&buf, caption, status, &prev)
|
|
})
|
|
out := buf.String()
|
|
assert.Contains(t, out, "ACTIVE")
|
|
}
|
|
|
|
func TestDisplayJobStatusProgress_Failed(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
caption := formatResourceCaption("job/myjob", false, true)
|
|
var prev job.JobStatus
|
|
status := job.JobStatus{
|
|
IsFailed: true,
|
|
FailedReason: "BackoffLimitExceeded",
|
|
}
|
|
|
|
assert.NotPanics(t, func() {
|
|
displayJobStatusProgress(&buf, caption, status, &prev)
|
|
})
|
|
out := buf.String()
|
|
assert.Contains(t, out, "error:")
|
|
assert.Contains(t, out, "BackoffLimitExceeded")
|
|
}
|
|
|
|
func TestDisplayJobStatusProgress_WithWaitingMessage(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
caption := formatResourceCaption("job/myjob", false, false)
|
|
var prev job.JobStatus
|
|
status := job.JobStatus{
|
|
WaitingForMessages: []string{"succeeded 0->1"},
|
|
Pods: map[string]pod.PodStatus{
|
|
"myjob-abc": {ReadyContainers: 0, TotalContainers: 1},
|
|
},
|
|
}
|
|
|
|
assert.NotPanics(t, func() {
|
|
displayJobStatusProgress(&buf, caption, status, &prev)
|
|
})
|
|
out := buf.String()
|
|
assert.Contains(t, out, "Waiting for:")
|
|
assert.Contains(t, out, "succeeded 0->1")
|
|
}
|
|
|
|
// --- displayChildPodsStatusProgress ---
|
|
|
|
func TestDisplayChildPodsStatusProgress_Empty(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
caption := formatResourceCaption("deploy/myapp", false, false)
|
|
// With no pods, only the header should be rendered
|
|
prev := deployment.DeploymentStatus{}
|
|
status := deployment.DeploymentStatus{
|
|
Pods: map[string]pod.PodStatus{},
|
|
}
|
|
assert.NotPanics(t, func() {
|
|
displayDeploymentStatusProgress(&buf, caption, status, &prev)
|
|
})
|
|
// No POD sub-table header when pods is empty
|
|
out := buf.String()
|
|
assert.NotContains(t, out, "POD")
|
|
}
|
|
|
|
func TestDisplayChildPodsStatusProgress_NewPodSet(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
caption := formatResourceCaption("deploy/myapp", false, false)
|
|
prev := deployment.DeploymentStatus{}
|
|
// Two pods: one new, one old
|
|
status := deployment.DeploymentStatus{
|
|
StatusGeneration: 1,
|
|
Pods: map[string]pod.PodStatus{
|
|
"pod-new-abc": {ReadyContainers: 0, TotalContainers: 1},
|
|
"pod-old-xyz": {ReadyContainers: 1, TotalContainers: 1},
|
|
},
|
|
NewPodsNames: []string{"pod-new-abc"},
|
|
}
|
|
|
|
assert.NotPanics(t, func() {
|
|
displayDeploymentStatusProgress(&buf, caption, status, &prev)
|
|
})
|
|
out := buf.String()
|
|
assert.Contains(t, out, "pod-new-abc")
|
|
assert.Contains(t, out, "pod-old-xyz")
|
|
}
|
|
|
|
func TestDisplayChildPodsStatusProgress_ManyPodsO1Check(t *testing.T) {
|
|
// Verifies O(1) new-pod detection works correctly for many pods
|
|
var buf bytes.Buffer
|
|
caption := formatResourceCaption("deploy/myapp", false, false)
|
|
prev := deployment.DeploymentStatus{}
|
|
|
|
pods := make(map[string]pod.PodStatus)
|
|
newNames := make([]string, 0, 10)
|
|
for i := 0; i < 20; i++ {
|
|
name := strings.Repeat("a", i+1)
|
|
pods[name] = pod.PodStatus{ReadyContainers: 1, TotalContainers: 1}
|
|
if i%2 == 0 {
|
|
newNames = append(newNames, name)
|
|
}
|
|
}
|
|
status := deployment.DeploymentStatus{
|
|
StatusGeneration: 1,
|
|
Pods: pods,
|
|
NewPodsNames: newNames,
|
|
}
|
|
|
|
assert.NotPanics(t, func() {
|
|
displayDeploymentStatusProgress(&buf, caption, status, &prev)
|
|
})
|
|
assert.NotEmpty(t, buf.String())
|
|
}
|
|
|
|
// --- displayCanaryStatus ---
|
|
|
|
func TestDisplayCanaryStatus_Normal(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
caption := formatResourceCaption("canary/myapp", false, false)
|
|
view := CanaryStatusView{Phase: "Progressing", Age: "1m"}
|
|
|
|
assert.NotPanics(t, func() {
|
|
displayCanaryStatus(&buf, caption, view)
|
|
})
|
|
out := buf.String()
|
|
assert.Contains(t, out, "Progressing")
|
|
assert.Contains(t, out, "1m")
|
|
}
|
|
|
|
func TestDisplayCanaryStatus_Failed(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
caption := formatResourceCaption("canary/myapp", false, true)
|
|
view := CanaryStatusView{Phase: "Failed", IsFailed: true}
|
|
|
|
assert.NotPanics(t, func() {
|
|
displayCanaryStatus(&buf, caption, view)
|
|
})
|
|
out := buf.String()
|
|
assert.Contains(t, out, "Failed")
|
|
}
|
|
|
|
func TestDisplayCanaryStatus_Succeeded(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
caption := formatResourceCaption("canary/myapp", true, false)
|
|
view := CanaryStatusView{Phase: "Succeeded"}
|
|
|
|
assert.NotPanics(t, func() {
|
|
displayCanaryStatus(&buf, caption, view)
|
|
})
|
|
out := buf.String()
|
|
assert.Contains(t, out, "Succeeded")
|
|
}
|
|
|
|
func TestDisplayCanaryStatus_EmptyPhaseAndAge(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
caption := formatResourceCaption("canary/myapp", false, false)
|
|
view := CanaryStatusView{}
|
|
|
|
assert.NotPanics(t, func() {
|
|
displayCanaryStatus(&buf, caption, view)
|
|
})
|
|
// Should still produce output (at least the caption + newline)
|
|
assert.NotEmpty(t, buf.String())
|
|
}
|
|
|
|
// --- writeOut ---
|
|
|
|
func TestWriteOut(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
writeOut(&buf, "hello world")
|
|
assert.Equal(t, "hello world", buf.String())
|
|
}
|
|
|
|
func TestWriteOut_Empty(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
writeOut(&buf, "")
|
|
assert.Equal(t, "", buf.String())
|
|
}
|