Skip to content

Commit 21cd9d4

Browse files
authored
fix: deterministic order of keys when diffing maps (#31)
Adapted from stretchr#1822 The SpewKeys setting looks sensible. test: refactored the diff test to make it a table-driven, iterable test. Signed-off-by: Frederic BIDON <[email protected]>
1 parent 69fcab1 commit 21cd9d4

File tree

2 files changed

+118
-77
lines changed

2 files changed

+118
-77
lines changed

internal/assertions/helpers_impl_test.go

Lines changed: 116 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,60 @@ func testDiff() func(*testing.T) {
4444
return func(t *testing.T) {
4545
t.Parallel()
4646

47-
expected := `
47+
for tt := range diffCases() {
48+
t.Run(tt.name, func(t *testing.T) {
49+
t.Parallel()
50+
51+
for range min(1, tt.repeat) { // for tests on maps, need to verify the ordering is stable
52+
actual := diff(
53+
tt.valueA,
54+
tt.valueB,
55+
)
56+
Equal(t, tt.expected, actual)
57+
}
58+
})
59+
}
60+
}
61+
}
62+
63+
func testDiffList() func(*testing.T) {
64+
return func(t *testing.T) {
65+
t.Parallel()
66+
67+
for test := range compareDiffListCases() {
68+
t.Run(test.name, func(t *testing.T) {
69+
t.Parallel()
70+
71+
actualExtraA, actualExtraB := diffLists(test.listA, test.listB)
72+
Equal(t, test.extraA, actualExtraA, "extra A does not match for listA=%v listB=%v",
73+
test.listA, test.listB)
74+
Equal(t, test.extraB, actualExtraB, "extra B does not match for listA=%v listB=%v",
75+
test.listA, test.listB)
76+
})
77+
}
78+
}
79+
}
80+
81+
type diffCase struct {
82+
name string
83+
repeat int
84+
valueA any
85+
valueB any
86+
expected string
87+
}
88+
89+
func diffCases() iter.Seq[diffCase] {
90+
const n = 5
91+
type Key struct {
92+
x int
93+
}
94+
95+
return slices.Values([]diffCase{
96+
{
97+
name: "with diff struct",
98+
valueA: struct{ foo string }{"hello"},
99+
valueB: struct{ foo string }{"bar"},
100+
expected: `
48101
49102
Diff:
50103
--- Expected
@@ -54,14 +107,13 @@ Diff:
54107
- foo: (string) (len=5) "hello"
55108
+ foo: (string) (len=3) "bar"
56109
}
57-
`
58-
actual := diff(
59-
struct{ foo string }{"hello"},
60-
struct{ foo string }{"bar"},
61-
)
62-
Equal(t, expected, actual)
63-
64-
expected = `
110+
`,
111+
},
112+
{
113+
name: "with diff slice",
114+
valueA: []int{1, 2, 3, 4},
115+
valueB: []int{1, 3, 5, 7},
116+
expected: `
65117
66118
Diff:
67119
--- Expected
@@ -74,14 +126,13 @@ Diff:
74126
+ (int) 5,
75127
+ (int) 7
76128
}
77-
`
78-
actual = diff(
79-
[]int{1, 2, 3, 4},
80-
[]int{1, 3, 5, 7},
81-
)
82-
Equal(t, expected, actual)
83-
84-
expected = `
129+
`,
130+
},
131+
{
132+
name: "with diff slice (sliced)",
133+
valueA: []int{1, 2, 3, 4}[0:3],
134+
valueB: []int{1, 3, 5, 7}[0:3],
135+
expected: `
85136
86137
Diff:
87138
--- Expected
@@ -93,14 +144,14 @@ Diff:
93144
+ (int) 3,
94145
+ (int) 5
95146
}
96-
`
97-
actual = diff(
98-
[]int{1, 2, 3, 4}[0:3],
99-
[]int{1, 3, 5, 7}[0:3],
100-
)
101-
Equal(t, expected, actual)
102-
103-
expected = `
147+
`,
148+
},
149+
{
150+
name: "with string keys map keys should be rendered deterministically in diffs",
151+
repeat: n,
152+
valueA: map[string]int{"one": 1, "two": 2, "three": 3, "four": 4},
153+
valueB: map[string]int{"one": 1, "three": 3, "five": 5, "seven": 7},
154+
expected: `
104155
105156
Diff:
106157
--- Expected
@@ -115,15 +166,13 @@ Diff:
115166
+ (string) (len=5) "seven": (int) 7,
116167
+ (string) (len=5) "three": (int) 3
117168
}
118-
`
119-
120-
actual = diff(
121-
map[string]int{"one": 1, "two": 2, "three": 3, "four": 4},
122-
map[string]int{"one": 1, "three": 3, "five": 5, "seven": 7},
123-
)
124-
Equal(t, expected, actual)
125-
126-
expected = `
169+
`,
170+
},
171+
{
172+
name: "with diff error",
173+
valueA: errors.New("some expected error"),
174+
valueB: errors.New("actual error"),
175+
expected: `
127176
128177
Diff:
129178
--- Expected
@@ -133,15 +182,30 @@ Diff:
133182
- s: (string) (len=19) "some expected error"
134183
+ s: (string) (len=12) "actual error"
135184
})
136-
`
137-
138-
actual = diff(
139-
errors.New("some expected error"),
140-
errors.New("actual error"),
141-
)
142-
Equal(t, expected, actual)
185+
`,
186+
},
187+
{
188+
name: "with arbitrary comparable keys map keys should be rendered deterministically in diffs",
189+
repeat: n,
190+
valueA: map[Key]int{{1}: 1, {2}: 2, {3}: 3, {4}: 4},
191+
valueB: map[Key]int{{1}: 1, {2}: 2, {3}: 3, {4}: 999},
192+
expected: `
143193
144-
expected = `
194+
Diff:
195+
--- Expected
196+
+++ Actual
197+
@@ -12,3 +12,3 @@
198+
x: (int) 4
199+
- }: (int) 4
200+
+ }: (int) 999
201+
}
202+
`,
203+
},
204+
{
205+
name: "with diff unexported struct",
206+
valueA: diffTestingStruct{A: "some string", B: 10},
207+
valueB: diffTestingStruct{A: "some string", B: 15},
208+
expected: `
145209
146210
Diff:
147211
--- Expected
@@ -151,15 +215,13 @@ Diff:
151215
- B: (int) 10
152216
+ B: (int) 15
153217
}
154-
`
155-
156-
actual = diff(
157-
diffTestingStruct{A: "some string", B: 10},
158-
diffTestingStruct{A: "some string", B: 15},
159-
)
160-
Equal(t, expected, actual)
161-
162-
expected = `
218+
`,
219+
},
220+
{
221+
name: "with diff date",
222+
valueA: time.Date(2020, 9, 24, 0, 0, 0, 0, time.UTC),
223+
valueB: time.Date(2020, 9, 25, 0, 0, 0, 0, time.UTC),
224+
expected: `
163225
164226
Diff:
165227
--- Expected
@@ -168,32 +230,9 @@ Diff:
168230
-(time.Time) 2020-09-24 00:00:00 +0000 UTC
169231
+(time.Time) 2020-09-25 00:00:00 +0000 UTC
170232
171-
`
172-
173-
actual = diff(
174-
time.Date(2020, 9, 24, 0, 0, 0, 0, time.UTC),
175-
time.Date(2020, 9, 25, 0, 0, 0, 0, time.UTC),
176-
)
177-
Equal(t, expected, actual)
178-
}
179-
}
180-
181-
func testDiffList() func(*testing.T) {
182-
return func(t *testing.T) {
183-
t.Parallel()
184-
185-
for test := range compareDiffListCases() {
186-
t.Run(test.name, func(t *testing.T) {
187-
t.Parallel()
188-
189-
actualExtraA, actualExtraB := diffLists(test.listA, test.listB)
190-
Equal(t, test.extraA, actualExtraA, "extra A does not match for listA=%v listB=%v",
191-
test.listA, test.listB)
192-
Equal(t, test.extraB, actualExtraB, "extra B does not match for listA=%v listB=%v",
193-
test.listA, test.listB)
194-
})
195-
}
196-
}
233+
`,
234+
},
235+
})
197236
}
198237

199238
type compareDiffListCase struct {

internal/assertions/spew.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ var (
1414
DisablePointerAddresses: true,
1515
DisableCapacities: true,
1616
SortKeys: true,
17+
SpewKeys: true,
1718
DisableMethods: true,
1819
MaxDepth: spewMaxDepth,
1920
}
@@ -23,6 +24,7 @@ var (
2324
DisablePointerAddresses: true,
2425
DisableCapacities: true,
2526
SortKeys: true,
27+
SpewKeys: true,
2628
MaxDepth: spewMaxDepth,
2729
}
2830
)

0 commit comments

Comments
 (0)