mirror of
https://source.quilibrium.com/quilibrium/ceremonyclient.git
synced 2024-12-26 08:35:17 +00:00
302 lines
7.7 KiB
Go
302 lines
7.7 KiB
Go
|
// Copyright 2019 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
||
|
// of this source code is governed by a BSD-style license that can be found in
|
||
|
// the LICENSE file.
|
||
|
|
||
|
package lint
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"fmt"
|
||
|
"go/build"
|
||
|
"os/exec"
|
||
|
"regexp"
|
||
|
"runtime"
|
||
|
"strings"
|
||
|
"testing"
|
||
|
|
||
|
"github.com/cockroachdb/errors"
|
||
|
"github.com/cockroachdb/pebble/internal/invariants"
|
||
|
"github.com/ghemawat/stream"
|
||
|
"github.com/stretchr/testify/require"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
cmdGo = "go"
|
||
|
golint = "golang.org/x/lint/golint@6edffad5e6160f5949cdefc81710b2706fbcd4f6"
|
||
|
staticcheck = "honnef.co/go/tools/cmd/staticcheck@2023.1"
|
||
|
crlfmt = "github.com/cockroachdb/crlfmt@44a36ec7"
|
||
|
)
|
||
|
|
||
|
func dirCmd(t *testing.T, dir string, name string, args ...string) stream.Filter {
|
||
|
cmd := exec.Command(name, args...)
|
||
|
cmd.Dir = dir
|
||
|
out, err := cmd.CombinedOutput()
|
||
|
switch err.(type) {
|
||
|
case nil:
|
||
|
case *exec.ExitError:
|
||
|
// Non-zero exit is expected.
|
||
|
default:
|
||
|
require.NoError(t, err)
|
||
|
}
|
||
|
return stream.ReadLines(bytes.NewReader(out))
|
||
|
}
|
||
|
|
||
|
func ignoreGoMod() stream.Filter {
|
||
|
return stream.GrepNot(`^go: (finding|extracting|downloading)`)
|
||
|
}
|
||
|
|
||
|
func TestLint(t *testing.T) {
|
||
|
if runtime.GOOS == "windows" {
|
||
|
t.Skip("lint checks skipped on Windows")
|
||
|
}
|
||
|
if invariants.RaceEnabled {
|
||
|
// We are not interested in race-testing the linters themselves.
|
||
|
t.Skip("lint checks skipped on race builds")
|
||
|
}
|
||
|
|
||
|
const root = "github.com/cockroachdb/pebble"
|
||
|
|
||
|
pkg, err := build.Import(root, "../..", 0)
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
var pkgs []string
|
||
|
if err := stream.ForEach(
|
||
|
stream.Sequence(
|
||
|
dirCmd(t, pkg.Dir, "go", "list", "./..."),
|
||
|
ignoreGoMod(),
|
||
|
), func(s string) {
|
||
|
pkgs = append(pkgs, s)
|
||
|
}); err != nil {
|
||
|
require.NoError(t, err)
|
||
|
}
|
||
|
|
||
|
t.Run("TestGolint", func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
args := []string{"run", golint}
|
||
|
args = append(args, pkgs...)
|
||
|
|
||
|
// This is overkill right now, but provides a structure for filtering out
|
||
|
// lint errors we don't care about.
|
||
|
if err := stream.ForEach(
|
||
|
stream.Sequence(
|
||
|
dirCmd(t, pkg.Dir, cmdGo, args...),
|
||
|
stream.GrepNot("go: downloading"),
|
||
|
), func(s string) {
|
||
|
t.Errorf("\n%s", s)
|
||
|
}); err != nil {
|
||
|
t.Error(err)
|
||
|
}
|
||
|
})
|
||
|
|
||
|
t.Run("TestStaticcheck", func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
args := []string{"run", staticcheck}
|
||
|
args = append(args, pkgs...)
|
||
|
|
||
|
if err := stream.ForEach(
|
||
|
stream.Sequence(
|
||
|
dirCmd(t, pkg.Dir, cmdGo, args...),
|
||
|
stream.GrepNot("go: downloading"),
|
||
|
), func(s string) {
|
||
|
t.Errorf("\n%s", s)
|
||
|
}); err != nil {
|
||
|
t.Error(err)
|
||
|
}
|
||
|
})
|
||
|
|
||
|
t.Run("TestGoVet", func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
if err := stream.ForEach(
|
||
|
stream.Sequence(
|
||
|
dirCmd(t, pkg.Dir, "go", "vet", "-all", "./..."),
|
||
|
stream.GrepNot(`^#`), // ignore comment lines
|
||
|
ignoreGoMod(),
|
||
|
), func(s string) {
|
||
|
t.Errorf("\n%s", s)
|
||
|
}); err != nil {
|
||
|
t.Error(err)
|
||
|
}
|
||
|
})
|
||
|
|
||
|
t.Run("TestFmtErrorf", func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
if err := stream.ForEach(
|
||
|
dirCmd(t, pkg.Dir, "git", "grep", "fmt\\.Errorf("),
|
||
|
func(s string) {
|
||
|
t.Errorf("\n%s <- please use \"errors.Errorf\" instead", s)
|
||
|
}); err != nil {
|
||
|
t.Error(err)
|
||
|
}
|
||
|
})
|
||
|
|
||
|
t.Run("TestOSIsErr", func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
if err := stream.ForEach(
|
||
|
dirCmd(t, pkg.Dir, "git", "grep", "os\\.Is"),
|
||
|
func(s string) {
|
||
|
t.Errorf("\n%s <- please use the \"oserror\" equivalent instead", s)
|
||
|
}); err != nil {
|
||
|
t.Error(err)
|
||
|
}
|
||
|
})
|
||
|
|
||
|
t.Run("TestSetFinalizer", func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
if err := stream.ForEach(
|
||
|
stream.Sequence(
|
||
|
dirCmd(t, pkg.Dir, "git", "grep", "-B1", "runtime\\.SetFinalizer("),
|
||
|
lintIgnore("lint:ignore SetFinalizer"),
|
||
|
stream.GrepNot(`^internal/invariants/finalizer_on.go`),
|
||
|
), func(s string) {
|
||
|
t.Errorf("\n%s <- please use the \"invariants.SetFinalizer\" equivalent instead", s)
|
||
|
}); err != nil {
|
||
|
t.Error(err)
|
||
|
}
|
||
|
})
|
||
|
|
||
|
// Disallow "raw" atomics; wrappers like atomic.Int32 provide much better
|
||
|
// safety and alignment guarantees.
|
||
|
t.Run("TestRawAtomics", func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
if err := stream.ForEach(
|
||
|
stream.Sequence(
|
||
|
dirCmd(t, pkg.Dir, "git", "grep", `atomic\.\(Load\|Store\|Add\|Swap\|Compare\)`),
|
||
|
lintIgnore("lint:ignore RawAtomics"),
|
||
|
), func(s string) {
|
||
|
t.Errorf("\n%s <- please use atomic wrappers (like atomic.Int32) instead", s)
|
||
|
}); err != nil {
|
||
|
t.Error(err)
|
||
|
}
|
||
|
})
|
||
|
|
||
|
t.Run("TestForbiddenImports", func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
// Forbidden-import-pkg -> permitted-replacement-pkg
|
||
|
forbiddenImports := map[string]string{
|
||
|
"errors": "github.com/cockroachdb/errors",
|
||
|
"pkg/errors": "github.com/cockroachdb/errors",
|
||
|
}
|
||
|
|
||
|
// grepBuf creates a grep string that matches any forbidden import pkgs.
|
||
|
var grepBuf bytes.Buffer
|
||
|
grepBuf.WriteByte('(')
|
||
|
for forbiddenPkg := range forbiddenImports {
|
||
|
grepBuf.WriteByte('|')
|
||
|
grepBuf.WriteString(regexp.QuoteMeta(forbiddenPkg))
|
||
|
}
|
||
|
grepBuf.WriteString(")$")
|
||
|
|
||
|
filter := stream.FilterFunc(func(arg stream.Arg) error {
|
||
|
for _, path := range pkgs {
|
||
|
buildContext := build.Default
|
||
|
buildContext.UseAllFiles = true
|
||
|
importPkg, err := buildContext.Import(path, pkg.Dir, 0)
|
||
|
if _, ok := err.(*build.MultiplePackageError); ok {
|
||
|
buildContext.UseAllFiles = false
|
||
|
importPkg, err = buildContext.Import(path, pkg.Dir, 0)
|
||
|
}
|
||
|
|
||
|
switch err.(type) {
|
||
|
case nil:
|
||
|
for _, s := range importPkg.Imports {
|
||
|
arg.Out <- importPkg.ImportPath + ": " + s
|
||
|
}
|
||
|
for _, s := range importPkg.TestImports {
|
||
|
arg.Out <- importPkg.ImportPath + ": " + s
|
||
|
}
|
||
|
for _, s := range importPkg.XTestImports {
|
||
|
arg.Out <- importPkg.ImportPath + ": " + s
|
||
|
}
|
||
|
case *build.NoGoError:
|
||
|
default:
|
||
|
return errors.Wrapf(err, "error loading package %s", path)
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
})
|
||
|
if err := stream.ForEach(stream.Sequence(
|
||
|
filter,
|
||
|
stream.Sort(),
|
||
|
stream.Uniq(),
|
||
|
stream.Grep(grepBuf.String()),
|
||
|
), func(s string) {
|
||
|
pkgStr := strings.Split(s, ": ")
|
||
|
importedPkg := pkgStr[1]
|
||
|
|
||
|
// Test that a disallowed package is not imported.
|
||
|
if replPkg, ok := forbiddenImports[importedPkg]; ok {
|
||
|
t.Errorf("\n%s <- please use %q instead of %q", s, replPkg, importedPkg)
|
||
|
}
|
||
|
}); err != nil {
|
||
|
t.Error(err)
|
||
|
}
|
||
|
})
|
||
|
|
||
|
t.Run("TestCrlfmt", func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
args := []string{"run", crlfmt, "-fast", "-tab", "2", "."}
|
||
|
var buf bytes.Buffer
|
||
|
if err := stream.ForEach(
|
||
|
stream.Sequence(
|
||
|
dirCmd(t, pkg.Dir, cmdGo, args...),
|
||
|
stream.GrepNot("go: downloading"),
|
||
|
),
|
||
|
func(s string) {
|
||
|
fmt.Fprintln(&buf, s)
|
||
|
}); err != nil {
|
||
|
t.Error(err)
|
||
|
}
|
||
|
errs := buf.String()
|
||
|
if len(errs) > 0 {
|
||
|
t.Errorf("\n%s", errs)
|
||
|
}
|
||
|
|
||
|
if t.Failed() {
|
||
|
reWriteCmd := []string{crlfmt, "-w"}
|
||
|
reWriteCmd = append(reWriteCmd, args...)
|
||
|
t.Logf("run the following to fix your formatting:\n"+
|
||
|
"\n%s\n\n"+
|
||
|
"Don't forget to add amend the result to the correct commits.",
|
||
|
strings.Join(reWriteCmd, " "),
|
||
|
)
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// lintIgnore is a stream.FilterFunc that filters out lines that are preceded by
|
||
|
// the given ignore directive. The function assumes the input stream receives a
|
||
|
// sequence of strings that are to be considered as pairs. If the first string
|
||
|
// in the sequence matches the ignore directive, the following string is
|
||
|
// dropped, else it is emitted.
|
||
|
//
|
||
|
// For example, given the sequence "foo", "bar", "baz", "bam", and an ignore
|
||
|
// directive "foo", the sequence "baz", "bam" would be emitted. If the directive
|
||
|
// was "baz", the sequence "foo", "bar" would be emitted.
|
||
|
func lintIgnore(ignore string) stream.FilterFunc {
|
||
|
return func(arg stream.Arg) error {
|
||
|
var prev string
|
||
|
var i int
|
||
|
for s := range arg.In {
|
||
|
if i%2 == 0 {
|
||
|
// Fist string in the pair is used as the filter. Store it.
|
||
|
prev = s
|
||
|
} else {
|
||
|
// Second string is emitted only if it _does not_ match the directive.
|
||
|
if !strings.Contains(prev, ignore) {
|
||
|
arg.Out <- s
|
||
|
}
|
||
|
}
|
||
|
i++
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
}
|