• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2022 The Chromium Authors
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.base.memory;
6 
7 import android.os.Looper;
8 
9 import androidx.test.filters.SmallTest;
10 
11 import org.junit.Assert;
12 import org.junit.Before;
13 import org.junit.Rule;
14 import org.junit.Test;
15 import org.junit.runner.RunWith;
16 import org.robolectric.annotation.Config;
17 import org.robolectric.shadows.ShadowLooper;
18 
19 import org.chromium.base.ApplicationState;
20 import org.chromium.base.BaseFeatures;
21 import org.chromium.base.FakeTimeTestRule;
22 import org.chromium.base.FeatureList;
23 import org.chromium.base.ThreadUtils;
24 import org.chromium.base.metrics.RecordHistogram;
25 import org.chromium.base.test.BaseRobolectricTestRunner;
26 
27 import java.util.Map;
28 import java.util.concurrent.Callable;
29 import java.util.concurrent.TimeUnit;
30 
31 /** Tests for MemoryPurgeManager. */
32 @RunWith(BaseRobolectricTestRunner.class)
33 @Config(manifest = Config.NONE)
34 public class MemoryPurgeManagerTest {
35     @Rule public FakeTimeTestRule mFakeTimeTestRule = new FakeTimeTestRule();
36     private Callable<Integer> mGetCount =
37             () -> {
38                 return RecordHistogram.getHistogramTotalCountForTesting(
39                         MemoryPurgeManager.BACKGROUND_DURATION_HISTOGRAM_NAME);
40             };
41 
42     private class MemoryPurgeManagerForTest extends MemoryPurgeManager {
MemoryPurgeManagerForTest(int initialState)43         public MemoryPurgeManagerForTest(int initialState) {
44             super();
45             mApplicationState = initialState;
46         }
47 
48         @Override
onApplicationStateChange(int state)49         public void onApplicationStateChange(int state) {
50             mApplicationState = state;
51             super.onApplicationStateChange(state);
52         }
53 
54         @Override
notifyMemoryPressure()55         protected void notifyMemoryPressure() {
56             mMemoryPressureNotifiedCount += 1;
57         }
58 
59         @Override
getApplicationState()60         protected int getApplicationState() {
61             return mApplicationState;
62         }
63 
64         public int mMemoryPressureNotifiedCount;
65         public int mApplicationState = ApplicationState.UNKNOWN;
66     }
67 
68     @Before
setUp()69     public void setUp() {
70         // Explicitly set main thread as UiThread. Other places rely on that.
71         ThreadUtils.setUiThread(Looper.getMainLooper());
72 
73         // Pause main thread to get control over when tasks are run (see runUiThreadFor()).
74         ShadowLooper.pauseMainLooper();
75     }
76 
77     @Test
78     @SmallTest
testSimple()79     public void testSimple() throws Exception {
80         FeatureList.setTestFeatures(Map.of(BaseFeatures.BROWSER_PROCESS_MEMORY_PURGE, true));
81 
82         int count = mGetCount.call();
83         var manager = new MemoryPurgeManagerForTest(ApplicationState.HAS_RUNNING_ACTIVITIES);
84         manager.start();
85 
86         // No notification when initial state has activities.
87         Assert.assertEquals(0, manager.mMemoryPressureNotifiedCount);
88         runUiThreadFor(MemoryPurgeManager.PURGE_DELAY_MS);
89         Assert.assertEquals(0, manager.mMemoryPressureNotifiedCount);
90         Assert.assertEquals(count, (int) mGetCount.call());
91 
92         // Notify after a delay.
93         manager.onApplicationStateChange(ApplicationState.HAS_STOPPED_ACTIVITIES);
94         Assert.assertEquals(0, manager.mMemoryPressureNotifiedCount); // Should be delayed.
95         runUiThreadFor(MemoryPurgeManager.PURGE_DELAY_MS);
96         Assert.assertEquals(1, manager.mMemoryPressureNotifiedCount);
97 
98         // Only one notification.
99         manager.onApplicationStateChange(ApplicationState.HAS_DESTROYED_ACTIVITIES);
100         runUiThreadFor(MemoryPurgeManager.PURGE_DELAY_MS);
101         Assert.assertEquals(1, manager.mMemoryPressureNotifiedCount);
102 
103         // Started in foreground, went to background once, and came back.
104         manager.onApplicationStateChange(ApplicationState.HAS_RUNNING_ACTIVITIES);
105         Assert.assertEquals(count + 1, (int) mGetCount.call());
106     }
107 
108     @Test
109     @SmallTest
testInitializedOnceInBackground()110     public void testInitializedOnceInBackground() throws Exception {
111         FeatureList.setTestFeatures(Map.of(BaseFeatures.BROWSER_PROCESS_MEMORY_PURGE, true));
112 
113         int count = mGetCount.call();
114         var manager = new MemoryPurgeManagerForTest(ApplicationState.HAS_STOPPED_ACTIVITIES);
115         manager.start();
116         Assert.assertEquals(0, manager.mMemoryPressureNotifiedCount);
117         runUiThreadFor(MemoryPurgeManager.PURGE_DELAY_MS);
118         Assert.assertEquals(1, manager.mMemoryPressureNotifiedCount);
119 
120         // Started in background, no histogram recording when coming to foreground.
121         manager.onApplicationStateChange(ApplicationState.HAS_RUNNING_ACTIVITIES);
122         Assert.assertEquals(count, (int) mGetCount.call());
123     }
124 
125     @Test
126     @SmallTest
testDontTriggerForProcessesWithNoActivities()127     public void testDontTriggerForProcessesWithNoActivities() {
128         FeatureList.setTestFeatures(Map.of(BaseFeatures.BROWSER_PROCESS_MEMORY_PURGE, true));
129 
130         var manager = new MemoryPurgeManagerForTest(ApplicationState.HAS_DESTROYED_ACTIVITIES);
131         manager.start();
132 
133         // Don't purge if the process never hosted any activity.
134         Assert.assertEquals(0, manager.mMemoryPressureNotifiedCount);
135         runUiThreadFor(MemoryPurgeManager.PURGE_DELAY_MS);
136         Assert.assertEquals(0, manager.mMemoryPressureNotifiedCount);
137 
138         // Starts when we cycle through foreground and background.
139         manager.onApplicationStateChange(ApplicationState.HAS_RUNNING_ACTIVITIES);
140         manager.onApplicationStateChange(ApplicationState.HAS_STOPPED_ACTIVITIES);
141         runUiThreadFor(MemoryPurgeManager.PURGE_DELAY_MS);
142         Assert.assertEquals(1, manager.mMemoryPressureNotifiedCount);
143     }
144 
145     @Test
146     @SmallTest
testMultiple()147     public void testMultiple() throws Exception {
148         FeatureList.setTestFeatures(Map.of(BaseFeatures.BROWSER_PROCESS_MEMORY_PURGE, true));
149 
150         int count = mGetCount.call();
151         var manager = new MemoryPurgeManagerForTest(ApplicationState.HAS_RUNNING_ACTIVITIES);
152         manager.start();
153 
154         // Notify after a delay.
155         manager.onApplicationStateChange(ApplicationState.HAS_STOPPED_ACTIVITIES);
156         runUiThreadFor(MemoryPurgeManager.PURGE_DELAY_MS);
157         Assert.assertEquals(1, manager.mMemoryPressureNotifiedCount);
158 
159         // Back to foreground, no notification.
160         manager.onApplicationStateChange(ApplicationState.HAS_RUNNING_ACTIVITIES);
161         runUiThreadFor(MemoryPurgeManager.PURGE_DELAY_MS);
162         Assert.assertEquals(1, manager.mMemoryPressureNotifiedCount);
163         Assert.assertEquals(count + 1, (int) mGetCount.call());
164 
165         // Background again, notify
166         manager.onApplicationStateChange(ApplicationState.HAS_STOPPED_ACTIVITIES);
167         runUiThreadFor(MemoryPurgeManager.PURGE_DELAY_MS);
168         Assert.assertEquals(2, manager.mMemoryPressureNotifiedCount);
169         Assert.assertEquals(count + 1, (int) mGetCount.call());
170 
171         // Foreground again, record the histogram.
172         manager.onApplicationStateChange(ApplicationState.HAS_RUNNING_ACTIVITIES);
173         Assert.assertEquals(count + 2, (int) mGetCount.call());
174     }
175 
176     @Test
177     @SmallTest
testNoEnoughTimeInBackground()178     public void testNoEnoughTimeInBackground() {
179         FeatureList.setTestFeatures(Map.of(BaseFeatures.BROWSER_PROCESS_MEMORY_PURGE, true));
180 
181         var manager = new MemoryPurgeManagerForTest(ApplicationState.HAS_RUNNING_ACTIVITIES);
182         manager.start();
183 
184         // Background, then foregound inside the delay period.
185         manager.onApplicationStateChange(ApplicationState.HAS_STOPPED_ACTIVITIES);
186         runUiThreadFor(MemoryPurgeManager.PURGE_DELAY_MS / 2);
187         manager.onApplicationStateChange(ApplicationState.HAS_RUNNING_ACTIVITIES);
188         runUiThreadFor(MemoryPurgeManager.PURGE_DELAY_MS);
189         // Went back to foreground, do nothing.
190         Assert.assertEquals(0, manager.mMemoryPressureNotifiedCount);
191 
192         // Starts the new task
193         manager.onApplicationStateChange(ApplicationState.HAS_STOPPED_ACTIVITIES);
194         // After some time, foregroung/background cycle.
195         runUiThreadFor(MemoryPurgeManager.PURGE_DELAY_MS / 2);
196         manager.onApplicationStateChange(ApplicationState.HAS_RUNNING_ACTIVITIES);
197         manager.onApplicationStateChange(ApplicationState.HAS_STOPPED_ACTIVITIES);
198         runUiThreadFor(MemoryPurgeManager.PURGE_DELAY_MS / 2);
199         // Not enough time in background.
200         Assert.assertEquals(0, manager.mMemoryPressureNotifiedCount);
201         // But the task got rescheduled.
202         runUiThreadFor(MemoryPurgeManager.PURGE_DELAY_MS / 2);
203         Assert.assertEquals(1, manager.mMemoryPressureNotifiedCount);
204 
205         // No new notification.
206         runUiThreadFor(MemoryPurgeManager.PURGE_DELAY_MS * 2);
207         Assert.assertEquals(1, manager.mMemoryPressureNotifiedCount);
208     }
209 
runUiThreadFor(long delayMs)210     private void runUiThreadFor(long delayMs) {
211         mFakeTimeTestRule.advanceMillis(delayMs);
212         ShadowLooper.idleMainLooper(delayMs, TimeUnit.MILLISECONDS);
213     }
214 }
215