github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/pkg/metrics/prowjobs/collector_test.go (about) 1 /* 2 Copyright 2019 The Kubernetes 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 prowjobs 18 19 import ( 20 "fmt" 21 "reflect" 22 "testing" 23 "time" 24 25 "github.com/google/go-cmp/cmp" 26 "github.com/prometheus/client_golang/prometheus" 27 dto "github.com/prometheus/client_model/go" 28 29 v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 clock "k8s.io/utils/clock/testing" 31 32 prowapi "sigs.k8s.io/prow/pkg/apis/prowjobs/v1" 33 ) 34 35 func TestProwJobLifecycleCollectorUpdate(t *testing.T) { 36 fakeClock := clock.NewFakeClock(time.Now()) 37 defaultCompleteTime := v1.NewTime(fakeClock.Now().Add(1 * time.Hour)) 38 defaultPendingTime := v1.NewTime(fakeClock.Now().Add(30 * time.Minute)) 39 type args struct { 40 oldJob *prowapi.ProwJob 41 newJob *prowapi.ProwJob 42 } 43 type expected struct { 44 collected []*dto.Metric 45 } 46 tests := []struct { 47 oldJobStates []prowapi.ProwJobState 48 newJobStates []prowapi.ProwJobState 49 name string 50 args args 51 expected expected 52 }{ 53 {name: "should not collect job without history when no state change was observed for state %s to %s", 54 oldJobStates: []prowapi.ProwJobState{ 55 prowapi.TriggeredState, 56 prowapi.PendingState, 57 prowapi.SuccessState, 58 prowapi.FailureState, 59 prowapi.ErrorState, 60 prowapi.AbortedState, 61 }, 62 newJobStates: []prowapi.ProwJobState{ 63 prowapi.TriggeredState, 64 prowapi.PendingState, 65 prowapi.SuccessState, 66 prowapi.FailureState, 67 prowapi.ErrorState, 68 prowapi.AbortedState, 69 }, 70 args: args{ 71 oldJob: &prowapi.ProwJob{ 72 ObjectMeta: v1.ObjectMeta{ 73 UID: "1234", 74 CreationTimestamp: v1.NewTime(fakeClock.Now()), 75 }, 76 Status: prowapi.ProwJobStatus{ 77 CompletionTime: &defaultCompleteTime, 78 PendingTime: &defaultPendingTime, 79 }, 80 }, 81 newJob: &prowapi.ProwJob{ 82 ObjectMeta: v1.ObjectMeta{ 83 UID: "1234", 84 CreationTimestamp: v1.NewTime(fakeClock.Now()), 85 }, 86 Status: prowapi.ProwJobStatus{ 87 CompletionTime: &defaultCompleteTime, 88 PendingTime: &defaultPendingTime, 89 }, 90 }, 91 }, expected: expected{ 92 collected: nil, 93 }}, 94 {name: "should collect job transitions for transitions from %s to %s", 95 oldJobStates: []prowapi.ProwJobState{ 96 prowapi.TriggeredState, 97 prowapi.TriggeredState, 98 prowapi.TriggeredState, 99 prowapi.PendingState, 100 prowapi.PendingState, 101 prowapi.PendingState, 102 prowapi.PendingState, 103 }, 104 newJobStates: []prowapi.ProwJobState{ 105 prowapi.PendingState, 106 prowapi.ErrorState, 107 prowapi.AbortedState, 108 prowapi.SuccessState, 109 prowapi.FailureState, 110 prowapi.ErrorState, 111 prowapi.AbortedState, 112 }, 113 args: args{ 114 oldJob: &prowapi.ProwJob{ 115 ObjectMeta: v1.ObjectMeta{ 116 UID: "1234", 117 CreationTimestamp: v1.NewTime(fakeClock.Now().Add(-time.Hour)), 118 }, 119 Status: prowapi.ProwJobStatus{ 120 CompletionTime: &defaultCompleteTime, 121 PendingTime: &defaultPendingTime, 122 State: prowapi.TriggeredState, 123 }, 124 }, 125 newJob: &prowapi.ProwJob{ 126 ObjectMeta: v1.ObjectMeta{ 127 UID: "1234", 128 Name: "testjob", 129 Namespace: "testnamespace", 130 CreationTimestamp: v1.NewTime(fakeClock.Now().Add(-time.Hour)), 131 }, 132 Spec: prowapi.ProwJobSpec{ 133 Job: "testjob", 134 Type: prowapi.PeriodicJob, 135 Refs: &prowapi.Refs{ 136 Org: "testorg", 137 Repo: "testrepo", 138 BaseRef: "master", 139 }, 140 }, 141 Status: prowapi.ProwJobStatus{ 142 State: prowapi.PendingState, 143 CompletionTime: &defaultCompleteTime, 144 PendingTime: &defaultPendingTime, 145 }, 146 }, 147 }, expected: expected{ 148 collected: []*dto.Metric{{Label: []*dto.LabelPair{ 149 toLabelPair("base_ref", "master"), 150 toLabelPair("job_name", "testjob"), 151 toLabelPair("job_namespace", "testnamespace"), 152 toLabelPair("last_state", string(prowapi.TriggeredState)), 153 toLabelPair("org", "testorg"), 154 toLabelPair("repo", "testrepo"), 155 toLabelPair("state", string(prowapi.PendingState)), 156 toLabelPair("type", string(prowapi.PeriodicJob)), 157 }}}, 158 }}, 159 {name: "should collect job transitions for transitions from %s to %s from extraRefs", 160 oldJobStates: []prowapi.ProwJobState{ 161 prowapi.TriggeredState, 162 prowapi.TriggeredState, 163 prowapi.TriggeredState, 164 prowapi.PendingState, 165 prowapi.PendingState, 166 prowapi.PendingState, 167 prowapi.PendingState, 168 }, 169 newJobStates: []prowapi.ProwJobState{ 170 prowapi.PendingState, 171 prowapi.ErrorState, 172 prowapi.AbortedState, 173 prowapi.SuccessState, 174 prowapi.FailureState, 175 prowapi.ErrorState, 176 prowapi.AbortedState, 177 }, 178 args: args{ 179 oldJob: &prowapi.ProwJob{ 180 ObjectMeta: v1.ObjectMeta{ 181 UID: "1234", 182 CreationTimestamp: v1.NewTime(fakeClock.Now().Add(-time.Hour)), 183 }, 184 Status: prowapi.ProwJobStatus{ 185 State: prowapi.TriggeredState, 186 CompletionTime: &defaultCompleteTime, 187 PendingTime: &defaultPendingTime, 188 }, 189 }, 190 newJob: &prowapi.ProwJob{ 191 ObjectMeta: v1.ObjectMeta{ 192 UID: "1234", 193 Name: "testjob", 194 Namespace: "testnamespace", 195 CreationTimestamp: v1.NewTime(fakeClock.Now().Add(-time.Hour)), 196 }, 197 Spec: prowapi.ProwJobSpec{ 198 Job: "testjob", 199 Type: prowapi.PeriodicJob, 200 ExtraRefs: []prowapi.Refs{{ 201 Org: "testorg", 202 Repo: "testrepo", 203 BaseRef: "master", 204 }}, 205 }, 206 Status: prowapi.ProwJobStatus{ 207 CompletionTime: &defaultCompleteTime, 208 PendingTime: &defaultPendingTime, 209 State: prowapi.PendingState, 210 }, 211 }, 212 }, expected: expected{ 213 collected: []*dto.Metric{{Label: []*dto.LabelPair{ 214 toLabelPair("base_ref", "master"), 215 toLabelPair("job_name", "testjob"), 216 toLabelPair("job_namespace", "testnamespace"), 217 toLabelPair("last_state", string(prowapi.TriggeredState)), 218 toLabelPair("org", "testorg"), 219 toLabelPair("repo", "testrepo"), 220 toLabelPair("state", string(prowapi.PendingState)), 221 toLabelPair("type", string(prowapi.PeriodicJob)), 222 }}}, 223 }}, 224 } 225 for _, tt := range tests { 226 for x := 0; x < len(tt.oldJobStates); x++ { 227 t.Run(fmt.Sprintf(tt.name, tt.oldJobStates[x], tt.newJobStates[x]), func(t *testing.T) { 228 histogramVec := newHistogramVec() 229 tt.args.oldJob.Status.State = tt.oldJobStates[x] 230 tt.args.newJob.Status.State = tt.newJobStates[x] 231 update(histogramVec, tt.args.oldJob, tt.args.newJob) 232 assertMetrics(t, collect(histogramVec), tt.expected.collected, tt.oldJobStates[x], tt.newJobStates[x]) 233 }) 234 } 235 } 236 } 237 238 func collect(histogram *prometheus.HistogramVec) []*dto.Metric { 239 metrics := make(chan prometheus.Metric, 1000) 240 histogram.Collect(metrics) 241 close(metrics) 242 var collected []*dto.Metric 243 for metric := range metrics { 244 m := dto.Metric{} 245 metric.Write(&m) 246 collected = append(collected, &m) 247 } 248 return collected 249 } 250 251 func assertMetrics(t *testing.T, actual, expected []*dto.Metric, lastState prowapi.ProwJobState, state prowapi.ProwJobState) { 252 if len(actual) != len(expected) { 253 t.Errorf("actual length differs from expected: %v, %v", len(actual), len(expected)) 254 return 255 } 256 for x := 0; x < len(actual); x++ { 257 expected[x].Label[3] = toLabelPair("last_state", string(lastState)) 258 expected[x].Label[6] = toLabelPair("state", string(state)) 259 if !reflect.DeepEqual(actual[x].Label, expected[x].Label) { 260 t.Errorf("actual differs from expected:\n%s", cmp.Diff(expected[x].Label, actual[x].Label)) 261 } 262 } 263 } 264 265 func toLabelPair(name, value string) *dto.LabelPair { 266 return &dto.LabelPair{Name: &name, Value: &value} 267 }