Skip to content
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

Merged
merged 1 commit into from
Nov 21, 2018
Merged

compare flux tables for equality #291

merged 1 commit into from
Nov 21, 2018

Conversation

jpacik
Copy link
Contributor

@jpacik jpacik commented Nov 16, 2018

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.

@jpacik jpacik self-assigned this Nov 16, 2018
Copy link

@wolffcm wolffcm left a 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.

// 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
Copy link

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.

}

func (t *tables) EqualWithTolerance(s *tables, tol float64, ulp uint) *results {
defer t.data.Release()
Copy link

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.

Copy link
Contributor Author

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.

return r.fmt
}

func (t *tables) EqualWithTolerance(s *tables, tol float64, ulp uint) *results {
Copy link

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.

Copy link
Contributor Author

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?

Copy link

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.

Copy link
Contributor Author

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.

data flux.ResultIterator
}

func CreateTables(name string, data flux.ResultIterator) *tables {
Copy link

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

@nathanielc nathanielc left a 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?

@jpacik
Copy link
Contributor Author

jpacik commented Nov 16, 2018

@nathanielc yes, if it implements the Result interface then it can be compared for equality.

@nathanielc
Copy link
Contributor

@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.

@jpacik
Copy link
Contributor Author

jpacik commented Nov 16, 2018

@nathanielc yeah I totally forgot about executetest. These methods should probably just operate on the Result and Table types in executetest.

Copy link
Contributor

@aanthony1243 aanthony1243 left a 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.

data flux.ResultIterator
}

func CreateTables(name string, data flux.ResultIterator) *tables {
Copy link
Contributor

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.

})
}

func equalFloats(a, b []float64, tol float64, ulp uint) (int, bool) {
Copy link
Contributor

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.

Copy link
Contributor Author

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?

Copy link
Contributor

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?

t.name, l.Name(), s.name, r.Name()),
}
}
var lt, rt []table
Copy link
Contributor

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.

Copy link
Contributor Author

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".

@jpacik
Copy link
Contributor Author

jpacik commented Nov 19, 2018

@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) {
Copy link
Contributor

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?

Copy link
Contributor Author

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)
@jpacik jpacik merged commit 54f9e3f into master Nov 21, 2018
@jpacik jpacik deleted the jl-compare-tables branch November 21, 2018 00:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants