...

Source file src/github.com/onsi/gomega/gstruct/elements.go

Documentation: github.com/onsi/gomega/gstruct

     1  // untested sections: 6
     2  
     3  package gstruct
     4  
     5  import (
     6  	"errors"
     7  	"fmt"
     8  	"reflect"
     9  	"runtime/debug"
    10  	"strconv"
    11  
    12  	"github.com/onsi/gomega/format"
    13  	errorsutil "github.com/onsi/gomega/gstruct/errors"
    14  	"github.com/onsi/gomega/types"
    15  )
    16  
    17  //MatchAllElements succeeds if every element of a slice matches the element matcher it maps to
    18  //through the id function, and every element matcher is matched.
    19  //    idFn := func(element interface{}) string {
    20  //        return fmt.Sprintf("%v", element)
    21  //    }
    22  //
    23  //    Expect([]string{"a", "b"}).To(MatchAllElements(idFn, Elements{
    24  //        "a": Equal("a"),
    25  //        "b": Equal("b"),
    26  //    }))
    27  func MatchAllElements(identifier Identifier, elements Elements) types.GomegaMatcher {
    28  	return &ElementsMatcher{
    29  		Identifier: identifier,
    30  		Elements:   elements,
    31  	}
    32  }
    33  
    34  //MatchAllElementsWithIndex succeeds if every element of a slice matches the element matcher it maps to
    35  //through the id with index function, and every element matcher is matched.
    36  //    idFn := func(index int, element interface{}) string {
    37  //        return strconv.Itoa(index)
    38  //    }
    39  //
    40  //    Expect([]string{"a", "b"}).To(MatchAllElements(idFn, Elements{
    41  //        "0": Equal("a"),
    42  //        "1": Equal("b"),
    43  //    }))
    44  func MatchAllElementsWithIndex(identifier IdentifierWithIndex, elements Elements) types.GomegaMatcher {
    45  	return &ElementsMatcher{
    46  		Identifier: identifier,
    47  		Elements:   elements,
    48  	}
    49  }
    50  
    51  //MatchElements succeeds if each element of a slice matches the element matcher it maps to
    52  //through the id function. It can ignore extra elements and/or missing elements.
    53  //    idFn := func(element interface{}) string {
    54  //        return fmt.Sprintf("%v", element)
    55  //    }
    56  //
    57  //    Expect([]string{"a", "b", "c"}).To(MatchElements(idFn, IgnoreExtras, Elements{
    58  //        "a": Equal("a"),
    59  //        "b": Equal("b"),
    60  //    }))
    61  //    Expect([]string{"a", "c"}).To(MatchElements(idFn, IgnoreMissing, Elements{
    62  //        "a": Equal("a"),
    63  //        "b": Equal("b"),
    64  //        "c": Equal("c"),
    65  //        "d": Equal("d"),
    66  //    }))
    67  func MatchElements(identifier Identifier, options Options, elements Elements) types.GomegaMatcher {
    68  	return &ElementsMatcher{
    69  		Identifier:      identifier,
    70  		Elements:        elements,
    71  		IgnoreExtras:    options&IgnoreExtras != 0,
    72  		IgnoreMissing:   options&IgnoreMissing != 0,
    73  		AllowDuplicates: options&AllowDuplicates != 0,
    74  	}
    75  }
    76  
    77  //MatchElementsWithIndex succeeds if each element of a slice matches the element matcher it maps to
    78  //through the id with index function. It can ignore extra elements and/or missing elements.
    79  //    idFn := func(index int, element interface{}) string {
    80  //        return strconv.Itoa(index)
    81  //    }
    82  //
    83  //    Expect([]string{"a", "b", "c"}).To(MatchElements(idFn, IgnoreExtras, Elements{
    84  //        "0": Equal("a"),
    85  //        "1": Equal("b"),
    86  //    }))
    87  //    Expect([]string{"a", "c"}).To(MatchElements(idFn, IgnoreMissing, Elements{
    88  //        "0": Equal("a"),
    89  //        "1": Equal("b"),
    90  //        "2": Equal("c"),
    91  //        "3": Equal("d"),
    92  //    }))
    93  func MatchElementsWithIndex(identifier IdentifierWithIndex, options Options, elements Elements) types.GomegaMatcher {
    94  	return &ElementsMatcher{
    95  		Identifier:      identifier,
    96  		Elements:        elements,
    97  		IgnoreExtras:    options&IgnoreExtras != 0,
    98  		IgnoreMissing:   options&IgnoreMissing != 0,
    99  		AllowDuplicates: options&AllowDuplicates != 0,
   100  	}
   101  }
   102  
   103  // ElementsMatcher is a NestingMatcher that applies custom matchers to each element of a slice mapped
   104  // by the Identifier function.
   105  // TODO: Extend this to work with arrays & maps (map the key) as well.
   106  type ElementsMatcher struct {
   107  	// Matchers for each element.
   108  	Elements Elements
   109  	// Function mapping an element to the string key identifying its matcher.
   110  	Identifier Identify
   111  
   112  	// Whether to ignore extra elements or consider it an error.
   113  	IgnoreExtras bool
   114  	// Whether to ignore missing elements or consider it an error.
   115  	IgnoreMissing bool
   116  	// Whether to key duplicates when matching IDs.
   117  	AllowDuplicates bool
   118  
   119  	// State.
   120  	failures []error
   121  }
   122  
   123  // Element ID to matcher.
   124  type Elements map[string]types.GomegaMatcher
   125  
   126  // Function for identifying (mapping) elements.
   127  type Identifier func(element interface{}) string
   128  
   129  // Calls the underlying fucntion with the provided params.
   130  // Identifier drops the index.
   131  func (i Identifier) WithIndexAndElement(index int, element interface{}) string {
   132  	return i(element)
   133  }
   134  
   135  // Uses the index and element to generate an element name
   136  type IdentifierWithIndex func(index int, element interface{}) string
   137  
   138  // Calls the underlying fucntion with the provided params.
   139  // IdentifierWithIndex uses the index.
   140  func (i IdentifierWithIndex) WithIndexAndElement(index int, element interface{}) string {
   141  	return i(index, element)
   142  }
   143  
   144  // Interface for identifing the element
   145  type Identify interface {
   146  	WithIndexAndElement(i int, element interface{}) string
   147  }
   148  
   149  // IndexIdentity is a helper function for using an index as
   150  // the key in the element map
   151  func IndexIdentity(index int, _ interface{}) string {
   152  	return strconv.Itoa(index)
   153  }
   154  
   155  func (m *ElementsMatcher) Match(actual interface{}) (success bool, err error) {
   156  	if reflect.TypeOf(actual).Kind() != reflect.Slice {
   157  		return false, fmt.Errorf("%v is type %T, expected slice", actual, actual)
   158  	}
   159  
   160  	m.failures = m.matchElements(actual)
   161  	if len(m.failures) > 0 {
   162  		return false, nil
   163  	}
   164  	return true, nil
   165  }
   166  
   167  func (m *ElementsMatcher) matchElements(actual interface{}) (errs []error) {
   168  	// Provide more useful error messages in the case of a panic.
   169  	defer func() {
   170  		if err := recover(); err != nil {
   171  			errs = append(errs, fmt.Errorf("panic checking %+v: %v\n%s", actual, err, debug.Stack()))
   172  		}
   173  	}()
   174  
   175  	val := reflect.ValueOf(actual)
   176  	elements := map[string]bool{}
   177  	for i := 0; i < val.Len(); i++ {
   178  		element := val.Index(i).Interface()
   179  		id := m.Identifier.WithIndexAndElement(i, element)
   180  		if elements[id] {
   181  			if !m.AllowDuplicates {
   182  				errs = append(errs, fmt.Errorf("found duplicate element ID %s", id))
   183  				continue
   184  			}
   185  		}
   186  		elements[id] = true
   187  
   188  		matcher, expected := m.Elements[id]
   189  		if !expected {
   190  			if !m.IgnoreExtras {
   191  				errs = append(errs, fmt.Errorf("unexpected element %s", id))
   192  			}
   193  			continue
   194  		}
   195  
   196  		match, err := matcher.Match(element)
   197  		if match {
   198  			continue
   199  		}
   200  
   201  		if err == nil {
   202  			if nesting, ok := matcher.(errorsutil.NestingMatcher); ok {
   203  				err = errorsutil.AggregateError(nesting.Failures())
   204  			} else {
   205  				err = errors.New(matcher.FailureMessage(element))
   206  			}
   207  		}
   208  		errs = append(errs, errorsutil.Nest(fmt.Sprintf("[%s]", id), err))
   209  	}
   210  
   211  	for id := range m.Elements {
   212  		if !elements[id] && !m.IgnoreMissing {
   213  			errs = append(errs, fmt.Errorf("missing expected element %s", id))
   214  		}
   215  	}
   216  
   217  	return errs
   218  }
   219  
   220  func (m *ElementsMatcher) FailureMessage(actual interface{}) (message string) {
   221  	failure := errorsutil.AggregateError(m.failures)
   222  	return format.Message(actual, fmt.Sprintf("to match elements: %v", failure))
   223  }
   224  
   225  func (m *ElementsMatcher) NegatedFailureMessage(actual interface{}) (message string) {
   226  	return format.Message(actual, "not to match elements")
   227  }
   228  
   229  func (m *ElementsMatcher) Failures() []error {
   230  	return m.failures
   231  }
   232  

View as plain text