Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Conversation

@xster
Copy link
Member

@xster xster commented Sep 5, 2019

I was pre-emptively adding some test before fixing flutter/flutter#37226.

Added some stuff to make the Scenario target runnable and interactive without wrapping with a test.

@xster xster requested review from dnfield and jmagman September 5, 2019 03:43
@dnfield
Copy link
Contributor

dnfield commented Sep 5, 2019

I was under the impression we didn't want to add swift to the language surface of anything we maintain. @cbracken @chinmaygarde thoughts?

@xster
Copy link
Member Author

xster commented Sep 5, 2019

We don't want to add swift to anything people depend on since there's users before 12.2. This is only in leaf targets that are only in tests that no one else uses.

@dnfield
Copy link
Contributor

dnfield commented Sep 5, 2019

I thought part of the idea was not adding more languages the team/contributors need to know

@chinmaygarde
Copy link
Member

If there isn't a pressing reason to use Swift, I'd rather stick to Objective-C. While Dan's point of needing to be language polyglot as being a potential hinderance to future contributions is certainly valid, I am also concerned that the usage of Swift might make it harder to integrate this target into the GN so that we can build all engine targets in one go. Currently, the way the Scenario's app is put together is a bit of a hack and needs a separate invocation after building the engine itself. Dan's comment here suggests that this is temporary till proper build system integration is in place. Needing to support Swift in GN will now become a prerequisite to this work.

As these are the first Swift TUs in the engine, I'd hold off on setting this precedent till wider consensus on Swift in the engine is reached.

@xster
Copy link
Member Author

xster commented Sep 6, 2019

Doh. I got excited too early.

In terms of our users, they're already there (https://dashboards.corp.google.com/_8211ecd6_e372_4f1c_b4f8_a1f4f0411822#oyjxd7sm0, 26% of our users use swift currently) but the build system is a sensible reason. I'll swap them back to obj-c.

@xster xster mentioned this pull request Sep 6, 2019
@xster xster added the Work in progress (WIP) Not ready (yet) for review! label Sep 7, 2019
@xster xster removed the Work in progress (WIP) Not ready (yet) for review! label Sep 9, 2019
@xster
Copy link
Member Author

xster commented Sep 9, 2019

Alright, re-translated. PTAL while I go puke some square brackets.

@xster xster added the CQ+1 label Sep 9, 2019
@xster
Copy link
Member Author

xster commented Sep 9, 2019

I don't know what CQ+1 does but it sounds fancy so I'm trying it.

@chinmaygarde
Copy link
Member

don't know what CQ+1 does but it sounds fancy

I edited the label description. It verified this PR in a try-job on LUCI. And yes, it is very fancy.


#import <Flutter/Flutter.h>

@interface Util : NSObject
Copy link
Member

@jmagman jmagman Sep 9, 2019

Choose a reason for hiding this comment

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

Util isn't a great name. Maybe make this a FlutterEngine category? Also _Nullable shouldn't be used on NSObjects.

NS_ASSUME_NONNULL_BEGIN
@interface FlutterEngine (ScenarioTesting)
- (void)runWithScenario:(NSString*)scenario
                                   withCompletion:(void (^)(void))engineRunCompletion;
@end
NS_ASSUME_NONNULL_END

Copy link
Member Author

Choose a reason for hiding this comment

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

oh ya, good idea. Done.

Also _Nullable shouldn't be used on NSObjects.

Not sure I understood. But I think I did what you meant.

Copy link
Member

Choose a reason for hiding this comment

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

Sorry, I meant to use nullable instead of _Nullable. Rule of thumb if the compiler lets you use nullable, then use it.

Copy link
Member Author

Choose a reason for hiding this comment

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

ohhhh withCompletion:(void (^_Nullable)(void))engineRunCompletion; should be withCompletion:(nullable void (^)(void))engineRunCompletion;. Sigh block syntax...

[engineStartedExpectation fulfill];
}];

