Repository: googleapis/gapic-generator-go
Pull request #27 (2018-10-05 21:04:21)

test: properly diff the text
This makes reading test failures a lot easier.


Patch:
diff --git a/internal/txtdiff/diff.go b/internal/txtdiff/diff.go
new file mode 100644
index 0000000..9d1069e
--- /dev/null
+++ b/internal/txtdiff/diff.go
@@ -0,0 +1,112 @@
+// Copyright 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package txtdiff provides text-related test helpers.
+package txtdiff
+
+import (
+	"flag"
+	"io/ioutil"
+	"strings"
+	"testing"
+)
+
+var updateGolden = flag.Bool("update_golden", false, "update golden files")
+
+func Diff(t *testing.T, name, got, goldenFile string) {
+	t.Helper()
+
+	if *updateGolden {
+		if err := ioutil.WriteFile(goldenFile, []byte(got), 0644); err != nil {
+			t.Fatal(err)
+		}
+		return
+	}
+
+	wantBytes, err := ioutil.ReadFile(goldenFile)
+	if err != nil {
+		t.Fatal(err)
+	}
+	want := string(wantBytes)
+
+	if got == want {
+		return
+	}
+
+	gotLines := strings.Split(got, "\n")
+	wantLines := strings.Split(want, "\n")
+	t.Errorf("%s: (-got,+want)\n%s", name, lcsDiff(gotLines, wantLines))
+}
+
+func lcsDiff(aLines, bLines []string) string {
+	// Algorithm is described by https://en.wikipedia.org/wiki/Longest_common_subsequence_problem.
+
+	// We require O(n^2) space to memoize LCS. This is not great, however
+	// imagine we have 10,000-line baseline; 1e4^2 = 1e8 ints ~= 1e9 bytes = 1GB.
+	// Most development computers have more memory than this and
+	// our baselines are orders of magnitude smaller; we should we fine.
+
+	// The article uses 1-based index and use index 0 to refer to the conceptual empty element.
+	// Instead of dancing around the index, we just create the empty element.
+	aLines = append([]string{""}, aLines...)
+	bLines = append([]string{""}, bLines...)
+
+	c := make([][]int, len(aLines))
+	for i := range c {
+		c[i] = make([]int, len(bLines))
+	}
+	for i := 1; i < len(aLines); i++ {
+		for j := 1; j < len(bLines); j++ {
+			if aLines[i] == bLines[j] {
+				c[i][j] = c[i-1][j-1] + 1
+			} else if c[i][j-1] < c[i-1][j] {
+				c[i][j] = c[i-1][j]
+			} else {
+				c[i][j] = c[i][j-1]
+			}
+		}
+	}
+
+	// The article uses recursion. I think iteration is more clear.
+	var diff []string
+	var sign []byte
+
+	i := len(aLines) - 1
+	j := len(bLines) - 1
+	for i > 0 || j > 0 {
+		if i > 0 && j > 0 && aLines[i] == bLines[j] {
+			diff = append(diff, aLines[i])
+			sign = append(sign, ' ')
+			i--
+			j--
+		} else if j > 0 && (i == 0 || c[i][j-1] >= c[i-1][j]) {
+			diff = append(diff, bLines[j])
+			sign = append(sign, '+')
+			j--
+		} else if i > 0 && (j == 0 || c[i][j-1] < c[i-1][j]) {
+			diff = append(diff, aLines[i])
+			sign = append(sign, '-')
+			i--
+		}
+	}
+
+	var sb strings.Builder
+	for i := len(diff) - 1; i >= 0; i-- {
+		sb.WriteByte(sign[i])
+		sb.WriteByte(' ')
+		sb.WriteString(diff[i])
+		sb.WriteByte('\n')
+	}
+	return sb.String()
+}


Test patch:
diff --git a/cmd/gen-go-sample/main_test.go b/cmd/gen-go-sample/main_test.go
index 1784fbe..476892b 100644
--- a/cmd/gen-go-sample/main_test.go
+++ b/cmd/gen-go-sample/main_test.go
@@ -15,37 +15,16 @@
 package main
 
 import (
-	"flag"
-	"io/ioutil"
 	"path/filepath"
 	"strings"
 	"testing"
 
 	"github.com/golang/protobuf/proto"
 	"github.com/golang/protobuf/protoc-gen-go/descriptor"
-	"github.com/google/go-cmp/cmp"
 	"github.com/googleapis/gapic-generator-go/internal/pbinfo"
+	"github.com/googleapis/gapic-generator-go/internal/txtdiff"
 )
 
