all: use Windows-supporting diff function

With this CL, the perf repo should now pass all tests on Windows.

Change-Id: I91fead1e065f22b54236ff96c6ace529dc3964a8
Reviewed-on: https://go-review.googlesource.com/36627
Run-TryBot: Quentin Smith <quentin@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
diff --git a/internal/diff/diff.go b/internal/diff/diff.go
new file mode 100644
index 0000000..09d3047
--- /dev/null
+++ b/internal/diff/diff.go
@@ -0,0 +1,58 @@
+// Copyright 2017 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package diff
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"runtime"
+)
+
+// Diff returns a human-readable description of the differences between s1 and s2.
+// If the "diff" command is available, it returns the output of unified diff on s1 and s2.
+// If the result is non-empty, the strings differ or the diff command failed.
+func Diff(s1, s2 string) string {
+	if s1 == s2 {
+		return ""
+	}
+	if _, err := exec.LookPath("diff"); err != nil {
+		return fmt.Sprintf("diff command unavailable\nold: %q\nnew: %q", s1, s2)
+	}
+	f1, err := ioutil.TempFile("", "benchfmt_test")
+	if err != nil {
+		return err.Error()
+	}
+	defer os.Remove(f1.Name())
+	defer f1.Close()
+
+	f2, err := ioutil.TempFile("", "benchfmt_test")
+	if err != nil {
+		return err.Error()
+	}
+	defer os.Remove(f2.Name())
+	defer f2.Close()
+
+	f1.Write([]byte(s1))
+	f2.Write([]byte(s2))
+
+	cmd := "diff"
+	if runtime.GOOS == "plan9" {
+		cmd = "/bin/ape/diff"
+	}
+
+	data, err := exec.Command(cmd, "-u", f1.Name(), f2.Name()).CombinedOutput()
+	if len(data) > 0 {
+		// diff exits with a non-zero status when the files don't match.
+		// Ignore that failure as long as we get output.
+		err = nil
+	}
+	if err != nil {
+		data = append(data, []byte(err.Error())...)
+	}
+	return string(data)
+
+}
diff --git a/storage/benchfmt/benchfmt_test.go b/storage/benchfmt/benchfmt_test.go
index 17c9340..4aa4ecf 100644
--- a/storage/benchfmt/benchfmt_test.go
+++ b/storage/benchfmt/benchfmt_test.go
@@ -7,12 +7,11 @@
 import (
 	"bytes"
 	"fmt"
-	"io/ioutil"
-	"os"
-	"os/exec"
 	"reflect"
 	"strings"
 	"testing"
+
+	"golang.org/x/perf/internal/diff"
 )
 
 func readAllResults(t *testing.T, r *Reader) []*Result {
@@ -176,42 +175,9 @@
 					t.Errorf("Print returned %v", err)
 				}
 			}
-			if diff := diff(have.String(), test.want); diff != "" {
+			if diff := diff.Diff(have.String(), test.want); diff != "" {
 				t.Errorf("wrong output: (- got/+ want)\n%s", diff)
 			}
 		})
 	}
 }
-
-// diff returns the output of unified diff on s1 and s2. If the result
-// is non-empty, the strings differ or the diff command failed.
-func diff(s1, s2 string) string {
-	f1, err := ioutil.TempFile("", "benchfmt_test")
-	if err != nil {
-		return err.Error()
-	}
-	defer os.Remove(f1.Name())
-	defer f1.Close()
-
-	f2, err := ioutil.TempFile("", "benchfmt_test")
-	if err != nil {
-		return err.Error()
-	}
-	defer os.Remove(f2.Name())
-	defer f2.Close()
-
-	f1.Write([]byte(s1))
-	f2.Write([]byte(s2))
-
-	data, err := exec.Command("diff", "-u", f1.Name(), f2.Name()).CombinedOutput()
-	if len(data) > 0 {
-		// diff exits with a non-zero status when the files don't match.
-		// Ignore that failure as long as we get output.
-		err = nil
-	}
-	if err != nil {
-		data = append(data, []byte(err.Error())...)
-	}
-	return string(data)
-
-}
diff --git a/storage/client_test.go b/storage/client_test.go
index 4e2d6dc..230d800 100644
--- a/storage/client_test.go
+++ b/storage/client_test.go
@@ -7,14 +7,12 @@
 import (
 	"bytes"
 	"fmt"
-	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
-	"os"
-	"os/exec"
 	"reflect"
 	"testing"
 
+	"golang.org/x/perf/internal/diff"
 	"golang.org/x/perf/storage/benchfmt"
 )
 
