support wrappers#217
Conversation
| func (h dotHandler) GetGomegaBasicInfo(expr *ast.CallExpr) (*GomegaBasicInfo, bool) { | ||
| info := &GomegaBasicInfo{} | ||
| func (h dotHandler) GetGomegaBasicInfo(expr *ast.CallExpr) (info *GomegaBasicInfo) { | ||
| defer finalizeGetGomegaWrapper(expr, h.pass, h.baseAssertionInterface, h.asyncAssertionInterface, &info) |
There was a problem hiding this comment.
finalizeGetGomegaWrapper is essentially the common part of dotHandler and nameHandler.
Originally I wanted to keep those because it seemed that they might enable some checks that cannot be supported with generic code. I am not sure anymore, maybe it is possible to drop more code.
There was a problem hiding this comment.
If we want to drop the name-based code, then we also need to add support for identifying calls which generate a GomegaMatcher: that is the other usage of GetGomegaBasicInfo.
There was a problem hiding this comment.
That idea worked out fine, see separate commit.
But unit testing becomes harder to set up, because the code now really needs a valid analysis.Pass instance. I can do that by setting up a source code directory for analysistest and in a fake analyzer run the unit test code. But before I continue in that direction I would like to hear whether you want to make this change.
There was a problem hiding this comment.
I've implemented unit testing again.
| Eventually(slowInt).WithPolling(time.Millisecond * 100).WithTimeout(time.Second * 2).Should(Equal(42)) // valid | ||
| Eventually(slowInt).WithPolling(time.Second * 2).WithTimeout(time.Millisecond * 100).Should(Equal(42)) // not valid, if validate-async-intervals flag is set | ||
| Eventually(slowInt()).WithPolling(time.Millisecond * 100).WithTimeout(time.Second * 2).Should(Equal(42)) // want `ginkgo-linter: use a function call in Eventually\. This actually checks nothing, because Eventually receives the function returned value, instead of function itself, and this value is never changed\. Consider using .Eventually\(slowInt\)\.WithPolling\(time\.Millisecond \* 100\)\.WithTimeout\(time\.Second \* 2\)\.Should\(Equal\(42\)\). instead` | ||
| wrapper.MyEventually(slowInt()).WithPolling(time.Millisecond * 100).WithTimeout(time.Second * 2).Should(Equal(42)) // want `ginkgo-linter: use a function call in MyEventually\. This actually checks nothing, because MyEventually receives the function returned value, instead of function itself, and this value is never changed\. Consider using .wrapper.MyEventually\(slowInt\)\.WithPolling\(time\.Millisecond \* 100\)\.WithTimeout\(time\.Second \* 2\)\.Should\(Equal\(42\)\). instead` |
There was a problem hiding this comment.
I didn't want to replicate all test cases with wrappers, instead I focused on those where wrapping might cause a difference: here it is determining the parameter offset (0 instead of -1).
| origActual := handler.GetActualExpr(origSel) | ||
| if origActual == nil { | ||
| return nil, false | ||
| rootCall := info.RootCall |
There was a problem hiding this comment.
I did not initially understand that origActual probably means "the original call which is passed the actual value" - that is the reason for the name, isn't it?
I can rename RootCall -> ActualCall, it might be a bit more consistent with the rest of the code base.
There was a problem hiding this comment.
We must not modify the ast tree, so we use a clone to perform modifications for the fixes. However, when quering the pass, we mudt use thd original objects from the ast tree. This is why we hold the original object, that we treat as read only, and its clone, side by side. And this is the reason for the naming, to make it clear that we won't modify the origs, and won't use the clones for queries.
There was a problem hiding this comment.
So "actual" means "the real instance from the ast"? Then "orig" and "actual" seem a bit redundant - they mean the same thing.
There was a problem hiding this comment.
"Actual" means the part of the expression that is the actual value to check, vs. the "Matcher" - the part of the expression that chechs the value. Maybe not the best names, but this is working for me 😄
There was a problem hiding this comment.
Okay, so that is what I meant in my initial comment, too. RootCall deviates from that convention, so my proposal to rename it (back) to ActualCall still stands.
There was a problem hiding this comment.
I do prefer "actual", mostly because it's used all over, including a package name and so on.
There was a problem hiding this comment.
"Actually" (pun intended, sorry), in GomegaBasicInfo it can also be the function which produces a matcher, so it's not always the ActualCall.
I'll keep RootCall in GomegaBasicInfo and check how it's called when retrieved from it, using actualCall as before there.
There was a problem hiding this comment.
Fun fact: if the wrapper is called exactly like the method that it is wrapping, then semantic checks for matchers still work (example: wrapper.Equal wrapping gomega.Equal). I think this is useful, even if it is a bit based on guesswork.
Pull Request Test Coverage Report for Build 19815117102Details
💛 - Coveralls |
42558e1 to
f3e67ae
Compare
nunnatsa
left a comment
There was a problem hiding this comment.
Thank @pohly ! you really got into it, and meaningfully improved the code.
Could you please try adding a simple functional test (under tests/)? I didn't try to create directories in the txtar files, but I think it's possible. Do you think you can create a super simple wrapper package and a ginkgo test file to use it?
Also, added a few comments inline.
| } | ||
|
|
||
| func implements(expr ast.Expr, pass *analysis.Pass, i *types.Interface) bool { | ||
| if exprType, ok := pass.TypesInfo.Types[expr]; ok && types.Implements(exprType.Type, i) { |
There was a problem hiding this comment.
Maybe:
| if exprType, ok := pass.TypesInfo.Types[expr]; ok && types.Implements(exprType.Type, i) { | |
| exprType, ok := pass.TypesInfo.Types[expr] | |
| return ok && types.Implements(exprType.Type, i) |
| origActual := handler.GetActualExpr(origSel) | ||
| if origActual == nil { | ||
| return nil, false | ||
| rootCall := info.RootCall |
There was a problem hiding this comment.
I do prefer "actual", mostly because it's used all over, including a package name and so on.
|
|
||
| import ( | ||
| "go/ast" | ||
| "go/types" |
| } | ||
|
|
||
| h := GetGomegaHandler(file, nil) | ||
| h := GetGomegaHandler(file, &analysis.Pass{Pkg: &types.Package{}}) |
There was a problem hiding this comment.
types.Package{} ==> gotypes,Package{}
|
Another question. Why canceling the |
After unifying |
On my machine (Arrow Lake, 24 cores) this cuts down test time from ~12s to ~8s.
412255e to
28e5e2c
Compare
Done. I think I have addressed all comments and cannot think of any other things which might be improved, so perhaps the PR is complete now? |
|
I verified that this works in Kubernetes through golangci-lint:
|
nunnatsa
left a comment
There was a problem hiding this comment.
Thanks for fixing the comments.
I Found some more issues - sorry for finding them only now.
Previously, linting was only done for function calls that the linter could identify by name as being one of the methods in Gomega that are known to return an Assertion or AsyncAssertion (Expect, Consistently, Eventually, etc.). Now wrappers are also supported. The basic idea is that any function call which returns any type that implements the Gomega assertion methods (To/Should, etc.) can be treated as the root of a Gomega expression call chain. Wrappers which take an offset as first parameter are not supported because the linter isn't smart enough to detect that the first parameter is an offset. Such wrappers should not be needed anymore thanks to ginkgo.Helper. The "missing assertion" error then needs to be a bit more vague because it cannot suggest what the appropriate assertion should be.
28e5e2c to
85e8d22
Compare
Description
Previously, linting was only done for function calls that the linter could identify by name as being one of the methods in Gomega that are known to return an Assertion or AsyncAssertion (Expect, Consistently, Eventually, etc.).
Now wrappers are also supported. The basic idea is that any function call which returns any type that implements the Gomega assertion methods (To/Should, etc.) can be treated as the root of a Gomega expression call chain.
Wrappers which take an offset as first parameter are not supported because the linter isn't smart enough to detect that the first parameter is an offset. Such wrappers should not be needed anymore thanks to ginkgo.Helper.
The "missing assertion" error then needs to be a bit more vague because it cannot suggest what the appropriate assertion should be.
Fixes #216
Type of change
^^^ not sure
How Has This Been Tested?
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration
Checklist:
make goimports@nunnatsa