forked from github/multus-cni
gomodule is still in progress to migrate for now, hence multus team decide to keep vendor directory to support build without gomodule.
190 lines
4.9 KiB
Go
190 lines
4.9 KiB
Go
package asyncassertion
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"time"
|
|
|
|
"github.com/onsi/gomega/internal/oraclematcher"
|
|
"github.com/onsi/gomega/types"
|
|
)
|
|
|
|
type AsyncAssertionType uint
|
|
|
|
const (
|
|
AsyncAssertionTypeEventually AsyncAssertionType = iota
|
|
AsyncAssertionTypeConsistently
|
|
)
|
|
|
|
type AsyncAssertion struct {
|
|
asyncType AsyncAssertionType
|
|
actualInput interface{}
|
|
timeoutInterval time.Duration
|
|
pollingInterval time.Duration
|
|
fail types.GomegaFailHandler
|
|
offset int
|
|
}
|
|
|
|
func New(asyncType AsyncAssertionType, actualInput interface{}, fail types.GomegaFailHandler, timeoutInterval time.Duration, pollingInterval time.Duration, offset int) *AsyncAssertion {
|
|
actualType := reflect.TypeOf(actualInput)
|
|
if actualType.Kind() == reflect.Func {
|
|
if actualType.NumIn() != 0 || actualType.NumOut() == 0 {
|
|
panic("Expected a function with no arguments and one or more return values.")
|
|
}
|
|
}
|
|
|
|
return &AsyncAssertion{
|
|
asyncType: asyncType,
|
|
actualInput: actualInput,
|
|
fail: fail,
|
|
timeoutInterval: timeoutInterval,
|
|
pollingInterval: pollingInterval,
|
|
offset: offset,
|
|
}
|
|
}
|
|
|
|
func (assertion *AsyncAssertion) Should(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
|
|
return assertion.match(matcher, true, optionalDescription...)
|
|
}
|
|
|
|
func (assertion *AsyncAssertion) ShouldNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
|
|
return assertion.match(matcher, false, optionalDescription...)
|
|
}
|
|
|
|
func (assertion *AsyncAssertion) buildDescription(optionalDescription ...interface{}) string {
|
|
switch len(optionalDescription) {
|
|
case 0:
|
|
return ""
|
|
default:
|
|
return fmt.Sprintf(optionalDescription[0].(string), optionalDescription[1:]...) + "\n"
|
|
}
|
|
}
|
|
|
|
func (assertion *AsyncAssertion) actualInputIsAFunction() bool {
|
|
actualType := reflect.TypeOf(assertion.actualInput)
|
|
return actualType.Kind() == reflect.Func && actualType.NumIn() == 0 && actualType.NumOut() > 0
|
|
}
|
|
|
|
func (assertion *AsyncAssertion) pollActual() (interface{}, error) {
|
|
if assertion.actualInputIsAFunction() {
|
|
values := reflect.ValueOf(assertion.actualInput).Call([]reflect.Value{})
|
|
|
|
extras := []interface{}{}
|
|
for _, value := range values[1:] {
|
|
extras = append(extras, value.Interface())
|
|
}
|
|
|
|
success, message := vetExtras(extras)
|
|
|
|
if !success {
|
|
return nil, errors.New(message)
|
|
}
|
|
|
|
return values[0].Interface(), nil
|
|
}
|
|
|
|
return assertion.actualInput, nil
|
|
}
|
|
|
|
func (assertion *AsyncAssertion) matcherMayChange(matcher types.GomegaMatcher, value interface{}) bool {
|
|
if assertion.actualInputIsAFunction() {
|
|
return true
|
|
}
|
|
|
|
return oraclematcher.MatchMayChangeInTheFuture(matcher, value)
|
|
}
|
|
|
|
func (assertion *AsyncAssertion) match(matcher types.GomegaMatcher, desiredMatch bool, optionalDescription ...interface{}) bool {
|
|
timer := time.Now()
|
|
timeout := time.After(assertion.timeoutInterval)
|
|
|
|
description := assertion.buildDescription(optionalDescription...)
|
|
|
|
var matches bool
|
|
var err error
|
|
mayChange := true
|
|
value, err := assertion.pollActual()
|
|
if err == nil {
|
|
mayChange = assertion.matcherMayChange(matcher, value)
|
|
matches, err = matcher.Match(value)
|
|
}
|
|
|
|
fail := func(preamble string) {
|
|
errMsg := ""
|
|
message := ""
|
|
if err != nil {
|
|
errMsg = "Error: " + err.Error()
|
|
} else {
|
|
if desiredMatch {
|
|
message = matcher.FailureMessage(value)
|
|
} else {
|
|
message = matcher.NegatedFailureMessage(value)
|
|
}
|
|
}
|
|
assertion.fail(fmt.Sprintf("%s after %.3fs.\n%s%s%s", preamble, time.Since(timer).Seconds(), description, message, errMsg), 3+assertion.offset)
|
|
}
|
|
|
|
if assertion.asyncType == AsyncAssertionTypeEventually {
|
|
for {
|
|
if err == nil && matches == desiredMatch {
|
|
return true
|
|
}
|
|
|
|
if !mayChange {
|
|
fail("No future change is possible. Bailing out early")
|
|
return false
|
|
}
|
|
|
|
select {
|
|
case <-time.After(assertion.pollingInterval):
|
|
value, err = assertion.pollActual()
|
|
if err == nil {
|
|
mayChange = assertion.matcherMayChange(matcher, value)
|
|
matches, err = matcher.Match(value)
|
|
}
|
|
case <-timeout:
|
|
fail("Timed out")
|
|
return false
|
|
}
|
|
}
|
|
} else if assertion.asyncType == AsyncAssertionTypeConsistently {
|
|
for {
|
|
if !(err == nil && matches == desiredMatch) {
|
|
fail("Failed")
|
|
return false
|
|
}
|
|
|
|
if !mayChange {
|
|
return true
|
|
}
|
|
|
|
select {
|
|
case <-time.After(assertion.pollingInterval):
|
|
value, err = assertion.pollActual()
|
|
if err == nil {
|
|
mayChange = assertion.matcherMayChange(matcher, value)
|
|
matches, err = matcher.Match(value)
|
|
}
|
|
case <-timeout:
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func vetExtras(extras []interface{}) (bool, string) {
|
|
for i, extra := range extras {
|
|
if extra != nil {
|
|
zeroValue := reflect.Zero(reflect.TypeOf(extra)).Interface()
|
|
if !reflect.DeepEqual(zeroValue, extra) {
|
|
message := fmt.Sprintf("Unexpected non-nil/non-zero extra argument at index %d:\n\t<%T>: %#v", i+1, extra, extra)
|
|
return false, message
|
|
}
|
|
}
|
|
}
|
|
return true, ""
|
|
}
|