[self waitForExpectations:[NSArray arrayWithObject:engineStartedExpectation] timeout:5];
Copy link
Member

Choose a reason for hiding this comment

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

You don't need to specify the expectation, you can use -[self waitForExpectationsWithTimeout:5.0:handler:nil]

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok

[self waitForExpectations:lifecycleExpectations timeout:5];

// Expected sequence from showing the FlutterViewController is inactive and resumed.
BOOL lifecyclesAsExpected = [lifecycleEvents
Copy link
Member

@jmagman jmagman Sep 9, 2019

Choose a reason for hiding this comment

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

Instead of doing this array checking, you can enforce expectation order. Something like:

XCTestExpectation *inactiveExpectation = [self expectationWithDescription:@"inactive"];
XCTestExpectation *resumedExpectation = [self expectationWithDescription:@"resume"];
[engine.lifecycleChannel setMessageHandler:^(id message, FlutterReply callback) {
  if ([message isEqual:@"AppLifecycleState.inactive"]) {
    [inactiveExpectation fulfill];
  else if ([message isEqual:@"AppLifecycleState.resumed"]) {
    [resumedExpectation fulfill];
  } else { 
    XCTFail(@"Unexpected lifecycle transition: %@", message);
  }
  }];
[self waitForExpectations:@[inactiveExpectation, resumedExpectation] timeout:5.0 enforceOrder:YES];

Copy link
Member Author

Choose a reason for hiding this comment

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

I think this would be confusing since I'm going to be checking that something isn't done once I fix the actual production problem in the next PR so I wouldn't be waiting for expectations in places.

Copy link
Member

Choose a reason for hiding this comment

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

What will the message be in the next PR that you're going to forbid that isn't covered by the XCTFail fall-through? Can you invert the expectation? I can write some example code with a concrete example.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, that's really useful. I think I'll go with your approach when I fix flutter/flutter#37226 and flip the check around.

How would I synchronize at each step to make sure nothing happens then? Would it still be timeout based?

Otherwise, I would expect the error message to be in the shape of:

Expecting transitions inactive, resume. But you got inactive, paused, inactive, resume, inactive, resume.

Copy link
Member

Choose a reason for hiding this comment

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

-inverted is timeout based liked all the expectations, unfortunately, and enforcing order doesn't make sense. So it will fail immediately if fulfilled, but then will need to wait for the timeout to succeed

[self waitForExpectations:@[inactiveExpectation, resumedExpectation] timeout:5.0 enforceOrder:YES];
[self waitForExpectations:@[invertedExpectation] timeout:1.0]; // inactiveExpectation, resumedExpectation already fulfilled so wait less time?

However, what I wrote above should fail with "inactive, paused, inactive, resume, inactive, resume"
as
[inactive silently fulfilled]
FAIL: Unexpected lifecycle transition: paused

If you want to also fail on inactive, resume, inactive, resume, you should also set the expectations' assertForOverFulfill to YES. And if you expect the fulfillment to be more than one, you can also set expectedFulfillmentCount.

Copy link
Member Author

Choose a reason for hiding this comment

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

I was halfway changing it to the way you said, but then I noticed that they both currently do the same thing. They both assert order, check on the same granularity and since NSNotificationCenter posts are synchronous, they also fail on unexpected events at the time of unexpected events.

I think I still prefer the present way which at places lists out the total map of expected behavior.

Copy link
Member Author

Choose a reason for hiding this comment

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

initWithDescription:@"Current implementation sends another AppLifecycleState event"]];
[[NSNotificationCenter defaultCenter]
postNotificationName:UIApplicationWillResignActiveNotification
object:nil];
Copy link
Member

Choose a reason for hiding this comment

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

This test is a bit confusing. Can you split it into a test per notification with the expectation order enforced instead of the array checks of the messages?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm going to be testing that there isn't an expectation per notification soon.

Copy link
Member

Choose a reason for hiding this comment

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

