sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/plugins/plugins_test.go (about) 1 /* 2 Copyright 2016 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 plugins 18 19 import ( 20 "os" 21 "path/filepath" 22 "regexp" 23 "testing" 24 25 "github.com/google/go-cmp/cmp" 26 "github.com/google/go-cmp/cmp/cmpopts" 27 "sigs.k8s.io/yaml" 28 ) 29 30 func TestEnsureEmbed(t *testing.T) { 31 if len(embededConfigGoFileContent) == 0 { 32 t.Error("EmbededConfigGoFileContent is empty.") 33 } 34 } 35 36 func TestHasSelfApproval(t *testing.T) { 37 cases := []struct { 38 name string 39 cfg string 40 expected bool 41 }{ 42 { 43 name: "self approval by default", 44 expected: true, 45 }, 46 { 47 name: "reject approval when require_self_approval set", 48 cfg: `{"require_self_approval": true}`, 49 expected: false, 50 }, 51 { 52 name: "has approval when require_self_approval set to false", 53 cfg: `{"require_self_approval": false}`, 54 expected: true, 55 }, 56 } 57 for _, tc := range cases { 58 t.Run(tc.name, func(t *testing.T) { 59 var a Approve 60 if err := yaml.Unmarshal([]byte(tc.cfg), &a); err != nil { 61 t.Fatalf("failed to unmarshal cfg: %v", err) 62 } 63 if actual := a.HasSelfApproval(); actual != tc.expected { 64 t.Errorf("%t != expected %t", actual, tc.expected) 65 } 66 }) 67 } 68 } 69 70 func TestConsiderReviewState(t *testing.T) { 71 cases := []struct { 72 name string 73 cfg string 74 expected bool 75 }{ 76 { 77 name: "consider by default", 78 expected: true, 79 }, 80 { 81 name: "do not consider when irs = true", 82 cfg: `{"ignore_review_state": true}`, 83 }, 84 { 85 name: "consider when irs = false", 86 cfg: `{"ignore_review_state": false}`, 87 expected: true, 88 }, 89 } 90 91 for _, tc := range cases { 92 t.Run(tc.name, func(t *testing.T) { 93 var a Approve 94 if err := yaml.Unmarshal([]byte(tc.cfg), &a); err != nil { 95 t.Fatalf("failed to unmarshal cfg: %v", err) 96 } 97 if actual := a.ConsiderReviewState(); actual != tc.expected { 98 t.Errorf("%t != expected %t", actual, tc.expected) 99 } 100 }) 101 } 102 } 103 104 func TestGetPluginsLegacy(t *testing.T) { 105 var testcases = []struct { 106 name string 107 pluginMap map[string][]string // this is read from the plugins.yaml file typically. 108 owner string 109 repo string 110 expectedPlugins []string 111 }{ 112 { 113 name: "All plugins enabled for org should be returned for any org/repo query", 114 pluginMap: map[string][]string{ 115 "org1": {"plugin1", "plugin2"}, 116 }, 117 owner: "org1", 118 repo: "repo", 119 expectedPlugins: []string{"plugin1", "plugin2"}, 120 }, 121 { 122 name: "All plugins enabled for org/repo should be returned for a org/repo query", 123 pluginMap: map[string][]string{ 124 "org1": {"plugin1", "plugin2"}, 125 "org1/repo": {"plugin3"}, 126 }, 127 owner: "org1", 128 repo: "repo", 129 expectedPlugins: []string{"plugin1", "plugin2", "plugin3"}, 130 }, 131 { 132 name: "Plugins for org1/repo should not be returned for org2/repo query", 133 pluginMap: map[string][]string{ 134 "org1": {"plugin1", "plugin2"}, 135 "org1/repo": {"plugin3"}, 136 }, 137 owner: "org2", 138 repo: "repo", 139 expectedPlugins: nil, 140 }, 141 { 142 name: "Plugins for org1 should not be returned for org2/repo query", 143 pluginMap: map[string][]string{ 144 "org1": {"plugin1", "plugin2"}, 145 "org2/repo": {"plugin3"}, 146 }, 147 owner: "org2", 148 repo: "repo", 149 expectedPlugins: []string{"plugin3"}, 150 }, 151 } 152 for _, tc := range testcases { 153 pa := ConfigAgent{configuration: &Configuration{Plugins: OldToNewPlugins(tc.pluginMap)}} 154 155 plugins := pa.getPlugins(tc.owner, tc.repo) 156 if diff := cmp.Diff(plugins, tc.expectedPlugins); diff != "" { 157 t.Errorf("Actual plugins differ from expected: %s", diff) 158 } 159 } 160 } 161 162 func TestGetPlugins(t *testing.T) { 163 var testcases = []struct { 164 name string 165 pluginMap Plugins // this is read from the plugins.yaml file typically. 166 owner string 167 repo string 168 expectedPlugins []string 169 }{ 170 { 171 name: "All plugins enabled for org should be returned for any org/repo query", 172 pluginMap: Plugins{ 173 "org1": {Plugins: []string{"plugin1", "plugin2"}}, 174 }, 175 owner: "org1", 176 repo: "repo", 177 expectedPlugins: []string{"plugin1", "plugin2"}, 178 }, 179 { 180 name: "All plugins enabled for org/repo should be returned for a org/repo query", 181 pluginMap: Plugins{ 182 "org1": {Plugins: []string{"plugin1", "plugin2"}}, 183 "org1/repo": {Plugins: []string{"plugin3"}}, 184 }, 185 owner: "org1", 186 repo: "repo", 187 expectedPlugins: []string{"plugin1", "plugin2", "plugin3"}, 188 }, 189 { 190 name: "Excluded plugins for repo enabled for org/repo should not be returned for a org/repo query", 191 pluginMap: Plugins{ 192 "org1": {Plugins: []string{"plugin1", "plugin2", "plugin3"}, ExcludedRepos: []string{"repo"}}, 193 "org1/repo": {Plugins: []string{"plugin3"}}, 194 }, 195 owner: "org1", 196 repo: "repo", 197 expectedPlugins: []string{"plugin3"}, 198 }, 199 { 200 name: "Plugins for org1/repo should not be returned for org2/repo query", 201 pluginMap: Plugins{ 202 "org1": {Plugins: []string{"plugin1", "plugin2"}}, 203 "org1/repo": {Plugins: []string{"plugin3"}}, 204 }, 205 owner: "org2", 206 repo: "repo", 207 expectedPlugins: nil, 208 }, 209 { 210 name: "Plugins for org1 should not be returned for org2/repo query", 211 pluginMap: Plugins{ 212 "org1": {Plugins: []string{"plugin1", "plugin2"}}, 213 "org2/repo": {Plugins: []string{"plugin3"}}, 214 }, 215 owner: "org2", 216 repo: "repo", 217 expectedPlugins: []string{"plugin3"}, 218 }, 219 } 220 for _, tc := range testcases { 221 pa := ConfigAgent{configuration: &Configuration{Plugins: tc.pluginMap}} 222 223 plugins := pa.getPlugins(tc.owner, tc.repo) 224 if diff := cmp.Diff(plugins, tc.expectedPlugins); diff != "" { 225 t.Errorf("Actual plugins differ from expected: %s", diff) 226 } 227 } 228 } 229 230 func TestLoad(t *testing.T) { 231 t.Parallel() 232 233 defaultedConfig := func(m ...func(*Configuration)) *Configuration { 234 cfg := &Configuration{ 235 Owners: Owners{LabelsDenyList: []string{"approved", "lgtm"}}, 236 Blunderbuss: Blunderbuss{ReviewerCount: func() *int { i := 2; return &i }()}, 237 CherryPickUnapproved: CherryPickUnapproved{ 238 BranchRegexp: "^release-.*$", 239 BranchRe: regexp.MustCompile("^release-.*$"), 240 Comment: "This PR is not for the master branch but does not have the `cherry-pick-approved` label. Adding the `do-not-merge/cherry-pick-not-approved` label.", 241 }, 242 ConfigUpdater: ConfigUpdater{ 243 Maps: map[string]ConfigMapSpec{ 244 "config/prow/config.yaml": {Name: "config", Clusters: map[string][]string{"default": {""}}}, 245 "config/prow/plugins.yaml": {Name: "plugins", Clusters: map[string][]string{"default": {""}}}}, 246 }, 247 Heart: Heart{CommentRe: regexp.MustCompile("")}, 248 SigMention: SigMention{ 249 Regexp: `(?m)@kubernetes/sig-([\w-]*)-(misc|test-failures|bugs|feature-requests|proposals|pr-reviews|api-reviews)`, 250 Re: regexp.MustCompile(`(?m)@kubernetes/sig-([\w-]*)-(misc|test-failures|bugs|feature-re)`), 251 }, 252 Help: Help{ 253 HelpGuidelinesURL: "https://git.k8s.io/community/contributors/guide/help-wanted.md", 254 }, 255 } 256 for _, modify := range m { 257 modify(cfg) 258 } 259 return cfg 260 } 261 262 testCases := []struct { 263 name string 264 config string 265 // filename -> content 266 supplementalConfigs map[string]string 267 supplementalPluginConfigFileSuffix string 268 269 expected *Configuration 270 }{ 271 { 272 name: "Single-file config gets loaded", 273 config: ` 274 plugins: 275 org/repo: 276 - wip`, 277 expected: defaultedConfig(func(c *Configuration) { 278 c.Plugins = Plugins{"org/repo": {Plugins: []string{"wip"}}} 279 }), 280 }, 281 { 282 name: "Supplemental configs get loaded and merged", 283 config: ` 284 plugins: 285 org/repo: 286 - wip`, 287 supplementalConfigs: map[string]string{ 288 "some-path-extra_config.yaml": ` 289 plugins: 290 org/repo2: 291 - wip`, 292 }, 293 supplementalPluginConfigFileSuffix: "extra_config.yaml", 294 expected: defaultedConfig(func(c *Configuration) { 295 c.Plugins = Plugins{ 296 "org/repo": {Plugins: []string{"wip"}}, 297 "org/repo2": {Plugins: []string{"wip"}}, 298 } 299 }), 300 }, 301 { 302 name: "Supplemental configs that do not have right suffix are ignored", 303 config: ` 304 plugins: 305 org/repo: 306 - wip`, 307 supplementalConfigs: map[string]string{ 308 "some-path-extra_config.yaml": ` 309 plugins: 310 org/repo: 311 - wip`, 312 }, 313 supplementalPluginConfigFileSuffix: "nope", 314 expected: defaultedConfig(func(c *Configuration) { 315 c.Plugins = Plugins{"org/repo": {Plugins: []string{"wip"}}} 316 }), 317 }, 318 } 319 320 for _, tc := range testCases { 321 tc := tc 322 t.Run(tc.name, func(t *testing.T) { 323 t.Parallel() 324 325 tempDir := t.TempDir() 326 if err := os.WriteFile(filepath.Join(tempDir, "_plugins.yaml"), []byte(tc.config), 0644); err != nil { 327 t.Fatalf("failed to write config: %v", err) 328 } 329 for supplementalConfigName, supplementalConfig := range tc.supplementalConfigs { 330 if err := os.WriteFile(filepath.Join(tempDir, supplementalConfigName), []byte(supplementalConfig), 0644); err != nil { 331 t.Fatalf("failed to write supplemental config %s: %v", supplementalConfigName, err) 332 } 333 } 334 335 agent := &ConfigAgent{} 336 if err := agent.Load(filepath.Join(tempDir, "_plugins.yaml"), []string{tempDir}, tc.supplementalPluginConfigFileSuffix, false, false); err != nil { 337 t.Fatalf("failed to load: %v", err) 338 } 339 340 if diff := cmp.Diff(tc.expected, agent.Config(), cmpopts.IgnoreTypes(regexp.Regexp{})); diff != "" { 341 t.Errorf("expected config differs from actual: %s", diff) 342 } 343 344 }) 345 } 346 } 347 348 const configUpdater = `--- 349 config_updater: 350 cluster_groups: 351 build_farm: 352 clusters: 353 - app.ci 354 - build01 355 namespaces: 356 - ci 357 gzip: false 358 maps: 359 ci-operator/config/**/*-fcos.yaml: 360 clusters: 361 app.ci: 362 - ci 363 name: ci-operator-misc-configs 364 ci-operator/templates/master-sidecar-3.yaml: 365 cluster_groups: 366 - build_farm 367 name: prow-job-master-sidecar-3 368 ` 369 370 func TestLoadConfigUpdater(t *testing.T) { 371 testCases := []struct { 372 name string 373 config string 374 skipResolveConfigUpdater bool 375 expected ConfigUpdater 376 }{ 377 { 378 name: "skip resolve", 379 config: configUpdater, 380 skipResolveConfigUpdater: true, 381 expected: ConfigUpdater{ 382 ClusterGroups: map[string]ClusterGroup{ 383 "build_farm": { 384 Clusters: []string{"app.ci", "build01"}, 385 Namespaces: []string{"ci"}, 386 }, 387 }, 388 Maps: map[string]ConfigMapSpec{ 389 "ci-operator/config/**/*-fcos.yaml": { 390 Name: "ci-operator-misc-configs", 391 Clusters: map[string][]string{"app.ci": {"ci"}}, 392 }, 393 "ci-operator/templates/master-sidecar-3.yaml": { 394 Name: "prow-job-master-sidecar-3", 395 ClusterGroups: []string{"build_farm"}, 396 }, 397 }, 398 }, 399 }, 400 { 401 name: "not skip resolve", 402 config: configUpdater, 403 expected: ConfigUpdater{ 404 Maps: map[string]ConfigMapSpec{ 405 "ci-operator/config/**/*-fcos.yaml": { 406 Name: "ci-operator-misc-configs", 407 Clusters: map[string][]string{"app.ci": {"ci"}}, 408 }, 409 "ci-operator/templates/master-sidecar-3.yaml": { 410 Name: "prow-job-master-sidecar-3", 411 Clusters: map[string][]string{"app.ci": {"ci"}, "build01": {"ci"}}, 412 }, 413 }, 414 }, 415 }, 416 } 417 for _, tc := range testCases { 418 t.Run(tc.name, func(t *testing.T) { 419 tempDir := t.TempDir() 420 if err := os.WriteFile(filepath.Join(tempDir, "_plugins.yaml"), []byte(tc.config), 0644); err != nil { 421 t.Fatalf("failed to write config: %v", err) 422 } 423 agent := &ConfigAgent{} 424 if err := agent.Load(filepath.Join(tempDir, "_plugins.yaml"), nil, "", false, tc.skipResolveConfigUpdater); err != nil { 425 t.Fatalf("failed to load: %v", err) 426 } 427 if diff := cmp.Diff(tc.expected, agent.Config().ConfigUpdater); diff != "" { 428 t.Errorf("expected config differs from actual: %s", diff) 429 } 430 }) 431 } 432 433 }