From a09aa25ba2c628fb604343b2402448403db4764e Mon Sep 17 00:00:00 2001 From: Jordan Barrett <90195985+barrettj12@users.noreply.github.com.> Date: Fri, 31 May 2024 15:37:12 +1200 Subject: [PATCH 1/6] first pass at list equals checker - implement slice diff algorithm --- checkers/listequals.go | 155 ++++++++++++++++++++++++++++++++++++ checkers/listequals_test.go | 20 +++++ 2 files changed, 175 insertions(+) create mode 100644 checkers/listequals.go create mode 100644 checkers/listequals_test.go diff --git a/checkers/listequals.go b/checkers/listequals.go new file mode 100644 index 0000000..d8db8e1 --- /dev/null +++ b/checkers/listequals.go @@ -0,0 +1,155 @@ +// Copyright 2024 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package checkers + +import ( + "fmt" + gc "gopkg.in/check.v1" +) + +type listEqualsChecker struct { + *gc.CheckerInfo +} + +// The ListEquals checker verifies if two lists are equal. If they are not, +// it will essentially run a "diff" algorithm to provide the developer with +// an easily understandable summary of the difference between the two lists. +var ListEquals gc.Checker = &listEqualsChecker{ + &gc.CheckerInfo{Name: "ListEquals", Params: []string{"obtained", "expected"}}, +} + +func (l listEqualsChecker) Check(params []interface{}, names []string) (result bool, error string) { + obtained := params[0] + expected := params[1] + + // TODO: Simple pre-checks: + // - both obtained and expected are indeed slices + // - of the same element type + // - and this element type is comparable. + _, _ = obtained, expected + + // The approach here is to find a longest-common subsequence using dynamic + // programming, and use this to generate the diff. This algorithm runs in + // O(n^2). However, naive list equality is only O(n). Hence, to be more + // efficient, we should first check if the lists are equal, and if they are + // not, we do the more complicated work to find out exactly *how* they are + // different. + + // Check length is equal + // Iterate through and check every element + + // If we're here, the lists are not equal, so run the DP algorithm to + // compute the diff. + return false, "" //generateDiff(obtained, expected) +} + +func generateDiff(obtained, expected []rune) string { + // lenLCS[m][n] stores the length of the longest common subsequence of + // obtained[:m] and expected[:n] + lenLCS := make([][]int, len(obtained)+1) + for i := 0; i <= len(obtained); i++ { + lenLCS[i] = make([]int, len(expected)+1) + } + + // lenLCS[i][0] and lenLCS[0][j] are already correctly initialised to 0 + + for i := 1; i <= len(obtained); i++ { + for j := 1; j <= len(expected); j++ { + if obtained[i-1] == expected[j-1] { + // We can extend the longest subsequence of obtained[:i-1] and expected[:j-1] + lenLCS[i][j] = lenLCS[i-1][j-1] + 1 + } else { + // We can't extend a previous subsequence + lenLCS[i][j] = max(lenLCS[i-1][j], lenLCS[i][j-1]) + } + } + } + + // print table + fmt.Print(" ") + for j := 0; j < len(expected); j++ { + fmt.Printf("%c ", expected[j]) + } + fmt.Println() + for i := 0; i <= len(obtained); i++ { + fmt.Printf("%c ", append([]rune{' '}, obtained...)[i]) + for j := 0; j <= len(expected); j++ { + fmt.Printf("%d ", lenLCS[i][j]) + } + fmt.Println() + } + + // "Traceback" to calculate the diff + var diffs []diff + i := len(obtained) + j := len(expected) + + for i > 0 && j > 0 { + if lenLCS[i][j] == lenLCS[i-1][j-1] { + // Element changed at this index + diffs = append(diffs, elementChanged{j, expected[j-1], obtained[i-1]}) + i -= 1 + j -= 1 + + } else if lenLCS[i][j] == lenLCS[i-1][j] { + // Additional/unexpected element at this index + diffs = append(diffs, elementAdded{j, obtained[i-1]}) + i -= 1 + + } else if lenLCS[i][j] == lenLCS[i][j-1] { + // Element missing at this index + diffs = append(diffs, elementRemoved{j, expected[j-1]}) + j -= 1 + + } else { + // Elements are the same at this index - no diff + i -= 1 + j -= 1 + } + } + + // Convert diffs array into human-readable error + description := "" + for k := len(diffs) - 1; k >= 0; k-- { + description += diffs[k].String() + "\n" + } + fmt.Println(description) + return description +} + +var GenerateDiff = generateDiff + +// diff represents a single difference between the two slices. +type diff interface { + // Prints a user friendly description of this difference. + String() string +} + +type elementAdded struct { + index int + element any +} + +func (d elementAdded) String() string { + return fmt.Sprintf("at index %d: unexpected element %v", d.index, d.element) +} + +type elementChanged struct { + index int + + original, changed any +} + +func (d elementChanged) String() string { + return fmt.Sprintf("at index %d: obtained element %v, expected %v", d.index, d.original, d.changed) +} + +type elementRemoved struct { + index int + element any +} + +func (d elementRemoved) String() string { + return fmt.Sprintf("at index %d: missing element %v", d.index, d.element) +} diff --git a/checkers/listequals_test.go b/checkers/listequals_test.go new file mode 100644 index 0000000..1ebfebb --- /dev/null +++ b/checkers/listequals_test.go @@ -0,0 +1,20 @@ +// Copyright 2024 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package checkers_test + +import ( + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" +) + +type listEqualsSuite struct{} + +var _ = gc.Suite(&listEqualsSuite{}) + +func (s *listEqualsSuite) TestFoo(c *gc.C) { + jc.GenerateDiff( + []rune("ABDEZGHILJK"), + []rune("ABCDEFGHIJK"), + ) +} From 1bce9453875c805819fa4cfd520875047780cd2e Mon Sep 17 00:00:00 2001 From: Jordan Barrett <90195985+barrettj12@users.noreply.github.com.> Date: Fri, 31 May 2024 16:00:48 +1200 Subject: [PATCH 2/6] use reflection to check list equality --- checkers/listequals.go | 86 +++++++++++++++++++++---------------- checkers/listequals_test.go | 3 +- 2 files changed, 50 insertions(+), 39 deletions(-) diff --git a/checkers/listequals.go b/checkers/listequals.go index d8db8e1..b659656 100644 --- a/checkers/listequals.go +++ b/checkers/listequals.go @@ -5,6 +5,8 @@ package checkers import ( "fmt" + "reflect" + gc "gopkg.in/check.v1" ) @@ -19,15 +21,24 @@ var ListEquals gc.Checker = &listEqualsChecker{ &gc.CheckerInfo{Name: "ListEquals", Params: []string{"obtained", "expected"}}, } -func (l listEqualsChecker) Check(params []interface{}, names []string) (result bool, error string) { +func (l *listEqualsChecker) Check(params []interface{}, names []string) (result bool, error string) { obtained := params[0] expected := params[1] - // TODO: Simple pre-checks: - // - both obtained and expected are indeed slices - // - of the same element type - // - and this element type is comparable. - _, _ = obtained, expected + // Do some simple pre-checks. First, that both 'obtained' and 'expected' + // are indeed slices. + vExp := reflect.ValueOf(expected) + if vExp.Kind() != reflect.Slice { + return false, fmt.Sprintf("expected value is not a slice") + } + + vObt := reflect.ValueOf(obtained) + if vObt.Kind() != reflect.Slice { + return false, fmt.Sprintf("obtained value is not a slice") + } + + // TODO: check that obtained and expected have of the same element type, + // and this element type is comparable. // The approach here is to find a longest-common subsequence using dynamic // programming, and use this to generate the diff. This algorithm runs in @@ -36,27 +47,42 @@ func (l listEqualsChecker) Check(params []interface{}, names []string) (result b // not, we do the more complicated work to find out exactly *how* they are // different. + slicesEqual := true // Check length is equal - // Iterate through and check every element + if vObt.Len() == vExp.Len() { + // Iterate through and check every element + for i := 0; i < vExp.Len(); i++ { + a := vObt.Index(i) + b := vExp.Index(i) + if !a.Equal(b) { + slicesEqual = false + break + } + } + + if slicesEqual { + return true, "" + } + } // If we're here, the lists are not equal, so run the DP algorithm to // compute the diff. - return false, "" //generateDiff(obtained, expected) + return false, generateDiff(vObt, vExp) } -func generateDiff(obtained, expected []rune) string { +func generateDiff(obtained, expected reflect.Value) string { // lenLCS[m][n] stores the length of the longest common subsequence of // obtained[:m] and expected[:n] - lenLCS := make([][]int, len(obtained)+1) - for i := 0; i <= len(obtained); i++ { - lenLCS[i] = make([]int, len(expected)+1) + lenLCS := make([][]int, obtained.Len()+1) + for i := 0; i <= obtained.Len(); i++ { + lenLCS[i] = make([]int, expected.Len()+1) } // lenLCS[i][0] and lenLCS[0][j] are already correctly initialised to 0 - for i := 1; i <= len(obtained); i++ { - for j := 1; j <= len(expected); j++ { - if obtained[i-1] == expected[j-1] { + for i := 1; i <= obtained.Len(); i++ { + for j := 1; j <= expected.Len(); j++ { + if obtained.Index(i - 1).Equal(expected.Index(j - 1)) { // We can extend the longest subsequence of obtained[:i-1] and expected[:j-1] lenLCS[i][j] = lenLCS[i-1][j-1] + 1 } else { @@ -66,40 +92,26 @@ func generateDiff(obtained, expected []rune) string { } } - // print table - fmt.Print(" ") - for j := 0; j < len(expected); j++ { - fmt.Printf("%c ", expected[j]) - } - fmt.Println() - for i := 0; i <= len(obtained); i++ { - fmt.Printf("%c ", append([]rune{' '}, obtained...)[i]) - for j := 0; j <= len(expected); j++ { - fmt.Printf("%d ", lenLCS[i][j]) - } - fmt.Println() - } - // "Traceback" to calculate the diff var diffs []diff - i := len(obtained) - j := len(expected) + i := obtained.Len() + j := expected.Len() for i > 0 && j > 0 { if lenLCS[i][j] == lenLCS[i-1][j-1] { // Element changed at this index - diffs = append(diffs, elementChanged{j, expected[j-1], obtained[i-1]}) + diffs = append(diffs, elementChanged{j, expected.Index(j - 1), obtained.Index(i - 1)}) i -= 1 j -= 1 } else if lenLCS[i][j] == lenLCS[i-1][j] { // Additional/unexpected element at this index - diffs = append(diffs, elementAdded{j, obtained[i-1]}) + diffs = append(diffs, elementAdded{j, obtained.Index(i - 1)}) i -= 1 } else if lenLCS[i][j] == lenLCS[i][j-1] { // Element missing at this index - diffs = append(diffs, elementRemoved{j, expected[j-1]}) + diffs = append(diffs, elementRemoved{j, expected.Index(j - 1)}) j -= 1 } else { @@ -110,16 +122,14 @@ func generateDiff(obtained, expected []rune) string { } // Convert diffs array into human-readable error - description := "" + description := "difference:" for k := len(diffs) - 1; k >= 0; k-- { - description += diffs[k].String() + "\n" + description += "\n - " + diffs[k].String() } fmt.Println(description) return description } -var GenerateDiff = generateDiff - // diff represents a single difference between the two slices. type diff interface { // Prints a user friendly description of this difference. diff --git a/checkers/listequals_test.go b/checkers/listequals_test.go index 1ebfebb..e828465 100644 --- a/checkers/listequals_test.go +++ b/checkers/listequals_test.go @@ -13,8 +13,9 @@ type listEqualsSuite struct{} var _ = gc.Suite(&listEqualsSuite{}) func (s *listEqualsSuite) TestFoo(c *gc.C) { - jc.GenerateDiff( + c.Check( []rune("ABDEZGHILJK"), + jc.ListEquals, []rune("ABCDEFGHIJK"), ) } From d8ee85439b37b8aaa3356427c4423f09d2d092ae Mon Sep 17 00:00:00 2001 From: Jordan Barrett <90195985+barrettj12@users.noreply.github.com.> Date: Fri, 31 May 2024 16:10:38 +1200 Subject: [PATCH 3/6] fix indices --- checkers/listequals.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/checkers/listequals.go b/checkers/listequals.go index b659656..e46ef1b 100644 --- a/checkers/listequals.go +++ b/checkers/listequals.go @@ -100,7 +100,7 @@ func generateDiff(obtained, expected reflect.Value) string { for i > 0 && j > 0 { if lenLCS[i][j] == lenLCS[i-1][j-1] { // Element changed at this index - diffs = append(diffs, elementChanged{j, expected.Index(j - 1), obtained.Index(i - 1)}) + diffs = append(diffs, elementChanged{j - 1, expected.Index(j - 1), obtained.Index(i - 1)}) i -= 1 j -= 1 @@ -111,7 +111,7 @@ func generateDiff(obtained, expected reflect.Value) string { } else if lenLCS[i][j] == lenLCS[i][j-1] { // Element missing at this index - diffs = append(diffs, elementRemoved{j, expected.Index(j - 1)}) + diffs = append(diffs, elementRemoved{j - 1, expected.Index(j - 1)}) j -= 1 } else { From 8aa60bb32757d6e00b2ad2d960c2a056b12fa87f Mon Sep 17 00:00:00 2001 From: Jordan Barrett <90195985+barrettj12@users.noreply.github.com.> Date: Fri, 7 Jun 2024 15:20:34 +1200 Subject: [PATCH 4/6] ListEquals: flesh out test suite Add some table driven tests, benchmarks and fuzzing. - precheck for comparable types in ListEquals - correctly handle elems added/missing at start --- checkers/listequals.go | 21 ++++- checkers/listequals_test.go | 155 ++++++++++++++++++++++++++++++++++-- 2 files changed, 166 insertions(+), 10 deletions(-) diff --git a/checkers/listequals.go b/checkers/listequals.go index e46ef1b..4467d3f 100644 --- a/checkers/listequals.go +++ b/checkers/listequals.go @@ -37,8 +37,14 @@ func (l *listEqualsChecker) Check(params []interface{}, names []string) (result return false, fmt.Sprintf("obtained value is not a slice") } - // TODO: check that obtained and expected have of the same element type, - // and this element type is comparable. + // Check that the element types are comparable. + if !vExp.Type().Elem().Comparable() { + return false, fmt.Sprintf("expected element type is not comparable") + } + + if !vObt.Type().Elem().Comparable() { + return false, fmt.Sprintf("obtained element type is not comparable") + } // The approach here is to find a longest-common subsequence using dynamic // programming, and use this to generate the diff. This algorithm runs in @@ -120,13 +126,22 @@ func generateDiff(obtained, expected reflect.Value) string { j -= 1 } } + for i > 0 { + // Extra elements have been added at the start + diffs = append(diffs, elementAdded{0, obtained.Index(i - 1)}) + i -= 1 + } + for j > 0 { + // Elements are missing at the start + diffs = append(diffs, elementRemoved{j - 1, expected.Index(j - 1)}) + j -= 1 + } // Convert diffs array into human-readable error description := "difference:" for k := len(diffs) - 1; k >= 0; k-- { description += "\n - " + diffs[k].String() } - fmt.Println(description) return description } diff --git a/checkers/listequals_test.go b/checkers/listequals_test.go index e828465..34171db 100644 --- a/checkers/listequals_test.go +++ b/checkers/listequals_test.go @@ -4,18 +4,159 @@ package checkers_test import ( - jc "github.com/juju/testing/checkers" + "reflect" + "testing" + gc "gopkg.in/check.v1" + + jc "github.com/juju/testing/checkers" ) type listEqualsSuite struct{} var _ = gc.Suite(&listEqualsSuite{}) -func (s *listEqualsSuite) TestFoo(c *gc.C) { - c.Check( - []rune("ABDEZGHILJK"), - jc.ListEquals, - []rune("ABCDEFGHIJK"), - ) +type testCase struct { + description string + list1, list2 any + equal bool + error string +} + +var testCases = []testCase{{ + description: "both not slices", + list1: struct{}{}, + list2: map[string]string{}, + error: "expected value is not a slice", +}, { + description: "obtained is not a slice", + list1: 1, + list2: []string{}, + error: "obtained value is not a slice", +}, { + description: "expected is not a slice", + list1: []string{}, + list2: "foobar", + error: "expected value is not a slice", +}, { + description: "same contents but different element type", + list1: []string{"A", "B", "C", "DEF"}, + list2: []any{"A", "B", "C", "DEF"}, + equal: true, +}, { + description: "different type in last position", + list1: []string{"A", "B", "C", "DEF"}, + list2: []any{"A", "B", "C", 321}, + equal: false, + error: `difference: + - at index 3: obtained element 321, expected DEF`, +}, { + description: "both element types not comparable", + list1: [][]string{{"A"}}, + list2: []func(){func() {}}, + error: "expected element type is not comparable", +}, { + description: "obtained element type not comparable", + list1: []map[string]string{{"A": "B"}}, + list2: []string{"A"}, + error: "obtained element type is not comparable", +}, { + description: "expected element type not comparable", + list1: []string{"A"}, + list2: [][]string{{"A"}}, + error: "expected element type is not comparable", +}, { + description: "incomparable values are fine", + list1: []string{"A"}, + list2: []any{[]string{"A"}}, + error: `difference: + - at index 0: obtained element \[A\], expected A`, +}, { + description: "elements missing at start", + list1: []int{5, 6}, + list2: []int{3, 4, 5, 6}, + equal: false, + error: `difference: + - at index 0: missing element 3 + - at index 1: missing element 4`, +}, { + description: "elements added at start", + list1: []int{1, 2, 3, 4, 5, 6}, + list2: []int{3, 4, 5, 6}, + equal: false, + error: `difference: + - at index 0: unexpected element 1 + - at index 0: unexpected element 2`, +}, { + description: "elements missing at end", + list1: []int{3, 4}, + list2: []int{3, 4, 5, 6}, + equal: false, + error: `difference: + - at index 2: missing element 5 + - at index 3: missing element 6`, +}, { + description: "elements added at end", + list1: []int{3, 4, 5, 6, 7, 8}, + list2: []int{3, 4, 5, 6}, + equal: false, + error: `difference: + - at index 4: unexpected element 7 + - at index 4: unexpected element 8`, +}, + +// TODO: add some expected tests cases for general differences +} + +func init() { + // Add a test case with two super long but equal arrays. In this case, we + // should find equality first in O(n), so it shouldn't be too slow. + + N := 10000 + superLongSlice := make([]int, N) + for i := 0; i < N; i++ { + superLongSlice[i] = i + } + + testCases = append(testCases, testCase{ + description: "super long slice", + list1: superLongSlice, + list2: superLongSlice, + equal: true, + }) +} + +func (s *listEqualsSuite) Test(c *gc.C) { + for _, test := range testCases { + c.Log(test.description) + res, err := jc.ListEquals.Check([]any{test.list1, test.list2}, nil) + c.Check(res, gc.Equals, test.equal) + c.Check(err, gc.Matches, test.error) + } +} + +func BenchmarkListEquals(b *testing.B) { + for _, test := range testCases { + b.Run(test.description, func(b *testing.B) { + for i := 0; i < b.N; i++ { + jc.ListEquals.Check([]any{test.list1, test.list2}, nil) + } + }) + } +} + +func FuzzListEquals(f *testing.F) { + f.Fuzz(func(t *testing.T, list1, list2 []byte) { + eq, errMsg := jc.ListEquals.Check([]any{list1, list1}, nil) + if eq == false || errMsg != "" { + t.Errorf("should ListEquals itself: %v", list1) + } + + eq, errMsg = jc.ListEquals.Check([]any{list1, list2}, nil) + if eq != (reflect.DeepEqual(list1, list2)) { + t.Errorf(`ListEquals returned incorrect value for +list1: %v +list2: %v`, list1, list2) + } + }) } From 33c8199472f6afe7711733615919cb1ced8596bb Mon Sep 17 00:00:00 2001 From: Jordan Barrett <90195985+barrettj12@users.noreply.github.com.> Date: Thu, 13 Jun 2024 14:59:36 +1200 Subject: [PATCH 5/6] Enforce slice element types to be equal e.g. a []string cannot equal an []any, even if they have the same contents - fix display of changed diffs - add some more test cases --- checkers/listequals.go | 16 +++++++----- checkers/listequals_test.go | 52 ++++++++++++++++++++++--------------- 2 files changed, 41 insertions(+), 27 deletions(-) diff --git a/checkers/listequals.go b/checkers/listequals.go index 4467d3f..b330569 100644 --- a/checkers/listequals.go +++ b/checkers/listequals.go @@ -37,13 +37,17 @@ func (l *listEqualsChecker) Check(params []interface{}, names []string) (result return false, fmt.Sprintf("obtained value is not a slice") } - // Check that the element types are comparable. - if !vExp.Type().Elem().Comparable() { - return false, fmt.Sprintf("expected element type is not comparable") + // Check that element types are the same + expElemType := vExp.Type().Elem() + obtElemType := vObt.Type().Elem() + + if expElemType != obtElemType { + return false, fmt.Sprintf("element types are not equal") } - if !vObt.Type().Elem().Comparable() { - return false, fmt.Sprintf("obtained element type is not comparable") + // Check that the element type is comparable. + if !expElemType.Comparable() { + return false, fmt.Sprintf("element type is not comparable") } // The approach here is to find a longest-common subsequence using dynamic @@ -167,7 +171,7 @@ type elementChanged struct { } func (d elementChanged) String() string { - return fmt.Sprintf("at index %d: obtained element %v, expected %v", d.index, d.original, d.changed) + return fmt.Sprintf("at index %d: obtained element %v, expected %v", d.index, d.changed, d.original) } type elementRemoved struct { diff --git a/checkers/listequals_test.go b/checkers/listequals_test.go index 34171db..d10164b 100644 --- a/checkers/listequals_test.go +++ b/checkers/listequals_test.go @@ -42,35 +42,25 @@ var testCases = []testCase{{ description: "same contents but different element type", list1: []string{"A", "B", "C", "DEF"}, list2: []any{"A", "B", "C", "DEF"}, - equal: true, + error: "element types are not equal", }, { description: "different type in last position", - list1: []string{"A", "B", "C", "DEF"}, + list1: []any{"A", "B", "C", "DEF"}, list2: []any{"A", "B", "C", 321}, equal: false, error: `difference: - - at index 3: obtained element 321, expected DEF`, + - at index 3: obtained element DEF, expected 321`, }, { - description: "both element types not comparable", + description: "incomparable element type", list1: [][]string{{"A"}}, - list2: []func(){func() {}}, - error: "expected element type is not comparable", -}, { - description: "obtained element type not comparable", - list1: []map[string]string{{"A": "B"}}, - list2: []string{"A"}, - error: "obtained element type is not comparable", -}, { - description: "expected element type not comparable", - list1: []string{"A"}, list2: [][]string{{"A"}}, - error: "expected element type is not comparable", + error: "element type is not comparable", }, { description: "incomparable values are fine", - list1: []string{"A"}, + list1: []any{"A"}, list2: []any{[]string{"A"}}, error: `difference: - - at index 0: obtained element \[A\], expected A`, + - at index 0: obtained element A, expected \[A\]`, }, { description: "elements missing at start", list1: []int{5, 6}, @@ -103,10 +93,30 @@ var testCases = []testCase{{ error: `difference: - at index 4: unexpected element 7 - at index 4: unexpected element 8`, -}, - -// TODO: add some expected tests cases for general differences -} +}, { + description: "basic test", + list1: []int{0, 2, 62, 4, 43, 5, 7, 104, 9, 56, 10}, + list2: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, + equal: false, + error: `difference: + - at index 1: missing element 1 + - at index 3: obtained element 62, expected 3 + - at index 5: unexpected element 43 + - at index 6: missing element 6 + - at index 8: obtained element 104, expected 8 + - at index 10: unexpected element 56 + - at index 11: missing element 11`, +}, { + description: "replaced elements", + list1: []string{"A", "Z", "C", "Y", "E", "X", "G", "W", "I"}, + list2: []string{"A", "B", "C", "D", "E", "F", "G", "H", "I"}, + equal: false, + error: `difference: + - at index 1: obtained element Z, expected B + - at index 3: obtained element Y, expected D + - at index 5: obtained element X, expected F + - at index 7: obtained element W, expected H`, +}} func init() { // Add a test case with two super long but equal arrays. In this case, we From 4181ec8224700e5cd05b1f0720400d50353b24bd Mon Sep 17 00:00:00 2001 From: Jordan Barrett <90195985+barrettj12@users.noreply.github.com.> Date: Thu, 13 Jun 2024 15:38:12 +1200 Subject: [PATCH 6/6] use %#v for better formatting --- checkers/listequals.go | 6 +++--- checkers/listequals_test.go | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/checkers/listequals.go b/checkers/listequals.go index b330569..a5ed544 100644 --- a/checkers/listequals.go +++ b/checkers/listequals.go @@ -161,7 +161,7 @@ type elementAdded struct { } func (d elementAdded) String() string { - return fmt.Sprintf("at index %d: unexpected element %v", d.index, d.element) + return fmt.Sprintf("at index %d: unexpected element %#v", d.index, d.element) } type elementChanged struct { @@ -171,7 +171,7 @@ type elementChanged struct { } func (d elementChanged) String() string { - return fmt.Sprintf("at index %d: obtained element %v, expected %v", d.index, d.changed, d.original) + return fmt.Sprintf("at index %d: obtained element %#v, expected %#v", d.index, d.changed, d.original) } type elementRemoved struct { @@ -180,5 +180,5 @@ type elementRemoved struct { } func (d elementRemoved) String() string { - return fmt.Sprintf("at index %d: missing element %v", d.index, d.element) + return fmt.Sprintf("at index %d: missing element %#v", d.index, d.element) } diff --git a/checkers/listequals_test.go b/checkers/listequals_test.go index d10164b..372802a 100644 --- a/checkers/listequals_test.go +++ b/checkers/listequals_test.go @@ -49,7 +49,7 @@ var testCases = []testCase{{ list2: []any{"A", "B", "C", 321}, equal: false, error: `difference: - - at index 3: obtained element DEF, expected 321`, + - at index 3: obtained element "DEF", expected 321`, }, { description: "incomparable element type", list1: [][]string{{"A"}}, @@ -60,7 +60,7 @@ var testCases = []testCase{{ list1: []any{"A"}, list2: []any{[]string{"A"}}, error: `difference: - - at index 0: obtained element A, expected \[A\]`, + - at index 0: obtained element "A", expected \[\]string\{"A"\}`, }, { description: "elements missing at start", list1: []int{5, 6}, @@ -112,10 +112,10 @@ var testCases = []testCase{{ list2: []string{"A", "B", "C", "D", "E", "F", "G", "H", "I"}, equal: false, error: `difference: - - at index 1: obtained element Z, expected B - - at index 3: obtained element Y, expected D - - at index 5: obtained element X, expected F - - at index 7: obtained element W, expected H`, + - at index 1: obtained element "Z", expected "B" + - at index 3: obtained element "Y", expected "D" + - at index 5: obtained element "X", expected "F" + - at index 7: obtained element "W", expected "H"`, }} func init() {