github.com/abayer/test-infra@v0.0.5/prow/plugins/blockade/blockade_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 blockade 18 19 import ( 20 "fmt" 21 "reflect" 22 "sort" 23 "strings" 24 "testing" 25 26 "github.com/sirupsen/logrus" 27 28 "k8s.io/test-infra/prow/github" 29 "k8s.io/test-infra/prow/github/fakegithub" 30 "k8s.io/test-infra/prow/plugins" 31 ) 32 33 var ( 34 // Sample changes: 35 docFile = github.PullRequestChange{Filename: "docs/documentation.md", BlobURL: "<URL1>"} 36 docOwners = github.PullRequestChange{Filename: "docs/OWNERS", BlobURL: "<URL2>"} 37 docOwners2 = github.PullRequestChange{Filename: "docs/2/OWNERS", BlobURL: "<URL3>"} 38 srcGo = github.PullRequestChange{Filename: "src/code.go", BlobURL: "<URL4>"} 39 srcSh = github.PullRequestChange{Filename: "src/shell.sh", BlobURL: "<URL5>"} 40 docSh = github.PullRequestChange{Filename: "docs/shell.sh", BlobURL: "<URL6>"} 41 42 // Sample blockades: 43 blockDocs = plugins.Blockade{ 44 Repos: []string{"org/repo"}, 45 BlockRegexps: []string{`docs/.*`}, 46 Explanation: "1", 47 } 48 blockDocsExceptOwners = plugins.Blockade{ 49 Repos: []string{"org/repo"}, 50 BlockRegexps: []string{`docs/.*`}, 51 ExceptionRegexps: []string{`.*OWNERS`}, 52 Explanation: "2", 53 } 54 blockShell = plugins.Blockade{ 55 Repos: []string{"org/repo"}, 56 BlockRegexps: []string{`.*\.sh`}, 57 Explanation: "3", 58 } 59 blockAllOrg = plugins.Blockade{ 60 Repos: []string{"org"}, 61 BlockRegexps: []string{`.*`}, 62 Explanation: "4", 63 } 64 blockAllOther = plugins.Blockade{ 65 Repos: []string{"org2"}, 66 BlockRegexps: []string{`.*`}, 67 Explanation: "5", 68 } 69 ) 70 71 // TestCalculateBlocks validates that changes are blocked or allowed correctly. 72 func TestCalculateBlocks(t *testing.T) { 73 tcs := []struct { 74 name string 75 changes []github.PullRequestChange 76 config []plugins.Blockade 77 expectedSummary summary 78 }{ 79 { 80 name: "blocked by 1/1 blockade (no exceptions), extra file", 81 config: []plugins.Blockade{blockDocs}, 82 changes: []github.PullRequestChange{docFile, docOwners, srcGo}, 83 expectedSummary: summary{ 84 "1": []github.PullRequestChange{docFile, docOwners}, 85 }, 86 }, 87 { 88 name: "blocked by 1/1 blockade (1/2 files are exceptions), extra file", 89 config: []plugins.Blockade{blockDocsExceptOwners}, 90 changes: []github.PullRequestChange{docFile, docOwners, srcGo}, 91 expectedSummary: summary{ 92 "2": []github.PullRequestChange{docFile}, 93 }, 94 }, 95 { 96 name: "blocked by 0/1 blockades (2/2 exceptions), extra file", 97 config: []plugins.Blockade{blockDocsExceptOwners}, 98 changes: []github.PullRequestChange{docOwners, docOwners2, srcGo}, 99 expectedSummary: summary{}, 100 }, 101 { 102 name: "blocked by 0/1 blockades (no exceptions), extra file", 103 config: []plugins.Blockade{blockDocsExceptOwners}, 104 changes: []github.PullRequestChange{srcGo, srcSh}, 105 expectedSummary: summary{}, 106 }, 107 { 108 name: "blocked by 2/2 blockades (no exceptions), extra file", 109 config: []plugins.Blockade{blockDocsExceptOwners, blockShell}, 110 changes: []github.PullRequestChange{srcGo, srcSh, docFile}, 111 expectedSummary: summary{ 112 "2": []github.PullRequestChange{docFile}, 113 "3": []github.PullRequestChange{srcSh}, 114 }, 115 }, 116 { 117 name: "blocked by 2/2 blockades w/ single file", 118 config: []plugins.Blockade{blockDocsExceptOwners, blockShell}, 119 changes: []github.PullRequestChange{docSh}, 120 expectedSummary: summary{ 121 "2": []github.PullRequestChange{docSh}, 122 "3": []github.PullRequestChange{docSh}, 123 }, 124 }, 125 { 126 name: "blocked by 2/2 blockades w/ single file (1/2 exceptions)", 127 config: []plugins.Blockade{blockDocsExceptOwners, blockShell}, 128 changes: []github.PullRequestChange{docSh, docOwners}, 129 expectedSummary: summary{ 130 "2": []github.PullRequestChange{docSh}, 131 "3": []github.PullRequestChange{docSh}, 132 }, 133 }, 134 { 135 name: "blocked by 1/2 blockades (1/2 exceptions), extra file", 136 config: []plugins.Blockade{blockDocsExceptOwners, blockShell}, 137 changes: []github.PullRequestChange{srcSh, docOwners, srcGo}, 138 expectedSummary: summary{ 139 "3": []github.PullRequestChange{srcSh}, 140 }, 141 }, 142 { 143 name: "blocked by 0/2 blockades (1/2 exceptions), extra file", 144 config: []plugins.Blockade{blockDocsExceptOwners, blockShell}, 145 changes: []github.PullRequestChange{docOwners, srcGo}, 146 expectedSummary: summary{}, 147 }, 148 } 149 150 for _, tc := range tcs { 151 blockades := compileApplicableBlockades("org", "repo", logrus.WithField("plugin", pluginName), tc.config) 152 sum := calculateBlocks(tc.changes, blockades) 153 if !reflect.DeepEqual(sum, tc.expectedSummary) { 154 t.Errorf("[%s] Expected summary: %#v, actual summary: %#v.", tc.name, tc.expectedSummary, sum) 155 } 156 } 157 } 158 159 func TestSummaryString(t *testing.T) { 160 // Just one example for now. 161 tcs := []struct { 162 name string 163 sum summary 164 expectedContents []string 165 }{ 166 { 167 name: "Simple example", 168 sum: summary{ 169 "reason A": []github.PullRequestChange{docFile}, 170 "reason B": []github.PullRequestChange{srcGo, srcSh}, 171 }, 172 expectedContents: []string{ 173 "#### Reasons for blocking this PR:\n", 174 "[reason A]\n- [docs/documentation.md](<URL1>)\n\n", 175 "[reason B]\n- [src/code.go](<URL4>)\n\n- [src/shell.sh](<URL5>)\n\n", 176 }, 177 }, 178 } 179 180 for _, tc := range tcs { 181 got := tc.sum.String() 182 for _, expected := range tc.expectedContents { 183 if !strings.Contains(got, expected) { 184 t.Errorf("[%s] Expected summary %#v to contain %q, but got %q.", tc.name, tc.sum, expected, got) 185 } 186 } 187 } 188 } 189 190 func formatLabel(label string) string { 191 return fmt.Sprintf("%s/%s#%d:%s", "org", "repo", 1, label) 192 } 193 194 type fakePruner struct{} 195 196 func (f *fakePruner) PruneComments(_ func(ic github.IssueComment) bool) {} 197 198 // TestHandle validates that: 199 // - The correct labels are added/removed. 200 // - A comment is created when needed. 201 // - Uninteresting events are ignored. 202 // - Blockades that don't apply to this repo are ignored. 203 func TestHandle(t *testing.T) { 204 // Don't need to validate the following because they are validated by other tests: 205 // - Block calculation. (Whether or not changes justify blocking the PR.) 206 // - Comment contents, just existence. 207 otherLabel := "lgtm" 208 209 tcs := []struct { 210 name string 211 action github.PullRequestEventAction 212 config []plugins.Blockade 213 hasLabel bool 214 filesBlock bool // This is ignored if there are no applicable blockades for the repo. 215 216 labelAdded string 217 labelRemoved string 218 commentCreated bool 219 }{ 220 { 221 name: "Boring action", 222 action: github.PullRequestActionEdited, 223 config: []plugins.Blockade{blockDocsExceptOwners}, 224 hasLabel: false, 225 filesBlock: true, 226 }, 227 { 228 name: "Basic block", 229 action: github.PullRequestActionOpened, 230 config: []plugins.Blockade{blockDocsExceptOwners}, 231 hasLabel: false, 232 filesBlock: true, 233 234 labelAdded: blockedPathsLabel, 235 commentCreated: true, 236 }, 237 { 238 name: "Basic block, already labeled", 239 action: github.PullRequestActionOpened, 240 config: []plugins.Blockade{blockDocsExceptOwners}, 241 hasLabel: true, 242 filesBlock: true, 243 }, 244 { 245 name: "Not blocked, not labeled", 246 action: github.PullRequestActionOpened, 247 config: []plugins.Blockade{blockDocsExceptOwners}, 248 hasLabel: false, 249 filesBlock: false, 250 }, 251 { 252 name: "Not blocked, has label", 253 action: github.PullRequestActionOpened, 254 config: []plugins.Blockade{blockDocsExceptOwners}, 255 hasLabel: true, 256 filesBlock: false, 257 258 labelRemoved: blockedPathsLabel, 259 }, 260 { 261 name: "No blockade, not labeled", 262 action: github.PullRequestActionOpened, 263 config: []plugins.Blockade{}, 264 hasLabel: false, 265 filesBlock: true, 266 }, 267 { 268 name: "No blockade, has label", 269 action: github.PullRequestActionOpened, 270 config: []plugins.Blockade{}, 271 hasLabel: true, 272 filesBlock: true, 273 274 labelRemoved: blockedPathsLabel, 275 }, 276 { 277 name: "Basic block (org scoped blockade)", 278 action: github.PullRequestActionOpened, 279 config: []plugins.Blockade{blockAllOrg}, 280 hasLabel: false, 281 filesBlock: true, 282 283 labelAdded: blockedPathsLabel, 284 commentCreated: true, 285 }, 286 { 287 name: "Would be blocked, but blockade is not applicable; not labeled", 288 action: github.PullRequestActionOpened, 289 config: []plugins.Blockade{blockAllOther}, 290 hasLabel: false, 291 filesBlock: true, 292 }, 293 } 294 295 for _, tc := range tcs { 296 expectAdded := []string{} 297 fakeClient := &fakegithub.FakeClient{ 298 ExistingLabels: []string{blockedPathsLabel, otherLabel}, 299 IssueComments: make(map[int][]github.IssueComment), 300 PullRequestChanges: make(map[int][]github.PullRequestChange), 301 LabelsAdded: []string{}, 302 LabelsRemoved: []string{}, 303 } 304 if tc.hasLabel { 305 label := formatLabel(blockedPathsLabel) 306 fakeClient.LabelsAdded = append(fakeClient.LabelsAdded, label) 307 expectAdded = append(expectAdded, label) 308 } 309 calcF := func(_ []github.PullRequestChange, blockades []blockade) summary { 310 if !tc.filesBlock { 311 return nil 312 } 313 sum := make(summary) 314 for _, b := range blockades { 315 // For this test assume 'docFile' is blocked by every blockade that is applicable to the repo. 316 sum[b.explanation] = []github.PullRequestChange{docFile} 317 } 318 return sum 319 } 320 pre := &github.PullRequestEvent{ 321 Action: tc.action, 322 Repo: github.Repo{Owner: github.User{Login: "org"}, Name: "repo"}, 323 Number: 1, 324 } 325 c := &client{ 326 ghc: fakeClient, 327 log: logrus.WithField("plugin", pluginName), 328 pruner: &fakePruner{}, 329 blockCalc: calcF, 330 } 331 if err := handle(c, tc.config, pre); err != nil { 332 t.Errorf("[%s] Unexpected error from handle: %v.", tc.name, err) 333 continue 334 } 335 336 if tc.labelAdded != "" { 337 expectAdded = append(expectAdded, formatLabel(tc.labelAdded)) 338 } 339 sort.Strings(expectAdded) 340 sort.Strings(fakeClient.LabelsAdded) 341 if !reflect.DeepEqual(expectAdded, fakeClient.LabelsAdded) { 342 t.Errorf("[%s]: Expected labels to be added: %q, but got: %q.", tc.name, expectAdded, fakeClient.LabelsAdded) 343 } 344 expectRemoved := []string{} 345 if tc.labelRemoved != "" { 346 expectRemoved = append(expectRemoved, formatLabel(tc.labelRemoved)) 347 } 348 sort.Strings(expectRemoved) 349 sort.Strings(fakeClient.LabelsRemoved) 350 if !reflect.DeepEqual(expectRemoved, fakeClient.LabelsRemoved) { 351 t.Errorf("[%s]: Expected labels to be removed: %q, but got: %q.", tc.name, expectRemoved, fakeClient.LabelsRemoved) 352 } 353 354 if count := len(fakeClient.IssueComments[1]); count > 1 { 355 t.Errorf("[%s] More than 1 comment created! (%d created).", tc.name, count) 356 } else if (count == 1) != tc.commentCreated { 357 t.Errorf("[%s] Expected comment created: %t, but got %t.", tc.name, tc.commentCreated, count == 1) 358 } 359 } 360 }