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

Commit 956f086

Browse files
committed
add tests
1 parent e449541 commit 956f086

File tree

14 files changed

+452
-154
lines changed

14 files changed

+452
-154
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
.*.sw?
55
.DS_Store
66
.ccls-cache
7+
.cache
78
.classpath
89
.clangd/
910
.cproject

shell/platform/android/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,7 @@ action("robolectric_tests") {
466466
"test/io/flutter/embedding/android/RobolectricFlutterActivity.java",
467467
"test/io/flutter/embedding/engine/FlutterEngineCacheTest.java",
468468
"test/io/flutter/embedding/engine/FlutterEngineConnectionRegistryTest.java",
469+
"test/io/flutter/embedding/engine/FlutterEngineGroupComponentTest.java",
469470
"test/io/flutter/embedding/engine/FlutterEngineTest.java",
470471
"test/io/flutter/embedding/engine/FlutterJNITest.java",
471472
"test/io/flutter/embedding/engine/FlutterShellArgsTest.java",

shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import android.content.Context;
88
import androidx.annotation.NonNull;
99
import androidx.annotation.Nullable;
10+
import androidx.annotation.VisibleForTesting;
1011
import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint;
1112
import java.util.ArrayList;
1213
import java.util.List;
@@ -35,7 +36,7 @@ public FlutterEngineGroup(@NonNull Context context) {
3536
}
3637

3738
private final Context context;
38-
private final List<FlutterEngine> activeEngines = new ArrayList<>();
39+
/* package */ @VisibleForTesting final List<FlutterEngine> activeEngines = new ArrayList<>();
3940

4041
public FlutterEngine createAndRunDefaultEngine() {
4142
return createAndRunEngine(null);
@@ -44,7 +45,7 @@ public FlutterEngine createAndRunDefaultEngine() {
4445
public FlutterEngine createAndRunEngine(@Nullable DartEntrypoint dartEntrypoint) {
4546
FlutterEngine engine = null;
4647
if (activeEngines.size() == 0) {
47-
engine = new FlutterEngine(context);
48+
engine = createEngine(context);
4849
}
4950

5051
if (dartEntrypoint == null) {
@@ -75,4 +76,9 @@ public void onEngineDestroy() {
7576
});
7677
return engine;
7778
}
79+
80+
@VisibleForTesting
81+
/* package */ FlutterEngine createEngine(Context context) {
82+
return new FlutterEngine(context);
83+
}
7884
}

shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public class FlutterJNI {
109109
* <p>This method should only be called once across all FlutterJNI instances.
110110
*/
111111
public void loadLibrary() {
112-
if (loadLibraryCalled = true) {
112+
if (loadLibraryCalled) {
113113
Log.w(TAG, "FlutterJNI.loadLibrary called more than once");
114114
}
115115

@@ -127,7 +127,7 @@ public void loadLibrary() {
127127
* <p>This method should only be called once across all FlutterJNI instances.
128128
*/
129129
public void prefetchDefaultFontManager() {
130-
if (prefetchDefaultFontManagerCalled = true) {
130+
if (prefetchDefaultFontManagerCalled) {
131131
Log.w(TAG, "FlutterJNI.prefetchDefaultFontManager called more than once");
132132
}
133133

@@ -156,7 +156,7 @@ public void init(
156156
@NonNull String appStoragePath,
157157
@NonNull String engineCachesPath,
158158
long initTimeMillis) {
159-
if (initCalled = true) {
159+
if (initCalled) {
160160
Log.w(TAG, "FlutterJNI.init called more than once");
161161
}
162162

@@ -214,7 +214,7 @@ public static String getObservatoryUri() {
214214
}
215215

216216
public static void setRefreshRateFPS(float refreshRateFPS) {
217-
if (setRefreshRateFPSCalled = true) {
217+
if (setRefreshRateFPSCalled) {
218218
Log.w(TAG, "FlutterJNI.setRefreshRateFPS called more than once");
219219
}
220220

shell/platform/android/test/io/flutter/FlutterTestSuite.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import io.flutter.embedding.android.FlutterViewTest;
1414
import io.flutter.embedding.engine.FlutterEngineCacheTest;
1515
import io.flutter.embedding.engine.FlutterEngineConnectionRegistryTest;
16+
import io.flutter.embedding.engine.FlutterEngineGroupComponentTest;
1617
import io.flutter.embedding.engine.FlutterJNITest;
1718
import io.flutter.embedding.engine.LocalizationPluginTest;
1819
import io.flutter.embedding.engine.RenderingComponentTest;
@@ -59,6 +60,7 @@
5960
FlutterAndroidComponentTest.class,
6061
FlutterEngineCacheTest.class,
6162
FlutterEngineConnectionRegistryTest.class,
63+
FlutterEngineGroupComponentTest.class,
6264
FlutterEngineTest.class,
6365
FlutterFragmentActivityTest.class,
6466
FlutterFragmentTest.class,
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.embedding.engine;
6+
7+
import static org.junit.Assert.assertEquals;
8+
import static org.junit.Assert.assertNotNull;
9+
import static org.junit.Assert.assertTrue;
10+
import static org.mockito.Mockito.any;
11+
import static org.mockito.Mockito.anyInt;
12+
import static org.mockito.Mockito.atLeast;
13+
import static org.mockito.Mockito.doAnswer;
14+
import static org.mockito.Mockito.doReturn;
15+
import static org.mockito.Mockito.mock;
16+
import static org.mockito.Mockito.never;
17+
import static org.mockito.Mockito.spy;
18+
import static org.mockito.Mockito.times;
19+
import static org.mockito.Mockito.verify;
20+
import static org.mockito.Mockito.when;
21+
22+
import android.content.Context;
23+
import android.content.pm.PackageManager.NameNotFoundException;
24+
import io.flutter.FlutterInjector;
25+
import io.flutter.embedding.engine.FlutterEngine;
26+
import io.flutter.embedding.engine.FlutterEngine.EngineLifecycleListener;
27+
import io.flutter.embedding.engine.dart.DartExecutor;
28+
import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint;;
29+
import io.flutter.embedding.engine.FlutterEngineGroup;
30+
import io.flutter.embedding.engine.FlutterJNI;
31+
import io.flutter.embedding.engine.loader.FlutterLoader;
32+
import io.flutter.plugin.platform.PlatformViewsController;
33+
import io.flutter.plugins.GeneratedPluginRegistrant;
34+
import java.util.List;
35+
import org.junit.After;
36+
import org.junit.Before;
37+
import org.junit.Test;
38+
import org.junit.runner.RunWith;
39+
import org.mockito.ArgumentCaptor;
40+
import org.mockito.Mock;
41+
import org.mockito.MockitoAnnotations;
42+
import org.mockito.invocation.InvocationOnMock;
43+
import org.mockito.stubbing.Answer;
44+
import org.robolectric.RobolectricTestRunner;
45+
import org.robolectric.RuntimeEnvironment;
46+
import org.robolectric.annotation.Config;
47+
48+
// It's a component test because it tests both FlutterEngineGroup and FlutterEngine.
49+
@Config(manifest = Config.NONE)
50+
@RunWith(RobolectricTestRunner.class)
51+
public class FlutterEngineGroupComponentTest {
52+
@Mock FlutterJNI flutterJNI;
53+
FlutterEngineGroup engineGroupUnderTest;
54+
FlutterEngine firstEngineUnderTest;
55+
boolean jniAttached;
56+
57+
@Before
58+
public void setUp() {
59+
MockitoAnnotations.initMocks(this);
60+
jniAttached = false;
61+
when(flutterJNI.isAttached()).thenAnswer(invocation -> jniAttached);
62+
doAnswer(
63+
new Answer() {
64+
@Override
65+
public Object answer(InvocationOnMock invocation) throws Throwable {
66+
jniAttached = true;
67+
return null;
68+
}
69+
})
70+
.when(flutterJNI)
71+
.attachToNative(false);
72+
GeneratedPluginRegistrant.clearRegisteredEngines();
73+
74+
firstEngineUnderTest = spy(new FlutterEngine(
75+
RuntimeEnvironment.application,
76+
mock(FlutterLoader.class),
77+
flutterJNI,
78+
/*dartVmArgs=*/ new String[] {},
79+
/*automaticallyRegisterPlugins=*/ false));
80+
when(firstEngineUnderTest.getDartExecutor()).thenReturn(mock(DartExecutor.class));
81+
engineGroupUnderTest = new FlutterEngineGroup(RuntimeEnvironment.application) {
82+
@Override
83+
FlutterEngine createEngine(Context context) {
84+
return firstEngineUnderTest;
85+
}
86+
};
87+
}
88+
89+
@After
90+
public void tearDown() {
91+
GeneratedPluginRegistrant.clearRegisteredEngines();
92+
engineGroupUnderTest = null;
93+
firstEngineUnderTest = null;
94+
}
95+
96+
@Test
97+
public void listensToEngineDestruction() {
98+
FlutterEngine firstEngine = engineGroupUnderTest.createAndRunEngine(mock(DartEntrypoint.class));
99+
assertEquals(1, engineGroupUnderTest.activeEngines.size());
100+
101+
firstEngine.destroy();
102+
assertEquals(0, engineGroupUnderTest.activeEngines.size());
103+
}
104+
105+
@Test
106+
public void canRecreateEngines() {
107+
FlutterEngine firstEngine = engineGroupUnderTest.createAndRunEngine(mock(DartEntrypoint.class));
108+
assertEquals(1, engineGroupUnderTest.activeEngines.size());
109+
110+
firstEngine.destroy();
111+
assertEquals(0, engineGroupUnderTest.activeEngines.size());
112+
113+
FlutterEngine secondEngine = engineGroupUnderTest.createAndRunEngine(mock(DartEntrypoint.class));
114+
assertEquals(1, engineGroupUnderTest.activeEngines.size());
115+
// They happen to be equal in our test since we mocked it to be so.
116+
assertEquals(firstEngine, secondEngine);
117+
}
118+
119+
@Test
120+
public void canSpawnMoreEngines() {
121+
FlutterEngine firstEngine = engineGroupUnderTest.createAndRunEngine(mock(DartEntrypoint.class));
122+
assertEquals(1, engineGroupUnderTest.activeEngines.size());
123+
124+
doReturn(mock(FlutterEngine.class)).when(firstEngine).spawn(any(DartEntrypoint.class));
125+
126+
FlutterEngine secondEngine = engineGroupUnderTest.createAndRunEngine(mock(DartEntrypoint.class));
127+
assertEquals(2, engineGroupUnderTest.activeEngines.size());
128+
129+
firstEngine.destroy();
130+
assertEquals(1, engineGroupUnderTest.activeEngines.size());
131+
132+
// Now the second spawned engine is the only one left and it will be called to spawn the next
133+
// engine in the chain.
134+
when(secondEngine.spawn(any(DartEntrypoint.class))).thenReturn(mock(FlutterEngine.class));
135+
136+
FlutterEngine thirdEngine = engineGroupUnderTest.createAndRunEngine(mock(DartEntrypoint.class));
137+
assertEquals(2, engineGroupUnderTest.activeEngines.size());
138+
}
139+
140+
}

shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineTest.java

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
import static org.mockito.Mockito.any;
77
import static org.mockito.Mockito.anyInt;
88
import static org.mockito.Mockito.atLeast;
9+
import static org.mockito.Mockito.doAnswer;
910
import static org.mockito.Mockito.mock;
11+
import static org.mockito.Mockito.never;
1012
import static org.mockito.Mockito.times;
1113
import static org.mockito.Mockito.verify;
1214
import static org.mockito.Mockito.when;
@@ -15,6 +17,7 @@
1517
import android.content.pm.PackageManager.NameNotFoundException;
1618
import io.flutter.FlutterInjector;
1719
import io.flutter.embedding.engine.FlutterEngine;
20+
import io.flutter.embedding.engine.FlutterEngine.EngineLifecycleListener;
1821
import io.flutter.embedding.engine.FlutterJNI;
1922
import io.flutter.embedding.engine.loader.FlutterLoader;
2023
import io.flutter.plugin.platform.PlatformViewsController;
@@ -27,6 +30,8 @@
2730
import org.mockito.ArgumentCaptor;
2831
import org.mockito.Mock;
2932
import org.mockito.MockitoAnnotations;
33+
import org.mockito.invocation.InvocationOnMock;
34+
import org.mockito.stubbing.Answer;
3035
import org.robolectric.RobolectricTestRunner;
3136
import org.robolectric.RuntimeEnvironment;
3237
import org.robolectric.annotation.Config;
@@ -35,11 +40,23 @@
3540
@RunWith(RobolectricTestRunner.class)
3641
public class FlutterEngineTest {
3742
@Mock FlutterJNI flutterJNI;
43+
boolean jniAttached;
3844

3945
@Before
4046
public void setUp() {
4147
MockitoAnnotations.initMocks(this);
42-
when(flutterJNI.isAttached()).thenReturn(true);
48+
jniAttached = false;
49+
when(flutterJNI.isAttached()).thenAnswer(invocation -> jniAttached);
50+
doAnswer(
51+
new Answer() {
52+
@Override
53+
public Object answer(InvocationOnMock invocation) throws Throwable {
54+
jniAttached = true;
55+
return null;
56+
}
57+
})
58+
.when(flutterJNI)
59+
.attachToNative(false);
4360
GeneratedPluginRegistrant.clearRegisteredEngines();
4461
}
4562

@@ -108,17 +125,14 @@ public void itNotifiesPlatformViewsControllerWhenDevHotRestart() {
108125

109126
@Test
110127
public void itNotifiesPlatformViewsControllerAboutJNILifecycle() {
111-
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
112-
when(mockFlutterJNI.isAttached()).thenReturn(true);
113-
114128
PlatformViewsController platformViewsController = mock(PlatformViewsController.class);
115129

116130
// Execute behavior under test.
117131
FlutterEngine engine =
118132
new FlutterEngine(
119133
RuntimeEnvironment.application,
120134
mock(FlutterLoader.class),
121-
mockFlutterJNI,
135+
flutterJNI,
122136
platformViewsController,
123137
/*dartVmArgs=*/ new String[] {},
124138
/*automaticallyRegisterPlugins=*/ false);
@@ -178,4 +192,44 @@ public void itCanUseFlutterLoaderInjectionViaFlutterInjector() throws NameNotFou
178192
verify(mockFlutterLoader, times(1)).startInitialization(any());
179193
verify(mockFlutterLoader, times(1)).ensureInitializationComplete(any(), any());
180194
}
195+
196+
@Test
197+
public void itNotifiesListenersForDestruction() throws NameNotFoundException {
198+
Context context = mock(Context.class);
199+
Context packageContext = mock(Context.class);
200+
201+
when(context.createPackageContext(any(), anyInt())).thenReturn(packageContext);
202+
203+
FlutterEngine engineUnderTest =
204+
new FlutterEngine(
205+
context,
206+
mock(FlutterLoader.class),
207+
flutterJNI,
208+
/*dartVmArgs=*/ new String[] {},
209+
/*automaticallyRegisterPlugins=*/ false);
210+
211+
EngineLifecycleListener listener = mock(EngineLifecycleListener.class);
212+
engineUnderTest.addEngineLifecycleListener(listener);
213+
engineUnderTest.destroy();
214+
verify(listener, times(1)).onEngineDestroy();
215+
}
216+
217+
@Test
218+
public void itDoesNotAttachAgainWhenBuiltWithAnAttachedJNI() throws NameNotFoundException {
219+
Context context = mock(Context.class);
220+
Context packageContext = mock(Context.class);
221+
222+
when(context.createPackageContext(any(), anyInt())).thenReturn(packageContext);
223+
when(flutterJNI.isAttached()).thenReturn(true);
224+
225+
FlutterEngine engineUnderTest =
226+
new FlutterEngine(
227+
context,
228+
mock(FlutterLoader.class),
229+
flutterJNI,
230+
/*dartVmArgs=*/ new String[] {},
231+
/*automaticallyRegisterPlugins=*/ false);
232+
233+
verify(flutterJNI, never()).attachToNative(false);
234+
}
181235
}

testing/scenario_app/android/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@
1212
/build
1313
/captures
1414
.externalNativeBuild
15+
.cache
Binary file not shown.

0 commit comments

Comments
 (0)