Refactor ProgressBar for easier testing

Refactors ProgressBar to be an interface with two implementations,
realProgressBar (the existing impl) & StubProgressBar (no-op for
unsupported terminals and testing.

The terminal character check is moved into the constructor to allow
selecting whether to return a realProgressBar or a
StubProgressBar. Callers no longer need to perform the check
themselves, and are guaranteed to get a ProgressBar back, so can avoid
later nil checks.

This change bypasses the need to write a dependency injection wrapper
for the `term` package for testing fuzz/main.go

Bug: 344014313
Change-Id: I8e4d5342ce4f42750b3179730a72ab6a4c7ace1d
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/258094
Commit-Queue: Ryan Harrison <rharrison@chromium.org>
Auto-Submit: Ryan Harrison <rharrison@chromium.org>
Commit-Queue: Brian Sheedy <bsheedy@google.com>
Reviewed-by: Brian Sheedy <bsheedy@google.com>
diff --git a/tools/src/cmd/fuzz/main.go b/tools/src/cmd/fuzz/main.go
index c758833..6e2e2ef 100644
--- a/tools/src/cmd/fuzz/main.go
+++ b/tools/src/cmd/fuzz/main.go
@@ -45,7 +45,6 @@
 	"dawn.googlesource.com/dawn/tools/src/glob"
 	"dawn.googlesource.com/dawn/tools/src/oswrapper"
 	"dawn.googlesource.com/dawn/tools/src/progressbar"
-	"dawn.googlesource.com/dawn/tools/src/term"
 	"dawn.googlesource.com/dawn/tools/src/transform"
 	"dawn.googlesource.com/dawn/tools/src/utils"
 )
