github.com/abayer/test-infra@v0.0.5/prow/plugins/releasenote/releasenote_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 releasenote
    18  
    19  import (
    20  	"fmt"
    21  	"reflect"
    22  	"sort"
    23  	"testing"
    24  
    25  	"github.com/sirupsen/logrus"
    26  
    27  	"k8s.io/test-infra/prow/github"
    28  	"k8s.io/test-infra/prow/github/fakegithub"
    29  )
    30  
    31  func TestReleaseNoteComment(t *testing.T) {
    32  	var testcases = []struct {
    33  		name          string
    34  		action        github.IssueCommentEventAction
    35  		commentBody   string
    36  		issueBody     string
    37  		isMember      bool
    38  		isAuthor      bool
    39  		currentLabels []string
    40  
    41  		deletedLabels []string
    42  		addedLabel    string
    43  		shouldComment bool
    44  	}{
    45  		{
    46  			name:          "unrelated comment",
    47  			action:        github.IssueCommentActionCreated,
    48  			commentBody:   "oh dear",
    49  			currentLabels: []string{releaseNoteLabelNeeded, "other"},
    50  		},
    51  		{
    52  			name:          "author release-note-none with missing block",
    53  			action:        github.IssueCommentActionCreated,
    54  			isAuthor:      true,
    55  			commentBody:   "/release-note-none",
    56  			currentLabels: []string{releaseNoteLabelNeeded, "other"},
    57  
    58  			deletedLabels: []string{releaseNoteLabelNeeded},
    59  			addedLabel:    releaseNoteNone,
    60  		},
    61  		{
    62  			name:          "author release-note-none with empty block",
    63  			action:        github.IssueCommentActionCreated,
    64  			isAuthor:      true,
    65  			commentBody:   "/release-note-none",
    66  			issueBody:     "bologna ```release-note \n ```",
    67  			currentLabels: []string{releaseNoteLabelNeeded, "other"},
    68  
    69  			deletedLabels: []string{releaseNoteLabelNeeded},
    70  			addedLabel:    releaseNoteNone,
    71  		},
    72  		{
    73  			name:          "author release-note-none with \"none\" block",
    74  			action:        github.IssueCommentActionCreated,
    75  			isAuthor:      true,
    76  			commentBody:   "/release-note-none",
    77  			issueBody:     "bologna ```release-note \nnone \n ```",
    78  			currentLabels: []string{releaseNoteLabelNeeded, "other"},
    79  
    80  			deletedLabels: []string{releaseNoteLabelNeeded},
    81  			addedLabel:    releaseNoteNone,
    82  		},
    83  		{
    84  			name:          "author release-note-none, trailing space.",
    85  			action:        github.IssueCommentActionCreated,
    86  			isAuthor:      true,
    87  			commentBody:   "/release-note-none ",
    88  			currentLabels: []string{releaseNoteLabelNeeded, "other"},
    89  
    90  			deletedLabels: []string{releaseNoteLabelNeeded},
    91  			addedLabel:    releaseNoteNone,
    92  		},
    93  		{
    94  			name:          "author release-note-none, no op.",
    95  			action:        github.IssueCommentActionCreated,
    96  			isAuthor:      true,
    97  			commentBody:   "/release-note-none",
    98  			currentLabels: []string{releaseNoteNone, "other"},
    99  		},
   100  		{
   101  			name:          "member release-note",
   102  			action:        github.IssueCommentActionCreated,
   103  			isMember:      true,
   104  			commentBody:   "/release-note",
   105  			currentLabels: []string{releaseNoteLabelNeeded, "other"},
   106  
   107  			shouldComment: true,
   108  		},
   109  		{
   110  			name:          "someone else release-note, trailing space.",
   111  			action:        github.IssueCommentActionCreated,
   112  			commentBody:   "/release-note \r",
   113  			currentLabels: []string{releaseNoteLabelNeeded, "other"},
   114  			shouldComment: true,
   115  		},
   116  		{
   117  			name:          "someone else release-note-none",
   118  			action:        github.IssueCommentActionCreated,
   119  			commentBody:   "/release-note-none",
   120  			currentLabels: []string{releaseNoteLabelNeeded, "other"},
   121  			shouldComment: true,
   122  		},
   123  		{
   124  			name:          "author release-note-action-required",
   125  			action:        github.IssueCommentActionCreated,
   126  			isAuthor:      true,
   127  			commentBody:   "/release-note-action-required",
   128  			currentLabels: []string{releaseNoteLabelNeeded, "other"},
   129  			shouldComment: true,
   130  		},
   131  		{
   132  			name:          "release-note-none, delete multiple labels",
   133  			action:        github.IssueCommentActionCreated,
   134  			isMember:      true,
   135  			commentBody:   "/release-note-none",
   136  			currentLabels: []string{releaseNote, releaseNoteLabelNeeded, releaseNoteActionRequired, releaseNoteNone, "other"},
   137  
   138  			deletedLabels: []string{releaseNoteLabelNeeded, releaseNoteActionRequired, releaseNote},
   139  		},
   140  		{
   141  			name:        "no label present",
   142  			action:      github.IssueCommentActionCreated,
   143  			isMember:    true,
   144  			commentBody: "/release-note-none",
   145  
   146  			addedLabel: releaseNoteNone,
   147  		},
   148  	}
   149  	for _, tc := range testcases {
   150  		fc := &fakegithub.FakeClient{
   151  			IssueComments: make(map[int][]github.IssueComment),
   152  			OrgMembers:    map[string][]string{"": {"m"}},
   153  		}
   154  		ice := github.IssueCommentEvent{
   155  			Action: tc.action,
   156  			Comment: github.IssueComment{
   157  				Body: tc.commentBody,
   158  			},
   159  			Issue: github.Issue{
   160  				Body:        tc.issueBody,
   161  				User:        github.User{Login: "a"},
   162  				Number:      5,
   163  				State:       "open",
   164  				PullRequest: &struct{}{},
   165  				Assignees:   []github.User{{Login: "r"}},
   166  			},
   167  		}
   168  		if tc.isAuthor {
   169  			ice.Comment.User.Login = "a"
   170  		} else if tc.isMember {
   171  			ice.Comment.User.Login = "m"
   172  		}
   173  		for _, l := range tc.currentLabels {
   174  			ice.Issue.Labels = append(ice.Issue.Labels, github.Label{Name: l})
   175  		}
   176  		if err := handleComment(fc, logrus.WithField("plugin", pluginName), ice); err != nil {
   177  			t.Errorf("For case %s, did not expect error: %v", tc.name, err)
   178  		}
   179  		if tc.shouldComment && len(fc.IssueComments[5]) == 0 {
   180  			t.Errorf("For case %s, didn't comment but should have.", tc.name)
   181  		}
   182  		if len(fc.LabelsAdded) > 1 {
   183  			t.Errorf("For case %s, added more than one label: %v", tc.name, fc.LabelsAdded)
   184  		} else if len(fc.LabelsAdded) == 0 && tc.addedLabel != "" {
   185  			t.Errorf("For case %s, should have added %s but didn't.", tc.name, tc.addedLabel)
   186  		} else if len(fc.LabelsAdded) == 1 && fc.LabelsAdded[0] != "/#5:"+tc.addedLabel {
   187  			t.Errorf("For case %s, added wrong label. Got %s, expected %s", tc.name, fc.LabelsAdded[0], tc.addedLabel)
   188  		}
   189  
   190  		var expectedDeleted []string
   191  		for _, expect := range tc.deletedLabels {
   192  			expectedDeleted = append(expectedDeleted, "/#5:"+expect)
   193  		}
   194  		sort.Strings(expectedDeleted)
   195  		sort.Strings(fc.LabelsRemoved)
   196  		if !reflect.DeepEqual(expectedDeleted, fc.LabelsRemoved) {
   197  			t.Errorf(
   198  				"For case %s, expected %q labels to be deleted, but %q were deleted.",
   199  				tc.name,
   200  				expectedDeleted,
   201  				fc.LabelsRemoved,
   202  			)
   203  		}
   204  	}
   205  }
   206  
   207  const lgtmLabel = "lgtm"
   208  
   209  func formatLabels(num int, labels ...string) []string {
   210  	out := make([]string, 0, len(labels))
   211  	for _, l := range labels {
   212  		out = append(out, fmt.Sprintf("org/repo#%d:%s", num, l))
   213  	}
   214  	return out
   215  }
   216  
   217  func newFakeClient(body, branch string, initialLabels, comments []string, parentPRs map[int]string) (*fakegithub.FakeClient, *github.PullRequestEvent) {
   218  	labels := formatLabels(1, initialLabels...)
   219  	for parent, l := range parentPRs {
   220  		labels = append(labels, formatLabels(parent, l)...)
   221  	}
   222  	var issueComments []github.IssueComment
   223  	for _, comment := range comments {
   224  		issueComments = append(issueComments, github.IssueComment{Body: comment})
   225  	}
   226  	return &fakegithub.FakeClient{
   227  			IssueComments: map[int][]github.IssueComment{1: issueComments},
   228  			ExistingLabels: []string{
   229  				lgtmLabel,
   230  				releaseNote,
   231  				releaseNoteLabelNeeded,
   232  				releaseNoteNone,
   233  				releaseNoteActionRequired,
   234  			},
   235  			LabelsAdded:   labels,
   236  			LabelsRemoved: []string{},
   237  		},
   238  		&github.PullRequestEvent{
   239  			Action: github.PullRequestActionEdited,
   240  			Number: 1,
   241  			PullRequest: github.PullRequest{
   242  				Base:   github.PullRequestBranch{Ref: branch},
   243  				Number: 1,
   244  				Body:   body,
   245  				User:   github.User{Login: "cjwagner"},
   246  			},
   247  			Repo: github.Repo{
   248  				Owner: github.User{Login: "org"},
   249  				Name:  "repo",
   250  			},
   251  		}
   252  }
   253  
   254  func TestReleaseNotePR(t *testing.T) {
   255  	tests := []struct {
   256  		name          string
   257  		initialLabels []string
   258  		body          string
   259  		branch        string // Defaults to master
   260  		parentPRs     map[int]string
   261  		issueComments []string
   262  		labelsAdded   []string
   263  		labelsRemoved []string
   264  	}{
   265  		{
   266  			name:          "LGTM with release-note",
   267  			initialLabels: []string{lgtmLabel, releaseNote},
   268  			body:          "```release-note\n note note note.\n```",
   269  		},
   270  		{
   271  			name:          "LGTM with release-note, arbitrary comment",
   272  			initialLabels: []string{lgtmLabel, releaseNote},
   273  			body:          "```release-note\n note note note.\n```",
   274  			issueComments: []string{"Release notes are great fun."},
   275  		},
   276  		{
   277  			name:          "LGTM with release-note-none",
   278  			initialLabels: []string{lgtmLabel, releaseNoteNone},
   279  			body:          "```release-note\nnone\n```",
   280  		},
   281  		{
   282  			name:          "LGTM with release-note-none, /release-note-none comment, empty block",
   283  			initialLabels: []string{lgtmLabel, releaseNoteNone},
   284  			body:          "```release-note\n```",
   285  			issueComments: []string{"/release-note-none "},
   286  		},
   287  		{
   288  			name:          "LGTM with release-note-action-required",
   289  			initialLabels: []string{lgtmLabel, releaseNoteActionRequired},
   290  			body:          "```release-note\n Action required.\n```",
   291  		},
   292  		{
   293  			name:          "LGTM with release-note-action-required, /release-note-none comment",
   294  			initialLabels: []string{lgtmLabel, releaseNoteActionRequired},
   295  			body:          "```release-note\n Action required.\n```",
   296  			issueComments: []string{"Release notes are great fun.", "Especially \n/release-note-none"},
   297  		},
   298  		{
   299  			name:          "LGTM with do-not-merge/release-note-label-needed",
   300  			initialLabels: []string{lgtmLabel, releaseNoteLabelNeeded},
   301  		},
   302  		{
   303  			name:          "LGTM with do-not-merge/release-note-label-needed, /release-note-none comment",
   304  			initialLabels: []string{lgtmLabel, releaseNoteLabelNeeded},
   305  			issueComments: []string{"Release notes are great fun.", "Especially \n/release-note-none"},
   306  			labelsAdded:   []string{releaseNoteNone},
   307  			labelsRemoved: []string{releaseNoteLabelNeeded},
   308  		},
   309  		{
   310  			name:          "LGTM only",
   311  			initialLabels: []string{lgtmLabel},
   312  			labelsAdded:   []string{releaseNoteLabelNeeded},
   313  		},
   314  		{
   315  			name:          "No labels",
   316  			initialLabels: []string{},
   317  			labelsAdded:   []string{releaseNoteLabelNeeded},
   318  		},
   319  		{
   320  			name:          "release-note",
   321  			initialLabels: []string{releaseNote},
   322  			body:          "```release-note normal note.```",
   323  		},
   324  		{
   325  			name:          "release-note, /release-note-none comment",
   326  			initialLabels: []string{releaseNote},
   327  			body:          "```release-note normal note.```",
   328  			issueComments: []string{"/release-note-none "},
   329  		},
   330  		{
   331  			name:          "release-note-none",
   332  			initialLabels: []string{releaseNoteNone},
   333  			body:          "```release-note\nnone\n```",
   334  		},
   335  		{
   336  			name:          "release-note-action-required",
   337  			initialLabels: []string{releaseNoteActionRequired},
   338  			body:          "```release-note\n action required```",
   339  		},
   340  		{
   341  			name:          "release-note and do-not-merge/release-note-label-needed with no note",
   342  			initialLabels: []string{releaseNote, releaseNoteLabelNeeded},
   343  			labelsRemoved: []string{releaseNote},
   344  		},
   345  		{
   346  			name:          "release-note and do-not-merge/release-note-label-needed with note",
   347  			initialLabels: []string{releaseNote, releaseNoteLabelNeeded},
   348  			body:          "```release-note note  ```",
   349  			labelsRemoved: []string{releaseNoteLabelNeeded},
   350  		},
   351  		{
   352  			name:          "release-note-none and do-not-merge/release-note-label-needed",
   353  			initialLabels: []string{releaseNoteNone, releaseNoteLabelNeeded},
   354  			body:          "```release-note\nnone\n```",
   355  			labelsRemoved: []string{releaseNoteLabelNeeded},
   356  		},
   357  		{
   358  			name:          "release-note-action-required and do-not-merge/release-note-label-needed",
   359  			initialLabels: []string{releaseNoteActionRequired, releaseNoteLabelNeeded},
   360  			body:          "```release-note\nSomething something dark side. Something something ACTION REQUIRED.```",
   361  			labelsRemoved: []string{releaseNoteLabelNeeded},
   362  		},
   363  		{
   364  			name:          "do not add needs label when parent PR has releaseNote label",
   365  			branch:        "release-1.2",
   366  			initialLabels: []string{},
   367  			body:          "Cherry pick of #2 on release-1.2.",
   368  			parentPRs:     map[int]string{2: releaseNote},
   369  		},
   370  		{
   371  			name:          "do not touch LGTM on non-master when parent PR has releaseNote label, but remove releaseNoteNeeded",
   372  			branch:        "release-1.2",
   373  			initialLabels: []string{lgtmLabel, releaseNoteLabelNeeded},
   374  			body:          "Cherry pick of #2 on release-1.2.",
   375  			parentPRs:     map[int]string{2: releaseNote},
   376  			labelsRemoved: []string{releaseNoteLabelNeeded},
   377  		},
   378  		{
   379  			name:          "do nothing when PR has releaseNoteActionRequired, but parent PR does not have releaseNote label",
   380  			branch:        "release-1.2",
   381  			initialLabels: []string{releaseNoteActionRequired},
   382  			body:          "Cherry pick of #2 on release-1.2.\n```release-note note action required note\n```",
   383  			parentPRs:     map[int]string{2: releaseNoteNone},
   384  		},
   385  		{
   386  			name:          "add releaseNoteNeeded on non-master when parent PR has releaseNoteNone label",
   387  			branch:        "release-1.2",
   388  			initialLabels: []string{lgtmLabel},
   389  			body:          "Cherry pick of #2 on release-1.2.",
   390  			parentPRs:     map[int]string{2: releaseNoteNone},
   391  			labelsAdded:   []string{releaseNoteLabelNeeded},
   392  		},
   393  		{
   394  			name:          "add releaseNoteNeeded on non-master when 1 of 2 parent PRs has releaseNoteNone",
   395  			branch:        "release-1.2",
   396  			initialLabels: []string{lgtmLabel},
   397  			body:          "Other text.\nCherry pick of #2 on release-1.2.\nCherry pick of #4 on release-1.2.\n",
   398  			parentPRs:     map[int]string{2: releaseNote, 4: releaseNoteNone},
   399  			labelsAdded:   []string{releaseNoteLabelNeeded},
   400  		},
   401  		{
   402  			name:          "remove releaseNoteNeeded on non-master when both parent PRs have a release note",
   403  			branch:        "release-1.2",
   404  			initialLabels: []string{lgtmLabel, releaseNoteLabelNeeded},
   405  			body:          "Other text.\nCherry pick of #2 on release-1.2.\nCherry pick of #4 on release-1.2.\n",
   406  			parentPRs:     map[int]string{2: releaseNote, 4: releaseNoteActionRequired},
   407  			labelsRemoved: []string{releaseNoteLabelNeeded},
   408  		},
   409  		{
   410  			name:          "add releaseNoteActionRequired on non-master when body contains note even though both parent PRs have a release note (non-mandatory RN)",
   411  			branch:        "release-1.2",
   412  			initialLabels: []string{lgtmLabel, releaseNoteLabelNeeded},
   413  			body:          "Other text.\nCherry pick of #2 on release-1.2.\nCherry pick of #4 on release-1.2.\n```release-note\nSome changes were made but there still is action required.\n```",
   414  			parentPRs:     map[int]string{2: releaseNote, 4: releaseNoteActionRequired},
   415  			labelsAdded:   []string{releaseNoteActionRequired},
   416  			labelsRemoved: []string{releaseNoteLabelNeeded},
   417  		},
   418  		{
   419  			name:          "add releaseNoteNeeded, remove release-note on non-master when release-note block is removed and parent PR has releaseNoteNone label",
   420  			branch:        "release-1.2",
   421  			initialLabels: []string{lgtmLabel, releaseNote},
   422  			body:          "Cherry pick of #2 on release-1.2.\n```release-note\n```\n/cc @cjwagner",
   423  			parentPRs:     map[int]string{2: releaseNoteNone},
   424  			labelsAdded:   []string{releaseNoteLabelNeeded},
   425  			labelsRemoved: []string{releaseNote},
   426  		},
   427  		{
   428  			name:          "add releaseNoteLabelNeeded, remove release-note on non-master when release-note block is removed and parent PR has releaseNoteNone label",
   429  			branch:        "release-1.2",
   430  			initialLabels: []string{lgtmLabel, releaseNote},
   431  			body:          "Cherry pick of #2 on release-1.2.\n```release-note\n```\n/cc @cjwagner",
   432  			parentPRs:     map[int]string{2: releaseNoteNone},
   433  			labelsAdded:   []string{releaseNoteLabelNeeded},
   434  			labelsRemoved: []string{releaseNote},
   435  		},
   436  	}
   437  	for _, test := range tests {
   438  		if test.branch == "" {
   439  			test.branch = "master"
   440  		}
   441  		fc, pr := newFakeClient(test.body, test.branch, test.initialLabels, test.issueComments, test.parentPRs)
   442  
   443  		err := handlePR(fc, logrus.WithField("plugin", pluginName), pr)
   444  		if err != nil {
   445  			t.Fatalf("Unexpected error from handlePR: %v", err)
   446  		}
   447  
   448  		// Check that all the correct labels (and only the correct labels) were added.
   449  		expectAdded := formatLabels(1, append(test.initialLabels, test.labelsAdded...)...)
   450  		for parent, label := range test.parentPRs {
   451  			expectAdded = append(expectAdded, formatLabels(parent, label)...)
   452  		}
   453  		sort.Strings(expectAdded)
   454  		sort.Strings(fc.LabelsAdded)
   455  		if !reflect.DeepEqual(expectAdded, fc.LabelsAdded) {
   456  			t.Errorf("(%s): Expected labels to be added: %q, but got: %q.", test.name, expectAdded, fc.LabelsAdded)
   457  		}
   458  		expectRemoved := formatLabels(1, test.labelsRemoved...)
   459  		sort.Strings(expectRemoved)
   460  		sort.Strings(fc.LabelsRemoved)
   461  		if !reflect.DeepEqual(expectRemoved, fc.LabelsRemoved) {
   462  			t.Errorf("(%s): Expected labels to be removed: %q, but got %q.", test.name, expectRemoved, fc.LabelsRemoved)
   463  		}
   464  	}
   465  }
   466  
   467  func TestGetReleaseNote(t *testing.T) {
   468  	tests := []struct {
   469  		body                        string
   470  		expectedReleaseNote         string
   471  		expectedReleaseNoteVariable string
   472  	}{
   473  		{
   474  			body:                        "**Release note**:  ```NONE```",
   475  			expectedReleaseNote:         "NONE",
   476  			expectedReleaseNoteVariable: releaseNoteNone,
   477  		},
   478  		{
   479  			body:                        "**Release note**:\n\n ```\nNONE\n```",
   480  			expectedReleaseNote:         "NONE",
   481  			expectedReleaseNoteVariable: releaseNoteNone,
   482  		},
   483  		{
   484  			body:                        "**Release note**:\n<!--  Steps to write your release note:\n...\n-->\n```NONE\n```",
   485  			expectedReleaseNote:         "NONE",
   486  			expectedReleaseNoteVariable: releaseNoteNone,
   487  		},
   488  		{
   489  			body:                        "**Release note**:\n\n  ```This is a description of my feature```",
   490  			expectedReleaseNote:         "This is a description of my feature",
   491  			expectedReleaseNoteVariable: releaseNote,
   492  		},
   493  		{
   494  			body:                        "**Release note**: ```This is my feature. There is some action required for my feature.```",
   495  			expectedReleaseNote:         "This is my feature. There is some action required for my feature.",
   496  			expectedReleaseNoteVariable: releaseNoteActionRequired,
   497  		},
   498  		{
   499  			body:                        "```release-note\nsomething great.\n```",
   500  			expectedReleaseNote:         "something great.",
   501  			expectedReleaseNoteVariable: releaseNote,
   502  		},
   503  		{
   504  			body:                        "```release-note\nNONE\n```",
   505  			expectedReleaseNote:         "NONE",
   506  			expectedReleaseNoteVariable: releaseNoteNone,
   507  		},
   508  		{
   509  			body:                        "```release-note\n`NONE`\n```",
   510  			expectedReleaseNote:         "`NONE`",
   511  			expectedReleaseNoteVariable: releaseNoteNone,
   512  		},
   513  		{
   514  			body:                        "```release-note\n`\"NONE\"`\n```",
   515  			expectedReleaseNote:         "`\"NONE\"`",
   516  			expectedReleaseNoteVariable: releaseNoteNone,
   517  		},
   518  		{
   519  			body:                        "**Release note**:\n```release-note\nNONE\n```\n",
   520  			expectedReleaseNote:         "NONE",
   521  			expectedReleaseNoteVariable: releaseNoteNone,
   522  		},
   523  		{
   524  			body:                        "",
   525  			expectedReleaseNote:         "",
   526  			expectedReleaseNoteVariable: releaseNoteLabelNeeded,
   527  		},
   528  	}
   529  
   530  	for testNum, test := range tests {
   531  		calculatedReleaseNote := getReleaseNote(test.body)
   532  		if test.expectedReleaseNote != calculatedReleaseNote {
   533  			t.Errorf("Test %v: Expected %v as the release note, got %v", testNum, test.expectedReleaseNote, calculatedReleaseNote)
   534  		}
   535  		calculatedLabel := determineReleaseNoteLabel(test.body)
   536  		if test.expectedReleaseNoteVariable != calculatedLabel {
   537  			t.Errorf("Test %v: Expected %v as the release note label, got %v", testNum, test.expectedReleaseNoteVariable, calculatedLabel)
   538  		}
   539  	}
   540  }