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

Migrate test data and infra to null safety. #3873

Merged
merged 21 commits into from
Mar 21, 2022
Merged

Migrate test data and infra to null safety. #3873

merged 21 commits into from
Mar 21, 2022

Conversation

polina-c
Copy link
Contributor

No description provided.

@polina-c polina-c marked this pull request as ready for review March 16, 2022 16:06
: _logPrefix = logPrefix != null ? '$logPrefix: ' : '';

final Directory projectFolder;
final String _logPrefix;
Process proc;
int procPid;
Process? proc;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

late

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 am confused by this below:

    if (proc != null) {
      debugPrint('Waiting for process to end');
      return proc!.exitCode.timeout(quitTimeout, onTimeout: killGracefully);
    }

proc is expected to become null at some point, but I do not see the code that assignes it to null. Am I missing something?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

searching for "proc = " I only see where proc is assigned to the result of await Process.start(...). How does the code snippet you pasted relate to marking proc as late? Does it give you an error? Can you remove the if (proc != null)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

_flutter = _flutterDriverFactory(Directory(testAppDirectory));
await _flutter.run(
_flutter = _flutterDriverFactory(Directory(testAppDirectory))
as FlutterRunTestDriver?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why does this need a nullable cast? _flutterDriverFactory should return a non-nullable value

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_flutter can probably be marked as a late field instead of being marked nullable (assuming setupEnvironment is always invoked at some point).

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 is assigned to null in tearDownEnvironment

Copy link
Contributor

@bkonyi bkonyi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kenzie covered most of the comments I have, but I found a few more places we can remove null checks.


Future<String> getFlutterIsolateId() async {
Future<String?> getFlutterIsolateId() async {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vm.isolates will never be null in practice, but it could be empty so you'll need a check to make sure there's an element before calling .first. Also, you can always assume an ID is provided (it's only nullable since tests with mocks like to omit them).

@@ -414,12 +414,12 @@ class FlutterRunTestDriver extends FlutterTestDriver {
Future<int> detach() async {
if (vmService != null) {
debugPrint('Closing VM service');
await vmService.dispose();
await vmService!.dispose();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assign vmService to a local so we only do one null check.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there were at least two null assersions, it would make sense. There is just one.

Copy link
Contributor

@bkonyi bkonyi Mar 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you do this:

final service = vmService;
if (service != null) {
  await service.dispose();
}

We only perform one null check instead of two. Anywhere with the pattern:

if (foo != null) {
  foo!.doSomething();
}

should either be updated to:

final localFoo = foo;
if (localFoo != null) {
  localFoo.doSomething();
}

or

foo?.doSomething();

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. What's the point to update

if (foo != null) {
  foo!.doSomething();
}

with

final localFoo = foo;
if (localFoo != null) {
  localFoo.doSomething();
}

?

The second piece seems less readable to me. Is there performance improvement?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In theory, yes. The code:

if (foo != null) {
  foo!.doSomething();
}

Effectively desugars to:

if (foo != null) {
  if (foo == null) {
    throw NullAssertionError();
  }
  foo.doSomething();
}

So we end up doing two null checks on foo. I wouldn't expect this to make a huge difference in performance in simple cases like this, but it's possible the compiler might try and do some optimizations when it knows nullable fields are non-null.

I'm not a huge fan of the readability either, but it's what we do throughout the core libraries too. An option to make it slightly better is to take advantage of (abuse?) variable shadowing to re-declare the variable in the scope of the function:

final foo = this.foo;
if (foo != null) {
  // Note: we no longer need a null assertion here since we're using the shadow variable.
  foo.doSomething();
}

I'm still not sure how I feel about this, but it definitely improves readability. For simple cases like this, I think it should be fine.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@leafpetersen @munificent do you have an opinion on whether we should recommend this style to users?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@munificent may have more to say here. Obviously, when applicable the foo?.bar() approach is fine (I like it). The style guide already softly recommends the local variable approach ("Consider"). I personally don't mind using a "!" when there's only one occurrence of the variable (even though there is the redundant check), but I'm pretty sure @lrhn yelled at me the last time I did that in a CL, so.... :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the link. I see there are three usages of '!' in the example. And, yes, the question is what we think about single usage.

There are actually two questions:

  1. Do we want to recommend local variable for this case to Dart users?
  2. Do we want to document the style as a standard across Dart code libraries?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH, my preferred answer to this is that you should use the new pattern feature that @munificent is working on to do the null check and the local variable binding in one step inside of the condition. Unfortunately he hasn't shipped it yet, dunno what the hold up is... :).

So I'll defer to others as to what we want to do in the interim, but I would caution against putting large effort into standardizing/cleaning up around one of the existing solutions given that I expect (hope) that we will be shipping a better solution within a year.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's cool! Let's wait then. I am curious how the code would look like.

_flutter = _flutterDriverFactory(Directory(testAppDirectory));
await _flutter.run(
_flutter = _flutterDriverFactory(Directory(testAppDirectory))
as FlutterRunTestDriver?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_flutter can probably be marked as a late field instead of being marked nullable (assuming setupEnvironment is always invoked at some point).

@polina-c polina-c marked this pull request as draft March 16, 2022 21:57
@polina-c polina-c marked this pull request as ready for review March 17, 2022 01:07
@polina-c
Copy link
Contributor Author

polina-c commented Mar 18, 2022

@bkonyi , replying your comments that do not have button 'Reply':

#3873 (comment)
_flutter is null checked, so it cannot be marked as late.

#3873 (comment)
I decided not to check for length before calling first, because this code does not fail for all existing tests. If/when it start failing, the test author will need to figure out what to do in case of empty collection. Makes sense?

@polina-c polina-c marked this pull request as draft March 18, 2022 15:53
@polina-c polina-c marked this pull request as ready for review March 20, 2022 13:32
@polina-c polina-c merged commit 87a3148 into flutter:master Mar 21, 2022
@polina-c polina-c deleted the data-infra branch March 21, 2022 17:53
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