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