-
Notifications
You must be signed in to change notification settings - Fork 887
fix: Fix cleanup in test helpers, prefer defer
in tests
#3113
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
Conversation
if runtime.GOOS == "windows" { | ||
// Closing the opened files for cleanup. | ||
err = urlFile.Close() | ||
require.NoError(t, err) | ||
assert.NoError(t, err) |
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.
Assert here, to avoid interrupting Close
below.
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 comment this in the file so it doesn't get reverted
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 kind of agree, but I'm less worried about this old code changing than new (incorrect) code going in. This is something everyone using require
must be very aware of (it halts execution flow).
Looks like I overlooked the fact that sometimes |
We should probably create a linter for this. cc @Emyrk our in-house linting DSL expert |
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.
👍 Linter would be cool and should be easy to detect all instances of t.Cleanup, with nolint for every exception
@@ -94,6 +94,8 @@ func TestAuthorizeAllEndpoints(t *testing.T) { | |||
t.Cleanup(func() { close(tickerCh) }) | |||
|
|||
ctx, cancelFunc := context.WithCancel(context.Background()) | |||
defer t.Cleanup(cancelFunc) // Defer to ensure cancelFunc is executed first. |
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.
Does defer cancelFunc()
not work here? I would expect defer to run before cleanups.
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.
See comment on the other instance: https://github.com/coder/coder/pull/3113/files#r928922910.
@@ -143,6 +143,8 @@ func newWithCloser(t *testing.T, options *Options) (*codersdk.Client, io.Closer) | |||
} | |||
|
|||
ctx, cancelFunc := context.WithCancel(context.Background()) | |||
defer t.Cleanup(cancelFunc) // Defer to ensure cancelFunc is executed first. |
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.
And 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.
Since this is a helper function, defer cancelFunc()
would run before the calling test gets a chance to do anything with it. And a t.Cleanup
without defer
would cancel the context too late, it would not trigger teardown. So I realize this is a bit of a sneaky way to do it, but this way t.Cleanup(cancelFunc)
will be left on top of the cleanup stack when this function exits, triggering the teardown we want (and same order as before the refactor).
Mixing t.Cleanup and defer can lead to unexpected order of execution.
193bb78
to
b624871
Compare
Writing the rule would be fairly straightforward: func testCleanup(m dsl.Matcher) {
m.Import("testing")
m.Match(`t.Cleanup($*fn)`).
Where(m["fn"].Type.Is("func()")).
Report("Only call t.Cleanup in helpers.").
Suggest("defer $fn()")
} But I'm not sure demanding a Is there a way we could improve upon this @Emyrk? For instance, if EDIT: Looks the above ruleguard also only works for single lines, if it's a multi-line function it won't be tagged. |
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.
Interesting. I always kinda wondered if t.Cleanup
vs defer
actually made an impact.
Yea matching negative cases is really tricky, but I think we can dial the Cleanup match to prevent false positives (helper funcs). We can expand the match to match in a whole test function, as I think helper functions have different signatures. m.Import("testing")
m.Match(`
$f(t *testing.T) {
$*_
t.Cleanup($*_)
$*_
}
`).
Where(m["f"].Text.Matches("^Test")).
Report("Only call t.Cleanup in helpers.").
At(m["c"]) The downside to this is that if the function calls |
@Emyrk Yeah, that may be the best approach. Thanks for the sample. We might want to add a separate case for |
Created #3181 to track the linting request and get this merged. |
This PR was sparked by #3109 (comment).
Taking a closer look at uses of
t.Cleanup
there were some places whererequire
interrupts cleanup.Motivation for preferring
defer
outside of helpers is that mixed use oft.Cleanup
anddefer
result in unexpected execution order, ideally we should strive to only uset.Cleanup
in helper functions.