github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/pkg/pjutil/pjutil_test.go (about)

     1  /*
     2  Copyright 2017 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 pjutil
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"reflect"
    23  	"testing"
    24  	"text/template"
    25  	"time"
    26  
    27  	"github.com/google/go-cmp/cmp"
    28  	"github.com/sirupsen/logrus"
    29  	corev1 "k8s.io/api/core/v1"
    30  	"k8s.io/apimachinery/pkg/api/equality"
    31  	"k8s.io/apimachinery/pkg/api/resource"
    32  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  	"k8s.io/apimachinery/pkg/util/diff"
    34  	"k8s.io/apimachinery/pkg/util/sets"
    35  
    36  	prowapi "sigs.k8s.io/prow/pkg/apis/prowjobs/v1"
    37  	"sigs.k8s.io/prow/pkg/config"
    38  	"sigs.k8s.io/prow/pkg/github"
    39  	"sigs.k8s.io/prow/pkg/kube"
    40  )
    41  
    42  func TestPostsubmitSpec(t *testing.T) {
    43  	tests := []struct {
    44  		name     string
    45  		p        config.Postsubmit
    46  		refs     prowapi.Refs
    47  		expected prowapi.ProwJobSpec
    48  	}{
    49  		{
    50  			name: "can override fields",
    51  			p: config.Postsubmit{
    52  				JobBase: config.JobBase{
    53  					UtilityConfig: config.UtilityConfig{
    54  						PathAlias:      "foo",
    55  						CloneURI:       "bar",
    56  						SkipSubmodules: true,
    57  						CloneDepth:     7,
    58  						SkipFetchHead:  true,
    59  					},
    60  				},
    61  			},
    62  			expected: prowapi.ProwJobSpec{
    63  				Type: prowapi.PostsubmitJob,
    64  				Refs: &prowapi.Refs{
    65  					PathAlias:      "foo",
    66  					CloneURI:       "bar",
    67  					SkipSubmodules: true,
    68  					CloneDepth:     7,
    69  					SkipFetchHead:  true,
    70  				},
    71  				Report: true,
    72  			},
    73  		},
    74  		{
    75  			name: "controller can default fields",
    76  			refs: prowapi.Refs{
    77  				PathAlias:      "fancy",
    78  				CloneURI:       "cats",
    79  				SkipSubmodules: true,
    80  				CloneDepth:     8,
    81  				SkipFetchHead:  true,
    82  			},
    83  			expected: prowapi.ProwJobSpec{
    84  				Type: prowapi.PostsubmitJob,
    85  				Refs: &prowapi.Refs{
    86  					PathAlias:      "fancy",
    87  					CloneURI:       "cats",
    88  					SkipSubmodules: true,
    89  					CloneDepth:     8,
    90  					SkipFetchHead:  true,
    91  				},
    92  				Report: true,
    93  			},
    94  		},
    95  		{
    96  			name: "job overrides take precedence over controller defaults",
    97  			p: config.Postsubmit{
    98  				JobBase: config.JobBase{
    99  					UtilityConfig: config.UtilityConfig{
   100  						PathAlias:  "foo",
   101  						CloneDepth: 3,
   102  						CloneURI:   "bar",
   103  					},
   104  				},
   105  			},
   106  			refs: prowapi.Refs{
   107  				PathAlias:  "fancy",
   108  				CloneDepth: 1,
   109  				CloneURI:   "cats",
   110  			},
   111  			expected: prowapi.ProwJobSpec{
   112  				Type: prowapi.PostsubmitJob,
   113  				Refs: &prowapi.Refs{
   114  					PathAlias:  "foo",
   115  					CloneDepth: 3,
   116  					CloneURI:   "bar",
   117  				},
   118  				Report: true,
   119  			},
   120  		},
   121  	}
   122  
   123  	for _, tc := range tests {
   124  		t.Run(tc.name, func(t *testing.T) {
   125  			actual := PostsubmitSpec(tc.p, tc.refs)
   126  			if diff := cmp.Diff(actual, tc.expected); diff != "" {
   127  				t.Errorf("PostsubmitSpec() got unexpected diff (-have, +want):\n%s", diff)
   128  			}
   129  		})
   130  	}
   131  }
   132  
   133  func TestPresubmitSpec(t *testing.T) {
   134  	tests := []struct {
   135  		name     string
   136  		p        config.Presubmit
   137  		refs     prowapi.Refs
   138  		expected prowapi.ProwJobSpec
   139  	}{
   140  		{
   141  			name: "can override path alias and cloneuri",
   142  			p: config.Presubmit{
   143  				JobBase: config.JobBase{
   144  					UtilityConfig: config.UtilityConfig{
   145  						PathAlias: "foo",
   146  						CloneURI:  "bar",
   147  					},
   148  				},
   149  			},
   150  			expected: prowapi.ProwJobSpec{
   151  				Type: prowapi.PresubmitJob,
   152  				Refs: &prowapi.Refs{
   153  					PathAlias: "foo",
   154  					CloneURI:  "bar",
   155  				},
   156  				Report: true,
   157  			},
   158  		},
   159  		{
   160  			name: "controller can default path alias and cloneuri",
   161  			refs: prowapi.Refs{
   162  				PathAlias: "fancy",
   163  				CloneURI:  "cats",
   164  			},
   165  			expected: prowapi.ProwJobSpec{
   166  				Type: prowapi.PresubmitJob,
   167  				Refs: &prowapi.Refs{
   168  					PathAlias: "fancy",
   169  					CloneURI:  "cats",
   170  				},
   171  				Report: true,
   172  			},
   173  		},
   174  		{
   175  			name: "job overrides take precedence over controller defaults",
   176  			p: config.Presubmit{
   177  				JobBase: config.JobBase{
   178  					UtilityConfig: config.UtilityConfig{
   179  						PathAlias: "foo",
   180  						CloneURI:  "bar",
   181  					},
   182  				},
   183  			},
   184  			refs: prowapi.Refs{
   185  				PathAlias: "fancy",
   186  				CloneURI:  "cats",
   187  			},
   188  			expected: prowapi.ProwJobSpec{
   189  				Type: prowapi.PresubmitJob,
   190  				Refs: &prowapi.Refs{
   191  					PathAlias: "foo",
   192  					CloneURI:  "bar",
   193  				},
   194  				Report: true,
   195  			},
   196  		},
   197  	}
   198  
   199  	for _, tc := range tests {
   200  		t.Run(tc.name, func(t *testing.T) {
   201  			actual := PresubmitSpec(tc.p, tc.refs)
   202  			if diff := cmp.Diff(actual, tc.expected); diff != "" {
   203  				t.Errorf("PresubmitSpec() got unexpected diff (-have, +want):\n%s", diff)
   204  			}
   205  		})
   206  	}
   207  }
   208  
   209  func TestBatchSpec(t *testing.T) {
   210  	tests := []struct {
   211  		name     string
   212  		p        config.Presubmit
   213  		refs     prowapi.Refs
   214  		expected prowapi.ProwJobSpec
   215  	}{
   216  		{
   217  			name: "can override path alias and cloneuri",
   218  			p: config.Presubmit{
   219  				JobBase: config.JobBase{
   220  					UtilityConfig: config.UtilityConfig{
   221  						PathAlias: "foo",
   222  						CloneURI:  "bar",
   223  					},
   224  				},
   225  			},
   226  			expected: prowapi.ProwJobSpec{
   227  				Type: prowapi.BatchJob,
   228  				Refs: &prowapi.Refs{
   229  					PathAlias: "foo",
   230  					CloneURI:  "bar",
   231  				},
   232  			},
   233  		},
   234  		{
   235  			name: "controller can default path alias and cloneuri",
   236  			refs: prowapi.Refs{
   237  				PathAlias: "fancy",
   238  				CloneURI:  "cats",
   239  			},
   240  			expected: prowapi.ProwJobSpec{
   241  				Type: prowapi.BatchJob,
   242  				Refs: &prowapi.Refs{
   243  					PathAlias: "fancy",
   244  					CloneURI:  "cats",
   245  				},
   246  			},
   247  		},
   248  		{
   249  			name: "job overrides take precedence over controller defaults",
   250  			p: config.Presubmit{
   251  				JobBase: config.JobBase{
   252  					UtilityConfig: config.UtilityConfig{
   253  						PathAlias: "foo",
   254  						CloneURI:  "bar",
   255  					},
   256  				},
   257  			},
   258  			refs: prowapi.Refs{
   259  				PathAlias: "fancy",
   260  				CloneURI:  "cats",
   261  			},
   262  			expected: prowapi.ProwJobSpec{
   263  				Type: prowapi.BatchJob,
   264  				Refs: &prowapi.Refs{
   265  					PathAlias: "foo",
   266  					CloneURI:  "bar",
   267  				},
   268  			},
   269  		},
   270  	}
   271  
   272  	for _, tc := range tests {
   273  		t.Run(tc.name, func(t *testing.T) {
   274  			actual := BatchSpec(tc.p, tc.refs)
   275  			if diff := cmp.Diff(actual, tc.expected); diff != "" {
   276  				t.Errorf("BatchSpec() got unexpected diff (-have, +want):\n%s", diff)
   277  			}
   278  		})
   279  	}
   280  }
   281  
   282  func TestCompletePrimaryRefs(t *testing.T) {
   283  	cases := []struct {
   284  		name     string
   285  		refs     prowapi.Refs
   286  		jobBase  config.JobBase
   287  		expected prowapi.Refs
   288  	}{
   289  		{
   290  			name: "basically works",
   291  		},
   292  		{
   293  			name: "use values from refs",
   294  			refs: prowapi.Refs{
   295  				PathAlias:      "this",
   296  				CloneURI:       "that",
   297  				SkipSubmodules: true,
   298  				CloneDepth:     1,
   299  				SkipFetchHead:  true,
   300  			},
   301  			expected: prowapi.Refs{
   302  				PathAlias:      "this",
   303  				CloneURI:       "that",
   304  				SkipSubmodules: true,
   305  				CloneDepth:     1,
   306  				SkipFetchHead:  true,
   307  			},
   308  		},
   309  		{
   310  			name: "use values from job base",
   311  			jobBase: config.JobBase{
   312  				UtilityConfig: config.UtilityConfig{
   313  					PathAlias:      "more",
   314  					CloneURI:       "fun",
   315  					SkipSubmodules: true,
   316  					CloneDepth:     2,
   317  					SkipFetchHead:  true,
   318  					DecorationConfig: &prowapi.DecorationConfig{
   319  						BloblessFetch: boolPtr(true),
   320  					},
   321  				},
   322  			},
   323  			expected: prowapi.Refs{
   324  				PathAlias:      "more",
   325  				CloneURI:       "fun",
   326  				SkipSubmodules: true,
   327  				CloneDepth:     2,
   328  				SkipFetchHead:  true,
   329  				BloblessFetch:  boolPtr(true),
   330  			},
   331  		},
   332  		{
   333  			name: "prefer job base values",
   334  			refs: prowapi.Refs{
   335  				PathAlias:  "this",
   336  				CloneURI:   "that",
   337  				CloneDepth: 1,
   338  			},
   339  			jobBase: config.JobBase{
   340  				UtilityConfig: config.UtilityConfig{
   341  					PathAlias:  "more",
   342  					CloneURI:   "fun",
   343  					CloneDepth: 2,
   344  				},
   345  			},
   346  			expected: prowapi.Refs{
   347  				PathAlias:  "more",
   348  				CloneURI:   "fun",
   349  				CloneDepth: 2,
   350  			},
   351  		},
   352  	}
   353  
   354  	for _, tc := range cases {
   355  		t.Run(tc.name, func(t *testing.T) {
   356  			actual := CompletePrimaryRefs(tc.refs, tc.jobBase)
   357  			if diff := cmp.Diff(actual, &tc.expected); diff != "" {
   358  				t.Errorf("CompletePrimaryRefs() got unexpected diff (-have, +want):\n%s", diff)
   359  			}
   360  		})
   361  	}
   362  }
   363  
   364  func TestPartitionActive(t *testing.T) {
   365  	tests := []struct {
   366  		pjs []prowapi.ProwJob
   367  
   368  		pending   sets.Set[string]
   369  		triggered sets.Set[string]
   370  		aborted   sets.Set[string]
   371  	}{
   372  		{
   373  			pjs: []prowapi.ProwJob{
   374  				{
   375  					ObjectMeta: metav1.ObjectMeta{
   376  						Name: "foo",
   377  					},
   378  					Status: prowapi.ProwJobStatus{
   379  						State: prowapi.TriggeredState,
   380  					},
   381  				},
   382  				{
   383  					ObjectMeta: metav1.ObjectMeta{
   384  						Name: "bar",
   385  					},
   386  					Status: prowapi.ProwJobStatus{
   387  						State: prowapi.PendingState,
   388  					},
   389  				},
   390  				{
   391  					ObjectMeta: metav1.ObjectMeta{
   392  						Name: "baz",
   393  					},
   394  					Status: prowapi.ProwJobStatus{
   395  						State: prowapi.SuccessState,
   396  					},
   397  				},
   398  				{
   399  					ObjectMeta: metav1.ObjectMeta{
   400  						Name: "error",
   401  					},
   402  					Status: prowapi.ProwJobStatus{
   403  						State: prowapi.ErrorState,
   404  					},
   405  				},
   406  				{
   407  					ObjectMeta: metav1.ObjectMeta{
   408  						Name: "bak",
   409  					},
   410  					Status: prowapi.ProwJobStatus{
   411  						State: prowapi.PendingState,
   412  					},
   413  				},
   414  				{
   415  					ObjectMeta: metav1.ObjectMeta{
   416  						Name: "aborted",
   417  					},
   418  					Status: prowapi.ProwJobStatus{
   419  						State: prowapi.AbortedState,
   420  					},
   421  				},
   422  				{
   423  					ObjectMeta: metav1.ObjectMeta{
   424  						Name: "aborted-and-completed",
   425  					},
   426  					Status: prowapi.ProwJobStatus{
   427  						State:          prowapi.AbortedState,
   428  						CompletionTime: &[]metav1.Time{metav1.Now()}[0],
   429  					},
   430  				},
   431  			},
   432  			pending:   sets.New[string]("bar", "bak"),
   433  			triggered: sets.New[string]("foo"),
   434  			aborted:   sets.New[string]("aborted"),
   435  		},
   436  	}
   437  
   438  	for i, test := range tests {
   439  		t.Logf("test run #%d", i)
   440  		pendingCh, triggeredCh, abortedCh := PartitionActive(test.pjs)
   441  		for job := range pendingCh {
   442  			if !test.pending.Has(job.Name) {
   443  				t.Errorf("didn't find pending job %#v", job)
   444  			}
   445  		}
   446  		for job := range triggeredCh {
   447  			if !test.triggered.Has(job.Name) {
   448  				t.Errorf("didn't find triggered job %#v", job)
   449  			}
   450  		}
   451  		for job := range abortedCh {
   452  			if !test.aborted.Has(job.Name) {
   453  				t.Errorf("didn't find aborted job %#v", job)
   454  			}
   455  		}
   456  	}
   457  }
   458  
   459  func TestGetLatestProwJobs(t *testing.T) {
   460  	tests := []struct {
   461  		name string
   462  
   463  		pjs     []prowapi.ProwJob
   464  		jobType string
   465  
   466  		expected map[string]struct{}
   467  	}{
   468  		{
   469  			pjs: []prowapi.ProwJob{
   470  				{
   471  					ObjectMeta: metav1.ObjectMeta{
   472  						Name: "831c7df0-baa4-11e7-a1a4-0a58ac10134a",
   473  					},
   474  					Spec: prowapi.ProwJobSpec{
   475  						Type:  prowapi.PresubmitJob,
   476  						Agent: prowapi.JenkinsAgent,
   477  						Job:   "test_pull_request_origin_extended_networking_minimal",
   478  						Refs: &prowapi.Refs{
   479  							Org:     "openshift",
   480  							Repo:    "origin",
   481  							BaseRef: "master",
   482  							BaseSHA: "e92d5c525795eafb82cf16e3ab151b567b47e333",
   483  							Pulls: []prowapi.Pull{
   484  								{
   485  									Number:  17061,
   486  									Author:  "enj",
   487  									SHA:     "f94a3a51f59a693642e39084f03efa83af9442d3",
   488  									HeadRef: "fixes-123",
   489  								},
   490  							},
   491  						},
   492  						Report:       true,
   493  						Context:      "ci/openshift-jenkins/extended_networking_minimal",
   494  						RerunCommand: "/test extended_networking_minimal",
   495  					},
   496  					Status: prowapi.ProwJobStatus{
   497  						StartTime:   metav1.Date(2017, time.October, 26, 23, 22, 19, 0, time.UTC),
   498  						State:       prowapi.FailureState,
   499  						Description: "Jenkins job failed.",
   500  						URL:         "https://openshift-gce-devel.appspot.com/build/origin-ci-test/pr-logs/pull/17061/test_pull_request_origin_extended_networking_minimal/9756/",
   501  						PodName:     "test_pull_request_origin_extended_networking_minimal-9756",
   502  						BuildID:     "9756",
   503  					},
   504  				},
   505  				{
   506  					ObjectMeta: metav1.ObjectMeta{
   507  						Name: "0079d4d3-ba25-11e7-ae3f-0a58ac10123b",
   508  					},
   509  					Spec: prowapi.ProwJobSpec{
   510  						Type:  prowapi.PresubmitJob,
   511  						Agent: prowapi.JenkinsAgent,
   512  						Job:   "test_pull_request_origin_extended_networking_minimal",
   513  						Refs: &prowapi.Refs{
   514  							Org:     "openshift",
   515  							Repo:    "origin",
   516  							BaseRef: "master",
   517  							BaseSHA: "e92d5c525795eafb82cf16e3ab151b567b47e333",
   518  							Pulls: []prowapi.Pull{
   519  								{
   520  									Number:  17061,
   521  									Author:  "enj",
   522  									SHA:     "f94a3a51f59a693642e39084f03efa83af9442d3",
   523  									HeadRef: "fixes-123",
   524  								},
   525  							},
   526  						},
   527  						Report:       true,
   528  						Context:      "ci/openshift-jenkins/extended_networking_minimal",
   529  						RerunCommand: "/test extended_networking_minimal",
   530  					},
   531  					Status: prowapi.ProwJobStatus{
   532  						StartTime:   metav1.Date(2017, time.October, 26, 22, 22, 19, 0, time.UTC),
   533  						State:       prowapi.FailureState,
   534  						Description: "Jenkins job failed.",
   535  						URL:         "https://openshift-gce-devel.appspot.com/build/origin-ci-test/pr-logs/pull/17061/test_pull_request_origin_extended_networking_minimal/9755/",
   536  						PodName:     "test_pull_request_origin_extended_networking_minimal-9755",
   537  						BuildID:     "9755",
   538  					},
   539  				},
   540  			},
   541  			jobType:  "presubmit",
   542  			expected: map[string]struct{}{"831c7df0-baa4-11e7-a1a4-0a58ac10134a": {}},
   543  		},
   544  	}
   545  
   546  	for _, test := range tests {
   547  		got := GetLatestProwJobs(test.pjs, prowapi.ProwJobType(test.jobType))
   548  		if len(got) != len(test.expected) {
   549  			t.Errorf("expected jobs:\n%+v\ngot jobs:\n%+v", test.expected, got)
   550  			continue
   551  		}
   552  		for name := range test.expected {
   553  			if _, ok := got[name]; ok {
   554  				t.Errorf("expected job: %s", name)
   555  			}
   556  		}
   557  	}
   558  }
   559  
   560  func TestNewProwJob(t *testing.T) {
   561  	var testCases = []struct {
   562  		name                string
   563  		spec                prowapi.ProwJobSpec
   564  		labels              map[string]string
   565  		expectedLabels      map[string]string
   566  		annotations         map[string]string
   567  		expectedAnnotations map[string]string
   568  	}{
   569  		{
   570  			name: "periodic job, no extra labels",
   571  			spec: prowapi.ProwJobSpec{
   572  				Job:     "job",
   573  				Context: "job-context",
   574  				Type:    prowapi.PeriodicJob,
   575  			},
   576  			labels: map[string]string{},
   577  			expectedLabels: map[string]string{
   578  				kube.CreatedByProw:     "true",
   579  				kube.ProwJobAnnotation: "job",
   580  				kube.ContextAnnotation: "job-context",
   581  				kube.ProwJobTypeLabel:  "periodic",
   582  			},
   583  			expectedAnnotations: map[string]string{
   584  				kube.ProwJobAnnotation: "job",
   585  				kube.ContextAnnotation: "job-context",
   586  			},
   587  		},
   588  		{
   589  			name: "periodic job, extra labels",
   590  			spec: prowapi.ProwJobSpec{
   591  				Job:     "job",
   592  				Context: "job-context",
   593  				Type:    prowapi.PeriodicJob,
   594  			},
   595  			labels: map[string]string{
   596  				"extra": "stuff",
   597  			},
   598  			expectedLabels: map[string]string{
   599  				kube.CreatedByProw:     "true",
   600  				kube.ProwJobAnnotation: "job",
   601  				kube.ContextAnnotation: "job-context",
   602  				kube.ProwJobTypeLabel:  "periodic",
   603  				"extra":                "stuff",
   604  			},
   605  			expectedAnnotations: map[string]string{
   606  				kube.ProwJobAnnotation: "job",
   607  				kube.ContextAnnotation: "job-context",
   608  			},
   609  		},
   610  		{
   611  			name: "periodic job with extra refs",
   612  			spec: prowapi.ProwJobSpec{
   613  				Job:     "job",
   614  				Context: "job-context",
   615  				Type:    prowapi.PeriodicJob,
   616  				ExtraRefs: []prowapi.Refs{{
   617  					Org:     "org",
   618  					Repo:    "repo",
   619  					BaseRef: "main",
   620  				}},
   621  			},
   622  			labels: map[string]string{},
   623  			expectedLabels: map[string]string{
   624  				kube.CreatedByProw:     "true",
   625  				kube.ProwJobAnnotation: "job",
   626  				kube.ContextAnnotation: "job-context",
   627  				kube.ProwJobTypeLabel:  "periodic",
   628  				kube.OrgLabel:          "org",
   629  				kube.RepoLabel:         "repo",
   630  				kube.BaseRefLabel:      "main",
   631  			},
   632  			expectedAnnotations: map[string]string{
   633  				kube.ProwJobAnnotation: "job",
   634  				kube.ContextAnnotation: "job-context",
   635  			},
   636  		},
   637  		{
   638  			name: "presubmit job",
   639  			spec: prowapi.ProwJobSpec{
   640  				Job:     "job",
   641  				Context: "job-context",
   642  				Type:    prowapi.PresubmitJob,
   643  				Refs: &prowapi.Refs{
   644  					Org:     "org",
   645  					Repo:    "repo",
   646  					BaseRef: "main",
   647  					Pulls: []prowapi.Pull{
   648  						{Number: 1},
   649  					},
   650  				},
   651  			},
   652  			labels: map[string]string{},
   653  			expectedLabels: map[string]string{
   654  				kube.CreatedByProw:     "true",
   655  				kube.ProwJobAnnotation: "job",
   656  				kube.ContextAnnotation: "job-context",
   657  				kube.ProwJobTypeLabel:  "presubmit",
   658  				kube.OrgLabel:          "org",
   659  				kube.RepoLabel:         "repo",
   660  				kube.BaseRefLabel:      "main",
   661  				kube.PullLabel:         "1",
   662  			},
   663  			expectedAnnotations: map[string]string{
   664  				kube.ProwJobAnnotation: "job",
   665  				kube.ContextAnnotation: "job-context",
   666  			},
   667  		},
   668  		{
   669  			name: "non-github presubmit job",
   670  			spec: prowapi.ProwJobSpec{
   671  				Job:     "job",
   672  				Context: "job-context",
   673  				Type:    prowapi.PresubmitJob,
   674  				Refs: &prowapi.Refs{
   675  					Org:     "https://some-gerrit-instance.foo.com",
   676  					Repo:    "some/invalid/repo",
   677  					BaseRef: "main",
   678  					Pulls: []prowapi.Pull{
   679  						{Number: 1},
   680  					},
   681  				},
   682  			},
   683  			labels: map[string]string{},
   684  			expectedLabels: map[string]string{
   685  				kube.CreatedByProw:     "true",
   686  				kube.ProwJobAnnotation: "job",
   687  				kube.ContextAnnotation: "job-context",
   688  				kube.ProwJobTypeLabel:  "presubmit",
   689  				kube.OrgLabel:          "some-gerrit-instance.foo.com",
   690  				kube.RepoLabel:         "repo",
   691  				kube.BaseRefLabel:      "main",
   692  				kube.PullLabel:         "1",
   693  			},
   694  			expectedAnnotations: map[string]string{
   695  				kube.ProwJobAnnotation: "job",
   696  				kube.ContextAnnotation: "job-context",
   697  			},
   698  		}, {
   699  			name: "job with name too long to fit in a label",
   700  			spec: prowapi.ProwJobSpec{
   701  				Job:     "job-created-by-someone-who-loves-very-very-very-long-names-so-long-that-it-does-not-fit-into-the-Kubernetes-label-so-it-needs-to-be-truncated-to-63-characters",
   702  				Context: "job-context",
   703  				Type:    prowapi.PresubmitJob,
   704  				Refs: &prowapi.Refs{
   705  					Org:     "org",
   706  					Repo:    "repo",
   707  					BaseRef: "main",
   708  					Pulls: []prowapi.Pull{
   709  						{Number: 1},
   710  					},
   711  				},
   712  			},
   713  			labels: map[string]string{},
   714  			expectedLabels: map[string]string{
   715  				kube.CreatedByProw:     "true",
   716  				kube.ProwJobAnnotation: "job-created-by-someone-who-loves-very-very-very-long-names-so-l",
   717  				kube.ContextAnnotation: "job-context",
   718  				kube.ProwJobTypeLabel:  "presubmit",
   719  				kube.OrgLabel:          "org",
   720  				kube.RepoLabel:         "repo",
   721  				kube.BaseRefLabel:      "main",
   722  				kube.PullLabel:         "1",
   723  			},
   724  			expectedAnnotations: map[string]string{
   725  				kube.ContextAnnotation: "job-context",
   726  				kube.ProwJobAnnotation: "job-created-by-someone-who-loves-very-very-very-long-names-so-long-that-it-does-not-fit-into-the-Kubernetes-label-so-it-needs-to-be-truncated-to-63-characters",
   727  			},
   728  		},
   729  		{
   730  			name: "job with context too long to fit in a label",
   731  			spec: prowapi.ProwJobSpec{
   732  				Job:     "job",
   733  				Context: "context-created-by-someone-who-loves-very-very-very-long-names-so-long-that-it-does-not-fit-into-the-Kubernetes-label-so-it-needs-to-be-truncated-to-63-characters",
   734  				Type:    prowapi.PresubmitJob,
   735  				Refs: &prowapi.Refs{
   736  					Org:     "org",
   737  					Repo:    "repo",
   738  					BaseRef: "main",
   739  					Pulls: []prowapi.Pull{
   740  						{Number: 1},
   741  					},
   742  				},
   743  			},
   744  			labels: map[string]string{},
   745  			expectedLabels: map[string]string{
   746  				kube.CreatedByProw:     "true",
   747  				kube.ProwJobAnnotation: "job",
   748  				kube.ContextAnnotation: "context-created-by-someone-who-loves-very-very-very-long-names",
   749  				kube.ProwJobTypeLabel:  "presubmit",
   750  				kube.OrgLabel:          "org",
   751  				kube.RepoLabel:         "repo",
   752  				kube.BaseRefLabel:      "main",
   753  				kube.PullLabel:         "1",
   754  			},
   755  			expectedAnnotations: map[string]string{
   756  				kube.ProwJobAnnotation: "job",
   757  				kube.ContextAnnotation: "context-created-by-someone-who-loves-very-very-very-long-names-so-long-that-it-does-not-fit-into-the-Kubernetes-label-so-it-needs-to-be-truncated-to-63-characters",
   758  			},
   759  		},
   760  		{
   761  			name: "periodic job, extra labels, extra annotations",
   762  			spec: prowapi.ProwJobSpec{
   763  				Job:     "job",
   764  				Context: "job-context",
   765  				Type:    prowapi.PeriodicJob,
   766  			},
   767  			labels: map[string]string{
   768  				"extra": "stuff",
   769  			},
   770  			annotations: map[string]string{
   771  				"extraannotation": "foo",
   772  			},
   773  			expectedLabels: map[string]string{
   774  				kube.CreatedByProw:     "true",
   775  				kube.ProwJobAnnotation: "job",
   776  				kube.ContextAnnotation: "job-context",
   777  				kube.ProwJobTypeLabel:  "periodic",
   778  				"extra":                "stuff",
   779  			},
   780  			expectedAnnotations: map[string]string{
   781  				kube.ProwJobAnnotation: "job",
   782  				kube.ContextAnnotation: "job-context",
   783  				"extraannotation":      "foo",
   784  			},
   785  		},
   786  		{
   787  			name: "job with podspec",
   788  			spec: prowapi.ProwJobSpec{
   789  				Job:     "job",
   790  				Context: "job-context",
   791  				Type:    prowapi.PeriodicJob,
   792  				PodSpec: &corev1.PodSpec{}, // Needed to catch race
   793  			},
   794  			expectedLabels: map[string]string{
   795  				kube.CreatedByProw:     "true",
   796  				kube.ProwJobAnnotation: "job",
   797  				kube.ContextAnnotation: "job-context",
   798  				kube.ProwJobTypeLabel:  "periodic",
   799  			},
   800  			expectedAnnotations: map[string]string{
   801  				kube.ProwJobAnnotation: "job",
   802  				kube.ContextAnnotation: "job-context",
   803  			},
   804  		},
   805  	}
   806  	for _, testCase := range testCases {
   807  		pj := NewProwJob(testCase.spec, testCase.labels, testCase.annotations)
   808  		if actual, expected := pj.Spec, testCase.spec; !equality.Semantic.DeepEqual(actual, expected) {
   809  			t.Errorf("%s: incorrect ProwJobSpec created: %s", testCase.name, diff.ObjectReflectDiff(actual, expected))
   810  		}
   811  		if actual, expected := pj.Labels, testCase.expectedLabels; !reflect.DeepEqual(actual, expected) {
   812  			t.Errorf("%s: incorrect ProwJob labels created: %s", testCase.name, diff.ObjectReflectDiff(actual, expected))
   813  		}
   814  		if actual, expected := pj.Annotations, testCase.expectedAnnotations; !reflect.DeepEqual(actual, expected) {
   815  			t.Errorf("%s: incorrect ProwJob annotations created: %s", testCase.name, diff.ObjectReflectDiff(actual, expected))
   816  		}
   817  		if pj.Spec.PodSpec != nil {
   818  			futzWithPodSpec := func(spec *corev1.PodSpec, val string) {
   819  				if spec == nil {
   820  					return
   821  				}
   822  				if spec.NodeSelector == nil {
   823  					spec.NodeSelector = map[string]string{}
   824  				}
   825  				spec.NodeSelector["foo"] = val
   826  				for i := range spec.Containers {
   827  					c := &spec.Containers[i]
   828  					if c.Resources.Limits == nil {
   829  						c.Resources.Limits = corev1.ResourceList{}
   830  					}
   831  					if c.Resources.Requests == nil {
   832  						c.Resources.Requests = corev1.ResourceList{}
   833  					}
   834  					c.Resources.Limits[corev1.ResourceCPU] = resource.MustParse(val)
   835  					c.Resources.Requests[corev1.ResourceCPU] = resource.MustParse(val)
   836  				}
   837  			}
   838  			go futzWithPodSpec(pj.Spec.PodSpec, "12M")
   839  			futzWithPodSpec(testCase.spec.PodSpec, "34M")
   840  		}
   841  	}
   842  }
   843  
   844  func TestNewProwJobWithAnnotations(t *testing.T) {
   845  	var testCases = []struct {
   846  		name                string
   847  		spec                prowapi.ProwJobSpec
   848  		annotations         map[string]string
   849  		expectedAnnotations map[string]string
   850  	}{
   851  		{
   852  			name: "job without annotation",
   853  			spec: prowapi.ProwJobSpec{
   854  				Job:     "job",
   855  				Context: "job-context",
   856  				Type:    prowapi.PeriodicJob,
   857  			},
   858  			annotations: nil,
   859  			expectedAnnotations: map[string]string{
   860  				kube.ProwJobAnnotation: "job",
   861  				kube.ContextAnnotation: "job-context",
   862  			},
   863  		},
   864  		{
   865  			name: "job with annotation",
   866  			spec: prowapi.ProwJobSpec{
   867  				Job:     "job",
   868  				Context: "job-context",
   869  				Type:    prowapi.PeriodicJob,
   870  			},
   871  			annotations: map[string]string{
   872  				"annotation": "foo",
   873  			},
   874  			expectedAnnotations: map[string]string{
   875  				"annotation":           "foo",
   876  				kube.ProwJobAnnotation: "job",
   877  				kube.ContextAnnotation: "job-context",
   878  			},
   879  		},
   880  	}
   881  
   882  	for _, testCase := range testCases {
   883  		pj := NewProwJob(testCase.spec, nil, testCase.annotations)
   884  		if actual, expected := pj.Spec, testCase.spec; !equality.Semantic.DeepEqual(actual, expected) {
   885  			t.Errorf("%s: incorrect ProwJobSpec created: %s", testCase.name, diff.ObjectReflectDiff(actual, expected))
   886  		}
   887  		if actual, expected := pj.Annotations, testCase.expectedAnnotations; !reflect.DeepEqual(actual, expected) {
   888  			t.Errorf("%s: incorrect ProwJob labels created: %s", testCase.name, diff.ObjectReflectDiff(actual, expected))
   889  		}
   890  	}
   891  }
   892  
   893  func TestNewProwJobWithModifiers(t *testing.T) {
   894  	stime, _ := time.Parse(time.DateOnly, "2024-02-27")
   895  	fakeStartTime := metav1.NewTime(stime)
   896  	fakeName := "foo"
   897  
   898  	for _, testCase := range []struct {
   899  		name        string
   900  		spec        *prowapi.ProwJobSpec
   901  		labels      map[string]string
   902  		annotations map[string]string
   903  		options     []Modifier
   904  		wantProwJob prowapi.ProwJob
   905  	}{
   906  		{
   907  			name: "no options",
   908  			spec: &prowapi.ProwJobSpec{
   909  				Job:     "job",
   910  				Context: "job-context",
   911  				Type:    prowapi.PeriodicJob,
   912  			},
   913  			labels:      map[string]string{"extra-label": "foo"},
   914  			annotations: map[string]string{"extra-annotation": "bar"},
   915  			wantProwJob: prowapi.ProwJob{
   916  				TypeMeta: metav1.TypeMeta{
   917  					APIVersion: "prow.k8s.io/v1",
   918  					Kind:       "ProwJob",
   919  				},
   920  				ObjectMeta: metav1.ObjectMeta{
   921  					Name: fakeName,
   922  					Labels: map[string]string{
   923  						kube.CreatedByProw:     "true",
   924  						kube.ProwJobAnnotation: "job",
   925  						kube.ContextAnnotation: "job-context",
   926  						kube.ProwJobTypeLabel:  string(prowapi.PeriodicJob),
   927  						"extra-label":          "foo",
   928  					},
   929  					Annotations: map[string]string{
   930  						kube.ProwJobAnnotation: "job",
   931  						kube.ContextAnnotation: "job-context",
   932  						"extra-annotation":     "bar",
   933  					},
   934  				},
   935  				Spec: prowapi.ProwJobSpec{
   936  					Job:     "job",
   937  					Context: "job-context",
   938  					Type:    prowapi.PeriodicJob,
   939  				},
   940  				Status: prowapi.ProwJobStatus{
   941  					StartTime: fakeStartTime,
   942  					State:     prowapi.TriggeredState,
   943  				},
   944  			},
   945  		},
   946  		{
   947  			name: "require scheduling",
   948  			spec: &prowapi.ProwJobSpec{
   949  				Job:     "job",
   950  				Context: "job-context",
   951  				Type:    prowapi.PeriodicJob,
   952  			},
   953  			labels:      map[string]string{"extra-label": "foo"},
   954  			annotations: map[string]string{"extra-annotation": "bar"},
   955  			options:     []Modifier{RequireScheduling(true)},
   956  			wantProwJob: prowapi.ProwJob{
   957  				TypeMeta: metav1.TypeMeta{
   958  					APIVersion: "prow.k8s.io/v1",
   959  					Kind:       "ProwJob",
   960  				},
   961  				ObjectMeta: metav1.ObjectMeta{
   962  					Name: fakeName,
   963  					Labels: map[string]string{
   964  						kube.CreatedByProw:     "true",
   965  						kube.ProwJobAnnotation: "job",
   966  						kube.ContextAnnotation: "job-context",
   967  						kube.ProwJobTypeLabel:  string(prowapi.PeriodicJob),
   968  						"extra-label":          "foo",
   969  					},
   970  					Annotations: map[string]string{
   971  						kube.ProwJobAnnotation: "job",
   972  						kube.ContextAnnotation: "job-context",
   973  						"extra-annotation":     "bar",
   974  					},
   975  				},
   976  				Spec: prowapi.ProwJobSpec{
   977  					Job:     "job",
   978  					Context: "job-context",
   979  					Type:    prowapi.PeriodicJob,
   980  				},
   981  				Status: prowapi.ProwJobStatus{
   982  					StartTime: fakeStartTime,
   983  					State:     prowapi.SchedulingState,
   984  				},
   985  			},
   986  		},
   987  	} {
   988  		t.Run(testCase.name, func(t *testing.T) {
   989  			pj := NewProwJob(*testCase.spec, testCase.labels, testCase.annotations, testCase.options...)
   990  			pj.Name = fakeName
   991  			pj.Status.StartTime = fakeStartTime
   992  
   993  			if diff := cmp.Diff(&testCase.wantProwJob, &pj); diff != "" {
   994  				t.Errorf("prowjob doesn't match: %s", diff)
   995  			}
   996  		})
   997  	}
   998  }
   999  
  1000  func TestNewPresubmitWithModifiers(t *testing.T) {
  1001  	stime, _ := time.Parse(time.DateOnly, "2024-02-27")
  1002  	fakeStartTime := metav1.NewTime(stime)
  1003  	fakeName := "foo"
  1004  	pr := github.PullRequest{
  1005  		Number:  42,
  1006  		Title:   "pr-title",
  1007  		HTMLURL: "https://github.example.com/kubernetes/test-infra/pull/42",
  1008  		Head: github.PullRequestBranch{
  1009  			SHA: "123456",
  1010  			Ref: "head-ref",
  1011  		},
  1012  		Base: github.PullRequestBranch{
  1013  			Ref: "base-ref",
  1014  			Repo: github.Repo{
  1015  				Name:    "repo-name",
  1016  				HTMLURL: "https://github.example.com/kubernetes/test-infra",
  1017  				Owner: github.User{
  1018  					Login: "kubernetes",
  1019  				},
  1020  			},
  1021  		},
  1022  		User: github.User{
  1023  			Login:   "user-login",
  1024  			HTMLURL: "https://github.example.com/user-login",
  1025  		},
  1026  	}
  1027  
  1028  	for _, testCase := range []struct {
  1029  		name        string
  1030  		spec        *prowapi.ProwJobSpec
  1031  		labels      map[string]string
  1032  		pr          *github.PullRequest
  1033  		baseSHA     string
  1034  		job         *config.Presubmit
  1035  		eventGUID   string
  1036  		options     []Modifier
  1037  		wantProwJob prowapi.ProwJob
  1038  	}{
  1039  		{
  1040  			name:    "no options",
  1041  			labels:  map[string]string{"extra-label": "foo"},
  1042  			pr:      &pr,
  1043  			baseSHA: "987654",
  1044  			job: &config.Presubmit{
  1045  				JobBase:  config.JobBase{Name: "job"},
  1046  				Reporter: config.Reporter{Context: "job-context"},
  1047  			},
  1048  			eventGUID: "0001",
  1049  			wantProwJob: prowapi.ProwJob{
  1050  				TypeMeta: metav1.TypeMeta{
  1051  					APIVersion: "prow.k8s.io/v1",
  1052  					Kind:       "ProwJob",
  1053  				},
  1054  				ObjectMeta: metav1.ObjectMeta{
  1055  					Name: fakeName,
  1056  					Labels: map[string]string{
  1057  						kube.CreatedByProw:     "true",
  1058  						kube.ProwJobAnnotation: "job",
  1059  						kube.ContextAnnotation: "job-context",
  1060  						kube.ProwJobTypeLabel:  string(prowapi.PresubmitJob),
  1061  						github.EventGUID:       "0001",
  1062  						kube.IsOptionalLabel:   "false",
  1063  						kube.OrgLabel:          "kubernetes",
  1064  						kube.RepoLabel:         "repo-name",
  1065  						kube.BaseRefLabel:      "base-ref",
  1066  						kube.PullLabel:         "42",
  1067  						"extra-label":          "foo",
  1068  					},
  1069  					Annotations: map[string]string{
  1070  						kube.ProwJobAnnotation: "job",
  1071  						kube.ContextAnnotation: "job-context",
  1072  					},
  1073  				},
  1074  				Spec: prowapi.ProwJobSpec{
  1075  					Job:     "job",
  1076  					Context: "job-context",
  1077  					Type:    prowapi.PresubmitJob,
  1078  					Report:  true,
  1079  					Refs: &prowapi.Refs{
  1080  						Org:      "kubernetes",
  1081  						Repo:     "repo-name",
  1082  						BaseSHA:  "987654",
  1083  						RepoLink: "https://github.example.com/kubernetes/test-infra",
  1084  						BaseRef:  "base-ref",
  1085  						BaseLink: "https://github.example.com/kubernetes/test-infra/commit/987654",
  1086  						Pulls: []prowapi.Pull{{
  1087  							Number:     42,
  1088  							Author:     "user-login",
  1089  							SHA:        "123456",
  1090  							Title:      "pr-title",
  1091  							HeadRef:    "head-ref",
  1092  							Link:       "https://github.example.com/kubernetes/test-infra/pull/42",
  1093  							CommitLink: "https://github.example.com/kubernetes/test-infra/pull/42/commits/123456",
  1094  							AuthorLink: "https://github.example.com/user-login",
  1095  						}},
  1096  					},
  1097  				},
  1098  				Status: prowapi.ProwJobStatus{
  1099  					StartTime: fakeStartTime,
  1100  					State:     prowapi.TriggeredState,
  1101  				},
  1102  			},
  1103  		},
  1104  		{
  1105  			name:    "require scheduling",
  1106  			labels:  map[string]string{"extra-label": "foo"},
  1107  			pr:      &pr,
  1108  			baseSHA: "987654",
  1109  			job: &config.Presubmit{
  1110  				JobBase:  config.JobBase{Name: "job"},
  1111  				Reporter: config.Reporter{Context: "job-context"},
  1112  			},
  1113  			eventGUID: "0001",
  1114  			options:   []Modifier{RequireScheduling(true)},
  1115  			wantProwJob: prowapi.ProwJob{
  1116  				TypeMeta: metav1.TypeMeta{
  1117  					APIVersion: "prow.k8s.io/v1",
  1118  					Kind:       "ProwJob",
  1119  				},
  1120  				ObjectMeta: metav1.ObjectMeta{
  1121  					Name: fakeName,
  1122  					Labels: map[string]string{
  1123  						kube.CreatedByProw:     "true",
  1124  						kube.ProwJobAnnotation: "job",
  1125  						kube.ContextAnnotation: "job-context",
  1126  						kube.ProwJobTypeLabel:  string(prowapi.PresubmitJob),
  1127  						github.EventGUID:       "0001",
  1128  						kube.IsOptionalLabel:   "false",
  1129  						kube.OrgLabel:          "kubernetes",
  1130  						kube.RepoLabel:         "repo-name",
  1131  						kube.BaseRefLabel:      "base-ref",
  1132  						kube.PullLabel:         "42",
  1133  						"extra-label":          "foo",
  1134  					},
  1135  					Annotations: map[string]string{
  1136  						kube.ProwJobAnnotation: "job",
  1137  						kube.ContextAnnotation: "job-context",
  1138  					},
  1139  				},
  1140  				Spec: prowapi.ProwJobSpec{
  1141  					Job:     "job",
  1142  					Context: "job-context",
  1143  					Type:    prowapi.PresubmitJob,
  1144  					Report:  true,
  1145  					Refs: &prowapi.Refs{
  1146  						Org:      "kubernetes",
  1147  						Repo:     "repo-name",
  1148  						BaseSHA:  "987654",
  1149  						RepoLink: "https://github.example.com/kubernetes/test-infra",
  1150  						BaseRef:  "base-ref",
  1151  						BaseLink: "https://github.example.com/kubernetes/test-infra/commit/987654",
  1152  						Pulls: []prowapi.Pull{{
  1153  							Number:     42,
  1154  							Author:     "user-login",
  1155  							SHA:        "123456",
  1156  							Title:      "pr-title",
  1157  							HeadRef:    "head-ref",
  1158  							Link:       "https://github.example.com/kubernetes/test-infra/pull/42",
  1159  							CommitLink: "https://github.example.com/kubernetes/test-infra/pull/42/commits/123456",
  1160  							AuthorLink: "https://github.example.com/user-login",
  1161  						}},
  1162  					},
  1163  				},
  1164  				Status: prowapi.ProwJobStatus{
  1165  					StartTime: fakeStartTime,
  1166  					State:     prowapi.SchedulingState,
  1167  				},
  1168  			},
  1169  		},
  1170  	} {
  1171  		t.Run(testCase.name, func(t *testing.T) {
  1172  			pj := NewPresubmit(*testCase.pr, testCase.baseSHA, *testCase.job, testCase.eventGUID, testCase.labels, testCase.options...)
  1173  			pj.Name = fakeName
  1174  			pj.Status.StartTime = fakeStartTime
  1175  
  1176  			if diff := cmp.Diff(&testCase.wantProwJob, &pj); diff != "" {
  1177  				t.Errorf("prowjob doesn't match: %s", diff)
  1178  			}
  1179  		})
  1180  	}
  1181  }
  1182  
  1183  func TestJobURL(t *testing.T) {
  1184  	var testCases = []struct {
  1185  		name        string
  1186  		plank       config.Plank
  1187  		pj          prowapi.ProwJob
  1188  		expected    string
  1189  		expectedErr string
  1190  	}{
  1191  		{
  1192  			name: "non-decorated job uses template",
  1193  			plank: config.Plank{
  1194  				Controller: config.Controller{
  1195  					JobURLTemplate: template.Must(template.New("test").Parse("{{.Spec.Type}}")),
  1196  				},
  1197  			},
  1198  			pj:       prowapi.ProwJob{Spec: prowapi.ProwJobSpec{Type: prowapi.PeriodicJob}},
  1199  			expected: "periodic",
  1200  		},
  1201  		{
  1202  			name: "non-decorated job with broken template gives empty string",
  1203  			plank: config.Plank{
  1204  				Controller: config.Controller{
  1205  					JobURLTemplate: template.Must(template.New("test").Parse("{{.Garbage}}")),
  1206  				},
  1207  			},
  1208  			pj:       prowapi.ProwJob{},
  1209  			expected: "",
  1210  		},
  1211  		{
  1212  			name: "decorated job without prefix uses template",
  1213  			plank: config.Plank{
  1214  				Controller: config.Controller{
  1215  					JobURLTemplate: template.Must(template.New("test").Parse("{{.Spec.Type}}")),
  1216  				},
  1217  			},
  1218  			pj:       prowapi.ProwJob{Spec: prowapi.ProwJobSpec{Type: prowapi.PeriodicJob}},
  1219  			expected: "periodic",
  1220  		},
  1221  		{
  1222  			name: "decorated job with prefix uses gcsupload",
  1223  			plank: config.Plank{
  1224  				JobURLPrefixConfig:                       map[string]string{"*": "https://gubernator.com/build"},
  1225  				JobURLPrefixDisableAppendStorageProvider: true,
  1226  			},
  1227  			pj: prowapi.ProwJob{Spec: prowapi.ProwJobSpec{
  1228  				Type: prowapi.PresubmitJob,
  1229  				Refs: &prowapi.Refs{
  1230  					Org:   "org",
  1231  					Repo:  "repo",
  1232  					Pulls: []prowapi.Pull{{Number: 1}},
  1233  				},
  1234  				DecorationConfig: &prowapi.DecorationConfig{GCSConfiguration: &prowapi.GCSConfiguration{
  1235  					Bucket:       "bucket",
  1236  					PathStrategy: prowapi.PathStrategyExplicit,
  1237  				}},
  1238  			}},
  1239  			expected: "https://gubernator.com/build/bucket/pr-logs/pull/org_repo/1",
  1240  		},
  1241  		{
  1242  			name: "decorated job with prefix uses gcsupload and new bucket format with gcs (new job url format)",
  1243  			plank: config.Plank{
  1244  				JobURLPrefixConfig: map[string]string{"*": "https://prow.k8s.io/view/"},
  1245  			},
  1246  			pj: prowapi.ProwJob{Spec: prowapi.ProwJobSpec{
  1247  				Type: prowapi.PresubmitJob,
  1248  				Refs: &prowapi.Refs{
  1249  					Org:   "org",
  1250  					Repo:  "repo",
  1251  					Pulls: []prowapi.Pull{{Number: 1}},
  1252  				},
  1253  				DecorationConfig: &prowapi.DecorationConfig{GCSConfiguration: &prowapi.GCSConfiguration{
  1254  					Bucket:       "gs://bucket",
  1255  					PathStrategy: prowapi.PathStrategyExplicit,
  1256  				}},
  1257  			}},
  1258  			expected: "https://prow.k8s.io/view/gs/bucket/pr-logs/pull/org_repo/1",
  1259  		},
  1260  		{
  1261  			name: "decorated job with prefix uses gcsupload and new bucket format with s3",
  1262  			plank: config.Plank{
  1263  				JobURLPrefixConfig: map[string]string{"*": "https://prow.k8s.io/view/"},
  1264  			},
  1265  			pj: prowapi.ProwJob{Spec: prowapi.ProwJobSpec{
  1266  				Type: prowapi.PresubmitJob,
  1267  				Refs: &prowapi.Refs{
  1268  					Org:   "org",
  1269  					Repo:  "repo",
  1270  					Pulls: []prowapi.Pull{{Number: 1}},
  1271  				},
  1272  				DecorationConfig: &prowapi.DecorationConfig{GCSConfiguration: &prowapi.GCSConfiguration{
  1273  					Bucket:       "s3://bucket",
  1274  					PathStrategy: prowapi.PathStrategyExplicit,
  1275  				}},
  1276  			}},
  1277  			expected: "https://prow.k8s.io/view/s3/bucket/pr-logs/pull/org_repo/1",
  1278  		},
  1279  		{
  1280  			name: "decorated job with prefix uses gcsupload with valid bucket with multiple separators",
  1281  			plank: config.Plank{
  1282  				JobURLPrefixConfig: map[string]string{"*": "https://prow.k8s.io/view/"},
  1283  			},
  1284  			pj: prowapi.ProwJob{Spec: prowapi.ProwJobSpec{
  1285  				Type: prowapi.PresubmitJob,
  1286  				Refs: &prowapi.Refs{
  1287  					Org:   "org",
  1288  					Repo:  "repo",
  1289  					Pulls: []prowapi.Pull{{Number: 1}},
  1290  				},
  1291  				DecorationConfig: &prowapi.DecorationConfig{GCSConfiguration: &prowapi.GCSConfiguration{
  1292  					Bucket:       "gs://my-floppy-backup/a://doom2.wad.006",
  1293  					PathStrategy: prowapi.PathStrategyExplicit,
  1294  				}},
  1295  			}},
  1296  			expected: "https://prow.k8s.io/view/gs/my-floppy-backup/a:/doom2.wad.006/pr-logs/pull/org_repo/1",
  1297  		},
  1298  	}
  1299  
  1300  	logger := logrus.New()
  1301  	for _, testCase := range testCases {
  1302  		t.Run(testCase.name, func(t *testing.T) {
  1303  			actual, actualErr := JobURL(testCase.plank, testCase.pj, logger.WithField("name", testCase.name))
  1304  			var actualErrStr string
  1305  			if actualErr != nil {
  1306  				actualErrStr = actualErr.Error()
  1307  			}
  1308  
  1309  			if actualErrStr != testCase.expectedErr {
  1310  				t.Errorf("%s: expectedErr = %v, but got %v", testCase.name, testCase.expectedErr, actualErrStr)
  1311  			}
  1312  			if actual != testCase.expected {
  1313  				t.Errorf("%s: expected URL to be %q but got %q", testCase.name, testCase.expected, actual)
  1314  			}
  1315  		})
  1316  	}
  1317  }
  1318  
  1319  func TestSetReportDefault(t *testing.T) {
  1320  	tests := []struct {
  1321  		name     string
  1322  		spec     *prowapi.ProwJobSpec
  1323  		wantSpec *prowapi.ProwJobSpec
  1324  	}{
  1325  		{
  1326  			name: "base-case",
  1327  			spec: &prowapi.ProwJobSpec{
  1328  				ReporterConfig: &prowapi.ReporterConfig{
  1329  					Slack: &prowapi.SlackReporterConfig{
  1330  						JobStatesToReport: []prowapi.ProwJobState{},
  1331  					},
  1332  				},
  1333  			},
  1334  			wantSpec: &prowapi.ProwJobSpec{
  1335  				ReporterConfig: &prowapi.ReporterConfig{
  1336  					Slack: &prowapi.SlackReporterConfig{
  1337  						JobStatesToReport: []prowapi.ProwJobState{},
  1338  						Report:            boolPtr(false),
  1339  					},
  1340  				},
  1341  			},
  1342  		},
  1343  		{
  1344  			name: "to-report",
  1345  			spec: &prowapi.ProwJobSpec{
  1346  				ReporterConfig: &prowapi.ReporterConfig{
  1347  					Slack: &prowapi.SlackReporterConfig{
  1348  						JobStatesToReport: []prowapi.ProwJobState{prowapi.AbortedState},
  1349  					},
  1350  				},
  1351  			},
  1352  			wantSpec: &prowapi.ProwJobSpec{
  1353  				ReporterConfig: &prowapi.ReporterConfig{
  1354  					Slack: &prowapi.SlackReporterConfig{
  1355  						JobStatesToReport: []prowapi.ProwJobState{prowapi.AbortedState},
  1356  						Report:            boolPtr(true),
  1357  					},
  1358  				},
  1359  			},
  1360  		},
  1361  		{
  1362  			name: "no-slack-report",
  1363  			spec: &prowapi.ProwJobSpec{
  1364  				ReporterConfig: &prowapi.ReporterConfig{},
  1365  			},
  1366  			wantSpec: &prowapi.ProwJobSpec{
  1367  				ReporterConfig: &prowapi.ReporterConfig{},
  1368  			},
  1369  		},
  1370  	}
  1371  
  1372  	for _, tc := range tests {
  1373  		t.Run(tc.name, func(t *testing.T) {
  1374  			setReportDefault(tc.spec)
  1375  			if diff := cmp.Diff(tc.wantSpec, tc.spec); diff != "" {
  1376  				t.Fatalf("Spec mismatch. Want(-), got(+):\n%s", diff)
  1377  			}
  1378  		})
  1379  	}
  1380  }
  1381  
  1382  func TestCreateRefs(t *testing.T) {
  1383  	pr := github.PullRequest{
  1384  		Number:  42,
  1385  		Title:   "hello world",
  1386  		HTMLURL: "https://github.example.com/kubernetes/Hello-World/pull/42",
  1387  		Head: github.PullRequestBranch{
  1388  			SHA: "123456",
  1389  			Ref: "my-great-change",
  1390  		},
  1391  		Base: github.PullRequestBranch{
  1392  			Ref: "master",
  1393  			Repo: github.Repo{
  1394  				Name:    "Hello-World",
  1395  				HTMLURL: "https://github.example.com/kubernetes/Hello-World",
  1396  				Owner: github.User{
  1397  					Login: "kubernetes",
  1398  				},
  1399  			},
  1400  		},
  1401  		User: github.User{
  1402  			Login:   "ibzib",
  1403  			HTMLURL: "https://github.example.com/ibzib",
  1404  		},
  1405  	}
  1406  	expected := prowapi.Refs{
  1407  		Org:      "kubernetes",
  1408  		Repo:     "Hello-World",
  1409  		RepoLink: "https://github.example.com/kubernetes/Hello-World",
  1410  		BaseRef:  "master",
  1411  		BaseSHA:  "abcdef",
  1412  		BaseLink: "https://github.example.com/kubernetes/Hello-World/commit/abcdef",
  1413  		Pulls: []prowapi.Pull{
  1414  			{
  1415  				Number:     42,
  1416  				Author:     "ibzib",
  1417  				SHA:        "123456",
  1418  				HeadRef:    "my-great-change",
  1419  				Title:      "hello world",
  1420  				Link:       "https://github.example.com/kubernetes/Hello-World/pull/42",
  1421  				AuthorLink: "https://github.example.com/ibzib",
  1422  				CommitLink: "https://github.example.com/kubernetes/Hello-World/pull/42/commits/123456",
  1423  			},
  1424  		},
  1425  	}
  1426  	if actual := createRefs(pr, "abcdef"); !reflect.DeepEqual(expected, actual) {
  1427  		t.Errorf("diff between expected and actual refs:%s", diff.ObjectReflectDiff(expected, actual))
  1428  	}
  1429  }
  1430  
  1431  func TestSpecFromJobBase(t *testing.T) {
  1432  	permittedGroups := []int{1234, 5678}
  1433  	permittedUsers := []string{"authorized_user", "another_authorized_user"}
  1434  	permittedOrgs := []string{"kubernetes", "kubernetes-sigs"}
  1435  	rerunAuthConfig := prowapi.RerunAuthConfig{
  1436  		AllowAnyone:   false,
  1437  		GitHubTeamIDs: permittedGroups,
  1438  		GitHubUsers:   permittedUsers,
  1439  		GitHubOrgs:    permittedOrgs,
  1440  	}
  1441  	testCases := []struct {
  1442  		name    string
  1443  		jobBase config.JobBase
  1444  		verify  func(prowapi.ProwJobSpec) error
  1445  	}{
  1446  		{
  1447  			name: "Verify reporter config gets copied",
  1448  			jobBase: config.JobBase{
  1449  				ReporterConfig: &prowapi.ReporterConfig{
  1450  					Slack: &prowapi.SlackReporterConfig{
  1451  						Channel: "my-channel",
  1452  					},
  1453  				},
  1454  			},
  1455  			verify: func(pj prowapi.ProwJobSpec) error {
  1456  				if pj.ReporterConfig == nil {
  1457  					return errors.New("Expected ReporterConfig to be non-nil")
  1458  				}
  1459  				if pj.ReporterConfig.Slack == nil {
  1460  					return errors.New("Expected ReporterConfig.Slack to be non-nil")
  1461  				}
  1462  				if pj.ReporterConfig.Slack.Channel != "my-channel" {
  1463  					return fmt.Errorf("Expected pj.ReporterConfig.Slack.Channel to be \"my-channel\", was %q",
  1464  						pj.ReporterConfig.Slack.Channel)
  1465  				}
  1466  				return nil
  1467  			},
  1468  		},
  1469  		{
  1470  			name: "Verify rerun permissions gets copied",
  1471  			jobBase: config.JobBase{
  1472  				RerunAuthConfig: &rerunAuthConfig,
  1473  			},
  1474  			verify: func(pj prowapi.ProwJobSpec) error {
  1475  				if pj.RerunAuthConfig.AllowAnyone {
  1476  					return errors.New("Expected RerunAuthConfig.AllowAnyone to be false")
  1477  				}
  1478  				if pj.RerunAuthConfig.GitHubTeamIDs == nil {
  1479  					return errors.New("Expected RerunAuthConfig.GitHubTeamIDs to be non-nil")
  1480  				}
  1481  				if pj.RerunAuthConfig.GitHubUsers == nil {
  1482  					return errors.New("Expected RerunAuthConfig.GitHubUsers to be non-nil")
  1483  				}
  1484  				if pj.RerunAuthConfig.GitHubOrgs == nil {
  1485  					return errors.New("Expected RerunAuthConfig.GitHubOrgs to be non-nil")
  1486  				}
  1487  				return nil
  1488  			},
  1489  		},
  1490  		{
  1491  			name: "Verify hidden property gets copied",
  1492  			jobBase: config.JobBase{
  1493  				Hidden: true,
  1494  			},
  1495  			verify: func(pj prowapi.ProwJobSpec) error {
  1496  				if !pj.Hidden {
  1497  					return errors.New("hidden property didnt get copied")
  1498  				}
  1499  				return nil
  1500  			},
  1501  		},
  1502  		{
  1503  			name: "Verify DecorateExtraRefs default true",
  1504  			jobBase: config.JobBase{
  1505  				UtilityConfig: config.UtilityConfig{
  1506  					DecorationConfig: &prowapi.DecorationConfig{
  1507  						BloblessFetch: boolPtr(true),
  1508  					},
  1509  					ExtraRefs: []prowapi.Refs{
  1510  						{
  1511  							Org:           "true-org",
  1512  							BloblessFetch: boolPtr(true),
  1513  						},
  1514  						{
  1515  							Org:           "false-org",
  1516  							BloblessFetch: boolPtr(false),
  1517  						},
  1518  						{
  1519  							Org:           "default-org",
  1520  							BloblessFetch: nil,
  1521  						},
  1522  					},
  1523  				},
  1524  			},
  1525  			verify: func(pj prowapi.ProwJobSpec) error {
  1526  				for _, r := range pj.ExtraRefs {
  1527  					got := r.BloblessFetch
  1528  					want := boolPtr(r.Org != "false-org")
  1529  					if diff := cmp.Diff(want, got); diff != "" {
  1530  						return fmt.Errorf("ExtraRefs BloblessFetch for %s differs (-want +got)\n%s", r.Org, diff)
  1531  					}
  1532  				}
  1533  				return nil
  1534  			},
  1535  		},
  1536  		{
  1537  			name: "Verify DecorateExtraRefs default false",
  1538  			jobBase: config.JobBase{
  1539  				UtilityConfig: config.UtilityConfig{
  1540  					DecorationConfig: &prowapi.DecorationConfig{
  1541  						BloblessFetch: boolPtr(false),
  1542  					},
  1543  					ExtraRefs: []prowapi.Refs{
  1544  						{
  1545  							Org:           "true-org",
  1546  							BloblessFetch: boolPtr(true),
  1547  						},
  1548  						{
  1549  							Org:           "false-org",
  1550  							BloblessFetch: boolPtr(false),
  1551  						},
  1552  						{
  1553  							Org:           "default-org",
  1554  							BloblessFetch: nil,
  1555  						},
  1556  					},
  1557  				},
  1558  			},
  1559  			verify: func(pj prowapi.ProwJobSpec) error {
  1560  				for _, r := range pj.ExtraRefs {
  1561  					got := r.BloblessFetch
  1562  					want := boolPtr(r.Org == "true-org")
  1563  					if diff := cmp.Diff(want, got); diff != "" {
  1564  						return fmt.Errorf("ExtraRefs BloblessFetch for %s differs (-want +got)\n%s", r.Org, diff)
  1565  					}
  1566  				}
  1567  				return nil
  1568  			},
  1569  		},
  1570  		{
  1571  			name: "Verify DecorateExtraRefs no decoration preserves existing",
  1572  			jobBase: config.JobBase{
  1573  				UtilityConfig: config.UtilityConfig{
  1574  					ExtraRefs: []prowapi.Refs{
  1575  						{
  1576  							Org:           "true-org",
  1577  							BloblessFetch: boolPtr(true),
  1578  						},
  1579  						{
  1580  							Org:           "false-org",
  1581  							BloblessFetch: boolPtr(false),
  1582  						},
  1583  						{
  1584  							Org:           "default-org",
  1585  							BloblessFetch: nil,
  1586  						},
  1587  					},
  1588  				},
  1589  			},
  1590  			verify: func(pj prowapi.ProwJobSpec) error {
  1591  				for _, r := range pj.ExtraRefs {
  1592  					got := r.BloblessFetch
  1593  					var want *bool
  1594  					switch r.Org {
  1595  					case "true-org":
  1596  						want = boolPtr(true)
  1597  					case "false-org":
  1598  						want = boolPtr(false)
  1599  					case "default-org":
  1600  						// Keep the unset default value.
  1601  						want = nil
  1602  					}
  1603  					if diff := cmp.Diff(want, got); diff != "" {
  1604  						return fmt.Errorf("ExtraRefs BloblessFetch for %s differs (-want +got)\n%s", r.Org, diff)
  1605  					}
  1606  				}
  1607  				return nil
  1608  			},
  1609  		},
  1610  	}
  1611  
  1612  	for _, tc := range testCases {
  1613  		t.Run(tc.name, func(t *testing.T) {
  1614  			pj := specFromJobBase(tc.jobBase)
  1615  			if err := tc.verify(pj); err != nil {
  1616  				t.Fatalf("Verification failed: %v", err)
  1617  			}
  1618  		})
  1619  	}
  1620  }
  1621  
  1622  func TestPeriodicSpec(t *testing.T) {
  1623  	testCases := []struct {
  1624  		name   string
  1625  		config config.Periodic
  1626  		verify func(prowapi.ProwJobSpec) error
  1627  	}{
  1628  		{
  1629  			name:   "Report gets set to true",
  1630  			config: config.Periodic{},
  1631  			verify: func(p prowapi.ProwJobSpec) error {
  1632  				if !p.Report {
  1633  					return errors.New("report is not true")
  1634  				}
  1635  				return nil
  1636  			},
  1637  		},
  1638  	}
  1639  
  1640  	for _, tc := range testCases {
  1641  		if err := tc.verify(PeriodicSpec(tc.config)); err != nil {
  1642  			t.Error(err)
  1643  		}
  1644  	}
  1645  }