package matchers import ( "fmt" "reflect" "github.com/onsi/gomega/format" "github.com/onsi/gomega/matchers/internal/miter" ) type mismatchFailure struct { failure string index int } type HaveExactElementsMatcher struct { Elements []interface{} mismatchFailures []mismatchFailure missingIndex int extraIndex int } func (matcher *HaveExactElementsMatcher) Match(actual interface{}) (success bool, err error) { matcher.resetState() if isMap(actual) || miter.IsSeq2(actual) { return false, fmt.Errorf("HaveExactElements matcher doesn't work on map or iter.Seq2. Got:\n%s", format.Object(actual, 1)) } matchers := matchers(matcher.Elements) lenMatchers := len(matchers) success = true if miter.IsIter(actual) { // In the worst case, we need to see everything before we can give our // verdict. The only exception is fast fail. i := 0 miter.IterateV(actual, func(v reflect.Value) bool { if i >= lenMatchers { // the iterator produces more values than we got matchers: this // is not good. matcher.extraIndex = i success = false return false } elemMatcher := matchers[i].(omegaMatcher) match, err := elemMatcher.Match(v.Interface()) if err != nil { matcher.mismatchFailures = append(matcher.mismatchFailures, mismatchFailure{ index: i, failure: err.Error(), }) success = false } else if !match { matcher.mismatchFailures = append(matcher.mismatchFailures, mismatchFailure{ index: i, failure: elemMatcher.FailureMessage(v.Interface()), }) success = false } i++ return true }) if i < len(matchers) { // the iterator produced less values than we got matchers: this is // no good, no no no. matcher.missingIndex = i success = false } return success, nil } values := valuesOf(actual) lenValues := len(values) for i := 0; i < lenMatchers || i < lenValues; i++ { if i >= lenMatchers { matcher.extraIndex = i success = false continue } if i >= lenValues { matcher.missingIndex = i success = false return } elemMatcher := matchers[i].(omegaMatcher) match, err := elemMatcher.Match(values[i]) if err != nil { matcher.mismatchFailures = append(matcher.mismatchFailures, mismatchFailure{ index: i, failure: err.Error(), }) success = false } else if !match { matcher.mismatchFailures = append(matcher.mismatchFailures, mismatchFailure{ index: i, failure: elemMatcher.FailureMessage(values[i]), }) success = false } } return success, nil } func (matcher *HaveExactElementsMatcher) FailureMessage(actual interface{}) (message string) { message = format.Message(actual, "to have exact elements with", presentable(matcher.Elements)) if matcher.missingIndex > 0 { message = fmt.Sprintf("%s\nthe missing elements start from index %d", message, matcher.missingIndex) } if matcher.extraIndex > 0 { message = fmt.Sprintf("%s\nthe extra elements start from index %d", message, matcher.extraIndex) } if len(matcher.mismatchFailures) != 0 { message = fmt.Sprintf("%s\nthe mismatch indexes were:", message) } for _, mismatch := range matcher.mismatchFailures { message = fmt.Sprintf("%s\n%d: %s", message, mismatch.index, mismatch.failure) } return } func (matcher *HaveExactElementsMatcher) NegatedFailureMessage(actual interface{}) (message string) { return format.Message(actual, "not to contain elements", presentable(matcher.Elements)) } func (matcher *HaveExactElementsMatcher) resetState() { matcher.mismatchFailures = nil matcher.missingIndex = 0 matcher.extraIndex = 0 }