Skip to content

Commit 4dbdc29

Browse files
test: add unit tests for controller util package
Signed-off-by: Artem Muterko <artem@sopho.tech>
1 parent 749e8f2 commit 4dbdc29

3 files changed

Lines changed: 460 additions & 0 deletions

File tree

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
package util
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/stretchr/testify/assert"
8+
v1 "k8s.io/api/core/v1"
9+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10+
11+
appsv1alpha1 "github.com/openkruise/kruise/apis/apps/v1alpha1"
12+
)
13+
14+
func TestGetMessageKvFromCondition(t *testing.T) {
15+
tests := []struct {
16+
name string
17+
condition *v1.PodCondition
18+
wantKv map[string]interface{}
19+
wantErr bool
20+
}{
21+
{
22+
name: "nil condition",
23+
condition: nil,
24+
wantKv: map[string]interface{}{},
25+
wantErr: false,
26+
},
27+
{
28+
name: "empty message",
29+
condition: &v1.PodCondition{Message: ""},
30+
wantKv: map[string]interface{}{},
31+
wantErr: false,
32+
},
33+
{
34+
name: "valid JSON message",
35+
condition: &v1.PodCondition{Message: `{"key":"value","count":42}`},
36+
wantKv: map[string]interface{}{"key": "value", "count": float64(42)},
37+
wantErr: false,
38+
},
39+
{
40+
name: "malformed JSON",
41+
condition: &v1.PodCondition{Message: `{not json}`},
42+
wantKv: nil,
43+
wantErr: true,
44+
},
45+
}
46+
47+
for _, tt := range tests {
48+
t.Run(tt.name, func(t *testing.T) {
49+
kv, err := GetMessageKvFromCondition(tt.condition)
50+
if tt.wantErr {
51+
assert.Error(t, err)
52+
assert.Nil(t, kv)
53+
} else {
54+
assert.NoError(t, err)
55+
assert.Equal(t, tt.wantKv, kv)
56+
}
57+
})
58+
}
59+
}
60+
61+
func TestUpdateMessageKvConditionRoundTrip(t *testing.T) {
62+
original := map[string]interface{}{
63+
"foo": "bar",
64+
"num": float64(123),
65+
}
66+
condition := &v1.PodCondition{}
67+
UpdateMessageKvCondition(original, condition)
68+
69+
assert.NotEmpty(t, condition.Message)
70+
71+
recovered, err := GetMessageKvFromCondition(condition)
72+
assert.NoError(t, err)
73+
assert.Equal(t, original, recovered)
74+
}
75+
76+
func TestGetTimeBeforePendingTimeout(t *testing.T) {
77+
now := time.Now()
78+
timeout := 10 * time.Minute
79+
deletionTime := metav1.Now()
80+
81+
tests := []struct {
82+
name string
83+
pod *v1.Pod
84+
wantTimeouted bool
85+
wantPositiveDur bool
86+
wantNegativeOneDur bool
87+
}{
88+
{
89+
name: "pod with DeletionTimestamp",
90+
pod: &v1.Pod{
91+
ObjectMeta: metav1.ObjectMeta{DeletionTimestamp: &deletionTime},
92+
Status: v1.PodStatus{Phase: v1.PodPending},
93+
},
94+
wantTimeouted: false,
95+
wantNegativeOneDur: true,
96+
},
97+
{
98+
name: "pod not in Pending phase",
99+
pod: &v1.Pod{
100+
Status: v1.PodStatus{Phase: v1.PodRunning},
101+
},
102+
wantTimeouted: false,
103+
wantNegativeOneDur: true,
104+
},
105+
{
106+
name: "pod with NodeName set",
107+
pod: &v1.Pod{
108+
Spec: v1.PodSpec{NodeName: "node-1"},
109+
Status: v1.PodStatus{Phase: v1.PodPending},
110+
},
111+
wantTimeouted: false,
112+
wantNegativeOneDur: true,
113+
},
114+
{
115+
name: "pending pod without Unschedulable condition",
116+
pod: &v1.Pod{
117+
Status: v1.PodStatus{
118+
Phase: v1.PodPending,
119+
Conditions: []v1.PodCondition{
120+
{Type: v1.PodReady, Status: v1.ConditionFalse},
121+
},
122+
},
123+
},
124+
wantTimeouted: false,
125+
wantNegativeOneDur: true,
126+
},
127+
{
128+
name: "PodScheduled=False but wrong Reason",
129+
pod: &v1.Pod{
130+
ObjectMeta: metav1.ObjectMeta{CreationTimestamp: metav1.NewTime(now.Add(-20 * time.Minute))},
131+
Status: v1.PodStatus{
132+
Phase: v1.PodPending,
133+
Conditions: []v1.PodCondition{
134+
{
135+
Type: v1.PodScheduled,
136+
Status: v1.ConditionFalse,
137+
Reason: "SomeOtherReason",
138+
},
139+
},
140+
},
141+
},
142+
wantTimeouted: false,
143+
wantNegativeOneDur: true,
144+
},
145+
{
146+
name: "condition matches, already past deadline",
147+
pod: &v1.Pod{
148+
ObjectMeta: metav1.ObjectMeta{CreationTimestamp: metav1.NewTime(now.Add(-20 * time.Minute))},
149+
Status: v1.PodStatus{
150+
Phase: v1.PodPending,
151+
Conditions: []v1.PodCondition{
152+
{
153+
Type: v1.PodScheduled,
154+
Status: v1.ConditionFalse,
155+
Reason: string(v1.PodReasonUnschedulable),
156+
},
157+
},
158+
},
159+
},
160+
wantTimeouted: true,
161+
wantNegativeOneDur: true,
162+
},
163+
{
164+
name: "condition matches, not yet past deadline",
165+
pod: &v1.Pod{
166+
ObjectMeta: metav1.ObjectMeta{CreationTimestamp: metav1.NewTime(now.Add(-5 * time.Minute))},
167+
Status: v1.PodStatus{
168+
Phase: v1.PodPending,
169+
Conditions: []v1.PodCondition{
170+
{
171+
Type: v1.PodScheduled,
172+
Status: v1.ConditionFalse,
173+
Reason: string(v1.PodReasonUnschedulable),
174+
},
175+
},
176+
},
177+
},
178+
wantTimeouted: false,
179+
wantPositiveDur: true,
180+
},
181+
}
182+
183+
for _, tt := range tests {
184+
t.Run(tt.name, func(t *testing.T) {
185+
timeouted, dur := GetTimeBeforePendingTimeout(tt.pod, timeout, now)
186+
assert.Equal(t, tt.wantTimeouted, timeouted)
187+
if tt.wantNegativeOneDur {
188+
assert.Equal(t, time.Duration(-1), dur)
189+
}
190+
if tt.wantPositiveDur {
191+
assert.True(t, dur > 0, "expected positive duration, got %v", dur)
192+
}
193+
})
194+
}
195+
}
196+
197+
func TestGetTimeBeforeUpdateTimeout(t *testing.T) {
198+
now := time.Now()
199+
timeout := 10 * time.Minute
200+
deletionTime := metav1.Now()
201+
202+
tests := []struct {
203+
name string
204+
pod *v1.Pod
205+
conditionTime time.Time
206+
wantTimeouted bool
207+
wantPositiveDur bool
208+
wantNegativeOneDur bool
209+
}{
210+
{
211+
name: "DeletionTimestamp set",
212+
pod: &v1.Pod{
213+
ObjectMeta: metav1.ObjectMeta{DeletionTimestamp: &deletionTime},
214+
},
215+
conditionTime: now.Add(-5 * time.Minute),
216+
wantTimeouted: false,
217+
wantNegativeOneDur: true,
218+
},
219+
{
220+
name: "before deadline",
221+
pod: &v1.Pod{},
222+
conditionTime: now.Add(-5 * time.Minute),
223+
wantTimeouted: false,
224+
wantPositiveDur: true,
225+
},
226+
{
227+
name: "past deadline",
228+
pod: &v1.Pod{},
229+
conditionTime: now.Add(-20 * time.Minute),
230+
wantTimeouted: true,
231+
wantNegativeOneDur: true,
232+
},
233+
}
234+
235+
for _, tt := range tests {
236+
t.Run(tt.name, func(t *testing.T) {
237+
condition := &appsv1alpha1.UnitedDeploymentCondition{
238+
LastTransitionTime: metav1.NewTime(tt.conditionTime),
239+
}
240+
timeouted, dur := GetTimeBeforeUpdateTimeout(tt.pod, condition, timeout, now)
241+
assert.Equal(t, tt.wantTimeouted, timeouted)
242+
if tt.wantNegativeOneDur {
243+
assert.Equal(t, time.Duration(-1), dur)
244+
}
245+
if tt.wantPositiveDur {
246+
assert.True(t, dur > 0, "expected positive duration, got %v", dur)
247+
}
248+
})
249+
}
250+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package util
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
corev1 "k8s.io/api/core/v1"
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
)
10+
11+
func TestGetParentNameAndOrdinal(t *testing.T) {
12+
tests := []struct {
13+
name string
14+
podName string
15+
wantParent string
16+
wantOrdinal int32
17+
}{
18+
{
19+
name: "normal pod name",
20+
podName: "foo-42",
21+
wantParent: "foo",
22+
wantOrdinal: 42,
23+
},
24+
{
25+
name: "multi-segment parent name",
26+
podName: "foo-bar-baz-7",
27+
wantParent: "foo-bar-baz",
28+
wantOrdinal: 7,
29+
},
30+
{
31+
name: "ordinal 0",
32+
podName: "foo-0",
33+
wantParent: "foo",
34+
wantOrdinal: 0,
35+
},
36+
{
37+
name: "no trailing digits",
38+
podName: "foo-bar",
39+
wantParent: "",
40+
wantOrdinal: -1,
41+
},
42+
{
43+
name: "empty string",
44+
podName: "",
45+
wantParent: "",
46+
wantOrdinal: -1,
47+
},
48+
{
49+
name: "large overflow number returns parent but ordinal -1",
50+
podName: "foo-9999999999999999999",
51+
wantParent: "foo",
52+
wantOrdinal: -1,
53+
},
54+
}
55+
56+
for _, tt := range tests {
57+
t.Run(tt.name, func(t *testing.T) {
58+
pod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: tt.podName}}
59+
parent, ordinal := getParentNameAndOrdinal(pod)
60+
assert.Equal(t, tt.wantParent, parent)
61+
assert.Equal(t, tt.wantOrdinal, ordinal)
62+
})
63+
}
64+
}
65+
66+
func TestGetOrdinal(t *testing.T) {
67+
tests := []struct {
68+
name string
69+
podName string
70+
wantOrdinal int32
71+
}{
72+
{
73+
name: "valid ordinal",
74+
podName: "web-3",
75+
wantOrdinal: 3,
76+
},
77+
{
78+
name: "no ordinal",
79+
podName: "web",
80+
wantOrdinal: -1,
81+
},
82+
}
83+
84+
for _, tt := range tests {
85+
t.Run(tt.name, func(t *testing.T) {
86+
pod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: tt.podName}}
87+
assert.Equal(t, tt.wantOrdinal, GetOrdinal(pod))
88+
})
89+
}
90+
}

0 commit comments

Comments
 (0)