-
Notifications
You must be signed in to change notification settings - Fork 175
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
Enable unawaited_futures #934
Conversation
TODO: |
This sounds helpful!! It looks like we have an issue for it: #731 🙂 |
c20a4a2
to
c2a820b
Compare
This is stacked on top of #937. |
a9031aa
to
ecb1a36
Compare
lib/widgets/actions.dart
Outdated
showErrorDialog(context: context, | ||
unawaited(showErrorDialog(context: context, |
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 just wrote down some thoughts on showErrorDialog
's API on a related PR:
#937 (comment)
so adding a link to that here.
I think the many unawaited
calls aroung showErrorDialog
here get fairly noisy. That makes the diff bigger, which is undesirable but could be OK… but then I think it also leaves enough noise in the tree that it might start to undermine the effect of this lint. If we find ourselves scattering unawaited
calls all over because there are some functions that just need it most times you call them, then the other more interesting unawaited
calls don't stand out so much.
Probably a good fix for that is to have our own pair of showFooDialog
wrappers provide an API that's more like the one I describe in that comment.
That would also be a better API for showDialog
upstream to have. But it's a tough change to make, because the change in return type would break tons of downstream callers; I don't see a clear way to get to a great API given that constraint. So I wouldn't invest in trying to make the change upstream.
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, I like the showSnackBar
API. Future
is more primitive in the sense that it doesn't carry much contextual information. The wrapper class is a step up from simply using typedef
, and offers good documentation discoverability.
da3c21c
to
826124c
Compare
This is stacked on top of #970 ( The PR is mostly small commits for refactors in a limited scope. |
test/api/core_test.dart
Outdated
checkRequest({'x': [1, 2]}, '/api/v1/example/route?x=%5B1%2C2%5D'); | ||
checkRequest({'x': {'y': 1}}, '/api/v1/example/route?x=%7B%22y%22%3A1%7D'); | ||
checkRequest({'x': RawParameter('foo')}, | ||
await checkRequest(null, '/api/v1/example/route'); |
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.
Are these needed? If I munge the expected value here, the test duly fails, even without await
:
00:05 +0 -1: ApiConnection.get [E]
Expected: a BaseRequest that:
is a Request
has method that:
equals 'GET'
has url that:
has toString that:
equals 'https://chat.example/api/v1/example/rote'
Actual: a BaseRequest that:
is a Request
has method that:
equals 'GET'
has url that:
has toString that:
Actual: 'https://chat.example/api/v1/example/route'
Which: differs at offset 38:
... example/rote
... example/route
^
package:checks/src/checks.dart 85:9 check.<fn>
package:checks/src/checks.dart 708:12 _TestContext.expect
package:checks/src/extensions/string.dart 108:13 StringChecks.equals
test/api/core_test.dart 28:26 main.<fn>.checkRequest.<fn>
===== asynchronous gap ===========================
dart:async _CustomZone.registerBinaryCallback
test/api/fake_api.dart 189:14 FakeApiConnection.with_
test/api/core_test.dart 23:32 main.<fn>.checkRequest
test/api/core_test.dart 37:5 main.<fn>
This test failed after it had already completed.
Make sure to use a matching library which informs the test runner
of pending async work.
package:checks/src/checks.dart 85:9 check.<fn>
package:checks/src/checks.dart 708:12 _TestContext.expect
package:checks/src/extensions/string.dart 108:13 StringChecks.equals
test/api/core_test.dart 28:26 main.<fn>.checkRequest.<fn>
===== asynchronous gap ===========================
dart:async _CustomZone.registerBinaryCallback
test/api/fake_api.dart 189:14 FakeApiConnection.with_
test/api/core_test.dart 23:32 main.<fn>.checkRequest
test/api/core_test.dart 37:5 main.<fn>
00:05 +17 -1: Some tests failed.
There is that warning message. But it points at a different solution: just make sure the test runner knows there's some pending async work.
That in turn can I think be done with check(…).completes()
. For example:
test('ApiConnection.get', () async {
- Future<void> checkRequest(Map<String, dynamic>? params, String expectedRelativeUrl) {
- return FakeApiConnection.with_(account: eg.selfAccount, (connection) async {
+ void checkRequest(Map<String, dynamic>? params, String expectedRelativeUrl) {
+ check(FakeApiConnection.with_(account: eg.selfAccount, (connection) async {
connection.prepare(json: {});
await connection.get(kExampleRouteName, (json) => json, 'example/route', params);
check(connection.lastRequest!).isA<http.Request>()
..method.equals('GET')
..url.asString.equals('${eg.realmUrl.origin}$expectedRelativeUrl')
..headers.deepEquals({
...authHeader(email: eg.selfAccount.email, apiKey: eg.selfAccount.apiKey),
...kFallbackUserAgentHeader,
})
..body.equals('');
- });
+ })).completes();
}
… In fact, looking at the implementation of completes
, I think it can be a bit cleaner than that. I'll send a PR.
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.
Skimming on mobile, this should also apply to c516066, right? I recall that check(SomethingFuture)
has some special handling.
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, the new finish
helper in #985 can probably apply to some other places as well. No need to go sweep through using it in all the existing tests, but worth using to simplify new tests or tests changed in this PR.
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.
Thanks @PIG208 for cleaning these up, and thanks @chrisbobbe for the previous review! Generally this all looks good; small comments below.
@@ -354,10 +355,9 @@ class NotificationDisplayManager { | |||
|
|||
assert(debugLog(' account: $account, narrow: $narrow')); | |||
// TODO(nav): Better interact with existing nav stack on notif open | |||
navigator.push(MaterialAccountWidgetRoute<void>(accountId: account.id, | |||
unawaited(navigator.push(MaterialAccountWidgetRoute<void>(accountId: account.id, |
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.
nit in commit message: duplicate text
The `Future` that `Navigator.push` returns resolves when a
corresponding `Navigator.pop` is called.
The future Navgiator.push returns completes when the there is a
corresponding Navigator.pop call. We don't use that as of now, so we
use unawaited to explicitly discard it.
test/widgets/actions_test.dart
Outdated
markNarrowAsRead(context, narrow); | ||
unawaited(markNarrowAsRead(context, narrow)); | ||
await tester.pump(Duration.zero); |
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.
Huh, odd. I wonder why this test is using tester.pump
— is that just a substitute for awaiting the future?
What if we replaced both these lines with this?
await markNarrowAsRead(context, narrow);
(Similarly for the other markNarrowAsRead
calls in this file.)
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's stuck forever, right before the connection.post
call to "messages/flags/narrow". It's puzzling that it happens to a FakeApiConnection
though. If I remove the connection.prepare call, the test would complete just fine, because updateMessageFlagsStartingFromAnchor
catches all errors.
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 guess this is because we call it inside testWidgets
. This issue is not specific to markNarrowAsRead
. For example, a store.sendMessage
call stucks as well. The FakeApiClient
returns a Future.delayed(response.delay, computation);
that does not complete in such tests.
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, I see. So basically it's running into the problem that awaitFakeAsync
solves.
It feels odd for these tests to not be awaiting that future — it means they're not ensuring that the requests have actually completed, and that the function has done the work it's going to do when it gets the responses, before they complete.
I'll send a quick separate PR that fixes these.
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.
test/model/store_test.dart
Outdated
finish(check(globalStore.updateAccount(eg.selfAccount.id, const AccountsCompanion( | ||
id: Value(1234)))).throws()); |
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.
For throws
, I think the normal thing is to await the future:
finish(check(globalStore.updateAccount(eg.selfAccount.id, const AccountsCompanion( | |
id: Value(1234)))).throws()); | |
await check(globalStore.updateAccount(eg.selfAccount.id, const AccountsCompanion( | |
id: Value(1234)))).throws(); |
From the docs linked in the commit message:
Expectation extension methods checking asynchronous behavior return a
Future
. The future should typically be awaited within the test body, […]
@@ -25,3 +25,4 @@ linter: | |||
no_literal_bool_comparisons: true | |||
prefer_relative_imports: true | |||
unnecessary_statements: true | |||
unawaited_futures: true |
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.
The commit message steps into a classic GitHub pitfall 🙂 :
Partially fixes: #731
GitHub doesn't understand the "partially", so that will close the issue.
If you don't mean "fixes", then the trick is to put some other word between it and the issue number. For example:
Fixes-partly: #731
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.
nit in commit message: the convention for email-header-style lines at the end of a commit message is that the words before the colon are joined by hyphens -
, not spaces
fe606f9
to
f9306cd
Compare
Thanks for the review! Updated the PR and fixed some more errors that appeared after rebasing (and applied #731) |
c37fd12
to
46ac571
Compare
testBinding.globalStore gets modified by multiple tests here. Signed-off-by: Zixuan James Li <[email protected]>
Signed-off-by: Zixuan James Li <[email protected]>
The future Navgiator.push returns completes when the there is a corresponding Navigator.pop call. We don't use that as of now, so we use unawaited to explicitly discard it. Other notes: * _onSubmitted doesn't need to return a Future<void> because we don't intend to call it with await. * This removes an unnecessary return in _navigateForNotification at the end of the method. It was added in commit 681a744. Signed-off-by: Zixuan James Li <[email protected]>
…Message Signed-off-by: Zixuan James Li <[email protected]>
Signed-off-by: Zixuan James Li <[email protected]>
The test helper receiveFcmMessage doesn't need to be asychronous given what it currently does. Signed-off-by: Zixuan James Li <[email protected]>
There may or may not be cases where later awaited calls to async functions effectively wait for store.handleEvent. We add await for store.handleEvent calls anyway for explicitness. Signed-off-by: Zixuan James Li <[email protected]>
These tests didn't break previously because the GlobalStore.add calls finished before the per account store was ever needed. A notable example is the test "find account among several" in test/notifications/display_test.dart. The accounts were added in parallel, which actually allowed the GlobalStore.add calls to bypass the invariant check performed by TestGlobalStore. The test passed anyway because it only requires the accounts to be there when some other checks are performed. The invariant check is to ensure that no accounts have the same email address within a realm. To fix this, we can potentially maintain a store of accounts with TestGlobalStore, and check for the constraints there. Signed-off-by: Zixuan James Li <[email protected]>
These tests actually worked as expected, even though we didn't await for the Future returned by FutureChecks.throws. This is because checks ensure that the asychronous expectations are complete before the test ends. Nonetheless, we should use await so that our intention is explicit (i.e. the future completes with an error as opposed to the future never completes and can't be awaited). See also: https://github.com/dart-lang/test/tree/master/pkgs/checks#checking-asynchronous-expectations Signed-off-by: Zixuan James Li <[email protected]>
The later `await tester.idle` call helps wait for the earlier mock platform message to be handled. We can remove that call and use await on the handlePlatformMessage call itself. Signed-off-by: Zixuan James Li <[email protected]>
These are function calls that need to happen asynchronously until we elapse the time with FakeAsync or WidgetTester. Signed-off-by: Zixuan James Li <[email protected]>
This explicitly ensures that these checks complete before the test ends. We can keep these single-line helper calls without adding `await`'s to all of them. Signed-off-by: Zixuan James Li <[email protected]>
Fixes-partly: zulip#731 Signed-off-by: Zixuan James Li <[email protected]>
The `finish` helper is only needed when the future that needs to finish isn't already one created by `check` (which takes care of the effect of `finish` for itself).
Thanks for the revision! All looks good; merging, with one NFC commit on top: 1aef868 api test [nfc]: Simplify some unawaited checks |
Fixes part of #731