-
Notifications
You must be signed in to change notification settings - Fork 6
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
[Enhancement] detect incorrect global assertions in Eventually
#127
Comments
Thanks you so much for your suggestion, @aecay! This idea was discussed before (#85). Using the general I tend to close the issue. WDYT? |
Apologies, I missed the prior discussion. I have never needed to bail out early from an But gomega provides I guess this is getting into stylistic territory (rather than strictly finding bugs), since global |
I was about to file an issue about this when I found that there is already one (okay, two...). This misuse of an assertion inside a Instead, the test gets marked as failed and I see two solutions:
Let's discuss here instead of splitting the discussion. cc @onsi The linter could annotate functions which call
|
+1, as a dev would love to be able to more easily identify that a test failure was caused by incorrect |
This should really get fixed in Ginkgo/Gomega. The strict separation was a cute decision 11 years ago but this pain point is pretty terrible. Gomega captures the panic. I bet there's a way to annotate it so that (a) Gomega knows it came from Ginkgo and (b) can extract a callback from the panic object to tell Ginkgo to ignore the failure. I'll dig into it - though I'm not confident about timelines. Let me see if I can make time for it soon... |
I don't think the problem is the implementation, at least not in the linter. This is not trivial, but doable. The main problem is that the linter, for several reasons, does not know how to read the developer mind, yet. ;) Global The gomega current solution is to replace the assertion (Expect...To), with regular golang code, and call WDYT about adding something to gomega, so we'll could have the option to bail out, but using the existing matchers. e.g. Eventually(func(g Gomega){
...
ExpectOrExit(vm.Status).ToNot(Equal("Failed"))
...
g.Expect(somethingElse).To(BeSomethingElse())
...
}).WithPolling(...).WithTimeout(...).Should(Succeed()) or maybe Eventually(func(g Gomega){
...
Expect(vm.Status).ToNotOrExit(Equal("Failed"), "VM must not be in Failed status")
...
g.Expect(somethingElse).To(BeSomethingElse())
...
}).WithPolling(...).WithTimeout(...).Should(Succeed()) |
But that doesn't work, does it? The async Gomega methods keep trying and eventually time out. Only then is the test really done and has failed. What I and @onsi suggested would change that slightly: if such an assertion fails only once, the next retry may succeed and the test passes. This is a admittedly a change of behavior, but as the former behavior is basically undefined and/or falls under "don't do this" I think it's okay. |
Yes it is working. Using the global omega bails out early, and sometimes this is what we want. |
@pohly here is an example: var _ = Describe("global Gomega vs. gomega var", func() {
It("should bail out immediately", func() {
Eventually(func() error {
Expect(2).ToNot(Equal(2))
return nil
}).WithTimeout(10 * time.Second).WithPolling(time.Second).Should(Succeed())
})
It("should fail after 10 seconds", func() {
Eventually(func(g Gomega) {
g.Expect(2).ToNot(Equal(2))
}).WithTimeout(10 * time.Second).WithPolling(time.Second).Should(Succeed())
})
}) When running these tests, I can see the timing in the output:
|
@onsi said that So as much as I would like |
If a linter check can be implemented, then it could be kept disabled by default. If enabled, it becomes a per-project policy that global asserts should be avoided due to the ambiguity, with either |
I guess it's doable, but not so simple with the current code of the linter. It's a bit challenge to understand the scope of a gomega assertion expression, to understand if it's in a function deceleration that is an argument of a async-gomega expression. And this would be the easy part. because sometimes these functions are declared elsewhere, even in a different package. Anyway, if someone have some time for it, then some help is welcome. I have an initial idea how to approach it - not sure it will work. |
You probably know best how to do this 😅 If you don't get to it, then please ping the issue again and I'll check whether I can make some time for it. |
hmm. sorry everyone, I was clearly misremembering. the existing behavior is surprising and unfortunate to me and I would consider it a bug. it's a conversation that's out of scope on this linter issue. in retrospect a better design would have been to avoid the Given that some folks are relying on the current behavior I don't think there's a straightforward way to change things without breaking backward compatibility. I could add a configuration but now we're just layering on complexity instead of fixing the underlying issue. The other alternative is to build a roadmap for Gomega 2.0 and put this breaking change on it. @pohly if you'd like to explore making this configurable further please open an issue on Gomega. |
@onsi: I agree with your assessment. Let's wait with the configuration option because even if it existed, I would have the problem that I can't be sure whether it is safe to enable without the linter check and going through all code locations that it points out. |
I managed to build something, in #178 But there are two limitation I couldn't solve. First, if the Eventually calls a function from another package (e.g. test utils package), the linter can't find it. Also, if the function is defined as a variable, again, the linter can't catch it, e.g. ...
testSomething := func() error
Expect(something).To(BeSomething())
...
return nil
}
...
It("could not be detected by the linter", func() {
Eventually(testSomething).To(Succeed())
} This is a very common pattern in some projects. Also, I implemented it by running an internal analyzer first, to gather all the async scopes, and then the regular one that applies the linter rules. This new implementation causes a performance degradation. WDYT? should I merge it? |
I haven't looked at your implementation, but it sounds like you aren't using the facts API, are you? My (naive) expectation is that using facts it should be possible to mark functions in one package as "calls ginkgo.Fail" and use that fact when analyzing some other package. It should also be possible to handle function variables by copying that fact from the function into the variable while analyzing assignments. |
I was looking at facts in the past. I seems not working for me. I'll give it another try. BTW, finding function that calls ginkgo.Fail is very hard. I' looking for assertions that called from the global Gomega, that are used within an async assertion. |
True, it's very indirect when Gomega gets involved. Using |
Is your feature request related to a problem? Please describe.
There are two ways of passing functions to gomega's
Eventually
. One is to pass a function that returns values, and assert on the return values. The other is to pass a function that does assertions itself (link). In the latter case, the function should takeg Gomega
as an argument and callg.Expect
in order to assert.Failing to do this will assert against the "global" gomega object -- which will only pass (or fail) the first time and will not continue to poll the assertions. This is a bug, and it can be quite timing dependent at runtime (e.g. if your local dev machine is fast enough that the async assertion passes the first time, but the machine where CI runs is slower and so the first call to the assertion fails).
Describe the solution you'd like
We can catch this in the linter, by looking for code of the form:
This should even be auto-fixable (if the
func
is 0 arity at least) by adding theg Gomega
argument and converting all the calls toExpect
tog.Expect
.I don't know if the linter can see the source of a function that is passed by name to Eventually (like
Eventually(someFuncThatMightCallExpect)
) -- if it can, we can find erroneous global calls toExpect
in that function as well. But even catching the error when the function is a literal, as in the earlier example, would be helpful.Describe alternatives you've considered
I don't know of any other potential way to prevent or detect this error before runtime.
Additional context
n/a
The text was updated successfully, but these errors were encountered: