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

Let first frame listeners self remove #11014

Closed
wants to merge 5 commits into from

Conversation

xster
Copy link
Member

@xster xster commented Aug 15, 2019

See test:

Let the listener callbacks remove themselves for a one-time callback without java.util.ConcurrentModificationException.

private static class FlutterJNITest extends FlutterJNI {
protected void onFirstFrame() {
// Exposed here for tests.
super.onFirstFrame();
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Discussed offline. Probably won't need this class once I move the package up one level at which point it'll be in the same package as the FlutterJNI.

@Before
public void setUp() {
jniUnderTest = new FlutterJNITest();
rendererUnderTest = new FlutterRenderer(jniUnderTest);
Copy link
Contributor

Choose a reason for hiding this comment

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

I recommend instantiating the object under test within each test. You never know when a test is going to be introduced that requires some kind of unusual instantiation, and when it is, that developer will be forced to change every other test to make that happen. Better to just let each test configure the object under test as it desired.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, I don't think we need to suffix things with "test"...everything in here is related to a test.

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 the first point was to not use test class setup/teardowns? It's probably a bit philosophical but I think it'll be efficient to just use industry standard practices (such as cs/@before$ file:test.java)
  • I think this case is a bit exceptional but most of the time, there'd be things that are under test and a bunch more things that are mocks supporting those real classes under test. This makes it clear which are actors in the test and which aren't (e.g. cs/"undertest =" file:test.java) but I don't feel strongly about it.

@Test
public void firstFrameListenerCanRemoveThemselves() {
AtomicInteger callbackCalled = new AtomicInteger(0);
OnFirstFrameRenderedListener callback = new OnFirstFrameRenderedListener() {
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 can probably accomplish this with a mock with less code and complexity than what you have here...

Copy link
Member Author

Choose a reason for hiding this comment

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

mockito is unfortunately a bit less capable for these level triggered stuff (mostly due to java)

An equivalent here will turn into

@Mock OnFirstFrameRenderedListener callback;
when(callback. onFirstFrameRendered()).thenAnswer(
    new Answer() {
         public Object answer(InvocationOnMock invocation) {
             rendererUnderTest.removeOnFirstFrameRenderedListener(callback);
         }
    });

which is even less directly readable.

}

@Test
public void firstFrameListenerCanRemoveThemselves() {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd like to follow a policy whereby any true unit test prefixes the test with "it", e.g., "itAllowsFirstFrameListenersToRemoveThemselves". This pattern will help ensure that unit tests really are unit tests by grammatically framing in terms of the object under test.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, this isn't really testing the broader capability of removing oneself as a listener. This is specifically testing the capabilityof removing oneself during a first frame callback. You may want to make that explicit in the test name.

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. Renamed.

I think the only way to not remove themselves during the first frame would be something like itAllowsFirstFrameListenersToRemoveThemselvesWithoutUsingAHandlerToPostBackADeferredRemoval :D

How's itAllowsFirstFrameListenersToRemoveThemselvesInline?

};

rendererUnderTest.addOnFirstFrameRenderedListener(callback);
jniUnderTest.onFirstFrame();
Copy link
Contributor

Choose a reason for hiding this comment

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

I would recommend that in all tests we clearly demarcate between setup/execution/verification. In this case everything up to and including the addOnFirstFrameRenderedListener is part of the setup, the onFirstFrame() invocation is the execution, and everything that follows is verification.

In addition to placing a blank line and maybe a comment between these 2 lines above, you could also slightly refactor what comes below. I dont think you actually need to interleave 2 onFirstFrame()s and 2 asserts. You can simply invoke onFirstFrame() twice in a row, and then verify that the callback was invoked exactly 1 time.

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 it's still worth checking for the actual timing (e.g. that we don't have false positives on onFirstFrame only calling listeners on second invocation or something)

ensureRunningOnMainThread();
if (renderSurface != null) {
renderSurface.onFirstFrameRendered();
}
// TODO(mattcarroll): log dropped messages when in debug mode (https://github.com/flutter/flutter/issues/25391)

for (OnFirstFrameRenderedListener listener : firstFrameListeners) {
CopyOnWriteArrayList<OnFirstFrameRenderedListener> mutableListeners =
Copy link
Contributor

Choose a reason for hiding this comment

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

This leads me to believe that this is not a test of FlutterRenderer but rather a test of FlutterJNI. I think something needs to change in this PR to achieve correctness in that regard. If you're truly unit testing FlutterRenderer then you shouldn't have to make this protected. If you're testing FlutterJNI then the test suite should be switched accordingly.

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 there's a couple of layers of testings we'll need either way.

  • I think we'll need strict unit tests. Where we're testing classes. I added a FlutterJNITest for this one.
  • Some of the classes are just not part of the public API are not going to be used. We should have medium tests that tests things between an API surface and some sort of abstraction layer. In this case, I think making things that platform_view_android_jni.cc calls also test callable probably makes sense.
  • We need integration tests that also includes C++ stuff but since those are heavier, they won't be per feature.

import java.util.concurrent.atomic.AtomicInteger;

@Config(manifest=Config.NONE)
@RunWith(RobolectricTestRunner.class)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why does this need to be robolectric?

Copy link
Member Author

Choose a reason for hiding this comment

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

FlutterRenderer imports android classes

@xster xster force-pushed the listener-self-remove branch from 037b7e3 to a9a9247 Compare August 20, 2019 21:53
@xster
Copy link
Member Author

xster commented Aug 22, 2019

cc @RedBrogdon since you wanted to follow along

@matthew-carroll
Copy link
Contributor

I think we can close this PR due to related changes made here:
#11357

@xster
Copy link
Member Author

xster commented Sep 3, 2019

yup, thanks for re-doing all the tests

@xster xster closed this Sep 3, 2019
@xster xster deleted the listener-self-remove branch September 3, 2019 22:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants