diff --git a/hack/verify-staticcheck.sh b/hack/verify-staticcheck.sh index f9f99a52a20..d8fa486a129 100755 --- a/hack/verify-staticcheck.sh +++ b/hack/verify-staticcheck.sh @@ -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=() diff --git a/hack/verify-typecheck.sh b/hack/verify-typecheck.sh index ebdc4c06853..78117eee334 100755 --- a/hack/verify-typecheck.sh +++ b/hack/verify-typecheck.sh @@ -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=$? diff --git a/test/typecheck/BUILD b/test/typecheck/BUILD index a7b35fd050d..2e1f2b95bd5 100644 --- a/test/typecheck/BUILD +++ b/test/typecheck/BUILD @@ -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"], ) diff --git a/test/typecheck/main.go b/test/typecheck/main.go index 12c7a9ee6e7..3434c69ae99 100644 --- a/test/typecheck/main.go +++ b/test/typecheck/main.go @@ -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) } } diff --git a/test/typecheck/main_test.go b/test/typecheck/main_test.go index b643d0cde2a..163e4aded62 100644 --- a/test/typecheck/main_test.go +++ b/test/typecheck/main_test.go @@ -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) } } } diff --git a/test/typecheck/testdata/bad/bad.go b/test/typecheck/testdata/bad/bad.go new file mode 100644 index 00000000000..5bc10b89877 --- /dev/null +++ b/test/typecheck/testdata/bad/bad.go @@ -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!") +} diff --git a/test/typecheck/testdata/good/_ignore/bad.go b/test/typecheck/testdata/good/_ignore/bad.go new file mode 100644 index 00000000000..85dc39e0e8a --- /dev/null +++ b/test/typecheck/testdata/good/_ignore/bad.go @@ -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!") +} diff --git a/test/typecheck/testdata/good/good.go b/test/typecheck/testdata/good/good.go new file mode 100644 index 00000000000..31d2d05d9e4 --- /dev/null +++ b/test/typecheck/testdata/good/good.go @@ -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!") +} diff --git a/test/typecheck/testdata/good/testdata/bad.go b/test/typecheck/testdata/good/testdata/bad.go new file mode 100644 index 00000000000..85dc39e0e8a --- /dev/null +++ b/test/typecheck/testdata/good/testdata/bad.go @@ -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!") +}