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 }