mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-12 17:27:50 +08:00
pkg/eval/evaltest: Move value and error matching code to separate file.
This commit is contained in:
parent
90a99c1792
commit
bd33459eaf
|
@ -17,10 +17,8 @@ package evaltest
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
@ -32,13 +30,6 @@ import (
|
|||
"github.com/elves/elvish/pkg/parse"
|
||||
)
|
||||
|
||||
// These two symbols are used for tests that need to compare floating point
|
||||
// values that can't be guaranteed to be bit for bit identical. Typically due
|
||||
// to tiny rounding errors that tend to occur in floating point operations.
|
||||
const float64EqualityThreshold = 1e-15
|
||||
|
||||
type Approximately struct{ F float64 }
|
||||
|
||||
// TestCase is a test case for Test.
|
||||
type TestCase struct {
|
||||
code string
|
||||
|
@ -54,98 +45,6 @@ type Result struct {
|
|||
Exception error
|
||||
}
|
||||
|
||||
type errorMatcher interface{ matchError(error) bool }
|
||||
|
||||
// AnyError is an error that can be passed to TestCase.Throws to match any
|
||||
// non-nil error.
|
||||
var AnyError = anyError{}
|
||||
|
||||
// An errorMatcher for any error.
|
||||
type anyError struct{}
|
||||
|
||||
func (anyError) Error() string { return "any error" }
|
||||
|
||||
func (anyError) matchError(e error) bool { return e != nil }
|
||||
|
||||
// An errorMatcher for exceptions.
|
||||
type exc struct {
|
||||
reason error
|
||||
stacks []string
|
||||
}
|
||||
|
||||
func (e exc) Error() string {
|
||||
if len(e.stacks) == 0 {
|
||||
return fmt.Sprintf("exception with reason %v", e.reason)
|
||||
}
|
||||
return fmt.Sprintf("exception with reason %v and stacks %v", e.reason, e.stacks)
|
||||
}
|
||||
|
||||
func (e exc) matchError(e2 error) bool {
|
||||
if e2, ok := e2.(*eval.Exception); ok {
|
||||
return matchErr(e.reason, e2.Reason) &&
|
||||
(len(e.stacks) == 0 ||
|
||||
reflect.DeepEqual(e.stacks, getStackTexts(e2.StackTrace)))
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getStackTexts(tb *eval.StackTrace) []string {
|
||||
texts := []string{}
|
||||
for tb != nil {
|
||||
ctx := tb.Head
|
||||
texts = append(texts, ctx.Source[ctx.From:ctx.To])
|
||||
tb = tb.Next
|
||||
}
|
||||
return texts
|
||||
}
|
||||
|
||||
// ErrorWithType returns an error that can be passed to the TestCase.Throws
|
||||
// to match any error with the same type as the argument.
|
||||
func ErrorWithType(v error) error { return errWithType{v} }
|
||||
|
||||
// An errorMatcher for any error with the given type.
|
||||
type errWithType struct{ v error }
|
||||
|
||||
func (e errWithType) Error() string { return fmt.Sprintf("error with type %T", e.v) }
|
||||
|
||||
func (e errWithType) matchError(e2 error) bool {
|
||||
return reflect.TypeOf(e.v) == reflect.TypeOf(e2)
|
||||
}
|
||||
|
||||
// ErrorWithMessage returns an error that can be passed to TestCase.Throws to
|
||||
// match any error with the given message.
|
||||
func ErrorWithMessage(msg string) error { return errWithMessage{msg} }
|
||||
|
||||
// An errorMatcher for any error with the given message.
|
||||
type errWithMessage struct{ msg string }
|
||||
|
||||
func (e errWithMessage) Error() string { return "error with message " + e.msg }
|
||||
|
||||
func (e errWithMessage) matchError(e2 error) bool {
|
||||
return e2 != nil && e.msg == e2.Error()
|
||||
}
|
||||
|
||||
// CmdExit returns an error that can be passed to TestCase.Throws to match an
|
||||
// eval.ExternalCmdExit ignoring the Pid field.
|
||||
func CmdExit(v eval.ExternalCmdExit) error { return errCmdExit{v} }
|
||||
|
||||
// An errorMatcher for an ExternalCmdExit error that ignores the `Pid` member.
|
||||
// We only match the command name and exit status because at run time we
|
||||
// cannot know the correct value for `Pid`.
|
||||
type errCmdExit struct{ v eval.ExternalCmdExit }
|
||||
|
||||
func (e errCmdExit) Error() string {
|
||||
return e.v.Error()
|
||||
}
|
||||
|
||||
func (e errCmdExit) matchError(gotErr error) bool {
|
||||
if gotErr == nil {
|
||||
return false
|
||||
}
|
||||
ge := gotErr.(eval.ExternalCmdExit)
|
||||
return e.v.CmdName == ge.CmdName && e.v.WaitStatus == ge.WaitStatus
|
||||
}
|
||||
|
||||
// The following functions and methods are used to build Test structs. They are
|
||||
// supposed to read like English, so a test that "put x" should put "x" reads:
|
||||
//
|
||||
|
@ -313,36 +212,26 @@ func matchOut(want, got []interface{}) bool {
|
|||
return false
|
||||
}
|
||||
for i := range got {
|
||||
// Equality of some data types needs to be special-cased in unit
|
||||
// tests. For example, by definition `NaN == NaN` is always false
|
||||
// since NaN is never equal to any other value; not even NaN. But for
|
||||
// unit tests we want to ensure that if the test is expected to
|
||||
// produce NaN it does so and the test passes.
|
||||
switch v := got[i].(type) {
|
||||
switch g := got[i].(type) {
|
||||
case float64:
|
||||
switch x := want[i].(type) {
|
||||
// Special-case float64 to correctly handle NaN and support
|
||||
// approximate comparison.
|
||||
switch w := want[i].(type) {
|
||||
case float64:
|
||||
if math.IsNaN(v) && math.IsNaN(x) {
|
||||
return true
|
||||
if !matchFloat64(g, w, 0) {
|
||||
return false
|
||||
}
|
||||
return v == x
|
||||
case Approximately:
|
||||
// Apply a reasonable epsilon if the user asked for an
|
||||
// approximate equality test.
|
||||
w := x.F
|
||||
if math.IsNaN(v) && math.IsNaN(w) {
|
||||
return true
|
||||
if !matchFloat64(g, w.F, ApproximatelyThreshold) {
|
||||
return false
|
||||
}
|
||||
if math.IsInf(v, 0) && math.IsInf(w, 0) &&
|
||||
math.Signbit(v) == math.Signbit(w) {
|
||||
return true
|
||||
}
|
||||
return math.Abs(v-w) <= float64EqualityThreshold
|
||||
default:
|
||||
return false
|
||||
}
|
||||
default:
|
||||
if !vals.Equal(got[i], want[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if !vals.Equal(got[i], want[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
|
120
pkg/eval/evaltest/matchers.go
Normal file
120
pkg/eval/evaltest/matchers.go
Normal file
|
@ -0,0 +1,120 @@
|
|||
package evaltest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
|
||||
"github.com/elves/elvish/pkg/eval"
|
||||
)
|
||||
|
||||
// ApproximatelyThreshold defines the threshold for matching float64 values when
|
||||
// using Approximately.
|
||||
const ApproximatelyThreshold = 1e-15
|
||||
|
||||
// Approximately can be passed to TestCase.Puts to match a float64 within the
|
||||
// threshold defined by ApproximatelyThreshold.
|
||||
type Approximately struct{ F float64 }
|
||||
|
||||
func matchFloat64(a, b, threshold float64) bool {
|
||||
if math.IsNaN(a) && math.IsNaN(b) {
|
||||
return true
|
||||
}
|
||||
if math.IsInf(a, 0) && math.IsInf(b, 0) &&
|
||||
math.Signbit(a) == math.Signbit(b) {
|
||||
return true
|
||||
}
|
||||
return math.Abs(a-b) <= threshold
|
||||
}
|
||||
|
||||
type errorMatcher interface{ matchError(error) bool }
|
||||
|
||||
// AnyError is an error that can be passed to TestCase.Throws to match any
|
||||
// non-nil error.
|
||||
var AnyError = anyError{}
|
||||
|
||||
// An errorMatcher for any error.
|
||||
type anyError struct{}
|
||||
|
||||
func (anyError) Error() string { return "any error" }
|
||||
|
||||
func (anyError) matchError(e error) bool { return e != nil }
|
||||
|
||||
// An errorMatcher for exceptions.
|
||||
type exc struct {
|
||||
reason error
|
||||
stacks []string
|
||||
}
|
||||
|
||||
func (e exc) Error() string {
|
||||
if len(e.stacks) == 0 {
|
||||
return fmt.Sprintf("exception with reason %v", e.reason)
|
||||
}
|
||||
return fmt.Sprintf("exception with reason %v and stacks %v", e.reason, e.stacks)
|
||||
}
|
||||
|
||||
func (e exc) matchError(e2 error) bool {
|
||||
if e2, ok := e2.(*eval.Exception); ok {
|
||||
return matchErr(e.reason, e2.Reason) &&
|
||||
(len(e.stacks) == 0 ||
|
||||
reflect.DeepEqual(e.stacks, getStackTexts(e2.StackTrace)))
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getStackTexts(tb *eval.StackTrace) []string {
|
||||
texts := []string{}
|
||||
for tb != nil {
|
||||
ctx := tb.Head
|
||||
texts = append(texts, ctx.Source[ctx.From:ctx.To])
|
||||
tb = tb.Next
|
||||
}
|
||||
return texts
|
||||
}
|
||||
|
||||
// ErrorWithType returns an error that can be passed to the TestCase.Throws
|
||||
// to match any error with the same type as the argument.
|
||||
func ErrorWithType(v error) error { return errWithType{v} }
|
||||
|
||||
// An errorMatcher for any error with the given type.
|
||||
type errWithType struct{ v error }
|
||||
|
||||
func (e errWithType) Error() string { return fmt.Sprintf("error with type %T", e.v) }
|
||||
|
||||
func (e errWithType) matchError(e2 error) bool {
|
||||
return reflect.TypeOf(e.v) == reflect.TypeOf(e2)
|
||||
}
|
||||
|
||||
// ErrorWithMessage returns an error that can be passed to TestCase.Throws to
|
||||
// match any error with the given message.
|
||||
func ErrorWithMessage(msg string) error { return errWithMessage{msg} }
|
||||
|
||||
// An errorMatcher for any error with the given message.
|
||||
type errWithMessage struct{ msg string }
|
||||
|
||||
func (e errWithMessage) Error() string { return "error with message " + e.msg }
|
||||
|
||||
func (e errWithMessage) matchError(e2 error) bool {
|
||||
return e2 != nil && e.msg == e2.Error()
|
||||
}
|
||||
|
||||
// CmdExit returns an error that can be passed to TestCase.Throws to match an
|
||||
// eval.ExternalCmdExit ignoring the Pid field.
|
||||
func CmdExit(v eval.ExternalCmdExit) error { return errCmdExit{v} }
|
||||
|
||||
// An errorMatcher for an ExternalCmdExit error that ignores the `Pid` member.
|
||||
// We only match the command name and exit status because at run time we
|
||||
// cannot know the correct value for `Pid`.
|
||||
type errCmdExit struct{ v eval.ExternalCmdExit }
|
||||
|
||||
func (e errCmdExit) Error() string {
|
||||
return e.v.Error()
|
||||
}
|
||||
|
||||
func (e errCmdExit) matchError(gotErr error) bool {
|
||||
if gotErr == nil {
|
||||
return false
|
||||
}
|
||||
ge := gotErr.(eval.ExternalCmdExit)
|
||||
return e.v.CmdName == ge.CmdName && e.v.WaitStatus == ge.WaitStatus
|
||||
}
|
Loading…
Reference in New Issue
Block a user