sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/crier/reporters/gerrit/reporter_test.go (about)

     1  /*
     2  Copyright 2018 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 gerrit
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"reflect"
    24  	"regexp"
    25  	"strconv"
    26  	"strings"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/andygrunwald/go-gerrit"
    31  	"github.com/sirupsen/logrus"
    32  	"golang.org/x/sync/errgroup"
    33  	"k8s.io/apimachinery/pkg/api/equality"
    34  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    35  	"k8s.io/apimachinery/pkg/util/diff"
    36  	fakectrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
    37  
    38  	v1 "sigs.k8s.io/prow/pkg/apis/prowjobs/v1"
    39  	"sigs.k8s.io/prow/pkg/crier/reporters/criercommonlib"
    40  	"sigs.k8s.io/prow/pkg/kube"
    41  )
    42  
    43  var timeNow = time.Date(1234, time.May, 15, 1, 2, 3, 4, time.UTC)
    44  
    45  const (
    46  	presubmit  = string(v1.PresubmitJob)
    47  	postsubmit = string(v1.PostsubmitJob)
    48  )
    49  
    50  type fgc struct {
    51  	reportMessage string
    52  	reportLabel   map[string]string
    53  	instance      string
    54  	changes       map[string][]*gerrit.ChangeInfo
    55  	count         int
    56  }
    57  
    58  func (f *fgc) SetReview(instance, id, revision, message string, labels map[string]string) error {
    59  	if instance != f.instance {
    60  		return fmt.Errorf("wrong instance: %s", instance)
    61  	}
    62  	exist, err := f.ChangeExist(instance, id)
    63  	if err != nil {
    64  		return err
    65  	}
    66  	if !exist {
    67  		return errors.New("change not exist: 404")
    68  	}
    69  	change, err := f.GetChange(instance, id)
    70  	if err != nil {
    71  		return err
    72  	}
    73  
    74  	if _, ok := change.Revisions[revision]; !ok {
    75  		return errors.New("revision doesn't exist")
    76  	}
    77  
    78  	for label := range labels {
    79  		if label == "bad-label" {
    80  			return fmt.Errorf("bad label")
    81  		}
    82  	}
    83  	f.reportMessage = message
    84  	if len(labels) > 0 {
    85  		f.reportLabel = labels
    86  	}
    87  	f.count++
    88  	return nil
    89  }
    90  
    91  func (f *fgc) GetChange(instance, id string, addtionalFields ...string) (*gerrit.ChangeInfo, error) {
    92  	if f.changes == nil {
    93  		return nil, errors.New("fake client changes is not initialized")
    94  	}
    95  	changes, ok := f.changes[instance]
    96  	if !ok {
    97  		return nil, fmt.Errorf("instance %s not found", instance)
    98  	}
    99  	for _, change := range changes {
   100  		if change.ID == id {
   101  			return change, nil
   102  		}
   103  	}
   104  	return nil, nil
   105  }
   106  
   107  func (f *fgc) ChangeExist(instance, id string) (bool, error) {
   108  	if f.changes == nil {
   109  		return false, errors.New("fake client changes is not initialized")
   110  	}
   111  	changes, ok := f.changes[instance]
   112  	if !ok {
   113  		return false, fmt.Errorf("instance %s not found", instance)
   114  	}
   115  	for _, change := range changes {
   116  		if change.ID == id {
   117  			return true, nil
   118  		}
   119  	}
   120  	return false, nil
   121  }
   122  
   123  func TestReport(t *testing.T) {
   124  	changes := map[string][]*gerrit.ChangeInfo{
   125  		"gerrit": {
   126  			{ID: "123-abc", Status: "NEW", Revisions: map[string]gerrit.RevisionInfo{"abc": {}}},
   127  			{ID: "merged", Status: "MERGED", Revisions: map[string]gerrit.RevisionInfo{"abc": {}}},
   128  		},
   129  	}
   130  	var testcases = []struct {
   131  		name              string
   132  		pj                *v1.ProwJob
   133  		existingPJs       []*v1.ProwJob
   134  		expectReport      bool
   135  		reportInclude     []string
   136  		reportExclude     []string
   137  		expectLabel       map[string]string
   138  		expectError       bool
   139  		numExpectedReport int
   140  	}{
   141  		{
   142  			name: "1 job, unfinished, should not report",
   143  			pj: &v1.ProwJob{
   144  				Spec: v1.ProwJobSpec{
   145  					Report: true,
   146  				},
   147  				Status: v1.ProwJobStatus{
   148  					State: v1.PendingState,
   149  				},
   150  			},
   151  		},
   152  		{
   153  			name: "1 job, finished, no labels, should not report",
   154  			pj: &v1.ProwJob{
   155  				Spec: v1.ProwJobSpec{
   156  					Report: true,
   157  				},
   158  				Status: v1.ProwJobStatus{
   159  					State: v1.SuccessState,
   160  				},
   161  			},
   162  		},
   163  		{
   164  			name: "1 job, finished, missing gerrit-id label, should not report",
   165  			pj: &v1.ProwJob{
   166  				Spec: v1.ProwJobSpec{
   167  					Report: true,
   168  				},
   169  				ObjectMeta: metav1.ObjectMeta{
   170  					Labels: map[string]string{
   171  						kube.GerritRevision:    "abc",
   172  						kube.ProwJobTypeLabel:  presubmit,
   173  						kube.GerritReportLabel: "Code-Review",
   174  					},
   175  					Annotations: map[string]string{
   176  						kube.GerritInstance: "gerrit",
   177  					},
   178  				},
   179  				Status: v1.ProwJobStatus{
   180  					State: v1.SuccessState,
   181  				},
   182  			},
   183  		},
   184  		{
   185  			name: "1 job, finished, missing gerrit-revision label, should not report",
   186  			pj: &v1.ProwJob{
   187  				Spec: v1.ProwJobSpec{
   188  					Report: true,
   189  				},
   190  				ObjectMeta: metav1.ObjectMeta{
   191  					Annotations: map[string]string{
   192  						kube.GerritID:          "123-abc",
   193  						kube.GerritInstance:    "gerrit",
   194  						kube.GerritReportLabel: "Code-Review",
   195  					},
   196  				},
   197  				Status: v1.ProwJobStatus{
   198  					State: v1.SuccessState,
   199  				},
   200  			},
   201  		},
   202  		{
   203  			name: "1 job, finished, missing gerrit-instance label, should not report",
   204  			pj: &v1.ProwJob{
   205  				Spec: v1.ProwJobSpec{
   206  					Report: true,
   207  				},
   208  				ObjectMeta: metav1.ObjectMeta{
   209  					Labels: map[string]string{
   210  						kube.GerritRevision:    "abc",
   211  						kube.ProwJobTypeLabel:  presubmit,
   212  						kube.GerritReportLabel: "Code-Review",
   213  					},
   214  					Annotations: map[string]string{
   215  						kube.GerritID: "123-abc",
   216  					},
   217  				},
   218  				Status: v1.ProwJobStatus{
   219  					State: v1.SuccessState,
   220  				},
   221  			},
   222  		},
   223  		{
   224  			name: "1 job, passed, should report",
   225  			pj: &v1.ProwJob{
   226  				ObjectMeta: metav1.ObjectMeta{
   227  					Labels: map[string]string{
   228  						kube.GerritRevision:    "abc",
   229  						kube.ProwJobTypeLabel:  presubmit,
   230  						kube.GerritReportLabel: "Code-Review",
   231  					},
   232  					Annotations: map[string]string{
   233  						kube.GerritID:       "123-abc",
   234  						kube.GerritInstance: "gerrit",
   235  					},
   236  					Name:      "ci-foo",
   237  					Namespace: "test-pods",
   238  				},
   239  				Status: v1.ProwJobStatus{
   240  					State: v1.SuccessState,
   241  					URL:   "guber/foo",
   242  				},
   243  				Spec: v1.ProwJobSpec{
   244  					Refs: &v1.Refs{
   245  						Repo: "foo",
   246  						Pulls: []v1.Pull{
   247  							{
   248  								Number: 0,
   249  							},
   250  						},
   251  					},
   252  					Job:    "ci-foo",
   253  					Report: true,
   254  				},
   255  			},
   256  			expectReport:      true,
   257  			reportInclude:     []string{"1 out of 1", "ci-foo", "SUCCESS", "guber/foo"},
   258  			expectLabel:       map[string]string{codeReview: lgtm},
   259  			numExpectedReport: 0,
   260  		},
   261  		{
   262  			name: "1-job-passed-change-missing",
   263  			pj: &v1.ProwJob{
   264  				ObjectMeta: metav1.ObjectMeta{
   265  					Labels: map[string]string{
   266  						kube.GerritRevision:    "abc",
   267  						kube.ProwJobTypeLabel:  presubmit,
   268  						kube.GerritReportLabel: "Code-Review",
   269  					},
   270  					Annotations: map[string]string{
   271  						kube.GerritID:       "123-not-exist",
   272  						kube.GerritInstance: "gerrit",
   273  					},
   274  					Name:      "ci-foo",
   275  					Namespace: "test-pods",
   276  				},
   277  				Status: v1.ProwJobStatus{
   278  					State: v1.SuccessState,
   279  					URL:   "guber/foo",
   280  				},
   281  				Spec: v1.ProwJobSpec{
   282  					Refs: &v1.Refs{
   283  						Repo: "foo",
   284  						Pulls: []v1.Pull{
   285  							{
   286  								Number: 0,
   287  							},
   288  						},
   289  					},
   290  					Job:    "ci-foo",
   291  					Report: true,
   292  				},
   293  			},
   294  			expectReport:      true,
   295  			numExpectedReport: 0,
   296  		},
   297  		{
   298  			name: "1-job-passed-revision-missing",
   299  			pj: &v1.ProwJob{
   300  				ObjectMeta: metav1.ObjectMeta{
   301  					Labels: map[string]string{
   302  						kube.GerritRevision:    "not-exist",
   303  						kube.ProwJobTypeLabel:  presubmit,
   304  						kube.GerritReportLabel: "Code-Review",
   305  					},
   306  					Annotations: map[string]string{
   307  						kube.GerritID:       "123-abc",
   308  						kube.GerritInstance: "gerrit",
   309  					},
   310  					Name:      "ci-foo",
   311  					Namespace: "test-pods",
   312  				},
   313  				Status: v1.ProwJobStatus{
   314  					State: v1.SuccessState,
   315  					URL:   "guber/foo",
   316  				},
   317  				Spec: v1.ProwJobSpec{
   318  					Refs: &v1.Refs{
   319  						Repo: "foo",
   320  						Pulls: []v1.Pull{
   321  							{
   322  								Number: 0,
   323  							},
   324  						},
   325  					},
   326  					Job:    "ci-foo",
   327  					Report: true,
   328  				},
   329  			},
   330  			expectReport:      true,
   331  			numExpectedReport: 0,
   332  		},
   333  		{
   334  			name: "1 job, passed, skip report set true, should not report",
   335  			pj: &v1.ProwJob{
   336  				ObjectMeta: metav1.ObjectMeta{
   337  					Labels: map[string]string{
   338  						kube.GerritRevision:    "abc",
   339  						kube.ProwJobTypeLabel:  presubmit,
   340  						kube.GerritReportLabel: "Code-Review",
   341  					},
   342  					Annotations: map[string]string{
   343  						kube.GerritID:       "123-abc",
   344  						kube.GerritInstance: "gerrit",
   345  					},
   346  				},
   347  				Status: v1.ProwJobStatus{
   348  					State: v1.SuccessState,
   349  					URL:   "guber/foo",
   350  				},
   351  				Spec: v1.ProwJobSpec{
   352  					Refs: &v1.Refs{
   353  						Repo: "foo",
   354  						Pulls: []v1.Pull{
   355  							{
   356  								Number: 0,
   357  							},
   358  						},
   359  					},
   360  					Job:    "ci-foo",
   361  					Report: false,
   362  				},
   363  			},
   364  		},
   365  		{
   366  			name: "1 job, passed, bad label, should report without label",
   367  			pj: &v1.ProwJob{
   368  				ObjectMeta: metav1.ObjectMeta{
   369  					Labels: map[string]string{
   370  						kube.GerritRevision:    "abc",
   371  						kube.ProwJobTypeLabel:  presubmit,
   372  						kube.GerritReportLabel: "bad-label",
   373  					},
   374  					Annotations: map[string]string{
   375  						kube.GerritID:       "123-abc",
   376  						kube.GerritInstance: "gerrit",
   377  					},
   378  					Name:      "ci-foo",
   379  					Namespace: "test-pods",
   380  				},
   381  				Status: v1.ProwJobStatus{
   382  					State: v1.SuccessState,
   383  					URL:   "guber/foo",
   384  				},
   385  				Spec: v1.ProwJobSpec{
   386  					Refs: &v1.Refs{
   387  						Repo: "foo",
   388  						Pulls: []v1.Pull{
   389  							{
   390  								Number: 0,
   391  							},
   392  						},
   393  					},
   394  					Job:    "ci-foo",
   395  					Report: true,
   396  				},
   397  			},
   398  			expectReport:      true,
   399  			reportInclude:     []string{"1 out of 1", "ci-foo", "SUCCESS", "guber/foo"},
   400  			numExpectedReport: 0,
   401  		},
   402  		{
   403  			name: "1 job, passed, empty label, should report, but not vote",
   404  			pj: &v1.ProwJob{
   405  				ObjectMeta: metav1.ObjectMeta{
   406  					Labels: map[string]string{
   407  						kube.GerritRevision:    "abc",
   408  						kube.ProwJobTypeLabel:  presubmit,
   409  						kube.GerritReportLabel: "",
   410  					},
   411  					Annotations: map[string]string{
   412  						kube.GerritID:       "123-abc",
   413  						kube.GerritInstance: "gerrit",
   414  					},
   415  					Name:      "ci-foo",
   416  					Namespace: "test-pods",
   417  				},
   418  				Status: v1.ProwJobStatus{
   419  					State: v1.SuccessState,
   420  					URL:   "guber/foo",
   421  				},
   422  				Spec: v1.ProwJobSpec{
   423  					Refs: &v1.Refs{
   424  						Repo: "foo",
   425  						Pulls: []v1.Pull{
   426  							{
   427  								Number: 0,
   428  							},
   429  						},
   430  					},
   431  					Job:    "ci-foo",
   432  					Report: true,
   433  				},
   434  			},
   435  			expectReport:      true,
   436  			reportInclude:     []string{"1 out of 1", "ci-foo", "SUCCESS", "guber/foo"},
   437  			numExpectedReport: 0,
   438  		},
   439  		{
   440  			name: "1 job, ABORTED, should not report",
   441  			pj: &v1.ProwJob{
   442  				ObjectMeta: metav1.ObjectMeta{
   443  					Labels: map[string]string{
   444  						kube.GerritRevision:    "abc",
   445  						kube.ProwJobTypeLabel:  presubmit,
   446  						kube.GerritReportLabel: "Code-Review",
   447  					},
   448  					Annotations: map[string]string{
   449  						kube.GerritID:       "123-abc",
   450  						kube.GerritInstance: "gerrit",
   451  					},
   452  				},
   453  				Status: v1.ProwJobStatus{
   454  					State: v1.AbortedState,
   455  					URL:   "guber/foo",
   456  				},
   457  				Spec: v1.ProwJobSpec{
   458  					Refs: &v1.Refs{
   459  						Repo: "foo",
   460  						Pulls: []v1.Pull{
   461  							{
   462  								Number: 0,
   463  							},
   464  						},
   465  					},
   466  					Job:    "ci-foo",
   467  					Report: true,
   468  				},
   469  			},
   470  			expectReport: false,
   471  		},
   472  		{
   473  			name: "1 job, passed, with customized label, should report to customized label",
   474  			pj: &v1.ProwJob{
   475  				ObjectMeta: metav1.ObjectMeta{
   476  					Labels: map[string]string{
   477  						kube.GerritRevision:    "abc",
   478  						kube.GerritReportLabel: "foobar-label",
   479  						kube.ProwJobTypeLabel:  presubmit,
   480  					},
   481  					Annotations: map[string]string{
   482  						kube.GerritID:       "123-abc",
   483  						kube.GerritInstance: "gerrit",
   484  					},
   485  					Name:      "ci-foo",
   486  					Namespace: "test-pods",
   487  				},
   488  				Status: v1.ProwJobStatus{
   489  					State: v1.SuccessState,
   490  					URL:   "guber/foo",
   491  				},
   492  				Spec: v1.ProwJobSpec{
   493  					Refs: &v1.Refs{
   494  						Repo: "foo",
   495  						Pulls: []v1.Pull{
   496  							{
   497  								Number: 0,
   498  							},
   499  						},
   500  					},
   501  					Job:    "ci-foo",
   502  					Report: true,
   503  				},
   504  			},
   505  			expectReport:      true,
   506  			reportInclude:     []string{"1 out of 1", "ci-foo", "SUCCESS", "guber/foo"},
   507  			expectLabel:       map[string]string{"foobar-label": lgtm},
   508  			numExpectedReport: 0,
   509  		},
   510  		{
   511  			name: "1 job, failed, should report",
   512  			pj: &v1.ProwJob{
   513  				ObjectMeta: metav1.ObjectMeta{
   514  					Labels: map[string]string{
   515  						kube.GerritRevision:    "abc",
   516  						kube.ProwJobTypeLabel:  presubmit,
   517  						kube.GerritReportLabel: "Code-Review",
   518  					},
   519  					Annotations: map[string]string{
   520  						kube.GerritID:       "123-abc",
   521  						kube.GerritInstance: "gerrit",
   522  					},
   523  					Name:      "ci-foo",
   524  					Namespace: "test-pods",
   525  				},
   526  				Status: v1.ProwJobStatus{
   527  					State: v1.FailureState,
   528  					URL:   "guber/foo",
   529  				},
   530  				Spec: v1.ProwJobSpec{
   531  					Type: v1.PresubmitJob,
   532  					Refs: &v1.Refs{
   533  						Repo: "foo",
   534  						Pulls: []v1.Pull{
   535  							{
   536  								Number: 0,
   537  							},
   538  						},
   539  					},
   540  					Job:    "ci-foo",
   541  					Report: true,
   542  				},
   543  			},
   544  			expectReport:      true,
   545  			reportInclude:     []string{"0 out of 1", "ci-foo", "FAILURE", "guber/foo"},
   546  			expectLabel:       map[string]string{codeReview: lbtm},
   547  			numExpectedReport: 0,
   548  		},
   549  		{
   550  			name: "1 job, passed, has slash in repo name, should report and handle slash properly",
   551  			pj: &v1.ProwJob{
   552  				ObjectMeta: metav1.ObjectMeta{
   553  					Labels: map[string]string{
   554  						kube.GerritRevision:    "abc",
   555  						kube.ProwJobTypeLabel:  presubmit,
   556  						kube.GerritReportLabel: "Code-Review",
   557  					},
   558  					Annotations: map[string]string{
   559  						kube.GerritID:       "123-abc",
   560  						kube.GerritInstance: "gerrit",
   561  					},
   562  					Name:      "ci-foo",
   563  					Namespace: "test-pods",
   564  				},
   565  				Status: v1.ProwJobStatus{
   566  					State: v1.SuccessState,
   567  					URL:   "guber/foo/bar",
   568  				},
   569  				Spec: v1.ProwJobSpec{
   570  					Refs: &v1.Refs{
   571  						Repo: "foo/bar",
   572  						Pulls: []v1.Pull{
   573  							{
   574  								Number: 0,
   575  							},
   576  						},
   577  					},
   578  					Job:    "ci-foo-bar",
   579  					Report: true,
   580  				},
   581  			},
   582  			expectReport:      true,
   583  			reportInclude:     []string{"1 out of 1", "ci-foo-bar", "SUCCESS", "guber/foo/bar"},
   584  			reportExclude:     []string{"foo_bar"},
   585  			expectLabel:       map[string]string{codeReview: lgtm},
   586  			numExpectedReport: 0,
   587  		},
   588  		{
   589  			name: "1 job, no pulls, should error",
   590  			pj: &v1.ProwJob{
   591  				ObjectMeta: metav1.ObjectMeta{
   592  					Labels: map[string]string{
   593  						kube.GerritRevision:    "abc",
   594  						kube.ProwJobTypeLabel:  presubmit,
   595  						kube.GerritReportLabel: "Code-Review",
   596  					},
   597  					Annotations: map[string]string{
   598  						kube.GerritID:       "123-abc",
   599  						kube.GerritInstance: "gerrit",
   600  					},
   601  				},
   602  				Status: v1.ProwJobStatus{
   603  					State: v1.SuccessState,
   604  					URL:   "guber/foo",
   605  				},
   606  				Spec: v1.ProwJobSpec{
   607  					Type: v1.PresubmitJob,
   608  					Refs: &v1.Refs{
   609  						Repo: "foo",
   610  					},
   611  					Job:    "ci-foo",
   612  					Report: true,
   613  				},
   614  			},
   615  			expectReport: true,
   616  			expectError:  true,
   617  		},
   618  		{
   619  			name: "1 postsubmit job, no pulls, should error",
   620  			pj: &v1.ProwJob{
   621  				ObjectMeta: metav1.ObjectMeta{
   622  					Labels: map[string]string{
   623  						kube.GerritRevision:    "abc",
   624  						kube.ProwJobTypeLabel:  postsubmit,
   625  						kube.GerritReportLabel: "Code-Review",
   626  					},
   627  					Annotations: map[string]string{
   628  						kube.GerritID:       "123-abc",
   629  						kube.GerritInstance: "gerrit",
   630  					},
   631  				},
   632  				Status: v1.ProwJobStatus{
   633  					State: v1.SuccessState,
   634  					URL:   "guber/foo",
   635  				},
   636  				Spec: v1.ProwJobSpec{
   637  					Type: v1.PostsubmitJob,
   638  					Refs: &v1.Refs{
   639  						Repo: "foo",
   640  					},
   641  					Job:    "ci-foo",
   642  					Report: true,
   643  				},
   644  			},
   645  			expectReport: true,
   646  			expectError:  true,
   647  		},
   648  		{
   649  			name: "2 jobs, one passed, other job finished but on different revision, should report",
   650  			pj: &v1.ProwJob{
   651  				ObjectMeta: metav1.ObjectMeta{
   652  					Labels: map[string]string{
   653  						kube.GerritRevision:    "abc",
   654  						kube.ProwJobTypeLabel:  presubmit,
   655  						kube.GerritReportLabel: "Code-Review",
   656  					},
   657  					Annotations: map[string]string{
   658  						kube.GerritID:       "123-abc",
   659  						kube.GerritInstance: "gerrit",
   660  					},
   661  					Name:      "ci-foo",
   662  					Namespace: "test-pods",
   663  				},
   664  				Status: v1.ProwJobStatus{
   665  					State: v1.SuccessState,
   666  					URL:   "guber/foo",
   667  				},
   668  				Spec: v1.ProwJobSpec{
   669  					Refs: &v1.Refs{
   670  						Repo: "foo",
   671  						Pulls: []v1.Pull{
   672  							{
   673  								Number: 0,
   674  							},
   675  						},
   676  					},
   677  					Job:    "ci-foo",
   678  					Report: true,
   679  				},
   680  			},
   681  			existingPJs: []*v1.ProwJob{
   682  				{
   683  					ObjectMeta: metav1.ObjectMeta{
   684  						Labels: map[string]string{
   685  							kube.GerritRevision:    "def",
   686  							kube.ProwJobTypeLabel:  presubmit,
   687  							kube.GerritReportLabel: "Code-Review",
   688  						},
   689  						Annotations: map[string]string{
   690  							kube.GerritID:       "123-def",
   691  							kube.GerritInstance: "gerrit",
   692  						},
   693  						Name:      "ci-foo",
   694  						Namespace: "test-pods",
   695  					},
   696  					Status: v1.ProwJobStatus{
   697  						State: v1.SuccessState,
   698  						URL:   "guber/bar",
   699  					},
   700  					Spec: v1.ProwJobSpec{
   701  						Refs: &v1.Refs{
   702  							Repo: "bar",
   703  							Pulls: []v1.Pull{
   704  								{
   705  									Number: 0,
   706  								},
   707  							},
   708  						},
   709  						Job:    "ci-bar",
   710  						Report: true,
   711  					},
   712  				},
   713  			},
   714  			expectReport:      true,
   715  			reportInclude:     []string{"1 out of 1", "ci-foo", "SUCCESS", "guber/foo"},
   716  			reportExclude:     []string{"2", "bar"},
   717  			expectLabel:       map[string]string{codeReview: lgtm},
   718  			numExpectedReport: 0,
   719  		},
   720  		{
   721  			name: "2 jobs, one passed, other job unfinished with same label, should not report",
   722  			pj: &v1.ProwJob{
   723  				ObjectMeta: metav1.ObjectMeta{
   724  					Labels: map[string]string{
   725  						kube.GerritRevision:    "abc",
   726  						kube.ProwJobTypeLabel:  presubmit,
   727  						kube.GerritReportLabel: "Code-Review",
   728  					},
   729  					Annotations: map[string]string{
   730  						kube.GerritID:       "123-abc",
   731  						kube.GerritInstance: "gerrit",
   732  					},
   733  				},
   734  				Status: v1.ProwJobStatus{
   735  					State: v1.SuccessState,
   736  					URL:   "guber/foo",
   737  				},
   738  				Spec: v1.ProwJobSpec{
   739  					Refs: &v1.Refs{
   740  						Repo: "foo",
   741  						Pulls: []v1.Pull{
   742  							{
   743  								Number: 0,
   744  							},
   745  						},
   746  					},
   747  					Job:    "ci-foo",
   748  					Report: true,
   749  				},
   750  			},
   751  			existingPJs: []*v1.ProwJob{
   752  				{
   753  					ObjectMeta: metav1.ObjectMeta{
   754  						Labels: map[string]string{
   755  							kube.GerritRevision:    "abc",
   756  							kube.ProwJobTypeLabel:  presubmit,
   757  							kube.GerritReportLabel: "Code-Review",
   758  						},
   759  						Annotations: map[string]string{
   760  							kube.GerritID:       "123-abc",
   761  							kube.GerritInstance: "gerrit",
   762  						},
   763  					},
   764  					Status: v1.ProwJobStatus{
   765  						State: v1.PendingState,
   766  						URL:   "guber/bar",
   767  					},
   768  					Spec: v1.ProwJobSpec{
   769  						Refs: &v1.Refs{
   770  							Repo: "bar",
   771  							Pulls: []v1.Pull{
   772  								{
   773  									Number: 0,
   774  								},
   775  							},
   776  						},
   777  						Job:    "ci-bar",
   778  						Report: true,
   779  					},
   780  				},
   781  			},
   782  		},
   783  		{
   784  			name: "2 jobs, 1 passed, 1 pending, empty labels, should not wait for aggregation, no vote",
   785  			pj: &v1.ProwJob{
   786  				ObjectMeta: metav1.ObjectMeta{
   787  					Labels: map[string]string{
   788  						kube.GerritRevision:    "abc",
   789  						kube.ProwJobTypeLabel:  presubmit,
   790  						kube.GerritReportLabel: "",
   791  					},
   792  					Annotations: map[string]string{
   793  						kube.GerritID:       "123-abc",
   794  						kube.GerritInstance: "gerrit",
   795  					},
   796  					Name:      "ci-foo",
   797  					Namespace: "test-pods",
   798  				},
   799  				Status: v1.ProwJobStatus{
   800  					State: v1.SuccessState,
   801  					URL:   "guber/foo",
   802  				},
   803  				Spec: v1.ProwJobSpec{
   804  					Refs: &v1.Refs{
   805  						Repo: "foo",
   806  						Pulls: []v1.Pull{
   807  							{
   808  								Number: 0,
   809  							},
   810  						},
   811  					},
   812  					Job:    "ci-foo",
   813  					Report: true,
   814  				},
   815  			},
   816  			existingPJs: []*v1.ProwJob{
   817  				{
   818  					ObjectMeta: metav1.ObjectMeta{
   819  						Labels: map[string]string{
   820  							kube.GerritRevision:    "abc",
   821  							kube.ProwJobTypeLabel:  presubmit,
   822  							kube.GerritReportLabel: "",
   823  						},
   824  						Annotations: map[string]string{
   825  							kube.GerritID:       "123-abc",
   826  							kube.GerritInstance: "gerrit",
   827  						},
   828  						Name:      "ci-foo",
   829  						Namespace: "test-pods",
   830  					},
   831  					Status: v1.ProwJobStatus{
   832  						State: v1.PendingState,
   833  						URL:   "guber/bar",
   834  					},
   835  					Spec: v1.ProwJobSpec{
   836  						Refs: &v1.Refs{
   837  							Repo: "bar",
   838  							Pulls: []v1.Pull{
   839  								{
   840  									Number: 0,
   841  								},
   842  							},
   843  						},
   844  						Job:    "ci-bar",
   845  						Report: true,
   846  					},
   847  				},
   848  			},
   849  			expectReport:      true,
   850  			reportInclude:     []string{"1 out of 1", "ci-foo", "SUCCESS", "guber/foo"},
   851  			reportExclude:     []string{"2", "bar"},
   852  			numExpectedReport: 0,
   853  		},
   854  		{
   855  			name: "non-presubmit failures vote zero",
   856  			pj: &v1.ProwJob{
   857  				ObjectMeta: metav1.ObjectMeta{
   858  					Labels: map[string]string{
   859  						kube.GerritRevision:    "abc",
   860  						kube.ProwJobTypeLabel:  postsubmit,
   861  						kube.GerritReportLabel: "Code-Review",
   862  					},
   863  					Annotations: map[string]string{
   864  						kube.GerritID:       "123-abc",
   865  						kube.GerritInstance: "gerrit",
   866  					},
   867  					Name:      "ci-foo",
   868  					Namespace: "test-pods",
   869  				},
   870  				Status: v1.ProwJobStatus{
   871  					State: v1.FailureState,
   872  					URL:   "guber/foo",
   873  				},
   874  				Spec: v1.ProwJobSpec{
   875  					Type: v1.PostsubmitJob,
   876  					Refs: &v1.Refs{
   877  						Repo: "foo",
   878  						Pulls: []v1.Pull{
   879  							{
   880  								Number: 0,
   881  							},
   882  						},
   883  					},
   884  					Job:    "ci-foo",
   885  					Report: true,
   886  				},
   887  			},
   888  			expectReport:      true,
   889  			expectLabel:       map[string]string{codeReview: lztm},
   890  			numExpectedReport: 0,
   891  		},
   892  		{
   893  			name: "2 jobs, one passed, other job failed, should report",
   894  			pj: &v1.ProwJob{
   895  				ObjectMeta: metav1.ObjectMeta{
   896  					Labels: map[string]string{
   897  						kube.GerritRevision:    "abc",
   898  						kube.ProwJobTypeLabel:  presubmit,
   899  						kube.GerritReportLabel: "Code-Review",
   900  					},
   901  					Annotations: map[string]string{
   902  						kube.GerritID:       "123-abc",
   903  						kube.GerritInstance: "gerrit",
   904  					},
   905  					Name:      "ci-foo",
   906  					Namespace: "test-pods",
   907  				},
   908  				Status: v1.ProwJobStatus{
   909  					State: v1.SuccessState,
   910  					URL:   "guber/foo",
   911  				},
   912  				Spec: v1.ProwJobSpec{
   913  					Type: v1.PresubmitJob,
   914  					Refs: &v1.Refs{
   915  						Repo: "foo",
   916  						Pulls: []v1.Pull{
   917  							{
   918  								Number: 0,
   919  							},
   920  						},
   921  					},
   922  					Job:    "ci-foo",
   923  					Report: true,
   924  				},
   925  			},
   926  			existingPJs: []*v1.ProwJob{
   927  				{
   928  					ObjectMeta: metav1.ObjectMeta{
   929  						Labels: map[string]string{
   930  							kube.GerritRevision:    "abc",
   931  							kube.ProwJobTypeLabel:  presubmit,
   932  							kube.GerritReportLabel: "Code-Review",
   933  						},
   934  						Annotations: map[string]string{
   935  							kube.GerritID:       "123-abc",
   936  							kube.GerritInstance: "gerrit",
   937  						},
   938  						Name:      "ci-bar",
   939  						Namespace: "test-pods",
   940  					},
   941  					Status: v1.ProwJobStatus{
   942  						State: v1.FailureState,
   943  						URL:   "guber/bar",
   944  					},
   945  					Spec: v1.ProwJobSpec{
   946  						Type: v1.PresubmitJob,
   947  						Refs: &v1.Refs{
   948  							Repo: "bar",
   949  							Pulls: []v1.Pull{
   950  								{
   951  									Number: 0,
   952  								},
   953  							},
   954  						},
   955  						Job:    "ci-bar",
   956  						Report: true,
   957  					},
   958  				},
   959  			},
   960  			expectReport:      true,
   961  			reportInclude:     []string{"1 out of 2", "ci-foo", "SUCCESS", "ci-bar", "FAILURE", "guber/foo", "guber/bar"},
   962  			reportExclude:     []string{"0", "2 out of 2"},
   963  			expectLabel:       map[string]string{codeReview: lbtm},
   964  			numExpectedReport: 0,
   965  		},
   966  		{
   967  			name: "2 jobs, both passed, should report",
   968  			pj: &v1.ProwJob{
   969  				ObjectMeta: metav1.ObjectMeta{
   970  					Labels: map[string]string{
   971  						kube.GerritRevision:    "abc",
   972  						kube.ProwJobTypeLabel:  presubmit,
   973  						kube.GerritReportLabel: "Code-Review",
   974  					},
   975  					Annotations: map[string]string{
   976  						kube.GerritID:       "123-abc",
   977  						kube.GerritInstance: "gerrit",
   978  					},
   979  					Name:      "ci-foo",
   980  					Namespace: "test-pods",
   981  				},
   982  				Status: v1.ProwJobStatus{
   983  					State: v1.SuccessState,
   984  					URL:   "guber/foo",
   985  				},
   986  				Spec: v1.ProwJobSpec{
   987  					Refs: &v1.Refs{
   988  						Repo: "foo",
   989  						Pulls: []v1.Pull{
   990  							{
   991  								Number: 0,
   992  							},
   993  						},
   994  					},
   995  					Job:    "ci-foo",
   996  					Report: true,
   997  				},
   998  			},
   999  			existingPJs: []*v1.ProwJob{
  1000  				{
  1001  					ObjectMeta: metav1.ObjectMeta{
  1002  						Labels: map[string]string{
  1003  							kube.GerritRevision:    "abc",
  1004  							kube.ProwJobTypeLabel:  presubmit,
  1005  							kube.GerritReportLabel: "Code-Review",
  1006  						},
  1007  						Annotations: map[string]string{
  1008  							kube.GerritID:       "123-abc",
  1009  							kube.GerritInstance: "gerrit",
  1010  						},
  1011  						Name:      "ci-bar",
  1012  						Namespace: "test-pods",
  1013  					},
  1014  					Status: v1.ProwJobStatus{
  1015  						State: v1.SuccessState,
  1016  						URL:   "guber/bar",
  1017  					},
  1018  					Spec: v1.ProwJobSpec{
  1019  						Refs: &v1.Refs{
  1020  							Repo: "bar",
  1021  							Pulls: []v1.Pull{
  1022  								{
  1023  									Number: 0,
  1024  								},
  1025  							},
  1026  						},
  1027  						Job:    "ci-bar",
  1028  						Report: true,
  1029  					},
  1030  				},
  1031  			},
  1032  			expectReport:      true,
  1033  			reportInclude:     []string{"2 out of 2", "ci-foo", "SUCCESS", "ci-bar", "guber/foo", "guber/bar"},
  1034  			reportExclude:     []string{"1", "0", "FAILURE"},
  1035  			expectLabel:       map[string]string{codeReview: lgtm},
  1036  			numExpectedReport: 0,
  1037  		},
  1038  		{
  1039  			name: "2 jobs, one passed, one aborted, should report",
  1040  			pj: &v1.ProwJob{
  1041  				ObjectMeta: metav1.ObjectMeta{
  1042  					Labels: map[string]string{
  1043  						kube.GerritRevision:    "abc",
  1044  						kube.ProwJobTypeLabel:  presubmit,
  1045  						kube.GerritReportLabel: "Code-Review",
  1046  					},
  1047  					Annotations: map[string]string{
  1048  						kube.GerritID:       "123-abc",
  1049  						kube.GerritInstance: "gerrit",
  1050  					},
  1051  					Name:      "ci-foo",
  1052  					Namespace: "test-pods",
  1053  				},
  1054  				Status: v1.ProwJobStatus{
  1055  					State: v1.SuccessState,
  1056  					URL:   "guber/foo",
  1057  				},
  1058  				Spec: v1.ProwJobSpec{
  1059  					Refs: &v1.Refs{
  1060  						Repo: "foo",
  1061  						Pulls: []v1.Pull{
  1062  							{
  1063  								Number: 0,
  1064  							},
  1065  						},
  1066  					},
  1067  					Job:    "ci-foo",
  1068  					Type:   v1.PresubmitJob,
  1069  					Report: true,
  1070  				},
  1071  			},
  1072  			existingPJs: []*v1.ProwJob{
  1073  				{
  1074  					ObjectMeta: metav1.ObjectMeta{
  1075  						Labels: map[string]string{
  1076  							kube.GerritRevision:    "abc",
  1077  							kube.ProwJobTypeLabel:  presubmit,
  1078  							kube.GerritReportLabel: "Code-Review",
  1079  						},
  1080  						Annotations: map[string]string{
  1081  							kube.GerritID:       "123-abc",
  1082  							kube.GerritInstance: "gerrit",
  1083  						},
  1084  						Name:      "ci-bar",
  1085  						Namespace: "test-pods",
  1086  					},
  1087  					Status: v1.ProwJobStatus{
  1088  						State: v1.AbortedState,
  1089  						URL:   "guber/bar",
  1090  					},
  1091  					Spec: v1.ProwJobSpec{
  1092  						Refs: &v1.Refs{
  1093  							Repo: "bar",
  1094  							Pulls: []v1.Pull{
  1095  								{
  1096  									Number: 0,
  1097  								},
  1098  							},
  1099  						},
  1100  						Job:    "ci-bar",
  1101  						Type:   v1.PresubmitJob,
  1102  						Report: true,
  1103  					},
  1104  				},
  1105  			},
  1106  			expectReport:      true,
  1107  			reportInclude:     []string{"1 out of 2", "ci-foo", "SUCCESS", "guber/foo"},
  1108  			expectLabel:       map[string]string{codeReview: lbtm},
  1109  			numExpectedReport: 0,
  1110  		},
  1111  		{
  1112  			name: "postsubmit after presubmit on same revision, should report separately",
  1113  			pj: &v1.ProwJob{
  1114  				ObjectMeta: metav1.ObjectMeta{
  1115  					Labels: map[string]string{
  1116  						kube.GerritRevision:    "abc",
  1117  						kube.GerritReportLabel: "postsubmit-label",
  1118  						kube.ProwJobTypeLabel:  postsubmit,
  1119  					},
  1120  					Annotations: map[string]string{
  1121  						kube.GerritID:       "123-abc",
  1122  						kube.GerritInstance: "gerrit",
  1123  					},
  1124  					Name:      "ci-foo",
  1125  					Namespace: "test-pods",
  1126  				},
  1127  				Status: v1.ProwJobStatus{
  1128  					State: v1.SuccessState,
  1129  					URL:   "guber/foo",
  1130  				},
  1131  				Spec: v1.ProwJobSpec{
  1132  					Refs: &v1.Refs{
  1133  						Repo: "foo",
  1134  						Pulls: []v1.Pull{
  1135  							{
  1136  								Number: 0,
  1137  							},
  1138  						},
  1139  					},
  1140  					Job:    "ci-foo",
  1141  					Report: true,
  1142  				},
  1143  			},
  1144  			existingPJs: []*v1.ProwJob{
  1145  				{
  1146  					ObjectMeta: metav1.ObjectMeta{
  1147  						Labels: map[string]string{
  1148  							kube.GerritRevision:    "abc",
  1149  							kube.ProwJobTypeLabel:  presubmit,
  1150  							kube.GerritReportLabel: "Code-Review",
  1151  						},
  1152  						Annotations: map[string]string{
  1153  							kube.GerritID:       "123-abc",
  1154  							kube.GerritInstance: "gerrit",
  1155  						},
  1156  						Name:      "ci-bar",
  1157  						Namespace: "test-pods",
  1158  					},
  1159  					Status: v1.ProwJobStatus{
  1160  						State: v1.SuccessState,
  1161  						URL:   "guber/bar",
  1162  					},
  1163  					Spec: v1.ProwJobSpec{
  1164  						Refs: &v1.Refs{
  1165  							Repo: "bar",
  1166  							Pulls: []v1.Pull{
  1167  								{
  1168  									Number: 0,
  1169  								},
  1170  							},
  1171  						},
  1172  						Job:    "ci-bar",
  1173  						Report: true,
  1174  					},
  1175  				},
  1176  			},
  1177  			expectReport:      true,
  1178  			reportInclude:     []string{"1 out of 1", "ci-foo", "SUCCESS", "guber/foo"},
  1179  			expectLabel:       map[string]string{"postsubmit-label": lgtm},
  1180  			numExpectedReport: 0,
  1181  		},
  1182  		{
  1183  			name: "2 jobs, both passed, different label, should report by itself",
  1184  			pj: &v1.ProwJob{
  1185  				ObjectMeta: metav1.ObjectMeta{
  1186  					Labels: map[string]string{
  1187  						kube.GerritRevision:    "abc",
  1188  						kube.ProwJobTypeLabel:  presubmit,
  1189  						kube.GerritReportLabel: "label-foo",
  1190  					},
  1191  					Annotations: map[string]string{
  1192  						kube.GerritID:       "123-abc",
  1193  						kube.GerritInstance: "gerrit",
  1194  					},
  1195  					Name:      "ci-foo",
  1196  					Namespace: "test-pods",
  1197  				},
  1198  				Status: v1.ProwJobStatus{
  1199  					State: v1.SuccessState,
  1200  					URL:   "guber/foo",
  1201  				},
  1202  				Spec: v1.ProwJobSpec{
  1203  					Refs: &v1.Refs{
  1204  						Repo: "foo",
  1205  						Pulls: []v1.Pull{
  1206  							{
  1207  								Number: 0,
  1208  							},
  1209  						},
  1210  					},
  1211  					Job:    "ci-foo",
  1212  					Report: true,
  1213  				},
  1214  			},
  1215  			existingPJs: []*v1.ProwJob{
  1216  				{
  1217  					ObjectMeta: metav1.ObjectMeta{
  1218  						Labels: map[string]string{
  1219  							kube.GerritRevision:    "abc",
  1220  							kube.ProwJobTypeLabel:  presubmit,
  1221  							kube.GerritReportLabel: "label-bar",
  1222  						},
  1223  						Annotations: map[string]string{
  1224  							kube.GerritID:       "123-abc",
  1225  							kube.GerritInstance: "gerrit",
  1226  						},
  1227  						Name:      "ci-foo",
  1228  						Namespace: "test-pods",
  1229  					},
  1230  					Status: v1.ProwJobStatus{
  1231  						State: v1.SuccessState,
  1232  						URL:   "guber/bar",
  1233  					},
  1234  					Spec: v1.ProwJobSpec{
  1235  						Refs: &v1.Refs{
  1236  							Repo: "bar",
  1237  							Pulls: []v1.Pull{
  1238  								{
  1239  									Number: 0,
  1240  								},
  1241  							},
  1242  						},
  1243  						Job:    "ci-bar",
  1244  						Report: true,
  1245  					},
  1246  				},
  1247  			},
  1248  			expectReport:      true,
  1249  			reportInclude:     []string{"1 out of 1", "ci-foo", "SUCCESS", "guber/foo"},
  1250  			expectLabel:       map[string]string{"label-foo": lgtm},
  1251  			numExpectedReport: 0,
  1252  		},
  1253  		{
  1254  			name: "one job, reported, retriggered, should report by itself",
  1255  			pj: &v1.ProwJob{
  1256  				ObjectMeta: metav1.ObjectMeta{
  1257  					Labels: map[string]string{
  1258  						kube.GerritRevision:    "abc",
  1259  						kube.ProwJobTypeLabel:  presubmit,
  1260  						kube.GerritReportLabel: "label-foo",
  1261  						kube.OrgLabel:          "org",
  1262  						kube.RepoLabel:         "repo",
  1263  						kube.PullLabel:         "0",
  1264  					},
  1265  					Annotations: map[string]string{
  1266  						kube.GerritID:       "123-abc",
  1267  						kube.GerritInstance: "gerrit",
  1268  					},
  1269  					CreationTimestamp: metav1.Time{
  1270  						Time: timeNow,
  1271  					},
  1272  					Name:      "ci-foo",
  1273  					Namespace: "test-pods",
  1274  				},
  1275  				Status: v1.ProwJobStatus{
  1276  					State: v1.SuccessState,
  1277  					URL:   "guber/foo",
  1278  				},
  1279  				Spec: v1.ProwJobSpec{
  1280  					Refs: &v1.Refs{
  1281  						Repo: "foo",
  1282  						Pulls: []v1.Pull{
  1283  							{
  1284  								Number: 0,
  1285  							},
  1286  						},
  1287  					},
  1288  					Job:    "ci-foo",
  1289  					Report: true,
  1290  				},
  1291  			},
  1292  			existingPJs: []*v1.ProwJob{
  1293  				{
  1294  					ObjectMeta: metav1.ObjectMeta{
  1295  						Labels: map[string]string{
  1296  							kube.GerritRevision:    "abc",
  1297  							kube.ProwJobTypeLabel:  presubmit,
  1298  							kube.GerritReportLabel: "label-foo",
  1299  							kube.OrgLabel:          "org",
  1300  							kube.RepoLabel:         "repo",
  1301  							kube.PullLabel:         "0",
  1302  						},
  1303  						Annotations: map[string]string{
  1304  							kube.GerritID:       "123-abc",
  1305  							kube.GerritInstance: "gerrit",
  1306  						},
  1307  						CreationTimestamp: metav1.Time{
  1308  							Time: timeNow.Add(-time.Minute),
  1309  						},
  1310  						Name:      "ci-foo",
  1311  						Namespace: "test-pods",
  1312  					},
  1313  					Status: v1.ProwJobStatus{
  1314  						PrevReportStates: map[string]v1.ProwJobState{
  1315  							"gerrit-reporter": v1.FailureState,
  1316  						},
  1317  						State: v1.FailureState,
  1318  						URL:   "guber/foo",
  1319  					},
  1320  					Spec: v1.ProwJobSpec{
  1321  						Refs: &v1.Refs{
  1322  							Repo: "foo",
  1323  							Pulls: []v1.Pull{
  1324  								{
  1325  									Number: 0,
  1326  								},
  1327  							},
  1328  						},
  1329  						Job:    "ci-foo",
  1330  						Report: true,
  1331  					},
  1332  				},
  1333  			},
  1334  			expectReport:      true,
  1335  			reportInclude:     []string{"1 out of 1", "ci-foo", "SUCCESS", "guber/foo"},
  1336  			expectLabel:       map[string]string{"label-foo": lgtm},
  1337  			numExpectedReport: 0,
  1338  		},
  1339  		{
  1340  			name: "older job, should not report",
  1341  			pj: &v1.ProwJob{
  1342  				ObjectMeta: metav1.ObjectMeta{
  1343  					Labels: map[string]string{
  1344  						kube.GerritRevision:    "abc",
  1345  						kube.ProwJobTypeLabel:  presubmit,
  1346  						kube.GerritReportLabel: "label-foo",
  1347  						kube.OrgLabel:          "org",
  1348  						kube.RepoLabel:         "repo",
  1349  						kube.PullLabel:         "0",
  1350  					},
  1351  					Annotations: map[string]string{
  1352  						kube.GerritID:       "123-abc",
  1353  						kube.GerritInstance: "gerrit",
  1354  					},
  1355  					CreationTimestamp: metav1.Time{
  1356  						Time: timeNow.Add(-2 * time.Minute),
  1357  					},
  1358  					Name:      "ci-foo",
  1359  					Namespace: "test-pods",
  1360  				},
  1361  				Status: v1.ProwJobStatus{
  1362  					State: v1.SuccessState,
  1363  					URL:   "guber/foo",
  1364  				},
  1365  				Spec: v1.ProwJobSpec{
  1366  					Refs: &v1.Refs{
  1367  						Repo: "foo",
  1368  						Pulls: []v1.Pull{
  1369  							{
  1370  								Number: 0,
  1371  							},
  1372  						},
  1373  					},
  1374  					Job:    "ci-foo",
  1375  					Report: true,
  1376  				},
  1377  			},
  1378  			existingPJs: []*v1.ProwJob{
  1379  				{
  1380  					ObjectMeta: metav1.ObjectMeta{
  1381  						Labels: map[string]string{
  1382  							kube.GerritRevision:    "abc",
  1383  							kube.ProwJobTypeLabel:  presubmit,
  1384  							kube.GerritReportLabel: "label-foo",
  1385  							kube.OrgLabel:          "org",
  1386  							kube.RepoLabel:         "repo",
  1387  							kube.PullLabel:         "0",
  1388  						},
  1389  						Annotations: map[string]string{
  1390  							kube.GerritID:       "123-abc",
  1391  							kube.GerritInstance: "gerrit",
  1392  						},
  1393  						CreationTimestamp: metav1.Time{
  1394  							Time: timeNow.Add(-time.Minute),
  1395  						},
  1396  						Name:      "ci-foo",
  1397  						Namespace: "test-pods",
  1398  					},
  1399  					Status: v1.ProwJobStatus{
  1400  						PrevReportStates: map[string]v1.ProwJobState{
  1401  							"gerrit-reporter": v1.FailureState,
  1402  						},
  1403  						State: v1.FailureState,
  1404  						URL:   "guber/foo",
  1405  					},
  1406  					Spec: v1.ProwJobSpec{
  1407  						Refs: &v1.Refs{
  1408  							Repo: "foo",
  1409  							Pulls: []v1.Pull{
  1410  								{
  1411  									Number: 0,
  1412  								},
  1413  							},
  1414  						},
  1415  						Job:    "ci-foo",
  1416  						Report: true,
  1417  					},
  1418  				},
  1419  			},
  1420  		},
  1421  		{
  1422  			name: "2 jobs, one SUCCESS one pending, different label, should report by itself",
  1423  			pj: &v1.ProwJob{
  1424  				ObjectMeta: metav1.ObjectMeta{
  1425  					Labels: map[string]string{
  1426  						kube.GerritRevision:    "abc",
  1427  						kube.ProwJobTypeLabel:  presubmit,
  1428  						kube.GerritReportLabel: "label-foo",
  1429  					},
  1430  					Annotations: map[string]string{
  1431  						kube.GerritID:       "123-abc",
  1432  						kube.GerritInstance: "gerrit",
  1433  					},
  1434  					Name:      "ci-foo",
  1435  					Namespace: "test-pods",
  1436  				},
  1437  				Status: v1.ProwJobStatus{
  1438  					State: v1.SuccessState,
  1439  					URL:   "guber/foo",
  1440  				},
  1441  				Spec: v1.ProwJobSpec{
  1442  					Refs: &v1.Refs{
  1443  						Repo: "foo",
  1444  						Pulls: []v1.Pull{
  1445  							{
  1446  								Number: 0,
  1447  							},
  1448  						},
  1449  					},
  1450  					Job:    "ci-foo",
  1451  					Report: true,
  1452  				},
  1453  			},
  1454  			existingPJs: []*v1.ProwJob{
  1455  				{
  1456  					ObjectMeta: metav1.ObjectMeta{
  1457  						Labels: map[string]string{
  1458  							kube.GerritRevision:    "abc",
  1459  							kube.ProwJobTypeLabel:  presubmit,
  1460  							kube.GerritReportLabel: "label-bar",
  1461  						},
  1462  						Annotations: map[string]string{
  1463  							kube.GerritID:       "123-abc",
  1464  							kube.GerritInstance: "gerrit",
  1465  						},
  1466  						Name:      "ci-bar",
  1467  						Namespace: "test-pods",
  1468  					},
  1469  					Status: v1.ProwJobStatus{
  1470  						State: v1.PendingState,
  1471  						URL:   "guber/bar",
  1472  					},
  1473  					Spec: v1.ProwJobSpec{
  1474  						Refs: &v1.Refs{
  1475  							Repo: "bar",
  1476  							Pulls: []v1.Pull{
  1477  								{
  1478  									Number: 0,
  1479  								},
  1480  							},
  1481  						},
  1482  						Job:    "ci-bar",
  1483  						Report: true,
  1484  					},
  1485  				},
  1486  			},
  1487  			expectReport:      true,
  1488  			reportInclude:     []string{"1 out of 1", "ci-foo", "SUCCESS", "guber/foo"},
  1489  			expectLabel:       map[string]string{"label-foo": lgtm},
  1490  			numExpectedReport: 0,
  1491  		},
  1492  		{
  1493  			name: "2 jobs, both failed, already reported, same label, retrigger one and passed, should report both and not lgtm",
  1494  			pj: &v1.ProwJob{
  1495  				ObjectMeta: metav1.ObjectMeta{
  1496  					Labels: map[string]string{
  1497  						kube.GerritRevision:    "abc",
  1498  						kube.ProwJobTypeLabel:  presubmit,
  1499  						kube.GerritReportLabel: "same-label",
  1500  					},
  1501  					Annotations: map[string]string{
  1502  						kube.GerritID:       "123-abc",
  1503  						kube.GerritInstance: "gerrit",
  1504  					},
  1505  					CreationTimestamp: metav1.Time{
  1506  						Time: timeNow,
  1507  					},
  1508  					Name:      "ci-foo",
  1509  					Namespace: "test-pods",
  1510  				},
  1511  				Status: v1.ProwJobStatus{
  1512  					State: v1.SuccessState,
  1513  					URL:   "guber/foo",
  1514  				},
  1515  				Spec: v1.ProwJobSpec{
  1516  					Type: v1.PresubmitJob,
  1517  					Refs: &v1.Refs{
  1518  						Repo: "foo",
  1519  						Pulls: []v1.Pull{
  1520  							{
  1521  								Number: 0,
  1522  							},
  1523  						},
  1524  					},
  1525  					Job:    "ci-foo",
  1526  					Report: true,
  1527  				},
  1528  			},
  1529  			existingPJs: []*v1.ProwJob{
  1530  				{
  1531  					ObjectMeta: metav1.ObjectMeta{
  1532  						Labels: map[string]string{
  1533  							kube.GerritRevision:    "abc",
  1534  							kube.ProwJobTypeLabel:  presubmit,
  1535  							kube.GerritReportLabel: "same-label",
  1536  						},
  1537  						Annotations: map[string]string{
  1538  							kube.GerritID:       "123-abc",
  1539  							kube.GerritInstance: "gerrit",
  1540  						},
  1541  						CreationTimestamp: metav1.Time{
  1542  							Time: timeNow.Add(-time.Hour),
  1543  						},
  1544  						Name:      "ci-bar",
  1545  						Namespace: "test-pods",
  1546  					},
  1547  					Status: v1.ProwJobStatus{
  1548  						State: v1.FailureState,
  1549  						URL:   "guber/bar",
  1550  						PrevReportStates: map[string]v1.ProwJobState{
  1551  							"gerrit-reporter": v1.FailureState,
  1552  						},
  1553  					},
  1554  					Spec: v1.ProwJobSpec{
  1555  						Refs: &v1.Refs{
  1556  							Repo: "bar",
  1557  							Pulls: []v1.Pull{
  1558  								{
  1559  									Number: 0,
  1560  								},
  1561  							},
  1562  						},
  1563  						Job:    "ci-bar",
  1564  						Type:   v1.PresubmitJob,
  1565  						Report: true,
  1566  					},
  1567  				},
  1568  				{
  1569  					ObjectMeta: metav1.ObjectMeta{
  1570  						Labels: map[string]string{
  1571  							kube.GerritRevision:    "abc",
  1572  							kube.ProwJobTypeLabel:  presubmit,
  1573  							kube.GerritReportLabel: "same-label",
  1574  						},
  1575  						Annotations: map[string]string{
  1576  							kube.GerritID:       "123-abc",
  1577  							kube.GerritInstance: "gerrit",
  1578  						},
  1579  						CreationTimestamp: metav1.Time{
  1580  							Time: timeNow.Add(-time.Hour),
  1581  						},
  1582  						Name:      "ci-foo",
  1583  						Namespace: "test-pods",
  1584  					},
  1585  					Status: v1.ProwJobStatus{
  1586  						State: v1.FailureState,
  1587  						URL:   "guber/foo",
  1588  						PrevReportStates: map[string]v1.ProwJobState{
  1589  							"gerrit-reporter": v1.FailureState,
  1590  						},
  1591  					},
  1592  					Spec: v1.ProwJobSpec{
  1593  						Refs: &v1.Refs{
  1594  							Repo: "foo",
  1595  							Pulls: []v1.Pull{
  1596  								{
  1597  									Number: 0,
  1598  								},
  1599  							},
  1600  						},
  1601  						Job:    "ci-foo",
  1602  						Type:   v1.PresubmitJob,
  1603  						Report: true,
  1604  					},
  1605  				},
  1606  			},
  1607  			expectReport:      true,
  1608  			reportInclude:     []string{"1 out of 2", "ci-foo", "SUCCESS", "ci-bar", "FAILURE", "guber/foo", "guber/bar", "Comment `/retest`"},
  1609  			expectLabel:       map[string]string{"same-label": lbtm},
  1610  			numExpectedReport: 0,
  1611  		},
  1612  		{
  1613  			name: "2 jobs, both failed, job from newer patchset pending, should not report",
  1614  			pj: &v1.ProwJob{
  1615  				ObjectMeta: metav1.ObjectMeta{
  1616  					Labels: map[string]string{
  1617  						kube.GerritRevision:    "abc",
  1618  						kube.ProwJobTypeLabel:  presubmit,
  1619  						kube.GerritReportLabel: "same-label",
  1620  						kube.GerritPatchset:    "5",
  1621  						kube.OrgLabel:          "same-org",
  1622  						kube.RepoLabel:         "same-repo",
  1623  						kube.PullLabel:         "123456",
  1624  					},
  1625  					Annotations: map[string]string{
  1626  						kube.GerritID:       "123-abc",
  1627  						kube.GerritInstance: "gerrit",
  1628  					},
  1629  					CreationTimestamp: metav1.Time{
  1630  						Time: timeNow,
  1631  					},
  1632  				},
  1633  				Status: v1.ProwJobStatus{
  1634  					State: v1.SuccessState,
  1635  					URL:   "guber/foo",
  1636  				},
  1637  				Spec: v1.ProwJobSpec{
  1638  					Type: v1.PresubmitJob,
  1639  					Refs: &v1.Refs{
  1640  						Repo: "foo",
  1641  						Pulls: []v1.Pull{
  1642  							{
  1643  								Number: 0,
  1644  							},
  1645  						},
  1646  					},
  1647  					Job:    "ci-foo",
  1648  					Report: true,
  1649  				},
  1650  			},
  1651  			existingPJs: []*v1.ProwJob{
  1652  				{
  1653  					ObjectMeta: metav1.ObjectMeta{
  1654  						Labels: map[string]string{
  1655  							kube.GerritRevision:    "abc",
  1656  							kube.ProwJobTypeLabel:  presubmit,
  1657  							kube.GerritReportLabel: "same-label",
  1658  							kube.GerritPatchset:    "5",
  1659  							kube.OrgLabel:          "same-org",
  1660  							kube.RepoLabel:         "same-repo",
  1661  							kube.PullLabel:         "123456",
  1662  						},
  1663  						Annotations: map[string]string{
  1664  							kube.GerritID:       "123-abc",
  1665  							kube.GerritInstance: "gerrit",
  1666  						},
  1667  						CreationTimestamp: metav1.Time{
  1668  							Time: timeNow.Add(-time.Hour),
  1669  						},
  1670  					},
  1671  					Status: v1.ProwJobStatus{
  1672  						State: v1.FailureState,
  1673  						URL:   "guber/bar",
  1674  						PrevReportStates: map[string]v1.ProwJobState{
  1675  							"gerrit-reporter": v1.FailureState,
  1676  						},
  1677  					},
  1678  					Spec: v1.ProwJobSpec{
  1679  						Refs: &v1.Refs{
  1680  							Repo: "bar",
  1681  							Pulls: []v1.Pull{
  1682  								{
  1683  									Number: 0,
  1684  								},
  1685  							},
  1686  						},
  1687  						Job:    "ci-bar",
  1688  						Type:   v1.PresubmitJob,
  1689  						Report: true,
  1690  					},
  1691  				},
  1692  				{
  1693  					ObjectMeta: metav1.ObjectMeta{
  1694  						Labels: map[string]string{
  1695  							kube.GerritRevision:    "abc",
  1696  							kube.ProwJobTypeLabel:  presubmit,
  1697  							kube.GerritReportLabel: "same-label",
  1698  							kube.GerritPatchset:    "5",
  1699  							kube.OrgLabel:          "same-org",
  1700  							kube.RepoLabel:         "same-repo",
  1701  							kube.PullLabel:         "123456",
  1702  						},
  1703  						Annotations: map[string]string{
  1704  							kube.GerritID:       "123-abc",
  1705  							kube.GerritInstance: "gerrit",
  1706  						},
  1707  						CreationTimestamp: metav1.Time{
  1708  							Time: timeNow.Add(-time.Hour),
  1709  						},
  1710  					},
  1711  					Status: v1.ProwJobStatus{
  1712  						State: v1.FailureState,
  1713  						URL:   "guber/foo",
  1714  						PrevReportStates: map[string]v1.ProwJobState{
  1715  							"gerrit-reporter": v1.FailureState,
  1716  						},
  1717  					},
  1718  					Spec: v1.ProwJobSpec{
  1719  						Refs: &v1.Refs{
  1720  							Repo: "foo",
  1721  							Pulls: []v1.Pull{
  1722  								{
  1723  									Number: 0,
  1724  								},
  1725  							},
  1726  						},
  1727  						Job:    "ci-foo",
  1728  						Type:   v1.PresubmitJob,
  1729  						Report: true,
  1730  					},
  1731  				},
  1732  				{
  1733  					ObjectMeta: metav1.ObjectMeta{
  1734  						Labels: map[string]string{
  1735  							kube.GerritRevision:    "def",
  1736  							kube.ProwJobTypeLabel:  presubmit,
  1737  							kube.GerritReportLabel: "same-label",
  1738  							kube.GerritPatchset:    "6",
  1739  							kube.OrgLabel:          "same-org",
  1740  							kube.RepoLabel:         "same-repo",
  1741  							kube.PullLabel:         "123456",
  1742  						},
  1743  						Annotations: map[string]string{
  1744  							kube.GerritID:       "123-def",
  1745  							kube.GerritInstance: "gerrit",
  1746  						},
  1747  						CreationTimestamp: metav1.Time{
  1748  							Time: timeNow.Add(-time.Hour),
  1749  						},
  1750  					},
  1751  					Status: v1.ProwJobStatus{
  1752  						State: v1.PendingState,
  1753  						URL:   "guber/foo",
  1754  					},
  1755  					Spec: v1.ProwJobSpec{
  1756  						Refs: &v1.Refs{
  1757  							Repo: "foo",
  1758  							Pulls: []v1.Pull{
  1759  								{
  1760  									Number: 0,
  1761  								},
  1762  							},
  1763  						},
  1764  						Job:    "ci-foo",
  1765  						Type:   v1.PresubmitJob,
  1766  						Report: true,
  1767  					},
  1768  				},
  1769  			},
  1770  			expectReport: false,
  1771  		},
  1772  		{
  1773  			name: "2 jobs, both failed, job from newer patchset failed, should not report",
  1774  			pj: &v1.ProwJob{
  1775  				ObjectMeta: metav1.ObjectMeta{
  1776  					Labels: map[string]string{
  1777  						kube.GerritRevision:    "abc",
  1778  						kube.ProwJobTypeLabel:  presubmit,
  1779  						kube.GerritReportLabel: "same-label",
  1780  						kube.GerritPatchset:    "5",
  1781  						kube.OrgLabel:          "same-org",
  1782  						kube.RepoLabel:         "same-repo",
  1783  						kube.PullLabel:         "123456",
  1784  					},
  1785  					Annotations: map[string]string{
  1786  						kube.GerritID:       "123-abc",
  1787  						kube.GerritInstance: "gerrit",
  1788  					},
  1789  					CreationTimestamp: metav1.Time{
  1790  						Time: timeNow,
  1791  					},
  1792  				},
  1793  				Status: v1.ProwJobStatus{
  1794  					State: v1.SuccessState,
  1795  					URL:   "guber/foo",
  1796  				},
  1797  				Spec: v1.ProwJobSpec{
  1798  					Type: v1.PresubmitJob,
  1799  					Refs: &v1.Refs{
  1800  						Repo: "foo",
  1801  						Pulls: []v1.Pull{
  1802  							{
  1803  								Number: 0,
  1804  							},
  1805  						},
  1806  					},
  1807  					Job:    "ci-foo",
  1808  					Report: true,
  1809  				},
  1810  			},
  1811  			existingPJs: []*v1.ProwJob{
  1812  				{
  1813  					ObjectMeta: metav1.ObjectMeta{
  1814  						Labels: map[string]string{
  1815  							kube.GerritRevision:    "abc",
  1816  							kube.ProwJobTypeLabel:  presubmit,
  1817  							kube.GerritReportLabel: "same-label",
  1818  							kube.GerritPatchset:    "5",
  1819  							kube.OrgLabel:          "same-org",
  1820  							kube.RepoLabel:         "same-repo",
  1821  							kube.PullLabel:         "123456",
  1822  						},
  1823  						Annotations: map[string]string{
  1824  							kube.GerritID:       "123-abc",
  1825  							kube.GerritInstance: "gerrit",
  1826  						},
  1827  						CreationTimestamp: metav1.Time{
  1828  							Time: timeNow.Add(-time.Hour),
  1829  						},
  1830  					},
  1831  					Status: v1.ProwJobStatus{
  1832  						State: v1.FailureState,
  1833  						URL:   "guber/bar",
  1834  						PrevReportStates: map[string]v1.ProwJobState{
  1835  							"gerrit-reporter": v1.FailureState,
  1836  						},
  1837  					},
  1838  					Spec: v1.ProwJobSpec{
  1839  						Refs: &v1.Refs{
  1840  							Repo: "bar",
  1841  							Pulls: []v1.Pull{
  1842  								{
  1843  									Number: 0,
  1844  								},
  1845  							},
  1846  						},
  1847  						Job:    "ci-bar",
  1848  						Type:   v1.PresubmitJob,
  1849  						Report: true,
  1850  					},
  1851  				},
  1852  				{
  1853  					ObjectMeta: metav1.ObjectMeta{
  1854  						Labels: map[string]string{
  1855  							kube.GerritRevision:    "abc",
  1856  							kube.ProwJobTypeLabel:  presubmit,
  1857  							kube.GerritReportLabel: "same-label",
  1858  							kube.GerritPatchset:    "5",
  1859  							kube.OrgLabel:          "same-org",
  1860  							kube.RepoLabel:         "same-repo",
  1861  							kube.PullLabel:         "123456",
  1862  						},
  1863  						Annotations: map[string]string{
  1864  							kube.GerritID:       "123-abc",
  1865  							kube.GerritInstance: "gerrit",
  1866  						},
  1867  						CreationTimestamp: metav1.Time{
  1868  							Time: timeNow.Add(-time.Hour),
  1869  						},
  1870  					},
  1871  					Status: v1.ProwJobStatus{
  1872  						State: v1.FailureState,
  1873  						URL:   "guber/foo",
  1874  						PrevReportStates: map[string]v1.ProwJobState{
  1875  							"gerrit-reporter": v1.FailureState,
  1876  						},
  1877  					},
  1878  					Spec: v1.ProwJobSpec{
  1879  						Refs: &v1.Refs{
  1880  							Repo: "foo",
  1881  							Pulls: []v1.Pull{
  1882  								{
  1883  									Number: 0,
  1884  								},
  1885  							},
  1886  						},
  1887  						Job:    "ci-foo",
  1888  						Type:   v1.PresubmitJob,
  1889  						Report: true,
  1890  					},
  1891  				},
  1892  				{
  1893  					ObjectMeta: metav1.ObjectMeta{
  1894  						Labels: map[string]string{
  1895  							kube.GerritRevision:    "def",
  1896  							kube.ProwJobTypeLabel:  presubmit,
  1897  							kube.GerritReportLabel: "same-label",
  1898  							kube.GerritPatchset:    "6",
  1899  							kube.OrgLabel:          "same-org",
  1900  							kube.RepoLabel:         "same-repo",
  1901  							kube.PullLabel:         "123456",
  1902  						},
  1903  						Annotations: map[string]string{
  1904  							kube.GerritID:       "123-def",
  1905  							kube.GerritInstance: "gerrit",
  1906  						},
  1907  						CreationTimestamp: metav1.Time{
  1908  							Time: timeNow.Add(-time.Hour),
  1909  						},
  1910  					},
  1911  					Status: v1.ProwJobStatus{
  1912  						State: v1.FailureState,
  1913  						URL:   "guber/foo",
  1914  					},
  1915  					Spec: v1.ProwJobSpec{
  1916  						Refs: &v1.Refs{
  1917  							Repo: "foo",
  1918  							Pulls: []v1.Pull{
  1919  								{
  1920  									Number: 0,
  1921  								},
  1922  							},
  1923  						},
  1924  						Job:    "ci-foo",
  1925  						Type:   v1.PresubmitJob,
  1926  						Report: true,
  1927  					},
  1928  				},
  1929  			},
  1930  			expectReport: false,
  1931  		},
  1932  		{
  1933  			name: "1 job, failed after merge, should report with non negative vote",
  1934  			pj: &v1.ProwJob{
  1935  				ObjectMeta: metav1.ObjectMeta{
  1936  					Labels: map[string]string{
  1937  						kube.GerritRevision:    "abc",
  1938  						kube.ProwJobTypeLabel:  presubmit,
  1939  						kube.GerritReportLabel: "Code-Review",
  1940  					},
  1941  					Annotations: map[string]string{
  1942  						kube.GerritID:       "merged",
  1943  						kube.GerritInstance: "gerrit",
  1944  					},
  1945  					Name:      "ci-foo",
  1946  					Namespace: "test-pods",
  1947  				},
  1948  				Status: v1.ProwJobStatus{
  1949  					State: v1.FailureState,
  1950  					URL:   "guber/foo",
  1951  				},
  1952  				Spec: v1.ProwJobSpec{
  1953  					Type: v1.PresubmitJob,
  1954  					Refs: &v1.Refs{
  1955  						Repo: "foo",
  1956  						Pulls: []v1.Pull{
  1957  							{
  1958  								Number: 0,
  1959  							},
  1960  						},
  1961  					},
  1962  					Job:    "ci-foo",
  1963  					Report: true,
  1964  				},
  1965  			},
  1966  			expectReport:      true,
  1967  			reportInclude:     []string{"0 out of 1", "ci-foo", "FAILURE", "guber/foo", "Comment `/retest`"},
  1968  			expectLabel:       map[string]string{codeReview: lztm},
  1969  			numExpectedReport: 0,
  1970  		},
  1971  		{
  1972  			name: "1 job, passed, should vote +1 even after merge",
  1973  			pj: &v1.ProwJob{
  1974  				ObjectMeta: metav1.ObjectMeta{
  1975  					Labels: map[string]string{
  1976  						kube.GerritRevision:    "abc",
  1977  						kube.ProwJobTypeLabel:  presubmit,
  1978  						kube.GerritReportLabel: "Code-Review",
  1979  					},
  1980  					Annotations: map[string]string{
  1981  						kube.GerritID:       "merged",
  1982  						kube.GerritInstance: "gerrit",
  1983  					},
  1984  					Name:      "ci-foo",
  1985  					Namespace: "test-pods",
  1986  				},
  1987  				Status: v1.ProwJobStatus{
  1988  					State: v1.SuccessState,
  1989  					URL:   "guber/foo",
  1990  				},
  1991  				Spec: v1.ProwJobSpec{
  1992  					Refs: &v1.Refs{
  1993  						Repo: "foo",
  1994  						Pulls: []v1.Pull{
  1995  							{
  1996  								Number: 0,
  1997  							},
  1998  						},
  1999  					},
  2000  					Job:    "ci-foo",
  2001  					Report: true,
  2002  				},
  2003  			},
  2004  			expectReport:      true,
  2005  			reportInclude:     []string{"1 out of 1", "ci-foo", "SUCCESS", "guber/foo"},
  2006  			expectLabel:       map[string]string{codeReview: lgtm},
  2007  			numExpectedReport: 0,
  2008  		},
  2009  	}
  2010  
  2011  	for _, tc := range testcases {
  2012  		t.Run(tc.name, func(t *testing.T) {
  2013  			fgc := &fgc{instance: "gerrit", changes: changes}
  2014  
  2015  			builder := fakectrlruntimeclient.NewClientBuilder().WithRuntimeObjects(tc.pj)
  2016  			for idx, pj := range tc.existingPJs {
  2017  				pj.Name = strconv.Itoa(idx)
  2018  				builder.WithRuntimeObjects(pj)
  2019  			}
  2020  
  2021  			reporter := &Client{
  2022  				gc:          fgc,
  2023  				pjclientset: builder.Build(),
  2024  				prLocks:     criercommonlib.NewShardedLock(),
  2025  			}
  2026  
  2027  			shouldReport := reporter.ShouldReport(context.Background(), logrus.NewEntry(logrus.StandardLogger()), tc.pj)
  2028  			if shouldReport != tc.expectReport {
  2029  				t.Errorf("shouldReport: %v, expectReport: %v", shouldReport, tc.expectReport)
  2030  			}
  2031  
  2032  			if !shouldReport {
  2033  				return
  2034  			}
  2035  
  2036  			reportedJobs, _, err := reporter.Report(context.Background(), logrus.NewEntry(logrus.StandardLogger()), tc.pj)
  2037  			if err != nil {
  2038  				if !tc.expectError {
  2039  					t.Errorf("Unexpected error: %v", err)
  2040  				}
  2041  				// if this error is expected then no need to verify anything
  2042  				// later
  2043  				return
  2044  			}
  2045  
  2046  			for _, include := range tc.reportInclude {
  2047  				if !strings.Contains(fgc.reportMessage, include) {
  2048  					t.Errorf("message: got %q, does not contain %s", fgc.reportMessage, include)
  2049  				}
  2050  			}
  2051  			for _, exclude := range tc.reportExclude {
  2052  				if strings.Contains(fgc.reportMessage, exclude) {
  2053  					t.Errorf("message: got %q, unexpectedly contains %s", fgc.reportMessage, exclude)
  2054  				}
  2055  			}
  2056  
  2057  			if !reflect.DeepEqual(tc.expectLabel, fgc.reportLabel) {
  2058  				t.Errorf("labels: got %v, want %v", fgc.reportLabel, tc.expectLabel)
  2059  			}
  2060  			if len(reportedJobs) != tc.numExpectedReport {
  2061  				t.Errorf("report count: got %d, want %d", len(reportedJobs), tc.numExpectedReport)
  2062  			}
  2063  		})
  2064  	}
  2065  }
  2066  
  2067  func TestMultipleWorks(t *testing.T) {
  2068  	samplePJ := v1.ProwJob{
  2069  		ObjectMeta: metav1.ObjectMeta{
  2070  			Labels: map[string]string{
  2071  				kube.GerritRevision:    "abc",
  2072  				kube.ProwJobTypeLabel:  presubmit,
  2073  				kube.GerritReportLabel: "same-label",
  2074  				kube.GerritPatchset:    "5",
  2075  				kube.OrgLabel:          "same-org",
  2076  				kube.RepoLabel:         "same-repo",
  2077  				kube.PullLabel:         "123456",
  2078  			},
  2079  			Annotations: map[string]string{
  2080  				kube.GerritID:       "123-abc",
  2081  				kube.GerritInstance: "gerrit",
  2082  			},
  2083  			CreationTimestamp: metav1.Time{
  2084  				Time: timeNow.Add(-time.Hour),
  2085  			},
  2086  		},
  2087  		Status: v1.ProwJobStatus{
  2088  			State: v1.FailureState,
  2089  			URL:   "guber/bar",
  2090  		},
  2091  		Spec: v1.ProwJobSpec{
  2092  			Refs: &v1.Refs{
  2093  				Repo: "bar",
  2094  				Pulls: []v1.Pull{
  2095  					{
  2096  						Number: 0,
  2097  					},
  2098  				},
  2099  			},
  2100  			Job:    "ci-bar",
  2101  			Type:   v1.PresubmitJob,
  2102  			Report: true,
  2103  		},
  2104  	}
  2105  
  2106  	// Running with 3 different batches to increase the chance of hitting races
  2107  	for _, count := range []int{10, 20, 30} {
  2108  		t.Run(fmt.Sprintf("%d-jobs", count), func(t *testing.T) {
  2109  			expectedCount := 1
  2110  			expectedComment := []string{" out of " + strconv.Itoa(count), "ci-bar", "FAILURE", "guber/bar", "Comment `/retest`"}
  2111  			var existingPJs []*v1.ProwJob
  2112  			for i := 0; i < count; i++ {
  2113  				pj := samplePJ.DeepCopy()
  2114  				pj.Spec.Job += strconv.Itoa(i)
  2115  				if i%2 == 0 {
  2116  					pj.Status.State = v1.SuccessState
  2117  				}
  2118  				existingPJs = append(existingPJs, pj)
  2119  			}
  2120  
  2121  			changes := map[string][]*gerrit.ChangeInfo{
  2122  				"gerrit": {
  2123  					{ID: "123-abc", Status: "NEW", Revisions: map[string]gerrit.RevisionInfo{"abc": {}}},
  2124  				},
  2125  			}
  2126  
  2127  			fgc := &fgc{instance: "gerrit", changes: changes}
  2128  
  2129  			builder := fakectrlruntimeclient.NewClientBuilder()
  2130  			for idx, pj := range existingPJs {
  2131  				pj.Name = strconv.Itoa(idx)
  2132  				builder.WithRuntimeObjects(pj)
  2133  			}
  2134  
  2135  			reporter := &Client{
  2136  				gc:          fgc,
  2137  				pjclientset: builder.Build(),
  2138  				prLocks:     criercommonlib.NewShardedLock(),
  2139  			}
  2140  
  2141  			g := new(errgroup.Group)
  2142  			resChan := make(chan []*v1.ProwJob, count)
  2143  			for _, pj := range existingPJs {
  2144  				pj := pj.DeepCopy()
  2145  				g.Go(func() error {
  2146  					toReportJobs, _, err := reporter.Report(context.Background(), logrus.NewEntry(logrus.StandardLogger()), pj)
  2147  					if err != nil {
  2148  						return err
  2149  					}
  2150  					resChan <- toReportJobs
  2151  					return nil
  2152  				})
  2153  			}
  2154  
  2155  			if err := g.Wait(); err != nil {
  2156  				t.Fatalf("Unexpected error: %v", err)
  2157  			}
  2158  			if expectedCount != fgc.count {
  2159  				t.Fatalf("Expect comment count: %d, got: %d", expectedCount, fgc.count)
  2160  			}
  2161  			for _, expect := range expectedComment {
  2162  				if !strings.Contains(fgc.reportMessage, expect) {
  2163  					t.Fatalf("Expect comment contains %q, got: %q", expect, fgc.reportMessage)
  2164  				}
  2165  			}
  2166  
  2167  			var reported bool
  2168  			for i := 0; i < count; i++ {
  2169  				toReportJobs := <-resChan
  2170  				if reported && len(toReportJobs) > 0 {
  2171  					t.Fatalf("These jobs were already reported, should omit reporting again.")
  2172  				}
  2173  				if len(toReportJobs) > 0 {
  2174  					reported = true
  2175  				}
  2176  			}
  2177  
  2178  			// Ensure that the statues were reported
  2179  			var pjs v1.ProwJobList
  2180  			if err := reporter.pjclientset.List(context.Background(), &pjs); err != nil {
  2181  				t.Fatalf("Failed listing prowjobs: %v", err)
  2182  			}
  2183  			if want, got := count, len(pjs.Items); want != got {
  2184  				t.Fatalf("Number of prowjobs mismatch. Want: %d, got: %d", want, got)
  2185  			}
  2186  			for _, pj := range pjs.Items {
  2187  				if pj.Status.PrevReportStates == nil {
  2188  					t.Fatalf("PrevReportStates should have been set")
  2189  				}
  2190  				if _, ok := pj.Status.PrevReportStates["gerrit-reporter"]; !ok {
  2191  					t.Fatalf("PrevReportStates should have been set. Got: %v", pj.Status.PrevReportStates)
  2192  				}
  2193  			}
  2194  		})
  2195  	}
  2196  }
  2197  
  2198  func TestJobReportFormats(t *testing.T) {
  2199  	tests := []struct {
  2200  		name        string
  2201  		format      string
  2202  		words       []interface{}
  2203  		formatRegex string
  2204  	}{
  2205  		{"jobReportFormat", jobReportFormat, []interface{}{"a", "b", "c", "d"}, jobReportFormatRegex},
  2206  		{"jobReportFormatUrlNotFound", jobReportFormatUrlNotFound, []interface{}{"a", "b", "c"}, jobReportFormatUrlNotFoundRegex},
  2207  		{"jobReportFormatWithoutURL", jobReportFormatWithoutURL, []interface{}{"a", "b", "c"}, jobReportFormatWithoutURLRegex},
  2208  	}
  2209  
  2210  	for _, tc := range tests {
  2211  		t.Run(tc.name, func(t *testing.T) {
  2212  			// In GenerateReport(), we use a trailing newline in the
  2213  			// jobReportFormat* constants, because we use a newline as a
  2214  			// delimiter. In ParseReport(), we split the overall report on
  2215  			// newlines first, before applying the jobReportFormat*Regex
  2216  			// regexes on them. To mimic this behavior, we trim the newline
  2217  			// before attempting to parse them with tc.formatRegex.
  2218  			serialized := fmt.Sprintf(tc.format, tc.words...)
  2219  			serializedWithoutNewline := strings.TrimSuffix(serialized, "\n")
  2220  			re := regexp.MustCompile(tc.formatRegex)
  2221  			if !re.MatchString(serializedWithoutNewline) {
  2222  				t.Fatalf("could not parse serialized job report line %q with regex %q", serializedWithoutNewline, tc.formatRegex)
  2223  			}
  2224  		})
  2225  	}
  2226  
  2227  	// Ensure the legacy job reporting format can be parsed by
  2228  	// jobReportFormatLegacyRegex.
  2229  	serializedWithoutNewline := "āœ”ļø some-job SUCCESS - https://someURL.com/somewhere"
  2230  	re := regexp.MustCompile(jobReportFormatLegacyRegex)
  2231  	if !re.MatchString(serializedWithoutNewline) {
  2232  		t.Fatalf("could not parse serialized job report line %q with regex %q", serializedWithoutNewline, jobReportFormatLegacyRegex)
  2233  	}
  2234  }
  2235  
  2236  func TestGenerateReport(t *testing.T) {
  2237  	job := func(name, url string, state v1.ProwJobState, createdByTide bool) *v1.ProwJob {
  2238  		var out v1.ProwJob
  2239  		out.Spec.Job = name
  2240  		out.Status.URL = url
  2241  		out.Status.State = state
  2242  		out.Labels = make(map[string]string)
  2243  		if createdByTide {
  2244  			out.Labels[kube.CreatedByTideLabel] = "true"
  2245  		}
  2246  		return &out
  2247  	}
  2248  
  2249  	tests := []struct {
  2250  		name             string
  2251  		jobs             []*v1.ProwJob
  2252  		commentSizeLimit int
  2253  		wantHeader       string
  2254  		wantMessage      string
  2255  	}{
  2256  		{
  2257  			name: "basic",
  2258  			jobs: []*v1.ProwJob{
  2259  				job("this", "url", v1.SuccessState, false),
  2260  				job("that", "hey", v1.FailureState, false),
  2261  				job("left", "foo", v1.AbortedState, false),
  2262  				job("right", "bar", v1.ErrorState, false),
  2263  			},
  2264  			wantHeader:  "Prow Status: 1 out of 4 pjs passed! šŸ‘‰ Comment `/retest` to rerun only failed tests (if any), or `/test all` to rerun all tests.\n",
  2265  			wantMessage: "āŒ [that](hey) FAILURE\n🚫 [right](bar) ERROR\n🚫 [left](foo) ABORTED\nāœ”ļø [this](url) SUCCESS\n",
  2266  		},
  2267  		{
  2268  			name: "include-tide-jobs",
  2269  			jobs: []*v1.ProwJob{
  2270  				job("this", "url", v1.SuccessState, true),
  2271  				job("that", "hey", v1.FailureState, false),
  2272  				job("left", "foo", v1.AbortedState, false),
  2273  				job("right", "bar", v1.ErrorState, false),
  2274  			},
  2275  			wantHeader:  "Prow Status: 1 out of 4 pjs passed! šŸ‘‰ Comment `/retest` to rerun only failed tests (if any), or `/test all` to rerun all tests. (Not a duplicated report. Some of the jobs below were triggered by Tide)\n",
  2276  			wantMessage: "āŒ [that](hey) FAILURE\n🚫 [right](bar) ERROR\n🚫 [left](foo) ABORTED\nāœ”ļø [this](url) SUCCESS\n",
  2277  		},
  2278  		{
  2279  			name: "short lines only",
  2280  			jobs: []*v1.ProwJob{
  2281  				job("this", "url", v1.SuccessState, false),
  2282  				job("that", "hey", v1.FailureState, false),
  2283  				job("some", "other", v1.SuccessState, false),
  2284  			},
  2285  			// 131 is the length of the Header.
  2286  			// 154 is the comment size room we give for the Message part. Note
  2287  			// that it should be 1 char more than what we have in the
  2288  			// wantMessage part, because we always return comments *under* the
  2289  			// commentSizeLimit.
  2290  			commentSizeLimit: 131 + 154,
  2291  			wantHeader:       "Prow Status: 2 out of 3 pjs passed! šŸ‘‰ Comment `/retest` to rerun only failed tests (if any), or `/test all` to rerun all tests.\n",
  2292  			wantMessage:      "āŒ that FAILURE\nāœ”ļø some SUCCESS\nāœ”ļø this SUCCESS\n[NOTE FROM PROW: Skipped displaying URLs for 3/3 jobs due to reaching gerrit comment size limit]",
  2293  		},
  2294  		{
  2295  			name: "mix of short and long lines",
  2296  			jobs: []*v1.ProwJob{
  2297  				job("this", "url", v1.SuccessState, false),
  2298  				job("that", "hey", v1.FailureState, false),
  2299  				job("some", "other", v1.SuccessState, false),
  2300  			},
  2301  			commentSizeLimit: 131 + 161,
  2302  			wantHeader:       "Prow Status: 2 out of 3 pjs passed! šŸ‘‰ Comment `/retest` to rerun only failed tests (if any), or `/test all` to rerun all tests.\n",
  2303  			wantMessage:      "āŒ [that](hey) FAILURE\nāœ”ļø some SUCCESS\nāœ”ļø this SUCCESS\n[NOTE FROM PROW: Skipped displaying URLs for 2/3 jobs due to reaching gerrit comment size limit]",
  2304  		},
  2305  		{
  2306  			name: "too many jobs",
  2307  			jobs: []*v1.ProwJob{
  2308  				job("this", "url", v1.SuccessState, false),
  2309  				job("that", "hey", v1.FailureState, false),
  2310  				job("some", "other", v1.SuccessState, false),
  2311  			},
  2312  			commentSizeLimit: 1,
  2313  			wantHeader:       "Prow Status: 2 out of 3 pjs passed! šŸ‘‰ Comment `/retest` to rerun only failed tests (if any), or `/test all` to rerun all tests.\n",
  2314  			wantMessage:      "[NOTE FROM PROW: Skipped displaying 3/3 jobs due to reaching gerrit comment size limit (too many jobs)]",
  2315  		},
  2316  		{
  2317  			name: "too many jobs; only truncate the last job",
  2318  			jobs: []*v1.ProwJob{
  2319  				job("this", "url", v1.SuccessState, false),
  2320  				job("that", "hey", v1.FailureState, false),
  2321  				job("some", "other", v1.SuccessState, false),
  2322  			},
  2323  			commentSizeLimit: 130 + 150,
  2324  			wantHeader:       "Prow Status: 2 out of 3 pjs passed! šŸ‘‰ Comment `/retest` to rerun only failed tests (if any), or `/test all` to rerun all tests.\n",
  2325  			wantMessage:      "āŒ that FAILURE\nāœ”ļø some SUCCESS\n[NOTE FROM PROW: Skipped displaying 1/3 jobs due to reaching gerrit comment size limit (too many jobs)]",
  2326  		},
  2327  		{
  2328  			// Check cases where the job could legitimately not have its URL
  2329  			// field set (because the job did not even get scheduled).
  2330  			name: "missing URLs",
  2331  			jobs: []*v1.ProwJob{
  2332  				job("right", "", v1.ErrorState, false),
  2333  			},
  2334  			commentSizeLimit: 1000,
  2335  			wantHeader:       "Prow Status: 0 out of 1 pjs passed! šŸ‘‰ Comment `/retest` to rerun only failed tests (if any), or `/test all` to rerun all tests.\n",
  2336  			wantMessage:      "🚫 right (URL_NOT_FOUND) ERROR\n",
  2337  		},
  2338  	}
  2339  
  2340  	for _, tc := range tests {
  2341  		t.Run(tc.name, func(t *testing.T) {
  2342  			gotReport := GenerateReport(tc.jobs, tc.commentSizeLimit)
  2343  			if want, got := tc.wantHeader, gotReport.Header; want != got {
  2344  				t.Fatalf("Header mismatch. Want:\n%s,\ngot: \n%s", want, got)
  2345  			}
  2346  			if want, got := tc.wantMessage, gotReport.Message; want != got {
  2347  				t.Fatalf("Message mismatch. Want:\n%s\ngot: \n%s", want, got)
  2348  			}
  2349  		})
  2350  	}
  2351  }
  2352  
  2353  func TestParseReport(t *testing.T) {
  2354  	var testcases = []struct {
  2355  		name         string
  2356  		comment      string
  2357  		expectedJobs int
  2358  		expectNil    bool
  2359  	}{
  2360  		// These tests all test the legacy format.
  2361  		{
  2362  			name:         "parse multiple jobs",
  2363  			comment:      "Prow Status: 0 out of 2 passed\nāŒļø foo-job FAILURE - http://foo-status\nāŒ bar-job FAILURE - http://bar-status",
  2364  			expectedJobs: 2,
  2365  		},
  2366  		{
  2367  			name:         "parse job without URL",
  2368  			comment:      "Prow Status: 0 out of 2 passed\nāŒļø foo-job FAILURE\nāŒ bar-job FAILURE",
  2369  			expectedJobs: 2,
  2370  		},
  2371  		{
  2372  			name:         "parse mixed formats",
  2373  			comment:      "Prow Status: 0 out of 2 passed\nāŒļø foo-job FAILURE - http://foo-status\nāŒ bar-job FAILURE\n[Skipped displaying URLs for 1/2 jobs due to reaching gerrit comment size limit]",
  2374  			expectedJobs: 2,
  2375  		},
  2376  		{
  2377  			name:         "parse one job",
  2378  			comment:      "Prow Status: 0 out of 1 passed\nāŒ bar-job FAILURE - http://bar-status",
  2379  			expectedJobs: 1,
  2380  		},
  2381  		{
  2382  			name:         "parse 0 jobs",
  2383  			comment:      "Prow Status: ",
  2384  			expectedJobs: 0,
  2385  		},
  2386  		{
  2387  			name:      "do not parse without the header",
  2388  			comment:   "0 out of 1 passed\nāŒ bar-job FAILURE - http://bar-status",
  2389  			expectNil: true,
  2390  		},
  2391  		{
  2392  			name:      "do not parse empty string",
  2393  			comment:   "",
  2394  			expectNil: true,
  2395  		},
  2396  		{
  2397  			name: "parse with extra stuff at the start as long as the header and jobs start on new lines",
  2398  			comment: `qwerty
  2399  Patch Set 1:
  2400  Prow Status: 0 out of 2 pjs passed!
  2401  āŒ foo-job FAILURE - https://foo-status
  2402  āŒ bar-job FAILURE - https://bar-status
  2403  `,
  2404  			expectedJobs: 2,
  2405  		},
  2406  		// New Markdown format (link uses Markdown syntax).
  2407  		{
  2408  			name:         "parse multiple jobs (Markdown)",
  2409  			comment:      "Prow Status: 0 out of 2 passed\nāŒļø [foo-job](http://foo-status) FAILURE\nāŒ [bar-job](http://bar-status) FAILURE",
  2410  			expectedJobs: 2,
  2411  		},
  2412  		{
  2413  			name:         "parse mixed formats (Markdown)",
  2414  			comment:      "Prow Status: 0 out of 2 passed\nāŒļø [foo-job](http://foo-status) FAILURE\nāŒ bar-job FAILURE\n[Skipped displaying URLs for 1/2 jobs due to reaching gerrit comment size limit]",
  2415  			expectedJobs: 2,
  2416  		},
  2417  		{
  2418  			name:         "parse one job (Markdown)",
  2419  			comment:      "Prow Status: 0 out of 1 passed\nāŒ [bar-job](http://bar-status) FAILURE",
  2420  			expectedJobs: 1,
  2421  		},
  2422  		{
  2423  			name:      "do not parse without the header (Markdown)",
  2424  			comment:   "0 out of 1 passed\nāŒ [bar-job](http://bar-status) FAILURE",
  2425  			expectNil: true,
  2426  		},
  2427  		{
  2428  			name: "parse with extra stuff at the start as long as the header and jobs start on new lines (Markdown)",
  2429  			comment: `qwerty
  2430  Patch Set 1:
  2431  Prow Status: 0 out of 2 pjs passed!
  2432  āŒ [foo-job](https://foo-status) FAILURE
  2433  āŒ [bar-job](https://bar-status) FAILURE
  2434  `,
  2435  			expectedJobs: 2,
  2436  		},
  2437  		{
  2438  			name:         "invalid job state (Markdown)",
  2439  			comment:      "Prow Status: 0 out of 1 passed\nāŒ [bar-job](http://bar-status) BANANAS",
  2440  			expectedJobs: 0,
  2441  		},
  2442  	}
  2443  	for _, tc := range testcases {
  2444  		report := ParseReport(tc.comment)
  2445  		if report == nil {
  2446  			if !tc.expectNil {
  2447  				t.Errorf("%s: expected non-nil report but got nil", tc.name)
  2448  			}
  2449  		} else {
  2450  			if tc.expectNil {
  2451  				t.Errorf("%s: expected nil report but got %v", tc.name, report)
  2452  			} else if tc.expectedJobs != len(report.Jobs) {
  2453  				t.Errorf("%s: expected %d jobs in the report but got %d instead", tc.name, tc.expectedJobs, len(report.Jobs))
  2454  			}
  2455  		}
  2456  	}
  2457  
  2458  }
  2459  
  2460  // TestReportStability ensures a generated report's string parses to the same report
  2461  func TestReportStability(t *testing.T) {
  2462  	job := func(name, url string, state v1.ProwJobState) *v1.ProwJob {
  2463  		var out v1.ProwJob
  2464  		out.Spec.Job = name
  2465  		out.Status.URL = url
  2466  		out.Status.State = state
  2467  		return &out
  2468  	}
  2469  	expected := GenerateReport([]*v1.ProwJob{
  2470  		job("this", "hey", v1.SuccessState),
  2471  		job("that", "url", v1.FailureState),
  2472  	}, 0)
  2473  	actual := ParseReport(expected.String())
  2474  	if !equality.Semantic.DeepEqual(&expected, actual) {
  2475  		t.Errorf(diff.ObjectReflectDiff(&expected, actual))
  2476  	}
  2477  }