sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/plugins/golint/golint_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 golint
    18  
    19  import (
    20  	"fmt"
    21  	"reflect"
    22  	"strings"
    23  	"testing"
    24  
    25  	"github.com/sirupsen/logrus"
    26  
    27  	"sigs.k8s.io/prow/pkg/config"
    28  	"sigs.k8s.io/prow/pkg/git/localgit"
    29  	"sigs.k8s.io/prow/pkg/github"
    30  	"sigs.k8s.io/prow/pkg/plugins"
    31  )
    32  
    33  var initialFiles = map[string][]byte{
    34  	"bar.go": []byte(`// Package bar does an interesting thing.
    35  package bar
    36  
    37  // Foo does a thing.
    38  func Foo(wow int) int {
    39  	return 42 + wow
    40  }
    41  `),
    42  }
    43  
    44  var pullFiles = map[string][]byte{
    45  	"qux.go": []byte(`package bar
    46  
    47  func Qux() error {
    48  	return nil
    49  }
    50  `),
    51  	"zz_generated.wowza.go": []byte(`package bar
    52  
    53  func Qux() error {
    54  	return nil
    55  }
    56  `),
    57  }
    58  
    59  type ghc struct {
    60  	genfile     []byte
    61  	pr          github.PullRequest
    62  	changes     []github.PullRequestChange
    63  	oldComments []github.ReviewComment
    64  	comment     github.DraftReview
    65  }
    66  
    67  func (g *ghc) GetPullRequestChanges(org, repo string, number int) ([]github.PullRequestChange, error) {
    68  	return g.changes, nil
    69  }
    70  
    71  func (g *ghc) CreateReview(org, repo string, number int, r github.DraftReview) error {
    72  	g.comment = r
    73  	return nil
    74  }
    75  
    76  func (g *ghc) ListPullRequestComments(org, repo string, number int) ([]github.ReviewComment, error) {
    77  	return g.oldComments, nil
    78  }
    79  
    80  func (g *ghc) GetFile(org, repo, filepath, commit string) ([]byte, error) {
    81  	return g.genfile, nil
    82  }
    83  
    84  func (g *ghc) GetPullRequest(org, repo string, number int) (*github.PullRequest, error) {
    85  	return &g.pr, nil
    86  }
    87  
    88  var e = &github.GenericCommentEvent{
    89  	Action:     github.GenericCommentActionCreated,
    90  	IssueState: "open",
    91  	Body:       "/lint",
    92  	User:       github.User{Login: "cjwagner"},
    93  	Number:     42,
    94  	IsPR:       true,
    95  	Repo: github.Repo{
    96  		Owner:    github.User{Login: "foo"},
    97  		Name:     "bar",
    98  		FullName: "foo/bar",
    99  	},
   100  }
   101  
   102  func TestMinConfidence(t *testing.T) {
   103  	zero := float64(0)
   104  	half := 0.5
   105  	cases := []struct {
   106  		name     string
   107  		golint   plugins.Golint
   108  		expected float64
   109  	}{
   110  		{
   111  			name:     "nothing set",
   112  			expected: defaultConfidence,
   113  		},
   114  		{
   115  			name:     "no confidence set",
   116  			golint:   plugins.Golint{},
   117  			expected: defaultConfidence,
   118  		},
   119  		{
   120  			name:     "confidence set to zero",
   121  			golint:   plugins.Golint{MinimumConfidence: &zero},
   122  			expected: zero,
   123  		},
   124  		{
   125  			name:     "confidence set positive",
   126  			golint:   plugins.Golint{MinimumConfidence: &half},
   127  			expected: half,
   128  		},
   129  	}
   130  	for _, tc := range cases {
   131  		t.Run(tc.name, func(t *testing.T) {
   132  			actual := minConfidence(tc.golint)
   133  			if actual != tc.expected {
   134  				t.Errorf("minimum confidence %f != expected %f", actual, tc.expected)
   135  			}
   136  		})
   137  	}
   138  }
   139  
   140  func TestLintV2(t *testing.T) {
   141  	testLint(localgit.NewV2, t)
   142  }
   143  
   144  func testLint(clients localgit.Clients, t *testing.T) {
   145  	lg, c, err := clients()
   146  	if err != nil {
   147  		t.Fatalf("Making localgit: %v", err)
   148  	}
   149  	defer func() {
   150  		if err := lg.Clean(); err != nil {
   151  			t.Errorf("Cleaning up localgit: %v", err)
   152  		}
   153  		if err := c.Clean(); err != nil {
   154  			t.Errorf("Cleaning up client: %v", err)
   155  		}
   156  	}()
   157  	if err := lg.MakeFakeRepo("foo", "bar"); err != nil {
   158  		t.Fatalf("Making fake repo: %v", err)
   159  	}
   160  	if err := lg.AddCommit("foo", "bar", initialFiles); err != nil {
   161  		t.Fatalf("Adding initial commit: %v", err)
   162  	}
   163  	if err := lg.CheckoutNewBranch("foo", "bar", "pull/42/head"); err != nil {
   164  		t.Fatalf("Checking out pull branch: %v", err)
   165  	}
   166  	if err := lg.AddCommit("foo", "bar", pullFiles); err != nil {
   167  		t.Fatalf("Adding PR commit: %v", err)
   168  	}
   169  
   170  	gh := &ghc{
   171  		genfile: []byte("file-prefix zz_generated"),
   172  		changes: []github.PullRequestChange{
   173  			{
   174  				Filename: "qux.go",
   175  				Patch:    "@@ -0,0 +1,5 @@\n+package bar\n+\n+func Qux() error {\n+   return nil\n+}",
   176  			},
   177  			{
   178  				Filename: "zz_generated.wowza.go",
   179  				Patch:    "@@ -0,0 +1,5 @@\n+package bar\n+\n+func Qux2() error {\n+   return nil\n+}",
   180  			},
   181  		},
   182  	}
   183  	if err := handle(0, gh, c, logrus.NewEntry(logrus.New()), e); err != nil {
   184  		t.Fatalf("Got error from handle: %v", err)
   185  	}
   186  	if len(gh.comment.Comments) != 2 {
   187  		t.Fatalf("Expected two comments, got %d: %v.", len(gh.comment.Comments), gh.comment.Comments)
   188  	}
   189  	for _, c := range gh.comment.Comments {
   190  		pos := c.Position
   191  		gh.oldComments = append(gh.oldComments, github.ReviewComment{
   192  			Path:     c.Path,
   193  			Position: &pos,
   194  			Body:     c.Body,
   195  		})
   196  	}
   197  	if err := handle(0, gh, c, logrus.NewEntry(logrus.New()), e); err != nil {
   198  		t.Fatalf("Got error from handle on second try: %v", err)
   199  	}
   200  	if len(gh.comment.Comments) != 0 {
   201  		t.Fatalf("Expected no comments, got %d: %v", len(gh.comment.Comments), gh.comment.Comments)
   202  	}
   203  
   204  	// Test that we limit comments.
   205  	badFileLines := []string{"package baz", ""}
   206  	for i := 0; i < maxComments+5; i++ {
   207  		badFileLines = append(badFileLines, fmt.Sprintf("type PublicType%d int", i))
   208  	}
   209  	gh.changes = append(gh.changes, github.PullRequestChange{
   210  		Filename: "baz.go",
   211  		Patch:    fmt.Sprintf("@@ -0,0 +1,%d @@\n+%s", len(badFileLines), strings.Join(badFileLines, "\n+")),
   212  	})
   213  	if err := lg.AddCommit("foo", "bar", map[string][]byte{"baz.go": []byte(strings.Join(badFileLines, "\n"))}); err != nil {
   214  		t.Fatalf("Adding PR commit: %v", err)
   215  	}
   216  	gh.oldComments = nil
   217  	if err := handle(0, gh, c, logrus.NewEntry(logrus.New()), e); err != nil {
   218  		t.Fatalf("Got error from handle on third try: %v", err)
   219  	}
   220  	if len(gh.comment.Comments) != maxComments {
   221  		t.Fatalf("Expected %d comments, got %d: %v", maxComments, len(gh.comment.Comments), gh.comment.Comments)
   222  	}
   223  }
   224  
   225  func TestLintCodeSuggestionV2(t *testing.T) {
   226  	testLintCodeSuggestion(localgit.NewV2, t)
   227  }
   228  
   229  func testLintCodeSuggestion(clients localgit.Clients, t *testing.T) {
   230  	var testcases = []struct {
   231  		name       string
   232  		codeChange string
   233  		pullFiles  map[string][]byte
   234  		comment    string
   235  	}{
   236  		{
   237  			name:       "Check names with underscore",
   238  			codeChange: "@@ -0,0 +1,7 @@\n+// Package bar comment\n+package bar\n+\n+// Qux_1 comment\n+func Qux_1_Func() error {\n+   return nil\n+}",
   239  			pullFiles: map[string][]byte{
   240  				"qux.go": []byte("// Package bar comment\npackage bar\n\n// Qux_1 comment\nfunc Qux_1() error {\n	return nil\n}\n"),
   241  			},
   242  			comment: "```suggestion\nfunc Qux1() error {\n```\nGolint naming: don't use underscores in Go names; func Qux_1 should be Qux1. [More info](http://golang.org/doc/effective_go.html#mixed-caps). <!-- golint -->",
   243  		},
   244  		{
   245  			name:       "Check names with all caps",
   246  			codeChange: "@@ -0,0 +1,7 @@\n+// Package bar comment\n+package bar\n+\n+// QUX_FUNC comment\n+func QUX_FUNC() error {\n+   return nil\n+}",
   247  			pullFiles: map[string][]byte{
   248  				"qux.go": []byte("// Package bar comment\npackage bar\n\n// QUX_FUNC comment\nfunc QUX_FUNC() error {\n       return nil\n}\n"),
   249  			},
   250  			comment: "```suggestion\nfunc QuxFunc() error {\n```\nGolint naming: don't use ALL_CAPS in Go names; use CamelCase. [More info](https://golang.org/wiki/CodeReviewComments#mixed-caps). <!-- golint -->",
   251  		},
   252  		{
   253  			name:       "Correct function name",
   254  			codeChange: "@@ -0,0 +1,7 @@\n+// Package bar comment\n+package bar\n+\n+// QuxFunc comment\n+func QuxFunc() error {\n+   return nil\n+}",
   255  			pullFiles: map[string][]byte{
   256  				"qux.go": []byte("// Package bar comment\npackage bar\n\n// QuxFunc comment\nfunc QuxFunc() error {\n       return nil\n}\n"),
   257  			},
   258  			comment: "",
   259  		},
   260  		{
   261  			name:       "Check stutter in function names",
   262  			codeChange: "@@ -0,0 +1,9 @@\n+/*\n+Package bar comment\n+*/\n+package bar\n+\n+// BarFunc comment\n+func BarFunc() error {\n+   return nil\n+}",
   263  			pullFiles: map[string][]byte{
   264  				"qux.go": []byte("/*\nPackage bar comment\n*/\npackage bar\n\n// BarFunc comment\nfunc BarFunc() error {\n   return nil\n}"),
   265  			},
   266  			comment: "```suggestion\nfunc Func() error {\n```\nGolint naming: func name will be used as bar.BarFunc by other packages, and that stutters; consider calling this Func. [More info](https://golang.org/wiki/CodeReviewComments#package-names). <!-- golint -->",
   267  		},
   268  		{
   269  			name:       "Check stutter in type names",
   270  			codeChange: "@@ -0,0 +1,8 @@\n+/*\n+Package bar comment\n+*/\n+package bar\n+\n+// BarMaker comment\n+type BarMaker struct{}\n+",
   271  			pullFiles: map[string][]byte{
   272  				"qux.go": []byte("/*\nPackage bar comment\n*/\npackage bar\n\n// BarMaker comment\ntype BarMaker struct{}\n"),
   273  			},
   274  			comment: "```suggestion\ntype Maker struct{}\n```\nGolint naming: type name will be used as bar.BarMaker by other packages, and that stutters; consider calling this Maker. [More info](https://golang.org/wiki/CodeReviewComments#package-names). <!-- golint -->",
   275  		},
   276  		{
   277  			name:       "Check stutter: no stutter",
   278  			codeChange: "@@ -0,0 +1,8 @@\n+/*\n+Package bar comment\n+*/\n+package bar\n+\n+// barMaker comment\n+type barMaker struct{}\n+",
   279  			pullFiles: map[string][]byte{
   280  				"qux.go": []byte("/*\nPackage bar comment\n*/\npackage bar\n\n// barMaker comment\ntype barMaker struct{}\n"),
   281  			},
   282  			comment: "",
   283  		},
   284  		{
   285  			name:       "Check errorf with errors",
   286  			codeChange: "@@ -0,0 +1,14 @@\n+/*\n+Package bar comment\n+*/\n+package bar\n+\n+import (\n+        \"errors\"\n+        \"fmt\"\n+)\n+\n+func f(x int) error {\n+        return errors.New(fmt.Sprintf(\"something %d\", x))\n+}\n+",
   287  			pullFiles: map[string][]byte{
   288  				"qux.go": []byte("/*\nPackage bar comment\n*/\npackage bar\n\nimport (\n        \"errors\"\n        \"fmt\"\n)\n\nfunc f(x int) error {\n        return errors.New(fmt.Sprintf(\"something %d\", x))\n}\n"),
   289  			},
   290  			comment: "```suggestion\n        return fmt.Errorf(\"something %d\", x)\n```\nGolint errors: should replace errors.New(fmt.Sprintf(...)) with fmt.Errorf(...). <!-- golint -->",
   291  		},
   292  		{
   293  			name:       "Check errorf: no error",
   294  			codeChange: "@@ -0,0 +1,14 @@\n+/*\n+Package bar comment\n+*/\n+package bar\n+\n+import (\n+        \"errors\"\n+        \"fmt\"\n+)\n+\n+func f(x int) error {\n+        return fmt.Errorf(\"something %!d(MISSING)\", x)\n+}\n+",
   295  			pullFiles: map[string][]byte{
   296  				"qux.go": []byte("/*\nPackage bar comment\n*/\npackage bar\n\nimport (\n        \"errors\"\n        \"fmt\"\n)\n\nfunc f(x int) error {        return fmt.Errorf(\"something %!d(MISSING)\", x)\n}\n"),
   297  			},
   298  			comment: "",
   299  		},
   300  		{
   301  			name:       "Check loop range: omit values",
   302  			codeChange: "@@ -0,0 +1,10 @@\n+/*\n+Package bar comment\n+*/\n+package bar\n+\n+func f() {\n+for _ = range m {\n+}\n+}\n+",
   303  			pullFiles: map[string][]byte{
   304  				"qux.go": []byte("/*\nPackage bar comment\n*/\npackage bar\n\nfunc f() {\nfor _ = range m {\n}\n}\n"),
   305  			},
   306  			comment: "```suggestion\nfor range m {\n```\nGolint range-loop: should omit values from range; this loop is equivalent to `for range ...`. <!-- golint -->",
   307  		},
   308  		{
   309  			name:       "Check loop range: omit two values",
   310  			codeChange: "@@ -0,0 +1,10 @@\n+/*\n+Package bar comment\n+*/\n+package bar\n+\n+func f() {\n+for _, _ = range m {\n+}\n+}\n+",
   311  			pullFiles: map[string][]byte{
   312  				"qux.go": []byte("/*\nPackage bar comment\n*/\npackage bar\n\nfunc f() {\nfor _, _ = range m {\n}\n}\n"),
   313  			},
   314  			comment: "```suggestion\nfor range m {\n```\nGolint range-loop: should omit values from range; this loop is equivalent to `for range ...`. <!-- golint -->",
   315  		},
   316  		{
   317  			name:       "Check loop range: omit 2nd value with =",
   318  			codeChange: "@@ -0,0 +1,11 @@\n+/*\n+Package bar comment\n+*/\n+package bar\n+\n+func f() {\n+var y = 0\n+for y, _ = range m {\n+}\n+}\n+",
   319  			pullFiles: map[string][]byte{
   320  				"qux.go": []byte("/*\nPackage bar comment\n*/\npackage bar\n\nfunc f() {\nvar y = 0\nfor y, _ = range m {\n}\n}\n"),
   321  			},
   322  			comment: "```suggestion\nfor y = range m {\n```\nGolint range-loop: should omit 2nd value from range; this loop is equivalent to `for y = range ...`. <!-- golint -->",
   323  		},
   324  		{
   325  			name:       "Check loop range: omit 2nd value with :=",
   326  			codeChange: "@@ -0,0 +1,10 @@\n+/*\n+Package bar comment\n+*/\n+package bar\n+\n+func f() {\n+for y, _ := range m {\n+}\n+}\n+",
   327  			pullFiles: map[string][]byte{
   328  				"qux.go": []byte("/*\nPackage bar comment\n*/\npackage bar\n\nfunc f() {\nfor y, _ := range m {\n}\n}\n"),
   329  			},
   330  			comment: "```suggestion\nfor y := range m {\n```\nGolint range-loop: should omit 2nd value from range; this loop is equivalent to `for y := range ...`. <!-- golint -->",
   331  		},
   332  		{
   333  			name:       "Check loop range: no error",
   334  			codeChange: "@@ -0,0 +1,10 @@\n+/*\n+Package bar comment\n+*/\n+package bar\n+\n+func f() {\n+for y := range m {\n+}\n+}\n+",
   335  			pullFiles: map[string][]byte{
   336  				"qux.go": []byte("/*\nPackage bar comment\n*/\npackage bar\n\nfunc f() {\nfor y := range m {\n}\n}\n"),
   337  			},
   338  			comment: "",
   339  		},
   340  		{
   341  			name:       "Check variable declaration: no value suggestion",
   342  			codeChange: "@@ -0,0 +1,9 @@\n+/*\n+Package bar comment\n+*/\n+package bar\n+\n+func f() {\n+var myZeroRune2 rune\n+}\n+",
   343  			pullFiles: map[string][]byte{
   344  				"qux.go": []byte("/*\nPackage bar comment\n*/\npackage bar\n\nfunc f() {\nvar myZeroRune2 rune\n}\n"),
   345  			},
   346  			comment: "",
   347  		},
   348  		{
   349  			name:       "Check variable declaration: no type suggestion",
   350  			codeChange: "@@ -0,0 +1,9 @@\n+/*\n+Package bar comment\n+*/\n+package bar\n+\n+func f() {\n+var myInt = 7\n+}\n+",
   351  			pullFiles: map[string][]byte{
   352  				"qux.go": []byte("/*\nPackage bar comment\n*/\npackage bar\n\nfunc f() {\nvar myInt = 7\n}\n"),
   353  			},
   354  			comment: "",
   355  		},
   356  	}
   357  
   358  	lg, c, err := clients()
   359  	if err != nil {
   360  		t.Fatalf("Making localgit: %v", err)
   361  	}
   362  	defer func() {
   363  		if err := lg.Clean(); err != nil {
   364  			t.Errorf("Cleaning up localgit: %v", err)
   365  		}
   366  		if err := c.Clean(); err != nil {
   367  			t.Errorf("Cleaning up client: %v", err)
   368  		}
   369  	}()
   370  	if err := lg.MakeFakeRepo("foo", "bar"); err != nil {
   371  		t.Fatalf("Making fake repo: %v", err)
   372  	}
   373  	if err := lg.AddCommit("foo", "bar", initialFiles); err != nil {
   374  		t.Fatalf("Adding initial commit: %v", err)
   375  	}
   376  	if err := lg.CheckoutNewBranch("foo", "bar", "pull/42/head"); err != nil {
   377  		t.Fatalf("Checking out pull branch: %v", err)
   378  	}
   379  
   380  	for _, test := range testcases {
   381  		t.Logf("Running test case %q...", test.name)
   382  		if err := lg.AddCommit("foo", "bar", test.pullFiles); err != nil {
   383  			t.Fatalf("Adding PR commit: %v", err)
   384  		}
   385  		gh := &ghc{
   386  			changes: []github.PullRequestChange{
   387  				{
   388  					Filename: "qux.go",
   389  					Patch:    test.codeChange,
   390  				},
   391  			},
   392  		}
   393  		if err := handle(0, gh, c, logrus.NewEntry(logrus.New()), e); err != nil {
   394  			t.Fatalf("Got error from handle: %v", err)
   395  		}
   396  
   397  		if test.comment == "" {
   398  			if len(gh.comment.Comments) > 0 {
   399  				t.Fatalf("Expected no comment, got %d: %v.", len(gh.comment.Comments), gh.comment.Comments)
   400  			}
   401  		} else {
   402  			if len(gh.comment.Comments) != 1 {
   403  				t.Fatalf("Expected one comments, got %d: %v.", len(gh.comment.Comments), gh.comment.Comments)
   404  			}
   405  			if test.comment != gh.comment.Comments[0].Body {
   406  				t.Fatalf("Expected\n" + test.comment + "\n but got\n" + gh.comment.Comments[0].Body)
   407  			}
   408  		}
   409  	}
   410  }
   411  
   412  func TestLintErrorV2(t *testing.T) {
   413  	testLintError(localgit.NewV2, t)
   414  }
   415  
   416  func testLintError(clients localgit.Clients, t *testing.T) {
   417  
   418  	var testcases = []struct {
   419  		name       string
   420  		codeChange string
   421  		pullFiles  map[string][]byte
   422  		comments   []github.DraftReviewComment
   423  	}{
   424  		{
   425  			name: "Golint error",
   426  			codeChange: "@@ -0,0 +1,15 @@\n+" +
   427  				"/*\n+" +
   428  				"Package bar comment\n+" +
   429  				"*/\n+" +
   430  				"package bar\n+" +
   431  				"\n+" +
   432  				"func f(m []string) {\n+" +
   433  				"      for _ = range m {\n+" +
   434  				"      }\n+" +
   435  				"}\n+" +
   436  				"\n+" +
   437  				"for b() error {\n+" +
   438  				"return nil\n+" +
   439  				"}\n+",
   440  			pullFiles: map[string][]byte{
   441  				"qux.go": []byte("/*\n" +
   442  					"Package bar comment\n" +
   443  					"*/\n" +
   444  					"package bar\n" +
   445  					"\n" +
   446  					"func f(m []string) {\n" +
   447  					"      for _ = range m {\n" +
   448  					"      }\n" +
   449  					"}\n" +
   450  					"\n" +
   451  					"for b() error {\n" +
   452  					"return nil\n" +
   453  					"}\n"),
   454  			},
   455  			comments: []github.DraftReviewComment{
   456  				{
   457  					Body:     "expected declaration, found 'for'",
   458  					Position: 11,
   459  					Path:     "qux.go",
   460  				},
   461  				{
   462  					Body: "computing added lines in qux.go: invalid patch: " +
   463  						"@@ -0,0 +1,15 @@\n+" +
   464  						"/*\n+" +
   465  						"Package bar comment\n+" +
   466  						"*/\n+" +
   467  						"package bar\n+" +
   468  						"\n+" +
   469  						"func f(m []string) {\n+" +
   470  						"      for _ = range m {\n+" +
   471  						"      }\n+" +
   472  						"}\n+" +
   473  						"\n+" +
   474  						"for b() error {\n+" +
   475  						"return nil\n+" +
   476  						"}\n+",
   477  					Position: 0,
   478  					Path:     "qux.go",
   479  				},
   480  			},
   481  		},
   482  		{
   483  			name: "No golint error",
   484  			codeChange: "@@ -0,0 +1,9 @@\n+" +
   485  				"/*\n+" +
   486  				"Package bar comment\n+" +
   487  				"*/\n+" +
   488  				"package bar\n+" +
   489  				"\n+" +
   490  				"func f() {\n+" +
   491  				"      var myZeroFlt float32 = 0.\n+" +
   492  				"}\n+",
   493  			pullFiles: map[string][]byte{
   494  				"qux.go": []byte("/*\n" +
   495  					"Package bar comment\n" +
   496  					"*/\n" +
   497  					"package bar\n" +
   498  					"\n" +
   499  					"func b() error {\n" +
   500  					"return nil\n" +
   501  					"}\n"),
   502  			},
   503  			comments: nil,
   504  		},
   505  	}
   506  
   507  	lg, c, err := clients()
   508  	if err != nil {
   509  		t.Fatalf("Making localgit: %v", err)
   510  	}
   511  	defer func() {
   512  		if err := lg.Clean(); err != nil {
   513  			t.Errorf("Cleaning up localgit: %v", err)
   514  		}
   515  		if err := c.Clean(); err != nil {
   516  			t.Errorf("Cleaning up client: %v", err)
   517  		}
   518  	}()
   519  	if err := lg.MakeFakeRepo("foo", "bar"); err != nil {
   520  		t.Fatalf("Making fake repo: %v", err)
   521  	}
   522  	if err := lg.AddCommit("foo", "bar", initialFiles); err != nil {
   523  		t.Fatalf("Adding initial commit: %v", err)
   524  	}
   525  	if err := lg.CheckoutNewBranch("foo", "bar", "pull/42/head"); err != nil {
   526  		t.Fatalf("Checking out pull branch: %v", err)
   527  	}
   528  
   529  	for _, test := range testcases {
   530  		t.Logf("Running test case %q...", test.name)
   531  		if err := lg.AddCommit("foo", "bar", test.pullFiles); err != nil {
   532  			t.Fatalf("Adding PR commit: %v", err)
   533  		}
   534  		gh := &ghc{
   535  			changes: []github.PullRequestChange{
   536  				{
   537  					Filename: "qux.go",
   538  					Patch:    test.codeChange,
   539  				},
   540  			},
   541  		}
   542  		if err := handle(0, gh, c, logrus.NewEntry(logrus.New()), e); err != nil {
   543  			t.Fatalf("Got error from handle: %v", err)
   544  		}
   545  
   546  		if test.comments == nil {
   547  			if len(gh.comment.Comments) > 0 {
   548  				t.Fatalf("Expected no comment, got %d: %v.", len(gh.comment.Comments), gh.comment.Comments)
   549  			}
   550  		} else {
   551  			if len(gh.comment.Comments) != len(test.comments) {
   552  				t.Fatalf("Expected one comments, got %d: %v.", len(gh.comment.Comments), gh.comment.Comments)
   553  			}
   554  			for _, testComment := range test.comments {
   555  				exists := false
   556  				for _, actualComment := range gh.comment.Comments {
   557  					if actualComment == testComment {
   558  						exists = true
   559  					}
   560  				}
   561  				if !exists {
   562  					t.Fatalf("Did not get back expected comment %v, actual comments are %v", testComment, gh.comment.Comments)
   563  				}
   564  			}
   565  		}
   566  	}
   567  }
   568  
   569  func TestAddedLines(t *testing.T) {
   570  	var testcases = []struct {
   571  		patch string
   572  		lines map[int]int
   573  		err   bool
   574  	}{
   575  		{
   576  			patch: "@@ -0,0 +1,5 @@\n+package bar\n+\n+func Qux() error {\n+   return nil\n+}",
   577  			lines: map[int]int{1: 1, 2: 2, 3: 3, 4: 4, 5: 5},
   578  		},
   579  		{
   580  			patch: "@@ -29,12 +29,14 @@ import (\n \t\"github.com/sirupsen/logrus\"\n \t\"sigs.k8s.io/yaml\"\n \n+\t\"sigs.k8s.io/prow/pkg/config\"\n \t\"sigs.k8s.io/prow/pkg/jenkins\"\n \t\"sigs.k8s.io/prow/pkg/kube\"\n \t\"sigs.k8s.io/prow/pkg/plank\"\n )\n \n var (\n+\tconfigPath   = flag.String(\"config-path\", \"/etc/config/config\", \"Path to config.yaml.\")\n \tbuildCluster = flag.String(\"build-cluster\", \"\", \"Path to file containing a YAML-marshalled kube.Cluster object. If empty, uses the local cluster.\")\n \n \tjenkinsURL       = flag.String(\"jenkins-url\", \"\", \"Jenkins URL\")\n@@ -47,18 +49,22 @@ var objReg = regexp.MustCompile(`^[\\w-]+$`)\n \n func main() {\n \tflag.Parse()\n-\n \tlogrus.SetFormatter(&logrus.JSONFormatter{})\n \n-\tkc, err := kube.NewClientInCluster(kube.ProwNamespace)\n+\tconfigAgent := &config.Agent{}\n+\tif err := configAgent.Start(*configPath); err != nil {\n+\t\tlogrus.WithError(err).Fatal(\"Error starting config agent.\")\n+\t}\n+\n+\tkc, err := kube.NewClientInCluster(configAgent.Config().ProwJobNamespace)\n \tif err != nil {\n \t\tlogrus.WithError(err).Fatal(\"Error getting client.\")\n \t}\n \tvar pkc *kube.Client\n \tif *buildCluster == \"\" {\n-\t\tpkc = kc.Namespace(kube.TestPodNamespace)\n+\t\tpkc = kc.Namespace(configAgent.Config().PodNamespace)\n \t} else {\n-\t\tpkc, err = kube.NewClientFromFile(*buildCluster, kube.TestPodNamespace)\n+\t\tpkc, err = kube.NewClientFromFile(*buildCluster, configAgent.Config().PodNamespace)\n \t\tif err != nil {\n \t\t\tlogrus.WithError(err).Fatal(\"Error getting kube client to build cluster.\")\n \t\t}",
   581  			lines: map[int]int{4: 32, 11: 39, 23: 54, 24: 55, 25: 56, 26: 57, 27: 58, 28: 59, 35: 65, 38: 67},
   582  		},
   583  		{
   584  			patch: "@@ -1 +0,0 @@\n-such",
   585  		},
   586  		{
   587  			patch: "@@ -1,3 +0,0 @@\n-such\n-a\n-doge",
   588  		},
   589  		{
   590  			patch: "@@ -0,0 +1 @@\n+wow",
   591  			lines: map[int]int{1: 1},
   592  		},
   593  		{
   594  			patch: "@@ -0,0 +1 @@\n+wow\n\\ No newline at end of file",
   595  			lines: map[int]int{1: 1},
   596  		},
   597  		{
   598  			patch: "@@ -1 +1 @@\n-doge\n+wow",
   599  			lines: map[int]int{2: 1},
   600  		},
   601  		{
   602  			patch: "something strange",
   603  			err:   true,
   604  		},
   605  		{
   606  			patch: "@@ -a,3 +0,0 @@\n-wow",
   607  			err:   true,
   608  		},
   609  		{
   610  			patch: "@@ -1 +1 @@",
   611  			err:   true,
   612  		},
   613  		{
   614  			patch: "",
   615  		},
   616  	}
   617  	for _, tc := range testcases {
   618  		als, err := AddedLines(tc.patch)
   619  		if err == nil == tc.err {
   620  			t.Errorf("For patch %s\nExpected error %v, got error %v", tc.patch, tc.err, err)
   621  			continue
   622  		}
   623  		if len(als) != len(tc.lines) {
   624  			t.Errorf("For patch %s\nAdded lines has wrong length. Got %v, expected %v", tc.patch, als, tc.lines)
   625  		}
   626  		for pl, l := range tc.lines {
   627  			if als[l] != pl {
   628  				t.Errorf("For patch %s\nExpected added line %d to be %d, but got %d", tc.patch, l, pl, als[l])
   629  			}
   630  		}
   631  	}
   632  }
   633  
   634  func TestModifiedGoFilesV2(t *testing.T) {
   635  	testModifiedGoFiles(localgit.NewV2, t)
   636  }
   637  
   638  func testModifiedGoFiles(clients localgit.Clients, t *testing.T) {
   639  	lg, c, err := clients()
   640  	if err != nil {
   641  		t.Fatalf("Making localgit: %v", err)
   642  	}
   643  	defer func() {
   644  		if err := lg.Clean(); err != nil {
   645  			t.Errorf("Cleaning up localgit: %v", err)
   646  		}
   647  		if err := c.Clean(); err != nil {
   648  			t.Errorf("Cleaning up client: %v", err)
   649  		}
   650  	}()
   651  	if err := lg.MakeFakeRepo("foo", "bar"); err != nil {
   652  		t.Fatalf("Making fake repo: %v", err)
   653  	}
   654  	if err := lg.AddCommit("foo", "bar", initialFiles); err != nil {
   655  		t.Fatalf("Adding initial commit: %v", err)
   656  	}
   657  	if err := lg.CheckoutNewBranch("foo", "bar", "pull/42/head"); err != nil {
   658  		t.Fatalf("Checking out pull branch: %v", err)
   659  	}
   660  	if err := lg.AddCommit("foo", "bar", pullFiles); err != nil {
   661  		t.Fatalf("Adding PR commit: %v", err)
   662  	}
   663  
   664  	var testcases = []struct {
   665  		name                  string
   666  		gh                    *ghc
   667  		expectedModifiedFiles map[string]string
   668  	}{
   669  		{
   670  			name: "modified files include vendor file",
   671  			gh: &ghc{
   672  				changes: []github.PullRequestChange{
   673  					{
   674  						Filename: "qux.go",
   675  						Patch:    "@@ -0,0 +1,5 @@\n+package bar\n+\n+func Qux() error {\n+   return nil\n+}",
   676  					},
   677  					{
   678  						Filename: "vendor/foo/bar.go",
   679  						Patch:    "@@ -0,0 +1,5 @@\n+package bar\n+\n+func Qux2() error {\n+   return nil\n+}",
   680  					},
   681  				},
   682  			},
   683  			expectedModifiedFiles: map[string]string{
   684  				"qux.go": "@@ -0,0 +1,5 @@\n+package bar\n+\n+func Qux() error {\n+   return nil\n+}",
   685  			},
   686  		},
   687  		{
   688  			name: "modified files include non go file",
   689  			gh: &ghc{
   690  				changes: []github.PullRequestChange{
   691  					{
   692  						Filename: "qux.go",
   693  						Patch:    "@@ -0,0 +1,5 @@\n+package bar\n+\n+func Qux() error {\n+   return nil\n+}",
   694  					},
   695  					{
   696  						Filename: "foo.md",
   697  						Patch:    "@@ -1,3 +1,4 @@\n+TODO",
   698  					},
   699  				},
   700  			},
   701  			expectedModifiedFiles: map[string]string{
   702  				"qux.go": "@@ -0,0 +1,5 @@\n+package bar\n+\n+func Qux() error {\n+   return nil\n+}",
   703  			},
   704  		},
   705  		{
   706  			name: "modified files include generated file",
   707  			gh: &ghc{
   708  				genfile: []byte("file-prefix zz_generated"),
   709  				changes: []github.PullRequestChange{
   710  					{
   711  						Filename: "qux.go",
   712  						Patch:    "@@ -0,0 +1,5 @@\n+package bar\n+\n+func Qux() error {\n+   return nil\n+}",
   713  					},
   714  					{
   715  						Filename: "zz_generated.wowza.go",
   716  						Patch:    "@@ -0,0 +1,5 @@\n+package bar\n+\n+func Qux2() error {\n+   return nil\n+}",
   717  					},
   718  				},
   719  			},
   720  			expectedModifiedFiles: map[string]string{
   721  				"qux.go": "@@ -0,0 +1,5 @@\n+package bar\n+\n+func Qux() error {\n+   return nil\n+}",
   722  			},
   723  		},
   724  		{
   725  			name: "modified files include removed file",
   726  			gh: &ghc{
   727  				changes: []github.PullRequestChange{
   728  					{
   729  						Filename: "qux.go",
   730  						Patch:    "@@ -0,0 +1,5 @@\n+package bar\n+\n+func Qux() error {\n+   return nil\n+}",
   731  					},
   732  					{
   733  						Filename: "bar.go",
   734  						Status:   github.PullRequestFileRemoved,
   735  						Patch:    "@@ -1,5 +0,0 @@\n-package bar\n-\n-func Qux() error {\n-   return nil\n-}",
   736  					},
   737  				},
   738  			},
   739  			expectedModifiedFiles: map[string]string{
   740  				"qux.go": "@@ -0,0 +1,5 @@\n+package bar\n+\n+func Qux() error {\n+   return nil\n+}",
   741  			},
   742  		},
   743  		{
   744  			name: "modified files include renamed file",
   745  			gh: &ghc{
   746  				changes: []github.PullRequestChange{
   747  					{
   748  						Filename: "qux.go",
   749  						Patch:    "@@ -0,0 +1,5 @@\n+package bar\n+\n+func Qux() error {\n+   return nil\n+}",
   750  					},
   751  					{
   752  						Filename: "bar.go",
   753  						Status:   github.PullRequestFileRenamed,
   754  					},
   755  				},
   756  			},
   757  			expectedModifiedFiles: map[string]string{
   758  				"qux.go": "@@ -0,0 +1,5 @@\n+package bar\n+\n+func Qux() error {\n+   return nil\n+}",
   759  			},
   760  		},
   761  		{
   762  			name: "added and modified files",
   763  			gh: &ghc{
   764  				changes: []github.PullRequestChange{
   765  					{
   766  						Filename: "qux.go",
   767  						Status:   github.PullRequestFileAdded,
   768  						Patch:    "@@ -0,0 +1,5 @@\n+package bar\n+\n+func Qux() error {\n+   return nil\n+}",
   769  					},
   770  					{
   771  						Filename: "bar.go",
   772  						Patch:    "@@ -0,0 +1,5 @@\n+package baz\n+\n+func Bar() error {\n+   return nil\n+}",
   773  					},
   774  				},
   775  			},
   776  			expectedModifiedFiles: map[string]string{
   777  				"qux.go": "@@ -0,0 +1,5 @@\n+package bar\n+\n+func Qux() error {\n+   return nil\n+}",
   778  				"bar.go": "@@ -0,0 +1,5 @@\n+package baz\n+\n+func Bar() error {\n+   return nil\n+}",
   779  			},
   780  		},
   781  		{
   782  			name: "removed and renamed files",
   783  			gh: &ghc{
   784  				changes: []github.PullRequestChange{
   785  					{
   786  						Filename: "qux.go",
   787  						Status:   github.PullRequestFileRemoved,
   788  						Patch:    "@@ -1,5 +0,0 @@\n-package bar\n-\n-func Qux() error {\n-   return nil\n-}",
   789  					},
   790  					{
   791  						Filename: "bar.go",
   792  						Status:   github.PullRequestFileRenamed,
   793  					},
   794  				},
   795  			},
   796  			expectedModifiedFiles: map[string]string{},
   797  		},
   798  	}
   799  	for _, tc := range testcases {
   800  		actualModifiedFiles, _ := modifiedGoFiles(tc.gh, "foo", "bar", 9527, "0ebb33b")
   801  		if !reflect.DeepEqual(tc.expectedModifiedFiles, actualModifiedFiles) {
   802  			t.Errorf("Expected: %#v, Got %#v in case %s.", tc.expectedModifiedFiles, actualModifiedFiles, tc.name)
   803  		}
   804  	}
   805  }
   806  
   807  func TestHelpProvider(t *testing.T) {
   808  	half := 0.5
   809  	enabledRepos := []config.OrgRepo{
   810  		{Org: "org1", Repo: "repo"},
   811  		{Org: "org2", Repo: "repo"},
   812  	}
   813  	cases := []struct {
   814  		name         string
   815  		config       *plugins.Configuration
   816  		enabledRepos []config.OrgRepo
   817  		err          bool
   818  	}{
   819  		{
   820  			name:         "Empty config",
   821  			config:       &plugins.Configuration{},
   822  			enabledRepos: enabledRepos,
   823  		},
   824  		{
   825  			name: "MinimumConfidence specified",
   826  			config: &plugins.Configuration{
   827  				Golint: plugins.Golint{
   828  					MinimumConfidence: &half,
   829  				},
   830  			},
   831  			enabledRepos: enabledRepos,
   832  		},
   833  	}
   834  	for _, c := range cases {
   835  		t.Run(c.name, func(t *testing.T) {
   836  			_, err := helpProvider(c.config, c.enabledRepos)
   837  			if err != nil && !c.err {
   838  				t.Fatalf("helpProvider error: %v", err)
   839  			}
   840  		})
   841  	}
   842  }