From 95ba078ff672ec372e1926f7ea4bd2c284b952c1 Mon Sep 17 00:00:00 2001 From: Qi Xiao Date: Sun, 13 Aug 2023 23:11:37 -0400 Subject: [PATCH] pkg/eval/evaltest: Implement value matchers with interface. --- .../{matchers.go => error_matchers.go} | 39 ------------ pkg/eval/evaltest/evaltest.go | 20 ++----- pkg/eval/evaltest/value_matchers.go | 59 +++++++++++++++++++ 3 files changed, 64 insertions(+), 54 deletions(-) rename pkg/eval/evaltest/{matchers.go => error_matchers.go} (75%) create mode 100644 pkg/eval/evaltest/value_matchers.go diff --git a/pkg/eval/evaltest/matchers.go b/pkg/eval/evaltest/error_matchers.go similarity index 75% rename from pkg/eval/evaltest/matchers.go rename to pkg/eval/evaltest/error_matchers.go index 57eb69fbc..2a6d64a11 100644 --- a/pkg/eval/evaltest/matchers.go +++ b/pkg/eval/evaltest/error_matchers.go @@ -2,51 +2,12 @@ package evaltest import ( "fmt" - "math" "reflect" - "regexp" "src.elv.sh/pkg/eval" "src.elv.sh/pkg/parse" ) -// Anything is a value that can be passed to [Case.Puts] to match any value. It -// is useful when the value contains information that is useful when the test -// fails. -var Anything = anything{} - -type anything struct{} - -// ApproximatelyThreshold defines the threshold for matching float64 values when -// using Approximately. -const ApproximatelyThreshold = 1e-15 - -// Approximately returns a value that can be passed to Case.Puts to match a -// float64 within the threshold defined by ApproximatelyThreshold. -func Approximately(f float64) any { return approximately{f} } - -type approximately struct{ value 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 -} - -// StringMatching returns a value that can be passed to Case.Puts to match any -// string matching a regexp pattern. If the pattern is not a valid regexp, the -// function panics. -func StringMatching(p string) any { - return stringMatching{regexp.MustCompile(p)} -} - -type stringMatching struct{ pattern *regexp.Regexp } - type errorMatcher interface{ matchError(error) bool } // An errorMatcher for compilation errors. diff --git a/pkg/eval/evaltest/evaltest.go b/pkg/eval/evaltest/evaltest.go index fe58c9b68..9a45accc6 100644 --- a/pkg/eval/evaltest/evaltest.go +++ b/pkg/eval/evaltest/evaltest.go @@ -259,23 +259,13 @@ func matchOut(want, got []any) bool { } func match(got, want any) bool { - if want == Anything { - return true + if matcher, ok := want.(ValueMatcher); ok { + return matcher.matchValue(got) } - switch got := got.(type) { - case float64: - // Special-case float64 to correctly handle NaN and support - // approximate comparison. - switch want := want.(type) { - case float64: + // Special-case float64 to handle NaNs and infinities. + if got, ok := got.(float64); ok { + if want, ok := want.(float64); ok { return matchFloat64(got, want, 0) - case approximately: - return matchFloat64(got, want.value, ApproximatelyThreshold) - } - case string: - switch want := want.(type) { - case stringMatching: - return want.pattern.MatchString(got) } } return vals.Equal(got, want) diff --git a/pkg/eval/evaltest/value_matchers.go b/pkg/eval/evaltest/value_matchers.go new file mode 100644 index 000000000..c9ccb8267 --- /dev/null +++ b/pkg/eval/evaltest/value_matchers.go @@ -0,0 +1,59 @@ +package evaltest + +import ( + "math" + "regexp" +) + +// ValueMatcher is a value that can be passed to [Case.Puts] and has its own +// matching semantics. +type ValueMatcher interface{ matchValue(any) bool } + +// Anything matches anything. It is useful when the value contains information +// that is useful when the test fails. +var Anything ValueMatcher = anything{} + +type anything struct{} + +func (anything) matchValue(any) bool { return true } + +// ApproximatelyThreshold defines the threshold for matching float64 values when +// using [Approximately]. +const ApproximatelyThreshold = 1e-15 + +// Approximately matches a float64 within the threshold defined by +// [ApproximatelyThreshold]. +func Approximately(f float64) ValueMatcher { return approximately{f} } + +type approximately struct{ value float64 } + +func (a approximately) matchValue(value any) bool { + if value, ok := value.(float64); ok { + return matchFloat64(a.value, value, ApproximatelyThreshold) + } + return false +} + +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 +} + +// [StringMatching] matches any string matching a regexp pattern. If the pattern +// is not a valid regexp, the function panics. +func StringMatching(p string) ValueMatcher { return stringMatching{regexp.MustCompile(p)} } + +type stringMatching struct{ pattern *regexp.Regexp } + +func (s stringMatching) matchValue(value any) bool { + if value, ok := value.(string); ok { + return s.pattern.MatchString(value) + } + return false +}