blob: 229c2d23b1be1a77f3cf79f922783c1999c8156e [file] [log] [blame]
// Copyright 2021 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package appengine
import (
"fmt"
"go/ast"
"go/types"
"os"
"os/exec"
"path"
"runtime"
"strings"
"testing"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/singlechecker"
)
var statusAssign = &analysis.Analyzer{
Name: "statusAssignment",
Doc: "reports direct assignments to Build.Status",
Run: func(pass *analysis.Pass) (interface{}, error) {
for _, file := range pass.Files {
if strings.HasSuffix(pass.Fset.File(file.Pos()).Name(), "_test.go") {
continue
}
ast.Inspect(file, func(n ast.Node) bool {
if assn, ok := n.(*ast.AssignStmt); ok {
checkAssignStmt(pass, assn)
}
return true
})
}
return nil, nil
},
}
const envKey = "BUILDBUCKET_RUN_STATUS_ASSIGN_CHECKER"
func init() {
if os.Getenv(envKey) != "" {
singlechecker.Main(statusAssign)
}
}
func checkAssignStmt(pass *analysis.Pass, stmt *ast.AssignStmt) {
for _, exp := range stmt.Lhs {
ast.Inspect(exp, func(n ast.Node) bool {
if se, ok := n.(*ast.SelectorExpr); ok {
ot := pass.TypesInfo.TypeOf(se.X)
if ot == nil {
return true
}
pt, ok := ot.(*types.Pointer)
if !ok {
return true
}
nt, ok := pt.Elem().(*types.Named)
if !ok {
return true
}
typ := nt.Obj()
if typ.Pkg().Path() != "go.chromium.org/luci/buildbucket/proto" || typ.Name() != "Build" {
return true
}
// At this point we know the left part of the selector is a *Build
switch se.Sel.Name {
case "Status", "StartTime", "EndTime":
pass.Report(analysis.Diagnostic{
Pos: stmt.Pos(),
Message: fmt.Sprintf("Bare assignment to Build.%s; Use protoutil.SetStatus instead.", se.Sel.Name),
})
}
return false
}
return true
})
}
}
func TestBuildStatusAssignment(t *testing.T) {
executable, err := os.Executable()
if err != nil {
t.Fatal(err)
}
_, curFile, _, _ := runtime.Caller(0)
cmd := exec.Command(executable, "-c", "0", path.Dir(curFile)+"/...")
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, envKey+"=1")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
switch err := cmd.Run().(type) {
case nil:
case *exec.ExitError:
t.Fail()
default:
t.Fatal(err)
}
}