github.com/ubuntu-core/snappy@v0.0.0-20210827154228-9e584df982bb/testutil/containschecker.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2015-2018 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package testutil
    21  
    22  import (
    23  	"fmt"
    24  	"reflect"
    25  	"strings"
    26  
    27  	"gopkg.in/check.v1"
    28  )
    29  
    30  type containsChecker struct {
    31  	*check.CheckerInfo
    32  }
    33  
    34  // Contains is a Checker that looks for a elem in a container.
    35  // The elem can be any object. The container can be an array, slice or string.
    36  var Contains check.Checker = &containsChecker{
    37  	&check.CheckerInfo{Name: "Contains", Params: []string{"container", "elem"}},
    38  }
    39  
    40  func commonEquals(container, elem interface{}, result *bool, error *string) bool {
    41  	containerV := reflect.ValueOf(container)
    42  	elemV := reflect.ValueOf(elem)
    43  	switch containerV.Kind() {
    44  	case reflect.Slice, reflect.Array, reflect.Map:
    45  		containerElemType := containerV.Type().Elem()
    46  		if containerElemType.Kind() == reflect.Interface {
    47  			// Ensure that element implements the type of elements stored in the container.
    48  			if !elemV.Type().Implements(containerElemType) {
    49  				*result = false
    50  				*error = fmt.Sprintf(""+
    51  					"container has items of interface type %s but expected"+
    52  					" element does not implement it", containerElemType)
    53  				return true
    54  			}
    55  		} else {
    56  			// Ensure that type of elements in container is compatible with elem
    57  			if containerElemType != elemV.Type() {
    58  				*result = false
    59  				*error = fmt.Sprintf(
    60  					"container has items of type %s but expected element is a %s",
    61  					containerElemType, elemV.Type())
    62  				return true
    63  			}
    64  		}
    65  	case reflect.String:
    66  		// When container is a string, we expect elem to be a string as well
    67  		if elemV.Kind() != reflect.String {
    68  			*result = false
    69  			*error = fmt.Sprintf("element is a %T but expected a string", elem)
    70  		} else {
    71  			*result = strings.Contains(containerV.String(), elemV.String())
    72  			*error = ""
    73  		}
    74  		return true
    75  	}
    76  	return false
    77  }
    78  
    79  func (c *containsChecker) Check(params []interface{}, names []string) (result bool, error string) {
    80  	defer func() {
    81  		if v := recover(); v != nil {
    82  			result = false
    83  			error = fmt.Sprint(v)
    84  		}
    85  	}()
    86  	var container interface{} = params[0]
    87  	var elem interface{} = params[1]
    88  	if commonEquals(container, elem, &result, &error) {
    89  		return result, error
    90  	}
    91  	// Do the actual test using ==
    92  	switch containerV := reflect.ValueOf(container); containerV.Kind() {
    93  	case reflect.Slice, reflect.Array:
    94  		for length, i := containerV.Len(), 0; i < length; i++ {
    95  			itemV := containerV.Index(i)
    96  			if itemV.Interface() == elem {
    97  				return true, ""
    98  			}
    99  		}
   100  		return false, ""
   101  	case reflect.Map:
   102  		for _, keyV := range containerV.MapKeys() {
   103  			itemV := containerV.MapIndex(keyV)
   104  			if itemV.Interface() == elem {
   105  				return true, ""
   106  			}
   107  		}
   108  		return false, ""
   109  	default:
   110  		return false, fmt.Sprintf("%T is not a supported container", container)
   111  	}
   112  }
   113  
   114  type deepContainsChecker struct {
   115  	*check.CheckerInfo
   116  }
   117  
   118  // DeepContains is a Checker that looks for a elem in a container using
   119  // DeepEqual. The elem can be any object. The container can be an array, slice
   120  // or string.
   121  var DeepContains check.Checker = &deepContainsChecker{
   122  	&check.CheckerInfo{Name: "DeepContains", Params: []string{"container", "elem"}},
   123  }
   124  
   125  func (c *deepContainsChecker) Check(params []interface{}, names []string) (result bool, error string) {
   126  	var container interface{} = params[0]
   127  	var elem interface{} = params[1]
   128  	if commonEquals(container, elem, &result, &error) {
   129  		return result, error
   130  	}
   131  	// Do the actual test using reflect.DeepEqual
   132  	switch containerV := reflect.ValueOf(container); containerV.Kind() {
   133  	case reflect.Slice, reflect.Array:
   134  		for length, i := containerV.Len(), 0; i < length; i++ {
   135  			itemV := containerV.Index(i)
   136  			if reflect.DeepEqual(itemV.Interface(), elem) {
   137  				return true, ""
   138  			}
   139  		}
   140  		return false, ""
   141  	case reflect.Map:
   142  		for _, keyV := range containerV.MapKeys() {
   143  			itemV := containerV.MapIndex(keyV)
   144  			if reflect.DeepEqual(itemV.Interface(), elem) {
   145  				return true, ""
   146  			}
   147  		}
   148  		return false, ""
   149  	default:
   150  		return false, fmt.Sprintf("%T is not a supported container", container)
   151  	}
   152  }
   153  
   154  type deepUnsortedMatchChecker struct {
   155  	*check.CheckerInfo
   156  }
   157  
   158  // DeepUnsortedMatches checks if two containers contain the same elements in
   159  // the same number (but possibly different order) using DeepEqual. The container
   160  // can be an array, a slice or a map.
   161  var DeepUnsortedMatches check.Checker = &deepUnsortedMatchChecker{
   162  	&check.CheckerInfo{Name: "DeepUnsortedMatches", Params: []string{"container1", "container2"}},
   163  }
   164  
   165  func (c *deepUnsortedMatchChecker) Check(params []interface{}, _ []string) (bool, string) {
   166  	container1 := reflect.ValueOf(params[0])
   167  	container2 := reflect.ValueOf(params[1])
   168  
   169  	// if both args are nil, return true
   170  	if container1.Kind() == reflect.Invalid && container2.Kind() == reflect.Invalid {
   171  		return true, ""
   172  	}
   173  
   174  	if container1.Kind() == reflect.Invalid || container2.Kind() == reflect.Invalid {
   175  		return false, "only one container was nil"
   176  	}
   177  
   178  	if container1.Kind() != container2.Kind() {
   179  		return false, fmt.Sprintf("containers are of different types: %s != %s", container1.Kind(), container2.Kind())
   180  	}
   181  
   182  	if container1.Kind() != reflect.Map && container1.Kind() != reflect.Slice && container1.Kind() != reflect.Array {
   183  		return false, fmt.Sprintf("'%s' is not a supported type: must be slice, array, map or nil", container1.Kind().String())
   184  	}
   185  
   186  	if container1.Type().Comparable() && params[0] == params[1] {
   187  		return true, ""
   188  	}
   189  
   190  	if container1.Len() != container2.Len() {
   191  		return false, fmt.Sprintf("containers have different lengths: %d != %d", container1.Len(), container2.Len())
   192  	}
   193  
   194  	switch container1.Kind() {
   195  	case reflect.Array, reflect.Slice:
   196  		return deepSequenceMatch(container1, container2)
   197  
   198  	case reflect.Map:
   199  		map1 := container1.Interface()
   200  		map2 := container2.Interface()
   201  		if !reflect.DeepEqual(map1, map2) {
   202  			return false, "maps don't match"
   203  		}
   204  
   205  		return true, ""
   206  
   207  	default:
   208  		return false, fmt.Sprintf("%T is not a supported container. Must be a slice, an array or a map", container1)
   209  	}
   210  }
   211  
   212  func deepSequenceMatch(container1 reflect.Value, container2 reflect.Value) (bool, string) {
   213  	matched := make([]bool, container1.Len())
   214  
   215  out:
   216  	for i := 0; i < container1.Len(); i++ {
   217  		el1 := container1.Index(i).Interface()
   218  
   219  		for e := 0; e < container2.Len(); e++ {
   220  			el2 := container2.Index(e).Interface()
   221  
   222  			if !matched[e] && reflect.DeepEqual(el1, el2) {
   223  				// mark already matched elements, so that duplicate elements in
   224  				// one container can't be matched to the same element in the other.
   225  				matched[e] = true
   226  				continue out
   227  			}
   228  		}
   229  
   230  		return false, fmt.Sprintf("element [%d]=%s was unmatched in the second container", i, el1)
   231  	}
   232  
   233  	return true, ""
   234  }