Skip to content

Commit 0168ee6

Browse files
chore(test): add unit tests for trainingruntime and trainjob utilities (#3370)
* test(util): add unit tests for trainingruntime and trainjob utilities Fixes #3152 pkg/util/trainingruntime (0 percent to 100 percent coverage): - New trainingruntime_test.go with 8 cases for IsSupportDeprecated covering nil, empty, absent key, wrong value, empty value, correct deprecated, multiple labels, and partial match edge case. pkg/util/trainjob (extended): - TestRuntimeRefIsClusterTrainingRuntime: 4 new edge cases added covering wrong APIGroup, nil APIGroup, nil Kind, both nil. - TestIsTrainJobFinished: 6 new cases covering all condition states. - Added metav1 import required for Condition status constants. Signed-off-by: Yugendhar S <yugendhars06@gmail.com> * chore(test): address review feedback from andreyvelich - Remove year from Copyright headers in both test files - Add trailing newline at end of both files - Fix error message format in TestRuntimeRefIsTrainingRuntime to use 'FunctionName(%v) = %v, want %v' pattern for consistency - Add nil edge cases to TestRuntimeRefIsTrainingRuntime for symmetry with TestRuntimeRefIsClusterTrainingRuntime (nil APIGroup, nil Kind, both nil, wrong APIGroup) Signed-off-by: Yugendhar S <yugendhars06@gmail.com> * chore(test): fix line endings and formatting for CI Signed-off-by: Yugendhar S <yugendhars06@gmail.com> --------- Signed-off-by: Yugendhar S <yugendhars06@gmail.com>
1 parent 85bbe58 commit 0168ee6

2 files changed

Lines changed: 233 additions & 4 deletions

File tree

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
Copyright The Kubeflow Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package trainingruntime
18+
19+
import (
20+
"testing"
21+
22+
"github.com/kubeflow/trainer/v2/pkg/constants"
23+
)
24+
25+
func TestIsSupportDeprecated(t *testing.T) {
26+
cases := map[string]struct {
27+
labels map[string]string
28+
want bool
29+
}{
30+
"nil labels returns false": {
31+
labels: nil,
32+
want: false,
33+
},
34+
"empty labels returns false": {
35+
labels: map[string]string{},
36+
want: false,
37+
},
38+
"label key absent returns false": {
39+
labels: map[string]string{
40+
"some-other-label": "value",
41+
},
42+
want: false,
43+
},
44+
"label key present but value is not deprecated returns false": {
45+
labels: map[string]string{
46+
constants.LabelSupport: "supported",
47+
},
48+
want: false,
49+
},
50+
"label key present but value is empty returns false": {
51+
labels: map[string]string{
52+
constants.LabelSupport: "",
53+
},
54+
want: false,
55+
},
56+
"label key present with deprecated value returns true": {
57+
labels: map[string]string{
58+
constants.LabelSupport: constants.SupportDeprecated,
59+
},
60+
want: true,
61+
},
62+
"deprecated label alongside other labels returns true": {
63+
labels: map[string]string{
64+
"app": "kubeflow",
65+
constants.LabelSupport: constants.SupportDeprecated,
66+
"version": "v2",
67+
},
68+
want: true,
69+
},
70+
"partial match of deprecated value returns false": {
71+
labels: map[string]string{
72+
constants.LabelSupport: constants.SupportDeprecated + "-extra",
73+
},
74+
want: false,
75+
},
76+
}
77+
for name, tc := range cases {
78+
t.Run(name, func(t *testing.T) {
79+
got := IsSupportDeprecated(tc.labels)
80+
if got != tc.want {
81+
t.Errorf("IsSupportDeprecated(%v) = %v, want %v", tc.labels, got, tc.want)
82+
}
83+
})
84+
}
85+
}

pkg/util/trainjob/trainjob_test.go

Lines changed: 148 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2025 The Kubeflow Authors.
2+
Copyright The Kubeflow Authors.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@ package trainjob
1919
import (
2020
"testing"
2121

22+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2223
"k8s.io/utils/ptr"
2324

2425
trainer "github.com/kubeflow/trainer/v2/pkg/apis/trainer/v1alpha1"
@@ -43,12 +44,37 @@ func TestRuntimeRefIsTrainingRuntime(t *testing.T) {
4344
},
4445
want: false,
4546
},
47+
"runtimeRef has wrong APIGroup": {
48+
ref: trainer.RuntimeRef{
49+
APIGroup: ptr.To("other.group.io"),
50+
Kind: ptr.To(trainer.TrainingRuntimeKind),
51+
},
52+
want: false,
53+
},
54+
"runtimeRef has nil APIGroup": {
55+
ref: trainer.RuntimeRef{
56+
APIGroup: nil,
57+
Kind: ptr.To(trainer.TrainingRuntimeKind),
58+
},
59+
want: false,
60+
},
61+
"runtimeRef has nil Kind": {
62+
ref: trainer.RuntimeRef{
63+
APIGroup: &trainer.GroupVersion.Group,
64+
Kind: nil,
65+
},
66+
want: false,
67+
},
68+
"runtimeRef has both nil": {
69+
ref: trainer.RuntimeRef{},
70+
want: false,
71+
},
4672
}
4773
for name, tc := range cases {
4874
t.Run(name, func(t *testing.T) {
4975
got := RuntimeRefIsTrainingRuntime(tc.ref)
5076
if got != tc.want {
51-
t.Errorf("Unexpected RuntimeRefIsTrainingRuntime()\nwant: %v\n, want: %v", got, tc.want)
77+
t.Errorf("RuntimeRefIsTrainingRuntime(%v) = %v, want %v", tc.ref, got, tc.want)
5278
}
5379
})
5480
}
@@ -66,19 +92,137 @@ func TestRuntimeRefIsClusterTrainingRuntime(t *testing.T) {
6692
},
6793
want: true,
6894
},
69-
"runtimeRef is not ClusterTrainingRuntime": {
95+
"runtimeRef is TrainingRuntime not ClusterTrainingRuntime": {
7096
ref: trainer.RuntimeRef{
7197
APIGroup: &trainer.GroupVersion.Group,
7298
Kind: ptr.To(trainer.TrainingRuntimeKind),
7399
},
74100
want: false,
75101
},
102+
"runtimeRef has wrong APIGroup": {
103+
ref: trainer.RuntimeRef{
104+
APIGroup: ptr.To("other.group.io"),
105+
Kind: ptr.To(trainer.ClusterTrainingRuntimeKind),
106+
},
107+
want: false,
108+
},
109+
"runtimeRef has nil APIGroup": {
110+
ref: trainer.RuntimeRef{
111+
APIGroup: nil,
112+
Kind: ptr.To(trainer.ClusterTrainingRuntimeKind),
113+
},
114+
want: false,
115+
},
116+
"runtimeRef has nil Kind": {
117+
ref: trainer.RuntimeRef{
118+
APIGroup: &trainer.GroupVersion.Group,
119+
Kind: nil,
120+
},
121+
want: false,
122+
},
123+
"runtimeRef has both nil": {
124+
ref: trainer.RuntimeRef{},
125+
want: false,
126+
},
76127
}
77128
for name, tc := range cases {
78129
t.Run(name, func(t *testing.T) {
79130
got := RuntimeRefIsClusterTrainingRuntime(tc.ref)
80131
if got != tc.want {
81-
t.Errorf("Unexpected RuntimeRefIsClusterTrainingRuntime()\nwant: %v\n, want: %v", got, tc.want)
132+
t.Errorf("RuntimeRefIsClusterTrainingRuntime(%v) = %v, want %v", tc.ref, got, tc.want)
133+
}
134+
})
135+
}
136+
}
137+
138+
func TestIsTrainJobFinished(t *testing.T) {
139+
cases := map[string]struct {
140+
trainJob *trainer.TrainJob
141+
want bool
142+
}{
143+
"completed TrainJob is finished": {
144+
trainJob: &trainer.TrainJob{
145+
Status: trainer.TrainJobStatus{
146+
Conditions: []metav1.Condition{
147+
{
148+
Type: trainer.TrainJobComplete,
149+
Status: metav1.ConditionTrue,
150+
},
151+
},
152+
},
153+
},
154+
want: true,
155+
},
156+
"failed TrainJob is finished": {
157+
trainJob: &trainer.TrainJob{
158+
Status: trainer.TrainJobStatus{
159+
Conditions: []metav1.Condition{
160+
{
161+
Type: trainer.TrainJobFailed,
162+
Status: metav1.ConditionTrue,
163+
},
164+
},
165+
},
166+
},
167+
want: true,
168+
},
169+
"running TrainJob with no conditions is not finished": {
170+
trainJob: &trainer.TrainJob{
171+
Status: trainer.TrainJobStatus{
172+
Conditions: []metav1.Condition{},
173+
},
174+
},
175+
want: false,
176+
},
177+
"TrainJob with Complete=False is not finished": {
178+
trainJob: &trainer.TrainJob{
179+
Status: trainer.TrainJobStatus{
180+
Conditions: []metav1.Condition{
181+
{
182+
Type: trainer.TrainJobComplete,
183+
Status: metav1.ConditionFalse,
184+
},
185+
},
186+
},
187+
},
188+
want: false,
189+
},
190+
"TrainJob with Failed=False is not finished": {
191+
trainJob: &trainer.TrainJob{
192+
Status: trainer.TrainJobStatus{
193+
Conditions: []metav1.Condition{
194+
{
195+
Type: trainer.TrainJobFailed,
196+
Status: metav1.ConditionFalse,
197+
},
198+
},
199+
},
200+
},
201+
want: false,
202+
},
203+
"TrainJob with both Complete and Failed true is finished": {
204+
trainJob: &trainer.TrainJob{
205+
Status: trainer.TrainJobStatus{
206+
Conditions: []metav1.Condition{
207+
{
208+
Type: trainer.TrainJobComplete,
209+
Status: metav1.ConditionTrue,
210+
},
211+
{
212+
Type: trainer.TrainJobFailed,
213+
Status: metav1.ConditionTrue,
214+
},
215+
},
216+
},
217+
},
218+
want: true,
219+
},
220+
}
221+
for name, tc := range cases {
222+
t.Run(name, func(t *testing.T) {
223+
got := IsTrainJobFinished(tc.trainJob)
224+
if got != tc.want {
225+
t.Errorf("IsTrainJobFinished(%v) = %v, want %v", tc.trainJob, got, tc.want)
82226
}
83227
})
84228
}

0 commit comments

Comments
 (0)