-
Notifications
You must be signed in to change notification settings - Fork 155
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
compare flux tables for equality #291
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks very nice. I think you should change the types to be exportable though.
It would be good to make the existing transformations_test
use this if it's not too much trouble. It would help cement this code as the go-to place for comparing results.
A future enhancement might be to allow results/streams/tables to appear in different orders if a non-deterministic (but still valid) ordering is possible. This would avoid having to add a sort
operation to each query if (for example) in the future we do something to execution that tweaks the order.
querytest/compare.go
Outdated
// 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be worth saying what ULP stands for here---I had to look it up.
querytest/compare.go
Outdated
} | ||
|
||
func (t *tables) EqualWithTolerance(s *tables, tol float64, ulp uint) *results { | ||
defer t.data.Release() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might the callers want to do more stuff with the results? Intuitively if tables
is not creating these results, then maybe it should be up to the callers to release them. If there's a reason this method should be releasing the results, it would be good to note it in a method comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, caller should be responsible for releasing tables.
querytest/compare.go
Outdated
return r.fmt | ||
} | ||
|
||
func (t *tables) EqualWithTolerance(s *tables, tol float64, ulp uint) *results { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be good to note which tables
instances are expected/actual or want/got here, and in error messages.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I gave tables
a name param which the caller provides, so that the caller doesn't have to remember if "want" should be on the left or right. See https://github.com/influxdata/platform/pull/1450/files#diff-afa3e17b057923f1e8fcb59b758370ddR273 for usage. Perhaps I should explain that here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, okay. That makes sense.
It would be good to add comments to all the exported items here, especially since this code could also be used by contributors as they add new functions to Flux.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, I'll get get these comments up with the rest of the changes.
querytest/compare.go
Outdated
data flux.ResultIterator | ||
} | ||
|
||
func CreateTables(name string, data flux.ResultIterator) *tables { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Returning unexported types from exported functions seems to be discouraged as a practice---for example, golint
will complain about this.
golang/lint#210
I think the structs returned from functions in this file should be exported.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than introducing a new type, perhaps we can add a Name()
function to the result iterator interface? then we can just return a ResultIterator which would help clarify the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I can export the types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I took a quick glance and it looks good. I am curious how this code is different from the execute test package https://github.com/influxdata/flux/blob/master/execute/executetest/result.go ? Is it that instead of converting the tables to a common test type it simply iterates over them to compare them, meaning it can compare any result implementations?
@nathanielc yes, if it implements the Result interface then it can be compared for equality. |
@jlapacik Sounds good, I am wondering if we should move things around so its clear why you would use one over the other. Or maybe we should just replace all existing instances if comparing executetest.Result types with this new code? Basically if we have two ways to do the same thing it needs to be clear why we use one over the other. Otherwise we should remove one of the way to do it. |
@nathanielc yeah I totally forgot about |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Had a few thoughts about the types. I think we should clean up/disambiguate some of the names.
I know that the go
mentality is that types are often associated with a package name so that the type name can be a common word like table
however, the table concept is so critical in our code, I am hesitant to have a table
type that doesn't implement our execute.Table
interface.
querytest/compare.go
Outdated
data flux.ResultIterator | ||
} | ||
|
||
func CreateTables(name string, data flux.ResultIterator) *tables { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than introducing a new type, perhaps we can add a Name()
function to the result iterator interface? then we can just return a ResultIterator which would help clarify the code.
querytest/compare.go
Outdated
}) | ||
} | ||
|
||
func equalFloats(a, b []float64, tol float64, ulp uint) (int, bool) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could add these helpers to the ColumnReader interface, or else adjacent to the column reader interface--ComparableColumnReader
? If they aren't useful in production, I would still recommend making these exportable in a separate file so that other tests may know about and share this code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should I just add the Equals()
method to the ColumnReader and Table interfaces?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is probably OK. If you don't want to deal with the interface cascade, you can just export these so others can use them. I'm on the fence whether we need this code outside of a testing scenario, but it might be useful some day, and odds are if we keep it in querytest
we would forget about it and re-implement the same feature in execute
@nathanielc what do you think?
querytest/compare.go
Outdated
t.name, l.Name(), s.name, r.Name()), | ||
} | ||
} | ||
var lt, rt []table |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like table
is overloaded as a keyword in this code. There's a flux.Table, then tables
then table
and below table
is a loop variable.
Why do we need the table
type? it seems above that the flux.Table
type has a Name()
function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah you're right I need better names. I just couldn't think of good descriptions. The reason tables have a Name param is so that the caller can label which result/table corresponds to "want" and which one corresponds to "got".
@aanthony1243 @nathanielc can you give this another review please. I've decided to just use the framework that already exists in the executetest package. I just added some extra options for comparing floats as well as an extra method for comparing a list of Flux Results. This option requires zero refactoring of any execute tests. And using go-cmp means we won't have to write code to test our testing code. Thoughts? |
@@ -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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where is this function used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will be used in platform for the generated ht tests.
equate floating point values using a combination of absolute error and ULP (unit of least precision)
76138d4
to
5452351
Compare
Related to #236 .
This PR exposes some utility functions for comparing Flux tables for equality. All values are compared for exact equality except floating point values - they are compared using a combination of absolute tolerance and ULPs (units in the last place).
When two values fail an equality comparison, a table formatter along with an appropriate error message is returned. This allows the caller of the function to print out both formatted tables along with the row and column number where the equality comparison failed.