github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/eval/evaltest/matchers.go (about)

     1  package evaltest
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  	"reflect"
     7  	"regexp"
     8  
     9  	"github.com/markusbkk/elvish/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 Case.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 Case.Puts to match a any string that matches
    32  // a regexp pattern. If the pattern is not a valid regexp, the test will panic.
    33  type MatchingRegexp struct{ Pattern string }
    34  
    35  func matchRegexp(p, s string) bool {
    36  	matched, err := regexp.MatchString(p, s)
    37  	if err != nil {
    38  		panic(err)
    39  	}
    40  	return matched
    41  }
    42  
    43  type errorMatcher interface{ matchError(error) bool }
    44  
    45  // An errorMatcher for any error.
    46  type anyError struct{}
    47  
    48  func (anyError) Error() string { return "any error" }
    49  
    50  func (anyError) matchError(e error) bool { return e != nil }
    51  
    52  // An errorMatcher for exceptions.
    53  type exc struct {
    54  	reason error
    55  	stacks []string
    56  }
    57  
    58  func (e exc) Error() string {
    59  	if len(e.stacks) == 0 {
    60  		return fmt.Sprintf("exception with reason %v", e.reason)
    61  	}
    62  	return fmt.Sprintf("exception with reason %v and stacks %v", e.reason, e.stacks)
    63  }
    64  
    65  func (e exc) matchError(e2 error) bool {
    66  	if e2, ok := e2.(eval.Exception); ok {
    67  		return matchErr(e.reason, e2.Reason()) &&
    68  			(len(e.stacks) == 0 ||
    69  				reflect.DeepEqual(e.stacks, getStackTexts(e2.StackTrace())))
    70  	}
    71  	return false
    72  }
    73  
    74  func getStackTexts(tb *eval.StackTrace) []string {
    75  	texts := []string{}
    76  	for tb != nil {
    77  		ctx := tb.Head
    78  		texts = append(texts, ctx.Source[ctx.From:ctx.To])
    79  		tb = tb.Next
    80  	}
    81  	return texts
    82  }
    83  
    84  // ErrorWithType returns an error that can be passed to the Case.Throws to match
    85  // any error with the same type as the argument.
    86  func ErrorWithType(v error) error { return errWithType{v} }
    87  
    88  // An errorMatcher for any error with the given type.
    89  type errWithType struct{ v error }
    90  
    91  func (e errWithType) Error() string { return fmt.Sprintf("error with type %T", e.v) }
    92  
    93  func (e errWithType) matchError(e2 error) bool {
    94  	return reflect.TypeOf(e.v) == reflect.TypeOf(e2)
    95  }
    96  
    97  // ErrorWithMessage returns an error that can be passed to Case.Throws to match
    98  // any error with the given message.
    99  func ErrorWithMessage(msg string) error { return errWithMessage{msg} }
   100  
   101  // An errorMatcher for any error with the given message.
   102  type errWithMessage struct{ msg string }
   103  
   104  func (e errWithMessage) Error() string { return "error with message " + e.msg }
   105  
   106  func (e errWithMessage) matchError(e2 error) bool {
   107  	return e2 != nil && e.msg == e2.Error()
   108  }
   109  
   110  // CmdExit returns an error that can be passed to Case.Throws to match an
   111  // eval.ExternalCmdExit ignoring the Pid field.
   112  func CmdExit(v eval.ExternalCmdExit) error { return errCmdExit{v} }
   113  
   114  // An errorMatcher for an ExternalCmdExit error that ignores the `Pid` member.
   115  // We only match the command name and exit status because at run time we
   116  // cannot know the correct value for `Pid`.
   117  type errCmdExit struct{ v eval.ExternalCmdExit }
   118  
   119  func (e errCmdExit) Error() string {
   120  	return e.v.Error()
   121  }
   122  
   123  func (e errCmdExit) matchError(gotErr error) bool {
   124  	if gotErr == nil {
   125  		return false
   126  	}
   127  	ge := gotErr.(eval.ExternalCmdExit)
   128  	return e.v.CmdName == ge.CmdName && e.v.WaitStatus == ge.WaitStatus
   129  }
   130  
   131  type errOneOf struct{ errs []error }
   132  
   133  func OneOfErrors(errs ...error) error { return errOneOf{errs} }
   134  
   135  func (e errOneOf) Error() string { return fmt.Sprint("one of", e.errs) }
   136  
   137  func (e errOneOf) matchError(gotError error) bool {
   138  	for _, want := range e.errs {
   139  		if matchErr(want, gotError) {
   140  			return true
   141  		}
   142  	}
   143  	return false
   144  }