Introduce infrastructure for calling and testing nested
commands, error messages and exit codes.

Also:
- implements the -Xclang-path= flag as use case of calling
  a nested command.
- adds tests for forwarding errors, comparing against the
  old wrapper, and exit codes.
- captures the source locations of errors in error messages.
- compares exit codes of new wrapper and old wrapper.

BUG=chromium:773875
TEST=unit test

Change-Id: I919e58091d093d68939809f676f799a68ec7a34e
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/toolchain-utils/+/1676833
Reviewed-by: George Burgess <gbiv@chromium.org>
Tested-by: Tobias Bosch <tbosch@google.com>
diff --git a/compiler_wrapper/errors.go b/compiler_wrapper/errors.go
new file mode 100644
index 0000000..525b986
--- /dev/null
+++ b/compiler_wrapper/errors.go
@@ -0,0 +1,57 @@
+package main
+
+import (
+	"fmt"
+	"os/exec"
+	"runtime"
+	"strings"
+	"syscall"
+)
+
+type userError struct {
+	err string
+}
+
+var _ error = userError{}
+
+func (err userError) Error() string {
+	return err.err
+}
+
+func newUserErrorf(format string, v ...interface{}) userError {
+	return userError{err: fmt.Sprintf(format, v...)}
+}
+
+func newErrorwithSourceLocf(format string, v ...interface{}) error {
+	return newErrorwithSourceLocfInternal(2, format, v...)
+}
+
+func wrapErrorwithSourceLocf(err error, format string, v ...interface{}) error {
+	return newErrorwithSourceLocfInternal(2, "%s: %s", fmt.Sprintf(format, v...), err.Error())
+}
+
+// Based on the implementation of log.Output
+func newErrorwithSourceLocfInternal(skip int, format string, v ...interface{}) error {
+	_, file, line, ok := runtime.Caller(skip)
+	if !ok {
+		file = "???"
+		line = 0
+	}
+	if lastSlash := strings.LastIndex(file, "/"); lastSlash >= 0 {
+		file = file[lastSlash+1:]
+	}
+
+	return fmt.Errorf("%s:%d: %s", file, line, fmt.Sprintf(format, v...))
+}
+
+func getExitCode(err error) (exitCode int, ok bool) {
+	if err == nil {
+		return 0, true
+	}
+	if exiterr, ok := err.(*exec.ExitError); ok {
+		if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
+			return status.ExitStatus(), true
+		}
+	}
+	return 0, false
+}