v.io/x/devtools/jiri-test: use runutil.Sequence.

MultiPart: 2/2

Change-Id: If26eb384d2a7294084215c19026c8ec406797e9f
diff --git a/runutil/sequence.go b/runutil/sequence.go
index 257c3fd..47fbf64 100644
--- a/runutil/sequence.go
+++ b/runutil/sequence.go
@@ -12,6 +12,7 @@
 	"os"
 	"path/filepath"
 	"runtime"
+	"sync"
 	"time"
 )
 
@@ -67,6 +68,7 @@
 	defaultStdin                 io.Reader
 	defaultStdout, defaultStderr io.Writer
 	dirs                         []string
+	verbosity                    *bool
 	timeout                      time.Duration
 }
 
@@ -117,6 +119,17 @@
 	return s
 }
 
+// Verbosity arranges for the next call to Run, Call or Last to use the specified
+// verbosity. This will be cleared and not used for any calls
+// to Run, Call or Last beyond the next one.
+func (s *Sequence) Verbose(verbosity bool) *Sequence {
+	if s.err != nil {
+		return s
+	}
+	s.verbosity = &verbosity
+	return s
+}
+
 // internal getOpts that doesn't override stdin, stdout, stderr
 func (s *Sequence) getOpts() Opts {
 	var opts Opts
@@ -143,16 +156,49 @@
 	s.opts = &opts
 }
 
+type wrappedError struct {
+	oe, we error
+}
+
+func (ie *wrappedError) Error() string {
+	return ie.we.Error()
+}
+
+// Error returns the error, if any, stored in the Sequence.
 func (s *Sequence) Error() error {
 	if s.err != nil && len(s.caller) > 0 {
-		// TODO(toddw): Wrapping the error here is bad, since some callers require
-		// the original error to be returned.  E.g. it's common to call os.Stat()
-		// and check against os.IsNotExist, which breaks with wrapped errors.
-		return fmt.Errorf("%s: %v", s.caller, s.err)
+		return &wrappedError{oe: s.err, we: fmt.Errorf("%s: %v", s.caller, s.err)}
 	}
 	return s.err
 }
 
+// IsExist returns a boolean indicating whether the error is known
+// to report that a file or directory already exists.
+func IsExist(err error) bool {
+	if we, ok := err.(*wrappedError); ok {
+		return os.IsExist(we.oe)
+	}
+	return os.IsExist(err)
+}
+
+// IsNotExist returns a boolean indicating whether the error is known
+// to report that a file or directory does not exist.
+func IsNotExist(err error) bool {
+	if we, ok := err.(*wrappedError); ok {
+		return os.IsNotExist(we.oe)
+	}
+	return os.IsNotExist(err)
+}
+
+// IsPermission returns a boolean indicating whether the error is known
+// to report that permission is denied.
+func IsPermission(err error) bool {
+	if we, ok := err.(*wrappedError); ok {
+		return os.IsPermission(we.oe)
+	}
+	return os.IsPermission(err)
+}
+
 func fmtError(depth int, err error, detail string) string {
 	_, file, line, _ := runtime.Caller(depth + 1)
 	return fmt.Sprintf("%s:%d: %s", filepath.Base(file), line, detail)
@@ -167,7 +213,8 @@
 }
 
 func (s *Sequence) reset() {
-	s.stdin, s.stdout, s.stderr, s.env, s.opts = nil, nil, nil, nil, nil
+	s.stdin, s.stdout, s.stderr, s.env = nil, nil, nil, nil
+	s.opts, s.verbosity = nil, nil
 	s.reading = false
 	s.timeout = 0
 }
@@ -206,6 +253,17 @@
 	}
 }
 
+type lockedWriter struct {
+	sync.Mutex
+	f io.Writer
+}
+
+func (lw *lockedWriter) Write(d []byte) (int, error) {
+	lw.Lock()
+	defer lw.Unlock()
+	return lw.f.Write(d)
+}
+
 func (s *Sequence) initAndDefer() func() {
 	if s.stdout == nil && s.stderr == nil {
 		fout, err := ioutil.TempFile("", "seq")
@@ -219,6 +277,9 @@
 		if s.reading {
 			opts.Stdin = s.stdin
 		}
+		if s.verbosity != nil {
+			opts.Verbose = *s.verbosity
+		}
 		s.setOpts(opts)
 		return func() {
 			filename := fout.Name()
@@ -233,30 +294,39 @@
 		}
 	}
 	opts := s.getOpts()
-	rStdin, wStdin := io.Pipe()
+	rStdout, wStdout := io.Pipe()
 	rStderr, wStderr := io.Pipe()
-	opts.Stdout = wStdin
+	opts.Stdout = wStdout
 	opts.Stderr = wStderr
 	opts.Env = s.env
 	if s.reading {
 		opts.Stdin = s.stdin
 	}
 	var stdinCh, stderrCh chan error
-	if s.stdout != nil {
+	stdout := s.stdout
+	stderr := s.stderr
+	if stdout != nil {
+		if stdout == stderr {
+			stdout = &lockedWriter{f: stdout}
+			stderr = &lockedWriter{f: stderr}
+		}
 		stdinCh = make(chan error)
-		go copy(s.stdout, rStdin, stdinCh)
+		go copy(stdout, rStdout, stdinCh)
 	} else {
 		opts.Stdout = s.defaultStdout
 	}
-	if s.stderr != nil {
+	if stderr != nil {
 		stderrCh = make(chan error)
-		go copy(s.stderr, rStderr, stderrCh)
+		go copy(stderr, rStderr, stderrCh)
 	} else {
 		opts.Stderr = s.defaultStderr
 	}
+	if s.verbosity != nil {
+		opts.Verbose = *s.verbosity
+	}
 	s.setOpts(opts)
 	return func() {
-		if err := s.done(wStdin, wStderr, stdinCh, stderrCh); err != nil && s.err == nil {
+		if err := s.done(wStdout, wStderr, stdinCh, stderrCh); err != nil && s.err == nil {
 			s.err = err
 		}
 	}
@@ -435,6 +505,15 @@
 	return s
 }
 
+// Fprintf calls fmt.Fprintf.
+func (s *Sequence) Fprintf(f io.Writer, format string, args ...interface{}) *Sequence {
+	if s.err != nil {
+		return s
+	}
+	fmt.Fprintf(f, format, args...)
+	return s
+}
+
 // Done returns the error stored in the Sequence and pops back to the first
 // entry in the directory stack if Pushd has been called. Done is a terminating
 // function. There is no need to ensure that Done is called before returning