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  }