mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-18 17:33:39 +00:00
Rewrite typecheck against x/tools/go/packages
This runs much faster than before. This change removes all of the async status output because all of the compute time is spent inside go/packages, with no opportunity to update the status. Adds testdata code to prove it fails when expected.
This commit is contained in:
parent
734f27d980
commit
d187d7effa
@ -68,7 +68,7 @@ while IFS='' read -r line; do
|
||||
all_packages+=("./$line")
|
||||
done < <( hack/make-rules/helpers/cache_go_dirs.sh "${KUBE_ROOT}/_tmp/all_go_dirs" |
|
||||
grep "^${FOCUS:-.}" |
|
||||
grep -vE "(third_party|generated|clientset_generated|hack|/_)" |
|
||||
grep -vE "(third_party|generated|clientset_generated|hack|testdata|/_)" |
|
||||
grep -vE "$ignore_pattern" )
|
||||
|
||||
failing_packages=()
|
||||
|
@ -30,10 +30,9 @@ cd "${KUBE_ROOT}"
|
||||
|
||||
make --no-print-directory -C "${KUBE_ROOT}" generated_files
|
||||
|
||||
# As of June, 2020 the typecheck tool is written in terms of go/types, but that
|
||||
# library doesn't work well with modules. Guidance is to rewrite tools against
|
||||
# golang.org/x/tools/go/packages. Until that is done, force this tooling to
|
||||
# run in a fake GOPATH.
|
||||
# As of June, 2020 the typecheck tool is written in terms of go/packages, but
|
||||
# that library doesn't work well with multiple modules. Until that is done,
|
||||
# force this tooling to run in a fake GOPATH.
|
||||
ret=0
|
||||
hack/run-in-gopath.sh \
|
||||
go run test/typecheck/main.go "$@" || ret=$?
|
||||
|
@ -1,3 +1,4 @@
|
||||
# gazelle:exclude testdata
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
@ -16,10 +17,7 @@ go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["main.go"],
|
||||
importpath = "k8s.io/kubernetes/test/typecheck",
|
||||
deps = [
|
||||
"//third_party/go-srcimporter:go_default_library",
|
||||
"//vendor/golang.org/x/crypto/ssh/terminal:go_default_library",
|
||||
],
|
||||
deps = ["//vendor/golang.org/x/tools/go/packages:go_default_library"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
@ -43,5 +41,12 @@ go_binary(
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["main_test.go"],
|
||||
# The "../../" is because $(location) gives a relative path, but
|
||||
# relative to some root for which I can't find a variable. Empirically
|
||||
# this is 2 levels up (since this file is 2 levels down from the repo
|
||||
# root). Hack.
|
||||
args = ["--go=../../$(location @go_sdk//:bin/go)"],
|
||||
data = ["@go_sdk//:bin/go"] + glob(["testdata/**"]),
|
||||
embed = [":go_default_library"],
|
||||
deps = ["//vendor/golang.org/x/tools/go/packages:go_default_library"],
|
||||
)
|
||||
|
@ -20,11 +20,6 @@ package main
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
@ -32,12 +27,9 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
|
||||
srcimporter "k8s.io/kubernetes/third_party/go-srcimporter"
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -51,9 +43,6 @@ var (
|
||||
tags = flag.String("tags", "", "comma-separated list of build tags to apply in addition to go's defaults")
|
||||
ignoreDirs = flag.String("ignore-dirs", "", "comma-separated list of directories to ignore in addition to the default hardcoded list including staging, vendor, and hidden dirs")
|
||||
|
||||
isTerminal = terminal.IsTerminal(int(os.Stdout.Fd()))
|
||||
logPrefix = ""
|
||||
|
||||
// When processed in order, windows and darwin are early to make
|
||||
// interesting OS-based errors happen earlier.
|
||||
crossPlatforms = []string{
|
||||
@ -63,8 +52,6 @@ var (
|
||||
"linux/arm64", "linux/ppc64le",
|
||||
"linux/s390x", "darwin/386",
|
||||
}
|
||||
darwinPlatString = "darwin/386,darwin/amd64"
|
||||
windowsPlatString = "windows/386,windows/amd64"
|
||||
|
||||
// directories we always ignore
|
||||
standardIgnoreDirs = []string{
|
||||
@ -88,168 +75,29 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
type analyzer struct {
|
||||
fset *token.FileSet // positions are relative to fset
|
||||
conf types.Config
|
||||
ctx build.Context
|
||||
failed bool
|
||||
platform string
|
||||
donePaths map[string]interface{}
|
||||
errors []string
|
||||
}
|
||||
|
||||
func newAnalyzer(platform string) *analyzer {
|
||||
ctx := build.Default
|
||||
func newConfig(platform string) *packages.Config {
|
||||
platSplit := strings.Split(platform, "/")
|
||||
ctx.GOOS, ctx.GOARCH = platSplit[0], platSplit[1]
|
||||
ctx.CgoEnabled = true
|
||||
if *tags != "" {
|
||||
tagsSplit := strings.Split(*tags, ",")
|
||||
ctx.BuildTags = append(ctx.BuildTags, tagsSplit...)
|
||||
}
|
||||
|
||||
// add selinux tag explicitly
|
||||
ctx.BuildTags = append(ctx.BuildTags, "selinux")
|
||||
|
||||
a := &analyzer{
|
||||
platform: platform,
|
||||
fset: token.NewFileSet(),
|
||||
ctx: ctx,
|
||||
donePaths: make(map[string]interface{}),
|
||||
}
|
||||
a.conf = types.Config{
|
||||
FakeImportC: true,
|
||||
Error: a.handleError,
|
||||
Sizes: types.SizesFor("gc", a.ctx.GOARCH),
|
||||
}
|
||||
|
||||
a.conf.Importer = srcimporter.New(
|
||||
&a.ctx, a.fset, make(map[string]*types.Package))
|
||||
|
||||
if *verbose {
|
||||
fmt.Printf("context: %#v\n", ctx)
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *analyzer) handleError(err error) {
|
||||
a.errors = append(a.errors, err.Error())
|
||||
if *serial {
|
||||
fmt.Fprintf(os.Stderr, "%sERROR(%s) %s\n", logPrefix, a.platform, err)
|
||||
}
|
||||
a.failed = true
|
||||
}
|
||||
|
||||
func (a *analyzer) dumpAndResetErrors() []string {
|
||||
es := a.errors
|
||||
a.errors = nil
|
||||
return es
|
||||
}
|
||||
|
||||
// collect extracts test metadata from a file.
|
||||
func (a *analyzer) collect(dir string) {
|
||||
if _, ok := a.donePaths[dir]; ok {
|
||||
return
|
||||
}
|
||||
a.donePaths[dir] = nil
|
||||
|
||||
// Create the AST by parsing src.
|
||||
fs, err := parser.ParseDir(a.fset, dir, nil, parser.AllErrors)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(logPrefix+"ERROR(syntax)", err)
|
||||
a.failed = true
|
||||
return
|
||||
}
|
||||
|
||||
if len(fs) > 1 && *verbose {
|
||||
fmt.Println("multiple packages in dir:", dir)
|
||||
}
|
||||
|
||||
for _, p := range fs {
|
||||
// returns first error, but a.handleError deals with it
|
||||
files := a.filterFiles(p.Files)
|
||||
if *verbose {
|
||||
fmt.Printf("path: %s package: %s files: ", dir, p.Name)
|
||||
for _, f := range files {
|
||||
fname := filepath.Base(a.fset.File(f.Pos()).Name())
|
||||
fmt.Printf("%s ", fname)
|
||||
}
|
||||
fmt.Printf("\n")
|
||||
}
|
||||
a.typeCheck(dir, files)
|
||||
}
|
||||
}
|
||||
|
||||
// filterFiles restricts a list of files to only those that should be built by
|
||||
// the current platform. This includes both build suffixes (_windows.go) and build
|
||||
// tags ("// +build !linux" at the beginning).
|
||||
func (a *analyzer) filterFiles(fs map[string]*ast.File) []*ast.File {
|
||||
files := []*ast.File{}
|
||||
for _, f := range fs {
|
||||
fpath := a.fset.File(f.Pos()).Name()
|
||||
if *skipTest && strings.HasSuffix(fpath, "_test.go") {
|
||||
continue
|
||||
}
|
||||
dir, name := filepath.Split(fpath)
|
||||
matches, err := a.ctx.MatchFile(dir, name)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%sERROR reading %s: %s\n", logPrefix, fpath, err)
|
||||
a.failed = true
|
||||
continue
|
||||
}
|
||||
if matches {
|
||||
files = append(files, f)
|
||||
}
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
func (a *analyzer) typeCheck(dir string, files []*ast.File) error {
|
||||
info := types.Info{
|
||||
Defs: make(map[*ast.Ident]types.Object),
|
||||
Uses: make(map[*ast.Ident]types.Object),
|
||||
}
|
||||
|
||||
// NOTE: this type check does a *recursive* import, but srcimporter
|
||||
// doesn't do a full type check (ignores function bodies)-- this has
|
||||
// some additional overhead.
|
||||
//
|
||||
// This means that we need to ensure that typeCheck runs on all
|
||||
// code we will be compiling.
|
||||
//
|
||||
// TODO(rmmh): Customize our forked srcimporter to do this better.
|
||||
pkg, err := a.conf.Check(dir, a.fset, files, &info)
|
||||
if err != nil {
|
||||
return err // type error
|
||||
}
|
||||
|
||||
// A significant fraction of vendored code only compiles on Linux,
|
||||
// but it's only imported by code that has build-guards for Linux.
|
||||
// Track vendored code to type-check it in a second pass.
|
||||
for _, imp := range pkg.Imports() {
|
||||
if strings.HasPrefix(imp.Path(), "k8s.io/kubernetes/vendor/") {
|
||||
vendorPath := imp.Path()[len("k8s.io/kubernetes/"):]
|
||||
if *verbose {
|
||||
fmt.Println("recursively checking vendor path:", vendorPath)
|
||||
}
|
||||
a.collect(vendorPath)
|
||||
}
|
||||
}
|
||||
|
||||
goos, goarch := platSplit[0], platSplit[1]
|
||||
mode := packages.NeedName | packages.NeedFiles | packages.NeedTypes | packages.NeedSyntax | packages.NeedDeps | packages.NeedImports
|
||||
if *defuses {
|
||||
for id, obj := range info.Defs {
|
||||
fmt.Printf("%s: %q defines %v\n",
|
||||
a.fset.Position(id.Pos()), id.Name, obj)
|
||||
}
|
||||
for id, obj := range info.Uses {
|
||||
fmt.Printf("%s: %q uses %v\n",
|
||||
a.fset.Position(id.Pos()), id.Name, obj)
|
||||
}
|
||||
mode = mode | packages.NeedTypesInfo
|
||||
}
|
||||
env := append(os.Environ(),
|
||||
"CGO_ENABLED=1",
|
||||
fmt.Sprintf("GOOS=%s", goos),
|
||||
fmt.Sprintf("GOARCH=%s", goarch))
|
||||
tagstr := "selinux"
|
||||
if *tags != "" {
|
||||
tagstr = tagstr + "," + *tags
|
||||
}
|
||||
flags := []string{"-tags", tagstr}
|
||||
|
||||
return nil
|
||||
return &packages.Config{
|
||||
Mode: mode,
|
||||
Env: env,
|
||||
BuildFlags: flags,
|
||||
Tests: !(*skipTest),
|
||||
}
|
||||
}
|
||||
|
||||
type collector struct {
|
||||
@ -257,6 +105,27 @@ type collector struct {
|
||||
ignoreDirs []string
|
||||
}
|
||||
|
||||
func newCollector(ignoreDirs string) collector {
|
||||
c := collector{
|
||||
ignoreDirs: append([]string(nil), standardIgnoreDirs...),
|
||||
}
|
||||
if ignoreDirs != "" {
|
||||
c.ignoreDirs = append(c.ignoreDirs, strings.Split(ignoreDirs, ",")...)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *collector) walk(roots []string) error {
|
||||
for _, root := range roots {
|
||||
err := filepath.Walk(root, c.handlePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
sort.Strings(c.dirs)
|
||||
return nil
|
||||
}
|
||||
|
||||
// handlePath walks the filesystem recursively, collecting directories,
|
||||
// ignoring some unneeded directories (hidden/vendored) that are handled
|
||||
// specially later.
|
||||
@ -265,63 +134,115 @@ func (c *collector) handlePath(path string, info os.FileInfo, err error) error {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
name := info.Name()
|
||||
// Ignore hidden directories (.git, .cache, etc)
|
||||
if len(path) > 1 && (path[0] == '.' || path[0] == '_') {
|
||||
if (len(name) > 1 && (name[0] == '.' || name[0] == '_')) || name == "testdata" {
|
||||
if *verbose {
|
||||
fmt.Printf("DBG: skipping dir %s\n", path)
|
||||
}
|
||||
return filepath.SkipDir
|
||||
}
|
||||
for _, dir := range c.ignoreDirs {
|
||||
if path == dir {
|
||||
if *verbose {
|
||||
fmt.Printf("DBG: ignoring dir %s\n", path)
|
||||
}
|
||||
return filepath.SkipDir
|
||||
}
|
||||
}
|
||||
c.dirs = append(c.dirs, path)
|
||||
// Make dirs into relative pkg names.
|
||||
// NOTE: can't use filepath.Join because it elides the leading "./"
|
||||
pkg := path
|
||||
if !strings.HasPrefix(pkg, "./") {
|
||||
pkg = "./" + pkg
|
||||
}
|
||||
c.dirs = append(c.dirs, pkg)
|
||||
if *verbose {
|
||||
fmt.Printf("DBG: added dir %s\n", path)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type analyzerResult struct {
|
||||
platform string
|
||||
dir string
|
||||
errors []string
|
||||
func (c *collector) verify(plat string) ([]string, error) {
|
||||
errors := []packages.Error{}
|
||||
start := time.Now()
|
||||
config := newConfig(plat)
|
||||
|
||||
rootPkgs, err := packages.Load(config, c.dirs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Recursively import all deps and flatten to one list.
|
||||
allMap := map[string]*packages.Package{}
|
||||
for _, pkg := range rootPkgs {
|
||||
if *verbose {
|
||||
serialFprintf(os.Stdout, "pkg %q has %d GoFiles\n", pkg.PkgPath, len(pkg.GoFiles))
|
||||
}
|
||||
allMap[pkg.PkgPath] = pkg
|
||||
if len(pkg.Imports) > 0 {
|
||||
for _, imp := range pkg.Imports {
|
||||
if *verbose {
|
||||
serialFprintf(os.Stdout, "pkg %q imports %q\n", pkg.PkgPath, imp.PkgPath)
|
||||
}
|
||||
allMap[imp.PkgPath] = imp
|
||||
}
|
||||
}
|
||||
}
|
||||
keys := make([]string, 0, len(allMap))
|
||||
for k := range allMap {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
allList := make([]*packages.Package, 0, len(keys))
|
||||
for _, k := range keys {
|
||||
allList = append(allList, allMap[k])
|
||||
}
|
||||
|
||||
for _, pkg := range allList {
|
||||
if len(pkg.GoFiles) > 0 {
|
||||
if len(pkg.Errors) > 0 {
|
||||
errors = append(errors, pkg.Errors...)
|
||||
}
|
||||
}
|
||||
if *defuses {
|
||||
for id, obj := range pkg.TypesInfo.Defs {
|
||||
serialFprintf(os.Stdout, "%s: %q defines %v\n",
|
||||
pkg.Fset.Position(id.Pos()), id.Name, obj)
|
||||
}
|
||||
for id, obj := range pkg.TypesInfo.Uses {
|
||||
serialFprintf(os.Stdout, "%s: %q uses %v\n",
|
||||
pkg.Fset.Position(id.Pos()), id.Name, obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
if *timings {
|
||||
serialFprintf(os.Stdout, "%s took %.1fs\n", plat, time.Since(start).Seconds())
|
||||
}
|
||||
return dedup(errors), nil
|
||||
}
|
||||
|
||||
func dedupeErrors(out io.Writer, results chan analyzerResult, nDirs, nPlatforms int) {
|
||||
pkgRes := make(map[string][]analyzerResult)
|
||||
for done := 0; done < nDirs; {
|
||||
res := <-results
|
||||
pkgRes[res.dir] = append(pkgRes[res.dir], res)
|
||||
if len(pkgRes[res.dir]) != nPlatforms {
|
||||
continue // expect more results for dir
|
||||
func dedup(errors []packages.Error) []string {
|
||||
ret := []string{}
|
||||
|
||||
m := map[string]bool{}
|
||||
for _, e := range errors {
|
||||
es := e.Error()
|
||||
if !m[es] {
|
||||
ret = append(ret, es)
|
||||
m[es] = true
|
||||
}
|
||||
done++
|
||||
// Collect list of platforms for each error
|
||||
errPlats := map[string][]string{}
|
||||
for _, res := range pkgRes[res.dir] {
|
||||
for _, err := range res.errors {
|
||||
errPlats[err] = append(errPlats[err], res.platform)
|
||||
}
|
||||
}
|
||||
// Print each error (in the same order!) once.
|
||||
for _, res := range pkgRes[res.dir] {
|
||||
for _, err := range res.errors {
|
||||
if errPlats[err] == nil {
|
||||
continue // already printed
|
||||
}
|
||||
sort.Strings(errPlats[err])
|
||||
plats := strings.Join(errPlats[err], ",")
|
||||
if len(errPlats[err]) == len(crossPlatforms) {
|
||||
plats = "all"
|
||||
} else if plats == darwinPlatString {
|
||||
plats = "darwin"
|
||||
} else if plats == windowsPlatString {
|
||||
plats = "windows"
|
||||
}
|
||||
fmt.Fprintf(out, "%sERROR(%s) %s\n", logPrefix, plats, err)
|
||||
delete(errPlats, err)
|
||||
}
|
||||
}
|
||||
delete(pkgRes, res.dir)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
var outMu sync.Mutex
|
||||
|
||||
func serialFprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||
outMu.Lock()
|
||||
defer outMu.Unlock()
|
||||
return fmt.Fprintf(w, format, a...)
|
||||
}
|
||||
|
||||
func main() {
|
||||
@ -336,97 +257,53 @@ func main() {
|
||||
args = append(args, ".")
|
||||
}
|
||||
|
||||
c := collector{
|
||||
ignoreDirs: append([]string(nil), standardIgnoreDirs...),
|
||||
}
|
||||
if *ignoreDirs != "" {
|
||||
c.ignoreDirs = append(c.ignoreDirs, strings.Split(*ignoreDirs, ",")...)
|
||||
c := newCollector(*ignoreDirs)
|
||||
|
||||
if err := c.walk(args); err != nil {
|
||||
log.Fatalf("Error walking: %v", err)
|
||||
}
|
||||
|
||||
for _, arg := range args {
|
||||
err := filepath.Walk(arg, c.handlePath)
|
||||
if err != nil {
|
||||
log.Fatalf("Error walking: %v", err)
|
||||
}
|
||||
}
|
||||
sort.Strings(c.dirs)
|
||||
|
||||
ps := crossPlatforms[:]
|
||||
plats := crossPlatforms[:]
|
||||
if *platforms != "" {
|
||||
ps = strings.Split(*platforms, ",")
|
||||
plats = strings.Split(*platforms, ",")
|
||||
} else if !*cross {
|
||||
ps = ps[:1]
|
||||
plats = plats[:1]
|
||||
}
|
||||
|
||||
fmt.Println("type-checking: ", strings.Join(ps, ", "))
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var processedDirs int64
|
||||
var currentWork int64 // (dir_index << 8) | platform_index
|
||||
statuses := make([]int, len(ps))
|
||||
var results chan analyzerResult
|
||||
if !*serial {
|
||||
results = make(chan analyzerResult)
|
||||
var failMu sync.Mutex
|
||||
failed := false
|
||||
for _, plat := range plats {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
dedupeErrors(os.Stderr, results, len(c.dirs), len(ps))
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
for i, p := range ps {
|
||||
wg.Add(1)
|
||||
fn := func(i int, p string) {
|
||||
start := time.Now()
|
||||
a := newAnalyzer(p)
|
||||
for n, dir := range c.dirs {
|
||||
a.collect(dir)
|
||||
atomic.AddInt64(&processedDirs, 1)
|
||||
atomic.StoreInt64(¤tWork, int64(n<<8|i))
|
||||
if results != nil {
|
||||
results <- analyzerResult{p, dir, a.dumpAndResetErrors()}
|
||||
fn := func(plat string) {
|
||||
f := false
|
||||
serialFprintf(os.Stdout, "type-checking %s\n", plat)
|
||||
errors, err := c.verify(plat)
|
||||
if err != nil {
|
||||
serialFprintf(os.Stderr, "ERROR(%s): failed to verify: %v\n", plat, err)
|
||||
} else if len(errors) > 0 {
|
||||
for _, e := range errors {
|
||||
// Special case CGo errors which may depend on headers we
|
||||
// don't have.
|
||||
if !strings.HasSuffix(e, "could not import C (no metadata for C)") {
|
||||
f = true
|
||||
serialFprintf(os.Stderr, "ERROR(%s): %s\n", plat, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
if a.failed {
|
||||
statuses[i] = 1
|
||||
}
|
||||
if *timings {
|
||||
fmt.Printf("%s took %.1fs\n", p, time.Since(start).Seconds())
|
||||
}
|
||||
failMu.Lock()
|
||||
failed = failed || f
|
||||
failMu.Unlock()
|
||||
wg.Done()
|
||||
}
|
||||
if *serial {
|
||||
fn(i, p)
|
||||
fn(plat)
|
||||
} else {
|
||||
go fn(i, p)
|
||||
go fn(plat)
|
||||
}
|
||||
}
|
||||
if isTerminal {
|
||||
logPrefix = "\r" // clear status bar when printing
|
||||
// Display a status bar so devs can estimate completion times.
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
total := len(ps) * len(c.dirs)
|
||||
for proc := 0; ; proc = int(atomic.LoadInt64(&processedDirs)) {
|
||||
work := atomic.LoadInt64(¤tWork)
|
||||
dir := c.dirs[work>>8]
|
||||
platform := ps[work&0xFF]
|
||||
if len(dir) > 80 {
|
||||
dir = dir[:80]
|
||||
}
|
||||
fmt.Printf("\r%d/%d \033[2m%-13s\033[0m %-80s", proc, total, platform, dir)
|
||||
if proc == total {
|
||||
fmt.Println()
|
||||
break
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
for _, status := range statuses {
|
||||
if status != 0 {
|
||||
os.Exit(status)
|
||||
}
|
||||
if failed {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
@ -17,131 +17,57 @@ limitations under the License.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"io/ioutil"
|
||||
"flag"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
var packageCases = []struct {
|
||||
code string
|
||||
errs map[string]string
|
||||
}{
|
||||
// Empty: no problems!
|
||||
{"", map[string]string{"linux/amd64": ""}},
|
||||
// Slightly less empty: no problems!
|
||||
{"func getRandomNumber() int { return 4; }", map[string]string{"darwin/386": ""}},
|
||||
// Fixed in #59243
|
||||
{`import "golang.org/x/sys/unix"
|
||||
func f(err error) {
|
||||
if err != unix.ENXIO {
|
||||
panic("woops")
|
||||
// This exists because `go` is not always in the PATH when running CI.
|
||||
var goBinary = flag.String("go", "", "path to a `go` binary")
|
||||
|
||||
func TestVerify(t *testing.T) {
|
||||
// x/tools/packages is going to literally exec `go`, so it needs some
|
||||
// setup.
|
||||
setEnvVars()
|
||||
|
||||
tcs := []struct {
|
||||
path string
|
||||
expect int
|
||||
}{
|
||||
{"./testdata/good", 0},
|
||||
{"./testdata/bad", 18},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
c := newCollector("")
|
||||
if err := c.walk([]string{tc.path}); err != nil {
|
||||
t.Fatalf("error walking %s: %v", tc.path, err)
|
||||
}
|
||||
}`, map[string]string{"linux/amd64": "", "windows/amd64": "test.go:4:18: ENXIO not declared by package unix"}},
|
||||
// Fixed in #51984
|
||||
{`import "golang.org/x/sys/unix"
|
||||
const linuxHugetlbfsMagic = 0x958458f6
|
||||
func IsHugeTlbfs() bool {
|
||||
buf := unix.Statfs_t{}
|
||||
unix.Statfs("/tmp/", &buf)
|
||||
return buf.Type == linuxHugetlbfsMagic
|
||||
}`, map[string]string{
|
||||
"linux/amd64": "",
|
||||
"linux/386": "test.go:7:22: linuxHugetlbfsMagic (untyped int constant 2508478710) overflows int32",
|
||||
}},
|
||||
// Fixed in #51873
|
||||
{`var a = map[string]interface{}{"num1": 9223372036854775807}`,
|
||||
map[string]string{"linux/arm": "test.go:2:40: 9223372036854775807 (untyped int constant) overflows int"}},
|
||||
|
||||
errs, err := c.verify("linux/amd64")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
} else if len(errs) != tc.expect {
|
||||
t.Errorf("Expected %d errors, got %d: %v", tc.expect, len(errs), errs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var testFiles = map[string]string{
|
||||
"golang.org/x/sys/unix/empty.go": `package unix`,
|
||||
"golang.org/x/sys/unix/errno_linux.go": `// +build linux
|
||||
package unix
|
||||
|
||||
type Errno string
|
||||
func (e Errno) Error() string { return string(e) }
|
||||
|
||||
var ENXIO = Errno("3")`,
|
||||
"golang.org/x/sys/unix/ztypes_linux_amd64.go": `// +build amd64,linux
|
||||
package unix
|
||||
type Statfs_t struct {
|
||||
Type int64
|
||||
}
|
||||
func Statfs(path string, statfs *Statfs_t) {}
|
||||
`,
|
||||
"golang.org/x/sys/unix/ztypes_linux_386.go": `// +build i386,linux
|
||||
package unix
|
||||
type Statfs_t struct {
|
||||
Type int32
|
||||
}
|
||||
func Statfs(path string, statfs *Statfs_t) {}
|
||||
`,
|
||||
}
|
||||
|
||||
func TestHandlePackage(t *testing.T) {
|
||||
// When running in Bazel, we don't have access to Go source code. Fake it instead!
|
||||
tmpDir, err := ioutil.TempDir("", "test_typecheck")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
for path, data := range testFiles {
|
||||
path := filepath.Join(tmpDir, "src", path)
|
||||
err := os.MkdirAll(filepath.Dir(path), 0755)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
func setEnvVars() {
|
||||
if *goBinary != "" {
|
||||
newPath := filepath.Dir(*goBinary)
|
||||
curPath := os.Getenv("PATH")
|
||||
if curPath != "" {
|
||||
newPath = newPath + ":" + curPath
|
||||
}
|
||||
err = ioutil.WriteFile(path, []byte(data), 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fmt.Println(path)
|
||||
os.Setenv("PATH", newPath)
|
||||
}
|
||||
|
||||
for _, test := range packageCases {
|
||||
for platform, expectedErr := range test.errs {
|
||||
a := newAnalyzer(platform)
|
||||
// Make Imports happen relative to our faked up GOROOT.
|
||||
a.ctx.GOROOT = tmpDir
|
||||
a.ctx.GOPATH = ""
|
||||
|
||||
errs := []string{}
|
||||
a.conf.Error = func(err error) {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
|
||||
code := "package test\n" + test.code
|
||||
parsed, err := parser.ParseFile(a.fset, "test.go", strings.NewReader(code), parser.AllErrors)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
a.typeCheck(tmpDir, []*ast.File{parsed})
|
||||
|
||||
if expectedErr == "" {
|
||||
if len(errs) > 0 {
|
||||
t.Errorf("code:\n%s\ngot %v\nwant %v",
|
||||
code, errs, expectedErr)
|
||||
}
|
||||
} else {
|
||||
if len(errs) != 1 {
|
||||
t.Errorf("code:\n%s\ngot %v\nwant %v",
|
||||
code, errs, expectedErr)
|
||||
} else {
|
||||
if errs[0] != expectedErr {
|
||||
t.Errorf("code:\n%s\ngot %v\nwant %v",
|
||||
code, errs[0], expectedErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if os.Getenv("HOME") == "" {
|
||||
os.Setenv("HOME", "/tmp")
|
||||
}
|
||||
}
|
||||
|
||||
@ -159,37 +85,38 @@ func TestHandlePath(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDedupeErrors(t *testing.T) {
|
||||
func TestDedup(t *testing.T) {
|
||||
testcases := []struct {
|
||||
nPlatforms int
|
||||
results []analyzerResult
|
||||
expected string
|
||||
}{
|
||||
{1, []analyzerResult{}, ""},
|
||||
{1, []analyzerResult{{"linux/arm", "test", nil}}, ""},
|
||||
{1, []analyzerResult{
|
||||
{"linux/arm", "test", []string{"a"}}},
|
||||
"ERROR(linux/arm) a\n"},
|
||||
{3, []analyzerResult{
|
||||
{"linux/arm", "test", []string{"a"}},
|
||||
{"windows/386", "test", []string{"b"}},
|
||||
{"windows/amd64", "test", []string{"b", "c"}}},
|
||||
"ERROR(linux/arm) a\n" +
|
||||
"ERROR(windows) b\n" +
|
||||
"ERROR(windows/amd64) c\n"},
|
||||
}
|
||||
for _, tc := range testcases {
|
||||
out := &bytes.Buffer{}
|
||||
results := make(chan analyzerResult, len(tc.results))
|
||||
for _, res := range tc.results {
|
||||
results <- res
|
||||
}
|
||||
close(results)
|
||||
dedupeErrors(out, results, len(tc.results)/tc.nPlatforms, tc.nPlatforms)
|
||||
outString := out.String()
|
||||
if outString != tc.expected {
|
||||
t.Errorf("dedupeErrors(%v) = '%s', expected '%s'",
|
||||
tc.results, outString, tc.expected)
|
||||
input []packages.Error
|
||||
expected int
|
||||
}{{
|
||||
input: nil,
|
||||
expected: 0,
|
||||
}, {
|
||||
input: []packages.Error{
|
||||
{Pos: "file:7", Msg: "message", Kind: packages.ParseError},
|
||||
},
|
||||
expected: 1,
|
||||
}, {
|
||||
input: []packages.Error{
|
||||
{Pos: "file:7", Msg: "message1", Kind: packages.ParseError},
|
||||
{Pos: "file:8", Msg: "message2", Kind: packages.ParseError},
|
||||
},
|
||||
expected: 2,
|
||||
}, {
|
||||
input: []packages.Error{
|
||||
{Pos: "file:7", Msg: "message1", Kind: packages.ParseError},
|
||||
{Pos: "file:8", Msg: "message2", Kind: packages.ParseError},
|
||||
{Pos: "file:7", Msg: "message1", Kind: packages.ParseError},
|
||||
},
|
||||
expected: 2,
|
||||
}}
|
||||
|
||||
for i, tc := range testcases {
|
||||
out := dedup(tc.input)
|
||||
if len(out) != tc.expected {
|
||||
t.Errorf("[%d] dedup(%v) = '%v', expected %d",
|
||||
i, tc.input, out, tc.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
52
test/typecheck/testdata/bad/bad.go
vendored
Normal file
52
test/typecheck/testdata/bad/bad.go
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
Copyright 2020 The Kubernetes 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 main
|
||||
|
||||
import "fmt"
|
||||
|
||||
var i int = int16(0)
|
||||
var pi *int = new(int16)
|
||||
var i16 int16 = int(0)
|
||||
var pi16 *int16 = new(int)
|
||||
var i32 int32 = int64(0)
|
||||
var pi32 *int32 = new(int64)
|
||||
var i64 int64 = int32(0)
|
||||
var pi64 *int64 = new(int32)
|
||||
|
||||
var f32 float32 = float64(0.0)
|
||||
var pf32 *float32 = new(float64)
|
||||
var f64 float64 = float32(0.0)
|
||||
var pf64 *float64 = new(float32)
|
||||
|
||||
var str string = false
|
||||
var pstr *string = new(bool)
|
||||
|
||||
type struc struct {
|
||||
i int
|
||||
f float64
|
||||
s string
|
||||
}
|
||||
|
||||
var stru struc = &struc{}
|
||||
var pstru *struc = struc{}
|
||||
|
||||
var sli []int = map[string]int{"zero": 0}
|
||||
var ma map[string]int = []int{0}
|
||||
|
||||
func main() {
|
||||
fmt.Println("hello, world!")
|
||||
}
|
25
test/typecheck/testdata/good/_ignore/bad.go
vendored
Normal file
25
test/typecheck/testdata/good/_ignore/bad.go
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
Copyright 2020 The Kubernetes 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 main
|
||||
|
||||
import "fmt"
|
||||
|
||||
var i int = int16(0)
|
||||
|
||||
func main() {
|
||||
fmt.Println("hello, world!")
|
||||
}
|
52
test/typecheck/testdata/good/good.go
vendored
Normal file
52
test/typecheck/testdata/good/good.go
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
Copyright 2020 The Kubernetes 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 main
|
||||
|
||||
import "fmt"
|
||||
|
||||
var i int = int(0)
|
||||
var pi *int = new(int)
|
||||
var i16 int16 = int16(0)
|
||||
var pi16 *int16 = new(int16)
|
||||
var i32 int32 = int32(0)
|
||||
var pi32 *int32 = new(int32)
|
||||
var i64 int64 = int64(0)
|
||||
var pi64 *int64 = new(int64)
|
||||
|
||||
var f32 float32 = float32(0.0)
|
||||
var pf32 *float32 = new(float32)
|
||||
var f64 float64 = float64(0.0)
|
||||
var pf64 *float64 = new(float64)
|
||||
|
||||
var str string = "a string"
|
||||
var pstr *string = new(string)
|
||||
|
||||
type struc struct {
|
||||
i int
|
||||
f float64
|
||||
s string
|
||||
}
|
||||
|
||||
var stru struc = struc{}
|
||||
var pstru *struc = &struc{}
|
||||
|
||||
var sli []int = []int{0}
|
||||
var ma map[string]int = map[string]int{"zero": 0}
|
||||
|
||||
func main() {
|
||||
fmt.Println("hello, world!")
|
||||
}
|
25
test/typecheck/testdata/good/testdata/bad.go
vendored
Normal file
25
test/typecheck/testdata/good/testdata/bad.go
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
Copyright 2020 The Kubernetes 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 main
|
||||
|
||||
import "fmt"
|
||||
|
||||
var i int = int16(0)
|
||||
|
||||
func main() {
|
||||
fmt.Println("hello, world!")
|
||||
}
|
Loading…
Reference in New Issue
Block a user