github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/testutils/lint/lint_test.go (about)

     1  // Copyright 2016 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  // +build lint
    12  
    13  package lint
    14  
    15  import (
    16  	"bufio"
    17  	"bytes"
    18  	"fmt"
    19  	"go/build"
    20  	"os"
    21  	"os/exec"
    22  	"path/filepath"
    23  	"regexp"
    24  	"strconv"
    25  	"strings"
    26  	"testing"
    27  
    28  	"github.com/cockroachdb/cockroach/pkg/sql/sem/builtins"
    29  	_ "github.com/cockroachdb/cockroach/pkg/testutils/buildutil"
    30  	"github.com/cockroachdb/errors"
    31  	"github.com/ghemawat/stream"
    32  	"golang.org/x/tools/go/buildutil"
    33  )
    34  
    35  const cockroachDB = "github.com/cockroachdb/cockroach"
    36  
    37  func dirCmd(
    38  	dir string, name string, args ...string,
    39  ) (*exec.Cmd, *bytes.Buffer, stream.Filter, error) {
    40  	cmd := exec.Command(name, args...)
    41  	cmd.Dir = dir
    42  	stdout, err := cmd.StdoutPipe()
    43  	if err != nil {
    44  		return nil, nil, nil, err
    45  	}
    46  	stderr := new(bytes.Buffer)
    47  	cmd.Stderr = stderr
    48  	return cmd, stderr, stream.ReadLines(stdout), nil
    49  }
    50  
    51  // vetCmd executes commands like dirCmd, but stderr is used as the output
    52  // instead of stdout, as produced by programs like `go vet`.
    53  func vetCmd(t *testing.T, dir, name string, args []string, filters []stream.Filter) {
    54  	cmd := exec.Command(name, args...)
    55  	cmd.Dir = dir
    56  	var b bytes.Buffer
    57  	cmd.Stdout = &b
    58  	cmd.Stderr = &b
    59  	switch err := cmd.Run(); err.(type) {
    60  	case nil:
    61  	case *exec.ExitError:
    62  		// Non-zero exit is expected.
    63  	default:
    64  		t.Fatal(err)
    65  	}
    66  	filters = append([]stream.Filter{
    67  		stream.FilterFunc(func(arg stream.Arg) error {
    68  			scanner := bufio.NewScanner(&b)
    69  			for scanner.Scan() {
    70  				if s := scanner.Text(); strings.TrimSpace(s) != "" {
    71  					arg.Out <- s
    72  				}
    73  			}
    74  			return scanner.Err()
    75  		})}, filters...)
    76  
    77  	var msgs strings.Builder
    78  	if err := stream.ForEach(stream.Sequence(filters...), func(s string) {
    79  		fmt.Fprintln(&msgs, s)
    80  	}); err != nil {
    81  		t.Error(err)
    82  	}
    83  	if msgs.Len() > 0 {
    84  		t.Errorf("\n%s", strings.ReplaceAll(msgs.String(), "\\n++", "\n"))
    85  	}
    86  }
    87  
    88  // TestLint runs a suite of linters on the codebase. This file is
    89  // organized into two sections. First are the global linters, which
    90  // run on the entire repo every time. Second are the package-scoped
    91  // linters, which can be restricted to a specific package with the PKG
    92  // makefile variable. Linters that require anything more than a `git
    93  // grep` should preferably be added to the latter group (and within
    94  // that group, adding to Megacheck is better than creating a new
    95  // test).
    96  //
    97  // Linters may be skipped for two reasons: The "short" flag (i.e.
    98  // `make lintshort`), which skips the most expensive linters (more for
    99  // memory than for CPU usage), and the PKG variable. Some linters in
   100  // the global group may be skipped if the PKG flag is set regardless
   101  // of the short flag since they cannot be restricted to the package.
   102  // It should be reasonable to run `make lintshort` and `make lint
   103  // PKG=some/modified/pkg` locally and rely on CI for the more
   104  // expensive linters.
   105  //
   106  // Linters which run in a single process without internal
   107  // parallelization, and which have reasonable memory consumption
   108  // should be marked with t.Parallel(). As a rule of thumb anything
   109  // that requires type-checking the go code needs too much memory to
   110  // parallelize here (although it's fine for such tests to run multiple
   111  // goroutines internally using a shared loader object).
   112  //
   113  // Performance notes: This needs a lot of memory and CPU time. As of
   114  // 2018-07-13, the largest consumers of memory are
   115  // TestMegacheck/staticcheck (9GB) and TestUnused (6GB). Memory
   116  // consumption of staticcheck could be reduced by running it on a
   117  // subset of the packages at a time, although this comes at the
   118  // expense of increased running time.
   119  func TestLint(t *testing.T) {
   120  	crdb, err := build.Import(cockroachDB, "", build.FindOnly)
   121  	if err != nil {
   122  		t.Skip(err)
   123  	}
   124  	pkgDir := filepath.Join(crdb.Dir, "pkg")
   125  
   126  	pkgVar, pkgSpecified := os.LookupEnv("PKG")
   127  
   128  	t.Run("TestLowercaseFunctionNames", func(t *testing.T) {
   129  		t.Parallel()
   130  		reSkipCasedFunction, err := regexp.Compile(`^(Binary file.*|[^:]+:\d+:(` +
   131  			`query error .*` + // OK when in logic tests
   132  			`|` +
   133  			`\s*(//|#).*` + // OK when mentioned in comment
   134  			`|` +
   135  			`.*lint: uppercase function OK` + // linter annotation at end of line
   136  			`))$`)
   137  		if err != nil {
   138  			t.Fatal(err)
   139  		}
   140  
   141  		var names []string
   142  		for _, name := range builtins.AllBuiltinNames {
   143  			switch name {
   144  			case "extract", "trim", "overlay", "position", "substring", "st_x", "st_y":
   145  				// Exempt special forms: EXTRACT(... FROM ...), etc.
   146  			default:
   147  				names = append(names, strings.ToUpper(name))
   148  			}
   149  		}
   150  
   151  		cmd, stderr, filter, err := dirCmd(crdb.Dir,
   152  			"git", "grep", "-nE", fmt.Sprintf(`[^_a-zA-Z](%s)\(`, strings.Join(names, "|")),
   153  			"--", "pkg")
   154  		if err != nil {
   155  			t.Fatal(err)
   156  		}
   157  
   158  		if err := cmd.Start(); err != nil {
   159  			t.Fatal(err)
   160  		}
   161  
   162  		if err := stream.ForEach(filter, func(s string) {
   163  			if reSkipCasedFunction.MatchString(s) {
   164  				// OK when mentioned in comment or lint disabled.
   165  				return
   166  			}
   167  			if strings.Contains(s, "FAMILY"+"(") {
   168  				t.Errorf("\n%s <- forbidden; use \"FAMILY (\" (with space) or "+
   169  					"lowercase \"family(\" for the built-in function", s)
   170  			} else {
   171  				t.Errorf("\n%s <- forbidden; use lowercase for SQL built-in functions", s)
   172  			}
   173  		}); err != nil {
   174  			t.Error(err)
   175  		}
   176  
   177  		if err := cmd.Wait(); err != nil {
   178  			if out := stderr.String(); len(out) > 0 {
   179  				t.Fatalf("err=%s, stderr=%s", err, out)
   180  			}
   181  		}
   182  	})
   183  
   184  	t.Run("TestCopyrightHeaders", func(t *testing.T) {
   185  		t.Parallel()
   186  
   187  		bslHeader := regexp.MustCompile(`// Copyright 20\d\d The Cockroach Authors.
   188  //
   189  // Use of this software is governed by the Business Source License
   190  // included in the file licenses/BSL.txt.
   191  //
   192  // As of the Change Date specified in that file, in accordance with
   193  // the Business Source License, use of this software will be governed
   194  // by the Apache License, Version 2.0, included in the file
   195  // licenses/APL.txt.
   196  `)
   197  
   198  		cclHeader := regexp.MustCompile(`// Copyright 20\d\d The Cockroach Authors.
   199  //
   200  // Licensed as a CockroachDB Enterprise file under the Cockroach Community
   201  // License \(the "License"\); you may not use this file except in compliance with
   202  // the License. You may obtain a copy of the License at
   203  //
   204  //     https://github.com/cockroachdb/cockroach/blob/master/licenses/CCL.txt
   205  `)
   206  
   207  		// These extensions identify source files that should have copyright headers.
   208  		extensions := []string{
   209  			"*.go", "*.cc", "*.h", "*.js", "*.ts", "*.tsx", "*.s", "*.S", "*.styl", "*.proto", "*.rl",
   210  		}
   211  
   212  		cmd, stderr, filter, err := dirCmd(pkgDir, "git", append([]string{"ls-files"}, extensions...)...)
   213  		if err != nil {
   214  			t.Fatal(err)
   215  		}
   216  
   217  		if err := cmd.Start(); err != nil {
   218  			t.Fatal(err)
   219  		}
   220  
   221  		if err := stream.ForEach(stream.Sequence(filter,
   222  			stream.GrepNot(`\.pb\.go`),
   223  			stream.GrepNot(`\.pb\.gw\.go`),
   224  			stream.GrepNot(`\.og\.go`),
   225  			stream.GrepNot(`_string\.go`),
   226  			stream.GrepNot(`_generated\.go`),
   227  			stream.GrepNot(`/embedded.go`),
   228  			stream.GrepNot(`geo/geographiclib/geodesic\.c$`),
   229  			stream.GrepNot(`geo/geographiclib/geodesic\.h$`),
   230  		), func(filename string) {
   231  			isCCL := strings.Contains(filename, "ccl/")
   232  			var expHeader *regexp.Regexp
   233  			if isCCL {
   234  				expHeader = cclHeader
   235  			} else {
   236  				expHeader = bslHeader
   237  			}
   238  
   239  			file, err := os.Open(filepath.Join(pkgDir, filename))
   240  			if err != nil {
   241  				t.Error(err)
   242  				return
   243  			}
   244  			defer file.Close()
   245  			data := make([]byte, 1024)
   246  			n, err := file.Read(data)
   247  			if err != nil {
   248  				t.Errorf("reading start of %s: %s", filename, err)
   249  			}
   250  			data = data[0:n]
   251  
   252  			if expHeader.Find(data) == nil {
   253  				t.Errorf("did not find expected license header (ccl=%v) in %s", isCCL, filename)
   254  			}
   255  		}); err != nil {
   256  			t.Fatal(err)
   257  		}
   258  
   259  		if err := cmd.Wait(); err != nil {
   260  			if out := stderr.String(); len(out) > 0 {
   261  				t.Fatalf("err=%s, stderr=%s", err, out)
   262  			}
   263  		}
   264  	})
   265  
   266  	t.Run("TestMissingLeakTest", func(t *testing.T) {
   267  		t.Parallel()
   268  		cmd, stderr, filter, err := dirCmd(pkgDir, "util/leaktest/check-leaktest.sh")
   269  		if err != nil {
   270  			t.Fatal(err)
   271  		}
   272  
   273  		if err := cmd.Start(); err != nil {
   274  			t.Fatal(err)
   275  		}
   276  
   277  		if err := stream.ForEach(filter, func(s string) {
   278  			t.Errorf("\n%s", s)
   279  		}); err != nil {
   280  			t.Error(err)
   281  		}
   282  
   283  		if err := cmd.Wait(); err != nil {
   284  			if out := stderr.String(); len(out) > 0 {
   285  				t.Fatalf("err=%s, stderr=%s", err, out)
   286  			}
   287  		}
   288  	})
   289  
   290  	t.Run("TestNoContextTODOInTests", func(t *testing.T) {
   291  		t.Parallel()
   292  		cmd, stderr, filter, err := dirCmd(
   293  			pkgDir,
   294  			"git",
   295  			"grep",
   296  			"-nE",
   297  			`context.TODO\(\)`,
   298  			"--",
   299  			"*_test.go",
   300  		)
   301  		if err != nil {
   302  			t.Fatal(err)
   303  		}
   304  
   305  		if err := cmd.Start(); err != nil {
   306  			t.Fatal(err)
   307  		}
   308  
   309  		if err := stream.ForEach(filter, func(s string) {
   310  			t.Errorf("\n%s <- forbidden; use context.Background() in tests.", s)
   311  		}); err != nil {
   312  			t.Error(err)
   313  		}
   314  
   315  		if err := cmd.Wait(); err != nil {
   316  			if out := stderr.String(); len(out) > 0 {
   317  				t.Fatalf("err=%s, stderr=%s", err, out)
   318  			}
   319  		}
   320  	})
   321  
   322  	t.Run("TestTabsInShellScripts", func(t *testing.T) {
   323  		t.Parallel()
   324  		cmd, stderr, filter, err := dirCmd(pkgDir, "git", "grep", "-nE", "^ *\t", "--", "*.sh")
   325  		if err != nil {
   326  			t.Fatal(err)
   327  		}
   328  
   329  		if err := cmd.Start(); err != nil {
   330  			t.Fatal(err)
   331  		}
   332  
   333  		if err := stream.ForEach(filter, func(s string) {
   334  			t.Errorf(`%s <- tab detected, use spaces instead`, s)
   335  		}); err != nil {
   336  			t.Error(err)
   337  		}
   338  
   339  		if err := cmd.Wait(); err != nil {
   340  			if out := stderr.String(); len(out) > 0 {
   341  				t.Fatalf("err=%s, stderr=%s", err, out)
   342  			}
   343  		}
   344  	})
   345  
   346  	t.Run("TestOptfmt", func(t *testing.T) {
   347  		t.Parallel()
   348  		if pkgSpecified {
   349  			t.Skip("PKG specified")
   350  		}
   351  
   352  		cmd, stderr, filter, err := dirCmd(pkgDir, "git", "ls-files", "*.opt", ":!*/testdata/*")
   353  		if err != nil {
   354  			t.Fatal(err)
   355  		}
   356  
   357  		if err := cmd.Start(); err != nil {
   358  			t.Fatal(err)
   359  		}
   360  
   361  		var buf bytes.Buffer
   362  		if err := stream.ForEach(
   363  			stream.Sequence(
   364  				filter,
   365  				stream.Map(func(s string) string {
   366  					return filepath.Join(pkgDir, s)
   367  				}),
   368  				stream.Xargs("optfmt", "-l"),
   369  			), func(s string) {
   370  				fmt.Fprintln(&buf, s)
   371  			}); err != nil {
   372  			t.Error(err)
   373  		}
   374  		errs := buf.String()
   375  		if len(errs) > 0 {
   376  			t.Errorf("\n%s", errs)
   377  		}
   378  
   379  		if err := cmd.Wait(); err != nil {
   380  			if out := stderr.String(); len(out) > 0 {
   381  				t.Fatalf("err=%s, stderr=%s", err, out)
   382  			}
   383  		}
   384  	})
   385  
   386  	t.Run("TestHttputil", func(t *testing.T) {
   387  		t.Parallel()
   388  		for _, tc := range []struct {
   389  			re       string
   390  			excludes []string
   391  		}{
   392  			{re: `\bhttp\.(Get|Put|Head)\(`},
   393  		} {
   394  			cmd, stderr, filter, err := dirCmd(
   395  				pkgDir,
   396  				"git",
   397  				append([]string{
   398  					"grep",
   399  					"-nE",
   400  					tc.re,
   401  					"--",
   402  					"*.go",
   403  				}, tc.excludes...)...,
   404  			)
   405  			if err != nil {
   406  				t.Fatal(err)
   407  			}
   408  
   409  			if err := cmd.Start(); err != nil {
   410  				t.Fatal(err)
   411  			}
   412  
   413  			if err := stream.ForEach(filter, func(s string) {
   414  				t.Errorf("\n%s <- forbidden; use 'httputil' instead", s)
   415  			}); err != nil {
   416  				t.Error(err)
   417  			}
   418  
   419  			if err := cmd.Wait(); err != nil {
   420  				if out := stderr.String(); len(out) > 0 {
   421  					t.Fatalf("err=%s, stderr=%s", err, out)
   422  				}
   423  			}
   424  		}
   425  	})
   426  
   427  	t.Run("TestEnvutil", func(t *testing.T) {
   428  		t.Parallel()
   429  		for _, tc := range []struct {
   430  			re       string
   431  			excludes []string
   432  		}{
   433  			{re: `\bos\.(Getenv|LookupEnv)\("COCKROACH`},
   434  			{
   435  				re: `\bos\.(Getenv|LookupEnv)\(`,
   436  				excludes: []string{
   437  					":!acceptance",
   438  					":!ccl/acceptanceccl/backup_test.go",
   439  					":!ccl/backupccl/backup_cloud_test.go",
   440  					":!storage/cloud",
   441  					":!ccl/workloadccl/fixture_test.go",
   442  					":!cmd",
   443  					":!nightly",
   444  					":!testutils/lint",
   445  					":!util/envutil/env.go",
   446  					":!util/log/tracebacks.go",
   447  					":!util/sdnotify/sdnotify_unix.go",
   448  				},
   449  			},
   450  		} {
   451  			cmd, stderr, filter, err := dirCmd(
   452  				pkgDir,
   453  				"git",
   454  				append([]string{
   455  					"grep",
   456  					"-nE",
   457  					tc.re,
   458  					"--",
   459  					"*.go",
   460  				}, tc.excludes...)...,
   461  			)
   462  			if err != nil {
   463  				t.Fatal(err)
   464  			}
   465  
   466  			if err := cmd.Start(); err != nil {
   467  				t.Fatal(err)
   468  			}
   469  
   470  			if err := stream.ForEach(filter, func(s string) {
   471  				t.Errorf("\n%s <- forbidden; use 'envutil' instead", s)
   472  			}); err != nil {
   473  				t.Error(err)
   474  			}
   475  
   476  			if err := cmd.Wait(); err != nil {
   477  				if out := stderr.String(); len(out) > 0 {
   478  					t.Fatalf("err=%s, stderr=%s", err, out)
   479  				}
   480  			}
   481  		}
   482  	})
   483  
   484  	t.Run("TestSyncutil", func(t *testing.T) {
   485  		t.Parallel()
   486  		cmd, stderr, filter, err := dirCmd(
   487  			pkgDir,
   488  			"git",
   489  			"grep",
   490  			"-nE",
   491  			`\bsync\.(RW)?Mutex`,
   492  			"--",
   493  			"*.go",
   494  			":!*/doc.go",
   495  			":!util/syncutil/mutex_sync.go",
   496  			":!util/syncutil/mutex_sync_race.go",
   497  		)
   498  		if err != nil {
   499  			t.Fatal(err)
   500  		}
   501  
   502  		if err := cmd.Start(); err != nil {
   503  			t.Fatal(err)
   504  		}
   505  
   506  		if err := stream.ForEach(filter, func(s string) {
   507  			t.Errorf("\n%s <- forbidden; use 'syncutil.{,RW}Mutex' instead", s)
   508  		}); err != nil {
   509  			t.Error(err)
   510  		}
   511  
   512  		if err := cmd.Wait(); err != nil {
   513  			if out := stderr.String(); len(out) > 0 {
   514  				t.Fatalf("err=%s, stderr=%s", err, out)
   515  			}
   516  		}
   517  	})
   518  
   519  	t.Run("TestSQLTelemetryDirectCount", func(t *testing.T) {
   520  		t.Parallel()
   521  		cmd, stderr, filter, err := dirCmd(
   522  			pkgDir,
   523  			"git",
   524  			"grep",
   525  			"-nE",
   526  			`[^[:alnum:]]telemetry\.Count\(`,
   527  			"--",
   528  			"sql",
   529  		)
   530  		if err != nil {
   531  			t.Fatal(err)
   532  		}
   533  
   534  		if err := cmd.Start(); err != nil {
   535  			t.Fatal(err)
   536  		}
   537  
   538  		if err := stream.ForEach(filter, func(s string) {
   539  			t.Errorf("\n%s <- forbidden; use 'sqltelemetry.xxxCounter()' / `telemetry.Inc' instead", s)
   540  		}); err != nil {
   541  			t.Error(err)
   542  		}
   543  
   544  		if err := cmd.Wait(); err != nil {
   545  			if out := stderr.String(); len(out) > 0 {
   546  				t.Fatalf("err=%s, stderr=%s", err, out)
   547  			}
   548  		}
   549  	})
   550  
   551  	t.Run("TestSQLTelemetryGetCounter", func(t *testing.T) {
   552  		t.Parallel()
   553  		cmd, stderr, filter, err := dirCmd(
   554  			pkgDir,
   555  			"git",
   556  			"grep",
   557  			"-nE",
   558  			`[^[:alnum:]]telemetry\.GetCounter`,
   559  			"--",
   560  			"sql",
   561  			":!sql/sqltelemetry",
   562  		)
   563  		if err != nil {
   564  			t.Fatal(err)
   565  		}
   566  
   567  		if err := cmd.Start(); err != nil {
   568  			t.Fatal(err)
   569  		}
   570  
   571  		if err := stream.ForEach(filter, func(s string) {
   572  			t.Errorf("\n%s <- forbidden; use 'sqltelemetry.xxxCounter() instead", s)
   573  		}); err != nil {
   574  			t.Error(err)
   575  		}
   576  
   577  		if err := cmd.Wait(); err != nil {
   578  			if out := stderr.String(); len(out) > 0 {
   579  				t.Fatalf("err=%s, stderr=%s", err, out)
   580  			}
   581  		}
   582  	})
   583  
   584  	t.Run("TestCBOPanics", func(t *testing.T) {
   585  		t.Parallel()
   586  		cmd, stderr, filter, err := dirCmd(
   587  			pkgDir,
   588  			"git",
   589  			"grep",
   590  			"-nE",
   591  			fmt.Sprintf(`[^[:alnum:]]panic\((%s|"|[a-z]+Error\{errors\.(New|Errorf)|fmt\.Errorf)`, "`"),
   592  			"--",
   593  			"sql/opt",
   594  			":!sql/opt/optgen",
   595  			":!sql/opt/testutils",
   596  			":!*.pb.go",
   597  		)
   598  		if err != nil {
   599  			t.Fatal(err)
   600  		}
   601  
   602  		if err := cmd.Start(); err != nil {
   603  			t.Fatal(err)
   604  		}
   605  
   606  		if err := stream.ForEach(filter, func(s string) {
   607  			t.Errorf("\n%s <- forbidden; use panic(errors.AssertionFailedf()) instead", s)
   608  		}); err != nil {
   609  			t.Error(err)
   610  		}
   611  
   612  		if err := cmd.Wait(); err != nil {
   613  			if out := stderr.String(); len(out) > 0 {
   614  				t.Fatalf("err=%s, stderr=%s", err, out)
   615  			}
   616  		}
   617  	})
   618  
   619  	t.Run("TestInternalErrorCodes", func(t *testing.T) {
   620  		t.Parallel()
   621  		cmd, stderr, filter, err := dirCmd(
   622  			pkgDir,
   623  			"git",
   624  			"grep",
   625  			"-nE",
   626  			`[^[:alnum:]]pgerror\.(NewError|Wrap).*pgerror\.CodeInternalError`,
   627  		)
   628  		if err != nil {
   629  			t.Fatal(err)
   630  		}
   631  
   632  		if err := cmd.Start(); err != nil {
   633  			t.Fatal(err)
   634  		}
   635  
   636  		if err := stream.ForEach(filter, func(s string) {
   637  			t.Errorf("\n%s <- forbidden; use errors.AssertionFailedf() instead", s)
   638  		}); err != nil {
   639  			t.Error(err)
   640  		}
   641  
   642  		if err := cmd.Wait(); err != nil {
   643  			if out := stderr.String(); len(out) > 0 {
   644  				t.Fatalf("err=%s, stderr=%s", err, out)
   645  			}
   646  		}
   647  	})
   648  
   649  	t.Run("TestTodoStyle", func(t *testing.T) {
   650  		t.Parallel()
   651  		// TODO(tamird): enforce presence of name.
   652  		cmd, stderr, filter, err := dirCmd(pkgDir, "git", "grep", "-nE", `\sTODO\([^)]+\)[^:]`, "--", "*.go")
   653  		if err != nil {
   654  			t.Fatal(err)
   655  		}
   656  
   657  		if err := cmd.Start(); err != nil {
   658  			t.Fatal(err)
   659  		}
   660  
   661  		if err := stream.ForEach(filter, func(s string) {
   662  			t.Errorf(`%s <- use 'TODO(...): ' instead`, s)
   663  		}); err != nil {
   664  			t.Error(err)
   665  		}
   666  
   667  		if err := cmd.Wait(); err != nil {
   668  			if out := stderr.String(); len(out) > 0 {
   669  				t.Fatalf("err=%s, stderr=%s", err, out)
   670  			}
   671  		}
   672  	})
   673  
   674  	t.Run("TestNonZeroOffsetInTests", func(t *testing.T) {
   675  		t.Parallel()
   676  		cmd, stderr, filter, err := dirCmd(pkgDir, "git", "grep", "-nE", `hlc\.NewClock\([^)]+, 0\)`, "--", "*_test.go")
   677  		if err != nil {
   678  			t.Fatal(err)
   679  		}
   680  
   681  		if err := cmd.Start(); err != nil {
   682  			t.Fatal(err)
   683  		}
   684  
   685  		if err := stream.ForEach(filter, func(s string) {
   686  			t.Errorf(`%s <- use non-zero clock offset`, s)
   687  		}); err != nil {
   688  			t.Error(err)
   689  		}
   690  
   691  		if err := cmd.Wait(); err != nil {
   692  			if out := stderr.String(); len(out) > 0 {
   693  				t.Fatalf("err=%s, stderr=%s", err, out)
   694  			}
   695  		}
   696  	})
   697  
   698  	t.Run("TestTimeutil", func(t *testing.T) {
   699  		t.Parallel()
   700  		cmd, stderr, filter, err := dirCmd(
   701  			pkgDir,
   702  			"git",
   703  			"grep",
   704  			"-nE",
   705  			`\btime\.(Now|Since|Unix)\(`,
   706  			"--",
   707  			"*.go",
   708  			":!**/embedded.go",
   709  			":!util/timeutil/time.go",
   710  			":!util/timeutil/now_unix.go",
   711  			":!util/timeutil/now_windows.go",
   712  			":!util/tracing/tracer_span.go",
   713  			":!util/tracing/tracer.go",
   714  		)
   715  		if err != nil {
   716  			t.Fatal(err)
   717  		}
   718  
   719  		if err := cmd.Start(); err != nil {
   720  			t.Fatal(err)
   721  		}
   722  
   723  		if err := stream.ForEach(filter, func(s string) {
   724  			t.Errorf("\n%s <- forbidden; use 'timeutil' instead", s)
   725  		}); err != nil {
   726  			t.Error(err)
   727  		}
   728  
   729  		if err := cmd.Wait(); err != nil {
   730  			if out := stderr.String(); len(out) > 0 {
   731  				t.Fatalf("err=%s, stderr=%s", err, out)
   732  			}
   733  		}
   734  	})
   735  
   736  	t.Run("TestContext", func(t *testing.T) {
   737  		t.Parallel()
   738  		cmd, stderr, filter, err := dirCmd(
   739  			pkgDir,
   740  			"git",
   741  			"grep",
   742  			"-nE",
   743  			`\bcontext\.With(Deadline|Timeout)\(`,
   744  			"--",
   745  			"*.go",
   746  			":!util/contextutil/context.go",
   747  			// TODO(jordan): ban these too?
   748  			":!server/debug/**",
   749  			":!workload/**",
   750  			":!*_test.go",
   751  			":!cli/debug_synctest.go",
   752  			":!cmd/**",
   753  		)
   754  		if err != nil {
   755  			t.Fatal(err)
   756  		}
   757  
   758  		if err := cmd.Start(); err != nil {
   759  			t.Fatal(err)
   760  		}
   761  
   762  		if err := stream.ForEach(filter, func(s string) {
   763  			t.Errorf("\n%s <- forbidden; use 'contextutil.RunWithTimeout' instead", s)
   764  		}); err != nil {
   765  			t.Error(err)
   766  		}
   767  
   768  		if err := cmd.Wait(); err != nil {
   769  			if out := stderr.String(); len(out) > 0 {
   770  				t.Fatalf("err=%s, stderr=%s", err, out)
   771  			}
   772  		}
   773  	})
   774  
   775  	t.Run("TestGrpc", func(t *testing.T) {
   776  		t.Parallel()
   777  		cmd, stderr, filter, err := dirCmd(
   778  			pkgDir,
   779  			"git",
   780  			"grep",
   781  			"-nE",
   782  			`\bgrpc\.NewServer\(`,
   783  			"--",
   784  			"*.go",
   785  			":!rpc/context_test.go",
   786  			":!rpc/context.go",
   787  			":!rpc/nodedialer/nodedialer_test.go",
   788  			":!util/grpcutil/grpc_util_test.go",
   789  			":!cli/systembench/network_test_server.go",
   790  		)
   791  		if err != nil {
   792  			t.Fatal(err)
   793  		}
   794  
   795  		if err := cmd.Start(); err != nil {
   796  			t.Fatal(err)
   797  		}
   798  
   799  		if err := stream.ForEach(filter, func(s string) {
   800  			t.Errorf("\n%s <- forbidden; use 'rpc.NewServer' instead", s)
   801  		}); err != nil {
   802  			t.Error(err)
   803  		}
   804  
   805  		if err := cmd.Wait(); err != nil {
   806  			if out := stderr.String(); len(out) > 0 {
   807  				t.Fatalf("err=%s, stderr=%s", err, out)
   808  			}
   809  		}
   810  	})
   811  
   812  	t.Run("TestProtoClone", func(t *testing.T) {
   813  		t.Parallel()
   814  		cmd, stderr, filter, err := dirCmd(
   815  			pkgDir,
   816  			"git",
   817  			"grep",
   818  			"-nE",
   819  			`\.Clone\([^)]`,
   820  			"--",
   821  			"*.go",
   822  			":!util/protoutil/clone_test.go",
   823  			":!util/protoutil/clone.go",
   824  		)
   825  		if err != nil {
   826  			t.Fatal(err)
   827  		}
   828  
   829  		if err := cmd.Start(); err != nil {
   830  			t.Fatal(err)
   831  		}
   832  
   833  		if err := stream.ForEach(stream.Sequence(
   834  			filter,
   835  			stream.GrepNot(`protoutil\.Clone\(`),
   836  		), func(s string) {
   837  			t.Errorf("\n%s <- forbidden; use 'protoutil.Clone' instead", s)
   838  		}); err != nil {
   839  			t.Error(err)
   840  		}
   841  
   842  		if err := cmd.Wait(); err != nil {
   843  			if out := stderr.String(); len(out) > 0 {
   844  				t.Fatalf("err=%s, stderr=%s", err, out)
   845  			}
   846  		}
   847  	})
   848  
   849  	t.Run("TestTParallel", func(t *testing.T) {
   850  		t.Parallel()
   851  		cmd, stderr, filter, err := dirCmd(
   852  			pkgDir,
   853  			"git",
   854  			"grep",
   855  			"-nE",
   856  			`\.Parallel\(\)`,
   857  			"--",
   858  			"*.go",
   859  			":!testutils/lint/*.go",
   860  		)
   861  		if err != nil {
   862  			t.Fatal(err)
   863  		}
   864  
   865  		if err := cmd.Start(); err != nil {
   866  			t.Fatal(err)
   867  		}
   868  
   869  		if err := stream.ForEach(stream.Sequence(
   870  			filter,
   871  			stream.GrepNot(`// SAFE FOR TESTING`),
   872  		), func(s string) {
   873  			t.Errorf("\n%s <- forbidden, use a sync.WaitGroup instead (cf https://github.com/golang/go/issues/31651)", s)
   874  		}); err != nil {
   875  			t.Error(err)
   876  		}
   877  
   878  		if err := cmd.Wait(); err != nil {
   879  			if out := stderr.String(); len(out) > 0 {
   880  				t.Fatalf("err=%s, stderr=%s", err, out)
   881  			}
   882  		}
   883  	})
   884  
   885  	t.Run("TestProtoMarshal", func(t *testing.T) {
   886  		t.Parallel()
   887  		cmd, stderr, filter, err := dirCmd(
   888  			pkgDir,
   889  			"git",
   890  			"grep",
   891  			"-nE",
   892  			`\.Marshal\(`,
   893  			"--",
   894  			"*.go",
   895  			":!sql/*.pb.go",
   896  			":!util/protoutil/marshal.go",
   897  			":!util/protoutil/marshaler.go",
   898  			":!settings/settings_test.go",
   899  		)
   900  		if err != nil {
   901  			t.Fatal(err)
   902  		}
   903  
   904  		if err := cmd.Start(); err != nil {
   905  			t.Fatal(err)
   906  		}
   907  
   908  		if err := stream.ForEach(stream.Sequence(
   909  			filter,
   910  			stream.GrepNot(`(json|yaml|protoutil|xml|\.Field|ewkb|wkb|wkt)\.Marshal\(`),
   911  		), func(s string) {
   912  			t.Errorf("\n%s <- forbidden; use 'protoutil.Marshal' instead", s)
   913  		}); err != nil {
   914  			t.Error(err)
   915  		}
   916  
   917  		if err := cmd.Wait(); err != nil {
   918  			if out := stderr.String(); len(out) > 0 {
   919  				t.Fatalf("err=%s, stderr=%s", err, out)
   920  			}
   921  		}
   922  	})
   923  
   924  	t.Run("TestProtoUnmarshal", func(t *testing.T) {
   925  		t.Parallel()
   926  		cmd, stderr, filter, err := dirCmd(
   927  			pkgDir,
   928  			"git",
   929  			"grep",
   930  			"-nE",
   931  			`\.Unmarshal\(`,
   932  			"--",
   933  			"*.go",
   934  			":!*.pb.go",
   935  			":!util/protoutil/marshal.go",
   936  			":!util/protoutil/marshaler.go",
   937  		)
   938  		if err != nil {
   939  			t.Fatal(err)
   940  		}
   941  
   942  		if err := cmd.Start(); err != nil {
   943  			t.Fatal(err)
   944  		}
   945  
   946  		if err := stream.ForEach(stream.Sequence(
   947  			filter,
   948  			stream.GrepNot(`(json|jsonpb|yaml|xml|protoutil|toml|Codec|ewkb|wkb)\.Unmarshal\(`),
   949  		), func(s string) {
   950  			t.Errorf("\n%s <- forbidden; use 'protoutil.Unmarshal' instead", s)
   951  		}); err != nil {
   952  			t.Error(err)
   953  		}
   954  
   955  		if err := cmd.Wait(); err != nil {
   956  			if out := stderr.String(); len(out) > 0 {
   957  				t.Fatalf("err=%s, stderr=%s", err, out)
   958  			}
   959  		}
   960  	})
   961  
   962  	t.Run("TestProtoMessage", func(t *testing.T) {
   963  		t.Parallel()
   964  		cmd, stderr, filter, err := dirCmd(
   965  			pkgDir,
   966  			"git",
   967  			"grep",
   968  			"-nEw",
   969  			`proto\.Message`,
   970  			"--",
   971  			"*.go",
   972  			":!*.pb.go",
   973  			":!*.pb.gw.go",
   974  			":!sql/pgwire/pgerror/severity.go",
   975  			":!sql/pgwire/pgerror/with_candidate_code.go",
   976  			":!sql/colexecbase/colexecerror/error.go",
   977  			":!util/protoutil/jsonpb_marshal.go",
   978  			":!util/protoutil/marshal.go",
   979  			":!util/protoutil/marshaler.go",
   980  			":!util/tracing/tracer_span.go",
   981  		)
   982  		if err != nil {
   983  			t.Fatal(err)
   984  		}
   985  
   986  		if err := cmd.Start(); err != nil {
   987  			t.Fatal(err)
   988  		}
   989  
   990  		if err := stream.ForEach(stream.Sequence(
   991  			filter,
   992  		), func(s string) {
   993  			t.Errorf("\n%s <- forbidden; use 'protoutil.Message' instead", s)
   994  		}); err != nil {
   995  			t.Error(err)
   996  		}
   997  
   998  		if err := cmd.Wait(); err != nil {
   999  			if out := stderr.String(); len(out) > 0 {
  1000  				t.Fatalf("err=%s, stderr=%s", err, out)
  1001  			}
  1002  		}
  1003  	})
  1004  
  1005  	t.Run("TestYaml", func(t *testing.T) {
  1006  		t.Parallel()
  1007  		cmd, stderr, filter, err := dirCmd(pkgDir, "git", "grep", "-nE", `\byaml\.Unmarshal\(`, "--", "*.go")
  1008  		if err != nil {
  1009  			t.Fatal(err)
  1010  		}
  1011  
  1012  		if err := cmd.Start(); err != nil {
  1013  			t.Fatal(err)
  1014  		}
  1015  
  1016  		if err := stream.ForEach(filter, func(s string) {
  1017  			t.Errorf("\n%s <- forbidden; use 'yaml.UnmarshalStrict' instead", s)
  1018  		}); err != nil {
  1019  			t.Error(err)
  1020  		}
  1021  
  1022  		if err := cmd.Wait(); err != nil {
  1023  			if out := stderr.String(); len(out) > 0 {
  1024  				t.Fatalf("err=%s, stderr=%s", err, out)
  1025  			}
  1026  		}
  1027  	})
  1028  
  1029  	t.Run("TestImportNames", func(t *testing.T) {
  1030  		t.Parallel()
  1031  		cmd, stderr, filter, err := dirCmd(pkgDir, "git", "grep", "-nE", `^(import|\s+)(\w+ )?"database/sql"$`, "--", "*.go")
  1032  		if err != nil {
  1033  			t.Fatal(err)
  1034  		}
  1035  
  1036  		if err := cmd.Start(); err != nil {
  1037  			t.Fatal(err)
  1038  		}
  1039  
  1040  		if err := stream.ForEach(stream.Sequence(
  1041  			filter,
  1042  			stream.GrepNot(`gosql "database/sql"`),
  1043  		), func(s string) {
  1044  			t.Errorf("\n%s <- forbidden; import 'database/sql' as 'gosql' to avoid confusion with 'cockroach/sql'", s)
  1045  		}); err != nil {
  1046  			t.Error(err)
  1047  		}
  1048  
  1049  		if err := cmd.Wait(); err != nil {
  1050  			if out := stderr.String(); len(out) > 0 {
  1051  				t.Fatalf("err=%s, stderr=%s", err, out)
  1052  			}
  1053  		}
  1054  	})
  1055  
  1056  	t.Run("TestMisspell", func(t *testing.T) {
  1057  		t.Parallel()
  1058  		if pkgSpecified {
  1059  			t.Skip("PKG specified")
  1060  		}
  1061  		cmd, stderr, filter, err := dirCmd(pkgDir, "git", "ls-files")
  1062  		if err != nil {
  1063  			t.Fatal(err)
  1064  		}
  1065  
  1066  		if err := cmd.Start(); err != nil {
  1067  			t.Fatal(err)
  1068  		}
  1069  
  1070  		ignoredRules := []string{
  1071  			"licence",
  1072  			"mitre",   // PostGIS commands spell these as mitre.
  1073  			"analyse", // required by SQL grammar
  1074  		}
  1075  
  1076  		if err := stream.ForEach(stream.Sequence(
  1077  			filter,
  1078  			stream.GrepNot(`.*\.lock`),
  1079  			stream.GrepNot(`^storage\/rocksdb_error_dict\.go$`),
  1080  			stream.GrepNot(`^workload/tpcds/tpcds.go$`),
  1081  			stream.Map(func(s string) string {
  1082  				return filepath.Join(pkgDir, s)
  1083  			}),
  1084  			stream.Xargs("misspell", "-locale", "US", "-i", strings.Join(ignoredRules, ",")),
  1085  		), func(s string) {
  1086  			t.Errorf("\n%s", s)
  1087  		}); err != nil {
  1088  			t.Error(err)
  1089  		}
  1090  
  1091  		if err := cmd.Wait(); err != nil {
  1092  			if out := stderr.String(); len(out) > 0 {
  1093  				t.Fatalf("err=%s, stderr=%s", err, out)
  1094  			}
  1095  		}
  1096  	})
  1097  
  1098  	t.Run("TestGofmtSimplify", func(t *testing.T) {
  1099  		t.Parallel()
  1100  		if pkgSpecified {
  1101  			t.Skip("PKG specified")
  1102  		}
  1103  
  1104  		cmd, stderr, filter, err := dirCmd(pkgDir, "git", "ls-files", "*.go", ":!*/testdata/*", ":!*_generated.go")
  1105  		if err != nil {
  1106  			t.Fatal(err)
  1107  		}
  1108  
  1109  		if err := cmd.Start(); err != nil {
  1110  			t.Fatal(err)
  1111  		}
  1112  
  1113  		var buf bytes.Buffer
  1114  		if err := stream.ForEach(
  1115  			stream.Sequence(
  1116  				filter,
  1117  				stream.Map(func(s string) string {
  1118  					return filepath.Join(pkgDir, s)
  1119  				}),
  1120  				stream.Xargs("gofmt", "-s", "-d", "-l"),
  1121  			), func(s string) {
  1122  				fmt.Fprintln(&buf, s)
  1123  			}); err != nil {
  1124  			t.Error(err)
  1125  		}
  1126  		errs := buf.String()
  1127  		if len(errs) > 0 {
  1128  			t.Errorf("\n%s", errs)
  1129  		}
  1130  
  1131  		if err := cmd.Wait(); err != nil {
  1132  			if out := stderr.String(); len(out) > 0 {
  1133  				t.Fatalf("err=%s, stderr=%s", err, out)
  1134  			}
  1135  		}
  1136  	})
  1137  
  1138  	t.Run("TestCrlfmt", func(t *testing.T) {
  1139  		t.Parallel()
  1140  		if pkgSpecified {
  1141  			t.Skip("PKG specified")
  1142  		}
  1143  		ignore := `\.(pb(\.gw)?)|(\.[eo]g)\.go|/testdata/|^sql/parser/sql\.go$|_generated\.go$`
  1144  		cmd, stderr, filter, err := dirCmd(pkgDir, "crlfmt", "-fast", "-ignore", ignore, "-tab", "2", ".")
  1145  		if err != nil {
  1146  			t.Fatal(err)
  1147  		}
  1148  
  1149  		if err := cmd.Start(); err != nil {
  1150  			t.Fatal(err)
  1151  		}
  1152  
  1153  		var buf bytes.Buffer
  1154  		if err := stream.ForEach(filter, func(s string) {
  1155  			fmt.Fprintln(&buf, s)
  1156  		}); err != nil {
  1157  			t.Error(err)
  1158  		}
  1159  		errs := buf.String()
  1160  		if len(errs) > 0 {
  1161  			t.Errorf("\n%s", errs)
  1162  		}
  1163  
  1164  		if err := cmd.Wait(); err != nil {
  1165  			if out := stderr.String(); len(out) > 0 {
  1166  				t.Fatalf("err=%s, stderr=%s", err, out)
  1167  			}
  1168  		}
  1169  
  1170  		if t.Failed() {
  1171  			args := append([]string(nil), cmd.Args[1:len(cmd.Args)-1]...)
  1172  			args = append(args, "-w", pkgDir)
  1173  			for i := range args {
  1174  				args[i] = strconv.Quote(args[i])
  1175  			}
  1176  			t.Logf("run the following to fix your formatting:\n"+
  1177  				"\nbin/crlfmt %s\n\n"+
  1178  				"Don't forget to add amend the result to the correct commits.",
  1179  				strings.Join(args, " "),
  1180  			)
  1181  		}
  1182  	})
  1183  
  1184  	t.Run("TestAuthorTags", func(t *testing.T) {
  1185  		t.Parallel()
  1186  		cmd, stderr, filter, err := dirCmd(pkgDir, "git", "grep", "-lE", "^// Author:")
  1187  		if err != nil {
  1188  			t.Fatal(err)
  1189  		}
  1190  
  1191  		if err := cmd.Start(); err != nil {
  1192  			t.Fatal(err)
  1193  		}
  1194  
  1195  		if err := stream.ForEach(filter, func(s string) {
  1196  			t.Errorf("\n%s <- please remove the Author comment within", s)
  1197  		}); err != nil {
  1198  			t.Error(err)
  1199  		}
  1200  
  1201  		if err := cmd.Wait(); err != nil {
  1202  			if out := stderr.String(); len(out) > 0 {
  1203  				t.Fatalf("err=%s, stderr=%s", err, out)
  1204  			}
  1205  		}
  1206  	})
  1207  
  1208  	// Things that are packaged scoped are below here.
  1209  	pkgScope := pkgVar
  1210  	if !pkgSpecified {
  1211  		pkgScope = "./pkg/..."
  1212  	}
  1213  
  1214  	t.Run("TestForbiddenImports", func(t *testing.T) {
  1215  		t.Parallel()
  1216  
  1217  		// forbiddenImportPkg -> permittedReplacementPkg
  1218  		forbiddenImports := map[string]string{
  1219  			"golang.org/x/net/context":                    "context",
  1220  			"log":                                         "util/log",
  1221  			"github.com/golang/protobuf/proto":            "github.com/gogo/protobuf/proto",
  1222  			"github.com/satori/go.uuid":                   "util/uuid",
  1223  			"golang.org/x/sync/singleflight":              "github.com/cockroachdb/cockroach/pkg/util/syncutil/singleflight",
  1224  			"syscall":                                     "sysutil",
  1225  			"errors":                                      "github.com/cockroachdb/errors",
  1226  			"github.com/pkg/errors":                       "github.com/cockroachdb/errors",
  1227  			"github.com/cockroachdb/errors/assert":        "github.com/cockroachdb/errors",
  1228  			"github.com/cockroachdb/errors/barriers":      "github.com/cockroachdb/errors",
  1229  			"github.com/cockroachdb/errors/contexttags":   "github.com/cockroachdb/errors",
  1230  			"github.com/cockroachdb/errors/domains":       "github.com/cockroachdb/errors",
  1231  			"github.com/cockroachdb/errors/errbase":       "github.com/cockroachdb/errors",
  1232  			"github.com/cockroachdb/errors/errutil":       "github.com/cockroachdb/errors",
  1233  			"github.com/cockroachdb/errors/issuelink":     "github.com/cockroachdb/errors",
  1234  			"github.com/cockroachdb/errors/markers":       "github.com/cockroachdb/errors",
  1235  			"github.com/cockroachdb/errors/report":        "github.com/cockroachdb/errors",
  1236  			"github.com/cockroachdb/errors/safedetails":   "github.com/cockroachdb/errors",
  1237  			"github.com/cockroachdb/errors/secondary":     "github.com/cockroachdb/errors",
  1238  			"github.com/cockroachdb/errors/telemetrykeys": "github.com/cockroachdb/errors",
  1239  			"github.com/cockroachdb/errors/withstack":     "github.com/cockroachdb/errors",
  1240  		}
  1241  
  1242  		// grepBuf creates a grep string that matches any forbidden import pkgs.
  1243  		var grepBuf bytes.Buffer
  1244  		grepBuf.WriteByte('(')
  1245  		for forbiddenPkg := range forbiddenImports {
  1246  			grepBuf.WriteByte('|')
  1247  			grepBuf.WriteString(regexp.QuoteMeta(forbiddenPkg))
  1248  		}
  1249  		grepBuf.WriteString(")$")
  1250  
  1251  		filter := stream.FilterFunc(func(arg stream.Arg) error {
  1252  			for _, useAllFiles := range []bool{false, true} {
  1253  				buildContext := build.Default
  1254  				buildContext.CgoEnabled = true
  1255  				buildContext.UseAllFiles = useAllFiles
  1256  			outer:
  1257  				for path := range buildutil.ExpandPatterns(&buildContext, []string{filepath.Join(cockroachDB, pkgScope)}) {
  1258  					importPkg, err := buildContext.Import(path, crdb.Dir, 0)
  1259  					switch err.(type) {
  1260  					case nil:
  1261  						for _, s := range importPkg.Imports {
  1262  							arg.Out <- importPkg.ImportPath + ": " + s
  1263  						}
  1264  						for _, s := range importPkg.TestImports {
  1265  							arg.Out <- importPkg.ImportPath + ": " + s
  1266  						}
  1267  						for _, s := range importPkg.XTestImports {
  1268  							arg.Out <- importPkg.ImportPath + ": " + s
  1269  						}
  1270  					case *build.NoGoError:
  1271  					case *build.MultiplePackageError:
  1272  						if useAllFiles {
  1273  							continue outer
  1274  						}
  1275  					default:
  1276  						return errors.Wrapf(err, "error loading package %s", path)
  1277  					}
  1278  				}
  1279  			}
  1280  			return nil
  1281  		})
  1282  		settingsPkgPrefix := `github.com/cockroachdb/cockroach/pkg/settings`
  1283  		if err := stream.ForEach(stream.Sequence(
  1284  			filter,
  1285  			stream.Sort(),
  1286  			stream.Uniq(),
  1287  			stream.Grep(`^`+settingsPkgPrefix+`: | `+grepBuf.String()),
  1288  			stream.GrepNot(`cockroach/pkg/cmd/`),
  1289  			stream.GrepNot(`cockroach/pkg/testutils/lint: log$`),
  1290  			stream.GrepNot(`cockroach/pkg/util/sysutil: syscall$`),
  1291  			stream.GrepNot(`cockroach/pkg/util/log: github\.com/pkg/errors$`),
  1292  			stream.GrepNot(`cockroach/pkg/(base|security|util/(log|randutil|stop)): log$`),
  1293  			stream.GrepNot(`cockroach/pkg/(server/serverpb|ts/tspb): github\.com/golang/protobuf/proto$`),
  1294  
  1295  			stream.GrepNot(`cockroach/pkg/util/uuid: github\.com/satori/go\.uuid$`),
  1296  		), func(s string) {
  1297  			pkgStr := strings.Split(s, ": ")
  1298  			importingPkg, importedPkg := pkgStr[0], pkgStr[1]
  1299  
  1300  			// Test that a disallowed package is not imported.
  1301  			if replPkg, ok := forbiddenImports[importedPkg]; ok {
  1302  				t.Errorf("\n%s <- please use %q instead of %q", s, replPkg, importedPkg)
  1303  			}
  1304  
  1305  			// Test that the settings package does not import CRDB dependencies.
  1306  			if importingPkg == settingsPkgPrefix && strings.HasPrefix(importedPkg, cockroachDB) {
  1307  				switch {
  1308  				case strings.HasSuffix(s, "humanizeutil"):
  1309  				case strings.HasSuffix(s, "protoutil"):
  1310  				case strings.HasSuffix(s, "testutils"):
  1311  				case strings.HasSuffix(s, "syncutil"):
  1312  				case strings.HasSuffix(s, settingsPkgPrefix):
  1313  				default:
  1314  					t.Errorf("%s <- please don't add CRDB dependencies to settings pkg", s)
  1315  				}
  1316  			}
  1317  		}); err != nil {
  1318  			t.Error(err)
  1319  		}
  1320  	})
  1321  
  1322  	// TODO(tamird): replace this with errcheck.NewChecker() when
  1323  	// https://github.com/dominikh/go-tools/issues/57 is fixed.
  1324  	t.Run("TestErrCheck", func(t *testing.T) {
  1325  		if testing.Short() {
  1326  			t.Skip("short flag")
  1327  		}
  1328  		excludesPath, err := filepath.Abs(filepath.Join("testdata", "errcheck_excludes.txt"))
  1329  		if err != nil {
  1330  			t.Fatal(err)
  1331  		}
  1332  		// errcheck uses 2GB of ram (as of 2017-07-13), so don't parallelize it.
  1333  		cmd, stderr, filter, err := dirCmd(
  1334  			crdb.Dir,
  1335  			"errcheck",
  1336  			"-exclude",
  1337  			excludesPath,
  1338  			pkgScope,
  1339  		)
  1340  		if err != nil {
  1341  			t.Fatal(err)
  1342  		}
  1343  
  1344  		if err := cmd.Start(); err != nil {
  1345  			t.Fatal(err)
  1346  		}
  1347  
  1348  		if err := stream.ForEach(filter, func(s string) {
  1349  			t.Errorf("%s <- unchecked error", s)
  1350  		}); err != nil {
  1351  			t.Error(err)
  1352  		}
  1353  
  1354  		if err := cmd.Wait(); err != nil {
  1355  			if out := stderr.String(); len(out) > 0 {
  1356  				t.Fatalf("err=%s, stderr=%s", err, out)
  1357  			}
  1358  		}
  1359  	})
  1360  
  1361  	t.Run("TestReturnCheck", func(t *testing.T) {
  1362  		if testing.Short() {
  1363  			t.Skip("short flag")
  1364  		}
  1365  		// returncheck uses 2GB of ram (as of 2017-07-13), so don't parallelize it.
  1366  		cmd, stderr, filter, err := dirCmd(crdb.Dir, "returncheck", pkgScope)
  1367  		if err != nil {
  1368  			t.Fatal(err)
  1369  		}
  1370  
  1371  		if err := cmd.Start(); err != nil {
  1372  			t.Fatal(err)
  1373  		}
  1374  
  1375  		if err := stream.ForEach(filter, func(s string) {
  1376  			t.Errorf("%s <- unchecked error", s)
  1377  		}); err != nil {
  1378  			t.Error(err)
  1379  		}
  1380  
  1381  		if err := cmd.Wait(); err != nil {
  1382  			if out := stderr.String(); len(out) > 0 {
  1383  				t.Fatalf("err=%s, stderr=%s", err, out)
  1384  			}
  1385  		}
  1386  	})
  1387  
  1388  	t.Run("TestGolint", func(t *testing.T) {
  1389  		t.Parallel()
  1390  		cmd, stderr, filter, err := dirCmd(crdb.Dir, "golint", pkgScope)
  1391  		if err != nil {
  1392  			t.Fatal(err)
  1393  		}
  1394  
  1395  		if err := cmd.Start(); err != nil {
  1396  			t.Fatal(err)
  1397  		}
  1398  
  1399  		if err := stream.ForEach(
  1400  			stream.Sequence(
  1401  				filter,
  1402  				stream.GrepNot("sql/.*exported func .* returns unexported type sql.planNode"),
  1403  				stream.GrepNot("pkg/sql/types/types.go.* var Uuid should be UUID"),
  1404  				stream.GrepNot("pkg/sql/oidext/oidext.go.*don't use underscores in Go names; const T_"),
  1405  			), func(s string) {
  1406  				t.Errorf("\n%s", s)
  1407  			}); err != nil {
  1408  			t.Error(err)
  1409  		}
  1410  
  1411  		if err := cmd.Wait(); err != nil {
  1412  			if out := stderr.String(); len(out) > 0 {
  1413  				t.Fatalf("err=%s, stderr=%s", err, out)
  1414  			}
  1415  		}
  1416  	})
  1417  
  1418  	t.Run("TestStaticCheck", func(t *testing.T) {
  1419  		// staticcheck uses 2.4GB of ram (as of 2019-05-10), so don't parallelize it.
  1420  		if testing.Short() {
  1421  			t.Skip("short flag")
  1422  		}
  1423  		cmd, stderr, filter, err := dirCmd(
  1424  			crdb.Dir,
  1425  			"staticcheck",
  1426  			"-unused.whole-program",
  1427  			pkgScope,
  1428  		)
  1429  		if err != nil {
  1430  			t.Fatal(err)
  1431  		}
  1432  
  1433  		if err := cmd.Start(); err != nil {
  1434  			t.Fatal(err)
  1435  		}
  1436  
  1437  		if err := stream.ForEach(
  1438  			stream.Sequence(
  1439  				filter,
  1440  				// Skip .pb.go and .pb.gw.go generated files.
  1441  				stream.GrepNot(`pkg/.*\.pb(\.gw|)\.go:`),
  1442  				// Skip generated file.
  1443  				stream.GrepNot(`pkg/ui/distoss/bindata.go`),
  1444  				stream.GrepNot(`pkg/ui/distccl/bindata.go`),
  1445  				// sql.go is the generated parser, which sets sqlDollar in all cases,
  1446  				// even if it might not be used again.
  1447  				stream.GrepNot(`pkg/sql/parser/sql.go:.*this value of sqlDollar is never used`),
  1448  				// Generated file containing many unused postgres error codes.
  1449  				stream.GrepNot(`pkg/sql/pgwire/pgcode/codes.go:.* const .* is unused`),
  1450  				// The methods in exprgen.customFuncs are used via reflection.
  1451  				stream.GrepNot(`pkg/sql/opt/optgen/exprgen/custom_funcs.go:.* func .* is unused`),
  1452  				// Using deprecated method to COPY.
  1453  				stream.GrepNot(`pkg/cli/nodelocal.go:.* stmt.Exec is deprecated: .*`),
  1454  			), func(s string) {
  1455  				t.Errorf("\n%s", s)
  1456  			}); err != nil {
  1457  			t.Error(err)
  1458  		}
  1459  
  1460  		if err := cmd.Wait(); err != nil {
  1461  			if out := stderr.String(); len(out) > 0 {
  1462  				t.Fatalf("err=%s, stderr=%s", err, out)
  1463  			}
  1464  		}
  1465  	})
  1466  
  1467  	t.Run("TestVectorizedPanics", func(t *testing.T) {
  1468  		t.Parallel()
  1469  		cmd, stderr, filter, err := dirCmd(
  1470  			pkgDir,
  1471  			"git",
  1472  			"grep",
  1473  			"-nE",
  1474  			fmt.Sprintf(`panic\(.*\)`),
  1475  			"--",
  1476  			// NOTE: if you're adding a new package to the list here because it
  1477  			// uses "panic-catch" error propagation mechanism of the vectorized
  1478  			// engine, don't forget to "register" the newly added package in
  1479  			// sql/colexecbase/colexecerror/error.go file.
  1480  			"sql/col*",
  1481  			":!sql/colexecbase/colexecerror/error.go",
  1482  			":!sql/colexec/execpb/stats.pb.go",
  1483  			":!sql/colflow/vectorized_panic_propagation_test.go",
  1484  		)
  1485  		if err != nil {
  1486  			t.Fatal(err)
  1487  		}
  1488  
  1489  		if err := cmd.Start(); err != nil {
  1490  			t.Fatal(err)
  1491  		}
  1492  
  1493  		if err := stream.ForEach(filter, func(s string) {
  1494  			t.Errorf("\n%s <- forbidden; use either colexecerror.InternalError() or colexecerror.ExpectedError() instead", s)
  1495  		}); err != nil {
  1496  			t.Error(err)
  1497  		}
  1498  
  1499  		if err := cmd.Wait(); err != nil {
  1500  			if out := stderr.String(); len(out) > 0 {
  1501  				t.Fatalf("err=%s, stderr=%s", err, out)
  1502  			}
  1503  		}
  1504  	})
  1505  
  1506  	t.Run("TestVectorizedAllocator", func(t *testing.T) {
  1507  		t.Parallel()
  1508  		cmd, stderr, filter, err := dirCmd(
  1509  			pkgDir,
  1510  			"git",
  1511  			"grep",
  1512  			"-nE",
  1513  			// We prohibit usage of:
  1514  			// - coldata.NewMemBatch
  1515  			// - coldata.NewMemBatchWithSize
  1516  			// - coldata.NewMemColumn
  1517  			// - coldata.Batch.AppendCol
  1518  			// TODO(yuzefovich): prohibit call to coldata.NewMemBatchNoCols.
  1519  			fmt.Sprintf(`(coldata\.NewMem(Batch|BatchWithSize|Column)|\.AppendCol)\(`),
  1520  			"--",
  1521  			// TODO(yuzefovich): prohibit calling coldata.* methods from other
  1522  			// sql/col* packages.
  1523  			"sql/colexec",
  1524  			"sql/colflow",
  1525  			":!sql/colexec/simple_project.go",
  1526  		)
  1527  		if err != nil {
  1528  			t.Fatal(err)
  1529  		}
  1530  
  1531  		if err := cmd.Start(); err != nil {
  1532  			t.Fatal(err)
  1533  		}
  1534  
  1535  		if err := stream.ForEach(filter, func(s string) {
  1536  			t.Errorf("\n%s <- forbidden; use colmem.Allocator object instead", s)
  1537  		}); err != nil {
  1538  			t.Error(err)
  1539  		}
  1540  
  1541  		if err := cmd.Wait(); err != nil {
  1542  			if out := stderr.String(); len(out) > 0 {
  1543  				t.Fatalf("err=%s, stderr=%s", err, out)
  1544  			}
  1545  		}
  1546  	})
  1547  
  1548  	t.Run("TestVectorizedAppendColumn", func(t *testing.T) {
  1549  		t.Parallel()
  1550  		cmd, stderr, filter, err := dirCmd(
  1551  			pkgDir,
  1552  			"git",
  1553  			"grep",
  1554  			"-nE",
  1555  			// We prohibit usage of Allocator.MaybeAppendColumn outside of
  1556  			// vectorTypeEnforcer and batchSchemaPrefixEnforcer.
  1557  			fmt.Sprintf(`(MaybeAppendColumn)\(`),
  1558  			"--",
  1559  			"sql/col*",
  1560  			":!sql/colexec/operator.go",
  1561  			":!sql/colmem/allocator.go",
  1562  		)
  1563  		if err != nil {
  1564  			t.Fatal(err)
  1565  		}
  1566  
  1567  		if err := cmd.Start(); err != nil {
  1568  			t.Fatal(err)
  1569  		}
  1570  
  1571  		if err := stream.ForEach(filter, func(s string) {
  1572  			t.Errorf("\n%s <- forbidden; use colexec.vectorTypeEnforcer "+
  1573  				"or colexec.batchSchemaPrefixEnforcer", s)
  1574  		}); err != nil {
  1575  			t.Error(err)
  1576  		}
  1577  
  1578  		if err := cmd.Wait(); err != nil {
  1579  			if out := stderr.String(); len(out) > 0 {
  1580  				t.Fatalf("err=%s, stderr=%s", err, out)
  1581  			}
  1582  		}
  1583  	})
  1584  
  1585  	t.Run("TestVectorizedTypeSchemaCopy", func(t *testing.T) {
  1586  		t.Parallel()
  1587  		cmd, stderr, filter, err := dirCmd(
  1588  			pkgDir,
  1589  			"git",
  1590  			"grep",
  1591  			"-nE",
  1592  			// We prohibit appending to the type schema and require allocating
  1593  			// a new slice. See the comment in execplan.go file.
  1594  			fmt.Sprintf(`(yps|ypes) = append\(`),
  1595  			"--",
  1596  			"sql/colexec/execplan.go",
  1597  		)
  1598  		if err != nil {
  1599  			t.Fatal(err)
  1600  		}
  1601  
  1602  		if err := cmd.Start(); err != nil {
  1603  			t.Fatal(err)
  1604  		}
  1605  
  1606  		if err := stream.ForEach(filter, func(s string) {
  1607  			t.Errorf("\n%s <- forbidden; allocate a new []*types.T slice", s)
  1608  		}); err != nil {
  1609  			t.Error(err)
  1610  		}
  1611  
  1612  		if err := cmd.Wait(); err != nil {
  1613  			if out := stderr.String(); len(out) > 0 {
  1614  				t.Fatalf("err=%s, stderr=%s", err, out)
  1615  			}
  1616  		}
  1617  	})
  1618  
  1619  	t.Run("TestTypesSlice", func(t *testing.T) {
  1620  		t.Parallel()
  1621  		cmd, stderr, filter, err := dirCmd(
  1622  			pkgDir,
  1623  			"git",
  1624  			"grep",
  1625  			"-nE",
  1626  			fmt.Sprintf(`\[\]types.T`),
  1627  			"--",
  1628  		)
  1629  		if err != nil {
  1630  			t.Fatal(err)
  1631  		}
  1632  
  1633  		if err := cmd.Start(); err != nil {
  1634  			t.Fatal(err)
  1635  		}
  1636  
  1637  		if err := stream.ForEach(filter, func(s string) {
  1638  			t.Errorf("\n%s <- forbidden; use []*types.T", s)
  1639  		}); err != nil {
  1640  			t.Error(err)
  1641  		}
  1642  
  1643  		if err := cmd.Wait(); err != nil {
  1644  			if out := stderr.String(); len(out) > 0 {
  1645  				t.Fatalf("err=%s, stderr=%s", err, out)
  1646  			}
  1647  		}
  1648  	})
  1649  
  1650  	// RoachVet is expensive memory-wise and thus should not run with t.Parallel().
  1651  	// RoachVet includes all of the passes of `go vet` plus first-party additions.
  1652  	// See pkg/cmd/roachvet.
  1653  	t.Run("TestRoachVet", func(t *testing.T) {
  1654  		if testing.Short() {
  1655  			t.Skip("short flag")
  1656  		}
  1657  		// The -printfuncs functionality is interesting and
  1658  		// under-documented. It checks two things:
  1659  		//
  1660  		// - that functions that accept formatting specifiers are given
  1661  		//   arguments of the proper type.
  1662  		// - that functions that don't accept formatting specifiers
  1663  		//   are not given any.
  1664  		//
  1665  		// Whether a function takes a format string or not is determined
  1666  		// as follows: (comment taken from the source of `go vet`)
  1667  		//
  1668  		//    A function may be a Printf or Print wrapper if its last argument is ...interface{}.
  1669  		//    If the next-to-last argument is a string, then this may be a Printf wrapper.
  1670  		//    Otherwise it may be a Print wrapper.
  1671  		printfuncs := strings.Join([]string{
  1672  			"ErrEvent",
  1673  			"ErrEventf",
  1674  			"Error",
  1675  			"Errorf",
  1676  			"ErrorfDepth",
  1677  			"Event",
  1678  			"Eventf",
  1679  			"Fatal",
  1680  			"Fatalf",
  1681  			"FatalfDepth",
  1682  			"Info",
  1683  			"Infof",
  1684  			"InfofDepth",
  1685  			"AssertionFailedf",
  1686  			"AssertionFailedWithDepthf",
  1687  			"NewAssertionErrorWithWrappedErrf",
  1688  			"DangerousStatementf",
  1689  			"pgerror.New",
  1690  			"pgerror.NewWithDepthf",
  1691  			"pgerror.Newf",
  1692  			"SetDetailf",
  1693  			"SetHintf",
  1694  			"Unimplemented",
  1695  			"Unimplementedf",
  1696  			"UnimplementedWithDepthf",
  1697  			"UnimplementedWithIssueDetailf",
  1698  			"UnimplementedWithIssuef",
  1699  			"VEvent",
  1700  			"VEventf",
  1701  			"Warning",
  1702  			"Warningf",
  1703  			"WarningfDepth",
  1704  			"Wrapf",
  1705  			"WrapWithDepthf",
  1706  		}, ",")
  1707  
  1708  		filters := []stream.Filter{
  1709  			// Ignore generated files.
  1710  			stream.GrepNot(`pkg/.*\.pb\.go:`),
  1711  			stream.GrepNot(`pkg/col/coldata/.*\.eg\.go:`),
  1712  			stream.GrepNot(`pkg/col/colserde/arrowserde/.*_generated\.go:`),
  1713  			stream.GrepNot(`pkg/sql/colexec/.*\.eg\.go:`),
  1714  			stream.GrepNot(`pkg/sql/colexec/.*_generated\.go:`),
  1715  			stream.GrepNot(`pkg/sql/pgwire/hba/conf.go:`),
  1716  
  1717  			// Ignore types that can change by system.
  1718  			stream.GrepNot(`pkg/util/sysutil/sysutil_unix.go:`),
  1719  
  1720  			stream.GrepNot(`declaration of "?(pE|e)rr"? shadows`),
  1721  			stream.GrepNot(`\.pb\.gw\.go:[0-9:]+: declaration of "?ctx"? shadows`),
  1722  			stream.GrepNot(`\.[eo]g\.go:[0-9:]+: declaration of ".*" shadows`),
  1723  			// This exception is for hash.go, which re-implements runtime.noescape
  1724  			// for efficient hashing.
  1725  			stream.GrepNot(`pkg/sql/colexec/hash.go:[0-9:]+: possible misuse of unsafe.Pointer`),
  1726  			stream.GrepNot(`^#`), // comment line
  1727  			// This exception is for the colexec generated files.
  1728  			stream.GrepNot(`pkg/sql/colexec/.*\.eg.go:[0-9:]+: self-assignment of .* to .*`),
  1729  			// Roachpb generated switch on `error`. It's OK for now because
  1730  			// the inner error is always unwrapped (it's a protobuf
  1731  			// enum). Eventually we want to use generalized error
  1732  			// encode/decode instead and drop the linter exception.
  1733  			stream.GrepNot(`pkg/roachpb/batch_generated\.go:.*invalid direct cast on error object`),
  1734  			// Roachpb's own error package takes ownership of error unwraps
  1735  			// (by enforcing that errors can never been wrapped under a
  1736  			// roachpb.Error, which is an inconvenient limitation but it is
  1737  			// what it is). Once this code is simplified to use generalized
  1738  			// error encode/decode, it can be dropped from the linter
  1739  			// exception as well.
  1740  			stream.GrepNot(`pkg/roachpb/errors\.go:.*invalid direct cast on error object`),
  1741  			// pgerror's pgcode logic uses its own custom cause recursion
  1742  			// algorithm and thus cannot use errors.If() which mandates a
  1743  			// different recursion order.
  1744  			//
  1745  			// It's a bit unfortunate that the entire file is added
  1746  			// as an exception here, given that only one function
  1747  			// really needs the linter. We could consider splitting
  1748  			// that function to a different file to limit the scope
  1749  			// of the exception.
  1750  			stream.GrepNot(`pkg/sql/pgwire/pgerror/pgcode\.go:.*invalid direct cast on error object`),
  1751  			// The crash reporting code uses its own custom cause recursion
  1752  			// algorithm and thus cannot use errors.Is.  However, it's also
  1753  			// due an overhaul - it's really redundant with the error
  1754  			// redaction code already present in the errors library.
  1755  			//
  1756  			// TODO(knz): remove the code in log and replace by the errors'
  1757  			// own redact code.
  1758  			stream.GrepNot(`pkg/util/log/crash_reporting\.go:.*invalid direct cast on error object`),
  1759  			stream.GrepNot(`pkg/util/log/crash_reporting\.go:.*invalid direct comparison of error object`),
  1760  			// The logging package translates log.Fatal calls into errors.
  1761  			// We can't use the regular exception mechanism via functions.go
  1762  			// because addStructured takes its positional argument as []interface{},
  1763  			// instead of ...interface{}.
  1764  			stream.GrepNot(`pkg/util/log/structured\.go:\d+:\d+: addStructured\(\): format argument is not a constant expression`),
  1765  		}
  1766  
  1767  		roachlint, err := exec.LookPath("roachvet")
  1768  		if err != nil {
  1769  			t.Fatalf("failed to find roachvet: %v", err)
  1770  		}
  1771  		vetCmd(t, crdb.Dir, "go",
  1772  			[]string{"vet", "-vettool", roachlint, "-all", "-printf.funcs", printfuncs, pkgScope},
  1773  			filters)
  1774  
  1775  	})
  1776  }