@@ -63,7 +61,7 @@
 		t.Fatalf("Err: %v", err)
 	}
 	want := "key: value\nBenchmarkOne 5 ns/op\nkey: value2\nBenchmarkTwo 10 ns/op\n"
-	if diff := diff(buf.String(), want); diff != "" {
+	if diff := diff.Diff(buf.String(), want); diff != "" {
 		t.Errorf("wrong results: (- have/+ want)\n%s", diff)
 	}
 }
@@ -92,36 +90,3 @@
 		t.Fatalf("Err: %v", err)
 	}
 }
-
-// diff returns the output of unified diff on s1 and s2. If the result
-// is non-empty, the strings differ or the diff command failed.
-func diff(s1, s2 string) string {
-	f1, err := ioutil.TempFile("", "benchfmt_test")
-	if err != nil {
-		return err.Error()
-	}
-	defer os.Remove(f1.Name())
-	defer f1.Close()
-
-	f2, err := ioutil.TempFile("", "benchfmt_test")
-	if err != nil {
-		return err.Error()
-	}
-	defer os.Remove(f2.Name())
-	defer f2.Close()
-
-	f1.Write([]byte(s1))
-	f2.Write([]byte(s2))
-
-	data, err := exec.Command("diff", "-u", f1.Name(), f2.Name()).CombinedOutput()
-	if len(data) > 0 {
-		// diff exits with a non-zero status when the files don't match.
-		// Ignore that failure as long as we get output.
-		err = nil
-	}
-	if err != nil {
-		data = append(data, []byte(err.Error())...)
-	}
-	return string(data)
-
-}
diff --git a/storage/db/db_test.go b/storage/db/db_test.go
index 48a257e..dc8b7e4 100644
--- a/storage/db/db_test.go
+++ b/storage/db/db_test.go
@@ -8,9 +8,6 @@
 	"bytes"
 	"context"
 	"fmt"
-	"io/ioutil"
-	"os"
-	"os/exec"
 	"reflect"
 	"sort"
 	"strconv"
@@ -18,6 +15,7 @@
 	"testing"
 	"time"
 
+	"golang.org/x/perf/internal/diff"
 	"golang.org/x/perf/storage/benchfmt"
 	. "golang.org/x/perf/storage/db"
 	"golang.org/x/perf/storage/db/dbtest"
@@ -84,7 +82,7 @@
 	if err := q.Err(); err != nil {
 		t.Fatalf("Err: %v", err)
 	}
-	if diff := diff(buf.String(), results); diff != "" {
+	if diff := diff.Diff(buf.String(), results); diff != "" {
 		t.Errorf("wrong results: (- have/+ want)\n%s", diff)
 	}
 }
@@ -329,39 +327,6 @@
 	}
 }
 
-// diff returns the output of unified diff on s1 and s2. If the result
-// is non-empty, the strings differ or the diff command failed.
-func diff(s1, s2 string) string {
-	f1, err := ioutil.TempFile("", "benchfmt_test")
-	if err != nil {
-		return err.Error()
-	}
-	defer os.Remove(f1.Name())
-	defer f1.Close()
-
-	f2, err := ioutil.TempFile("", "benchfmt_test")
-	if err != nil {
-		return err.Error()
-	}
-	defer os.Remove(f2.Name())
-	defer f2.Close()
-
-	f1.Write([]byte(s1))
-	f2.Write([]byte(s2))
-
-	data, err := exec.Command("diff", "-u", f1.Name(), f2.Name()).CombinedOutput()
-	if len(data) > 0 {
-		// diff exits with a non-zero status when the files don't match.
-		// Ignore that failure as long as we get output.
-		err = nil
-	}
-	if err != nil {
-		data = append(data, []byte(err.Error())...)
-	}
-	return string(data)
-
-}
-
 // TestListUploads verifies that ListUploads returns the correct values.
 func TestListUploads(t *testing.T) {
 	SetNow(time.Unix(0, 0))