-var updateGolden = flag.Bool("update_golden", false, "update golden files")
-
-func diff(t *testing.T, name, got, goldenFile string) {
-	t.Helper()
-
-	if *updateGolden {
-		if err := ioutil.WriteFile(goldenFile, []byte(got), 0644); err != nil {
-			t.Fatal(err)
-		}
-	}
-	want, err := ioutil.ReadFile(goldenFile)
-	if err != nil {
-		t.Fatal(err)
-	}
-	if diff := cmp.Diff(got, string(want)); diff != "" {
-		t.Errorf("%s: (-got,+want)\n%s", name, diff)
-	}
-}
-
 func TestSample(t *testing.T) {
 	inType := &descriptor.DescriptorProto{
 		Name: proto.String("InputType"),
@@ -140,5 +119,5 @@ func TestSample(t *testing.T) {
 	if err := g.commit(gofmt, year, &sb); err != nil {
 		t.Fatal(err)
 	}
-	diff(t, "TestSample", sb.String(), filepath.Join("testdata", "sample.want"))
+	txtdiff.Diff(t, "TestSample", sb.String(), filepath.Join("testdata", "sample.want"))
 }
diff --git a/internal/gengapic/client_init_test.go b/internal/gengapic/client_init_test.go
index 3ef79cf..a4d82e5 100644
--- a/internal/gengapic/client_init_test.go
+++ b/internal/gengapic/client_init_test.go
@@ -15,38 +15,17 @@
 package gengapic
 
 import (
-	"flag"
-	"io/ioutil"
 	"path/filepath"
 	"testing"
 
 	"github.com/golang/protobuf/proto"
 	"github.com/golang/protobuf/protoc-gen-go/descriptor"
-	"github.com/google/go-cmp/cmp"
 	"github.com/googleapis/gapic-generator-go/internal/pbinfo"
+	"github.com/googleapis/gapic-generator-go/internal/txtdiff"
 	"google.golang.org/genproto/googleapis/api/annotations"
 	"google.golang.org/genproto/googleapis/rpc/code"
 )
 
-var updateGolden = flag.Bool("update_golden", false, "update golden files")
-
-func diff(t *testing.T, name, got, goldenFile string) {
-	t.Helper()
-
-	if *updateGolden {
-		if err := ioutil.WriteFile(goldenFile, []byte(got), 0644); err != nil {
-			t.Fatal(err)
-		}
-	}
-	want, err := ioutil.ReadFile(goldenFile)
-	if err != nil {
-		t.Fatal(err)
-	}
-	if diff := cmp.Diff(got, string(want)); diff != "" {
-		t.Errorf("%s: (-got,+want)\n%s", name, diff)
-	}
-}
-
 func TestClientOpt(t *testing.T) {
 	var g generator
 	g.imports = map[pbinfo.ImportSpec]bool{}
@@ -88,7 +67,7 @@ func TestClientOpt(t *testing.T) {
 			t.Error(err)
 			continue
 		}
-		diff(t, tst.tstName, g.pt.String(), filepath.Join("testdata", tst.tstName+".want"))
+		txtdiff.Diff(t, tst.tstName, g.pt.String(), filepath.Join("testdata", tst.tstName+".want"))
 	}
 }
 
@@ -133,6 +112,6 @@ func TestClientInit(t *testing.T) {
 
 		g.reset()
 		g.clientInit(tst.serv, tst.servName)
-		diff(t, tst.tstName, g.pt.String(), filepath.Join("testdata", tst.tstName+".want"))
+		txtdiff.Diff(t, tst.tstName, g.pt.String(), filepath.Join("testdata", tst.tstName+".want"))
 	}
 }
diff --git a/internal/gengapic/doc_file_test.go b/internal/gengapic/doc_file_test.go
index 2103982..8ed81aa 100644
--- a/internal/gengapic/doc_file_test.go
+++ b/internal/gengapic/doc_file_test.go
@@ -17,11 +17,13 @@ package gengapic
 import (
 	"path/filepath"
 	"testing"
+
+	"github.com/googleapis/gapic-generator-go/internal/txtdiff"
 )
 
 func TestDocFile(t *testing.T) {
 	var g generator
 	g.apiName = "Awesome Foo"
 	g.genDocFile("path/to/awesome", "awesome", 42, []string{"https://foo.bar.com/auth", "https://zip.zap.com/auth"})
-	diff(t, "doc_file", g.pt.String(), filepath.Join("testdata", "doc_file.want"))
+	txtdiff.Diff(t, "doc_file", g.pt.String(), filepath.Join("testdata", "doc_file.want"))
 }
diff --git a/internal/gengapic/example_test.go b/internal/gengapic/example_test.go
index 5dda6ea..b1c95c3 100644
--- a/internal/gengapic/example_test.go
+++ b/internal/gengapic/example_test.go
@@ -21,6 +21,7 @@ import (
 	"github.com/golang/protobuf/proto"
 	"github.com/golang/protobuf/protoc-gen-go/descriptor"
 	"github.com/googleapis/gapic-generator-go/internal/pbinfo"
+	"github.com/googleapis/gapic-generator-go/internal/txtdiff"
 )
 
 func TestExample(t *testing.T) {
@@ -126,7 +127,7 @@ func TestExample(t *testing.T) {
 	} {
 		g.reset()
 		g.genExampleFile(serv, tst.pkgName)
-		diff(t, tst.tstName, g.pt.String(), filepath.Join("testdata", tst.tstName+".want"))
+		txtdiff.Diff(t, tst.tstName, g.pt.String(), filepath.Join("testdata", tst.tstName+".want"))
 	}
 }
 
diff --git a/internal/gengapic/gengapic_test.go b/internal/gengapic/gengapic_test.go
index 40e5b17..ffe3c7e 100644
--- a/internal/gengapic/gengapic_test.go
+++ b/internal/gengapic/gengapic_test.go
@@ -21,6 +21,7 @@ import (
 	"github.com/golang/protobuf/proto"
 	"github.com/golang/protobuf/protoc-gen-go/descriptor"
 	"github.com/googleapis/gapic-generator-go/internal/pbinfo"
+	"github.com/googleapis/gapic-generator-go/internal/txtdiff"
 	"google.golang.org/genproto/googleapis/api/annotations"
 )
 
@@ -256,6 +257,6 @@ methods:
 			g.pagingIter(iter)
 		}
 
-		diff(t, m.GetName(), g.pt.String(), filepath.Join("testdata", "method_"+m.GetName()+".want"))
+		txtdiff.Diff(t, m.GetName(), g.pt.String(), filepath.Join("testdata", "method_"+m.GetName()+".want"))
 	}
 }