github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/pkg/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  	"log"
    22  	"reflect"
    23  	"strings"
    24  	"testing"
    25  
    26  	"github.com/sirupsen/logrus"
    27  	"k8s.io/apimachinery/pkg/util/sets"
    28  	clienttesting "k8s.io/client-go/testing"
    29  
    30  	prowapi "sigs.k8s.io/prow/pkg/apis/prowjobs/v1"
    31  	"sigs.k8s.io/prow/pkg/client/clientset/versioned/fake"
    32  	"sigs.k8s.io/prow/pkg/config"
    33  	"sigs.k8s.io/prow/pkg/github"
    34  	"sigs.k8s.io/prow/pkg/github/fakegithub"
    35  	"sigs.k8s.io/prow/pkg/labels"
    36  	"sigs.k8s.io/prow/pkg/pjutil"
    37  	"sigs.k8s.io/prow/pkg/plugins"
    38  )
    39  
    40  func issueLabels(labels ...string) []string {
    41  	var ls []string
    42  	for _, label := range labels {
    43  		ls = append(ls, fmt.Sprintf("org/repo#0:%s", label))
    44  	}
    45  	return ls
    46  }
    47  
    48  type testcase struct {
    49  	name string
    50  
    51  	Author         string
    52  	PRAuthor       string
    53  	Body           string
    54  	State          string
    55  	IsPR           bool
    56  	Branch         string
    57  	ShouldBuild    bool
    58  	AddedLabels    []string
    59  	RemovedLabels  []string
    60  	StartsExactly  string
    61  	Presubmits     map[string][]config.Presubmit
    62  	IssueLabels    []string
    63  	IgnoreOkToTest bool
    64  	AddedComment   string
    65  }
    66  
    67  func TestHandleGenericComment(t *testing.T) {
    68  	helpComment := "The following commands are available to trigger required jobs:\n* `/test jib`\n* `/test job`\n\n"
    69  	helpTestAllWithJobsComment := fmt.Sprintf("Use `/test all` to run the following jobs that were automatically triggered:%s\n\n", "\n* `job`")
    70  	var testcases = []testcase{
    71  		{
    72  			name: "Not a PR.",
    73  
    74  			Author:      "trusted-member",
    75  			Body:        "/ok-to-test",
    76  			State:       "open",
    77  			IsPR:        false,
    78  			ShouldBuild: false,
    79  		},
    80  		{
    81  			name: "Closed PR.",
    82  
    83  			Author:      "trusted-member",
    84  			Body:        "/ok-to-test",
    85  			State:       "closed",
    86  			IsPR:        true,
    87  			ShouldBuild: false,
    88  		},
    89  		{
    90  			name: "Comment by a bot.",
    91  
    92  			Author:      "k8s-ci-robot",
    93  			Body:        "/ok-to-test",
    94  			State:       "open",
    95  			IsPR:        true,
    96  			ShouldBuild: false,
    97  		},
    98  		{
    99  			name: "Irrelevant comment leads to no action.",
   100  
   101  			Author:      "trusted-member",
   102  			Body:        "Nice weather outside, right?",
   103  			State:       "open",
   104  			IsPR:        true,
   105  			ShouldBuild: false,
   106  		},
   107  		{
   108  			name: "Non-trusted member's ok to test.",
   109  
   110  			Author:      "untrusted-member",
   111  			Body:        "/ok-to-test",
   112  			State:       "open",
   113  			IsPR:        true,
   114  			ShouldBuild: false,
   115  		},
   116  		{
   117  			name:        "accept /test from non-trusted member if PR author is trusted",
   118  			Author:      "untrusted-member",
   119  			PRAuthor:    "trusted-member",
   120  			Body:        "/test all",
   121  			State:       "open",
   122  			IsPR:        true,
   123  			ShouldBuild: true,
   124  		},
   125  		{
   126  			name:        "reject /test from non-trusted member when PR author is untrusted",
   127  			Author:      "untrusted-member",
   128  			PRAuthor:    "untrusted-member",
   129  			Body:        "/test all",
   130  			State:       "open",
   131  			IsPR:        true,
   132  			ShouldBuild: false,
   133  		},
   134  		{
   135  			name: `Non-trusted member after "/ok-to-test".`,
   136  
   137  			Author:      "untrusted-member",
   138  			Body:        "/test all",
   139  			State:       "open",
   140  			IsPR:        true,
   141  			ShouldBuild: true,
   142  			IssueLabels: issueLabels(labels.OkToTest),
   143  		},
   144  		{
   145  			name: `Non-trusted member after "/ok-to-test", needs-ok-to-test label wasn't deleted.`,
   146  
   147  			Author:        "untrusted-member",
   148  			Body:          "/test all",
   149  			State:         "open",
   150  			IsPR:          true,
   151  			ShouldBuild:   true,
   152  			IssueLabels:   issueLabels(labels.NeedsOkToTest, labels.OkToTest),
   153  			RemovedLabels: issueLabels(labels.NeedsOkToTest),
   154  		},
   155  		{
   156  			name: "Trusted member's ok to test, IgnoreOkToTest",
   157  
   158  			Author:         "trusted-member",
   159  			Body:           "/ok-to-test",
   160  			State:          "open",
   161  			IsPR:           true,
   162  			ShouldBuild:    false,
   163  			IgnoreOkToTest: true,
   164  		},
   165  		{
   166  			name: "Trusted member's ok to test",
   167  
   168  			Author:      "trusted-member",
   169  			Body:        "looks great, thanks!\n/ok-to-test",
   170  			State:       "open",
   171  			IsPR:        true,
   172  			ShouldBuild: true,
   173  			AddedLabels: issueLabels(labels.OkToTest),
   174  		},
   175  		{
   176  			name: "Trusted member's ok to test, trailing space.",
   177  
   178  			Author:      "trusted-member",
   179  			Body:        "looks great, thanks!\n/ok-to-test \r",
   180  			State:       "open",
   181  			IsPR:        true,
   182  			ShouldBuild: true,
   183  			AddedLabels: issueLabels(labels.OkToTest),
   184  		},
   185  		{
   186  			name: "Trusted member's not ok to test.",
   187  
   188  			Author:      "trusted-member",
   189  			Body:        "not /ok-to-test",
   190  			State:       "open",
   191  			IsPR:        true,
   192  			ShouldBuild: false,
   193  		},
   194  		{
   195  			name: "Trusted member's test this.",
   196  
   197  			Author:      "trusted-member",
   198  			Body:        "/test all",
   199  			State:       "open",
   200  			IsPR:        true,
   201  			ShouldBuild: true,
   202  		},
   203  		{
   204  			name: "Wrong branch",
   205  
   206  			Author:      "trusted-member",
   207  			Body:        "/test all",
   208  			State:       "open",
   209  			IsPR:        true,
   210  			Branch:      "other",
   211  			ShouldBuild: false,
   212  		},
   213  		{
   214  			name: "Retest with one running and one failed",
   215  
   216  			Author:        "trusted-member",
   217  			Body:          "/retest",
   218  			State:         "open",
   219  			IsPR:          true,
   220  			ShouldBuild:   true,
   221  			StartsExactly: "pull-jib",
   222  		},
   223  		{
   224  			name: "Retest with one running and one failed, trailing space.",
   225  
   226  			Author:        "trusted-member",
   227  			Body:          "/retest \r",
   228  			State:         "open",
   229  			IsPR:          true,
   230  			ShouldBuild:   true,
   231  			StartsExactly: "pull-jib",
   232  		},
   233  		{
   234  			name:   "test of silly regex job",
   235  			Author: "trusted-member",
   236  			Body:   "Nice weather outside, right?",
   237  			State:  "open",
   238  			IsPR:   true,
   239  			Presubmits: map[string][]config.Presubmit{
   240  				"org/repo": {
   241  					{
   242  						JobBase: config.JobBase{
   243  							Name: "jab",
   244  						},
   245  						Brancher: config.Brancher{Branches: []string{"master"}},
   246  						Reporter: config.Reporter{
   247  							Context: "pull-jab",
   248  						},
   249  						Trigger:      "Nice weather outside, right?",
   250  						RerunCommand: "Nice weather outside, right?",
   251  					},
   252  				},
   253  			},
   254  			ShouldBuild:   true,
   255  			StartsExactly: "pull-jab",
   256  		},
   257  		{
   258  			name: "needs-ok-to-test label is removed when no presubmit runs by default",
   259  
   260  			Author:      "trusted-member",
   261  			Body:        "/ok-to-test",
   262  			State:       "open",
   263  			IsPR:        true,
   264  			ShouldBuild: false,
   265  			Presubmits: map[string][]config.Presubmit{
   266  				"org/repo": {
   267  					{
   268  						JobBase: config.JobBase{
   269  							Name: "job",
   270  						},
   271  						AlwaysRun: false,
   272  						Reporter: config.Reporter{
   273  							Context: "pull-job",
   274  						},
   275  						Trigger:      `(?m)^/test (?:.*? )?job(?: .*?)?$`,
   276  						RerunCommand: `/test job`,
   277  					},
   278  					{
   279  						JobBase: config.JobBase{
   280  							Name: "jib",
   281  						},
   282  						AlwaysRun: false,
   283  						Reporter: config.Reporter{
   284  							Context: "pull-jib",
   285  						},
   286  						Trigger:      `(?m)^/test (?:.*? )?jib(?: .*?)?$`,
   287  						RerunCommand: `/test jib`,
   288  					},
   289  				},
   290  			},
   291  			IssueLabels:   issueLabels(labels.NeedsOkToTest),
   292  			AddedLabels:   issueLabels(labels.OkToTest),
   293  			RemovedLabels: issueLabels(labels.NeedsOkToTest),
   294  		},
   295  		{
   296  			name:   "Wrong branch w/ SkipReport",
   297  			Author: "trusted-member",
   298  			Body:   "/test all",
   299  			Branch: "other",
   300  			State:  "open",
   301  			IsPR:   true,
   302  			Presubmits: map[string][]config.Presubmit{
   303  				"org/repo": {
   304  					{
   305  						JobBase: config.JobBase{
   306  							Name: "job",
   307  						},
   308  						AlwaysRun: true,
   309  						Reporter: config.Reporter{
   310  							SkipReport: true,
   311  							Context:    "pull-job",
   312  						},
   313  						Trigger:      `(?m)^/test (?:.*? )?job(?: .*?)?$`,
   314  						RerunCommand: `/test job`,
   315  						Brancher:     config.Brancher{Branches: []string{"master"}},
   316  					},
   317  				},
   318  			},
   319  		},
   320  		{
   321  			name:   "Retest of run_if_changed job that hasn't run. Changes require job",
   322  			Author: "trusted-member",
   323  			Body:   "/retest",
   324  			State:  "open",
   325  			IsPR:   true,
   326  			Presubmits: map[string][]config.Presubmit{
   327  				"org/repo": {
   328  					{
   329  						JobBase: config.JobBase{
   330  							Name: "jab",
   331  						},
   332  						RegexpChangeMatcher: config.RegexpChangeMatcher{
   333  							RunIfChanged: "CHANGED",
   334  						},
   335  						Reporter: config.Reporter{
   336  							SkipReport: true,
   337  							Context:    "pull-jab",
   338  						},
   339  						Trigger:      `(?m)^/test (?:.*? )?jab(?: .*?)?$`,
   340  						RerunCommand: `/test jab`,
   341  					},
   342  				},
   343  			},
   344  			ShouldBuild:   true,
   345  			StartsExactly: "pull-jab",
   346  		},
   347  		{
   348  			name:   "Retest of skip_if_only_changed job that hasn't run. Changes require job",
   349  			Author: "trusted-member",
   350  			Body:   "/retest",
   351  			State:  "open",
   352  			IsPR:   true,
   353  			Presubmits: map[string][]config.Presubmit{
   354  				"org/repo": {
   355  					{
   356  						JobBase: config.JobBase{
   357  							Name: "jab",
   358  						},
   359  						RegexpChangeMatcher: config.RegexpChangeMatcher{
   360  							SkipIfOnlyChanged: "CHANGED2",
   361  						},
   362  						Reporter: config.Reporter{
   363  							SkipReport: true,
   364  							Context:    "pull-jab",
   365  						},
   366  						Trigger:      `(?m)^/test (?:.*? )?jab(?: .*?)?$`,
   367  						RerunCommand: `/test jab`,
   368  					},
   369  				},
   370  			},
   371  			ShouldBuild:   true,
   372  			StartsExactly: "pull-jab",
   373  		},
   374  		{
   375  			name:   "Retest of run_if_changed job that failed. Changes require job",
   376  			Author: "trusted-member",
   377  			Body:   "/retest",
   378  			State:  "open",
   379  			IsPR:   true,
   380  			Presubmits: map[string][]config.Presubmit{
   381  				"org/repo": {
   382  					{
   383  						JobBase: config.JobBase{
   384  							Name: "jib",
   385  						},
   386  						RegexpChangeMatcher: config.RegexpChangeMatcher{
   387  							RunIfChanged: "CHANGED",
   388  						},
   389  						Reporter: config.Reporter{
   390  							Context: "pull-jib",
   391  						},
   392  						Trigger:      `(?m)^/test (?:.*? )?jib(?: .*?)?$`,
   393  						RerunCommand: `/test jib`,
   394  					},
   395  				},
   396  			},
   397  			ShouldBuild:   true,
   398  			StartsExactly: "pull-jib",
   399  		},
   400  		{
   401  			name:   "Retest of skip_if_only_changed job that failed. Changes require job",
   402  			Author: "trusted-member",
   403  			Body:   "/retest",
   404  			State:  "open",
   405  			IsPR:   true,
   406  			Presubmits: map[string][]config.Presubmit{
   407  				"org/repo": {
   408  					{
   409  						JobBase: config.JobBase{
   410  							Name: "jib",
   411  						},
   412  						RegexpChangeMatcher: config.RegexpChangeMatcher{
   413  							SkipIfOnlyChanged: "CHANGED2",
   414  						},
   415  						Reporter: config.Reporter{
   416  							Context: "pull-jib",
   417  						},
   418  						Trigger:      `(?m)^/test (?:.*? )?jib(?: .*?)?$`,
   419  						RerunCommand: `/test jib`,
   420  					},
   421  				},
   422  			},
   423  			ShouldBuild:   true,
   424  			StartsExactly: "pull-jib",
   425  		},
   426  		{
   427  			name:   "/test of run_if_changed job that has passed",
   428  			Author: "trusted-member",
   429  			Body:   "/test jub",
   430  			State:  "open",
   431  			IsPR:   true,
   432  			Presubmits: map[string][]config.Presubmit{
   433  				"org/repo": {
   434  					{
   435  						JobBase: config.JobBase{
   436  							Name: "jub",
   437  						},
   438  						RegexpChangeMatcher: config.RegexpChangeMatcher{
   439  							RunIfChanged: "CHANGED",
   440  						},
   441  						Reporter: config.Reporter{
   442  							Context: "pull-jub",
   443  						},
   444  						Trigger:      `(?m)^/test (?:.*? )?jub(?: .*?)?$`,
   445  						RerunCommand: `/test jub`,
   446  					},
   447  				},
   448  			},
   449  			ShouldBuild:   true,
   450  			StartsExactly: "pull-jub",
   451  		},
   452  		{
   453  			name:   "/test of skip_if_only_changed job that has passed",
   454  			Author: "trusted-member",
   455  			Body:   "/test jub",
   456  			State:  "open",
   457  			IsPR:   true,
   458  			Presubmits: map[string][]config.Presubmit{
   459  				"org/repo": {
   460  					{
   461  						JobBase: config.JobBase{
   462  							Name: "jub",
   463  						},
   464  						RegexpChangeMatcher: config.RegexpChangeMatcher{
   465  							SkipIfOnlyChanged: "CHANGED2",
   466  						},
   467  						Reporter: config.Reporter{
   468  							Context: "pull-jub",
   469  						},
   470  						Trigger:      `(?m)^/test (?:.*? )?jub(?: .*?)?$`,
   471  						RerunCommand: `/test jub`,
   472  					},
   473  				},
   474  			},
   475  			ShouldBuild:   true,
   476  			StartsExactly: "pull-jub",
   477  		},
   478  		{
   479  			name:   "Retest triggers failed job",
   480  			Author: "trusted-member",
   481  			Body:   "/retest",
   482  			State:  "open",
   483  			IsPR:   true,
   484  			Presubmits: map[string][]config.Presubmit{
   485  				"org/repo": {
   486  					{
   487  						JobBase: config.JobBase{
   488  							Name: "jib",
   489  						},
   490  						Reporter: config.Reporter{
   491  							Context: "pull-jib",
   492  						},
   493  						Trigger:      `(?m)^/test (?:.*? )?jib(?: .*?)?$`,
   494  						RerunCommand: `/test jib`,
   495  					},
   496  				},
   497  			},
   498  			ShouldBuild: true,
   499  		},
   500  		{
   501  			name:   "Retest triggers failed job that is optional",
   502  			Author: "trusted-member",
   503  			Body:   "/retest",
   504  			State:  "open",
   505  			IsPR:   true,
   506  			Presubmits: map[string][]config.Presubmit{
   507  				"org/repo": {
   508  					{
   509  						JobBase: config.JobBase{
   510  							Name: "jib",
   511  						},
   512  						Reporter: config.Reporter{
   513  							Context: "pull-jib",
   514  						},
   515  						Trigger:      `(?m)^/test (?:.*? )?jib(?: .*?)?$`,
   516  						RerunCommand: `/test jib`,
   517  						Optional:     true,
   518  					},
   519  				},
   520  			},
   521  			ShouldBuild: true,
   522  		},
   523  		{
   524  			name:   "Retest-Required doesn't triggers failed job",
   525  			Author: "trusted-member",
   526  			Body:   "/retest-required",
   527  			State:  "open",
   528  			IsPR:   true,
   529  			Presubmits: map[string][]config.Presubmit{
   530  				"org/repo": {
   531  					{
   532  						JobBase: config.JobBase{
   533  							Name: "jib",
   534  						},
   535  						Reporter: config.Reporter{
   536  							Context: "pull-jib",
   537  						},
   538  						Trigger:      `(?m)^/test (?:.*? )?jib(?: .*?)?$`,
   539  						RerunCommand: `/test jib`,
   540  					},
   541  				},
   542  			},
   543  			ShouldBuild: true,
   544  		},
   545  		{
   546  			name:   "Retest-Required doesn't trigger failed job that is optional",
   547  			Author: "trusted-member",
   548  			Body:   "/retest-required",
   549  			State:  "open",
   550  			IsPR:   true,
   551  			Presubmits: map[string][]config.Presubmit{
   552  				"org/repo": {
   553  					{
   554  						JobBase: config.JobBase{
   555  							Name: "jib",
   556  						},
   557  						Reporter: config.Reporter{
   558  							Context: "pull-jib",
   559  						},
   560  						Trigger:      `(?m)^/test (?:.*? )?jib(?: .*?)?$`,
   561  						RerunCommand: `/test jib`,
   562  						Optional:     true,
   563  					},
   564  				},
   565  			},
   566  		},
   567  		{
   568  			name:   "Retest of run_if_changed job that failed. Changes do not require the job",
   569  			Author: "trusted-member",
   570  			Body:   "/retest",
   571  			State:  "open",
   572  			IsPR:   true,
   573  			Presubmits: map[string][]config.Presubmit{
   574  				"org/repo": {
   575  					{
   576  						JobBase: config.JobBase{
   577  							Name: "jib",
   578  						},
   579  						RegexpChangeMatcher: config.RegexpChangeMatcher{
   580  							RunIfChanged: "CHANGED2",
   581  						},
   582  						Reporter: config.Reporter{
   583  							Context: "pull-jib",
   584  						},
   585  						Trigger:      `(?m)^/test (?:.*? )?jib(?: .*?)?$`,
   586  						RerunCommand: `/test jib`,
   587  					},
   588  				},
   589  			},
   590  			ShouldBuild: true,
   591  		},
   592  		{
   593  			name:   "Retest of skip_if_only_changed job that failed. Changes do not require the job",
   594  			Author: "trusted-member",
   595  			Body:   "/retest",
   596  			State:  "open",
   597  			IsPR:   true,
   598  			Presubmits: map[string][]config.Presubmit{
   599  				"org/repo": {
   600  					{
   601  						JobBase: config.JobBase{
   602  							Name: "jib",
   603  						},
   604  						RegexpChangeMatcher: config.RegexpChangeMatcher{
   605  							SkipIfOnlyChanged: "CHANGED",
   606  						},
   607  						Reporter: config.Reporter{
   608  							Context: "pull-jib",
   609  						},
   610  						Trigger:      `(?m)^/test (?:.*? )?jib(?: .*?)?$`,
   611  						RerunCommand: `/test jib`,
   612  					},
   613  				},
   614  			},
   615  			ShouldBuild: true,
   616  		},
   617  		{
   618  			name:   "Run if changed job triggered by /ok-to-test",
   619  			Author: "trusted-member",
   620  			Body:   "/ok-to-test",
   621  			State:  "open",
   622  			IsPR:   true,
   623  			Presubmits: map[string][]config.Presubmit{
   624  				"org/repo": {
   625  					{
   626  						JobBase: config.JobBase{
   627  							Name: "jab",
   628  						},
   629  						RegexpChangeMatcher: config.RegexpChangeMatcher{
   630  							RunIfChanged: "CHANGED",
   631  						},
   632  						Reporter: config.Reporter{
   633  							Context: "pull-jab",
   634  						},
   635  						Trigger:      `(?m)^/test (?:.*? )?jab(?: .*?)?$`,
   636  						RerunCommand: `/test jab`,
   637  					},
   638  				},
   639  			},
   640  			ShouldBuild:   true,
   641  			StartsExactly: "pull-jab",
   642  			IssueLabels:   issueLabels(labels.NeedsOkToTest),
   643  			AddedLabels:   issueLabels(labels.OkToTest),
   644  			RemovedLabels: issueLabels(labels.NeedsOkToTest),
   645  		},
   646  		{
   647  			name:   "Run if non-skipped job triggered by /ok-to-test",
   648  			Author: "trusted-member",
   649  			Body:   "/ok-to-test",
   650  			State:  "open",
   651  			IsPR:   true,
   652  			Presubmits: map[string][]config.Presubmit{
   653  				"org/repo": {
   654  					{
   655  						JobBase: config.JobBase{
   656  							Name: "jab",
   657  						},
   658  						RegexpChangeMatcher: config.RegexpChangeMatcher{
   659  							SkipIfOnlyChanged: "CHANGED2",
   660  						},
   661  						Reporter: config.Reporter{
   662  							Context: "pull-jab",
   663  						},
   664  						Trigger:      `(?m)^/test (?:.*? )?jab(?: .*?)?$`,
   665  						RerunCommand: `/test jab`,
   666  					},
   667  				},
   668  			},
   669  			ShouldBuild:   true,
   670  			StartsExactly: "pull-jab",
   671  			IssueLabels:   issueLabels(labels.NeedsOkToTest),
   672  			AddedLabels:   issueLabels(labels.OkToTest),
   673  			RemovedLabels: issueLabels(labels.NeedsOkToTest),
   674  		},
   675  		{
   676  			name:   "/test of branch-sharded job",
   677  			Author: "trusted-member",
   678  			Body:   "/test jab",
   679  			State:  "open",
   680  			IsPR:   true,
   681  			Presubmits: map[string][]config.Presubmit{
   682  				"org/repo": {
   683  					{
   684  						JobBase: config.JobBase{
   685  							Name: "jab",
   686  						},
   687  						Brancher: config.Brancher{Branches: []string{"master"}},
   688  						Reporter: config.Reporter{
   689  							Context: "pull-jab",
   690  						},
   691  						Trigger:      `(?m)^/test (?:.*? )?jab(?: .*?)?$`,
   692  						RerunCommand: `/test jab`,
   693  					},
   694  					{
   695  						JobBase: config.JobBase{
   696  							Name: "jab",
   697  						},
   698  						Brancher: config.Brancher{Branches: []string{"release"}},
   699  						Reporter: config.Reporter{
   700  							Context: "pull-jab",
   701  						},
   702  						Trigger:      `(?m)^/test (?:.*? )?jab(?: .*?)?$`,
   703  						RerunCommand: `/test jab`,
   704  					},
   705  				},
   706  			},
   707  			ShouldBuild:   true,
   708  			StartsExactly: "pull-jab",
   709  		},
   710  		{
   711  			name:   "branch-sharded job. no shard matches base branch",
   712  			Author: "trusted-member",
   713  			Branch: "branch",
   714  			Body:   "/test jab",
   715  			State:  "open",
   716  			IsPR:   true,
   717  			Presubmits: map[string][]config.Presubmit{
   718  				"org/repo": {
   719  					{
   720  						JobBase: config.JobBase{
   721  							Name: "jab",
   722  						},
   723  						Brancher: config.Brancher{Branches: []string{"master"}},
   724  						Reporter: config.Reporter{
   725  							Context: "pull-jab",
   726  						},
   727  						Trigger:      `(?m)^/test (?:.*? )?jab(?: .*?)?$`,
   728  						RerunCommand: `/test jab`,
   729  					},
   730  					{
   731  						JobBase: config.JobBase{
   732  							Name: "jab",
   733  						},
   734  						Brancher: config.Brancher{Branches: []string{"release"}},
   735  						Reporter: config.Reporter{
   736  							Context: "pull-jab",
   737  						},
   738  						Trigger:      `(?m)^/test (?:.*? )?jab(?: .*?)?$`,
   739  						RerunCommand: `/test jab`,
   740  					},
   741  				},
   742  			},
   743  		},
   744  		{
   745  			name: "/retest of RunIfChanged job that doesn't need to run and hasn't run",
   746  
   747  			Author: "trusted-member",
   748  			Body:   "/retest",
   749  			State:  "open",
   750  			IsPR:   true,
   751  			Presubmits: map[string][]config.Presubmit{
   752  				"org/repo": {
   753  					{
   754  						JobBase: config.JobBase{
   755  							Name: "jeb",
   756  						},
   757  						RegexpChangeMatcher: config.RegexpChangeMatcher{
   758  							RunIfChanged: "CHANGED2",
   759  						},
   760  						Reporter: config.Reporter{
   761  							Context: "pull-jeb",
   762  						},
   763  						Trigger:      `(?m)^/test (?:.*? )?jeb(?: .*?)?$`,
   764  						RerunCommand: `/test jeb`,
   765  					},
   766  				},
   767  			},
   768  		},
   769  		{
   770  			name: "/retest of SkipIfOnlyChanged job that doesn't need to run and hasn't run",
   771  
   772  			Author: "trusted-member",
   773  			Body:   "/retest",
   774  			State:  "open",
   775  			IsPR:   true,
   776  			Presubmits: map[string][]config.Presubmit{
   777  				"org/repo": {
   778  					{
   779  						JobBase: config.JobBase{
   780  							Name: "jeb",
   781  						},
   782  						RegexpChangeMatcher: config.RegexpChangeMatcher{
   783  							SkipIfOnlyChanged: "CHANGED",
   784  						},
   785  						Reporter: config.Reporter{
   786  							Context: "pull-jeb",
   787  						},
   788  						Trigger:      `(?m)^/test (?:.*? )?jeb(?: .*?)?$`,
   789  						RerunCommand: `/test jeb`,
   790  					},
   791  				},
   792  			},
   793  		},
   794  		{
   795  			name: "explicit /test for RunIfChanged job that doesn't need to run",
   796  
   797  			Author: "trusted-member",
   798  			Body:   "/test pull-jeb",
   799  			State:  "open",
   800  			IsPR:   true,
   801  			Presubmits: map[string][]config.Presubmit{
   802  				"org/repo": {
   803  					{
   804  						JobBase: config.JobBase{
   805  							Name: "jeb",
   806  						},
   807  						RegexpChangeMatcher: config.RegexpChangeMatcher{
   808  							RunIfChanged: "CHANGED2",
   809  						},
   810  						Reporter: config.Reporter{
   811  							Context: "pull-jeb",
   812  						},
   813  						Trigger:      `(?m)^/test (?:.*? )?jeb(?: .*?)?$`,
   814  						RerunCommand: `/test jeb`,
   815  					},
   816  				},
   817  			},
   818  			ShouldBuild: false,
   819  		},
   820  		{
   821  			name: "explicit /test for SkipIfOnlyChanged job that doesn't need to run",
   822  
   823  			Author: "trusted-member",
   824  			Body:   "/test pull-jeb",
   825  			State:  "open",
   826  			IsPR:   true,
   827  			Presubmits: map[string][]config.Presubmit{
   828  				"org/repo": {
   829  					{
   830  						JobBase: config.JobBase{
   831  							Name: "jeb",
   832  						},
   833  						RegexpChangeMatcher: config.RegexpChangeMatcher{
   834  							SkipIfOnlyChanged: "CHANGED",
   835  						},
   836  						Reporter: config.Reporter{
   837  							Context: "pull-jeb",
   838  						},
   839  						Trigger:      `(?m)^/test (?:.*? )?jeb(?: .*?)?$`,
   840  						RerunCommand: `/test jeb`,
   841  					},
   842  				},
   843  			},
   844  			ShouldBuild: false,
   845  		},
   846  		{
   847  			name:   "/test all of run_if_changed job that has passed and needs to run",
   848  			Author: "trusted-member",
   849  			Body:   "/test all",
   850  			State:  "open",
   851  			IsPR:   true,
   852  			Presubmits: map[string][]config.Presubmit{
   853  				"org/repo": {
   854  					{
   855  						JobBase: config.JobBase{
   856  							Name: "jub",
   857  						},
   858  						RegexpChangeMatcher: config.RegexpChangeMatcher{
   859  							RunIfChanged: "CHANGED",
   860  						},
   861  						Reporter: config.Reporter{
   862  							Context: "pull-jub",
   863  						},
   864  						Trigger:      `(?m)^/test (?:.*? )?jub(?: .*?)?$`,
   865  						RerunCommand: `/test jub`,
   866  					},
   867  				},
   868  			},
   869  			ShouldBuild:   true,
   870  			StartsExactly: "pull-jub",
   871  		},
   872  		{
   873  			name:   "/test all of skip_if_only_changed job that has passed and needs to run",
   874  			Author: "trusted-member",
   875  			Body:   "/test all",
   876  			State:  "open",
   877  			IsPR:   true,
   878  			Presubmits: map[string][]config.Presubmit{
   879  				"org/repo": {
   880  					{
   881  						JobBase: config.JobBase{
   882  							Name: "jub",
   883  						},
   884  						RegexpChangeMatcher: config.RegexpChangeMatcher{
   885  							SkipIfOnlyChanged: "CHANGED2",
   886  						},
   887  						Reporter: config.Reporter{
   888  							Context: "pull-jub",
   889  						},
   890  						Trigger:      `(?m)^/test (?:.*? )?jub(?: .*?)?$`,
   891  						RerunCommand: `/test jub`,
   892  					},
   893  				},
   894  			},
   895  			ShouldBuild:   true,
   896  			StartsExactly: "pull-jub",
   897  		},
   898  		{
   899  			name:   "/test all of run_if_changed job that has passed and doesn't need to run",
   900  			Author: "trusted-member",
   901  			Body:   "/test all",
   902  			State:  "open",
   903  			IsPR:   true,
   904  			Presubmits: map[string][]config.Presubmit{
   905  				"org/repo": {
   906  					{
   907  						JobBase: config.JobBase{
   908  							Name: "jub",
   909  						},
   910  						RegexpChangeMatcher: config.RegexpChangeMatcher{
   911  							RunIfChanged: "CHANGED2",
   912  						},
   913  						Reporter: config.Reporter{
   914  							Context: "pull-jub",
   915  						},
   916  						Trigger:      `(?m)^/test (?:.*? )?jub(?: .*?)?$`,
   917  						RerunCommand: `/test jub`,
   918  					},
   919  				},
   920  			},
   921  		},
   922  		{
   923  			name:   "/test all of skip_if_only_changed job that has passed and doesn't need to run",
   924  			Author: "trusted-member",
   925  			Body:   "/test all",
   926  			State:  "open",
   927  			IsPR:   true,
   928  			Presubmits: map[string][]config.Presubmit{
   929  				"org/repo": {
   930  					{
   931  						JobBase: config.JobBase{
   932  							Name: "jub",
   933  						},
   934  						RegexpChangeMatcher: config.RegexpChangeMatcher{
   935  							SkipIfOnlyChanged: "CHANGED",
   936  						},
   937  						Reporter: config.Reporter{
   938  							Context: "pull-jub",
   939  						},
   940  						Trigger:      `(?m)^/test (?:.*? )?jub(?: .*?)?$`,
   941  						RerunCommand: `/test jub`,
   942  					},
   943  				},
   944  			},
   945  		},
   946  		{
   947  			name:        "accept /test all from trusted user",
   948  			Author:      "trusted-member",
   949  			PRAuthor:    "trusted-member",
   950  			Body:        "/test all",
   951  			State:       "open",
   952  			IsPR:        true,
   953  			ShouldBuild: true,
   954  		},
   955  		{
   956  			name:        `Non-trusted member after "/lgtm" and "/approve"`,
   957  			Author:      "untrusted-member",
   958  			PRAuthor:    "untrusted-member",
   959  			Body:        "/retest",
   960  			State:       "open",
   961  			IsPR:        true,
   962  			ShouldBuild: false,
   963  			IssueLabels: issueLabels(labels.LGTM, labels.Approved),
   964  		},
   965  		{
   966  			name:   `help command "/test ?" lists available presubmits`,
   967  			Author: "trusted-member",
   968  			Body:   "/test ?",
   969  			State:  "open",
   970  			IsPR:   true,
   971  			Presubmits: map[string][]config.Presubmit{
   972  				"org/repo": {
   973  					{
   974  						JobBase: config.JobBase{
   975  							Name: "job",
   976  						},
   977  						AlwaysRun: true,
   978  						Reporter: config.Reporter{
   979  							Context: "pull-job",
   980  						},
   981  						Trigger:      `(?m)^/test (?:.*? )?job(?: .*?)?$`,
   982  						RerunCommand: `/test job`,
   983  					},
   984  					{
   985  						JobBase: config.JobBase{
   986  							Name: "jib",
   987  						},
   988  						AlwaysRun: true,
   989  						Reporter: config.Reporter{
   990  							Context: "pull-jib",
   991  						},
   992  						Trigger:      `(?m)^/test (?:.*? )?jib(?: .*?)?$`,
   993  						RerunCommand: `/test jib`,
   994  					},
   995  				},
   996  			},
   997  			AddedComment: helpComment + "Use `/test all` to run all jobs.",
   998  		},
   999  		{
  1000  			name:   `help command "/test ?" uses unique RerunCommand field of presubmits`,
  1001  			Author: "trusted-member",
  1002  			Body:   "/test ?",
  1003  			State:  "open",
  1004  			IsPR:   true,
  1005  			Presubmits: map[string][]config.Presubmit{
  1006  				"org/repo": {
  1007  					{
  1008  						JobBase: config.JobBase{
  1009  							Name: "jub",
  1010  						},
  1011  						AlwaysRun: true,
  1012  						Reporter: config.Reporter{
  1013  							Context: "pull-jub",
  1014  						},
  1015  						Trigger:      `/rerun_command`,
  1016  						RerunCommand: `/rerun_command`,
  1017  					},
  1018  					{
  1019  						JobBase: config.JobBase{
  1020  							Name: "jib",
  1021  						},
  1022  						AlwaysRun: true,
  1023  						Reporter: config.Reporter{
  1024  							Context: "pull-jib",
  1025  						},
  1026  						Trigger:      `/command_foo`,
  1027  						RerunCommand: `/command_foo`,
  1028  					},
  1029  					{
  1030  						JobBase: config.JobBase{
  1031  							Name: "jab",
  1032  						},
  1033  						Reporter: config.Reporter{
  1034  							Context: "pull-jab",
  1035  						},
  1036  						Trigger:      `/rerun_command`,
  1037  						RerunCommand: `/rerun_command`,
  1038  					},
  1039  				},
  1040  			},
  1041  			AddedComment: "@trusted-member: The following commands are available to trigger required jobs:\n" +
  1042  				"* `/command_foo`\n* `/rerun_command`\n\n" +
  1043  				"Use `/test all` to run all jobs.",
  1044  		},
  1045  		{
  1046  			name:         "/test with no target results in a help message",
  1047  			Author:       "trusted-member",
  1048  			Body:         "/test",
  1049  			State:        "open",
  1050  			IsPR:         true,
  1051  			AddedComment: pjutil.TestWithoutTargetNote + helpComment + helpTestAllWithJobsComment,
  1052  		},
  1053  		{
  1054  			name:         "/test with no target but ? in the next line results in an invalid test command message",
  1055  			Author:       "trusted-member",
  1056  			Body:         "/test \r\n?",
  1057  			State:        "open",
  1058  			IsPR:         true,
  1059  			AddedComment: pjutil.TestWithoutTargetNote + helpComment + helpTestAllWithJobsComment,
  1060  		},
  1061  		{
  1062  			name:         "/retest with trailing words results in a help message",
  1063  			Author:       "trusted-member",
  1064  			Body:         "/retest FOO",
  1065  			State:        "open",
  1066  			IsPR:         true,
  1067  			AddedComment: pjutil.RetestWithTargetNote + helpComment + helpTestAllWithJobsComment,
  1068  		},
  1069  		{
  1070  			name:          "/retest without target but with lines following it, is valid",
  1071  			Author:        "trusted-member",
  1072  			Body:          "/retest \r\n/other-command",
  1073  			State:         "open",
  1074  			IsPR:          true,
  1075  			ShouldBuild:   true,
  1076  			StartsExactly: "pull-jib",
  1077  		},
  1078  		{
  1079  			name:         "/test with unknown target results in a help message",
  1080  			Author:       "trusted-member",
  1081  			Body:         "/test FOO",
  1082  			State:        "open",
  1083  			IsPR:         true,
  1084  			AddedComment: pjutil.TargetNotFoundNote + helpComment + helpTestAllWithJobsComment,
  1085  		},
  1086  		{
  1087  			name:   "help comment should list only eligible jobs under '/test all'",
  1088  			Author: "trusted-member",
  1089  			Body:   "/test ?",
  1090  			State:  "open",
  1091  			IsPR:   true,
  1092  			Presubmits: map[string][]config.Presubmit{
  1093  				"org/repo": {
  1094  					{
  1095  						JobBase: config.JobBase{
  1096  							Name: "job",
  1097  						},
  1098  						AlwaysRun: true,
  1099  						Reporter: config.Reporter{
  1100  							Context: "pull-job",
  1101  						},
  1102  						Trigger:      `(?m)^/test job$`,
  1103  						RerunCommand: `/test job`,
  1104  					},
  1105  					{
  1106  						JobBase: config.JobBase{
  1107  							Name: "jib",
  1108  						},
  1109  						Reporter: config.Reporter{
  1110  							Context: "pull-jib",
  1111  						},
  1112  						Trigger:      `(?m)^/test (?:.*? )?jib(?: .*?)?$`,
  1113  						RerunCommand: `/test jib`,
  1114  					},
  1115  				},
  1116  			},
  1117  			AddedComment: helpComment + helpTestAllWithJobsComment,
  1118  		},
  1119  		{
  1120  			name:   "when no jobs can be run with /test all, respond accordingly",
  1121  			Author: "trusted-member",
  1122  			Body:   "/test all",
  1123  			State:  "open",
  1124  			IsPR:   true,
  1125  			Presubmits: map[string][]config.Presubmit{
  1126  				"org/repo": {
  1127  					{
  1128  						JobBase: config.JobBase{
  1129  							Name: "job",
  1130  						},
  1131  						AlwaysRun: false,
  1132  						Reporter: config.Reporter{
  1133  							Context: "pull-job",
  1134  						},
  1135  						Trigger:      `(?m)^/test job$`,
  1136  						RerunCommand: `/test job`,
  1137  					},
  1138  					{
  1139  						JobBase: config.JobBase{
  1140  							Name: "jib",
  1141  						},
  1142  						AlwaysRun: false,
  1143  						Reporter: config.Reporter{
  1144  							Context: "pull-jib",
  1145  						},
  1146  						Trigger:      `(?m)^/test jib$`,
  1147  						RerunCommand: `/test jib`,
  1148  					},
  1149  				},
  1150  			},
  1151  			AddedComment: pjutil.ThereAreNoTestAllJobsNote + helpComment,
  1152  		},
  1153  		{
  1154  			name:   "available presubmits should not list those excluded by branch",
  1155  			Author: "trusted-member",
  1156  			Body:   "/test ?",
  1157  			State:  "open",
  1158  			IsPR:   true,
  1159  
  1160  			Presubmits: map[string][]config.Presubmit{
  1161  				"org/repo": {
  1162  					{
  1163  						JobBase: config.JobBase{
  1164  							Name: "job-excluded-by-brancher",
  1165  						},
  1166  						Brancher: config.Brancher{
  1167  							SkipBranches: []string{"master"},
  1168  						},
  1169  						AlwaysRun: true,
  1170  						Reporter: config.Reporter{
  1171  							Context: "pull-job-excluded-by-brancher",
  1172  						},
  1173  						Trigger:      `(?m)^/test job-excluded$`,
  1174  						RerunCommand: `/test job-excluded`,
  1175  					},
  1176  					{
  1177  						JobBase: config.JobBase{
  1178  							Name: "job",
  1179  						},
  1180  						AlwaysRun: true,
  1181  						Reporter: config.Reporter{
  1182  							Context: "pull-job",
  1183  						},
  1184  						Trigger:      `(?m)^/test job$`,
  1185  						RerunCommand: `/test job`,
  1186  					},
  1187  					{
  1188  						JobBase: config.JobBase{
  1189  							Name: "jib",
  1190  						},
  1191  						Reporter: config.Reporter{
  1192  							Context: "pull-jib",
  1193  						},
  1194  						Trigger:      `(?m)^/test (?:.*? )?jib(?: .*?)?$`,
  1195  						RerunCommand: `/test jib`,
  1196  					},
  1197  				},
  1198  			},
  1199  			AddedComment: helpComment + helpTestAllWithJobsComment,
  1200  		},
  1201  		{
  1202  			name:   `help command "/test ?" differs between optional and required jobs`,
  1203  			Author: "trusted-member",
  1204  			Body:   "/test ?",
  1205  			State:  "open",
  1206  			IsPR:   true,
  1207  			Presubmits: map[string][]config.Presubmit{
  1208  				"org/repo": {
  1209  					{
  1210  						JobBase: config.JobBase{
  1211  							Name: "job",
  1212  						},
  1213  						AlwaysRun: true,
  1214  						Reporter: config.Reporter{
  1215  							Context: "pull-job",
  1216  						},
  1217  						Trigger:      `(?m)^/test (?:.*? )?job(?: .*?)?$`,
  1218  						RerunCommand: `/test job`,
  1219  					},
  1220  					{
  1221  						JobBase: config.JobBase{
  1222  							Name: "jib",
  1223  						},
  1224  						AlwaysRun: true,
  1225  						Reporter: config.Reporter{
  1226  							Context: "pull-jib",
  1227  						},
  1228  						Trigger:      `(?m)^/test (?:.*? )?jib(?: .*?)?$`,
  1229  						RerunCommand: `/test jib`,
  1230  					},
  1231  					{
  1232  						JobBase: config.JobBase{
  1233  							Name: "jub",
  1234  						},
  1235  						AlwaysRun: true,
  1236  						Optional:  true,
  1237  						Reporter: config.Reporter{
  1238  							Context: "pull-jub",
  1239  						},
  1240  						Trigger:      `(?m)^/test (?:.*? )?jub(?: .*?)?$`,
  1241  						RerunCommand: `/test jub`,
  1242  					},
  1243  				},
  1244  			},
  1245  			AddedComment: helpComment +
  1246  				"The following commands are available to trigger optional jobs:\n* `/test jub`\n\n" +
  1247  				"Use `/test all` to run all jobs.",
  1248  		},
  1249  	}
  1250  	for _, tc := range testcases {
  1251  		t.Run(tc.name, func(t *testing.T) {
  1252  			if tc.Branch == "" {
  1253  				tc.Branch = "master"
  1254  			}
  1255  			g := fakegithub.NewFakeClient()
  1256  			g.IssueComments = map[int][]github.IssueComment{}
  1257  			g.OrgMembers = map[string][]string{"org": {"trusted-member"}}
  1258  			g.PullRequests = map[int]*github.PullRequest{
  1259  				0: {
  1260  					User:   github.User{Login: tc.PRAuthor},
  1261  					Number: 0,
  1262  					Head: github.PullRequestBranch{
  1263  						SHA: "cafe",
  1264  					},
  1265  					Base: github.PullRequestBranch{
  1266  						Ref: tc.Branch,
  1267  						Repo: github.Repo{
  1268  							Owner: github.User{Login: "org"},
  1269  							Name:  "repo",
  1270  						},
  1271  					},
  1272  				},
  1273  			}
  1274  			g.IssueLabelsExisting = tc.IssueLabels
  1275  			g.PullRequestChanges = map[int][]github.PullRequestChange{0: {{Filename: "CHANGED"}}}
  1276  			g.CombinedStatuses = map[string]*github.CombinedStatus{
  1277  				"cafe": {
  1278  					Statuses: []github.Status{
  1279  						{State: github.StatusPending, Context: "pull-job"},
  1280  						{State: github.StatusFailure, Context: "pull-jib"},
  1281  						{State: github.StatusSuccess, Context: "pull-jub"},
  1282  					},
  1283  				},
  1284  			}
  1285  			g.Collaborators = []string{"k8s-ci-robot"}
  1286  			fakeConfig := &config.Config{ProwConfig: config.ProwConfig{ProwJobNamespace: "prowjobs"}}
  1287  			fakeProwJobClient := fake.NewSimpleClientset()
  1288  			c := Client{
  1289  				GitHubClient:  g,
  1290  				ProwJobClient: fakeProwJobClient.ProwV1().ProwJobs(fakeConfig.ProwJobNamespace),
  1291  				Config:        fakeConfig,
  1292  				Logger:        logrus.WithField("plugin", PluginName),
  1293  				GitClient:     nil,
  1294  			}
  1295  			presubmits := tc.Presubmits
  1296  			if presubmits == nil {
  1297  				presubmits = map[string][]config.Presubmit{
  1298  					"org/repo": {
  1299  						{
  1300  							JobBase: config.JobBase{
  1301  								Name: "job",
  1302  							},
  1303  							AlwaysRun: true,
  1304  							Reporter: config.Reporter{
  1305  								Context: "pull-job",
  1306  							},
  1307  							Trigger:      `(?m)^/test (?:.*? )?job(?: .*?)?$`,
  1308  							RerunCommand: `/test job`,
  1309  							Brancher:     config.Brancher{Branches: []string{"master"}},
  1310  						},
  1311  						{
  1312  							JobBase: config.JobBase{
  1313  								Name: "jib",
  1314  							},
  1315  							AlwaysRun: false,
  1316  							Reporter: config.Reporter{
  1317  								Context: "pull-jib",
  1318  							},
  1319  							Trigger:      `(?m)^/test (?:.*? )?jib(?: .*?)?$`,
  1320  							RerunCommand: `/test jib`,
  1321  						},
  1322  					},
  1323  				}
  1324  			}
  1325  			if err := c.Config.SetPresubmits(presubmits); err != nil {
  1326  				t.Fatalf("%s: failed to set presubmits: %v", tc.name, err)
  1327  			}
  1328  
  1329  			event := github.GenericCommentEvent{
  1330  				Action: github.GenericCommentActionCreated,
  1331  				Repo: github.Repo{
  1332  					Owner:    github.User{Login: "org"},
  1333  					Name:     "repo",
  1334  					FullName: "org/repo",
  1335  				},
  1336  				Body:        tc.Body,
  1337  				User:        github.User{Login: tc.Author},
  1338  				IssueAuthor: github.User{Login: tc.PRAuthor},
  1339  				IssueState:  tc.State,
  1340  				IsPR:        tc.IsPR,
  1341  			}
  1342  
  1343  			trigger := plugins.Trigger{
  1344  				IgnoreOkToTest: tc.IgnoreOkToTest,
  1345  			}
  1346  			trigger.SetDefaults()
  1347  
  1348  			log.Printf("running case %s", tc.name)
  1349  			// In some cases handleGenericComment can be called twice for the same event.
  1350  			// For instance on Issue/PR creation and modification.
  1351  			// Let's call it twice to ensure idempotency.
  1352  			if err := handleGenericComment(c, trigger, event); err != nil {
  1353  				t.Fatalf("%s: didn't expect error: %s", tc.name, err)
  1354  			}
  1355  			validate(t, fakeProwJobClient.Fake.Actions(), g, tc)
  1356  			if err := handleGenericComment(c, trigger, event); err != nil {
  1357  				t.Fatalf("%s: didn't expect error: %s", tc.name, err)
  1358  			}
  1359  			validate(t, fakeProwJobClient.Fake.Actions(), g, tc)
  1360  		})
  1361  	}
  1362  }
  1363  
  1364  func validate(t *testing.T, actions []clienttesting.Action, g *fakegithub.FakeClient, tc testcase) {
  1365  	startedContexts := sets.New[string]()
  1366  	for _, action := range actions {
  1367  		switch action := action.(type) {
  1368  		case clienttesting.CreateActionImpl:
  1369  			if prowJob, ok := action.Object.(*prowapi.ProwJob); ok {
  1370  				startedContexts.Insert(prowJob.Spec.Context)
  1371  			}
  1372  		}
  1373  	}
  1374  	if len(startedContexts) > 0 && !tc.ShouldBuild {
  1375  		t.Errorf("Built but should not have: %+v", tc)
  1376  	} else if len(startedContexts) == 0 && tc.ShouldBuild {
  1377  		t.Errorf("Not built but should have: %+v", tc)
  1378  	}
  1379  	if tc.StartsExactly != "" && (startedContexts.Len() != 1 || !startedContexts.Has(tc.StartsExactly)) {
  1380  		t.Errorf("didn't build expected context %v, instead built %v", tc.StartsExactly, startedContexts)
  1381  	}
  1382  	if !reflect.DeepEqual(g.IssueLabelsAdded, tc.AddedLabels) {
  1383  		t.Errorf("expected %q to be added, got %q", tc.AddedLabels, g.IssueLabelsAdded)
  1384  	}
  1385  	if !reflect.DeepEqual(g.IssueLabelsRemoved, tc.RemovedLabels) {
  1386  		t.Errorf("expected %q to be removed, got %q", tc.RemovedLabels, g.IssueLabelsRemoved)
  1387  	}
  1388  	if tc.AddedComment != "" {
  1389  		if len(g.IssueComments[0]) == 0 {
  1390  			t.Errorf("expected the comments to contain %s, got no comments", tc.AddedComment)
  1391  		}
  1392  		for _, c := range g.IssueComments[0] {
  1393  			if !strings.Contains(c.Body, tc.AddedComment) {
  1394  				t.Errorf("expected the comment to contain %s, got %s", tc.AddedComment, c.Body)
  1395  			}
  1396  		}
  1397  	}
  1398  }
  1399  
  1400  func TestRetestFilter(t *testing.T) {
  1401  	var testCases = []struct {
  1402  		name           string
  1403  		failedContexts sets.Set[string]
  1404  		allContexts    sets.Set[string]
  1405  		presubmits     []config.Presubmit
  1406  		expected       [][]bool
  1407  	}{
  1408  		{
  1409  			name:           "retest filter matches jobs that produce contexts which have failed",
  1410  			failedContexts: sets.New[string]("failed"),
  1411  			allContexts:    sets.New[string]("failed", "succeeded"),
  1412  			presubmits: []config.Presubmit{
  1413  				{
  1414  					JobBase: config.JobBase{
  1415  						Name: "failed",
  1416  					},
  1417  					Reporter: config.Reporter{
  1418  						Context: "failed",
  1419  					},
  1420  				},
  1421  				{
  1422  					JobBase: config.JobBase{
  1423  						Name: "succeeded",
  1424  					},
  1425  					Reporter: config.Reporter{
  1426  						Context: "succeeded",
  1427  					},
  1428  				},
  1429  			},
  1430  			expected: [][]bool{{true, false, true}, {false, false, false}},
  1431  		},
  1432  		{
  1433  			name:           "retest filter matches jobs that would run automatically and haven't yet ",
  1434  			failedContexts: sets.New[string](),
  1435  			allContexts:    sets.New[string]("finished"),
  1436  			presubmits: []config.Presubmit{
  1437  				{
  1438  					JobBase: config.JobBase{
  1439  						Name: "finished",
  1440  					},
  1441  					Reporter: config.Reporter{
  1442  						Context: "finished",
  1443  					},
  1444  				},
  1445  				{
  1446  					JobBase: config.JobBase{
  1447  						Name: "not-yet-run",
  1448  					},
  1449  					AlwaysRun: true,
  1450  					Reporter: config.Reporter{
  1451  						Context: "not-yet-run",
  1452  					},
  1453  				},
  1454  			},
  1455  			expected: [][]bool{{false, false, false}, {true, false, false}},
  1456  		},
  1457  	}
  1458  
  1459  	for _, testCase := range testCases {
  1460  		t.Run(testCase.name, func(t *testing.T) {
  1461  			if len(testCase.presubmits) != len(testCase.expected) {
  1462  				t.Fatalf("%s: have %d presubmits but only %d expected filter outputs", testCase.name, len(testCase.presubmits), len(testCase.expected))
  1463  			}
  1464  			if err := config.SetPresubmitRegexes(testCase.presubmits); err != nil {
  1465  				t.Fatalf("%s: could not set presubmit regexes: %v", testCase.name, err)
  1466  			}
  1467  			filter := pjutil.NewRetestFilter(testCase.failedContexts, testCase.allContexts)
  1468  			for i, presubmit := range testCase.presubmits {
  1469  				actualFiltered, actualForced, actualDefault := filter.ShouldRun(presubmit)
  1470  				expectedFiltered, expectedForced, expectedDefault := testCase.expected[i][0], testCase.expected[i][1], testCase.expected[i][2]
  1471  				if actualFiltered != expectedFiltered {
  1472  					t.Errorf("%s: filter did not evaluate correctly, expected %v but got %v for %v", testCase.name, expectedFiltered, actualFiltered, presubmit.Name)
  1473  				}
  1474  				if actualForced != expectedForced {
  1475  					t.Errorf("%s: filter did not determine forced correctly, expected %v but got %v for %v", testCase.name, expectedForced, actualForced, presubmit.Name)
  1476  				}
  1477  				if actualDefault != expectedDefault {
  1478  					t.Errorf("%s: filter did not determine default correctly, expected %v but got %v for %v", testCase.name, expectedDefault, actualDefault, presubmit.Name)
  1479  				}
  1480  			}
  1481  		})
  1482  	}
  1483  }