1// Command testgate runs `go test` and refuses to call a skipped test a pass.2//3// `go test` exits zero when every test in a package skips, so a suite whose4// inputs were missing would say PASS while judging nothing. This command is5// the structural answer: a test that does not run is a finding, not a pass,6// and the required surface holds no optional inputs for one to be missing.7//8// Usage:9//10// go run ./tools/testgate [go test flags] [packages]11//12// Everything after the command name is handed to `go test -json` verbatim,13// so `go run ./tools/testgate -race ./...` means what it looks like. The14// output is the ordinary `go test` narration, reassembled from the JSON15// stream; on top of it, any test-level skip fails the run with the list of16// what skipped and why that is not allowed here.17//18// A package with no test files still reports itself as skipped in the JSON19// stream with no test name attached; that is the one skip this gate accepts,20// because there is nothing there to have judged.21package main2223import (24 "bufio"25 "encoding/json"26 "fmt"27 "os"28 "os/exec"29)3031// event is the subset of test2json's record this gate reads.32type event struct {33 Action string `json:"Action"`34 Package string `json:"Package"`35 Test string `json:"Test"`36 Output string `json:"Output"`37}3839func main() {40 args := append([]string{"test", "-json"}, os.Args[1:]...)41 if len(os.Args) == 1 {42 args = append(args, "./...")43 }44 cmd := exec.Command("go", args...)45 cmd.Stderr = os.Stderr46 out, err := cmd.StdoutPipe()47 if err != nil {48 fmt.Fprintln(os.Stderr, "testgate:", err)49 os.Exit(1)50 }51 if err := cmd.Start(); err != nil {52 fmt.Fprintln(os.Stderr, "testgate:", err)53 os.Exit(1)54 }5556 var skipped []string57 scanner := bufio.NewScanner(out)58 scanner.Buffer(make([]byte, 0, 1024*1024), 16*1024*1024)59 for scanner.Scan() {60 line := scanner.Bytes()61 var ev event62 if err := json.Unmarshal(line, &ev); err != nil {63 // Non-JSON lines happen when a build fails; pass them through.64 os.Stdout.Write(append(line, '\n'))65 continue66 }67 if ev.Action == "output" {68 os.Stdout.WriteString(ev.Output)69 }70 if ev.Action == "skip" && ev.Test != "" {71 skipped = append(skipped, ev.Package+" "+ev.Test)72 }73 }74 if err := scanner.Err(); err != nil {75 fmt.Fprintln(os.Stderr, "testgate:", err)76 os.Exit(1)77 }7879 code := 080 if err := cmd.Wait(); err != nil {81 if exit, ok := err.(*exec.ExitError); ok {82 code = exit.ExitCode()83 } else {84 fmt.Fprintln(os.Stderr, "testgate:", err)85 code = 186 }87 }88 if len(skipped) > 0 {89 fmt.Fprintf(os.Stderr, "\ntestgate: %d test(s) skipped; a required suite may not skip:\n", len(skipped))90 for _, s := range skipped {91 fmt.Fprintln(os.Stderr, " ", s)92 }93 if code == 0 {94 code = 195 }96 }97 os.Exit(code)98}