mirror of
				https://source.quilibrium.com/quilibrium/ceremonyclient.git
				synced 2025-11-04 03:07:28 +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
 | 
						|
	}
 | 
						|
}
 |