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  }