build: Enable gometalinter "gosimple" check, improve build.go

This commit is contained in:
Jakob Borg 2016-12-18 19:57:41 +01:00 committed by Jakob Borg
parent 47f22ff3e5
commit d41c131364
19 changed files with 108 additions and 130 deletions

134
build.go
View File

@ -27,7 +27,6 @@ import (
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
"syscall"
"text/template" "text/template"
"time" "time"
) )
@ -154,6 +153,39 @@ var targets = map[string]target{
}, },
} }
var (
// fast linters complete in a fraction of a second and might as well be
// run always as part of the build
fastLinters = []string{
"deadcode",
"golint",
"ineffassign",
"vet",
}
// slow linters take several seconds and are run only as part of the
// "metalint" command.
slowLinters = []string{
"gosimple",
"staticcheck",
"structcheck",
"unused",
"varcheck",
}
// Which parts of the tree to lint
lintDirs = []string{".", "./lib/...", "./cmd/..."}
// Messages to ignore
lintExcludes = []string{
".pb.go",
"should have comment",
"protocol.Vector composite literal uses unkeyed fields",
"Use DialContext instead", // Go 1.7
"os.SEEK_SET is deprecated", // Go 1.7
}
)
func init() { func init() {
// The "syncthing" target includes a few more files found in the "etc" // The "syncthing" target includes a few more files found in the "etc"
// and "extra" dirs. // and "extra" dirs.
@ -203,8 +235,6 @@ func main() {
// which is what you want for maximum error checking during development. // which is what you want for maximum error checking during development.
if flag.NArg() == 0 { if flag.NArg() == 0 {
runCommand("install", targets["all"]) runCommand("install", targets["all"])
runCommand("vet", target{})
runCommand("lint", target{})
} else { } else {
// with any command given but not a target, the target is // with any command given but not a target, the target is
// "syncthing". So "go run build.go install" is "go run build.go install // "syncthing". So "go run build.go install" is "go run build.go install
@ -242,6 +272,7 @@ func runCommand(cmd string, target target) {
tags = []string{"noupgrade"} tags = []string{"noupgrade"}
} }
install(target, tags) install(target, tags)
metalint(fastLinters, lintDirs)
case "build": case "build":
var tags []string var tags []string
@ -249,6 +280,7 @@ func runCommand(cmd string, target target) {
tags = []string{"noupgrade"} tags = []string{"noupgrade"}
} }
build(target, tags) build(target, tags)
metalint(fastLinters, lintDirs)
case "test": case "test":
test("./lib/...", "./cmd/...") test("./lib/...", "./cmd/...")
@ -284,28 +316,14 @@ func runCommand(cmd string, target target) {
clean() clean()
case "vet": case "vet":
vet("build.go") metalint(fastLinters, lintDirs)
vet("cmd", "lib")
case "lint": case "lint":
lint(".") metalint(fastLinters, lintDirs)
lint("./cmd/...")
lint("./lib/...")
case "metalint": case "metalint":
if isGometalinterInstalled() { metalint(fastLinters, lintDirs)
dirs := []string{".", "./cmd/...", "./lib/..."} metalint(slowLinters, lintDirs)
ok := gometalinter("deadcode", dirs, "test/util.go")
ok = gometalinter("structcheck", dirs) && ok
ok = gometalinter("varcheck", dirs) && ok
ok = gometalinter("ineffassign", dirs) && ok
ok = gometalinter("unused", dirs) && ok
ok = gometalinter("staticcheck", dirs) && ok
ok = gometalinter("unconvert", dirs) && ok
if !ok {
os.Exit(1)
}
}
case "version": case "version":
fmt.Println(getVersion()) fmt.Println(getVersion())
@ -373,6 +391,7 @@ func setup() {
"github.com/tsenart/deadcode", "github.com/tsenart/deadcode",
"golang.org/x/net/html", "golang.org/x/net/html",
"golang.org/x/tools/cmd/cover", "golang.org/x/tools/cmd/cover",
"honnef.co/go/simple/cmd/gosimple",
"honnef.co/go/staticcheck/cmd/staticcheck", "honnef.co/go/staticcheck/cmd/staticcheck",
"honnef.co/go/unused/cmd/unused", "honnef.co/go/unused/cmd/unused",
} }
@ -1009,48 +1028,6 @@ func zipFile(out string, files []archiveFile) {
} }
} }
func vet(dirs ...string) {
params := []string{"tool", "vet", "-all"}
params = append(params, dirs...)
bs, err := runError("go", params...)
if len(bs) > 0 {
log.Printf("%s", bs)
}
if err != nil {
if exitStatus(err) == 3 {
// Exit code 3, the "vet" tool is not installed
return
}
// A genuine error exit from the vet tool.
log.Fatal(err)
}
}
func lint(pkg string) {
bs, err := runError("golint", pkg)
if err != nil {
log.Println(`- No golint, not linting. Try "go get -u github.com/golang/lint/golint".`)
return
}
analCommentPolicy := regexp.MustCompile(`exported (function|method|const|type|var) [^\s]+ should have comment`)
for _, line := range strings.Split(string(bs), "\n") {
if line == "" {
continue
}
if analCommentPolicy.MatchString(line) {
continue
}
if strings.Contains(line, ".pb.go:") {
continue
}
log.Println(line)
}
}
func macosCodesign(file string) { func macosCodesign(file string) {
if pass := os.Getenv("CODESIGN_KEYCHAIN_PASS"); pass != "" { if pass := os.Getenv("CODESIGN_KEYCHAIN_PASS"); pass != "" {
bs, err := runError("security", "unlock-keychain", "-p", pass) bs, err := runError("security", "unlock-keychain", "-p", pass)
@ -1070,14 +1047,16 @@ func macosCodesign(file string) {
} }
} }
func exitStatus(err error) int { func metalint(linters []string, dirs []string) {
if err, ok := err.(*exec.ExitError); ok { ok := true
if ws, ok := err.ProcessState.Sys().(syscall.WaitStatus); ok { if isGometalinterInstalled() {
return ws.ExitStatus() if !gometalinter(linters, dirs, lintExcludes...) {
ok = false
} }
} }
if !ok {
return -1 log.Fatal("Build succeeded, but there were lint warnings")
}
} }
func isGometalinterInstalled() bool { func isGometalinterInstalled() bool {
@ -1088,10 +1067,12 @@ func isGometalinterInstalled() bool {
return true return true
} }
func gometalinter(linter string, dirs []string, excludes ...string) bool { func gometalinter(linters []string, dirs []string, excludes ...string) bool {
params := []string{"--disable-all"} params := []string{"--disable-all", "--concurrency=2", "--deadline=300s"}
params = append(params, fmt.Sprintf("--deadline=%ds", 60))
params = append(params, "--enable="+linter) for _, linter := range linters {
params = append(params, "--enable="+linter)
}
for _, exclude := range excludes { for _, exclude := range excludes {
params = append(params, "--exclude="+exclude) params = append(params, "--exclude="+exclude)
@ -1104,14 +1085,19 @@ func gometalinter(linter string, dirs []string, excludes ...string) bool {
bs, _ := runError("gometalinter", params...) bs, _ := runError("gometalinter", params...)
nerr := 0 nerr := 0
lines := make(map[string]struct{})
for _, line := range strings.Split(string(bs), "\n") { for _, line := range strings.Split(string(bs), "\n") {
if line == "" { if line == "" {
continue continue
} }
if strings.Contains(line, ".pb.go:") { if _, ok := lines[line]; ok {
continue continue
} }
log.Println(line) log.Println(line)
if strings.Contains(line, "executable file not found") {
log.Println(` - Try "go run build.go setup" to install missing tools`)
}
lines[line] = struct{}{}
nerr++ nerr++
} }

View File

@ -62,9 +62,9 @@ func generalID(c *cli.Context) {
func generalStatus(c *cli.Context) { func generalStatus(c *cli.Context) {
response := httpGet(c, "system/config/insync") response := httpGet(c, "system/config/insync")
status := make(map[string]interface{}) var status struct{ ConfigInSync bool }
json.Unmarshal(responseToBArray(response), &status) json.Unmarshal(responseToBArray(response), &status)
if status["configInSync"] != true { if !status.ConfigInSync {
die("Config out of sync") die("Config out of sync")
} }
fmt.Println("Config in sync") fmt.Println("Config in sync")

View File

@ -85,12 +85,7 @@ func generateOneFile(fd io.ReadSeeker, p1 string, s int64) error {
_ = os.Chmod(p1, os.FileMode(rand.Intn(0777)|0400)) _ = os.Chmod(p1, os.FileMode(rand.Intn(0777)|0400))
t := time.Now().Add(-time.Duration(rand.Intn(30*86400)) * time.Second) t := time.Now().Add(-time.Duration(rand.Intn(30*86400)) * time.Second)
err = os.Chtimes(p1, t, t) return os.Chtimes(p1, t, t)
if err != nil {
return err
}
return nil
} }
func randomName() string { func randomName() string {

View File

@ -381,10 +381,8 @@ func (s *apiService) String() string {
} }
func (s *apiService) VerifyConfiguration(from, to config.Configuration) error { func (s *apiService) VerifyConfiguration(from, to config.Configuration) error {
if _, err := net.ResolveTCPAddr("tcp", to.GUI.Address()); err != nil { _, err := net.ResolveTCPAddr("tcp", to.GUI.Address())
return err return err
}
return nil
} }
func (s *apiService) CommitConfiguration(from, to config.Configuration) bool { func (s *apiService) CommitConfiguration(from, to config.Configuration) bool {

View File

@ -99,10 +99,7 @@ func (f *FolderConfiguration) CreateMarker() error {
func (f *FolderConfiguration) HasMarker() bool { func (f *FolderConfiguration) HasMarker() bool {
_, err := os.Stat(filepath.Join(f.Path(), ".stfolder")) _, err := os.Stat(filepath.Join(f.Path(), ".stfolder"))
if err != nil { return err == nil
return false
}
return true
} }
func (f FolderConfiguration) Description() string { func (f FolderConfiguration) Description() string {

View File

@ -58,10 +58,7 @@ func setup() (*Instance, *BlockFinder) {
func dbEmpty(db *Instance) bool { func dbEmpty(db *Instance) bool {
iter := db.NewIterator(util.BytesPrefix([]byte{KeyTypeBlock}), nil) iter := db.NewIterator(util.BytesPrefix([]byte{KeyTypeBlock}), nil)
defer iter.Release() defer iter.Release()
if iter.Next() { return !iter.Next()
return false
}
return true
} }
func TestBlockMapAddUpdateWipe(t *testing.T) { func TestBlockMapAddUpdateWipe(t *testing.T) {

View File

@ -4,7 +4,8 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this file, // License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/. // You can obtain one at http://mozilla.org/MPL/2.0/.
// +build ignore // this is a really tedious test for an old issue // this is a really tedious test for an old issue
// +build ignore
package db_test package db_test

View File

@ -51,7 +51,7 @@ func NewLocal(id protocol.DeviceID, addr string, addrList AddressLister) (Finder
Supervisor: suture.NewSimple("local"), Supervisor: suture.NewSimple("local"),
myID: id, myID: id,
addrList: addrList, addrList: addrList,
localBcastTick: time.Tick(BroadcastInterval), localBcastTick: time.NewTicker(BroadcastInterval).C,
forcedBcastTick: make(chan time.Time), forcedBcastTick: make(chan time.Time),
localBcastStart: time.Now(), localBcastStart: time.Now(),
cache: newCache(), cache: newCache(),

View File

@ -247,22 +247,23 @@ func BenchmarkBufferedSub(b *testing.B) {
} }
// Receive the events // Receive the events
done := make(chan struct{}) done := make(chan error)
go func() { go func() {
defer close(done)
recv := 0 recv := 0
var evs []Event var evs []Event
for i := 0; i < b.N; { for i := 0; i < b.N; {
evs = bs.Since(recv, evs[:0]) evs = bs.Since(recv, evs[:0])
for _, ev := range evs { for _, ev := range evs {
if ev.GlobalID != recv+1 { if ev.GlobalID != recv+1 {
b.Fatal("skipped event", ev.GlobalID, recv) done <- fmt.Errorf("skipped event %v %v", ev.GlobalID, recv)
return
} }
recv = ev.GlobalID recv = ev.GlobalID
coord <- struct{}{} coord <- struct{}{}
} }
i += len(evs) i += len(evs)
} }
done <- nil
}() }()
// Send the events // Send the events
@ -276,7 +277,9 @@ func BenchmarkBufferedSub(b *testing.B) {
<-coord <-coord
} }
<-done if err := <-done; err != nil {
b.Error(err)
}
b.ReportAllocs() b.ReportAllocs()
} }

View File

@ -15,7 +15,7 @@ func TestCache(t *testing.T) {
c := newCache(nil) c := newCache(nil)
res, ok := c.get("nonexistent") res, ok := c.get("nonexistent")
if res.IsIgnored() || res.IsDeletable() || ok != false { if res.IsIgnored() || res.IsDeletable() || ok {
t.Errorf("res %v, ok %v for nonexistent item", res, ok) t.Errorf("res %v, ok %v for nonexistent item", res, ok)
} }
@ -25,12 +25,12 @@ func TestCache(t *testing.T) {
c.set("false", 0) c.set("false", 0)
res, ok = c.get("true") res, ok = c.get("true")
if !res.IsIgnored() || !res.IsDeletable() || ok != true { if !res.IsIgnored() || !res.IsDeletable() || !ok {
t.Errorf("res %v, ok %v for true item", res, ok) t.Errorf("res %v, ok %v for true item", res, ok)
} }
res, ok = c.get("false") res, ok = c.get("false")
if res.IsIgnored() || res.IsDeletable() || ok != true { if res.IsIgnored() || res.IsDeletable() || !ok {
t.Errorf("res %v, ok %v for false item", res, ok) t.Errorf("res %v, ok %v for false item", res, ok)
} }
@ -41,12 +41,12 @@ func TestCache(t *testing.T) {
// Same values should exist // Same values should exist
res, ok = c.get("true") res, ok = c.get("true")
if !res.IsIgnored() || !res.IsDeletable() || ok != true { if !res.IsIgnored() || !res.IsDeletable() || !ok {
t.Errorf("res %v, ok %v for true item", res, ok) t.Errorf("res %v, ok %v for true item", res, ok)
} }
res, ok = c.get("false") res, ok = c.get("false")
if res.IsIgnored() || res.IsDeletable() || ok != true { if res.IsIgnored() || res.IsDeletable() || !ok {
t.Errorf("res %v, ok %v for false item", res, ok) t.Errorf("res %v, ok %v for false item", res, ok)
} }

View File

@ -37,6 +37,7 @@ func (d *deadlockDetector) Watch(name string, mut sync.Locker) {
go func() { go func() {
mut.Lock() mut.Lock()
_ = 1 // empty critical section
mut.Unlock() mut.Unlock()
ok <- true ok <- true
}() }()

View File

@ -22,10 +22,7 @@ func SyncFile(path string) error {
} }
defer fd.Close() defer fd.Close()
// MacOS and Windows do not flush the disk cache // MacOS and Windows do not flush the disk cache
if err := fd.Sync(); err != nil { return fd.Sync()
return err
}
return nil
} }
func SyncDir(path string) error { func SyncDir(path string) error {

View File

@ -17,10 +17,10 @@ func (p *bufferPool) get(size int) []byte {
return p.new(size) return p.new(size)
} }
bs := intf.([]byte) bs := *intf.(*[]byte)
if cap(bs) < size { if cap(bs) < size {
// Buffer was too small, leave it for someone else and allocate. // Buffer was too small, leave it for someone else and allocate.
p.put(bs) p.pool.Put(intf)
return p.new(size) return p.new(size)
} }
@ -43,7 +43,7 @@ func (p *bufferPool) upgrade(bs []byte, size int) []byte {
// put returns the buffer to the pool // put returns the buffer to the pool
func (p *bufferPool) put(bs []byte) { func (p *bufferPool) put(bs []byte) {
p.pool.Put(bs) p.pool.Put(&bs)
} }
// new creates a new buffer of the requested size, taking the minimum // new creates a new buffer of the requested size, taking the minimum

View File

@ -105,11 +105,7 @@ func readHello(c io.Reader) (HelloResult, error) {
if err := hello.UnmarshalXDR(buf); err != nil { if err := hello.UnmarshalXDR(buf); err != nil {
return HelloResult{}, err return HelloResult{}, err
} }
res := HelloResult{ res := HelloResult(hello)
DeviceName: hello.DeviceName,
ClientName: hello.ClientName,
ClientVersion: hello.ClientVersion,
}
return res, ErrTooOldVersion13 return res, ErrTooOldVersion13
case 0x00010001, 0x00010000: case 0x00010001, 0x00010000:

View File

@ -516,7 +516,7 @@ func (c *rawConnection) handleRequest(req Request) {
var done chan struct{} var done chan struct{}
if usePool { if usePool {
buf = c.pool.Get().([]byte)[:size] buf = (*c.pool.Get().(*[]byte))[:size]
done = make(chan struct{}) done = make(chan struct{})
} else { } else {
buf = make([]byte, size) buf = make([]byte, size)
@ -539,7 +539,7 @@ func (c *rawConnection) handleRequest(req Request) {
if usePool { if usePool {
<-done <-done
c.pool.Put(buf) c.pool.Put(&buf)
} }
} }
@ -762,11 +762,12 @@ func (c *rawConnection) close(err error) {
// results in an effecting ping interval of somewhere between // results in an effecting ping interval of somewhere between
// PingSendInterval/2 and PingSendInterval. // PingSendInterval/2 and PingSendInterval.
func (c *rawConnection) pingSender() { func (c *rawConnection) pingSender() {
ticker := time.Tick(PingSendInterval / 2) ticker := time.NewTicker(PingSendInterval / 2)
defer ticker.Stop()
for { for {
select { select {
case <-ticker: case <-ticker.C:
d := time.Since(c.cw.Last()) d := time.Since(c.cw.Last())
if d < PingSendInterval/2 { if d < PingSendInterval/2 {
l.Debugln(c.id, "ping skipped after wr", d) l.Debugln(c.id, "ping skipped after wr", d)
@ -786,11 +787,12 @@ func (c *rawConnection) pingSender() {
// but we expect pings in the absence of other messages) within the last // but we expect pings in the absence of other messages) within the last
// ReceiveTimeout. If not, we close the connection with an ErrTimeout. // ReceiveTimeout. If not, we close the connection with an ErrTimeout.
func (c *rawConnection) pingReceiver() { func (c *rawConnection) pingReceiver() {
ticker := time.Tick(ReceiveTimeout / 2) ticker := time.NewTicker(ReceiveTimeout / 2)
defer ticker.Stop()
for { for {
select { select {
case <-ticker: case <-ticker.C:
d := time.Since(c.cr.Last()) d := time.Since(c.cr.Last())
if d > ReceiveTimeout { if d > ReceiveTimeout {
l.Debugln(c.id, "ping timeout", d) l.Debugln(c.id, "ping timeout", d)

View File

@ -80,7 +80,7 @@ func (h holder) String() string {
if h.at == "" { if h.at == "" {
return "not held" return "not held"
} }
return fmt.Sprintf("at %s goid: %d for %s", h.at, h.goid, time.Now().Sub(h.time)) return fmt.Sprintf("at %s goid: %d for %s", h.at, h.goid, time.Since(h.time))
} }
type loggedMutex struct { type loggedMutex struct {
@ -95,7 +95,7 @@ func (m *loggedMutex) Lock() {
func (m *loggedMutex) Unlock() { func (m *loggedMutex) Unlock() {
currentHolder := m.holder.Load().(holder) currentHolder := m.holder.Load().(holder)
duration := time.Now().Sub(currentHolder.time) duration := time.Since(currentHolder.time)
if duration >= threshold { if duration >= threshold {
l.Debugf("Mutex held for %v. Locked at %s unlocked at %s", duration, currentHolder.at, getHolder().at) l.Debugf("Mutex held for %v. Locked at %s unlocked at %s", duration, currentHolder.at, getHolder().at)
} }
@ -147,7 +147,7 @@ func (m *loggedRWMutex) Lock() {
func (m *loggedRWMutex) Unlock() { func (m *loggedRWMutex) Unlock() {
currentHolder := m.holder.Load().(holder) currentHolder := m.holder.Load().(holder)
duration := time.Now().Sub(currentHolder.time) duration := time.Since(currentHolder.time)
if duration >= threshold { if duration >= threshold {
l.Debugf("RWMutex held for %v. Locked at %s unlocked at %s", duration, currentHolder.at, getHolder().at) l.Debugf("RWMutex held for %v. Locked at %s unlocked at %s", duration, currentHolder.at, getHolder().at)
} }
@ -201,7 +201,7 @@ type loggedWaitGroup struct {
func (wg *loggedWaitGroup) Wait() { func (wg *loggedWaitGroup) Wait() {
start := time.Now() start := time.Now()
wg.WaitGroup.Wait() wg.WaitGroup.Wait()
duration := time.Now().Sub(start) duration := time.Since(start)
if duration >= threshold { if duration >= threshold {
l.Debugf("WaitGroup took %v at %s", duration, getHolder()) l.Debugf("WaitGroup took %v at %s", duration, getHolder())
} }

View File

@ -22,7 +22,7 @@ func TestSetDefaults(t *testing.T) {
t.Error("int failed") t.Error("int failed")
} else if x.C != 0 { } else if x.C != 0 {
t.Errorf("float failed") t.Errorf("float failed")
} else if x.D != false { } else if x.D {
t.Errorf("bool failed") t.Errorf("bool failed")
} }
@ -36,7 +36,7 @@ func TestSetDefaults(t *testing.T) {
t.Error("int failed") t.Error("int failed")
} else if x.C != 2.2 { } else if x.C != 2.2 {
t.Errorf("float failed") t.Errorf("float failed")
} else if x.D != true { } else if !x.D {
t.Errorf("bool failed") t.Errorf("bool failed")
} }
} }

View File

@ -73,11 +73,14 @@ func NewStaggered(folderID, folderPath string, params map[string]string) Version
l.Debugf("instantiated %#v", s) l.Debugf("instantiated %#v", s)
go func() { go func() {
// TODO: This should be converted to a Serve() method.
s.clean() s.clean()
if testCleanDone != nil { if testCleanDone != nil {
close(testCleanDone) close(testCleanDone)
} }
for range time.Tick(time.Duration(cleanInterval) * time.Second) { tck := time.NewTicker(time.Duration(cleanInterval) * time.Second)
defer tck.Stop()
for range tck.C {
s.clean() s.clean()
} }
}() }()

View File

@ -17,6 +17,8 @@ import (
"testing" "testing"
"time" "time"
"io"
"github.com/syncthing/syncthing/lib/config" "github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/protocol" "github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/rc" "github.com/syncthing/syncthing/lib/rc"