From 640983c1250704dba0f5425a364db015cd9bc2b4 Mon Sep 17 00:00:00 2001 From: Sevki Date: Wed, 16 Aug 2017 22:54:50 +0200 Subject: [PATCH 1/4] use t.Run for tests using `t.Run` instead including the test name in error message is a more idiomatic way of testing things in go. Since this feature was added to go in 1.7 release and the travis config explicitly specifies 1.4 and 1.5 (2 and 2,5 year old releases) as test targets this change will break the CI builds, therefore this commit also proposes dropping those releases in favour of adding a newer version of go (1.8) as the test target. Signed-off-by: Sevki --- .travis.yml | 3 +-- main_test.go | 33 +++++++++++++++++---------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.travis.yml b/.travis.yml index f8de2ef..0802a6c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,7 @@ language: go sudo: false go: - - 1.4 - - 1.5 + - 1.8 - tip before_install: - go get github.com/axw/gocov/gocov diff --git a/main_test.go b/main_test.go index b1bf2bc..ba99cba 100644 --- a/main_test.go +++ b/main_test.go @@ -16,9 +16,7 @@ limitations under the License. package jsonnet -import ( - "testing" -) +import "testing" // Just a few simple sanity tests for now. Eventually we'll share end-to-end tests with the C++ // implementation but unsure if that should be done here or via some external framework. @@ -52,18 +50,21 @@ var mainTests = []mainTest{ func TestMain(t *testing.T) { for _, test := range mainTests { - vm := MakeVM() - output, err := vm.EvaluateSnippet(test.name, test.input) - var errString string - if err != nil { - errString = err.Error() - } - if errString != test.errString { - t.Errorf("%s: error result does not match. got\n\t%+v\nexpected\n\t%+v", - test.input, errString, test.errString) - } - if err == nil && output != test.golden { - t.Errorf("%s: got\n\t%+v\nexpected\n\t%+v", test.name, output, test.golden) - } + t.Run(test.name, func(t *testing.T) { + vm := MakeVM() + output, err := vm.EvaluateSnippet(test.name, test.input) + var errString string + if err != nil { + errString = err.Error() + } + + if errString != test.errString { + t.Errorf("%s: error result does not match. got\n\t%+v\nexpected\n\t%+v", + test.input, errString, test.errString) + } + if err == nil && output != test.golden { + t.Errorf("got\n\t%+v\nexpected\n\t%+v", output, test.golden) + } + }) } } From 4582b1c4aea7d7807fa7063b0f4d0577509e5e39 Mon Sep 17 00:00:00 2001 From: Sevki Date: Wed, 16 Aug 2017 23:07:31 +0200 Subject: [PATCH 2/4] remove errString from main_test.go since non of the test cases actually use the err string field and the golden file testing pattern does not really need it, this commit proposes the removal of the errString field from tests Signed-off-by: Sevki --- main_test.go | 49 ++++++++++++++++++++----------------------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/main_test.go b/main_test.go index ba99cba..fb3e0ef 100644 --- a/main_test.go +++ b/main_test.go @@ -22,30 +22,29 @@ import "testing" // implementation but unsure if that should be done here or via some external framework. type mainTest struct { - name string - input string - golden string - errString string + name string + input string + golden string } var mainTests = []mainTest{ - {"numeric_literal", "100", "100", ""}, - {"boolean_literal", "true", "true", ""}, - {"simple_arith1", "3 + 3", "6", ""}, - {"simple_arith2", "3 + 3 + 3", "9", ""}, - {"simple_arith3", "(3 + 3) + (3 + 3)", "12", ""}, - {"unicode", `"\u263A"`, `"☺"`, ""}, - {"unicode2", `"\u263a"`, `"☺"`, ""}, - {"escaped_single_quote", `"\\'"`, `"\\'"`, ""}, - {"simple_arith_string", "\"aaa\" + \"bbb\"", "\"aaabbb\"", ""}, - {"simple_arith_string2", "\"aaa\" + \"\"", "\"aaa\"", ""}, - {"simple_arith_string3", "\"\" + \"bbb\"", "\"bbb\"", ""}, - {"simple_arith_string_empty", "\"\" + \"\"", "\"\"", ""}, - {"verbatim_string", `@"blah ☺"`, `"blah ☺"`, ""}, - {"empty_array", "[]", "[ ]", ""}, - {"array", "[1, 2, 1 + 2]", "[\n 1,\n 2,\n 3\n]", ""}, - {"empty_object", "{}", "{ }", ""}, - {"object", `{"x": 1+1}`, "{\n \"x\": 2\n}", ""}, + {"numeric_literal", "100", "100"}, + {"boolean_literal", "true", "true"}, + {"simple_arith1", "3 + 3", "6"}, + {"simple_arith2", "3 + 3 + 3", "9"}, + {"simple_arith3", "(3 + 3) + (3 + 3)", "12"}, + {"unicode", `"\u263A"`, `"☺"`}, + {"unicode2", `"\u263a"`, `"☺"`}, + {"escaped_single_quote", `"\\'"`, `"\\'"`}, + {"simple_arith_string", "\"aaa\" + \"bbb\"", "\"aaabbb\""}, + {"simple_arith_string2", "\"aaa\" + \"\"", "\"aaa\""}, + {"simple_arith_string3", "\"\" + \"bbb\"", "\"bbb\""}, + {"simple_arith_string_empty", "\"\" + \"\"", "\"\""}, + {"verbatim_string", `@"blah ☺"`, `"blah ☺"`}, + {"empty_array", "[]", "[ ]"}, + {"array", "[1, 2, 1 + 2]", "[\n 1,\n 2,\n 3\n]"}, + {"empty_object", "{}", "{ }"}, + {"object", `{"x": 1+1}`, "{\n \"x\": 2\n}"}, } func TestMain(t *testing.T) { @@ -53,15 +52,7 @@ func TestMain(t *testing.T) { t.Run(test.name, func(t *testing.T) { vm := MakeVM() output, err := vm.EvaluateSnippet(test.name, test.input) - var errString string - if err != nil { - errString = err.Error() - } - if errString != test.errString { - t.Errorf("%s: error result does not match. got\n\t%+v\nexpected\n\t%+v", - test.input, errString, test.errString) - } if err == nil && output != test.golden { t.Errorf("got\n\t%+v\nexpected\n\t%+v", output, test.golden) } From f53577632f9d178e6bf7e0217792534ba0fd7d7a Mon Sep 17 00:00:00 2001 From: Sevki Date: Wed, 16 Aug 2017 23:59:55 +0200 Subject: [PATCH 3/4] refactor golden tests to look more like std library tests this commit proposes using a file based golden tests approach, parts of this refactor uses code and conventions from the go std library. At it's current state go-jsonnet does not support many of the examples on http://jsonnet.org/docs/demo.html, motive behind the refactor is to add the demo examples to the test suite and the demo examples are too big to be inlined. Signed-off-by: Sevki --- main_test.go | 112 +++++++++++++++++----- testdata/array.golden | 5 + testdata/array.input | 1 + testdata/boolean_literal.golden | 1 + testdata/boolean_literal.input | 1 + testdata/empty_array.golden | 1 + testdata/empty_array.input | 1 + testdata/empty_object.golden | 1 + testdata/empty_object.input | 1 + testdata/escaped_single_quote.golden | 1 + testdata/escaped_single_quote.input | 1 + testdata/numeric_literal.golden | 1 + testdata/numeric_literal.input | 1 + testdata/object.golden | 3 + testdata/object.input | 1 + testdata/simple_arith1.golden | 1 + testdata/simple_arith1.input | 1 + testdata/simple_arith2.golden | 1 + testdata/simple_arith2.input | 1 + testdata/simple_arith3.golden | 1 + testdata/simple_arith3.input | 1 + testdata/simple_arith_string.golden | 1 + testdata/simple_arith_string.input | 1 + testdata/simple_arith_string2.golden | 1 + testdata/simple_arith_string2.input | 1 + testdata/simple_arith_string3.golden | 1 + testdata/simple_arith_string3.input | 1 + testdata/simple_arith_string_empty.golden | 1 + testdata/simple_arith_string_empty.input | 1 + testdata/unicode.golden | 1 + testdata/unicode.input | 1 + testdata/unicode2.golden | 1 + testdata/unicode2.input | 1 + testdata/verbatim_string.golden | 1 + testdata/verbatim_string.input | 1 + 35 files changed, 128 insertions(+), 24 deletions(-) create mode 100644 testdata/array.golden create mode 100644 testdata/array.input create mode 100644 testdata/boolean_literal.golden create mode 100644 testdata/boolean_literal.input create mode 100644 testdata/empty_array.golden create mode 100644 testdata/empty_array.input create mode 100644 testdata/empty_object.golden create mode 100644 testdata/empty_object.input create mode 100644 testdata/escaped_single_quote.golden create mode 100644 testdata/escaped_single_quote.input create mode 100644 testdata/numeric_literal.golden create mode 100644 testdata/numeric_literal.input create mode 100644 testdata/object.golden create mode 100644 testdata/object.input create mode 100644 testdata/simple_arith1.golden create mode 100644 testdata/simple_arith1.input create mode 100644 testdata/simple_arith2.golden create mode 100644 testdata/simple_arith2.input create mode 100644 testdata/simple_arith3.golden create mode 100644 testdata/simple_arith3.input create mode 100644 testdata/simple_arith_string.golden create mode 100644 testdata/simple_arith_string.input create mode 100644 testdata/simple_arith_string2.golden create mode 100644 testdata/simple_arith_string2.input create mode 100644 testdata/simple_arith_string3.golden create mode 100644 testdata/simple_arith_string3.input create mode 100644 testdata/simple_arith_string_empty.golden create mode 100644 testdata/simple_arith_string_empty.input create mode 100644 testdata/unicode.golden create mode 100644 testdata/unicode.input create mode 100644 testdata/unicode2.golden create mode 100644 testdata/unicode2.input create mode 100644 testdata/verbatim_string.golden create mode 100644 testdata/verbatim_string.input diff --git a/main_test.go b/main_test.go index fb3e0ef..bc01cac 100644 --- a/main_test.go +++ b/main_test.go @@ -16,7 +16,18 @@ limitations under the License. package jsonnet -import "testing" +import ( + "bytes" + "flag" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" +) + +var update = flag.Bool("update", false, "update .golden files") // Just a few simple sanity tests for now. Eventually we'll share end-to-end tests with the C++ // implementation but unsure if that should be done here or via some external framework. @@ -27,35 +38,88 @@ type mainTest struct { golden string } -var mainTests = []mainTest{ - {"numeric_literal", "100", "100"}, - {"boolean_literal", "true", "true"}, - {"simple_arith1", "3 + 3", "6"}, - {"simple_arith2", "3 + 3 + 3", "9"}, - {"simple_arith3", "(3 + 3) + (3 + 3)", "12"}, - {"unicode", `"\u263A"`, `"☺"`}, - {"unicode2", `"\u263a"`, `"☺"`}, - {"escaped_single_quote", `"\\'"`, `"\\'"`}, - {"simple_arith_string", "\"aaa\" + \"bbb\"", "\"aaabbb\""}, - {"simple_arith_string2", "\"aaa\" + \"\"", "\"aaa\""}, - {"simple_arith_string3", "\"\" + \"bbb\"", "\"bbb\""}, - {"simple_arith_string_empty", "\"\" + \"\"", "\"\""}, - {"verbatim_string", `@"blah ☺"`, `"blah ☺"`}, - {"empty_array", "[]", "[ ]"}, - {"array", "[1, 2, 1 + 2]", "[\n 1,\n 2,\n 3\n]"}, - {"empty_object", "{}", "{ }"}, - {"object", `{"x": 1+1}`, "{\n \"x\": 2\n}"}, -} - func TestMain(t *testing.T) { + flag.Parse() + var mainTests []mainTest + match, err := filepath.Glob("testdata/*.input") + if err != nil { + t.Fatal(err) + } + for _, input := range match { + golden := input + name := input + if strings.HasSuffix(input, ".input") { + name = input[:len(input)-len(".input")] + golden = name + ".golden" + } + mainTests = append(mainTests, mainTest{name: name, input: input, golden: golden}) + } for _, test := range mainTests { t.Run(test.name, func(t *testing.T) { vm := MakeVM() - output, err := vm.EvaluateSnippet(test.name, test.input) + read := func(file string) []byte { + bytz, err := ioutil.ReadFile(file) + if err != nil { + t.Fatalf("reading file: %s: %v", file, err) + } + return bytz + } + + input := read(test.input) + output, err := vm.EvaluateSnippet(test.name, string(input)) + if err != nil { + t.Fail() + t.Errorf("evaluate snippet: %v", err) + } + if *update { + err := ioutil.WriteFile(test.golden, []byte(output), 0666) + if err != nil { + t.Errorf("error updating golden files: %v", err) + } + return + } + golden := read(test.golden) + if bytes.Compare(golden, []byte(output)) != 0 { + t.Fail() + t.Errorf("%s.input != %s\n", test.name, test.golden) + data, err := diff(golden, []byte(output)) + if err != nil { + t.Errorf("computing diff: %s", err) + } + t.Errorf("diff %s jsonnet %s.input\n", test.golden, test.name) + t.Errorf(string(data)) - if err == nil && output != test.golden { - t.Errorf("got\n\t%+v\nexpected\n\t%+v", output, test.golden) } }) } } + +// "barrowed" from the std library +// https://golang.org/src/cmd/gofmt/gofmt.go#L228 +func diff(b1, b2 []byte) (data []byte, err error) { + f1, err := ioutil.TempFile("", "jsonnet") + if err != nil { + return + } + defer os.Remove(f1.Name()) + defer f1.Close() + + f2, err := ioutil.TempFile("", "jsonnet") + if err != nil { + return + } + defer os.Remove(f2.Name()) + defer f2.Close() + + f1.Write(b1) + f2.Write(b2) + + 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 + } + return + +} diff --git a/testdata/array.golden b/testdata/array.golden new file mode 100644 index 0000000..c856f5c --- /dev/null +++ b/testdata/array.golden @@ -0,0 +1,5 @@ +[ + 1, + 2, + 3 +] \ No newline at end of file diff --git a/testdata/array.input b/testdata/array.input new file mode 100644 index 0000000..80cc40d --- /dev/null +++ b/testdata/array.input @@ -0,0 +1 @@ +[1, 2, 1 + 2] \ No newline at end of file diff --git a/testdata/boolean_literal.golden b/testdata/boolean_literal.golden new file mode 100644 index 0000000..f32a580 --- /dev/null +++ b/testdata/boolean_literal.golden @@ -0,0 +1 @@ +true \ No newline at end of file diff --git a/testdata/boolean_literal.input b/testdata/boolean_literal.input new file mode 100644 index 0000000..f32a580 --- /dev/null +++ b/testdata/boolean_literal.input @@ -0,0 +1 @@ +true \ No newline at end of file diff --git a/testdata/empty_array.golden b/testdata/empty_array.golden new file mode 100644 index 0000000..8878e54 --- /dev/null +++ b/testdata/empty_array.golden @@ -0,0 +1 @@ +[ ] \ No newline at end of file diff --git a/testdata/empty_array.input b/testdata/empty_array.input new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/testdata/empty_array.input @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/testdata/empty_object.golden b/testdata/empty_object.golden new file mode 100644 index 0000000..6f31cf5 --- /dev/null +++ b/testdata/empty_object.golden @@ -0,0 +1 @@ +{ } \ No newline at end of file diff --git a/testdata/empty_object.input b/testdata/empty_object.input new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/testdata/empty_object.input @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/testdata/escaped_single_quote.golden b/testdata/escaped_single_quote.golden new file mode 100644 index 0000000..eb16fcd --- /dev/null +++ b/testdata/escaped_single_quote.golden @@ -0,0 +1 @@ +"\\'" \ No newline at end of file diff --git a/testdata/escaped_single_quote.input b/testdata/escaped_single_quote.input new file mode 100644 index 0000000..eb16fcd --- /dev/null +++ b/testdata/escaped_single_quote.input @@ -0,0 +1 @@ +"\\'" \ No newline at end of file diff --git a/testdata/numeric_literal.golden b/testdata/numeric_literal.golden new file mode 100644 index 0000000..105d7d9 --- /dev/null +++ b/testdata/numeric_literal.golden @@ -0,0 +1 @@ +100 \ No newline at end of file diff --git a/testdata/numeric_literal.input b/testdata/numeric_literal.input new file mode 100644 index 0000000..105d7d9 --- /dev/null +++ b/testdata/numeric_literal.input @@ -0,0 +1 @@ +100 \ No newline at end of file diff --git a/testdata/object.golden b/testdata/object.golden new file mode 100644 index 0000000..3c7020e --- /dev/null +++ b/testdata/object.golden @@ -0,0 +1,3 @@ +{ + "x": 2 +} \ No newline at end of file diff --git a/testdata/object.input b/testdata/object.input new file mode 100644 index 0000000..6d7857b --- /dev/null +++ b/testdata/object.input @@ -0,0 +1 @@ +{"x": 1+1} \ No newline at end of file diff --git a/testdata/simple_arith1.golden b/testdata/simple_arith1.golden new file mode 100644 index 0000000..62f9457 --- /dev/null +++ b/testdata/simple_arith1.golden @@ -0,0 +1 @@ +6 \ No newline at end of file diff --git a/testdata/simple_arith1.input b/testdata/simple_arith1.input new file mode 100644 index 0000000..8fc606f --- /dev/null +++ b/testdata/simple_arith1.input @@ -0,0 +1 @@ +3 + 3 \ No newline at end of file diff --git a/testdata/simple_arith2.golden b/testdata/simple_arith2.golden new file mode 100644 index 0000000..f11c82a --- /dev/null +++ b/testdata/simple_arith2.golden @@ -0,0 +1 @@ +9 \ No newline at end of file diff --git a/testdata/simple_arith2.input b/testdata/simple_arith2.input new file mode 100644 index 0000000..28de3de --- /dev/null +++ b/testdata/simple_arith2.input @@ -0,0 +1 @@ +3 + 3 + 3 \ No newline at end of file diff --git a/testdata/simple_arith3.golden b/testdata/simple_arith3.golden new file mode 100644 index 0000000..3cacc0b --- /dev/null +++ b/testdata/simple_arith3.golden @@ -0,0 +1 @@ +12 \ No newline at end of file diff --git a/testdata/simple_arith3.input b/testdata/simple_arith3.input new file mode 100644 index 0000000..97529e6 --- /dev/null +++ b/testdata/simple_arith3.input @@ -0,0 +1 @@ +(3 + 3) + (3 + 3) \ No newline at end of file diff --git a/testdata/simple_arith_string.golden b/testdata/simple_arith_string.golden new file mode 100644 index 0000000..bf056f9 --- /dev/null +++ b/testdata/simple_arith_string.golden @@ -0,0 +1 @@ +"aaabbb" \ No newline at end of file diff --git a/testdata/simple_arith_string.input b/testdata/simple_arith_string.input new file mode 100644 index 0000000..dd1044b --- /dev/null +++ b/testdata/simple_arith_string.input @@ -0,0 +1 @@ +"aaa" + "bbb" \ No newline at end of file diff --git a/testdata/simple_arith_string2.golden b/testdata/simple_arith_string2.golden new file mode 100644 index 0000000..a9cf1d4 --- /dev/null +++ b/testdata/simple_arith_string2.golden @@ -0,0 +1 @@ +"aaa" \ No newline at end of file diff --git a/testdata/simple_arith_string2.input b/testdata/simple_arith_string2.input new file mode 100644 index 0000000..1e9bd2a --- /dev/null +++ b/testdata/simple_arith_string2.input @@ -0,0 +1 @@ +"aaa" + "" \ No newline at end of file diff --git a/testdata/simple_arith_string3.golden b/testdata/simple_arith_string3.golden new file mode 100644 index 0000000..b209038 --- /dev/null +++ b/testdata/simple_arith_string3.golden @@ -0,0 +1 @@ +"bbb" \ No newline at end of file diff --git a/testdata/simple_arith_string3.input b/testdata/simple_arith_string3.input new file mode 100644 index 0000000..1af9d9c --- /dev/null +++ b/testdata/simple_arith_string3.input @@ -0,0 +1 @@ +"" + "bbb" \ No newline at end of file diff --git a/testdata/simple_arith_string_empty.golden b/testdata/simple_arith_string_empty.golden new file mode 100644 index 0000000..3cc762b --- /dev/null +++ b/testdata/simple_arith_string_empty.golden @@ -0,0 +1 @@ +"" \ No newline at end of file diff --git a/testdata/simple_arith_string_empty.input b/testdata/simple_arith_string_empty.input new file mode 100644 index 0000000..d9883df --- /dev/null +++ b/testdata/simple_arith_string_empty.input @@ -0,0 +1 @@ +"" + "" \ No newline at end of file diff --git a/testdata/unicode.golden b/testdata/unicode.golden new file mode 100644 index 0000000..4526bf4 --- /dev/null +++ b/testdata/unicode.golden @@ -0,0 +1 @@ +"☺" \ No newline at end of file diff --git a/testdata/unicode.input b/testdata/unicode.input new file mode 100644 index 0000000..d514a5d --- /dev/null +++ b/testdata/unicode.input @@ -0,0 +1 @@ +"\u263A" \ No newline at end of file diff --git a/testdata/unicode2.golden b/testdata/unicode2.golden new file mode 100644 index 0000000..4526bf4 --- /dev/null +++ b/testdata/unicode2.golden @@ -0,0 +1 @@ +"☺" \ No newline at end of file diff --git a/testdata/unicode2.input b/testdata/unicode2.input new file mode 100644 index 0000000..2402faf --- /dev/null +++ b/testdata/unicode2.input @@ -0,0 +1 @@ +"\u263a" \ No newline at end of file diff --git a/testdata/verbatim_string.golden b/testdata/verbatim_string.golden new file mode 100644 index 0000000..562bed5 --- /dev/null +++ b/testdata/verbatim_string.golden @@ -0,0 +1 @@ +"blah ☺" \ No newline at end of file diff --git a/testdata/verbatim_string.input b/testdata/verbatim_string.input new file mode 100644 index 0000000..ed3f2d6 --- /dev/null +++ b/testdata/verbatim_string.input @@ -0,0 +1 @@ +@"blah ☺" \ No newline at end of file From 7b875bd141af03f317697d07e60bec224345c71b Mon Sep 17 00:00:00 2001 From: Sevki Date: Fri, 18 Aug 2017 00:19:43 +0200 Subject: [PATCH 4/4] change the diff function the proposed change would remove the dependency of a diff binary in favour of using a library Signed-off-by: Sevki --- main_test.go | 38 +++++++------------------------------- 1 file changed, 7 insertions(+), 31 deletions(-) diff --git a/main_test.go b/main_test.go index bc01cac..e2b4123 100644 --- a/main_test.go +++ b/main_test.go @@ -20,11 +20,11 @@ import ( "bytes" "flag" "io/ioutil" - "os" - "os/exec" "path/filepath" "strings" "testing" + + "github.com/sergi/go-diff/diffmatchpatch" ) var update = flag.Bool("update", false, "update .golden files") @@ -82,7 +82,7 @@ func TestMain(t *testing.T) { if bytes.Compare(golden, []byte(output)) != 0 { t.Fail() t.Errorf("%s.input != %s\n", test.name, test.golden) - data, err := diff(golden, []byte(output)) + data := diff( output, string(golden),) if err != nil { t.Errorf("computing diff: %s", err) } @@ -94,32 +94,8 @@ func TestMain(t *testing.T) { } } -// "barrowed" from the std library -// https://golang.org/src/cmd/gofmt/gofmt.go#L228 -func diff(b1, b2 []byte) (data []byte, err error) { - f1, err := ioutil.TempFile("", "jsonnet") - if err != nil { - return - } - defer os.Remove(f1.Name()) - defer f1.Close() - - f2, err := ioutil.TempFile("", "jsonnet") - if err != nil { - return - } - defer os.Remove(f2.Name()) - defer f2.Close() - - f1.Write(b1) - f2.Write(b2) - - 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 - } - return - +func diff(a, b string) string { + dmp := diffmatchpatch.New() + diffs := dmp.DiffMain(a, b, false) + return dmp.DiffPrettyText(diffs) }