cmd/bench: integrate the Sweet benchmarks

For golang/go#49207.

Change-Id: Ib18c5f574e30333a7d9d80019e26d6a565f4db1e
Reviewed-on: https://go-review.googlesource.com/c/benchmarks/+/378274
Reviewed-by: Michael Pratt <mpratt@google.com>
Trust: Michael Knyszek <mknyszek@google.com>
diff --git a/cmd/bench/main.go b/cmd/bench/main.go
index e3bb6f2..12fe37b 100644
--- a/cmd/bench/main.go
+++ b/cmd/bench/main.go
@@ -68,6 +68,10 @@
 		pass = false
 		log.Printf("Error running bent: %v", err)
 	}
+	if err := sweet(tcs); err != nil {
+		pass = false
+		log.Printf("Error running sweet: %v", err)
+	}
 	if !pass {
 		return fmt.Errorf("benchmarks failed")
 	}
diff --git a/cmd/bench/sweet.go b/cmd/bench/sweet.go
new file mode 100644
index 0000000..54cbffd
--- /dev/null
+++ b/cmd/bench/sweet.go
@@ -0,0 +1,139 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"fmt"
+	"io"
+	"log"
+	"os"
+	"os/exec"
+	"path/filepath"
+
+	"golang.org/x/benchmarks/sweet/common"
+)
+
+func writeSweetConfiguration(filename string, tcs []*toolchain) error {
+	var cfg common.ConfigFile
+	for _, tc := range tcs {
+		cfg.Configs = append(cfg.Configs, &common.Config{
+			Name:   tc.Name,
+			GoRoot: tc.GOROOT(),
+		})
+	}
+	f, err := os.Create(filename)
+	if err != nil {
+		return fmt.Errorf("error creating configuration file for Sweet: %w", err)
+	}
+	defer f.Close()
+	b, err := common.ConfigFileMarshalTOML(&cfg)
+	if err != nil {
+		return fmt.Errorf("error marshaling configuration file for Sweet: %w", err)
+	}
+	if _, err := f.Write(b); err != nil {
+		return fmt.Errorf("error writing configuration file for Sweet: %w", err)
+	}
+	return nil
+}
+
+func sweet(tcs []*toolchain) (err error) {
+	tmpDir, err := os.MkdirTemp("", "go-sweet")
+	if err != nil {
+		return fmt.Errorf("error creating temporary directory: %w", err)
+	}
+	defer func() {
+		err = removeAllIncludingReadonly(tmpDir)
+		if err != nil {
+			err = fmt.Errorf("error removing temporary directory: %w", err)
+		}
+	}()
+	log.Printf("Sweet temporary directory: %s", tmpDir)
+
+	dirBytes, err := tcs[0].List("-f", "{{.Dir}}", "golang.org/x/benchmarks/sweet/cmd/sweet")
+	if err != nil {
+		return fmt.Errorf("finding sweet root: %w", err)
+	}
+	sweetRoot := filepath.Dir(filepath.Dir(string(dirBytes)))
+	sweetBin := filepath.Join(tmpDir, "sweet")
+
+	log.Printf("Building Sweet...")
+
+	// Build Sweet itself. N.B. we don't need to do this with the goroot
+	// under test since we aren't testing sweet itself, but we are sure that
+	// this toolchain exists.
+	if err := tcs[0].BuildPackage("golang.org/x/benchmarks/sweet/cmd/sweet", sweetBin); err != nil {
+		return fmt.Errorf("building sweet: %v", err)
+	}
+
+	log.Printf("Initializing Sweet...")
+
+	var assetsCacheDir string
+	if os.Getenv("GO_BUILDER_NAME") != "" {
+		// Be explicit that we want /tmp, because the builder is
+		// going to try and give us /workdir/tmp which will not
+		// have enough space for us.
+		assetsCacheDir = filepath.Join("/", "tmp", "go-sweet-assets")
+	} else {
+		assetsCacheDir = filepath.Join(tmpDir, "assets")
+	}
+	cmd := exec.Command(
+		sweetBin, "get",
+		"-cache", assetsCacheDir,
+		"-auth", "none",
+		"-assets-hash-file", fmt.Sprintf("%s/assets.hash", sweetRoot),
+	)
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+	if err := cmd.Run(); err != nil {
+		return fmt.Errorf("error running sweet get: %w", err)
+	}
+
+	confFile := filepath.Join(tmpDir, "config.toml")
+	if err := writeSweetConfiguration(confFile, tcs); err != nil {
+		return fmt.Errorf("error writing configuration: %w", err)
+	}
+
+	log.Printf("Running Sweet...")
+
+	// Finally we can actually run the benchmarks.
+	resultsDir := filepath.Join(tmpDir, "results")
+	workDir := filepath.Join(tmpDir, "work")
+	cmd = exec.Command(
+		sweetBin, "run",
+		"-run", "all",
+		"-count", "10",
+		"-bench-dir", fmt.Sprintf("%s/benchmarks", sweetRoot),
+		"-cache", assetsCacheDir,
+		"-work-dir", workDir,
+		"-results", resultsDir,
+		confFile,
+	)
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+	if err := cmd.Run(); err != nil {
+		return fmt.Errorf("error running sweet run: %w", err)
+	}
+
+	// Dump results to stdout.
+	for _, tc := range tcs {
+		matches, err := filepath.Glob(filepath.Join(resultsDir, "*", fmt.Sprintf("%s.results", tc.Name)))
+		if err != nil {
+			return fmt.Errorf("searching for results for %s in %s: %v", tc.Name, resultsDir, err)
+		}
+		fmt.Printf("toolchain: %s\n", tc.Name)
+		for _, match := range matches {
+			f, err := os.Open(match)
+			if err != nil {
+				return fmt.Errorf("opening result %s: %v", match, err)
+			}
+			if _, err := io.Copy(os.Stdout, f); err != nil {
+				f.Close()
+				return fmt.Errorf("reading result %s: %v", match, err)
+			}
+			f.Close()
+		}
+	}
+	return nil
+}
diff --git a/sweet/common/gotool.go b/sweet/common/gotool.go
index 633092f..a718950 100644
--- a/sweet/common/gotool.go
+++ b/sweet/common/gotool.go
@@ -41,6 +41,13 @@
 	return cmd.Run()
 }
 
+func (g *Go) List(args ...string) ([]byte, error) {
+	cmd := exec.Command(g.Tool, append([]string{"list"}, args...)...)
+	cmd.Env = g.Env.Collapse()
+	log.TraceCommand(cmd, false)
+	return cmd.CombinedOutput()
+}
+
 func (g *Go) GOROOT() string {
 	return filepath.Dir(filepath.Dir(g.Tool))
 }