It would still make sense to assert the expectations between notifications? Can you break it down into separate tests at all?

Copy link
Member

Choose a reason for hiding this comment

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

I still think this is hard to read and it doesn't actually verify that the expectation was hit because of the notification, because it doesn't wait for the expectations between notifications. If this test ever fails because something is out of order it's going to be tricky to know which notification went awry and difficult to debug.

Copy link
Member Author

Choose a reason for hiding this comment

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

NSNotificationCenter posts are synchronous, and I'm removing these expectations right after in the next PR.

Copy link
Member

Choose a reason for hiding this comment

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

Well you would set up the expectation, post, then wait. We can talk about it in the next PR.


#import <Flutter/Flutter.h>
#import <XCTest/XCTest.h>
#import "../Scenarios/ScreenBeforeFlutter.h"
Copy link
Member

Choose a reason for hiding this comment

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

You can just use a project import here.

#import "ScreenBeforeFlutter.h"

Copy link
Member Author

Choose a reason for hiding this comment

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

Ummm weird. Not sure why I was running into errors trying it before. Done.

@jmagman
Copy link
Member

jmagman commented Sep 9, 2019

If there isn't a pressing reason to use Swift, I'd rather stick to Objective-C. While Dan's point of needing to be language polyglot as being a potential hinderance to future contributions is certainly valid, I am also concerned that the usage of Swift might make it harder to integrate this target into the GN so that we can build all engine targets in one go.

Once Swift in GN is figured out, a good unit test with minimum language-polyglot overhead would be one that imports the Flutter module to make sure it can be imported in Swift.

import Flutter

class SwiftModuleTests: XCTestCase {
    func testModuleImport() {
        // This test will fail to compile if the Swift module cannot be imported.
    }
}

@xster
Copy link
Member Author

xster commented Sep 10, 2019

Thanks for reviewing

// Expected sequence from showing the FlutterViewController is inactive and resumed.
BOOL lifecyclesAsExpected = [lifecycleEvents
isEqualToArray:@[ @"AppLifecycleState.inactive", @"AppLifecycleState.resumed" ]];
XCTAssert(lifecyclesAsExpected,
Copy link
Member

Choose a reason for hiding this comment

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

XCTAssertEqualObjects

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

Copy link
Contributor

@dnfield dnfield left a comment

Choose a reason for hiding this comment

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

Few nits on the Dart code. I haven't looked at the ObjC too closely since it seems like @jmagman has that covered and probably is finding far more than I would have :)

) {
window.sendPlatformMessage(name, data, null);
}
} No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: new line at EOF.

Copy link
Member Author

Choose a reason for hiding this comment

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

ok

canvas.drawRect(
Rect.fromLTWH(0, 0, window.physicalSize.width - 1, window.physicalSize.height - 1),
Paint()..color = const Color.fromARGB(255, 255, 255, 255),
);
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe just canvas.drawPaint?

Copy link
Member Author

Choose a reason for hiding this comment

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

oh cool, never noticed this API. Neat


@override
void onDrawFrame() {
window.scheduleFrame();
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you might want this to go to the end of onMetricsChanged and _pop. Right now this is drawing the same frames over and over again even if nothing has changed.

Copy link
Member Author

Choose a reason for hiding this comment

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

ya, makes sense

initWithDescription:@"Current implementation sends another AppLifecycleState event"]];
[[NSNotificationCenter defaultCenter]
postNotificationName:UIApplicationWillResignActiveNotification
object:nil];
Copy link
Member

Choose a reason for hiding this comment

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

Well you would set up the expectation, post, then wait. We can talk about it in the next PR.

@xster xster merged commit 46ff053 into flutter:master Sep 12, 2019
@xster xster deleted the lifecycle-test branch September 12, 2019 23:36
aam added a commit to aam/engine that referenced this pull request Sep 13, 2019
xster added a commit that referenced this pull request Sep 13, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants