github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/pkg/eval/evaltest/matchers.go (about)

     1  package evaltest
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  	"reflect"
     7  	"regexp"
     8  
     9  	"src.elv.sh/pkg/eval"
    10  )
    11  
    12  // ApproximatelyThreshold defines the threshold for matching float64 values when
    13  // using Approximately.
    14  const ApproximatelyThreshold = 1e-15
    15  
    16  // Approximately can be passed to TestCase.Puts to match a float64 within the
    17  // threshold defined by ApproximatelyThreshold.
    18  type Approximately struct{ F float64 }
    19  
    20  func matchFloat64(a, b, threshold float64) bool {
    21  	if math.IsNaN(a) && math.IsNaN(b) {
    22  		return true
    23  	}
    24  	if math.IsInf(a, 0) && math.IsInf(b, 0) &&
    25  		math.Signbit(a) == math.Signbit(b) {
    26  		return true
    27  	}
    28  	return math.Abs(a-b) <= threshold
    29  }
    30  
    31  // MatchingRegexp can be passed to TestCase.Puts to match a any string that
    32  // matches a regexp pattern. If the pattern is not a valid regexp, the test will
    33  // panic.
    34  type MatchingRegexp struct{ Pattern string }
    35  
    36  func matchRegexp(p, s string) bool {
    37  	matched, err := regexp.MatchString(p, s)
    38  	if err != nil {
    39  		panic(err)
    40  	}
    41  	return matched
    42  }
    43  
    44  type errorMatcher interface{ matchError(error) bool }
    45  
    46  // AnyError is an error that can be passed to TestCase.Throws to match any
    47  // non-nil error.
    48  var AnyError = anyError{}
    49  
    50  // An errorMatcher for any error.
    51  type anyError struct{}
    52  
    53  func (anyError) Error() string { return "any error" }
    54  
    55  func (anyError) matchError(e error) bool { return e != nil }
    56  
    57  // An errorMatcher for exceptions.
    58  type exc struct {
    59  	reason error
    60  	stacks []string
    61  }
    62  
    63  func (e exc) Error() string {
    64  	if len(e.stacks) == 0 {
    65  		return fmt.Sprintf("exception with reason %v", e.reason)
    66  	}
    67  	return fmt.Sprintf("exception with reason %v and stacks %v", e.reason, e.stacks)
    68  }
    69  
    70  func (e exc) matchError(e2 error) bool {
    71  	if e2, ok := e2.(eval.Exception); ok {
    72  		return matchErr(e.reason, e2.Reason()) &&
    73  			(len(e.stacks) == 0 ||
    74  				reflect.DeepEqual(e.stacks, getStackTexts(e2.StackTrace())))
    75  	}
    76  	return false
    77  }
    78  
    79  func getStackTexts(tb *eval.StackTrace) []string {
    80  	texts := []string{}
    81  	for tb != nil {
    82  		ctx := tb.Head
    83  		texts = append(texts, ctx.Source[ctx.From:ctx.To])
    84  		tb = tb.Next
    85  	}
    86  	return texts
    87  }
    88  
    89  // ErrorWithType returns an error that can be passed to the TestCase.Throws
    90  // to match any error with the same type as the argument.
    91  func ErrorWithType(v error) error { return errWithType{v} }
    92  
    93  // An errorMatcher for any error with the given type.
    94  type errWithType struct{ v error }
    95  
    96  func (e errWithType) Error() string { return fmt.Sprintf("error with type %T", e.v) }
    97  
    98  func (e errWithType) matchError(e2 error) bool {
    99  	return reflect.TypeOf(e.v) == reflect.TypeOf(e2)
   100  }
   101  
   102  // ErrorWithMessage returns an error that can be passed to TestCase.Throws to
   103  // match any error with the given message.
   104  func ErrorWithMessage(msg string) error { return errWithMessage{msg} }
   105  
   106  // An errorMatcher for any error with the given message.
   107  type errWithMessage struct{ msg string }
   108  
   109  func (e errWithMessage) Error() string { return "error with message " + e.msg }
   110  
   111  func (e errWithMessage) matchError(e2 error) bool {
   112  	return e2 != nil && e.msg == e2.Error()
   113  }
   114  
   115  // CmdExit returns an error that can be passed to TestCase.Throws to match an
   116  // eval.ExternalCmdExit ignoring the Pid field.
   117  func CmdExit(v eval.ExternalCmdExit) error { return errCmdExit{v} }
   118  
   119  // An errorMatcher for an ExternalCmdExit error that ignores the `Pid` member.
   120  // We only match the command name and exit status because at run time we
   121  // cannot know the correct value for `Pid`.
   122  type errCmdExit struct{ v eval.ExternalCmdExit }
   123  
   124  func (e errCmdExit) Error() string {
   125  	return e.v.Error()
   126  }
   127  
   128  func (e errCmdExit) matchError(gotErr error) bool {
   129  	if gotErr == nil {
   130  		return false
   131  	}
   132  	ge := gotErr.(eval.ExternalCmdExit)
   133  	return e.v.CmdName == ge.CmdName && e.v.WaitStatus == ge.WaitStatus
   134  }