Merge pull request #89669 from yangkev/yangkev-make-tests-table-driven-patch

allow running cronjob controller unit tests individually by name
This commit is contained in:
Kubernetes Prow Robot 2020-04-15 13:14:58 -07:00 committed by GitHub
commit b86297c837
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -250,113 +250,117 @@ func TestSyncOne_RunOrNot(t *testing.T) {
"prev ran but done, long overdue, past short deadline, F": {f, F, onTheHour, shortDead, T, F, weekAfterTheHour(), T, F, 1, 0}, "prev ran but done, long overdue, past short deadline, F": {f, F, onTheHour, shortDead, T, F, weekAfterTheHour(), T, F, 1, 0},
} }
for name, tc := range testCases { for name, tc := range testCases {
sj := cronJob() name := name
sj.Spec.ConcurrencyPolicy = tc.concurrencyPolicy tc := tc
sj.Spec.Suspend = &tc.suspend t.Run(name, func(t *testing.T) {
sj.Spec.Schedule = tc.schedule sj := cronJob()
if tc.deadline != noDead { sj.Spec.ConcurrencyPolicy = tc.concurrencyPolicy
sj.Spec.StartingDeadlineSeconds = &tc.deadline sj.Spec.Suspend = &tc.suspend
} sj.Spec.Schedule = tc.schedule
if tc.deadline != noDead {
var ( sj.Spec.StartingDeadlineSeconds = &tc.deadline
job *batchv1.Job
err error
)
js := []batchv1.Job{}
if tc.ranPreviously {
sj.ObjectMeta.CreationTimestamp = metav1.Time{Time: justBeforeThePriorHour()}
sj.Status.LastScheduleTime = &metav1.Time{Time: justAfterThePriorHour()}
job, err = getJobFromTemplate(&sj, sj.Status.LastScheduleTime.Time)
if err != nil {
t.Fatalf("%s: unexpected error creating a job from template: %v", name, err)
} }
job.UID = "1234"
job.Namespace = ""
if tc.stillActive {
sj.Status.Active = []v1.ObjectReference{{UID: job.UID}}
js = append(js, *job)
}
} else {
sj.ObjectMeta.CreationTimestamp = metav1.Time{Time: justBeforeTheHour()}
if tc.stillActive {
t.Errorf("%s: test setup error: this case makes no sense", name)
}
}
jc := &fakeJobControl{Job: job} var (
sjc := &fakeSJControl{} job *batchv1.Job
recorder := record.NewFakeRecorder(10) err error
)
syncOne(&sj, js, tc.now, jc, sjc, recorder) js := []batchv1.Job{}
expectedCreates := 0 if tc.ranPreviously {
if tc.expectCreate { sj.ObjectMeta.CreationTimestamp = metav1.Time{Time: justBeforeThePriorHour()}
expectedCreates = 1 sj.Status.LastScheduleTime = &metav1.Time{Time: justAfterThePriorHour()}
} job, err = getJobFromTemplate(&sj, sj.Status.LastScheduleTime.Time)
if len(jc.Jobs) != expectedCreates { if err != nil {
t.Errorf("%s: expected %d job started, actually %v", name, expectedCreates, len(jc.Jobs)) t.Fatalf("%s: unexpected error creating a job from template: %v", name, err)
} }
for i := range jc.Jobs { job.UID = "1234"
job := &jc.Jobs[i] job.Namespace = ""
controllerRef := metav1.GetControllerOf(job) if tc.stillActive {
if controllerRef == nil { sj.Status.Active = []v1.ObjectReference{{UID: job.UID}}
t.Errorf("%s: expected job to have ControllerRef: %#v", name, job) js = append(js, *job)
}
} else { } else {
if got, want := controllerRef.APIVersion, "batch/v1beta1"; got != want { sj.ObjectMeta.CreationTimestamp = metav1.Time{Time: justBeforeTheHour()}
t.Errorf("%s: controllerRef.APIVersion = %q, want %q", name, got, want) if tc.stillActive {
} t.Errorf("%s: test setup error: this case makes no sense", name)
if got, want := controllerRef.Kind, "CronJob"; got != want {
t.Errorf("%s: controllerRef.Kind = %q, want %q", name, got, want)
}
if got, want := controllerRef.Name, sj.Name; got != want {
t.Errorf("%s: controllerRef.Name = %q, want %q", name, got, want)
}
if got, want := controllerRef.UID, sj.UID; got != want {
t.Errorf("%s: controllerRef.UID = %q, want %q", name, got, want)
}
if controllerRef.Controller == nil || *controllerRef.Controller != true {
t.Errorf("%s: controllerRef.Controller is not set to true", name)
} }
} }
}
expectedDeletes := 0 jc := &fakeJobControl{Job: job}
if tc.expectDelete { sjc := &fakeSJControl{}
expectedDeletes = 1 recorder := record.NewFakeRecorder(10)
}
if len(jc.DeleteJobName) != expectedDeletes {
t.Errorf("%s: expected %d job deleted, actually %v", name, expectedDeletes, len(jc.DeleteJobName))
}
// Status update happens once when ranging through job list, and another one if create jobs. syncOne(&sj, js, tc.now, jc, sjc, recorder)
expectUpdates := 1 expectedCreates := 0
expectedEvents := 0 if tc.expectCreate {
if tc.expectCreate { expectedCreates = 1
expectedEvents++ }
expectUpdates++ if len(jc.Jobs) != expectedCreates {
} t.Errorf("%s: expected %d job started, actually %v", name, expectedCreates, len(jc.Jobs))
if tc.expectDelete { }
expectedEvents++ for i := range jc.Jobs {
} job := &jc.Jobs[i]
expectedEvents += tc.expectedWarnings controllerRef := metav1.GetControllerOf(job)
if controllerRef == nil {
if len(recorder.Events) != expectedEvents { t.Errorf("%s: expected job to have ControllerRef: %#v", name, job)
t.Errorf("%s: expected %d event, actually %v", name, expectedEvents, len(recorder.Events)) } else {
} if got, want := controllerRef.APIVersion, "batch/v1beta1"; got != want {
t.Errorf("%s: controllerRef.APIVersion = %q, want %q", name, got, want)
numWarnings := 0 }
for i := 1; i <= len(recorder.Events); i++ { if got, want := controllerRef.Kind, "CronJob"; got != want {
e := <-recorder.Events t.Errorf("%s: controllerRef.Kind = %q, want %q", name, got, want)
if strings.HasPrefix(e, v1.EventTypeWarning) { }
numWarnings++ if got, want := controllerRef.Name, sj.Name; got != want {
t.Errorf("%s: controllerRef.Name = %q, want %q", name, got, want)
}
if got, want := controllerRef.UID, sj.UID; got != want {
t.Errorf("%s: controllerRef.UID = %q, want %q", name, got, want)
}
if controllerRef.Controller == nil || *controllerRef.Controller != true {
t.Errorf("%s: controllerRef.Controller is not set to true", name)
}
}
} }
}
if numWarnings != tc.expectedWarnings {
t.Errorf("%s: expected %d warnings, actually %v", name, tc.expectedWarnings, numWarnings)
}
if tc.expectActive != len(sjc.Updates[expectUpdates-1].Status.Active) { expectedDeletes := 0
t.Errorf("%s: expected Active size %d, got %d", name, tc.expectActive, len(sjc.Updates[expectUpdates-1].Status.Active)) if tc.expectDelete {
} expectedDeletes = 1
}
if len(jc.DeleteJobName) != expectedDeletes {
t.Errorf("%s: expected %d job deleted, actually %v", name, expectedDeletes, len(jc.DeleteJobName))
}
// Status update happens once when ranging through job list, and another one if create jobs.
expectUpdates := 1
expectedEvents := 0
if tc.expectCreate {
expectedEvents++
expectUpdates++
}
if tc.expectDelete {
expectedEvents++
}
expectedEvents += tc.expectedWarnings
if len(recorder.Events) != expectedEvents {
t.Errorf("%s: expected %d event, actually %v", name, expectedEvents, len(recorder.Events))
}
numWarnings := 0
for i := 1; i <= len(recorder.Events); i++ {
e := <-recorder.Events
if strings.HasPrefix(e, v1.EventTypeWarning) {
numWarnings++
}
}
if numWarnings != tc.expectedWarnings {
t.Errorf("%s: expected %d warnings, actually %v", name, tc.expectedWarnings, numWarnings)
}
if tc.expectActive != len(sjc.Updates[expectUpdates-1].Status.Active) {
t.Errorf("%s: expected Active size %d, got %d", name, tc.expectActive, len(sjc.Updates[expectUpdates-1].Status.Active))
}
})
} }
} }
@ -486,103 +490,107 @@ func TestCleanupFinishedJobs_DeleteOrNot(t *testing.T) {
} }
for name, tc := range testCases { for name, tc := range testCases {
sj := cronJob() name := name
suspend := false tc := tc
sj.Spec.ConcurrencyPolicy = f t.Run(name, func(t *testing.T) {
sj.Spec.Suspend = &suspend sj := cronJob()
sj.Spec.Schedule = onTheHour suspend := false
sj.Spec.ConcurrencyPolicy = f
sj.Spec.Suspend = &suspend
sj.Spec.Schedule = onTheHour
sj.Spec.SuccessfulJobsHistoryLimit = tc.successfulJobsHistoryLimit sj.Spec.SuccessfulJobsHistoryLimit = tc.successfulJobsHistoryLimit
sj.Spec.FailedJobsHistoryLimit = tc.failedJobsHistoryLimit sj.Spec.FailedJobsHistoryLimit = tc.failedJobsHistoryLimit
var ( var (
job *batchv1.Job job *batchv1.Job
err error err error
) )
// Set consistent timestamps for the CronJob // Set consistent timestamps for the CronJob
if len(tc.jobSpecs) != 0 { if len(tc.jobSpecs) != 0 {
firstTime := startTimeStringToTime(tc.jobSpecs[0].StartTime) firstTime := startTimeStringToTime(tc.jobSpecs[0].StartTime)
lastTime := startTimeStringToTime(tc.jobSpecs[len(tc.jobSpecs)-1].StartTime) lastTime := startTimeStringToTime(tc.jobSpecs[len(tc.jobSpecs)-1].StartTime)
sj.ObjectMeta.CreationTimestamp = metav1.Time{Time: firstTime} sj.ObjectMeta.CreationTimestamp = metav1.Time{Time: firstTime}
sj.Status.LastScheduleTime = &metav1.Time{Time: lastTime} sj.Status.LastScheduleTime = &metav1.Time{Time: lastTime}
} else { } else {
sj.ObjectMeta.CreationTimestamp = metav1.Time{Time: justBeforeTheHour()} sj.ObjectMeta.CreationTimestamp = metav1.Time{Time: justBeforeTheHour()}
}
// Create jobs
js := []batchv1.Job{}
jobsToDelete := sets.NewString()
sj.Status.Active = []v1.ObjectReference{}
for i, spec := range tc.jobSpecs {
job, err = getJobFromTemplate(&sj, startTimeStringToTime(spec.StartTime))
if err != nil {
t.Fatalf("%s: unexpected error creating a job from template: %v", name, err)
} }
job.UID = types.UID(strconv.Itoa(i)) // Create jobs
job.Namespace = "" js := []batchv1.Job{}
jobsToDelete := sets.NewString()
sj.Status.Active = []v1.ObjectReference{}
if spec.IsFinished { for i, spec := range tc.jobSpecs {
var conditionType batchv1.JobConditionType job, err = getJobFromTemplate(&sj, startTimeStringToTime(spec.StartTime))
if spec.IsSuccessful { if err != nil {
conditionType = batchv1.JobComplete t.Fatalf("%s: unexpected error creating a job from template: %v", name, err)
} else {
conditionType = batchv1.JobFailed
} }
condition := batchv1.JobCondition{Type: conditionType, Status: v1.ConditionTrue}
job.Status.Conditions = append(job.Status.Conditions, condition)
if spec.IsStillInActiveList { job.UID = types.UID(strconv.Itoa(i))
job.Namespace = ""
if spec.IsFinished {
var conditionType batchv1.JobConditionType
if spec.IsSuccessful {
conditionType = batchv1.JobComplete
} else {
conditionType = batchv1.JobFailed
}
condition := batchv1.JobCondition{Type: conditionType, Status: v1.ConditionTrue}
job.Status.Conditions = append(job.Status.Conditions, condition)
if spec.IsStillInActiveList {
sj.Status.Active = append(sj.Status.Active, v1.ObjectReference{UID: job.UID})
}
} else {
if spec.IsSuccessful || spec.IsStillInActiveList {
t.Errorf("%s: test setup error: this case makes no sense", name)
}
sj.Status.Active = append(sj.Status.Active, v1.ObjectReference{UID: job.UID}) sj.Status.Active = append(sj.Status.Active, v1.ObjectReference{UID: job.UID})
} }
} else {
if spec.IsSuccessful || spec.IsStillInActiveList { js = append(js, *job)
t.Errorf("%s: test setup error: this case makes no sense", name) if spec.ExpectDelete {
jobsToDelete.Insert(job.Name)
} }
sj.Status.Active = append(sj.Status.Active, v1.ObjectReference{UID: job.UID})
} }
js = append(js, *job) jc := &fakeJobControl{Job: job}
if spec.ExpectDelete { sjc := &fakeSJControl{}
jobsToDelete.Insert(job.Name) recorder := record.NewFakeRecorder(10)
cleanupFinishedJobs(&sj, js, jc, sjc, recorder)
// Check we have actually deleted the correct jobs
if len(jc.DeleteJobName) != len(jobsToDelete) {
t.Errorf("%s: expected %d job deleted, actually %d", name, len(jobsToDelete), len(jc.DeleteJobName))
} else {
jcDeleteJobName := sets.NewString(jc.DeleteJobName...)
if !jcDeleteJobName.Equal(jobsToDelete) {
t.Errorf("%s: expected jobs: %v deleted, actually: %v deleted", name, jobsToDelete, jcDeleteJobName)
}
} }
}
jc := &fakeJobControl{Job: job} // Check for events
sjc := &fakeSJControl{} expectedEvents := len(jobsToDelete)
recorder := record.NewFakeRecorder(10) if name == "failed list pod err" {
expectedEvents = len(tc.jobSpecs)
cleanupFinishedJobs(&sj, js, jc, sjc, recorder) }
if len(recorder.Events) != expectedEvents {
// Check we have actually deleted the correct jobs t.Errorf("%s: expected %d event, actually %v", name, expectedEvents, len(recorder.Events))
if len(jc.DeleteJobName) != len(jobsToDelete) {
t.Errorf("%s: expected %d job deleted, actually %d", name, len(jobsToDelete), len(jc.DeleteJobName))
} else {
jcDeleteJobName := sets.NewString(jc.DeleteJobName...)
if !jcDeleteJobName.Equal(jobsToDelete) {
t.Errorf("%s: expected jobs: %v deleted, actually: %v deleted", name, jobsToDelete, jcDeleteJobName)
} }
}
// Check for events // Check for jobs still in active list
expectedEvents := len(jobsToDelete) numActive := 0
if name == "failed list pod err" { if len(sjc.Updates) != 0 {
expectedEvents = len(tc.jobSpecs) numActive = len(sjc.Updates[len(sjc.Updates)-1].Status.Active)
} }
if len(recorder.Events) != expectedEvents { if tc.expectActive != numActive {
t.Errorf("%s: expected %d event, actually %v", name, expectedEvents, len(recorder.Events)) t.Errorf("%s: expected Active size %d, got %d", name, tc.expectActive, numActive)
} }
})
// Check for jobs still in active list
numActive := 0
if len(sjc.Updates) != 0 {
numActive = len(sjc.Updates[len(sjc.Updates)-1].Status.Active)
}
if tc.expectActive != numActive {
t.Errorf("%s: expected Active size %d, got %d", name, tc.expectActive, numActive)
}
} }
} }
@ -669,97 +677,101 @@ func TestSyncOne_Status(t *testing.T) {
} }
for name, tc := range testCases { for name, tc := range testCases {
// Setup the test name := name
sj := cronJob() tc := tc
sj.Spec.ConcurrencyPolicy = tc.concurrencyPolicy t.Run(name, func(t *testing.T) {
sj.Spec.Suspend = &tc.suspend // Setup the test
sj.Spec.Schedule = tc.schedule sj := cronJob()
if tc.deadline != noDead { sj.Spec.ConcurrencyPolicy = tc.concurrencyPolicy
sj.Spec.StartingDeadlineSeconds = &tc.deadline sj.Spec.Suspend = &tc.suspend
} sj.Spec.Schedule = tc.schedule
if tc.ranPreviously { if tc.deadline != noDead {
sj.ObjectMeta.CreationTimestamp = metav1.Time{Time: justBeforeThePriorHour()} sj.Spec.StartingDeadlineSeconds = &tc.deadline
sj.Status.LastScheduleTime = &metav1.Time{Time: justAfterThePriorHour()}
} else {
if tc.hasFinishedJob || tc.hasUnexpectedJob || tc.hasMissingJob {
t.Errorf("%s: test setup error: this case makes no sense", name)
} }
sj.ObjectMeta.CreationTimestamp = metav1.Time{Time: justBeforeTheHour()} if tc.ranPreviously {
} sj.ObjectMeta.CreationTimestamp = metav1.Time{Time: justBeforeThePriorHour()}
jobs := []batchv1.Job{} sj.Status.LastScheduleTime = &metav1.Time{Time: justAfterThePriorHour()}
if tc.hasFinishedJob { } else {
ref, err := getRef(&finishedJob) if tc.hasFinishedJob || tc.hasUnexpectedJob || tc.hasMissingJob {
if err != nil { t.Errorf("%s: test setup error: this case makes no sense", name)
t.Errorf("%s: test setup error: failed to get job's ref: %v.", name, err) }
sj.ObjectMeta.CreationTimestamp = metav1.Time{Time: justBeforeTheHour()}
} }
sj.Status.Active = []v1.ObjectReference{*ref} jobs := []batchv1.Job{}
jobs = append(jobs, finishedJob) if tc.hasFinishedJob {
} ref, err := getRef(&finishedJob)
if tc.hasUnexpectedJob { if err != nil {
jobs = append(jobs, unexpectedJob) t.Errorf("%s: test setup error: failed to get job's ref: %v.", name, err)
} }
if tc.hasMissingJob { sj.Status.Active = []v1.ObjectReference{*ref}
ref, err := getRef(&missingJob) jobs = append(jobs, finishedJob)
if err != nil { }
t.Errorf("%s: test setup error: failed to get job's ref: %v.", name, err) if tc.hasUnexpectedJob {
jobs = append(jobs, unexpectedJob)
}
if tc.hasMissingJob {
ref, err := getRef(&missingJob)
if err != nil {
t.Errorf("%s: test setup error: failed to get job's ref: %v.", name, err)
}
sj.Status.Active = append(sj.Status.Active, *ref)
}
if tc.beingDeleted {
timestamp := metav1.NewTime(tc.now)
sj.DeletionTimestamp = &timestamp
} }
sj.Status.Active = append(sj.Status.Active, *ref)
}
if tc.beingDeleted {
timestamp := metav1.NewTime(tc.now)
sj.DeletionTimestamp = &timestamp
}
jc := &fakeJobControl{} jc := &fakeJobControl{}
sjc := &fakeSJControl{} sjc := &fakeSJControl{}
recorder := record.NewFakeRecorder(10) recorder := record.NewFakeRecorder(10)
// Run the code // Run the code
syncOne(&sj, jobs, tc.now, jc, sjc, recorder) syncOne(&sj, jobs, tc.now, jc, sjc, recorder)
// Status update happens once when ranging through job list, and another one if create jobs. // Status update happens once when ranging through job list, and another one if create jobs.
expectUpdates := 1 expectUpdates := 1
// Events happens when there's unexpected / finished jobs, and upon job creation / deletion. // Events happens when there's unexpected / finished jobs, and upon job creation / deletion.
expectedEvents := 0 expectedEvents := 0
if tc.expectCreate { if tc.expectCreate {
expectUpdates++ expectUpdates++
expectedEvents++ expectedEvents++
} }
if tc.expectDelete { if tc.expectDelete {
expectedEvents++ expectedEvents++
} }
if tc.hasFinishedJob { if tc.hasFinishedJob {
expectedEvents++ expectedEvents++
} }
if tc.hasUnexpectedJob { if tc.hasUnexpectedJob {
expectedEvents++ expectedEvents++
} }
if tc.hasMissingJob { if tc.hasMissingJob {
expectedEvents++ expectedEvents++
} }
if len(recorder.Events) != expectedEvents { if len(recorder.Events) != expectedEvents {
t.Errorf("%s: expected %d event, actually %v: %#v", name, expectedEvents, len(recorder.Events), recorder.Events) t.Errorf("%s: expected %d event, actually %v: %#v", name, expectedEvents, len(recorder.Events), recorder.Events)
} }
if expectUpdates != len(sjc.Updates) { if expectUpdates != len(sjc.Updates) {
t.Errorf("%s: expected %d status updates, actually %d", name, expectUpdates, len(sjc.Updates)) t.Errorf("%s: expected %d status updates, actually %d", name, expectUpdates, len(sjc.Updates))
} }
if tc.hasFinishedJob && inActiveList(sjc.Updates[0], finishedJob.UID) { if tc.hasFinishedJob && inActiveList(sjc.Updates[0], finishedJob.UID) {
t.Errorf("%s: expected finished job removed from active list, actually active list = %#v", name, sjc.Updates[0].Status.Active) t.Errorf("%s: expected finished job removed from active list, actually active list = %#v", name, sjc.Updates[0].Status.Active)
} }
if tc.hasUnexpectedJob && inActiveList(sjc.Updates[0], unexpectedJob.UID) { if tc.hasUnexpectedJob && inActiveList(sjc.Updates[0], unexpectedJob.UID) {
t.Errorf("%s: expected unexpected job not added to active list, actually active list = %#v", name, sjc.Updates[0].Status.Active) t.Errorf("%s: expected unexpected job not added to active list, actually active list = %#v", name, sjc.Updates[0].Status.Active)
} }
if tc.hasMissingJob && inActiveList(sjc.Updates[0], missingJob.UID) { if tc.hasMissingJob && inActiveList(sjc.Updates[0], missingJob.UID) {
t.Errorf("%s: expected missing job to be removed from active list, actually active list = %#v", name, sjc.Updates[0].Status.Active) t.Errorf("%s: expected missing job to be removed from active list, actually active list = %#v", name, sjc.Updates[0].Status.Active)
} }
if tc.expectCreate && !sjc.Updates[1].Status.LastScheduleTime.Time.Equal(topOfTheHour()) { if tc.expectCreate && !sjc.Updates[1].Status.LastScheduleTime.Time.Equal(topOfTheHour()) {
t.Errorf("%s: expected LastScheduleTime updated to %s, got %s", name, topOfTheHour(), sjc.Updates[1].Status.LastScheduleTime) t.Errorf("%s: expected LastScheduleTime updated to %s, got %s", name, topOfTheHour(), sjc.Updates[1].Status.LastScheduleTime)
} }
})
} }
} }