k8s.io/test-infra@v0.0.0-20240520184403-27c6b4c223d8/config/tests/jobs/from_prow_test.go (about) 1 /* 2 Copyright 2018 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 tests 18 19 import ( 20 "regexp" 21 "testing" 22 23 v1 "k8s.io/api/core/v1" 24 "k8s.io/apimachinery/pkg/util/sets" 25 "sigs.k8s.io/prow/pkg/config" 26 ) 27 28 // This file contains tests that previously lived in prow/config/jobs_test.go and prow/config/jobtests/job_config_test.go 29 // Some (all?) of these tests are not specific to the K8s Prow instance and should 30 // be ported to checkconfig so that all Prow instances can benefit from them. 31 // TODO: move such validations to checkconfig if they are not present already. 32 33 var podRe = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?$`) 34 35 // Returns if two brancher has overlapping branches 36 func checkOverlapBrancher(b1, b2 config.Brancher) bool { 37 if b1.RunsAgainstAllBranch() || b2.RunsAgainstAllBranch() { 38 return true 39 } 40 41 for _, run1 := range b1.Branches { 42 if b2.ShouldRun(run1) { 43 return true 44 } 45 } 46 47 for _, run2 := range b2.Branches { 48 if b1.ShouldRun(run2) { 49 return true 50 } 51 } 52 53 return false 54 } 55 56 func TestPresubmits(t *testing.T) { 57 if len(c.PresubmitsStatic) == 0 { 58 t.Fatalf("No jobs found in presubmit.yaml.") 59 } 60 61 for _, rootJobs := range c.PresubmitsStatic { 62 for i, job := range rootJobs { 63 if job.Name == "" { 64 t.Errorf("Job %v needs a name.", job) 65 continue 66 } 67 if !job.SkipReport && job.Context == "" { 68 t.Errorf("Job %s needs a context.", job.Name) 69 } 70 if job.RerunCommand == "" || job.Trigger == "" { 71 t.Errorf("Job %s needs a trigger and a rerun command.", job.Name) 72 continue 73 } 74 75 if len(job.Brancher.Branches) > 0 && len(job.Brancher.SkipBranches) > 0 { 76 t.Errorf("Job %s : Cannot have both branches and skip_branches set", job.Name) 77 } 78 // Next check that the rerun command doesn't run any other jobs. 79 for j, job2 := range rootJobs[i+1:] { 80 if job.Name == job2.Name { 81 // Make sure max_concurrency are the same 82 if job.MaxConcurrency != job2.MaxConcurrency { 83 t.Errorf("Jobs %s share same name but has different max_concurrency", job.Name) 84 } 85 // Make sure branches are not overlapping 86 if checkOverlapBrancher(job.Brancher, job2.Brancher) { 87 t.Errorf("Two jobs have the same name: %s, and have conflicting branches", job.Name) 88 } 89 } else { 90 if job.Context == job2.Context { 91 t.Errorf("Jobs %s and %s have the same context: %s", job.Name, job2.Name, job.Context) 92 } 93 if job2.TriggerMatches(job.RerunCommand) { 94 t.Errorf("%d, %d, RerunCommand \"%s\" from job %s matches \"%v\" from job %s but shouldn't.", i, j, job.RerunCommand, job.Name, job2.Trigger, job2.Name) 95 } 96 } 97 } 98 } 99 } 100 } 101 102 // TODO(krzyzacy): technically this, and TestPresubmits above should belong to config/ instead of prow/ 103 func TestPostsubmits(t *testing.T) { 104 if len(c.PostsubmitsStatic) == 0 { 105 t.Fatalf("No jobs found in presubmit.yaml.") 106 } 107 108 for _, rootJobs := range c.PostsubmitsStatic { 109 for i, job := range rootJobs { 110 if job.Name == "" { 111 t.Errorf("Job %v needs a name.", job) 112 continue 113 } 114 if !job.SkipReport && job.Context == "" { 115 t.Errorf("Job %s needs a context.", job.Name) 116 } 117 118 if len(job.Brancher.Branches) > 0 && len(job.Brancher.SkipBranches) > 0 { 119 t.Errorf("Job %s : Cannot have both branches and skip_branches set", job.Name) 120 } 121 // Next check that the rerun command doesn't run any other jobs. 122 for _, job2 := range rootJobs[i+1:] { 123 if job.Name == job2.Name { 124 // Make sure max_concurrency are the same 125 if job.MaxConcurrency != job2.MaxConcurrency { 126 t.Errorf("Jobs %s share same name but has different max_concurrency", job.Name) 127 } 128 // Make sure branches are not overlapping 129 if checkOverlapBrancher(job.Brancher, job2.Brancher) { 130 t.Errorf("Two jobs have the same name: %s, and have conflicting branches", job.Name) 131 } 132 } else { 133 if job.Context == job2.Context { 134 t.Errorf("Jobs %s and %s have the same context: %s", job.Name, job2.Name, job.Context) 135 } 136 } 137 } 138 } 139 } 140 } 141 142 func TestValidPodNames(t *testing.T) { 143 for _, j := range c.AllStaticPresubmits([]string{}) { 144 if !podRe.MatchString(j.Name) { 145 t.Errorf("Job \"%s\" must match regex \"%s\".", j.Name, podRe.String()) 146 } 147 } 148 for _, j := range c.AllStaticPostsubmits([]string{}) { 149 if !podRe.MatchString(j.Name) { 150 t.Errorf("Job \"%s\" must match regex \"%s\".", j.Name, podRe.String()) 151 } 152 } 153 for _, j := range c.AllPeriodics() { 154 if !podRe.MatchString(j.Name) { 155 t.Errorf("Job \"%s\" must match regex \"%s\".", j.Name, podRe.String()) 156 } 157 } 158 } 159 160 func TestNoDuplicateJobs(t *testing.T) { 161 // Presubmit test is covered under TestPresubmits() above 162 163 allJobs := make(map[string]bool) 164 for _, j := range c.AllStaticPostsubmits([]string{}) { 165 if allJobs[j.Name] { 166 t.Errorf("Found duplicate job in postsubmit: %s.", j.Name) 167 } 168 allJobs[j.Name] = true 169 } 170 171 allJobs = make(map[string]bool) 172 for _, j := range c.AllPeriodics() { 173 if allJobs[j.Name] { 174 t.Errorf("Found duplicate job in periodic %s.", j.Name) 175 } 176 allJobs[j.Name] = true 177 } 178 } 179 180 func missingVolumesForContainer(mounts []v1.VolumeMount, volumes []v1.Volume) sets.Set[string] { 181 mountNames := sets.New[string]() 182 volumeNames := sets.New[string]() 183 for _, m := range mounts { 184 mountNames.Insert(m.Name) 185 } 186 for _, v := range volumes { 187 volumeNames.Insert(v.Name) 188 } 189 return mountNames.Difference(volumeNames) 190 } 191 192 func missingVolumesForSpec(spec *v1.PodSpec) map[string]sets.Set[string] { 193 malformed := map[string]sets.Set[string]{} 194 for _, container := range spec.InitContainers { 195 malformed[container.Name] = missingVolumesForContainer(container.VolumeMounts, spec.Volumes) 196 } 197 for _, container := range spec.Containers { 198 malformed[container.Name] = missingVolumesForContainer(container.VolumeMounts, spec.Volumes) 199 } 200 return malformed 201 } 202 203 func missingMountsForSpec(spec *v1.PodSpec) sets.Set[string] { 204 mountNames := sets.New[string]() 205 volumeNames := sets.New[string]() 206 for _, container := range spec.Containers { 207 for _, m := range container.VolumeMounts { 208 mountNames.Insert(m.Name) 209 } 210 } 211 for _, container := range spec.InitContainers { 212 for _, m := range container.VolumeMounts { 213 mountNames.Insert(m.Name) 214 } 215 } 216 for _, v := range spec.Volumes { 217 volumeNames.Insert(v.Name) 218 } 219 return volumeNames.Difference(mountNames) 220 } 221 222 // verify that all volume mounts reference volumes that exist 223 func TestMountsHaveVolumes(t *testing.T) { 224 for _, job := range c.AllStaticPresubmits(nil) { 225 if job.Spec != nil { 226 validateVolumesAndMounts(job.Name, job.Spec, t) 227 } 228 } 229 for _, job := range c.AllStaticPostsubmits(nil) { 230 if job.Spec != nil { 231 validateVolumesAndMounts(job.Name, job.Spec, t) 232 } 233 } 234 for _, job := range c.AllPeriodics() { 235 if job.Spec != nil { 236 validateVolumesAndMounts(job.Name, job.Spec, t) 237 } 238 } 239 } 240 241 func validateVolumesAndMounts(name string, spec *v1.PodSpec, t *testing.T) { 242 for container, missingVolumes := range missingVolumesForSpec(spec) { 243 if len(missingVolumes) > 0 { 244 t.Errorf("job %s in container %s has mounts that are missing volumes: %v", name, container, sets.List(missingVolumes)) 245 } 246 } 247 if missingMounts := missingMountsForSpec(spec); len(missingMounts) > 0 { 248 t.Errorf("job %s has volumes that are not mounted: %v", name, sets.List(missingMounts)) 249 } 250 }