github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/pkg/github/report/report_test.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package report
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"strconv"
    23  	"strings"
    24  	"testing"
    25  	"text/template"
    26  
    27  	"github.com/google/go-cmp/cmp"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	prowapi "sigs.k8s.io/prow/pkg/apis/prowjobs/v1"
    30  	"sigs.k8s.io/prow/pkg/config"
    31  	"sigs.k8s.io/prow/pkg/github"
    32  	"sigs.k8s.io/prow/pkg/kube"
    33  )
    34  
    35  func TestParseIssueComment(t *testing.T) {
    36  	var testcases = []struct {
    37  		name            string
    38  		context         string
    39  		state           string
    40  		ics             []github.IssueComment
    41  		expectedDeletes []int
    42  		expectedEntries []string
    43  		expectedUpdate  int
    44  		isOptional      bool
    45  	}{
    46  		{
    47  			name:            "should create a new comment",
    48  			context:         "bla test",
    49  			state:           github.StatusFailure,
    50  			expectedEntries: []string{createReportEntry("bla test", true)},
    51  		},
    52  		{
    53  			name:            "should create a new optional comment",
    54  			context:         "bla test",
    55  			state:           github.StatusFailure,
    56  			isOptional:      true,
    57  			expectedEntries: []string{createReportEntry("bla test", false)},
    58  		},
    59  		{
    60  			name:    "should not delete an up-to-date comment",
    61  			context: "bla test",
    62  			state:   github.StatusSuccess,
    63  			ics: []github.IssueComment{
    64  				{
    65  					User: github.User{Login: "k8s-ci-robot"},
    66  					Body: "--- | --- | ---\nfoo test | something | or other\n\n",
    67  				},
    68  			},
    69  		},
    70  		{
    71  			name:    "should delete when all tests pass",
    72  			context: "bla test",
    73  			state:   github.StatusSuccess,
    74  			ics: []github.IssueComment{
    75  				{
    76  					User: github.User{Login: "k8s-ci-robot"},
    77  					Body: "--- | --- | ---\nbla test | something | or other\n\n" + commentTag,
    78  					ID:   123,
    79  				},
    80  			},
    81  			expectedDeletes: []int{123},
    82  			expectedEntries: []string{},
    83  		},
    84  		{
    85  			name:    "should delete a passing test with \\r",
    86  			context: "bla test",
    87  			state:   github.StatusSuccess,
    88  			ics: []github.IssueComment{
    89  				{
    90  					User: github.User{Login: "k8s-ci-robot"},
    91  					Body: "--- | --- | ---\r\nbla test | something | or other\r\n\r\n" + commentTag,
    92  					ID:   123,
    93  				},
    94  			},
    95  			expectedDeletes: []int{123},
    96  			expectedEntries: []string{},
    97  		},
    98  
    99  		{
   100  			name:    "should update a failed test",
   101  			context: "bla test",
   102  			state:   github.StatusFailure,
   103  			ics: []github.IssueComment{
   104  				{
   105  					User: github.User{Login: "k8s-ci-robot"},
   106  					Body: "--- | --- | ---\nbla test | something | or other\n\n" + commentTag,
   107  					ID:   123,
   108  				},
   109  			},
   110  			expectedDeletes: []int{123},
   111  			expectedEntries: []string{"bla test"},
   112  		},
   113  		{
   114  			name:    "should preserve old results when updating",
   115  			context: "bla test",
   116  			state:   github.StatusFailure,
   117  			ics: []github.IssueComment{
   118  				{
   119  					User: github.User{Login: "k8s-ci-robot"},
   120  					Body: "--- | --- | ---\nbla test | something | or other\nfoo test | wow | aye\n\n" + commentTag,
   121  					ID:   123,
   122  				},
   123  			},
   124  			expectedDeletes: []int{123},
   125  			expectedEntries: []string{"bla test", "foo test"},
   126  		},
   127  		{
   128  			name:    "should merge duplicates",
   129  			context: "bla test",
   130  			state:   github.StatusFailure,
   131  			ics: []github.IssueComment{
   132  				{
   133  					User: github.User{Login: "k8s-ci-robot"},
   134  					Body: "--- | --- | ---\nbla test | something | or other\nfoo test | wow such\n\n" + commentTag,
   135  					ID:   123,
   136  				},
   137  				{
   138  					User: github.User{Login: "k8s-ci-robot"},
   139  					Body: "--- | --- | ---\nfoo test | beep | boop\n\n" + commentTag,
   140  					ID:   124,
   141  				},
   142  			},
   143  			expectedDeletes: []int{123, 124},
   144  			expectedEntries: []string{"bla test", "foo test"},
   145  		},
   146  		{
   147  			name:    "should update an old comment when a test passes",
   148  			context: "bla test",
   149  			state:   github.StatusSuccess,
   150  			ics: []github.IssueComment{
   151  				{
   152  					User: github.User{Login: "k8s-ci-robot"},
   153  					Body: "--- | --- | ---\nbla test | something | or other\nfoo test | wow | aye\n\n" + commentTag,
   154  					ID:   123,
   155  				},
   156  			},
   157  			expectedDeletes: []int{},
   158  			expectedEntries: []string{"foo test"},
   159  			expectedUpdate:  123,
   160  		},
   161  	}
   162  	for _, tc := range testcases {
   163  		t.Run(tc.name, func(t *testing.T) {
   164  			pj := prowapi.ProwJob{
   165  				ObjectMeta: metav1.ObjectMeta{
   166  					Labels: map[string]string{
   167  						kube.IsOptionalLabel: strconv.FormatBool(tc.isOptional),
   168  					},
   169  				},
   170  				Spec: prowapi.ProwJobSpec{
   171  					Type:    prowapi.PresubmitJob,
   172  					Context: tc.context,
   173  					Refs:    &prowapi.Refs{Pulls: []prowapi.Pull{{}}},
   174  				},
   175  				Status: prowapi.ProwJobStatus{
   176  					State: prowapi.ProwJobState(tc.state),
   177  				},
   178  			}
   179  			isBot := func(candidate string) bool {
   180  				return candidate == "k8s-ci-robot"
   181  			}
   182  			deletes, entries, update := parseIssueComments([]prowapi.ProwJob{pj}, isBot, tc.ics)
   183  			if len(deletes) != len(tc.expectedDeletes) {
   184  				t.Errorf("It %q: wrong number of deletes. Got %v, expected %v", tc.name, deletes, tc.expectedDeletes)
   185  			} else {
   186  				for _, edel := range tc.expectedDeletes {
   187  					found := false
   188  					for _, del := range deletes {
   189  						if del == edel {
   190  							found = true
   191  							break
   192  						}
   193  					}
   194  					if !found {
   195  						t.Errorf("It %q: expected to find %d in %v", tc.name, edel, deletes)
   196  					}
   197  				}
   198  			}
   199  			if len(entries) != len(tc.expectedEntries) {
   200  				t.Errorf("It %q: wrong number of entries. Got %v, expected %v", tc.name, entries, tc.expectedEntries)
   201  			}
   202  			if tc.expectedUpdate != update {
   203  				t.Errorf("It %q: expected update %d, got %d", tc.name, tc.expectedUpdate, update)
   204  			}
   205  
   206  			for _, expectedEntry := range tc.expectedEntries {
   207  				found := false
   208  				for _, ent := range entries {
   209  					if strings.Contains(ent, expectedEntry) {
   210  						found = true
   211  						break
   212  					}
   213  				}
   214  				if !found {
   215  					t.Errorf("It %q: expected to find %q in %v", tc.name, expectedEntry, entries)
   216  				}
   217  			}
   218  		})
   219  	}
   220  }
   221  
   222  func createReportEntry(context string, isRequired bool) string {
   223  	return fmt.Sprintf("%s |  | [link]() | %s | ", context, strconv.FormatBool(isRequired))
   224  }
   225  
   226  type fakeGhClient struct {
   227  	status   []github.Status
   228  	comments []string
   229  }
   230  
   231  func (gh fakeGhClient) BotUserCheckerWithContext(_ context.Context) (func(string) bool, error) {
   232  	return func(candidate string) bool {
   233  		return candidate == "BotName"
   234  	}, nil
   235  }
   236  
   237  const maxLen = 140
   238  
   239  func (gh *fakeGhClient) CreateStatusWithContext(_ context.Context, org, repo, ref string, s github.Status) error {
   240  	if d := s.Description; len(d) > maxLen {
   241  		return fmt.Errorf("%s is len %d, more than max of %d chars", d, len(d), maxLen)
   242  	}
   243  	gh.status = append(gh.status, s)
   244  	return nil
   245  
   246  }
   247  func (gh fakeGhClient) ListIssueCommentsWithContext(_ context.Context, org, repo string, number int) ([]github.IssueComment, error) {
   248  	return nil, nil
   249  }
   250  func (gh *fakeGhClient) CreateCommentWithContext(_ context.Context, org, repo string, number int, comment string) error {
   251  	gh.comments = append(gh.comments, comment)
   252  	return nil
   253  }
   254  func (gh fakeGhClient) DeleteCommentWithContext(_ context.Context, org, repo string, ID int) error {
   255  	return nil
   256  }
   257  func (gh fakeGhClient) EditCommentWithContext(_ context.Context, org, repo string, ID int, comment string) error {
   258  	return nil
   259  }
   260  
   261  func shout(i int) string {
   262  	if i == 0 {
   263  		return "start"
   264  	}
   265  	return fmt.Sprintf("%s part%d", shout(i-1), i)
   266  }
   267  
   268  func TestReportStatus(t *testing.T) {
   269  	const (
   270  		defMsg = "default-message"
   271  	)
   272  	tests := []struct {
   273  		name string
   274  
   275  		state            prowapi.ProwJobState
   276  		report           bool
   277  		desc             string // override default msg
   278  		pjType           prowapi.ProwJobType
   279  		expectedStatuses []string
   280  		expectedDesc     string
   281  	}{
   282  		{
   283  			name: "Successful prowjob with report true should set status",
   284  
   285  			state:            prowapi.SuccessState,
   286  			pjType:           prowapi.PresubmitJob,
   287  			report:           true,
   288  			expectedStatuses: []string{"success"},
   289  		},
   290  		{
   291  			name: "Successful prowjob with report false should not set status",
   292  
   293  			state:            prowapi.SuccessState,
   294  			pjType:           prowapi.PresubmitJob,
   295  			report:           false,
   296  			expectedStatuses: []string{},
   297  		},
   298  		{
   299  			name: "Pending prowjob with report true should set status",
   300  
   301  			state:            prowapi.PendingState,
   302  			report:           true,
   303  			pjType:           prowapi.PresubmitJob,
   304  			expectedStatuses: []string{"pending"},
   305  		},
   306  		{
   307  			name: "Aborted presubmit job with report true should set failure status",
   308  
   309  			state:            prowapi.AbortedState,
   310  			report:           true,
   311  			pjType:           prowapi.PresubmitJob,
   312  			expectedStatuses: []string{"failure"},
   313  		},
   314  		{
   315  			name: "Triggered presubmit job with report true should set pending status",
   316  
   317  			state:            prowapi.TriggeredState,
   318  			report:           true,
   319  			pjType:           prowapi.PresubmitJob,
   320  			expectedStatuses: []string{"pending"},
   321  		},
   322  		{
   323  			name: "really long description is truncated",
   324  
   325  			state:            prowapi.TriggeredState,
   326  			report:           true,
   327  			expectedStatuses: []string{"pending"},
   328  			desc:             shout(maxLen), // resulting string will exceed maxLen
   329  			expectedDesc:     config.ContextDescriptionWithBaseSha(shout(maxLen), ""),
   330  		},
   331  		{
   332  			name: "Successful postsubmit job with report true should set success status",
   333  
   334  			state:  prowapi.SuccessState,
   335  			report: true,
   336  			pjType: prowapi.PostsubmitJob,
   337  
   338  			expectedStatuses: []string{"success"},
   339  		},
   340  	}
   341  
   342  	for _, tc := range tests {
   343  		t.Run(tc.name, func(t *testing.T) {
   344  			// Setup
   345  			ghc := &fakeGhClient{}
   346  
   347  			if tc.desc == "" {
   348  				tc.desc = defMsg
   349  			}
   350  			if tc.expectedDesc == "" {
   351  				tc.expectedDesc = defMsg
   352  			}
   353  			pj := prowapi.ProwJob{
   354  				Status: prowapi.ProwJobStatus{
   355  					State:       tc.state,
   356  					Description: tc.desc,
   357  					URL:         "http://mytest.com",
   358  				},
   359  				Spec: prowapi.ProwJobSpec{
   360  					Job:     "job-name",
   361  					Type:    tc.pjType,
   362  					Context: "parent",
   363  					Report:  tc.report,
   364  					Refs: &prowapi.Refs{
   365  						Org:  "k8s",
   366  						Repo: "test-infra",
   367  						Pulls: []prowapi.Pull{{
   368  							Author:  "me",
   369  							Number:  1,
   370  							SHA:     "abcdef",
   371  							HeadRef: "fixes-123",
   372  						}},
   373  					},
   374  				},
   375  			}
   376  			// Run
   377  			if err := reportStatus(context.Background(), ghc, pj); err != nil {
   378  				t.Error(err)
   379  			}
   380  			// Check
   381  			if len(ghc.status) != len(tc.expectedStatuses) {
   382  				t.Errorf("expected %d status(es), found %d", len(tc.expectedStatuses), len(ghc.status))
   383  				return
   384  			}
   385  			for i, status := range ghc.status {
   386  				if status.State != tc.expectedStatuses[i] {
   387  					t.Errorf("unexpected status: %s, expected: %s", status.State, tc.expectedStatuses[i])
   388  				}
   389  				if i == 0 && status.Description != tc.expectedDesc {
   390  					t.Errorf("description %d %s != expected %s", i, status.Description, tc.expectedDesc)
   391  				}
   392  			}
   393  		})
   394  	}
   395  }
   396  
   397  func TestShouldReport(t *testing.T) {
   398  	var testcases = []struct {
   399  		name       string
   400  		pj         prowapi.ProwJob
   401  		validTypes []prowapi.ProwJobType
   402  		report     bool
   403  	}{
   404  		{
   405  			name: "should not report skip report job",
   406  			pj: prowapi.ProwJob{
   407  				Spec: prowapi.ProwJobSpec{
   408  					Type:   prowapi.PresubmitJob,
   409  					Report: false,
   410  				},
   411  			},
   412  			validTypes: []prowapi.ProwJobType{prowapi.PresubmitJob},
   413  		},
   414  		{
   415  			name: "should report presubmit job",
   416  			pj: prowapi.ProwJob{
   417  				Spec: prowapi.ProwJobSpec{
   418  					Type:   prowapi.PresubmitJob,
   419  					Report: true,
   420  				},
   421  			},
   422  			validTypes: []prowapi.ProwJobType{prowapi.PresubmitJob},
   423  			report:     true,
   424  		},
   425  		{
   426  			name: "should not report postsubmit job",
   427  			pj: prowapi.ProwJob{
   428  				Spec: prowapi.ProwJobSpec{
   429  					Type:   prowapi.PostsubmitJob,
   430  					Report: true,
   431  				},
   432  			},
   433  			validTypes: []prowapi.ProwJobType{prowapi.PresubmitJob},
   434  		},
   435  		{
   436  			name: "should report postsubmit job if told to",
   437  			pj: prowapi.ProwJob{
   438  				Spec: prowapi.ProwJobSpec{
   439  					Type:   prowapi.PostsubmitJob,
   440  					Report: true,
   441  				},
   442  			},
   443  			validTypes: []prowapi.ProwJobType{prowapi.PresubmitJob, prowapi.PostsubmitJob},
   444  			report:     true,
   445  		},
   446  	}
   447  
   448  	for _, tc := range testcases {
   449  		r := ShouldReport(tc.pj, tc.validTypes)
   450  
   451  		if r != tc.report {
   452  			t.Errorf("Unexpected result from test: %s.\nExpected: %v\nGot: %v",
   453  				tc.name, tc.report, r)
   454  		}
   455  	}
   456  }
   457  
   458  func TestCreateComment(t *testing.T) {
   459  	tests := []struct {
   460  		name     string
   461  		template *template.Template
   462  		pjs      []prowapi.ProwJob
   463  		entries  []string
   464  		want     string
   465  		wantErr  bool
   466  	}{
   467  		{
   468  			name:     "single-job-single-failure",
   469  			template: mustParseTemplate(t, ""),
   470  			pjs: []prowapi.ProwJob{
   471  				{
   472  					Spec: prowapi.ProwJobSpec{
   473  						Refs: &prowapi.Refs{
   474  							Pulls: []prowapi.Pull{
   475  								{
   476  									Author: "chaodaig",
   477  								},
   478  							},
   479  						},
   480  					},
   481  				},
   482  			},
   483  			entries: []string{
   484  				"aaa | bbb | ccc | ddd | eee",
   485  			},
   486  			want: `@chaodaig: The following test **failed**, say ` + "`/retest`" + ` to rerun all failed tests or ` + "`/retest-required`" + ` to rerun all mandatory failed tests:
   487  
   488  Test name | Commit | Details | Required | Rerun command
   489  --- | --- | --- | --- | ---
   490  aaa | bbb | ccc | ddd | eee
   491  
   492  
   493  
   494  <details>
   495  
   496  Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
   497  </details>
   498  <!-- test report -->`,
   499  		},
   500  		{
   501  			name:     "single-job-multi;e-failure",
   502  			template: mustParseTemplate(t, ""),
   503  			pjs: []prowapi.ProwJob{
   504  				{
   505  					Spec: prowapi.ProwJobSpec{
   506  						Refs: &prowapi.Refs{
   507  							Pulls: []prowapi.Pull{
   508  								{
   509  									Author: "chaodaig",
   510  								},
   511  							},
   512  						},
   513  					},
   514  				},
   515  			},
   516  			entries: []string{
   517  				"aaa | bbb | ccc | ddd | eee",
   518  				"fff | ggg | hhh | iii | jjj",
   519  			},
   520  			want: `@chaodaig: The following tests **failed**, say ` + "`/retest`" + ` to rerun all failed tests or ` + "`/retest-required`" + ` to rerun all mandatory failed tests:
   521  
   522  Test name | Commit | Details | Required | Rerun command
   523  --- | --- | --- | --- | ---
   524  aaa | bbb | ccc | ddd | eee
   525  fff | ggg | hhh | iii | jjj
   526  
   527  
   528  
   529  <details>
   530  
   531  Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
   532  </details>
   533  <!-- test report -->`,
   534  		},
   535  		{
   536  			name:     "multiple-job-only-use-first-one",
   537  			template: mustParseTemplate(t, "{{.Spec.Job}}"),
   538  			pjs: []prowapi.ProwJob{
   539  				{
   540  					Spec: prowapi.ProwJobSpec{
   541  						Job: "job-a",
   542  						Refs: &prowapi.Refs{
   543  							Pulls: []prowapi.Pull{
   544  								{
   545  									Author: "chaodaig",
   546  								},
   547  							},
   548  						},
   549  					},
   550  				},
   551  				{
   552  					Spec: prowapi.ProwJobSpec{
   553  						Job: "job-b",
   554  						Refs: &prowapi.Refs{
   555  							Pulls: []prowapi.Pull{
   556  								{
   557  									Author: "chaodaig",
   558  								},
   559  							},
   560  						},
   561  					},
   562  				},
   563  			},
   564  			entries: []string{
   565  				"aaa | bbb | ccc | ddd | eee",
   566  			},
   567  			want: `@chaodaig: The following test **failed**, say ` + "`/retest`" + ` to rerun all failed tests or ` + "`/retest-required`" + ` to rerun all mandatory failed tests:
   568  
   569  Test name | Commit | Details | Required | Rerun command
   570  --- | --- | --- | --- | ---
   571  aaa | bbb | ccc | ddd | eee
   572  
   573  job-a
   574  
   575  <details>
   576  
   577  Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
   578  </details>
   579  <!-- test report -->`,
   580  		},
   581  		{
   582  			name: "multiple-job-all-passed",
   583  			pjs: []prowapi.ProwJob{
   584  				{
   585  					Spec: prowapi.ProwJobSpec{
   586  						Job: "job-a",
   587  						Refs: &prowapi.Refs{
   588  							Pulls: []prowapi.Pull{
   589  								{
   590  									Author: "chaodaig",
   591  								},
   592  							},
   593  						},
   594  					},
   595  				},
   596  				{
   597  					Spec: prowapi.ProwJobSpec{
   598  						Job: "job-b",
   599  						Refs: &prowapi.Refs{
   600  							Pulls: []prowapi.Pull{
   601  								{
   602  									Author: "chaodaig",
   603  								},
   604  							},
   605  						},
   606  					},
   607  				},
   608  			},
   609  			entries: []string{},
   610  			want: `@chaodaig: all tests **passed!**
   611  
   612  
   613  <details>
   614  
   615  Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
   616  </details>
   617  <!-- test report -->`,
   618  		},
   619  	}
   620  
   621  	for _, tc := range tests {
   622  		t.Run(tc.name, func(t *testing.T) {
   623  			gotComment, gotErr := createComment(tc.template, tc.pjs, tc.entries)
   624  			if diff := cmp.Diff(gotComment, tc.want); diff != "" {
   625  				t.Fatalf("comment mismatch:\n%s", diff)
   626  			}
   627  			if (gotErr != nil && !tc.wantErr) || (gotErr == nil && tc.wantErr) {
   628  				t.Fatalf("error mismatch. got: %v, want: %v", gotErr, tc.wantErr)
   629  			}
   630  		})
   631  	}
   632  }
   633  
   634  func mustParseTemplate(t *testing.T, s string) *template.Template {
   635  	tmpl, err := template.New("test").Parse(s)
   636  	if err != nil {
   637  		t.Fatal(err)
   638  	}
   639  	return tmpl
   640  }
   641  
   642  func TestReportComment(t *testing.T) {
   643  	t.Parallel()
   644  	testCases := []struct {
   645  		name            string
   646  		pjs             []prowapi.ProwJob
   647  		reporterConfig  config.GitHubReporter
   648  		mustCreate      bool
   649  		expectedComment bool
   650  	}{
   651  		{
   652  			name: "failed pj",
   653  			pjs: []prowapi.ProwJob{{
   654  				Spec: prowapi.ProwJobSpec{
   655  					Type:   prowapi.PresubmitJob,
   656  					Report: true,
   657  					Refs: &prowapi.Refs{
   658  						Pulls: []prowapi.Pull{{}},
   659  					},
   660  				},
   661  				Status: prowapi.ProwJobStatus{
   662  					State:          prowapi.FailureState,
   663  					CompletionTime: &metav1.Time{},
   664  				}},
   665  			},
   666  			reporterConfig: config.GitHubReporter{
   667  				JobTypesToReport: []prowapi.ProwJobType{prowapi.PresubmitJob},
   668  			},
   669  			expectedComment: true,
   670  		},
   671  		{
   672  			name: "succeeded pj when mustCreate is true",
   673  			pjs: []prowapi.ProwJob{{
   674  				Spec: prowapi.ProwJobSpec{
   675  					Type:   prowapi.PresubmitJob,
   676  					Report: true,
   677  					Refs: &prowapi.Refs{
   678  						Pulls: []prowapi.Pull{{}},
   679  					},
   680  				},
   681  				Status: prowapi.ProwJobStatus{
   682  					State:          prowapi.SuccessState,
   683  					CompletionTime: &metav1.Time{},
   684  				}},
   685  			},
   686  			reporterConfig: config.GitHubReporter{
   687  				JobTypesToReport: []prowapi.ProwJobType{prowapi.PresubmitJob},
   688  			},
   689  			mustCreate:      true,
   690  			expectedComment: true,
   691  		},
   692  		{
   693  			name: "aborted pj when mustCreate is true",
   694  			pjs: []prowapi.ProwJob{{
   695  				Spec: prowapi.ProwJobSpec{
   696  					Type:   prowapi.PresubmitJob,
   697  					Report: true,
   698  					Refs: &prowapi.Refs{
   699  						Pulls: []prowapi.Pull{{}},
   700  					},
   701  				},
   702  				Status: prowapi.ProwJobStatus{
   703  					State:          prowapi.AbortedState,
   704  					CompletionTime: &metav1.Time{},
   705  				}},
   706  			},
   707  			reporterConfig: config.GitHubReporter{
   708  				JobTypesToReport: []prowapi.ProwJobType{prowapi.PresubmitJob},
   709  			},
   710  			mustCreate: true,
   711  		},
   712  	}
   713  	for _, tc := range testCases {
   714  		t.Run(tc.name, func(t *testing.T) {
   715  			fghc := &fakeGhClient{}
   716  			err := ReportComment(context.Background(), fghc, nil, tc.pjs, tc.reporterConfig, tc.mustCreate)
   717  			if err != nil {
   718  				t.Fatalf("unexpected error: %v", err)
   719  			}
   720  
   721  			if diff := cmp.Diff(tc.expectedComment, len(fghc.comments) == 1); diff != "" {
   722  				t.Fatalf("expectedComment didn't match result, diff: %s", diff)
   723  			}
   724  		})
   725  	}
   726  }