Skip to content

Commit 71e6386

Browse files
committed
feat(Map,SubMapOf,SuperMapOf): expectedEntries now optional or multiple
If multiple, all maps are merged from left to right. Signed-off-by: Maxime Soulé <btik-git@scoubidou.com>
1 parent 7783296 commit 71e6386

File tree

6 files changed

+111
-23
lines changed

6 files changed

+111
-23
lines changed

td/cmp_funcs.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,10 @@ func CmpLte(t TestingT, got, maxExpectedValue any, args ...any) bool {
685685
//
686686
// See [Map] for details.
687687
//
688+
// [Map] optional parameter expectedEntries is here mandatory.
689+
// nil value should be passed to mimic its absence in
690+
// original [Map] call.
691+
//
688692
// Returns true if the test is OK, false if it fails.
689693
//
690694
// If t is a [*T] then its Config field is inherited.
@@ -1297,6 +1301,10 @@ func CmpSubJSONOf(t TestingT, got, expectedJSON any, params []any, args ...any)
12971301
//
12981302
// See [SubMapOf] for details.
12991303
//
1304+
// [SubMapOf] optional parameter expectedEntries is here mandatory.
1305+
// nil value should be passed to mimic its absence in
1306+
// original [SubMapOf] call.
1307+
//
13001308
// Returns true if the test is OK, false if it fails.
13011309
//
13021310
// If t is a [*T] then its Config field is inherited.
@@ -1381,6 +1389,10 @@ func CmpSuperJSONOf(t TestingT, got, expectedJSON any, params []any, args ...any
13811389
//
13821390
// See [SuperMapOf] for details.
13831391
//
1392+
// [SuperMapOf] optional parameter expectedEntries is here mandatory.
1393+
// nil value should be passed to mimic its absence in
1394+
// original [SuperMapOf] call.
1395+
//
13841396
// Returns true if the test is OK, false if it fails.
13851397
//
13861398
// If t is a [*T] then its Config field is inherited.

td/t.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,10 @@ func (t *T) Lte(got, maxExpectedValue any, args ...any) bool {
554554
//
555555
// See [Map] for details.
556556
//
557+
// [Map] optional parameter expectedEntries is here mandatory.
558+
// nil value should be passed to mimic its absence in
559+
// original [Map] call.
560+
//
557561
// Returns true if the test is OK, false if it fails.
558562
//
559563
// args... are optional and allow to name the test. This name is
@@ -1110,6 +1114,10 @@ func (t *T) SubJSONOf(got, expectedJSON any, params []any, args ...any) bool {
11101114
//
11111115
// See [SubMapOf] for details.
11121116
//
1117+
// [SubMapOf] optional parameter expectedEntries is here mandatory.
1118+
// nil value should be passed to mimic its absence in
1119+
// original [SubMapOf] call.
1120+
//
11131121
// Returns true if the test is OK, false if it fails.
11141122
//
11151123
// args... are optional and allow to name the test. This name is
@@ -1186,6 +1194,10 @@ func (t *T) SuperJSONOf(got, expectedJSON any, params []any, args ...any) bool {
11861194
//
11871195
// See [SuperMapOf] for details.
11881196
//
1197+
// [SuperMapOf] optional parameter expectedEntries is here mandatory.
1198+
// nil value should be passed to mimic its absence in
1199+
// original [SuperMapOf] call.
1200+
//
11891201
// Returns true if the test is OK, false if it fails.
11901202
//
11911203
// args... are optional and allow to name the test. This name is

td/td_json.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,6 @@ func (u tdJSONUnmarshaler) resolveOp() func(json.Operator, json.Position) (any,
185185

186186
// Special cases
187187
var min, max int
188-
addNilParam := false
189188
switch jop.Name {
190189
case "Between":
191190
min, max = 2, 3
@@ -221,7 +220,7 @@ func (u tdJSONUnmarshaler) resolveOp() func(json.Operator, json.Position) (any,
221220
case "Sorted":
222221
min, max = 0, -1
223222
case "SubMapOf", "SuperMapOf":
224-
min, max, addNilParam = 1, 1, true
223+
min, max = 1, 1
225224
default:
226225
min = tfn.NumIn()
227226
if tfn.IsVariadic() {
@@ -253,9 +252,6 @@ func (u tdJSONUnmarshaler) resolveOp() func(json.Operator, json.Position) (any,
253252
for i, p := range jop.Params {
254253
in[i] = reflect.ValueOf(p)
255254
}
256-
if addNilParam {
257-
in = append(in, reflect.ValueOf(MapEntries(nil)))
258-
}
259255

260256
// If the function is variadic, no need to check each param as all
261257
// variadic operators have always a ...any

td/td_map.go

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,24 @@ type mapEntryInfo struct {
4444
// value (which can be a [TestDeep] operator as well as a zero value.)
4545
type MapEntries map[any]any
4646

47-
func newMap(model any, entries MapEntries, kind mapKind) *tdMap {
47+
func mergeMapEntries(mes ...MapEntries) MapEntries {
48+
switch len(mes) {
49+
case 0:
50+
return nil
51+
case 1:
52+
return mes[0]
53+
}
54+
55+
ret := make(MapEntries, len(mes[0]))
56+
for _, me := range mes {
57+
for k, v := range me {
58+
ret[k] = v
59+
}
60+
}
61+
return ret
62+
}
63+
64+
func newMap(model any, kind mapKind, mes ...MapEntries) *tdMap {
4865
vmodel := reflect.ValueOf(model)
4966

5067
m := tdMap{
@@ -64,7 +81,7 @@ func newMap(model any, entries MapEntries, kind mapKind) *tdMap {
6481

6582
if vmodel.IsNil() {
6683
m.expectedType = vmodel.Type().Elem()
67-
m.populateExpectedEntries(entries, reflect.Value{})
84+
m.populateExpectedEntries(mergeMapEntries(mes...), reflect.Value{})
6885
return &m
6986
}
7087

@@ -73,7 +90,7 @@ func newMap(model any, entries MapEntries, kind mapKind) *tdMap {
7390

7491
case reflect.Map:
7592
m.expectedType = vmodel.Type()
76-
m.populateExpectedEntries(entries, vmodel)
93+
m.populateExpectedEntries(mergeMapEntries(mes...), vmodel)
7794
return &m
7895
}
7996

@@ -160,8 +177,9 @@ func (m *tdMap) populateExpectedEntries(entries MapEntries, expectedModel reflec
160177
//
161178
// model must be the same type as compared data.
162179
//
163-
// expectedEntries can be nil, if no zero entries are expected and
164-
// no [TestDeep] operators are involved.
180+
// expectedEntries can be omitted, if no [TestDeep] operators are
181+
// involved. If expectedEntries contains more than one item, all items
182+
// are merged before their use, from left to right.
165183
//
166184
// During a match, all expected entries must be found and all data
167185
// entries must be expected to succeed.
@@ -184,8 +202,8 @@ func (m *tdMap) populateExpectedEntries(entries MapEntries, expectedModel reflec
184202
// TypeBehind method returns the [reflect.Type] of model.
185203
//
186204
// See also [SubMapOf] and [SuperMapOf].
187-
func Map(model any, expectedEntries MapEntries) TestDeep {
188-
return newMap(model, expectedEntries, allMap)
205+
func Map(model any, expectedEntries ...MapEntries) TestDeep {
206+
return newMap(model, allMap, expectedEntries...)
189207
}
190208

191209
// summary(SubMapOf): compares the contents of a map but with
@@ -197,8 +215,9 @@ func Map(model any, expectedEntries MapEntries) TestDeep {
197215
//
198216
// model must be the same type as compared data.
199217
//
200-
// expectedEntries can be nil, if no zero entries are expected and
201-
// no [TestDeep] operators are involved.
218+
// expectedEntries can be omitted, if no [TestDeep] operators are
219+
// involved. If expectedEntries contains more than one item, all items
220+
// are merged before their use, from left to right.
202221
//
203222
// During a match, each map entry should be matched by an expected
204223
// entry to succeed. But some expected entries can be missing from the
@@ -230,8 +249,8 @@ func Map(model any, expectedEntries MapEntries) TestDeep {
230249
// TypeBehind method returns the [reflect.Type] of model.
231250
//
232251
// See also [Map] and [SuperMapOf].
233-
func SubMapOf(model any, expectedEntries MapEntries) TestDeep {
234-
return newMap(model, expectedEntries, subMap)
252+
func SubMapOf(model any, expectedEntries ...MapEntries) TestDeep {
253+
return newMap(model, subMap, expectedEntries...)
235254
}
236255

237256
// summary(SuperMapOf): compares the contents of a map but with
@@ -243,8 +262,9 @@ func SubMapOf(model any, expectedEntries MapEntries) TestDeep {
243262
//
244263
// model must be the same type as compared data.
245264
//
246-
// expectedEntries can be nil, if no zero entries are expected and
247-
// no [TestDeep] operators are involved.
265+
// expectedEntries can be omitted, if no [TestDeep] operators are
266+
// involved. If expectedEntries contains more than one item, all items
267+
// are merged before their use, from left to right.
248268
//
249269
// During a match, each expected entry should match in the compared
250270
// map. But some entries in the compared map may not be expected.
@@ -275,8 +295,8 @@ func SubMapOf(model any, expectedEntries MapEntries) TestDeep {
275295
// TypeBehind method returns the [reflect.Type] of model.
276296
//
277297
// See also [SuperMapOf] and [SubMapOf].
278-
func SuperMapOf(model any, expectedEntries MapEntries) TestDeep {
279-
return newMap(model, expectedEntries, superMap)
298+
func SuperMapOf(model any, expectedEntries ...MapEntries) TestDeep {
299+
return newMap(model, superMap, expectedEntries...)
280300
}
281301

282302
func (m *tdMap) Match(ctx ctxerr.Context, got reflect.Value) (err *ctxerr.Error) {

td/td_map_test.go

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ func TestMap(t *testing.T) {
3030

3131
gotMap := map[string]int{"foo": 1, "bar": 2}
3232

33+
checkOK(t, gotMap, td.Map(map[string]int{"foo": 1, "bar": 2}))
3334
checkOK(t, gotMap, td.Map(map[string]int{"foo": 1, "bar": 2}, nil))
3435
checkOK(t, gotMap,
3536
td.Map(map[string]int{"foo": 1}, td.MapEntries{"bar": 2}))
@@ -38,6 +39,13 @@ func TestMap(t *testing.T) {
3839
checkOK(t, gotMap,
3940
td.Map((map[string]int)(nil), td.MapEntries{"foo": 1, "bar": 2}))
4041

42+
checkOK(t, gotMap,
43+
td.Map((map[string]int)(nil),
44+
td.MapEntries{"foo": 66, "bar": 0},
45+
td.MapEntries{"foo": 42, "bar": 1},
46+
td.MapEntries{"foo": 1, "bar": 2},
47+
))
48+
4149
one := 1
4250
checkOK(t, map[string]*int{"foo": nil, "bar": &one},
4351
td.Map(map[string]*int{}, td.MapEntries{"foo": nil, "bar": &one}))
@@ -235,12 +243,29 @@ func TestMap(t *testing.T) {
235243

236244
//
237245
// SuperMapOf
246+
checkOK(t, gotMap, td.SuperMapOf(map[string]int{"foo": 1}))
238247
checkOK(t, gotMap, td.SuperMapOf(map[string]int{"foo": 1}, nil))
239248
checkOK(t, gotMap,
240249
td.SuperMapOf(map[string]int{"foo": 1}, td.MapEntries{"bar": 2}))
241250
checkOK(t, gotMap,
242251
td.SuperMapOf(map[string]int{}, td.MapEntries{"foo": 1, "bar": 2}))
243252

253+
checkOK(t, gotMap,
254+
td.SuperMapOf(map[string]int{"foo": 1},
255+
td.MapEntries{"bar": 0},
256+
td.MapEntries{"bar": 1},
257+
td.MapEntries{"bar": 2},
258+
))
259+
260+
checkError(t, gotMap,
261+
td.SuperMapOf(map[string]int{"foo": 1, "bar": 3}),
262+
expectedError{
263+
Message: mustBe("values differ"),
264+
Path: mustBe(`DATA["bar"]`),
265+
Got: mustBe("2"),
266+
Expected: mustBe("3"),
267+
})
268+
244269
checkError(t, gotMap,
245270
td.SuperMapOf(map[string]int{"foo": 1, "bar": 3}, nil),
246271
expectedError{
@@ -250,7 +275,7 @@ func TestMap(t *testing.T) {
250275
Expected: mustBe("3"),
251276
})
252277

253-
checkError(t, gotMap, td.SuperMapOf(map[string]int{"test": 2}, nil),
278+
checkError(t, gotMap, td.SuperMapOf(map[string]int{"test": 2}),
254279
expectedError{
255280
Message: mustBe("comparing hash keys of %%"),
256281
Path: mustBe("DATA"),
@@ -270,13 +295,31 @@ func TestMap(t *testing.T) {
270295

271296
//
272297
// SubMapOf
298+
checkOK(t, gotMap,
299+
td.SubMapOf(map[string]int{"foo": 1, "bar": 2, "tst": 3}))
273300
checkOK(t, gotMap,
274301
td.SubMapOf(map[string]int{"foo": 1, "bar": 2, "tst": 3}, nil))
275302
checkOK(t, gotMap,
276303
td.SubMapOf(map[string]int{"foo": 1, "tst": 3}, td.MapEntries{"bar": 2}))
277304
checkOK(t, gotMap,
278305
td.SubMapOf(map[string]int{}, td.MapEntries{"foo": 1, "bar": 2, "tst": 3}))
279306

307+
checkOK(t, gotMap,
308+
td.SubMapOf(map[string]int{"foo": 1, "tst": 3},
309+
td.MapEntries{"bar": 0},
310+
td.MapEntries{"bar": 1},
311+
td.MapEntries{"bar": 2},
312+
))
313+
314+
checkError(t, gotMap,
315+
td.SubMapOf(map[string]int{"foo": 1, "bar": 3}),
316+
expectedError{
317+
Message: mustBe("values differ"),
318+
Path: mustBe(`DATA["bar"]`),
319+
Got: mustBe("2"),
320+
Expected: mustBe("3"),
321+
})
322+
280323
checkError(t, gotMap,
281324
td.SubMapOf(map[string]int{"foo": 1, "bar": 3}, nil),
282325
expectedError{
@@ -286,7 +329,7 @@ func TestMap(t *testing.T) {
286329
Expected: mustBe("3"),
287330
})
288331

289-
checkError(t, gotMap, td.SubMapOf(map[string]int{"foo": 1}, nil),
332+
checkError(t, gotMap, td.SubMapOf(map[string]int{"foo": 1}),
290333
expectedError{
291334
Message: mustBe("comparing hash keys of %%"),
292335
Path: mustBe("DATA"),

tools/gen_funcs.pl

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,12 @@
107107
# These operators accept several StructFields,
108108
# but we want only one here
109109
Struct => 'nil',
110-
SStruct => 'nil');
110+
SStruct => 'nil',
111+
# These operators accept several MapEntries,
112+
# but we want only one here
113+
Map => 'nil',
114+
SubMapOf => 'nil',
115+
SuperMapOf => 'nil');
111116

112117
# Smuggler operators (automatically filled)
113118
my %SMUGGLER_OPERATORS;

0 commit comments

Comments
 (0)