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

[pigeon] Move test support logic out of the default Dart output. #246

Merged
merged 1 commit into from
Dec 10, 2020

Conversation

Hixie
Copy link
Contributor

@Hixie Hixie commented Nov 20, 2020

This will allow us to move the mock logic out of the core framework.

I also got distracted and changed some of the output to be cleaner while I was at it.

@Hixie
Copy link
Contributor Author

Hixie commented Nov 20, 2020

flutter/plugins#3281 applies these changes to video_player.

@Hixie Hixie marked this pull request as draft November 20, 2020 20:02
@Hixie Hixie force-pushed the extract_tests branch 11 times, most recently from dd8a506 to 0bcb6a8 Compare November 24, 2020 06:11
@Hixie Hixie marked this pull request as ready for review November 30, 2020 18:16
@Hixie Hixie requested a review from gaaclarke December 1, 2020 00:45
Copy link
Member

@gaaclarke gaaclarke left a comment

Choose a reason for hiding this comment

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

I'm going to pick this up tomorrow, I got to it late and it's taking longer than I expected. Generally looking good I want to run it a few times while I think about it. I think the encode and decode methods was a good compromise while I'm looking at it.

final String nullTag = opt.isNullSafe ? '?' : '';
final String nullable = opt.isNullSafe ? '?' : '';
final String notNullable = opt.isNullSafe ? '!' : '';
bool first = true;
Copy link
Member

Choose a reason for hiding this comment

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

I find these variable names confusing. nullTag described it as a part of the language, nullable doesn't describe it as a character that has semantic meaning. I checked the Dart documentation to see what they call the ! operator and unfortunately I couldn't find a name. In other languages it's called unwrapping.

How about nullableTag and unwrapOperator or unwrapper?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oh i didn't realise "null tag" was the official name. Happy to change these to whatever you want.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done, ?=nulltag and !=unwrapOperator

} else if (replyMap['error'] != null) {
\tfinal Map<dynamic, dynamic> error = replyMap['${Keys.error}'];
\tfinal Map<Object$nullable, Object$nullable> error = replyMap['${Keys.error}'] as Map<Object$nullable, Object$nullable>;
Copy link
Member

Choose a reason for hiding this comment

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

Why did you change this? Because dynamic doesn't denote null-safety? Why was there a dynamic and an Object before NNBD? Are some things not Objects?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

dynamic hides bugs, because it disables all type checking. By using Object? we can see where we're casting and we can make sure the methods (etc) we're using on those objects don't have typos.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(To answer your questions, dynamic is essentially the same as Object?, except that the language never checks what you call on objects that are marked dynamic, it's like you're in an untyped language. Everything is an Object?.)

@gaaclarke gaaclarke changed the title Move test support logic out of the default Dart output. [pigeon] Move test support logic out of the default Dart output. Dec 1, 2020
Copy link
Member

@gaaclarke gaaclarke left a comment

Choose a reason for hiding this comment

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

Okay, finished up the review. The only big issues I noticed is that there is a null check that has been removed from the decode method. That makes me nervous, do you think that is correct and that it is adequately tested already? Also there is an added case where channels can fail silently and bypass the provided API.

/// calls from your real [HostApi] class in Dart instead of the host
/// platform code, as is typical.
///
/// When using this, you must specify the `--out-test-dart` argument
Copy link
Member

Choose a reason for hiding this comment

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

You need to use snake case, not kebob case here.

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.

from: context.fromUri(
path.toUri(path.dirname(path.absolute(options.dartTestOut))),
),
);
Copy link
Member

Choose a reason for hiding this comment

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

You do the same path juggling here twice, it could be more clear to pull some of that into a helper function.

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 you mean the code two lines above this, it's subtly different in ways that makes it hard to usefully factor out. :-(
(I spent some time trying to find a way to factor it out cleanly and it ended up just being simpler to do it this way.)
That said if you have any suggestions on how to factor it out let me know.

Copy link
Member

Choose a reason for hiding this comment

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

Couldn't you just do this?