@@ -73,17 +72,18 @@
 )
 
 type cmdConfig struct {
-	verbose      bool
-	dump         bool
-	fuzzMode     FuzzMode
-	cmdMode      TaskMode // meta-task being requested by the user, may require running multiple tasks internally
-	filter       string
-	inputs       string
-	build        string
-	out          string
-	numProcesses int
-	osWrapper    oswrapper.OSWrapper
-	execWrapper  execwrapper.ExecWrapper
+	verbose         bool
+	dump            bool
+	fuzzMode        FuzzMode
+	cmdMode         TaskMode // meta-task being requested by the user, may require running multiple tasks internally
+	filter          string
+	inputs          string
+	build           string
+	out             string
+	numProcesses    int
+	osWrapper       oswrapper.OSWrapper
+	execWrapper     execwrapper.ExecWrapper
+	progressBuilder progressbar.Build
 }
 
 func showUsage() {
@@ -106,6 +106,7 @@
 	c := cmdConfig{}
 	c.osWrapper = oswrapper.GetRealOSWrapper()
 	c.execWrapper = execwrapper.CreateRealExecWrapper()
+	c.progressBuilder = progressbar.New
 
 	flag.Usage = showUsage
 
@@ -308,24 +309,19 @@
 
 	remaining := transform.SliceToChan(files)
 
-	var pb *progressbar.ProgressBar
-	if term.CanUseAnsiEscapeSequences() {
-		pb = progressbar.New(os.Stdout, nil)
-		defer pb.Stop()
-	}
+	pb := t.progressBuilder(os.Stdout, nil)
+	defer pb.Stop()
 	var numDone uint32
 
 	routine := func() error {
 		for file := range remaining {
 			atomic.AddUint32(&numDone, 1)
-			if pb != nil {
-				pb.Update(progressbar.Status{
-					Total: len(files),
-					Segments: []progressbar.Segment{
-						{Count: int(atomic.LoadUint32(&numDone))},
-					},
-				})
-			}
+			pb.Update(progressbar.Status{
+				Total: len(files),
+				Segments: []progressbar.Segment{
+					{Count: int(atomic.LoadUint32(&numDone))},
+				},
+			})
 
 			if out, err := t.execWrapper.Command(t.fuzzer, file).RunWithCombinedOutput(); err != nil {
 				_, fuzzer := filepath.Split(t.fuzzer)
diff --git a/tools/src/progressbar/progressbar.go b/tools/src/progressbar/progressbar.go
index cbd0c21..4e21aa3 100644
--- a/tools/src/progressbar/progressbar.go
+++ b/tools/src/progressbar/progressbar.go
@@ -25,7 +25,7 @@
 // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-// Package progressbar provides functions for drawing unicode progress bars to
+// Package progressbar provides functions for drawing Unicode progress bars to
 // the terminal
 package progressbar
 
@@ -36,6 +36,8 @@
 	"math"
 	"strings"
 	"time"
+
+	"dawn.googlesource.com/dawn/tools/src/term"
 )
 
 // Defaults for the Config
@@ -80,19 +82,34 @@
 	Segments []Segment
 }
 
-// ProgressBar returns a string with an ANSI-colored progress bar, providing
-// realtime information about the status of the CTS run.
-// Note: We'll want to skip this if !isatty or if we're running on windows.
-type ProgressBar struct {
-	Config
-	out io.Writer
-	c   chan Status
+// ProgressBar is an interface for progress bars.
+type ProgressBar interface {
+	// Update updates the ProgressBar with the given status
+	Update(s Status)
+	// Stop stops drawing the progress bar.
+	// Once called, the ProgressBar must not be used.
+	Stop()
 }
 
+type Build func(out io.Writer, cfg *Config) ProgressBar
+
 // New returns a new ProgressBar that streams output to out.
+// Selects between realProgressBar and StubProgressBar automatically depending
+// on if the output will be displayed in the terminal correctly.
 // Call ProgressBar.Stop() once finished.
-func New(out io.Writer, cfg *Config) *ProgressBar {
-	p := &ProgressBar{out: out, c: make(chan Status, 64)}
+func New(out io.Writer, cfg *Config) ProgressBar {
+	if !term.CanUseAnsiEscapeSequences() {
+		return NewStubProgressBar(out, cfg)
+	}
+
+	return newRealProgressBar(out, cfg)
+}
+
+// NewRealProgressBar constructs and returns a realProgressBar.
+// Does not check if the terminal supports the needed escape characters, which
+// is why it is not exposed outside the package, use New() instead.
+func newRealProgressBar(out io.Writer, cfg *Config) ProgressBar {
+	p := &realProgressBar{out: out, c: make(chan Status, 64)}
 	if cfg != nil {
 		p.Config = *cfg
 	} else {
@@ -125,14 +142,36 @@
 	return p
 }
 
-// Update updates the ProgressBar with the given status
-func (p *ProgressBar) Update(s Status) {
+// NewStubProgressBar returns a new StubProgressBar, used in tests to guarantee
+// the type of ProgressBar returned.
+// Note: In production code, use New() instead.
+func NewStubProgressBar(_ io.Writer, _ *Config) ProgressBar {
+	return &StubProgressBar{}
+}
+
+// StubProgressBar is a ProgressBar that does nothing, used in environments that
+// don't support the control characters needed or when testing to avoid spamming
+// output.
+type StubProgressBar struct{}
+
+func (p *StubProgressBar) Update(s Status) {}
+
+func (p *StubProgressBar) Stop() {}
+
+// realProgressBar writes out a string with an ANSI-colored progress bar,
+// providing realtime information about the status of the CTS run.
+// Note: Does not work if !isatty or if running on windows.
+type realProgressBar struct {
+	Config
+	out io.Writer
+	c   chan Status
+}
+
+func (p *realProgressBar) Update(s Status) {
 	p.c <- s
 }
 
-// Stop stops drawing the progress bar.
-// Once called, the ProgressBar must not be used.
-func (p *ProgressBar) Stop() {
+func (p *realProgressBar) Stop() {
 	close(p.c)
 }