github.com/elves/elvish@v0.15.0/pkg/eval/evaltest/matchers.go (about)

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