mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-19 01:40:13 +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")
|
all_packages+=("./$line")
|
||||||
done < <( hack/make-rules/helpers/cache_go_dirs.sh "${KUBE_ROOT}/_tmp/all_go_dirs" |
|
done < <( hack/make-rules/helpers/cache_go_dirs.sh "${KUBE_ROOT}/_tmp/all_go_dirs" |
|
||||||
grep "^${FOCUS:-.}" |
|
grep "^${FOCUS:-.}" |
|
||||||
grep -vE "(third_party|generated|clientset_generated|hack|/_)" |
|
grep -vE "(third_party|generated|clientset_generated|hack|testdata|/_)" |
|
||||||
grep -vE "$ignore_pattern" )
|
grep -vE "$ignore_pattern" )
|
||||||
|
|
||||||
failing_packages=()
|
failing_packages=()
|
||||||
|
@ -30,10 +30,9 @@ cd "${KUBE_ROOT}"
|
|||||||
|
|
||||||
make --no-print-directory -C "${KUBE_ROOT}" generated_files
|
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
|
# As of June, 2020 the typecheck tool is written in terms of go/packages, but
|
||||||
# library doesn't work well with modules. Guidance is to rewrite tools against
|
# that library doesn't work well with multiple modules. Until that is done,
|
||||||
# golang.org/x/tools/go/packages. Until that is done, force this tooling to
|
# force this tooling to run in a fake GOPATH.
|
||||||
# run in a fake GOPATH.
|
|
||||||
ret=0
|
ret=0
|
||||||
hack/run-in-gopath.sh \
|
hack/run-in-gopath.sh \
|
||||||
go run test/typecheck/main.go "$@" || ret=$?
|
go run test/typecheck/main.go "$@" || ret=$?
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# gazelle:exclude testdata
|
||||||
package(default_visibility = ["//visibility:public"])
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
load(
|
load(
|
||||||
@ -16,10 +17,7 @@ go_library(
|
|||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = ["main.go"],
|
srcs = ["main.go"],
|
||||||
importpath = "k8s.io/kubernetes/test/typecheck",
|
importpath = "k8s.io/kubernetes/test/typecheck",
|
||||||
deps = [
|
deps = ["//vendor/golang.org/x/tools/go/packages:go_default_library"],
|
||||||
"//third_party/go-srcimporter:go_default_library",
|
|
||||||
"//vendor/golang.org/x/crypto/ssh/terminal:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
filegroup(
|
filegroup(
|
||||||
@ -43,5 +41,12 @@ go_binary(
|
|||||||
go_test(
|
go_test(
|
||||||
name = "go_default_test",
|
name = "go_default_test",
|
||||||
srcs = ["main_test.go"],
|
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"],
|
embed = [":go_default_library"],
|
||||||
|
deps = ["//vendor/golang.org/x/tools/go/packages:go_default_library"],
|
||||||
)
|
)
|
||||||
|
@ -20,11 +20,6 @@ package main
|
|||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/ast"
|
|
||||||
"go/build"
|
|
||||||
"go/parser"
|
|
||||||
"go/token"
|
|
||||||
"go/types"
|
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
@ -32,12 +27,9 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh/terminal"
|
"golang.org/x/tools/go/packages"
|
||||||
|
|
||||||
srcimporter "k8s.io/kubernetes/third_party/go-srcimporter"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -51,9 +43,6 @@ var (
|
|||||||
tags = flag.String("tags", "", "comma-separated list of build tags to apply in addition to go's defaults")
|
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")
|
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
|
// When processed in order, windows and darwin are early to make
|
||||||
// interesting OS-based errors happen earlier.
|
// interesting OS-based errors happen earlier.
|
||||||
crossPlatforms = []string{
|
crossPlatforms = []string{
|
||||||
@ -63,8 +52,6 @@ var (
|
|||||||
"linux/arm64", "linux/ppc64le",
|
"linux/arm64", "linux/ppc64le",
|
||||||
"linux/s390x", "darwin/386",
|
"linux/s390x", "darwin/386",
|
||||||
}
|
}
|
||||||
darwinPlatString = "darwin/386,darwin/amd64"
|
|
||||||
windowsPlatString = "windows/386,windows/amd64"
|
|
||||||
|
|
||||||
// directories we always ignore
|
// directories we always ignore
|
||||||
standardIgnoreDirs = []string{
|
standardIgnoreDirs = []string{
|
||||||
@ -88,168 +75,29 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
type analyzer struct {
|
func newConfig(platform string) *packages.Config {
|
||||||
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
|
|
||||||
platSplit := strings.Split(platform, "/")
|
platSplit := strings.Split(platform, "/")
|
||||||
ctx.GOOS, ctx.GOARCH = platSplit[0], platSplit[1]
|
goos, goarch := platSplit[0], platSplit[1]
|
||||||
ctx.CgoEnabled = true
|
mode := packages.NeedName | packages.NeedFiles | packages.NeedTypes | packages.NeedSyntax | packages.NeedDeps | packages.NeedImports
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if *defuses {
|
if *defuses {
|
||||||
for id, obj := range info.Defs {
|
mode = mode | packages.NeedTypesInfo
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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 {
|
type collector struct {
|
||||||
@ -257,6 +105,27 @@ type collector struct {
|
|||||||
ignoreDirs []string
|
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,
|
// handlePath walks the filesystem recursively, collecting directories,
|
||||||
// ignoring some unneeded directories (hidden/vendored) that are handled
|
// ignoring some unneeded directories (hidden/vendored) that are handled
|
||||||
// specially later.
|
// specially later.
|
||||||
@ -265,63 +134,115 @@ func (c *collector) handlePath(path string, info os.FileInfo, err error) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if info.IsDir() {
|
if info.IsDir() {
|
||||||
|
name := info.Name()
|
||||||
// Ignore hidden directories (.git, .cache, etc)
|
// 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
|
return filepath.SkipDir
|
||||||
}
|
}
|
||||||
for _, dir := range c.ignoreDirs {
|
for _, dir := range c.ignoreDirs {
|
||||||
if path == dir {
|
if path == dir {
|
||||||
|
if *verbose {
|
||||||
|
fmt.Printf("DBG: ignoring dir %s\n", path)
|
||||||
|
}
|
||||||
return filepath.SkipDir
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type analyzerResult struct {
|
func (c *collector) verify(plat string) ([]string, error) {
|
||||||
platform string
|
errors := []packages.Error{}
|
||||||
dir string
|
start := time.Now()
|
||||||
errors []string
|
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) {
|
func dedup(errors []packages.Error) []string {
|
||||||
pkgRes := make(map[string][]analyzerResult)
|
ret := []string{}
|
||||||
for done := 0; done < nDirs; {
|
|
||||||
res := <-results
|
m := map[string]bool{}
|
||||||
pkgRes[res.dir] = append(pkgRes[res.dir], res)
|
for _, e := range errors {
|
||||||
if len(pkgRes[res.dir]) != nPlatforms {
|
es := e.Error()
|
||||||
continue // expect more results for dir
|
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() {
|
func main() {
|
||||||
@ -336,97 +257,53 @@ func main() {
|
|||||||
args = append(args, ".")
|
args = append(args, ".")
|
||||||
}
|
}
|
||||||
|
|
||||||
c := collector{
|
c := newCollector(*ignoreDirs)
|
||||||
ignoreDirs: append([]string(nil), standardIgnoreDirs...),
|
|
||||||
}
|
if err := c.walk(args); err != nil {
|
||||||
if *ignoreDirs != "" {
|
log.Fatalf("Error walking: %v", err)
|
||||||
c.ignoreDirs = append(c.ignoreDirs, strings.Split(*ignoreDirs, ",")...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, arg := range args {
|
plats := crossPlatforms[:]
|
||||||
err := filepath.Walk(arg, c.handlePath)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error walking: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Strings(c.dirs)
|
|
||||||
|
|
||||||
ps := crossPlatforms[:]
|
|
||||||
if *platforms != "" {
|
if *platforms != "" {
|
||||||
ps = strings.Split(*platforms, ",")
|
plats = strings.Split(*platforms, ",")
|
||||||
} else if !*cross {
|
} else if !*cross {
|
||||||
ps = ps[:1]
|
plats = plats[:1]
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("type-checking: ", strings.Join(ps, ", "))
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
var processedDirs int64
|
var failMu sync.Mutex
|
||||||
var currentWork int64 // (dir_index << 8) | platform_index
|
failed := false
|
||||||
statuses := make([]int, len(ps))
|
for _, plat := range plats {
|
||||||
var results chan analyzerResult
|
|
||||||
if !*serial {
|
|
||||||
results = make(chan analyzerResult)
|
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
fn := func(plat string) {
|
||||||
dedupeErrors(os.Stderr, results, len(c.dirs), len(ps))
|
f := false
|
||||||
wg.Done()
|
serialFprintf(os.Stdout, "type-checking %s\n", plat)
|
||||||
}()
|
errors, err := c.verify(plat)
|
||||||
}
|
if err != nil {
|
||||||
for i, p := range ps {
|
serialFprintf(os.Stderr, "ERROR(%s): failed to verify: %v\n", plat, err)
|
||||||
wg.Add(1)
|
} else if len(errors) > 0 {
|
||||||
fn := func(i int, p string) {
|
for _, e := range errors {
|
||||||
start := time.Now()
|
// Special case CGo errors which may depend on headers we
|
||||||
a := newAnalyzer(p)
|
// don't have.
|
||||||
for n, dir := range c.dirs {
|
if !strings.HasSuffix(e, "could not import C (no metadata for C)") {
|
||||||
a.collect(dir)
|
f = true
|
||||||
atomic.AddInt64(&processedDirs, 1)
|
serialFprintf(os.Stderr, "ERROR(%s): %s\n", plat, e)
|
||||||
atomic.StoreInt64(¤tWork, int64(n<<8|i))
|
}
|
||||||
if results != nil {
|
|
||||||
results <- analyzerResult{p, dir, a.dumpAndResetErrors()}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if a.failed {
|
failMu.Lock()
|
||||||
statuses[i] = 1
|
failed = failed || f
|
||||||
}
|
failMu.Unlock()
|
||||||
if *timings {
|
|
||||||
fmt.Printf("%s took %.1fs\n", p, time.Since(start).Seconds())
|
|
||||||
}
|
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}
|
}
|
||||||
if *serial {
|
if *serial {
|
||||||
fn(i, p)
|
fn(plat)
|
||||||
} else {
|
} 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()
|
wg.Wait()
|
||||||
for _, status := range statuses {
|
if failed {
|
||||||
if status != 0 {
|
os.Exit(1)
|
||||||
os.Exit(status)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,131 +17,57 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"flag"
|
||||||
"go/ast"
|
|
||||||
"go/parser"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/packages"
|
||||||
)
|
)
|
||||||
|
|
||||||
var packageCases = []struct {
|
// This exists because `go` is not always in the PATH when running CI.
|
||||||
code string
|
var goBinary = flag.String("go", "", "path to a `go` binary")
|
||||||
errs map[string]string
|
|
||||||
}{
|
func TestVerify(t *testing.T) {
|
||||||
// Empty: no problems!
|
// x/tools/packages is going to literally exec `go`, so it needs some
|
||||||
{"", map[string]string{"linux/amd64": ""}},
|
// setup.
|
||||||
// Slightly less empty: no problems!
|
setEnvVars()
|
||||||
{"func getRandomNumber() int { return 4; }", map[string]string{"darwin/386": ""}},
|
|
||||||
// Fixed in #59243
|
tcs := []struct {
|
||||||
{`import "golang.org/x/sys/unix"
|
path string
|
||||||
func f(err error) {
|
expect int
|
||||||
if err != unix.ENXIO {
|
}{
|
||||||
panic("woops")
|
{"./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
|
errs, err := c.verify("linux/amd64")
|
||||||
{`import "golang.org/x/sys/unix"
|
if err != nil {
|
||||||
const linuxHugetlbfsMagic = 0x958458f6
|
t.Errorf("unexpected error: %v", err)
|
||||||
func IsHugeTlbfs() bool {
|
} else if len(errs) != tc.expect {
|
||||||
buf := unix.Statfs_t{}
|
t.Errorf("Expected %d errors, got %d: %v", tc.expect, len(errs), errs)
|
||||||
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"}},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var testFiles = map[string]string{
|
func setEnvVars() {
|
||||||
"golang.org/x/sys/unix/empty.go": `package unix`,
|
if *goBinary != "" {
|
||||||
"golang.org/x/sys/unix/errno_linux.go": `// +build linux
|
newPath := filepath.Dir(*goBinary)
|
||||||
package unix
|
curPath := os.Getenv("PATH")
|
||||||
|
if curPath != "" {
|
||||||
type Errno string
|
newPath = newPath + ":" + curPath
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
err = ioutil.WriteFile(path, []byte(data), 0644)
|
os.Setenv("PATH", newPath)
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
fmt.Println(path)
|
|
||||||
}
|
}
|
||||||
|
if os.Getenv("HOME") == "" {
|
||||||
for _, test := range packageCases {
|
os.Setenv("HOME", "/tmp")
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,37 +85,38 @@ func TestHandlePath(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDedupeErrors(t *testing.T) {
|
func TestDedup(t *testing.T) {
|
||||||
testcases := []struct {
|
testcases := []struct {
|
||||||
nPlatforms int
|
input []packages.Error
|
||||||
results []analyzerResult
|
expected int
|
||||||
expected string
|
}{{
|
||||||
}{
|
input: nil,
|
||||||
{1, []analyzerResult{}, ""},
|
expected: 0,
|
||||||
{1, []analyzerResult{{"linux/arm", "test", nil}}, ""},
|
}, {
|
||||||
{1, []analyzerResult{
|
input: []packages.Error{
|
||||||
{"linux/arm", "test", []string{"a"}}},
|
{Pos: "file:7", Msg: "message", Kind: packages.ParseError},
|
||||||
"ERROR(linux/arm) a\n"},
|
},
|
||||||
{3, []analyzerResult{
|
expected: 1,
|
||||||
{"linux/arm", "test", []string{"a"}},
|
}, {
|
||||||
{"windows/386", "test", []string{"b"}},
|
input: []packages.Error{
|
||||||
{"windows/amd64", "test", []string{"b", "c"}}},
|
{Pos: "file:7", Msg: "message1", Kind: packages.ParseError},
|
||||||
"ERROR(linux/arm) a\n" +
|
{Pos: "file:8", Msg: "message2", Kind: packages.ParseError},
|
||||||
"ERROR(windows) b\n" +
|
},
|
||||||
"ERROR(windows/amd64) c\n"},
|
expected: 2,
|
||||||
}
|
}, {
|
||||||
for _, tc := range testcases {
|
input: []packages.Error{
|
||||||
out := &bytes.Buffer{}
|
{Pos: "file:7", Msg: "message1", Kind: packages.ParseError},
|
||||||
results := make(chan analyzerResult, len(tc.results))
|
{Pos: "file:8", Msg: "message2", Kind: packages.ParseError},
|
||||||
for _, res := range tc.results {
|
{Pos: "file:7", Msg: "message1", Kind: packages.ParseError},
|
||||||
results <- res
|
},
|
||||||
}
|
expected: 2,
|
||||||
close(results)
|
}}
|
||||||
dedupeErrors(out, results, len(tc.results)/tc.nPlatforms, tc.nPlatforms)
|
|
||||||
outString := out.String()
|
for i, tc := range testcases {
|
||||||
if outString != tc.expected {
|
out := dedup(tc.input)
|
||||||
t.Errorf("dedupeErrors(%v) = '%s', expected '%s'",
|
if len(out) != tc.expected {
|
||||||
tc.results, outString, 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