cmd/test2json2gha/main.go (115 lines of code) (raw):

package main import ( "encoding/json" "flag" "fmt" "io" "log/slog" "os" "runtime/debug" "sync" "time" "github.com/pkg/errors" ) type config struct { slowThreshold time.Duration modName string verbose bool stream bool logDir string } func main() { tmp, err := os.MkdirTemp("", "test2json2gha-") if err != nil { panic(err) } var cfg config flag.DurationVar(&cfg.slowThreshold, "slow", 500*time.Millisecond, "Threshold to mark test as slow") flag.BoolVar(&cfg.verbose, "verbose", false, "Enable verbose output") flag.BoolVar(&cfg.stream, "stream", false, "Enable streaming output") flag.StringVar(&cfg.logDir, "logdir", "", "Directory to store all test logs") flag.Parse() logger := slog.New(slog.NewTextHandler(os.Stderr, nil)) slog.SetDefault(logger) info, _ := debug.ReadBuildInfo() if info != nil { cfg.modName = info.Main.Path } // Set TMPDIR so that [os.CreateTemp] can use an empty string as the dir // and wind up in our dir. os.Setenv("TMPDIR", tmp) cleanup := func() { os.RemoveAll(tmp) } anyFail, err := do(os.Stdin, os.Stdout, cfg) cleanup() if err != nil { fmt.Fprintf(os.Stderr, "%+v", err) os.Exit(1) } if anyFail { // In case pipefail is not enabled, make sure we exit non-zero os.Exit(2) } } func do(in io.Reader, out io.Writer, cfg config) (bool, error) { dec := json.NewDecoder(in) results := &resultsHandler{} defer results.Cleanup() defer func() { var wg waitGroup wg.Go(func() { var rf ResultsFormatter rf = &consoleFormatter{modName: cfg.modName, verbose: cfg.verbose} if err := rf.FormatResults(results.Results(), out); err != nil { slog.Error("Error writing annotations", "error", err) } rf = &errorAnnotationFormatter{} if err := rf.FormatResults(results.Results(), out); err != nil { slog.Error("Error writing error annotations", "error", err) } }) wg.Go(func() { summary := getSummaryFile() formatter := &summaryFormatter{slowThreshold: cfg.slowThreshold} if err := formatter.FormatResults(results.Results(), getSummaryFile()); err != nil { slog.Error("Error writing summary", "error", err) } summary.Close() }) wg.Wait() if cfg.logDir != "" { results.WriteLogs(cfg.logDir) } }() var anyFailed checkFailed handlers := []EventHandler{ results, &anyFailed, } if cfg.stream { handlers = append(handlers, &outputStreamer{out: out}) } te := &TestEvent{} for { *te = TestEvent{} // Reset the event struct to avoid reusing old data err := dec.Decode(te) if err != nil { if errors.Is(err, io.EOF) { break } return false, errors.WithStack(err) } for _, h := range handlers { if err := h.HandleEvent(te); err != nil { slog.Error("Error handling event", "error", err) } } } return bool(anyFailed), nil } type waitGroup struct { sync.WaitGroup } func (wg *waitGroup) Go(f func()) { wg.Add(1) go func() { f() wg.Done() }() }