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 }