github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/plugins/trigger/generic-comment_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 trigger
    18  
    19  import (
    20  	"fmt"
    21  	"reflect"
    22  	"testing"
    23  
    24  	"github.com/sirupsen/logrus"
    25  
    26  	"k8s.io/apimachinery/pkg/util/sets"
    27  	"k8s.io/test-infra/prow/config"
    28  	"k8s.io/test-infra/prow/github"
    29  	"k8s.io/test-infra/prow/github/fakegithub"
    30  	"k8s.io/test-infra/prow/kube"
    31  	"k8s.io/test-infra/prow/labels"
    32  	"k8s.io/test-infra/prow/plugins"
    33  )
    34  
    35  type fkc struct {
    36  	started []string
    37  }
    38  
    39  func (c *fkc) CreateProwJob(pj kube.ProwJob) (kube.ProwJob, error) {
    40  	if !sets.NewString(c.started...).Has(pj.Spec.Context) {
    41  		c.started = append(c.started, pj.Spec.Context)
    42  	}
    43  	return pj, nil
    44  }
    45  
    46  func issueLabels(labels ...string) []string {
    47  	var ls []string
    48  	for _, label := range labels {
    49  		ls = append(ls, fmt.Sprintf("org/repo#0:%s", label))
    50  	}
    51  	return ls
    52  }
    53  
    54  type testcase struct {
    55  	name string
    56  
    57  	Author         string
    58  	PRAuthor       string
    59  	Body           string
    60  	State          string
    61  	IsPR           bool
    62  	Branch         string
    63  	ShouldBuild    bool
    64  	ShouldReport   bool
    65  	AddedLabels    []string
    66  	RemovedLabels  []string
    67  	StartsExactly  string
    68  	Presubmits     map[string][]config.Presubmit
    69  	IssueLabels    []string
    70  	IgnoreOkToTest bool
    71  }
    72  
    73  func TestHandleGenericComment(t *testing.T) {
    74  	var testcases = []testcase{
    75  		{
    76  			name: "Not a PR.",
    77  
    78  			Author:      "t",
    79  			Body:        "/ok-to-test",
    80  			State:       "open",
    81  			IsPR:        false,
    82  			ShouldBuild: false,
    83  		},
    84  		{
    85  			name: "Closed PR.",
    86  
    87  			Author:      "t",
    88  			Body:        "/ok-to-test",
    89  			State:       "closed",
    90  			IsPR:        true,
    91  			ShouldBuild: false,
    92  		},
    93  		{
    94  			name: "Comment by a bot.",
    95  
    96  			Author:      "k8s-bot",
    97  			Body:        "/ok-to-test",
    98  			State:       "open",
    99  			IsPR:        true,
   100  			ShouldBuild: false,
   101  		},
   102  		{
   103  			name: "Non-trusted member's ok to test.",
   104  
   105  			Author:      "u",
   106  			Body:        "/ok-to-test",
   107  			State:       "open",
   108  			IsPR:        true,
   109  			ShouldBuild: false,
   110  		},
   111  		{
   112  			name:        "accept /test from non-trusted member if PR author is trusted",
   113  			Author:      "u",
   114  			PRAuthor:    "t",
   115  			Body:        "/test all",
   116  			State:       "open",
   117  			IsPR:        true,
   118  			ShouldBuild: true,
   119  		},
   120  		{
   121  			name:        "reject /test from non-trusted member when PR author is untrusted",
   122  			Author:      "u",
   123  			PRAuthor:    "u",
   124  			Body:        "/test all",
   125  			State:       "open",
   126  			IsPR:        true,
   127  			ShouldBuild: false,
   128  		},
   129  		{
   130  			name: `Non-trusted member after "/ok-to-test".`,
   131  
   132  			Author:      "u",
   133  			Body:        "/test all",
   134  			State:       "open",
   135  			IsPR:        true,
   136  			ShouldBuild: true,
   137  			IssueLabels: issueLabels(labels.OkToTest),
   138  		},
   139  		{
   140  			name: `Non-trusted member after "/ok-to-test", needs-ok-to-test label wasn't deleted.`,
   141  
   142  			Author:        "u",
   143  			Body:          "/test all",
   144  			State:         "open",
   145  			IsPR:          true,
   146  			ShouldBuild:   true,
   147  			IssueLabels:   issueLabels(labels.NeedsOkToTest, labels.OkToTest),
   148  			RemovedLabels: issueLabels(labels.NeedsOkToTest),
   149  		},
   150  		{
   151  			name: "Trusted member's ok to test, IgnoreOkToTest",
   152  
   153  			Author:         "t",
   154  			Body:           "/ok-to-test",
   155  			State:          "open",
   156  			IsPR:           true,
   157  			ShouldBuild:    false,
   158  			IgnoreOkToTest: true,
   159  		},
   160  		{
   161  			name: "Trusted member's ok to test",
   162  
   163  			Author:      "t",
   164  			Body:        "looks great, thanks!\n/ok-to-test",
   165  			State:       "open",
   166  			IsPR:        true,
   167  			ShouldBuild: true,
   168  			AddedLabels: issueLabels(labels.OkToTest),
   169  		},
   170  		{
   171  			name: "Trusted member's ok to test, trailing space.",
   172  
   173  			Author:      "t",
   174  			Body:        "looks great, thanks!\n/ok-to-test \r",
   175  			State:       "open",
   176  			IsPR:        true,
   177  			ShouldBuild: true,
   178  			AddedLabels: issueLabels(labels.OkToTest),
   179  		},
   180  		{
   181  			name: "Trusted member's not ok to test.",
   182  
   183  			Author:      "t",
   184  			Body:        "not /ok-to-test",
   185  			State:       "open",
   186  			IsPR:        true,
   187  			ShouldBuild: false,
   188  		},
   189  		{
   190  			name: "Trusted member's test this.",
   191  
   192  			Author:      "t",
   193  			Body:        "/test all",
   194  			State:       "open",
   195  			IsPR:        true,
   196  			ShouldBuild: true,
   197  		},
   198  		{
   199  			name: "Wrong branch.",
   200  
   201  			Author:       "t",
   202  			Body:         "/test all",
   203  			State:        "open",
   204  			IsPR:         true,
   205  			Branch:       "other",
   206  			ShouldBuild:  false,
   207  			ShouldReport: true,
   208  		},
   209  		{
   210  			name: "Retest with one running and one failed",
   211  
   212  			Author:        "t",
   213  			Body:          "/retest",
   214  			State:         "open",
   215  			IsPR:          true,
   216  			ShouldBuild:   true,
   217  			StartsExactly: "pull-jib",
   218  		},
   219  		{
   220  			name: "Retest with one running and one failed, trailing space.",
   221  
   222  			Author:        "t",
   223  			Body:          "/retest \r",
   224  			State:         "open",
   225  			IsPR:          true,
   226  			ShouldBuild:   true,
   227  			StartsExactly: "pull-jib",
   228  		},
   229  		{
   230  			name: "needs-ok-to-test label is removed when no presubmit runs by default",
   231  
   232  			Author:      "t",
   233  			Body:        "/ok-to-test",
   234  			State:       "open",
   235  			IsPR:        true,
   236  			ShouldBuild: false,
   237  			Presubmits: map[string][]config.Presubmit{
   238  				"org/repo": {
   239  					{
   240  						JobBase: config.JobBase{
   241  							Name: "job",
   242  						},
   243  						AlwaysRun:    false,
   244  						Context:      "pull-job",
   245  						Trigger:      `/test all`,
   246  						RerunCommand: `/test all`,
   247  					},
   248  					{
   249  						JobBase: config.JobBase{
   250  							Name: "jib",
   251  						},
   252  						AlwaysRun:    false,
   253  						Context:      "pull-jib",
   254  						Trigger:      `/test jib`,
   255  						RerunCommand: `/test jib`,
   256  					},
   257  				},
   258  			},
   259  			IssueLabels:   issueLabels(labels.NeedsOkToTest),
   260  			AddedLabels:   issueLabels(labels.OkToTest),
   261  			RemovedLabels: issueLabels(labels.NeedsOkToTest),
   262  		},
   263  		{
   264  			name:   "Wrong branch w/ SkipReport",
   265  			Author: "t",
   266  			Body:   "/test all",
   267  			Branch: "other",
   268  			State:  "open",
   269  			IsPR:   true,
   270  			Presubmits: map[string][]config.Presubmit{
   271  				"org/repo": {
   272  					{
   273  						JobBase: config.JobBase{
   274  							Name: "job",
   275  						},
   276  						AlwaysRun:    true,
   277  						SkipReport:   true,
   278  						Context:      "pull-job",
   279  						Trigger:      `/test all`,
   280  						RerunCommand: `/test all`,
   281  						Brancher:     config.Brancher{Branches: []string{"master"}},
   282  					},
   283  				},
   284  			},
   285  		},
   286  		{
   287  			name:   "Retest of run_if_changed job that hasn't run. Changes require job",
   288  			Author: "t",
   289  			Body:   "/retest",
   290  			State:  "open",
   291  			IsPR:   true,
   292  			Presubmits: map[string][]config.Presubmit{
   293  				"org/repo": {
   294  					{
   295  						JobBase: config.JobBase{
   296  							Name: "jab",
   297  						},
   298  						RegexpChangeMatcher: config.RegexpChangeMatcher{
   299  							RunIfChanged: "CHANGED",
   300  						},
   301  						SkipReport:   true,
   302  						Context:      "pull-jab",
   303  						Trigger:      `/test all`,
   304  						RerunCommand: `/test all`,
   305  					},
   306  				},
   307  			},
   308  			ShouldBuild:   true,
   309  			StartsExactly: "pull-jab",
   310  		},
   311  		{
   312  			name:   "Retest of run_if_changed job that failed. Changes require job",
   313  			Author: "t",
   314  			Body:   "/retest",
   315  			State:  "open",
   316  			IsPR:   true,
   317  			Presubmits: map[string][]config.Presubmit{
   318  				"org/repo": {
   319  					{
   320  						JobBase: config.JobBase{
   321  							Name: "jib",
   322  						},
   323  						RegexpChangeMatcher: config.RegexpChangeMatcher{
   324  							RunIfChanged: "CHANGED",
   325  						},
   326  						Context:      "pull-jib",
   327  						Trigger:      `/test all`,
   328  						RerunCommand: `/test all`,
   329  					},
   330  				},
   331  			},
   332  			ShouldBuild:   true,
   333  			StartsExactly: "pull-jib",
   334  		},
   335  		{
   336  			name:   "/test of run_if_changed job that has passed",
   337  			Author: "t",
   338  			Body:   "/test jub",
   339  			State:  "open",
   340  			IsPR:   true,
   341  			Presubmits: map[string][]config.Presubmit{
   342  				"org/repo": {
   343  					{
   344  						JobBase: config.JobBase{
   345  							Name: "jub",
   346  						},
   347  						RegexpChangeMatcher: config.RegexpChangeMatcher{
   348  							RunIfChanged: "CHANGED",
   349  						},
   350  						Context:      "pull-jub",
   351  						Trigger:      `/test jub`,
   352  						RerunCommand: `/test jub`,
   353  					},
   354  				},
   355  			},
   356  			ShouldBuild:   true,
   357  			StartsExactly: "pull-jub",
   358  		},
   359  		{
   360  			name:   "Retest of run_if_changed job that failed. Changes do not require the job",
   361  			Author: "t",
   362  			Body:   "/retest",
   363  			State:  "open",
   364  			IsPR:   true,
   365  			Presubmits: map[string][]config.Presubmit{
   366  				"org/repo": {
   367  					{
   368  						JobBase: config.JobBase{
   369  							Name: "jib",
   370  						},
   371  						RegexpChangeMatcher: config.RegexpChangeMatcher{
   372  							RunIfChanged: "CHANGED2",
   373  						},
   374  						Context:      "pull-jib",
   375  						Trigger:      `/test all`,
   376  						RerunCommand: `/test all`,
   377  					},
   378  				},
   379  			},
   380  			ShouldBuild: true,
   381  		},
   382  		{
   383  			name:   "Run if changed job triggered by /ok-to-test",
   384  			Author: "t",
   385  			Body:   "/ok-to-test",
   386  			State:  "open",
   387  			IsPR:   true,
   388  			Presubmits: map[string][]config.Presubmit{
   389  				"org/repo": {
   390  					{
   391  						JobBase: config.JobBase{
   392  							Name: "jab",
   393  						},
   394  						RegexpChangeMatcher: config.RegexpChangeMatcher{
   395  							RunIfChanged: "CHANGED",
   396  						},
   397  						Context:      "pull-jab",
   398  						Trigger:      `/test all`,
   399  						RerunCommand: `/test all`,
   400  					},
   401  				},
   402  			},
   403  			ShouldBuild:   true,
   404  			StartsExactly: "pull-jab",
   405  			IssueLabels:   issueLabels(labels.NeedsOkToTest),
   406  			AddedLabels:   issueLabels(labels.OkToTest),
   407  			RemovedLabels: issueLabels(labels.NeedsOkToTest),
   408  		},
   409  		{
   410  			name:   "/test of branch-sharded job",
   411  			Author: "t",
   412  			Body:   "/test jab",
   413  			State:  "open",
   414  			IsPR:   true,
   415  			Presubmits: map[string][]config.Presubmit{
   416  				"org/repo": {
   417  					{
   418  						JobBase: config.JobBase{
   419  							Name: "jab",
   420  						},
   421  						Brancher:     config.Brancher{Branches: []string{"master"}},
   422  						Context:      "pull-jab",
   423  						Trigger:      `/test jab`,
   424  						RerunCommand: `/test jab`,
   425  					},
   426  					{
   427  						JobBase: config.JobBase{
   428  							Name: "jab",
   429  						},
   430  						Brancher:     config.Brancher{Branches: []string{"release"}},
   431  						Context:      "pull-jab",
   432  						Trigger:      `/test jab`,
   433  						RerunCommand: `/test jab`,
   434  					},
   435  				},
   436  			},
   437  			ShouldBuild:   true,
   438  			StartsExactly: "pull-jab",
   439  		},
   440  		{
   441  			name:   "branch-sharded job. no shard matches base branch",
   442  			Author: "t",
   443  			Branch: "branch",
   444  			Body:   "/test jab",
   445  			State:  "open",
   446  			IsPR:   true,
   447  			Presubmits: map[string][]config.Presubmit{
   448  				"org/repo": {
   449  					{
   450  						JobBase: config.JobBase{
   451  							Name: "jab",
   452  						},
   453  						Brancher:     config.Brancher{Branches: []string{"master"}},
   454  						Context:      "pull-jab",
   455  						Trigger:      `/test jab`,
   456  						RerunCommand: `/test jab`,
   457  					},
   458  					{
   459  						JobBase: config.JobBase{
   460  							Name: "jab",
   461  						},
   462  						Brancher:     config.Brancher{Branches: []string{"release"}},
   463  						Context:      "pull-jab",
   464  						Trigger:      `/test jab`,
   465  						RerunCommand: `/test jab`,
   466  					},
   467  				},
   468  			},
   469  			ShouldReport: true,
   470  		},
   471  		{
   472  			name: "/retest of RunIfChanged job that doesn't need to run and hasn't run",
   473  
   474  			Author: "t",
   475  			Body:   "/retest",
   476  			State:  "open",
   477  			IsPR:   true,
   478  			Presubmits: map[string][]config.Presubmit{
   479  				"org/repo": {
   480  					{
   481  						JobBase: config.JobBase{
   482  							Name: "jeb",
   483  						},
   484  						RegexpChangeMatcher: config.RegexpChangeMatcher{
   485  							RunIfChanged: "CHANGED2",
   486  						},
   487  						Context:      "pull-jeb",
   488  						Trigger:      `/test all`,
   489  						RerunCommand: `/test all`,
   490  					},
   491  				},
   492  			},
   493  			ShouldReport: true,
   494  		},
   495  		{
   496  			name: "explicit /test for RunIfChanged job that doesn't need to run",
   497  
   498  			Author: "t",
   499  			Body:   "/test pull-jeb",
   500  			State:  "open",
   501  			IsPR:   true,
   502  			Presubmits: map[string][]config.Presubmit{
   503  				"org/repo": {
   504  					{
   505  						JobBase: config.JobBase{
   506  							Name: "jeb",
   507  						},
   508  						RegexpChangeMatcher: config.RegexpChangeMatcher{
   509  							RunIfChanged: "CHANGED2",
   510  						},
   511  						Context:      "pull-jib",
   512  						Trigger:      `/test (all|pull-jeb)`,
   513  						RerunCommand: `/test pull-jeb`,
   514  					},
   515  				},
   516  			},
   517  			ShouldBuild: true,
   518  		},
   519  		{
   520  			name:   "/test all of run_if_changed job that has passed and needs to run",
   521  			Author: "t",
   522  			Body:   "/test all",
   523  			State:  "open",
   524  			IsPR:   true,
   525  			Presubmits: map[string][]config.Presubmit{
   526  				"org/repo": {
   527  					{
   528  						JobBase: config.JobBase{
   529  							Name: "jub",
   530  						},
   531  						RegexpChangeMatcher: config.RegexpChangeMatcher{
   532  							RunIfChanged: "CHANGED",
   533  						},
   534  						Context:      "pull-jub",
   535  						Trigger:      `/test jub`,
   536  						RerunCommand: `/test jub`,
   537  					},
   538  				},
   539  			},
   540  			ShouldBuild:   true,
   541  			StartsExactly: "pull-jub",
   542  		},
   543  		{
   544  			name:   "/test all of run_if_changed job that has passed and doesn't need to run",
   545  			Author: "t",
   546  			Body:   "/test all",
   547  			State:  "open",
   548  			IsPR:   true,
   549  			Presubmits: map[string][]config.Presubmit{
   550  				"org/repo": {
   551  					{
   552  						JobBase: config.JobBase{
   553  							Name: "jub",
   554  						},
   555  						RegexpChangeMatcher: config.RegexpChangeMatcher{
   556  							RunIfChanged: "CHANGED2",
   557  						},
   558  						Context:      "pull-jub",
   559  						Trigger:      `/test jub`,
   560  						RerunCommand: `/test jub`,
   561  					},
   562  				},
   563  			},
   564  			ShouldReport: true,
   565  		},
   566  		{
   567  			name:        "accept /test all from trusted user",
   568  			Author:      "t",
   569  			PRAuthor:    "t",
   570  			Body:        "/test all",
   571  			State:       "open",
   572  			IsPR:        true,
   573  			ShouldBuild: true,
   574  		},
   575  		{
   576  			name:        `Non-trusted member after "/lgtm" and "/approve"`,
   577  			Author:      "u",
   578  			PRAuthor:    "u",
   579  			Body:        "/retest",
   580  			State:       "open",
   581  			IsPR:        true,
   582  			ShouldBuild: false,
   583  			IssueLabels: issueLabels(labels.LGTM, labels.Approved),
   584  		},
   585  	}
   586  	for _, tc := range testcases {
   587  		t.Logf("running scenario %q", tc.name)
   588  		if tc.Branch == "" {
   589  			tc.Branch = "master"
   590  		}
   591  		g := &fakegithub.FakeClient{
   592  			CreatedStatuses: map[string][]github.Status{},
   593  			IssueComments:   map[int][]github.IssueComment{},
   594  			OrgMembers:      map[string][]string{"org": {"t"}},
   595  			PullRequests: map[int]*github.PullRequest{
   596  				0: {
   597  					User:   github.User{Login: tc.PRAuthor},
   598  					Number: 0,
   599  					Head: github.PullRequestBranch{
   600  						SHA: "cafe",
   601  					},
   602  					Base: github.PullRequestBranch{
   603  						Ref: tc.Branch,
   604  						Repo: github.Repo{
   605  							Owner: github.User{Login: "org"},
   606  							Name:  "repo",
   607  						},
   608  					},
   609  				},
   610  			},
   611  			IssueLabelsExisting: tc.IssueLabels,
   612  			PullRequestChanges:  map[int][]github.PullRequestChange{0: {{Filename: "CHANGED"}}},
   613  			CombinedStatuses: map[string]*github.CombinedStatus{
   614  				"cafe": {
   615  					Statuses: []github.Status{
   616  						{State: "pending", Context: "pull-job"},
   617  						{State: "failure", Context: "pull-jib"},
   618  						{State: "success", Context: "pull-jub"},
   619  					},
   620  				},
   621  			},
   622  		}
   623  		kc := &fkc{}
   624  		c := Client{
   625  			GitHubClient: g,
   626  			KubeClient:   kc,
   627  			Config:       &config.Config{},
   628  			Logger:       logrus.WithField("plugin", pluginName),
   629  		}
   630  		presubmits := tc.Presubmits
   631  		if presubmits == nil {
   632  			presubmits = map[string][]config.Presubmit{
   633  				"org/repo": {
   634  					{
   635  						JobBase: config.JobBase{
   636  							Name: "job",
   637  						},
   638  						AlwaysRun:    true,
   639  						Context:      "pull-job",
   640  						Trigger:      `/test all`,
   641  						RerunCommand: `/test all`,
   642  						Brancher:     config.Brancher{Branches: []string{"master"}},
   643  					},
   644  					{
   645  						JobBase: config.JobBase{
   646  							Name: "jib",
   647  						},
   648  						AlwaysRun:    false,
   649  						Context:      "pull-jib",
   650  						Trigger:      `/test jib`,
   651  						RerunCommand: `/test jib`,
   652  					},
   653  				},
   654  			}
   655  		}
   656  		if err := c.Config.SetPresubmits(presubmits); err != nil {
   657  			t.Fatalf("failed to set presubmits: %v", err)
   658  		}
   659  
   660  		event := github.GenericCommentEvent{
   661  			Action: github.GenericCommentActionCreated,
   662  			Repo: github.Repo{
   663  				Owner:    github.User{Login: "org"},
   664  				Name:     "repo",
   665  				FullName: "org/repo",
   666  			},
   667  			Body:        tc.Body,
   668  			User:        github.User{Login: tc.Author},
   669  			IssueAuthor: github.User{Login: tc.PRAuthor},
   670  			IssueState:  tc.State,
   671  			IsPR:        tc.IsPR,
   672  		}
   673  
   674  		trigger := plugins.Trigger{
   675  			IgnoreOkToTest: tc.IgnoreOkToTest,
   676  		}
   677  
   678  		// In some cases handleGenericComment can be called twice for the same event.
   679  		// For instance on Issue/PR creation and modification.
   680  		// Let's call it twice to ensure idempotency.
   681  		if err := handleGenericComment(c, &trigger, event); err != nil {
   682  			t.Fatalf("Didn't expect error: %s", err)
   683  		}
   684  		validate(kc, g, tc, t)
   685  		if err := handleGenericComment(c, &trigger, event); err != nil {
   686  			t.Fatalf("Didn't expect error: %s", err)
   687  		}
   688  		validate(kc, g, tc, t)
   689  	}
   690  }
   691  
   692  func validate(kc *fkc, g *fakegithub.FakeClient, tc testcase, t *testing.T) {
   693  	if len(kc.started) > 0 && !tc.ShouldBuild {
   694  		t.Errorf("Built but should not have: %+v", tc)
   695  	} else if len(kc.started) == 0 && tc.ShouldBuild {
   696  		t.Errorf("Not built but should have: %+v", tc)
   697  	}
   698  	if tc.StartsExactly != "" && (len(kc.started) != 1 || kc.started[0] != tc.StartsExactly) {
   699  		t.Errorf("Didn't build expected context %v, instead built %v", tc.StartsExactly, kc.started)
   700  	}
   701  	if tc.ShouldReport && len(g.CreatedStatuses) == 0 {
   702  		t.Error("Expected report to github")
   703  	} else if !tc.ShouldReport && len(g.CreatedStatuses) > 0 {
   704  		t.Errorf("Expected no reports to github, but got %d", len(g.CreatedStatuses))
   705  	}
   706  	if !reflect.DeepEqual(g.IssueLabelsAdded, tc.AddedLabels) {
   707  		t.Errorf("expected %q to be added, got %q", tc.AddedLabels, g.IssueLabelsAdded)
   708  	}
   709  	if !reflect.DeepEqual(g.IssueLabelsRemoved, tc.RemovedLabels) {
   710  		t.Errorf("expected %q to be removed, got %q", tc.RemovedLabels, g.IssueLabelsRemoved)
   711  	}
   712  }