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 }