[reporting] Avoid output interleaving
Fixes a bug where a multi-line chunk of stdout from a check could have
output from other checks interleaved within it.
Change-Id: I84665689614c847547226bda5b21578e22a96ba6
Reviewed-on: https://fuchsia-review.googlesource.com/c/shac-project/shac/+/921694
Commit-Queue: Auto-Submit <auto-submit@fuchsia-infra.iam.gserviceaccount.com>
Reviewed-by: Marc-Antoine Ruel <maruel@google.com>
Fuchsia-Auto-Submit: Oliver Newman <olivernewman@google.com>
diff --git a/internal/reporting/reporting.go b/internal/reporting/reporting.go
index 827477b..5dd980c 100644
--- a/internal/reporting/reporting.go
+++ b/internal/reporting/reporting.go
@@ -22,6 +22,7 @@
"io"
"os"
"path/filepath"
+ "sync"
"time"
"github.com/mattn/go-colorable"
@@ -56,24 +57,64 @@
switch {
case os.Getenv("GITHUB_RUN_ID") != "":
// On GitHub Actions. Emits GitHub Workflows commands.
- r.Reporters = append(r.Reporters, &github{out: os.Stdout})
+ r.Reporters = append(r.Reporters, &synchronized{r: &github{out: os.Stdout}})
case os.Getenv("TERM") != "dumb" && isatty.IsTerminal(os.Stderr.Fd()):
// Active terminal. Colors! This includes VSCode's integrated terminal.
- r.Reporters = append(r.Reporters, &interactive{
+ r.Reporters = append(r.Reporters, &synchronized{r: &interactive{
out: colorable.NewColorableStdout(),
- })
+ }})
case os.Getenv("VSCODE_GIT_IPC_HANDLE") != "":
// VSCode extension.
// TODO(maruel): Return SARIF.
- r.Reporters = append(r.Reporters, &basic{out: os.Stdout})
+ r.Reporters = append(r.Reporters, &synchronized{r: &basic{out: os.Stdout}})
default:
// Anything else, e.g. redirected output.
- r.Reporters = append(r.Reporters, &basic{out: os.Stdout})
+ r.Reporters = append(r.Reporters, &synchronized{r: &basic{out: os.Stdout}})
}
return r, nil
}
+// synchronized wraps a Report object and adds synchronization of calls to
+// ensure that checks cannot emit potentially multi-line data simultaneously.
+// For example, we don't want two checks to simultaneously emit multi-line
+// chunks of output to the command line and have those chunks of output be
+// interleaved.
+//
+// It should be used to wrap any reporter that writes to stdout.
+type synchronized struct {
+ r Report
+ mu sync.Mutex
+}
+
+func (s *synchronized) Close() error {
+ return s.r.Close()
+}
+
+func (s *synchronized) EmitFinding(ctx context.Context, check string, level engine.Level, message, root, file string, span engine.Span, replacements []string) error {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ return s.r.EmitFinding(ctx, check, level, message, root, file, span, replacements)
+}
+
+func (s *synchronized) EmitArtifact(ctx context.Context, check, root, file string, content []byte) error {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ return s.r.EmitArtifact(ctx, check, root, file, content)
+}
+
+func (s *synchronized) CheckCompleted(ctx context.Context, check string, start time.Time, d time.Duration, level engine.Level, err error) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ s.r.CheckCompleted(ctx, check, start, d, level, err)
+}
+
+func (s *synchronized) Print(ctx context.Context, check, file string, line int, message string) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ s.r.Print(ctx, check, file, line, message)
+}
+
type basic struct {
out io.Writer
}