github.com/yrj2011/jx-test-infra@v0.0.0-20190529031832-7a2065ee98eb/prow/tide/status_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 tide 18 19 import ( 20 "fmt" 21 "testing" 22 23 "github.com/shurcooL/githubv4" 24 "github.com/sirupsen/logrus" 25 26 "k8s.io/test-infra/prow/config" 27 "k8s.io/test-infra/prow/github" 28 ) 29 30 func TestExpectedStatus(t *testing.T) { 31 neededLabels := []string{"need-1", "need-2", "need-a-very-super-duper-extra-not-short-at-all-label-name"} 32 forbiddenLabels := []string{"forbidden-1", "forbidden-2"} 33 testcases := []struct { 34 name string 35 36 baseref string 37 branchWhitelist []string 38 branchBlacklist []string 39 labels []string 40 milestone string 41 contexts []Context 42 inPool bool 43 44 state string 45 desc string 46 }{ 47 { 48 name: "in pool", 49 inPool: true, 50 51 state: github.StatusSuccess, 52 desc: statusInPool, 53 }, 54 { 55 name: "check truncation of label list", 56 inPool: false, 57 58 state: github.StatusPending, 59 desc: fmt.Sprintf(statusNotInPool, " Needs need-1, need-2 labels."), 60 }, 61 { 62 name: "check truncation of label list is not excessive", 63 labels: append([]string{}, neededLabels[:2]...), 64 inPool: false, 65 66 state: github.StatusPending, 67 desc: fmt.Sprintf(statusNotInPool, " Needs need-a-very-super-duper-extra-not-short-at-all-label-name label."), 68 }, 69 { 70 name: "has forbidden labels", 71 labels: append(append([]string{}, neededLabels...), forbiddenLabels...), 72 inPool: false, 73 74 state: github.StatusPending, 75 desc: fmt.Sprintf(statusNotInPool, " Should not have forbidden-1, forbidden-2 labels."), 76 }, 77 { 78 name: "has one forbidden label", 79 labels: append(append([]string{}, neededLabels...), forbiddenLabels[0]), 80 inPool: false, 81 82 state: github.StatusPending, 83 desc: fmt.Sprintf(statusNotInPool, " Should not have forbidden-1 label."), 84 }, 85 { 86 name: "only mention one requirement class", 87 labels: append(append([]string{}, neededLabels[1:]...), forbiddenLabels[0]), 88 inPool: false, 89 90 state: github.StatusPending, 91 desc: fmt.Sprintf(statusNotInPool, " Needs need-1 label."), 92 }, 93 { 94 name: "against excluded branch", 95 baseref: "bad", 96 branchBlacklist: []string{"bad"}, 97 labels: neededLabels, 98 inPool: false, 99 100 state: github.StatusPending, 101 desc: fmt.Sprintf(statusNotInPool, " Merging to branch bad is forbidden."), 102 }, 103 { 104 name: "not against included branch", 105 baseref: "bad", 106 branchWhitelist: []string{"good"}, 107 labels: neededLabels, 108 inPool: false, 109 110 state: github.StatusPending, 111 desc: fmt.Sprintf(statusNotInPool, " Merging to branch bad is forbidden."), 112 }, 113 { 114 name: "only failed tide context", 115 labels: neededLabels, 116 milestone: "v1.0", 117 contexts: []Context{{Context: githubv4.String(statusContext), State: githubv4.StatusStateError}}, 118 inPool: false, 119 120 state: github.StatusPending, 121 desc: fmt.Sprintf(statusNotInPool, ""), 122 }, 123 { 124 name: "single bad context", 125 labels: neededLabels, 126 contexts: []Context{{Context: githubv4.String("job-name"), State: githubv4.StatusStateError}}, 127 inPool: false, 128 129 state: github.StatusPending, 130 desc: fmt.Sprintf(statusNotInPool, " Job job-name has not succeeded."), 131 }, 132 { 133 name: "multiple bad contexts", 134 labels: neededLabels, 135 contexts: []Context{ 136 {Context: githubv4.String("job-name"), State: githubv4.StatusStateError}, 137 {Context: githubv4.String("other-job-name"), State: githubv4.StatusStateError}, 138 }, 139 inPool: false, 140 141 state: github.StatusPending, 142 desc: fmt.Sprintf(statusNotInPool, " Jobs job-name, other-job-name have not succeeded."), 143 }, 144 { 145 name: "wrong milestone", 146 labels: neededLabels, 147 milestone: "v1.1", 148 contexts: []Context{{Context: githubv4.String("job-name"), State: githubv4.StatusStateSuccess}}, 149 inPool: false, 150 151 state: github.StatusPending, 152 desc: fmt.Sprintf(statusNotInPool, " Must be in milestone v1.0."), 153 }, 154 { 155 name: "unknown requirement", 156 labels: neededLabels, 157 milestone: "v1.0", 158 contexts: []Context{{Context: githubv4.String("job-name"), State: githubv4.StatusStateSuccess}}, 159 inPool: false, 160 161 state: github.StatusPending, 162 desc: fmt.Sprintf(statusNotInPool, ""), 163 }, 164 { 165 name: "check that min diff query is used", 166 labels: []string{"3", "4", "5", "6", "7"}, 167 inPool: false, 168 169 state: github.StatusPending, 170 desc: fmt.Sprintf(statusNotInPool, " Needs 1, 2 labels."), 171 }, 172 } 173 174 for _, tc := range testcases { 175 t.Logf("Test Case: %q\n", tc.name) 176 queriesByRepo := config.QueryMap(map[string]config.TideQueries{ 177 "": { 178 config.TideQuery{ 179 ExcludedBranches: tc.branchBlacklist, 180 IncludedBranches: tc.branchWhitelist, 181 Labels: neededLabels, 182 MissingLabels: forbiddenLabels, 183 Milestone: "v1.0", 184 }, 185 config.TideQuery{ 186 Labels: []string{"1", "2", "3", "4", "5", "6", "7"}, // lots of requirements 187 }, 188 }, 189 }) 190 var pr PullRequest 191 pr.BaseRef = struct { 192 Name githubv4.String 193 Prefix githubv4.String 194 }{ 195 Name: githubv4.String(tc.baseref), 196 } 197 for _, label := range tc.labels { 198 pr.Labels.Nodes = append( 199 pr.Labels.Nodes, 200 struct{ Name githubv4.String }{Name: githubv4.String(label)}, 201 ) 202 } 203 if len(tc.contexts) > 0 { 204 pr.HeadRefOID = githubv4.String("head") 205 pr.Commits.Nodes = append( 206 pr.Commits.Nodes, 207 struct{ Commit Commit }{ 208 Commit: Commit{ 209 Status: struct{ Contexts []Context }{ 210 Contexts: tc.contexts, 211 }, 212 OID: githubv4.String("head"), 213 }, 214 }, 215 ) 216 } 217 if tc.milestone != "" { 218 pr.Milestone = &struct { 219 Title githubv4.String 220 }{githubv4.String(tc.milestone)} 221 } 222 var pool map[string]PullRequest 223 if tc.inPool { 224 pool = map[string]PullRequest{"#0": {}} 225 } 226 227 state, desc := expectedStatus(queriesByRepo, &pr, pool, &config.TideContextPolicy{}) 228 if state != tc.state { 229 t.Errorf("Expected status state %q, but got %q.", string(tc.state), string(state)) 230 } 231 if desc != tc.desc { 232 t.Errorf("Expected status description %q, but got %q.", tc.desc, desc) 233 } 234 } 235 } 236 237 func TestSetStatuses(t *testing.T) { 238 statusNotInPoolEmpty := fmt.Sprintf(statusNotInPool, "") 239 testcases := []struct { 240 name string 241 242 inPool bool 243 hasContext bool 244 state githubv4.StatusState 245 desc string 246 247 shouldSet bool 248 }{ 249 { 250 name: "in pool with proper context", 251 252 inPool: true, 253 hasContext: true, 254 state: githubv4.StatusStateSuccess, 255 desc: statusInPool, 256 257 shouldSet: false, 258 }, 259 { 260 name: "in pool without context", 261 262 inPool: true, 263 hasContext: false, 264 265 shouldSet: true, 266 }, 267 { 268 name: "in pool with improper context", 269 270 inPool: true, 271 hasContext: true, 272 state: githubv4.StatusStateSuccess, 273 desc: statusNotInPoolEmpty, 274 275 shouldSet: true, 276 }, 277 { 278 name: "in pool with wrong state", 279 280 inPool: true, 281 hasContext: true, 282 state: githubv4.StatusStatePending, 283 desc: statusInPool, 284 285 shouldSet: true, 286 }, 287 { 288 name: "not in pool with proper context", 289 290 inPool: false, 291 hasContext: true, 292 state: githubv4.StatusStatePending, 293 desc: statusNotInPoolEmpty, 294 295 shouldSet: false, 296 }, 297 { 298 name: "not in pool with improper context", 299 300 inPool: false, 301 hasContext: true, 302 state: githubv4.StatusStatePending, 303 desc: statusInPool, 304 305 shouldSet: true, 306 }, 307 { 308 name: "not in pool with no context", 309 310 inPool: false, 311 hasContext: false, 312 313 shouldSet: true, 314 }, 315 } 316 for _, tc := range testcases { 317 var pr PullRequest 318 pr.Commits.Nodes = []struct{ Commit Commit }{{}} 319 if tc.hasContext { 320 pr.Commits.Nodes[0].Commit.Status.Contexts = []Context{ 321 { 322 Context: githubv4.String(statusContext), 323 State: tc.state, 324 Description: githubv4.String(tc.desc), 325 }, 326 } 327 } 328 pool := make(map[string]PullRequest) 329 if tc.inPool { 330 pool[prKey(&pr)] = pr 331 } 332 fc := &fgc{} 333 ca := &config.Agent{} 334 ca.Set(&config.Config{}) 335 // setStatuses logs instead of returning errors. 336 // Construct a logger to watch for errors to be printed. 337 log := logrus.WithField("component", "tide") 338 initialLog, err := log.String() 339 if err != nil { 340 t.Fatalf("Failed to get log output before testing: %v", err) 341 } 342 343 sc := &statusController{ghc: fc, ca: ca, logger: log} 344 sc.setStatuses([]PullRequest{pr}, pool) 345 if str, err := log.String(); err != nil { 346 t.Fatalf("For case %s: failed to get log output: %v", tc.name, err) 347 } else if str != initialLog { 348 t.Errorf("For case %s: error setting status: %s", tc.name, str) 349 } 350 if tc.shouldSet && !fc.setStatus { 351 t.Errorf("For case %s: should set but didn't", tc.name) 352 } else if !tc.shouldSet && fc.setStatus { 353 t.Errorf("For case %s: should not set but did", tc.name) 354 } 355 } 356 } 357 358 func TestTargetUrl(t *testing.T) { 359 testcases := []struct { 360 name string 361 pr *PullRequest 362 config config.Tide 363 364 expectedURL string 365 }{ 366 { 367 name: "no config", 368 pr: &PullRequest{}, 369 config: config.Tide{}, 370 expectedURL: "", 371 }, 372 { 373 name: "tide overview config", 374 pr: &PullRequest{}, 375 config: config.Tide{TargetURL: "tide.com"}, 376 expectedURL: "tide.com", 377 }, 378 { 379 name: "PR dashboard config and overview config", 380 pr: &PullRequest{}, 381 config: config.Tide{TargetURL: "tide.com", PRStatusBaseURL: "pr.status.com"}, 382 expectedURL: "tide.com", 383 }, 384 { 385 name: "PR dashboard config", 386 pr: &PullRequest{ 387 Author: struct { 388 Login githubv4.String 389 }{Login: githubv4.String("author")}, 390 Repository: struct { 391 Name githubv4.String 392 NameWithOwner githubv4.String 393 Owner struct { 394 Login githubv4.String 395 } 396 }{NameWithOwner: githubv4.String("org/repo")}, 397 HeadRefName: "head", 398 }, 399 config: config.Tide{PRStatusBaseURL: "pr.status.com"}, 400 expectedURL: "pr.status.com?query=is%3Apr+repo%3Aorg%2Frepo+author%3Aauthor+head%3Ahead", 401 }, 402 } 403 404 for _, tc := range testcases { 405 ca := &config.Agent{} 406 ca.Set(&config.Config{ProwConfig: config.ProwConfig{Tide: tc.config}}) 407 log := logrus.WithField("controller", "status-update") 408 if actual, expected := targetURL(ca, tc.pr, log), tc.expectedURL; actual != expected { 409 t.Errorf("%s: expected target URL %s but got %s", tc.name, expected, actual) 410 } 411 } 412 }