String posixify(String path) {
  final path.Context context = path.Context(style: path.Style.posix);
  return context.fromUri(path.toUri(path.absolute(path));
}
posixify(options.dartOut);
posixify(path.dirname(options.dartTestOut);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The theory being that path.dirname(path.absolute(x)) == path.absolute(path.dirname(x))? Seems reasonable, let me give it a try.

Comment on lines +23 to +24
final Map<Object, Object> pigeonMap = message as Map<Object, Object>;
return SearchReply()
Copy link
Member

Choose a reason for hiding this comment

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

_fromMap used to accept null input, now decode doesn't. I see there appears to be one place where decode is called without a null check to the argument. Are you sure this is safe to remove?

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 must have misread the code; it looked like it used to crash when the message was null, but I see in the output for video_player that there's a null check.

I'm happy to make it accept null again.

(Is there ever a valid reason to send 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.

Oh I remember why I made it not accept null -- it's not so much that the input is non-nullable, so much as that the output is non-nullable. With a null input I don't know what to return. I could return null but then I need to null check around every call site anyway, and it seemed cleaner to have the null check before the call than after the call.

Copy link
Member

@gaaclarke gaaclarke Dec 8, 2020

Choose a reason for hiding this comment

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

Yea, that makes sense. I just checked the output with nullsafety and it looks like this:

  static SearchReply _fromMap(Map<dynamic, dynamic> pigeonMap) {
    final SearchReply result = SearchReply();
    result.result = pigeonMap['result'];
    result.error = pigeonMap['error'];
    return result;
  }

Looks like I already got rid of the null check, this generated code just hasn't been updated in a while.

channel.setMessageHandler(null);
} else {
channel.setMessageHandler((Object message) async {
if (message == null) {
Copy link
Member

Choose a reason for hiding this comment

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

We shouldn't bypass calling into the api. Either we support null here and pass it through to the api, or we don't and we assert. Silently bypassing the API is just going to create hard to debug situations.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But then FlutterSearchApi.search has to take a nullable argument. Do we want that?

Copy link
Contributor Author

@Hixie Hixie Dec 1, 2020

Choose a reason for hiding this comment

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

I think this is the main open question here. (bold so I can find this in the github page)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok sounds like from the previous comment that you're good leaving this as-is.


final Map<dynamic, dynamic> replyMap = await channel.send(requestMap);
final Map<Object, Object> replyMap =
await channel.send(encoded) as Map<Object, Object>;
Copy link
Member

Choose a reason for hiding this comment

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

Are these casts incurring any runtime cost?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My understanding is that casts are cheaper than dynamic dispatch on dynamic these days, but I could be wrong.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(To put it another way: there were casts before, this just made them visible.)

Copy link
Contributor Author

@Hixie Hixie Dec 1, 2020

Choose a reason for hiding this comment

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

(Specifically, before, we were assigning a dynamic (the return value of await channel.send(encoded)) to a variable of type Map<dynamic, dynamic>, which implies a cast. Now we are assigning an Object to a variable of type Map<Object, Object> with an explicit cast. I wouldn't be surprised if the assembly was actually byte for byte identical, though I haven't checked.)

final SearchReply output = api.search(input);
return <dynamic, dynamic>{'result': output._toMap()};
});
return SearchReply.decode(replyMap['result']);
Copy link
Member

Choose a reason for hiding this comment

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

Here's the call whose behavior would change as a result of removing the null check in decode.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, yes. In null-safe mode this has a ! in the new generated code. I can check it for a result key and throw if it's missing, if you like. Or do something else. Your call.

Copy link
Member

@gaaclarke gaaclarke left a comment

Choose a reason for hiding this comment

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

I think this is good. I got a confused by the skip in checking in generated code. Thanks ian.

This will allow us to move the mock logic out of the core framework.
@Hixie
Copy link
Contributor Author

Hixie commented Dec 10, 2020

Comments applied, tests pass! I'ma land it!

@Hixie Hixie merged commit 1313c80 into flutter:master Dec 10, 2020
@Hixie Hixie deleted the extract_tests branch December 10, 2020 21:29
austinstoker pushed a commit to austinstoker/packages that referenced this pull request Apr 29, 2022
Deep location example: Migrate to null safety
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.

2 participants