@@ -16,7 +16,7 @@ import (
1616//
1717// Len also fails if the object has a type that len() does not accept.
1818//
19- // The asserted object can be a string, a slice, a map, an array or a channel.
19+ // The asserted object can be a string, a slice, a map, an array, pointer to array or a channel.
2020//
2121// See also [reflect.Len].
2222//
@@ -301,12 +301,16 @@ func MapNotContainsT[Map ~map[K]V, K comparable, V any](t T, m Map, key K, msgAn
301301 return true
302302}
303303
304+ const unsupportedCollectionType = "%q has an unsupported type %s"
305+
304306// Subset asserts that the list (array, slice, or map) contains all elements
305307// given in the subset (array, slice, or map).
306308//
307309// Map elements are key-value pairs unless compared with an array or slice where
308310// only the map key is evaluated.
309311//
312+ // nil values are considered as empty sets.
313+ //
310314// # Usage
311315//
312316// assertions.Subset(t, []int{1, 2, 3}, []int{1, 2})
@@ -324,37 +328,46 @@ func Subset(t T, list, subset any, msgAndArgs ...any) (ok bool) {
324328 h .Helper ()
325329 }
326330
327- if subset == nil {
328- return true // we consider nil to be equal to the nil set
329- }
331+ subsetType := reflect .TypeOf (subset )
332+ listType := reflect .TypeOf (list )
330333
331- listKind := reflect .TypeOf (list ).Kind ()
332- if listKind != reflect .Array && listKind != reflect .Slice && listKind != reflect .Map {
333- return Fail (t , fmt .Sprintf ("%q has an unsupported type %s" , list , listKind ), msgAndArgs ... )
334+ if subsetType == nil {
335+ if listType == nil { // ∅ ⊂ ∅
336+ return true // we consider nil to be equal to the nil set
337+ }
338+
339+ listKind := listType .Kind ()
340+ if listKind != reflect .Array && listKind != reflect .Slice && listKind != reflect .Map {
341+ return Fail (t , fmt .Sprintf (unsupportedCollectionType , list , listKind ), msgAndArgs ... )
342+ }
343+
344+ return true
334345 }
335346
336- subsetKind := reflect . TypeOf ( subset ) .Kind ()
347+ subsetKind := subsetType .Kind ()
337348 if subsetKind != reflect .Array && subsetKind != reflect .Slice && subsetKind != reflect .Map {
338- return Fail (t , fmt .Sprintf ("%q has an unsupported type %s" , subset , subsetKind ), msgAndArgs ... )
349+ return Fail (t , fmt .Sprintf (unsupportedCollectionType , subset , subsetKind ), msgAndArgs ... )
350+ }
351+
352+ if listType == nil {
353+ subsetList := reflect .ValueOf (subset )
354+ if subsetList .Len () == 0 {
355+ return true
356+ }
357+
358+ return Fail (t , fmt .Sprintf ("%q is not a subset of the empty set" , subset ), msgAndArgs ... )
359+ }
360+
361+ listKind := listType .Kind ()
362+ if listKind != reflect .Array && listKind != reflect .Slice && listKind != reflect .Map {
363+ return Fail (t , fmt .Sprintf (unsupportedCollectionType , list , listKind ), msgAndArgs ... )
339364 }
340365
341366 if subsetKind == reflect .Map && listKind == reflect .Map {
342367 subsetMap := reflect .ValueOf (subset )
343368 actualMap := reflect .ValueOf (list )
344369
345- for _ , k := range subsetMap .MapKeys () {
346- ev := subsetMap .MapIndex (k )
347- av := actualMap .MapIndex (k )
348-
349- if ! av .IsValid () {
350- return Fail (t , fmt .Sprintf ("%s does not contain %s" , truncatingFormat ("%#v" , list ), truncatingFormat ("%#v" , subset )), msgAndArgs ... )
351- }
352- if ! ObjectsAreEqual (ev .Interface (), av .Interface ()) {
353- return Fail (t , fmt .Sprintf ("%s does not contain %s" , truncatingFormat ("%#v" , list ), truncatingFormat ("%#v" , subset )), msgAndArgs ... )
354- }
355- }
356-
357- return true
370+ return isSubsetMap (t , list , subset , subsetMap , actualMap , msgAndArgs ... )
358371 }
359372
360373 subsetList := reflect .ValueOf (subset )
@@ -366,18 +379,7 @@ func Subset(t T, list, subset any, msgAndArgs ...any) (ok bool) {
366379 subsetList = reflect .ValueOf (keys )
367380 }
368381
369- for i := range subsetList .Len () {
370- element := subsetList .Index (i ).Interface ()
371- ok , found := containsElement (list , element )
372- if ! ok {
373- return Fail (t , fmt .Sprintf ("%#v could not be applied builtin len()" , list ), msgAndArgs ... )
374- }
375- if ! found {
376- return Fail (t , fmt .Sprintf ("%s does not contain %#v" , truncatingFormat ("%#v" , list ), element ), msgAndArgs ... )
377- }
378- }
379-
380- return true
382+ return isSubsetList (t , list , subsetList , msgAndArgs ... )
381383}
382384
383385// SliceSubsetT asserts that a slice of comparable elements contains all the elements given in the subset.
@@ -426,37 +428,44 @@ func NotSubset(t T, list, subset any, msgAndArgs ...any) (ok bool) {
426428 if h , ok := t .(H ); ok {
427429 h .Helper ()
428430 }
429- if subset == nil {
430- return Fail (t , "nil is the empty set which is a subset of every set" , msgAndArgs ... )
431+ const emptySetMessage = "nil is the empty set which is a subset of every set"
432+
433+ subsetType := reflect .TypeOf (subset )
434+ listType := reflect .TypeOf (list )
435+
436+ if subsetType == nil {
437+ return Fail (t , emptySetMessage , msgAndArgs ... )
431438 }
432439
433- listKind := reflect .TypeOf (list ).Kind ()
440+ if listType == nil {
441+ subsetKind := subsetType .Kind ()
442+ if subsetKind != reflect .Array && subsetKind != reflect .Slice && subsetKind != reflect .Map {
443+ return Fail (t , fmt .Sprintf (unsupportedCollectionType , subset , subsetKind ), msgAndArgs ... )
444+ }
445+
446+ subsetList := reflect .ValueOf (subset )
447+ if subsetList .Len () != 0 {
448+ return true
449+ }
450+
451+ return Fail (t , emptySetMessage , msgAndArgs ... )
452+ }
453+
454+ listKind := listType .Kind ()
434455 if listKind != reflect .Array && listKind != reflect .Slice && listKind != reflect .Map {
435- return Fail (t , fmt .Sprintf ("%q has an unsupported type %s" , list , listKind ), msgAndArgs ... )
456+ return Fail (t , fmt .Sprintf (unsupportedCollectionType , list , listKind ), msgAndArgs ... )
436457 }
437458
438- subsetKind := reflect . TypeOf ( subset ) .Kind ()
459+ subsetKind := subsetType .Kind ()
439460 if subsetKind != reflect .Array && subsetKind != reflect .Slice && subsetKind != reflect .Map {
440- return Fail (t , fmt .Sprintf ("%q has an unsupported type %s" , subset , subsetKind ), msgAndArgs ... )
461+ return Fail (t , fmt .Sprintf (unsupportedCollectionType , subset , subsetKind ), msgAndArgs ... )
441462 }
442463
443464 if subsetKind == reflect .Map && listKind == reflect .Map {
444465 subsetMap := reflect .ValueOf (subset )
445466 actualMap := reflect .ValueOf (list )
446467
447- for _ , k := range subsetMap .MapKeys () {
448- ev := subsetMap .MapIndex (k )
449- av := actualMap .MapIndex (k )
450-
451- if ! av .IsValid () {
452- return true
453- }
454- if ! ObjectsAreEqual (ev .Interface (), av .Interface ()) {
455- return true
456- }
457- }
458-
459- return Fail (t , fmt .Sprintf ("%s is a subset of %s" , truncatingFormat ("%q" , subset ), truncatingFormat ("%q" , list )), msgAndArgs ... )
468+ return isNotSubsetMap (t , list , subset , subsetMap , actualMap , msgAndArgs ... )
460469 }
461470
462471 subsetList := reflect .ValueOf (subset )
@@ -468,18 +477,7 @@ func NotSubset(t T, list, subset any, msgAndArgs ...any) (ok bool) {
468477 subsetList = reflect .ValueOf (keys )
469478 }
470479
471- for i := range subsetList .Len () {
472- element := subsetList .Index (i ).Interface ()
473- ok , found := containsElement (list , element )
474- if ! ok {
475- return Fail (t , fmt .Sprintf ("%q could not be applied builtin len()" , list ), msgAndArgs ... )
476- }
477- if ! found {
478- return true
479- }
480- }
481-
482- return Fail (t , fmt .Sprintf ("%s is a subset of %s" , truncatingFormat ("%q" , subset ), truncatingFormat ("%q" , list )), msgAndArgs ... )
480+ return isNotSubsetList (t , list , subset , subsetList , msgAndArgs ... )
483481}
484482
485483// SliceNotSubsetT asserts that a slice of comparable elements does not contain all the elements given in the subset.
@@ -649,6 +647,63 @@ func NotElementsMatchT[E comparable](t T, listA, listB []E, msgAndArgs ...any) (
649647 return true
650648}
651649
650+ func isSubsetMap (t T , list , subset any , subsetMap , actualMap reflect.Value , msgAndArgs ... any ) bool {
651+ for _ , k := range subsetMap .MapKeys () {
652+ ev := subsetMap .MapIndex (k )
653+ av := actualMap .MapIndex (k )
654+
655+ if ! av .IsValid () {
656+ return Fail (t , fmt .Sprintf ("%s does not contain %s" , truncatingFormat ("%#v" , list ), truncatingFormat ("%#v" , subset )), msgAndArgs ... )
657+ }
658+ if ! ObjectsAreEqual (ev .Interface (), av .Interface ()) {
659+ return Fail (t , fmt .Sprintf ("%s does not contain %s" , truncatingFormat ("%#v" , list ), truncatingFormat ("%#v" , subset )), msgAndArgs ... )
660+ }
661+ }
662+
663+ return true
664+ }
665+
666+ func isNotSubsetMap (t T , list , subset any , subsetMap , actualMap reflect.Value , msgAndArgs ... any ) bool {
667+ for _ , k := range subsetMap .MapKeys () {
668+ ev := subsetMap .MapIndex (k )
669+ av := actualMap .MapIndex (k )
670+
671+ if ! av .IsValid () {
672+ return true
673+ }
674+
675+ if ! ObjectsAreEqual (ev .Interface (), av .Interface ()) {
676+ return true
677+ }
678+ }
679+
680+ return Fail (t , fmt .Sprintf ("%s is a subset of %s" , truncatingFormat ("%q" , subset ), truncatingFormat ("%q" , list )), msgAndArgs ... )
681+ }
682+
683+ func isSubsetList (t T , list any , subsetList reflect.Value , msgAndArgs ... any ) bool {
684+ for i := range subsetList .Len () {
685+ element := subsetList .Index (i ).Interface ()
686+ _ , found := containsElement (list , element ) // containsElement will work for this type: no need to check the ok bool
687+ if ! found {
688+ return Fail (t , fmt .Sprintf ("%s does not contain %#v" , truncatingFormat ("%#v" , list ), element ), msgAndArgs ... )
689+ }
690+ }
691+
692+ return true
693+ }
694+
695+ func isNotSubsetList (t T , list , subset any , subsetList reflect.Value , msgAndArgs ... any ) bool {
696+ for i := range subsetList .Len () {
697+ element := subsetList .Index (i ).Interface ()
698+ _ , found := containsElement (list , element )
699+ if ! found {
700+ return true
701+ }
702+ }
703+
704+ return Fail (t , fmt .Sprintf ("%s is a subset of %s" , truncatingFormat ("%q" , subset ), truncatingFormat ("%q" , list )), msgAndArgs ... )
705+ }
706+
652707// containsElement tries to loop over the list check if the list includes the element.
653708//
654709// return (false, false) if impossible.
0 commit comments