diff --git a/test/typecheck/main.go b/test/typecheck/main.go index 411a945d47..2df32f41c8 100644 --- a/test/typecheck/main.go +++ b/test/typecheck/main.go @@ -24,6 +24,7 @@ import ( "go/build" "go/parser" "go/token" + "io" "log" "os" "path/filepath" @@ -60,6 +61,8 @@ var ( "linux/arm64", "linux/ppc64le", "linux/s390x", "darwin/386", } + darwinPlatString = "darwin/386,darwin/amd64" + windowsPlatString = "windows/386,windows/amd64" ) type analyzer struct { @@ -69,6 +72,7 @@ type analyzer struct { failed bool platform string donePaths map[string]interface{} + errors []string } func newAnalyzer(platform string) *analyzer { @@ -120,11 +124,19 @@ func (a *analyzer) handleError(err error) { return } } - // TODO(rmmh): dedup errors across platforms? - fmt.Fprintf(os.Stderr, "%sERROR(%s) %s\n", logPrefix, a.platform, err) + 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 { @@ -262,6 +274,51 @@ func (c *collector) handlePath(path string, info os.FileInfo, err error) error { return nil } +type analyzerResult struct { + platform string + dir string + errors []string +} + +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 + } + 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) + } +} + func main() { flag.Parse() args := flag.Args() @@ -296,6 +353,15 @@ func main() { 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) + 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) { @@ -305,6 +371,9 @@ func main() { a.collect(dir) atomic.AddInt64(&processedDirs, 1) atomic.StoreInt64(¤tWork, int64(n<<8|i)) + if results != nil { + results <- analyzerResult{p, dir, a.dumpAndResetErrors()} + } } if a.failed { statuses[i] = 1 diff --git a/test/typecheck/main_test.go b/test/typecheck/main_test.go index 803963f2a8..9b1fb830d5 100644 --- a/test/typecheck/main_test.go +++ b/test/typecheck/main_test.go @@ -17,6 +17,7 @@ limitations under the License. package main import ( + "bytes" "errors" "fmt" "go/ast" @@ -155,3 +156,38 @@ func TestHandlePath(t *testing.T) { t.Error("should skip vendor") } } + +func TestDedupeErrors(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) + } + } +}