diff --git a/execute/executetest/result.go b/execute/executetest/result.go index 2fd1c899cf..dd57c3b35f 100644 --- a/execute/executetest/result.go +++ b/execute/executetest/result.go @@ -1,6 +1,9 @@ package executetest import ( + "fmt" + + "github.com/google/go-cmp/cmp" "github.com/influxdata/flux" ) @@ -45,3 +48,47 @@ func (ti *TableIterator) Do(f func(flux.Table) error) error { } return nil } + +// EqualResults compares two lists of Flux Results for equlity +func EqualResults(want, got []flux.Result) (bool, error) { + if len(want) != len(got) { + return false, fmt.Errorf("unexpected number of results - want %d results, got %d results", len(want), len(got)) + } + for i, result := range want { + w := result + g := got[i] + if w.Name() != g.Name() { + return false, fmt.Errorf("unexpected result name - want %s, got %s", w.Name(), g.Name()) + } + var wt, gt []*Table + if err := w.Tables().Do(func(tbl flux.Table) error { + t, err := ConvertTable(tbl) + if err != nil { + return err + } + wt = append(wt, t) + return nil + }); err != nil { + return false, err + } + if err := g.Tables().Do(func(tbl flux.Table) error { + t, err := ConvertTable(tbl) + if err != nil { + return err + } + gt = append(gt, t) + return nil + }); err != nil { + return false, err + } + NormalizeTables(wt) + NormalizeTables(gt) + if len(wt) != len(gt) { + return false, fmt.Errorf("unexpected size for result %s - want %d tables, got %d tables", w.Name(), len(wt), len(gt)) + } + if !cmp.Equal(wt, gt, floatOptions) { + return false, fmt.Errorf("unexpected tables -want/+got\n%s", cmp.Diff(wt, gt)) + } + } + return true, nil +} diff --git a/execute/executetest/table.go b/execute/executetest/table.go index ca8928c10a..41d7c8977a 100644 --- a/execute/executetest/table.go +++ b/execute/executetest/table.go @@ -3,10 +3,9 @@ package executetest import ( "fmt" - "github.com/influxdata/flux/semantic" - "github.com/influxdata/flux" "github.com/influxdata/flux/execute" + "github.com/influxdata/flux/semantic" "github.com/influxdata/flux/values" ) diff --git a/execute/executetest/transformation.go b/execute/executetest/transformation.go index 0c0f178e40..f8793a9cf4 100644 --- a/execute/executetest/transformation.go +++ b/execute/executetest/transformation.go @@ -1,15 +1,49 @@ package executetest import ( + "math" "sort" "testing" + "github.com/gonum/floats" + "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + "github.com/influxdata/flux" "github.com/influxdata/flux/execute" ) +// Two floating point values are considered +// equal if they are within tol of each other. +const tol float64 = 1e-25 + +// The maximum number of floating point values that are allowed +// to lie between two float64s and still be considered equal. +const ulp uint = 2 + +// Comparison options for floating point values. +// NaNs are considered equal, and float64s must +// be sufficiently close to be considered equal. +var floatOptions = cmp.Options{ + cmpopts.EquateNaNs(), + cmp.FilterValues(func(x, y float64) bool { + return !math.IsNaN(x) && !math.IsNaN(y) + }, cmp.Comparer(func(x, y float64) bool { + // If sufficiently close, then move on. + // This avoids situations close to zero. + if floats.EqualWithinAbs(x, y, tol) { + return true + } + // If not sufficiently close, both floats + // must be within ulp steps of each other. + if !floats.EqualWithinULP(x, y, ulp) { + return false + } + return true + })), +} + func ProcessTestHelper( t *testing.T, data []flux.Table, @@ -50,7 +84,7 @@ func ProcessTestHelper( sort.Sort(SortedTables(got)) sort.Sort(SortedTables(want)) - if !cmp.Equal(want, got, cmpopts.EquateNaNs()) { + if !cmp.Equal(want, got, floatOptions) { t.Errorf("unexpected tables -want/+got\n%s", cmp.Diff(want, got)) } } diff --git a/querytest/compare.go b/querytest/compare.go deleted file mode 100644 index 7e38d87d58..0000000000 --- a/querytest/compare.go +++ /dev/null @@ -1,222 +0,0 @@ -package querytest - -import ( - "errors" - "fmt" - "math" - - "github.com/gonum/floats" - "github.com/influxdata/flux" - "github.com/influxdata/flux/execute" - "github.com/influxdata/flux/values" -) - -// FloatTolerance represents a sufficient distance that all -// float values must be within in order to be considered equal. -const FloatTolerance float64 = 1e-50 - -// FloatULP is the maximum number of floats that are allowed to lie -// between two floating point values and still be considered equal. -const FloatULP uint = 2 - -type tables struct { - name string - data flux.ResultIterator -} - -func CreateTables(name string, data flux.ResultIterator) *tables { - return &tables{ - name: name, - data: data, - } -} - -type results struct { - equal bool - err error - fmt map[string]*execute.Formatter -} - -func (r *results) Equal() bool { - return r.equal -} -func (r *results) Err() error { - return r.err -} -func (r *results) Fmt() map[string]*execute.Formatter { - return r.fmt -} - -func (t *tables) EqualWithTolerance(s *tables, tol float64, ulp uint) *results { - defer t.data.Release() - defer s.data.Release() - for t.data.More() && s.data.More() { - l := t.data.Next() - r := s.data.Next() - if l.Name() != r.Name() { - return &results{err: fmt.Errorf( - "result names not equal - %s: %s != %s: %s", - t.name, l.Name(), s.name, r.Name()), - } - } - var lt, rt []table - l.Tables().Do(func(tbl flux.Table) error { - lt = append(lt, table{ - name: t.name, - data: tbl, - }) - return nil - }) - r.Tables().Do(func(tbl flux.Table) error { - rt = append(rt, table{ - name: s.name, - data: tbl, - }) - return nil - }) - if len(lt) != len(rt) { - return &results{err: fmt.Errorf( - "result sizes not equal - %s:%d != %s:%d", - t.name, len(lt), s.name, len(rt)), - } - } - for i, table := range lt { - if ok, err := table.equalWithTolerance(rt[i], tol, ulp); !ok { - return &results{ - fmt: map[string]*execute.Formatter{ - t.name: execute.NewFormatter(table.data, nil), - s.name: execute.NewFormatter(rt[i].data, nil), - }, - err: err, - } - } - } - } - if t.data.More() || s.data.More() { - return &results{ - err: errors.New("number of results not equal"), - } - } - return &results{equal: true} -} - -type table struct { - name string - data flux.Table -} - -func (t table) equalWithTolerance(s table, tol float64, ulp uint) (bool, error) { - if !equalKeys(t.data.Key(), s.data.Key()) { - return false, fmt.Errorf("group keys not equal - %s: %v != %s: %v", - t.name, t.data.Key(), s.name, s.data.Key()) - } - if !equalCols(t.data.Cols(), s.data.Cols()) { - return false, fmt.Errorf("column schemas not equal - %s: %v != %s: %v", - t.name, t.data.Cols(), s.name, s.data.Cols()) - } - var eq bool - var row int - return eq, t.data.Do(func(tr flux.ColReader) error { - return s.data.Do(func(sr flux.ColReader) error { - if tr.Len() != sr.Len() { - return fmt.Errorf("tables have different lengths - %s: %d != %s: %d", - t.name, tr.Len(), s.name, sr.Len()) - } - for i, col := range t.data.Cols() { - switch col.Type { - case flux.TBool: - row, eq = equalBools(tr.Bools(i), sr.Bools(i)) - case flux.TInt: - row, eq = equalInts(tr.Ints(i), sr.Ints(i)) - case flux.TUInt: - row, eq = equalUInts(tr.UInts(i), sr.UInts(i)) - case flux.TFloat: - row, eq = equalFloats(tr.Floats(i), sr.Floats(i), tol, ulp) - case flux.TString: - row, eq = equalStrings(tr.Strings(i), sr.Strings(i)) - case flux.TTime: - row, eq = equalTimes(tr.Times(i), sr.Times(i)) - } - if !eq { - return fmt.Errorf("values not equal at row %d column %s", row, tr.Cols()[i].Label) - } - } - return nil - }) - }) -} - -func equalFloats(a, b []float64, tol float64, ulp uint) (int, bool) { - for i, v := range a { - // NaNs are special, nothing is equal to NaN - if math.IsNaN(v) || math.IsNaN(b[i]) { - return i, false - } - // If sufficiently close, then move on. - // This avoids situations close to zero. - if floats.EqualWithinAbs(v, b[i], tol) { - continue - } - // If not sufficiently close, both floats - // must be within ulp steps of each other. - if !floats.EqualWithinULP(v, b[i], ulp) { - return i, false - } - } - return 0, true -} -func equalKeys(a, b flux.GroupKey) bool { - return a.Equal(b) -} -func equalCols(a, b []flux.ColMeta) bool { - for i, col := range a { - if col.Label != b[i].Label { - return false - } - if col.Type != b[i].Type { - return false - } - } - return true -} -func equalBools(a, b []bool) (int, bool) { - for i, v := range a { - if !(v && b[i]) { - continue - } - return i, false - } - return 0, true -} -func equalInts(a, b []int64) (int, bool) { - for i, v := range a { - if v != b[i] { - return i, false - } - } - return 0, true -} -func equalUInts(a, b []uint64) (int, bool) { - for i, v := range a { - if v != b[i] { - return i, false - } - } - return 0, true -} -func equalStrings(a, b []string) (int, bool) { - for i, v := range a { - if v != b[i] { - return i, false - } - } - return 0, true -} -func equalTimes(a, b []values.Time) (int, bool) { - for i, v := range a { - if v != b[i] { - return i, false - } - } - return 0, true -}