1 // Copyright 2014 The Chromium 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 org.chromium.content.browser;
6 
7 import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL;
8 import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
9 import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE;
10 
11 import android.app.Activity;
12 import android.app.Application;
13 import android.content.ComponentName;
14 import android.util.Pair;
15 
16 import org.junit.After;
17 import org.junit.Assert;
18 import org.junit.Before;
19 import org.junit.Test;
20 import org.junit.runner.RunWith;
21 import org.robolectric.Robolectric;
22 import org.robolectric.annotation.Config;
23 import org.robolectric.shadows.ShadowLooper;
24 
25 import org.chromium.base.process_launcher.ChildProcessConnection;
26 import org.chromium.base.test.BaseRobolectricTestRunner;
27 import org.chromium.base.test.TestChildProcessConnection;
28 import org.chromium.base.test.util.Feature;
29 
30 import java.util.ArrayList;
31 import java.util.List;
32 
33 /**
34  * Unit tests for BindingManager and ChildProcessConnection.
35  *
36  * Default property of being low-end device is overriden, so that both low-end and high-end policies
37  * are tested.
38  */
39 @RunWith(BaseRobolectricTestRunner.class)
40 @Config(manifest = Config.NONE)
41 public class BindingManagerTest {
42     // Creates a mocked ChildProcessConnection that is optionally added to a BindingManager.
createTestChildProcessConnection( int pid, BindingManager manager, List<ChildProcessConnection> iterable)43     private static ChildProcessConnection createTestChildProcessConnection(
44             int pid, BindingManager manager, List<ChildProcessConnection> iterable) {
45         TestChildProcessConnection connection = new TestChildProcessConnection(
46                 new ComponentName("org.chromium.test", "TestService"),
47                 false /* bindToCallerCheck */, false /* bindAsExternalService */,
48                 null /* serviceBundle */);
49         connection.setPid(pid);
50         connection.start(false /* useStrongBinding */, null /* serviceCallback */);
51         manager.addConnection(connection);
52         iterable.add(connection);
53         connection.removeModerateBinding(); // Remove initial binding.
54         return connection;
55     }
56 
57     Activity mActivity;
58 
59     // Created in setUp() for convenience.
60     BindingManager mManager;
61     BindingManager mVariableManager;
62 
63     List<ChildProcessConnection> mIterable;
64 
65     @Before
setUp()66     public void setUp() {
67         // The tests run on only one thread. Pretend that is the launcher thread so LauncherThread
68         // asserts are not triggered.
69         LauncherThread.setCurrentThreadAsLauncherThread();
70         mActivity = Robolectric.buildActivity(Activity.class).setup().get();
71         mIterable = new ArrayList<>();
72         mManager = new BindingManager(mActivity, 4, mIterable);
73         mVariableManager = new BindingManager(mActivity, mIterable);
74     }
75 
76     @After
tearDown()77     public void tearDown() {
78         LauncherThread.setLauncherThreadAsLauncherThread();
79     }
80 
81     /**
82      * Verifies that onSentToBackground() drops all the moderate bindings after some delay, and
83      * onBroughtToForeground() doesn't recover them.
84      */
85     @Test
86     @Feature({"ProcessManagement"})
testModerateBindingDropOnBackground()87     public void testModerateBindingDropOnBackground() {
88         doTestModerateBindingDropOnBackground(mManager);
89     }
90 
91     @Test
92     @Feature({"ProcessManagement"})
testModerateBindingDropOnBackgroundWithVariableSize()93     public void testModerateBindingDropOnBackgroundWithVariableSize() {
94         doTestModerateBindingDropOnBackground(mVariableManager);
95     }
96 
doTestModerateBindingDropOnBackground(BindingManager manager)97     private void doTestModerateBindingDropOnBackground(BindingManager manager) {
98         ChildProcessConnection[] connections = new ChildProcessConnection[3];
99         for (int i = 0; i < connections.length; i++) {
100             connections[i] = createTestChildProcessConnection(i + 1 /* pid */, manager, mIterable);
101         }
102 
103         // Verify that each connection has a moderate binding after binding and releasing a strong
104         // binding.
105         for (ChildProcessConnection connection : connections) {
106             Assert.assertTrue(connection.isModerateBindingBound());
107         }
108 
109         ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
110 
111         // Verify that leaving the application for a short time doesn't clear the moderate bindings.
112         manager.onSentToBackground();
113         for (ChildProcessConnection connection : connections) {
114             Assert.assertTrue(connection.isModerateBindingBound());
115         }
116         manager.onBroughtToForeground();
117         ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
118         for (ChildProcessConnection connection : connections) {
119             Assert.assertTrue(connection.isModerateBindingBound());
120         }
121 
122         // Call onSentToBackground() and verify that all the moderate bindings drop after some
123         // delay.
124         manager.onSentToBackground();
125         for (ChildProcessConnection connection : connections) {
126             Assert.assertTrue(connection.isModerateBindingBound());
127         }
128         ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
129         for (ChildProcessConnection connection : connections) {
130             Assert.assertFalse(connection.isModerateBindingBound());
131         }
132 
133         // Call onBroughtToForeground() and verify that the previous moderate bindings aren't
134         // recovered.
135         manager.onBroughtToForeground();
136         for (ChildProcessConnection connection : connections) {
137             Assert.assertFalse(connection.isModerateBindingBound());
138         }
139     }
140 
141     /**
142      * Verifies that onLowMemory() drops all the moderate bindings.
143      */
144     @Test
145     @Feature({"ProcessManagement"})
testModerateBindingDropOnLowMemory()146     public void testModerateBindingDropOnLowMemory() {
147         doTestModerateBindingDropOnLowMemory(mManager);
148     }
149 
150     @Test
151     @Feature({"ProcessManagement"})
testModerateBindingDropOnLowMemoryVariableSize()152     public void testModerateBindingDropOnLowMemoryVariableSize() {
153         doTestModerateBindingDropOnLowMemory(mVariableManager);
154     }
155 
doTestModerateBindingDropOnLowMemory(BindingManager manager)156     private void doTestModerateBindingDropOnLowMemory(BindingManager manager) {
157         final Application app = mActivity.getApplication();
158 
159         ChildProcessConnection[] connections = new ChildProcessConnection[4];
160         for (int i = 0; i < connections.length; i++) {
161             connections[i] = createTestChildProcessConnection(i + 1 /* pid */, manager, mIterable);
162         }
163 
164         for (ChildProcessConnection connection : connections) {
165             Assert.assertTrue(connection.isModerateBindingBound());
166         }
167 
168         // Call onLowMemory() and verify that all the moderate bindings drop.
169         app.onLowMemory();
170         for (ChildProcessConnection connection : connections) {
171             Assert.assertFalse(connection.isModerateBindingBound());
172         }
173     }
174 
175     /**
176      * Verifies that onTrimMemory() drops moderate bindings properly.
177      */
178     @Test
179     @Feature({"ProcessManagement"})
testModerateBindingDropOnTrimMemory()180     public void testModerateBindingDropOnTrimMemory() {
181         doTestModerateBindingDropOnTrimMemory(mManager);
182     }
183 
184     @Test
185     @Feature({"ProcessManagement"})
testModerateBindingDropOnTrimMemoryWithVariableSize()186     public void testModerateBindingDropOnTrimMemoryWithVariableSize() {
187         doTestModerateBindingDropOnTrimMemory(mVariableManager);
188     }
189 
doTestModerateBindingDropOnTrimMemory(BindingManager manager)190     private void doTestModerateBindingDropOnTrimMemory(BindingManager manager) {
191         final Application app = mActivity.getApplication();
192         // This test applies only to the moderate-binding manager.
193 
194         ArrayList<Pair<Integer, Integer>> levelAndExpectedVictimCountList = new ArrayList<>();
195         levelAndExpectedVictimCountList.add(
196                 new Pair<Integer, Integer>(TRIM_MEMORY_RUNNING_MODERATE, 1));
197         levelAndExpectedVictimCountList.add(new Pair<Integer, Integer>(TRIM_MEMORY_RUNNING_LOW, 2));
198         levelAndExpectedVictimCountList.add(
199                 new Pair<Integer, Integer>(TRIM_MEMORY_RUNNING_CRITICAL, 4));
200 
201         ChildProcessConnection[] connections = new ChildProcessConnection[4];
202         for (int i = 0; i < connections.length; i++) {
203             connections[i] = createTestChildProcessConnection(i + 1 /* pid */, manager, mIterable);
204         }
205 
206         for (Pair<Integer, Integer> pair : levelAndExpectedVictimCountList) {
207             String message = "Failed for the level=" + pair.first;
208             mIterable.clear();
209             // Verify that each connection has a moderate binding after binding and releasing a
210             // strong binding.
211             for (ChildProcessConnection connection : connections) {
212                 manager.addConnection(connection);
213                 mIterable.add(connection);
214                 Assert.assertTrue(message, connection.isModerateBindingBound());
215             }
216 
217             app.onTrimMemory(pair.first);
218             // Verify that some of the moderate bindings have been dropped.
219             for (int i = 0; i < connections.length; i++) {
220                 Assert.assertEquals(
221                         message, i >= pair.second, connections[i].isModerateBindingBound());
222             }
223         }
224     }
225 
226     /*
227      * Test that Chrome is sent to the background, that the initially added moderate bindings are
228      * removed and are not re-added when Chrome is brought back to the foreground.
229      */
230     @Test
231     @Feature({"ProcessManagement"})
testModerateBindingTillBackgroundedSentToBackground()232     public void testModerateBindingTillBackgroundedSentToBackground() {
233         doTestModerateBindingTillBackgroundedSentToBackground(mManager);
234     }
235 
236     @Test
237     @Feature({"ProcessManagement"})
testModerateBindingTillBackgroundedSentToBackgroundWithVariableSize()238     public void testModerateBindingTillBackgroundedSentToBackgroundWithVariableSize() {
239         doTestModerateBindingTillBackgroundedSentToBackground(mVariableManager);
240     }
241 
doTestModerateBindingTillBackgroundedSentToBackground(BindingManager manager)242     private void doTestModerateBindingTillBackgroundedSentToBackground(BindingManager manager) {
243         ChildProcessConnection connection = createTestChildProcessConnection(0, manager, mIterable);
244         Assert.assertTrue(connection.isModerateBindingBound());
245 
246         manager.onSentToBackground();
247         ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
248         Assert.assertFalse(connection.isModerateBindingBound());
249 
250         // Bringing Chrome to the foreground should not re-add the moderate bindings.
251         manager.onBroughtToForeground();
252         Assert.assertFalse(connection.isModerateBindingBound());
253     }
254 
255     @Test
256     @Feature({"ProcessManagement"})
testOneWaivedConnection()257     public void testOneWaivedConnection() {
258         testOneWaivedConnection(mManager);
259     }
260 
261     @Test
262     @Feature({"ProcessManagement"})
testOneWaivedConnectionWithVariableSize()263     public void testOneWaivedConnectionWithVariableSize() {
264         testOneWaivedConnection(mVariableManager);
265     }
266 
testOneWaivedConnection(BindingManager manager)267     private void testOneWaivedConnection(BindingManager manager) {
268         ChildProcessConnection[] connections = new ChildProcessConnection[3];
269         for (int i = 0; i < connections.length; i++) {
270             connections[i] = createTestChildProcessConnection(i + 1 /* pid */, manager, mIterable);
271         }
272 
273         // Make sure binding is added for all connections.
274         for (ChildProcessConnection c : connections) {
275             Assert.assertTrue(c.isModerateBindingBound());
276         }
277 
278         // Move middle connection to be the first (ie lowest ranked).
279         mIterable.set(0, connections[1]);
280         mIterable.set(1, connections[0]);
281         manager.rankingChanged();
282         Assert.assertTrue(connections[0].isModerateBindingBound());
283         Assert.assertFalse(connections[1].isModerateBindingBound());
284         Assert.assertTrue(connections[2].isModerateBindingBound());
285 
286         // Swap back.
287         mIterable.set(0, connections[0]);
288         mIterable.set(1, connections[1]);
289         manager.rankingChanged();
290         Assert.assertFalse(connections[0].isModerateBindingBound());
291         Assert.assertTrue(connections[1].isModerateBindingBound());
292         Assert.assertTrue(connections[2].isModerateBindingBound());
293 
294         manager.removeConnection(connections[1]);
295         Assert.assertFalse(connections[0].isModerateBindingBound());
296         Assert.assertFalse(connections[1].isModerateBindingBound());
297         Assert.assertTrue(connections[2].isModerateBindingBound());
298 
299         manager.removeConnection(connections[0]);
300         Assert.assertFalse(connections[0].isModerateBindingBound());
301         Assert.assertFalse(connections[1].isModerateBindingBound());
302         Assert.assertTrue(connections[2].isModerateBindingBound());
303     }
304 }
305