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