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  }