1
16
17 package v1_test
18
19 import (
20 "math"
21 "reflect"
22 "testing"
23
24 "github.com/google/go-cmp/cmp"
25 batchv1 "k8s.io/api/batch/v1"
26 v1 "k8s.io/api/core/v1"
27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28 "k8s.io/apimachinery/pkg/runtime"
29 utilfeature "k8s.io/apiserver/pkg/util/feature"
30 featuregatetesting "k8s.io/component-base/featuregate/testing"
31 "k8s.io/kubernetes/pkg/api/legacyscheme"
32 _ "k8s.io/kubernetes/pkg/apis/batch/install"
33 _ "k8s.io/kubernetes/pkg/apis/core/install"
34 "k8s.io/kubernetes/pkg/features"
35 "k8s.io/utils/pointer"
36
37 . "k8s.io/kubernetes/pkg/apis/batch/v1"
38 )
39
40 func TestSetDefaultJob(t *testing.T) {
41 defaultLabels := map[string]string{"default": "default"}
42 validPodTemplateSpec := v1.PodTemplateSpec{
43 ObjectMeta: metav1.ObjectMeta{Labels: defaultLabels},
44 }
45 tests := map[string]struct {
46 original *batchv1.Job
47 expected *batchv1.Job
48 expectLabels bool
49 enablePodReplacementPolicy bool
50 }{
51 "Pod failure policy with some field values unspecified -> set default values": {
52 original: &batchv1.Job{
53 Spec: batchv1.JobSpec{
54 Template: v1.PodTemplateSpec{
55 ObjectMeta: metav1.ObjectMeta{Labels: defaultLabels},
56 },
57 PodFailurePolicy: &batchv1.PodFailurePolicy{
58 Rules: []batchv1.PodFailurePolicyRule{
59 {
60 Action: batchv1.PodFailurePolicyActionFailJob,
61 OnPodConditions: []batchv1.PodFailurePolicyOnPodConditionsPattern{
62 {
63 Type: v1.DisruptionTarget,
64 Status: v1.ConditionTrue,
65 },
66 {
67 Type: v1.PodConditionType("MemoryLimitExceeded"),
68 Status: v1.ConditionFalse,
69 },
70 {
71 Type: v1.PodConditionType("DiskLimitExceeded"),
72 },
73 },
74 },
75 {
76 Action: batchv1.PodFailurePolicyActionFailJob,
77 OnExitCodes: &batchv1.PodFailurePolicyOnExitCodesRequirement{
78 Operator: batchv1.PodFailurePolicyOnExitCodesOpIn,
79 Values: []int32{1},
80 },
81 },
82 {
83 Action: batchv1.PodFailurePolicyActionFailJob,
84 OnPodConditions: []batchv1.PodFailurePolicyOnPodConditionsPattern{
85 {
86 Type: v1.DisruptionTarget,
87 },
88 },
89 },
90 },
91 },
92 },
93 },
94 expected: &batchv1.Job{
95 Spec: batchv1.JobSpec{
96 Completions: pointer.Int32(1),
97 Parallelism: pointer.Int32(1),
98 BackoffLimit: pointer.Int32(6),
99 CompletionMode: completionModePtr(batchv1.NonIndexedCompletion),
100 Suspend: pointer.Bool(false),
101 ManualSelector: pointer.Bool(false),
102 PodFailurePolicy: &batchv1.PodFailurePolicy{
103 Rules: []batchv1.PodFailurePolicyRule{
104 {
105 Action: batchv1.PodFailurePolicyActionFailJob,
106 OnPodConditions: []batchv1.PodFailurePolicyOnPodConditionsPattern{
107 {
108 Type: v1.DisruptionTarget,
109 Status: v1.ConditionTrue,
110 },
111 {
112 Type: v1.PodConditionType("MemoryLimitExceeded"),
113 Status: v1.ConditionFalse,
114 },
115 {
116 Type: v1.PodConditionType("DiskLimitExceeded"),
117 Status: v1.ConditionTrue,
118 },
119 },
120 },
121 {
122 Action: batchv1.PodFailurePolicyActionFailJob,
123 OnExitCodes: &batchv1.PodFailurePolicyOnExitCodesRequirement{
124 Operator: batchv1.PodFailurePolicyOnExitCodesOpIn,
125 Values: []int32{1},
126 },
127 },
128 {
129 Action: batchv1.PodFailurePolicyActionFailJob,
130 OnPodConditions: []batchv1.PodFailurePolicyOnPodConditionsPattern{
131 {
132 Type: v1.DisruptionTarget,
133 Status: v1.ConditionTrue,
134 },
135 },
136 },
137 },
138 },
139 },
140 },
141 expectLabels: true,
142 },
143 "Pod failure policy and defaulting for pod replacement policy": {
144 original: &batchv1.Job{
145 Spec: batchv1.JobSpec{
146 Template: v1.PodTemplateSpec{
147 ObjectMeta: metav1.ObjectMeta{Labels: defaultLabels},
148 },
149 PodFailurePolicy: &batchv1.PodFailurePolicy{
150 Rules: []batchv1.PodFailurePolicyRule{
151 {
152 Action: batchv1.PodFailurePolicyActionFailJob,
153 OnExitCodes: &batchv1.PodFailurePolicyOnExitCodesRequirement{
154 Operator: batchv1.PodFailurePolicyOnExitCodesOpIn,
155 Values: []int32{1},
156 },
157 },
158 },
159 },
160 },
161 },
162 expected: &batchv1.Job{
163 Spec: batchv1.JobSpec{
164 Completions: pointer.Int32(1),
165 Parallelism: pointer.Int32(1),
166 BackoffLimit: pointer.Int32(6),
167 CompletionMode: completionModePtr(batchv1.NonIndexedCompletion),
168 Suspend: pointer.Bool(false),
169 PodReplacementPolicy: podReplacementPtr(batchv1.Failed),
170 ManualSelector: pointer.Bool(false),
171 PodFailurePolicy: &batchv1.PodFailurePolicy{
172 Rules: []batchv1.PodFailurePolicyRule{
173 {
174 Action: batchv1.PodFailurePolicyActionFailJob,
175 OnExitCodes: &batchv1.PodFailurePolicyOnExitCodesRequirement{
176 Operator: batchv1.PodFailurePolicyOnExitCodesOpIn,
177 Values: []int32{1},
178 },
179 },
180 },
181 },
182 },
183 },
184 expectLabels: true,
185 enablePodReplacementPolicy: true,
186 },
187 "All unspecified and podReplacementPolicyEnabled -> sets all to default values": {
188 original: &batchv1.Job{
189 Spec: batchv1.JobSpec{
190 Template: v1.PodTemplateSpec{
191 ObjectMeta: metav1.ObjectMeta{Labels: defaultLabels},
192 },
193 },
194 },
195 expected: &batchv1.Job{
196 Spec: batchv1.JobSpec{
197 Completions: pointer.Int32(1),
198 Parallelism: pointer.Int32(1),
199 BackoffLimit: pointer.Int32(6),
200 CompletionMode: completionModePtr(batchv1.NonIndexedCompletion),
201 Suspend: pointer.Bool(false),
202 PodReplacementPolicy: podReplacementPtr(batchv1.TerminatingOrFailed),
203 ManualSelector: pointer.Bool(false),
204 },
205 },
206 expectLabels: true,
207 enablePodReplacementPolicy: true,
208 },
209 "All unspecified -> sets all to default values": {
210 original: &batchv1.Job{
211 Spec: batchv1.JobSpec{
212 Template: v1.PodTemplateSpec{
213 ObjectMeta: metav1.ObjectMeta{Labels: defaultLabels},
214 },
215 },
216 },
217 expected: &batchv1.Job{
218 Spec: batchv1.JobSpec{
219 Completions: pointer.Int32(1),
220 Parallelism: pointer.Int32(1),
221 BackoffLimit: pointer.Int32(6),
222 CompletionMode: completionModePtr(batchv1.NonIndexedCompletion),
223 Suspend: pointer.Bool(false),
224 ManualSelector: pointer.Bool(false),
225 },
226 },
227 expectLabels: true,
228 },
229 "All unspecified, suspend job enabled -> sets all to default values": {
230 original: &batchv1.Job{
231 Spec: batchv1.JobSpec{
232 Template: v1.PodTemplateSpec{
233 ObjectMeta: metav1.ObjectMeta{Labels: defaultLabels},
234 },
235 },
236 },
237 expected: &batchv1.Job{
238 Spec: batchv1.JobSpec{
239 Completions: pointer.Int32(1),
240 Parallelism: pointer.Int32(1),
241 BackoffLimit: pointer.Int32(6),
242 CompletionMode: completionModePtr(batchv1.NonIndexedCompletion),
243 Suspend: pointer.Bool(false),
244 ManualSelector: pointer.Bool(false),
245 },
246 },
247 expectLabels: true,
248 },
249 "suspend set, everything else is defaulted": {
250 original: &batchv1.Job{
251 Spec: batchv1.JobSpec{
252 Suspend: pointer.Bool(true),
253 Template: v1.PodTemplateSpec{
254 ObjectMeta: metav1.ObjectMeta{Labels: defaultLabels},
255 },
256 },
257 },
258 expected: &batchv1.Job{
259 Spec: batchv1.JobSpec{
260 Completions: pointer.Int32(1),
261 Parallelism: pointer.Int32(1),
262 BackoffLimit: pointer.Int32(6),
263 CompletionMode: completionModePtr(batchv1.NonIndexedCompletion),
264 Suspend: pointer.Bool(true),
265 ManualSelector: pointer.Bool(false),
266 },
267 },
268 expectLabels: true,
269 },
270 "All unspecified -> all pointers are defaulted and no default labels": {
271 original: &batchv1.Job{
272 ObjectMeta: metav1.ObjectMeta{
273 Labels: map[string]string{"mylabel": "myvalue"},
274 },
275 Spec: batchv1.JobSpec{
276 Template: v1.PodTemplateSpec{
277 ObjectMeta: metav1.ObjectMeta{Labels: defaultLabels},
278 },
279 },
280 },
281 expected: &batchv1.Job{
282 Spec: batchv1.JobSpec{
283 Completions: pointer.Int32(1),
284 Parallelism: pointer.Int32(1),
285 BackoffLimit: pointer.Int32(6),
286 CompletionMode: completionModePtr(batchv1.NonIndexedCompletion),
287 Suspend: pointer.Bool(false),
288 ManualSelector: pointer.Bool(false),
289 },
290 },
291 },
292 "WQ: Parallelism explicitly 0 and completions unset -> BackoffLimit is defaulted": {
293 original: &batchv1.Job{
294 Spec: batchv1.JobSpec{
295 Parallelism: pointer.Int32(0),
296 Template: v1.PodTemplateSpec{
297 ObjectMeta: metav1.ObjectMeta{Labels: defaultLabels},
298 },
299 },
300 },
301 expected: &batchv1.Job{
302 Spec: batchv1.JobSpec{
303 Parallelism: pointer.Int32(0),
304 BackoffLimit: pointer.Int32(6),
305 CompletionMode: completionModePtr(batchv1.NonIndexedCompletion),
306 Suspend: pointer.Bool(false),
307 ManualSelector: pointer.Bool(false),
308 },
309 },
310 expectLabels: true,
311 },
312 "WQ: Parallelism explicitly 2 and completions unset -> BackoffLimit is defaulted": {
313 original: &batchv1.Job{
314 Spec: batchv1.JobSpec{
315 Parallelism: pointer.Int32(2),
316 Template: v1.PodTemplateSpec{
317 ObjectMeta: metav1.ObjectMeta{Labels: defaultLabels},
318 },
319 },
320 },
321 expected: &batchv1.Job{
322 Spec: batchv1.JobSpec{
323 Parallelism: pointer.Int32(2),
324 BackoffLimit: pointer.Int32(6),
325 CompletionMode: completionModePtr(batchv1.NonIndexedCompletion),
326 Suspend: pointer.Bool(false),
327 ManualSelector: pointer.Bool(false),
328 },
329 },
330 expectLabels: true,
331 },
332 "Completions explicitly 2 and others unset -> parallelism and BackoffLimit are defaulted": {
333 original: &batchv1.Job{
334 Spec: batchv1.JobSpec{
335 Completions: pointer.Int32(2),
336 Template: v1.PodTemplateSpec{
337 ObjectMeta: metav1.ObjectMeta{Labels: defaultLabels},
338 },
339 },
340 },
341 expected: &batchv1.Job{
342 Spec: batchv1.JobSpec{
343 Completions: pointer.Int32(2),
344 Parallelism: pointer.Int32(1),
345 BackoffLimit: pointer.Int32(6),
346 CompletionMode: completionModePtr(batchv1.NonIndexedCompletion),
347 Suspend: pointer.Bool(false),
348 ManualSelector: pointer.Bool(false),
349 },
350 },
351 expectLabels: true,
352 },
353 "BackoffLimit explicitly 5 and others unset -> parallelism and completions are defaulted": {
354 original: &batchv1.Job{
355 Spec: batchv1.JobSpec{
356 BackoffLimit: pointer.Int32(5),
357 Template: v1.PodTemplateSpec{
358 ObjectMeta: metav1.ObjectMeta{Labels: defaultLabels},
359 },
360 },
361 },
362 expected: &batchv1.Job{
363 Spec: batchv1.JobSpec{
364 Completions: pointer.Int32(1),
365 Parallelism: pointer.Int32(1),
366 BackoffLimit: pointer.Int32(5),
367 CompletionMode: completionModePtr(batchv1.NonIndexedCompletion),
368 Suspend: pointer.Bool(false),
369 ManualSelector: pointer.Bool(false),
370 },
371 },
372 expectLabels: true,
373 },
374 "All set -> no change": {
375 original: &batchv1.Job{
376 Spec: batchv1.JobSpec{
377 Completions: pointer.Int32(8),
378 Parallelism: pointer.Int32(9),
379 BackoffLimit: pointer.Int32(10),
380 CompletionMode: completionModePtr(batchv1.NonIndexedCompletion),
381 Suspend: pointer.Bool(false),
382 PodReplacementPolicy: podReplacementPtr(batchv1.TerminatingOrFailed),
383 ManualSelector: pointer.Bool(false),
384 Template: v1.PodTemplateSpec{
385 ObjectMeta: metav1.ObjectMeta{Labels: defaultLabels},
386 },
387 },
388 },
389 expected: &batchv1.Job{
390 Spec: batchv1.JobSpec{
391 Completions: pointer.Int32(8),
392 Parallelism: pointer.Int32(9),
393 BackoffLimit: pointer.Int32(10),
394 CompletionMode: completionModePtr(batchv1.NonIndexedCompletion),
395 Suspend: pointer.Bool(false),
396 PodReplacementPolicy: podReplacementPtr(batchv1.TerminatingOrFailed),
397 ManualSelector: pointer.Bool(false),
398 Template: v1.PodTemplateSpec{
399 ObjectMeta: metav1.ObjectMeta{Labels: defaultLabels},
400 },
401 },
402 },
403 expectLabels: true,
404 },
405 "All set, flipped -> no change": {
406 original: &batchv1.Job{
407 Spec: batchv1.JobSpec{
408 Completions: pointer.Int32(11),
409 Parallelism: pointer.Int32(10),
410 BackoffLimit: pointer.Int32(9),
411 CompletionMode: completionModePtr(batchv1.IndexedCompletion),
412 Suspend: pointer.Bool(true),
413 PodReplacementPolicy: podReplacementPtr(batchv1.Failed),
414 ManualSelector: pointer.Bool(true),
415 Template: v1.PodTemplateSpec{
416 ObjectMeta: metav1.ObjectMeta{Labels: defaultLabels},
417 },
418 },
419 },
420 expected: &batchv1.Job{
421 Spec: batchv1.JobSpec{
422 Completions: pointer.Int32(11),
423 Parallelism: pointer.Int32(10),
424 BackoffLimit: pointer.Int32(9),
425 CompletionMode: completionModePtr(batchv1.IndexedCompletion),
426 Suspend: pointer.Bool(true),
427 PodReplacementPolicy: podReplacementPtr(batchv1.Failed),
428 ManualSelector: pointer.Bool(true),
429 },
430 },
431 expectLabels: true,
432 },
433 "BackoffLimitPerIndex specified, but no BackoffLimit -> default BackoffLimit to max int32": {
434 original: &batchv1.Job{
435 Spec: batchv1.JobSpec{
436 Completions: pointer.Int32(11),
437 Parallelism: pointer.Int32(10),
438 BackoffLimitPerIndex: pointer.Int32(1),
439 CompletionMode: completionModePtr(batchv1.IndexedCompletion),
440 Template: validPodTemplateSpec,
441 Suspend: pointer.Bool(true),
442 ManualSelector: pointer.Bool(false),
443 },
444 },
445 expected: &batchv1.Job{
446 Spec: batchv1.JobSpec{
447 Completions: pointer.Int32(11),
448 Parallelism: pointer.Int32(10),
449 BackoffLimit: pointer.Int32(math.MaxInt32),
450 BackoffLimitPerIndex: pointer.Int32(1),
451 CompletionMode: completionModePtr(batchv1.IndexedCompletion),
452 Template: validPodTemplateSpec,
453 Suspend: pointer.Bool(true),
454 ManualSelector: pointer.Bool(false),
455 },
456 },
457 expectLabels: true,
458 },
459 "BackoffLimitPerIndex and BackoffLimit specified -> no change": {
460 original: &batchv1.Job{
461 Spec: batchv1.JobSpec{
462 Completions: pointer.Int32(11),
463 Parallelism: pointer.Int32(10),
464 BackoffLimit: pointer.Int32(3),
465 BackoffLimitPerIndex: pointer.Int32(1),
466 CompletionMode: completionModePtr(batchv1.IndexedCompletion),
467 Template: validPodTemplateSpec,
468 Suspend: pointer.Bool(true),
469 ManualSelector: pointer.Bool(true),
470 },
471 },
472 expected: &batchv1.Job{
473 Spec: batchv1.JobSpec{
474 Completions: pointer.Int32(11),
475 Parallelism: pointer.Int32(10),
476 BackoffLimit: pointer.Int32(3),
477 BackoffLimitPerIndex: pointer.Int32(1),
478 CompletionMode: completionModePtr(batchv1.IndexedCompletion),
479 Template: validPodTemplateSpec,
480 Suspend: pointer.Bool(true),
481 ManualSelector: pointer.Bool(true),
482 },
483 },
484 expectLabels: true,
485 },
486 }
487
488 for name, test := range tests {
489 t.Run(name, func(t *testing.T) {
490 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.JobPodReplacementPolicy, test.enablePodReplacementPolicy)()
491 original := test.original
492 expected := test.expected
493 obj2 := roundTrip(t, runtime.Object(original))
494 actual, ok := obj2.(*batchv1.Job)
495 if !ok {
496 t.Fatalf("Unexpected object: %v", actual)
497 }
498
499 if diff := cmp.Diff(expected.Spec.Suspend, actual.Spec.Suspend); diff != "" {
500 t.Errorf(".spec.suspend does not match; -want,+got:\n%s", diff)
501 }
502 validateDefaultInt32(t, "Completions", actual.Spec.Completions, expected.Spec.Completions)
503 validateDefaultInt32(t, "Parallelism", actual.Spec.Parallelism, expected.Spec.Parallelism)
504 validateDefaultInt32(t, "BackoffLimit", actual.Spec.BackoffLimit, expected.Spec.BackoffLimit)
505
506 if diff := cmp.Diff(expected.Spec.PodFailurePolicy, actual.Spec.PodFailurePolicy); diff != "" {
507 t.Errorf("unexpected diff in errors (-want, +got):\n%s", diff)
508 }
509 if test.expectLabels != reflect.DeepEqual(actual.Labels, actual.Spec.Template.Labels) {
510 if test.expectLabels {
511 t.Errorf("Expected labels: %v, got: %v", actual.Spec.Template.Labels, actual.Labels)
512 } else {
513 t.Errorf("Unexpected equality: %v", actual.Labels)
514 }
515 }
516 if diff := cmp.Diff(expected.Spec.CompletionMode, actual.Spec.CompletionMode); diff != "" {
517 t.Errorf("Unexpected CompletionMode (-want,+got):\n%s", diff)
518 }
519 if diff := cmp.Diff(expected.Spec.PodReplacementPolicy, actual.Spec.PodReplacementPolicy); diff != "" {
520 t.Errorf("Unexpected PodReplacementPolicy (-want,+got):\n%s", diff)
521 }
522 if diff := cmp.Diff(expected.Spec.ManualSelector, actual.Spec.ManualSelector); diff != "" {
523 t.Errorf("Unexpected ManualSelector (-want,+got):\n%s", diff)
524 }
525 })
526 }
527 }
528
529 func validateDefaultInt32(t *testing.T, field string, actual *int32, expected *int32) {
530 if (actual == nil) != (expected == nil) {
531 t.Errorf("Got different *%s than expected: %v %v", field, actual, expected)
532 }
533 if actual != nil && expected != nil {
534 if *actual != *expected {
535 t.Errorf("Got different %s than expected: %d %d", field, *actual, *expected)
536 }
537 }
538 }
539
540 func roundTrip(t *testing.T, obj runtime.Object) runtime.Object {
541 data, err := runtime.Encode(legacyscheme.Codecs.LegacyCodec(SchemeGroupVersion), obj)
542 if err != nil {
543 t.Errorf("%v\n %#v", err, obj)
544 return nil
545 }
546 obj2, err := runtime.Decode(legacyscheme.Codecs.UniversalDecoder(), data)
547 if err != nil {
548 t.Errorf("%v\nData: %s\nSource: %#v", err, string(data), obj)
549 return nil
550 }
551 obj3 := reflect.New(reflect.TypeOf(obj).Elem()).Interface().(runtime.Object)
552 err = legacyscheme.Scheme.Convert(obj2, obj3, nil)
553 if err != nil {
554 t.Errorf("%v\nSource: %#v", err, obj2)
555 return nil
556 }
557 return obj3
558 }
559
560 func TestSetDefaultCronJob(t *testing.T) {
561 tests := map[string]struct {
562 original *batchv1.CronJob
563 expected *batchv1.CronJob
564 }{
565 "empty batchv1.CronJob should default batchv1.ConcurrencyPolicy and Suspend": {
566 original: &batchv1.CronJob{},
567 expected: &batchv1.CronJob{
568 Spec: batchv1.CronJobSpec{
569 ConcurrencyPolicy: batchv1.AllowConcurrent,
570 Suspend: pointer.Bool(false),
571 SuccessfulJobsHistoryLimit: pointer.Int32(3),
572 FailedJobsHistoryLimit: pointer.Int32(1),
573 },
574 },
575 },
576 "set fields should not be defaulted": {
577 original: &batchv1.CronJob{
578 Spec: batchv1.CronJobSpec{
579 ConcurrencyPolicy: batchv1.ForbidConcurrent,
580 Suspend: pointer.Bool(true),
581 SuccessfulJobsHistoryLimit: pointer.Int32(5),
582 FailedJobsHistoryLimit: pointer.Int32(5),
583 },
584 },
585 expected: &batchv1.CronJob{
586 Spec: batchv1.CronJobSpec{
587 ConcurrencyPolicy: batchv1.ForbidConcurrent,
588 Suspend: pointer.Bool(true),
589 SuccessfulJobsHistoryLimit: pointer.Int32(5),
590 FailedJobsHistoryLimit: pointer.Int32(5),
591 },
592 },
593 },
594 }
595
596 for name, test := range tests {
597 original := test.original
598 expected := test.expected
599 obj2 := roundTrip(t, runtime.Object(original))
600 actual, ok := obj2.(*batchv1.CronJob)
601 if !ok {
602 t.Errorf("%s: unexpected object: %v", name, actual)
603 t.FailNow()
604 }
605 if actual.Spec.ConcurrencyPolicy != expected.Spec.ConcurrencyPolicy {
606 t.Errorf("%s: got different concurrencyPolicy than expected: %v %v", name, actual.Spec.ConcurrencyPolicy, expected.Spec.ConcurrencyPolicy)
607 }
608 if *actual.Spec.Suspend != *expected.Spec.Suspend {
609 t.Errorf("%s: got different suspend than expected: %v %v", name, *actual.Spec.Suspend, *expected.Spec.Suspend)
610 }
611 if *actual.Spec.SuccessfulJobsHistoryLimit != *expected.Spec.SuccessfulJobsHistoryLimit {
612 t.Errorf("%s: got different successfulJobsHistoryLimit than expected: %v %v", name, *actual.Spec.SuccessfulJobsHistoryLimit, *expected.Spec.SuccessfulJobsHistoryLimit)
613 }
614 if *actual.Spec.FailedJobsHistoryLimit != *expected.Spec.FailedJobsHistoryLimit {
615 t.Errorf("%s: got different failedJobsHistoryLimit than expected: %v %v", name, *actual.Spec.FailedJobsHistoryLimit, *expected.Spec.FailedJobsHistoryLimit)
616 }
617 }
618 }
619
620 func completionModePtr(m batchv1.CompletionMode) *batchv1.CompletionMode {
621 return &m
622 }
623
624 func podReplacementPtr(m batchv1.PodReplacementPolicy) *batchv1.PodReplacementPolicy {
625 return &m
626 }
627
View as plain text