| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.base.memory; |
| |
| import android.content.ComponentCallbacks2; |
| import android.os.Looper; |
| import android.support.test.filters.SmallTest; |
| |
| import org.junit.Assert; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.robolectric.annotation.Config; |
| import org.robolectric.shadows.ShadowLooper; |
| |
| import org.chromium.base.MemoryPressureLevel; |
| import org.chromium.base.Supplier; |
| import org.chromium.base.ThreadUtils; |
| import org.chromium.base.test.BaseRobolectricTestRunner; |
| |
| import java.util.concurrent.TimeUnit; |
| |
| /** |
| * Test for MemoryPressureMonitor. |
| */ |
| @RunWith(BaseRobolectricTestRunner.class) |
| @Config(manifest = Config.NONE) |
| public class MemoryPressureMonitorTest { |
| private MemoryPressureMonitor mMonitor; |
| |
| private static class TestPressureCallback implements MemoryPressureCallback { |
| private Integer mReportedPressure; |
| |
| public void assertCalledWith(@MemoryPressureLevel int expectedPressure) { |
| Assert.assertNotNull("Callback was not called", mReportedPressure); |
| Assert.assertEquals(expectedPressure, (int) mReportedPressure); |
| } |
| |
| public void assertNotCalled() { |
| Assert.assertNull(mReportedPressure); |
| } |
| |
| public void reset() { |
| mReportedPressure = null; |
| } |
| |
| @Override |
| public void onPressure(@MemoryPressureLevel int pressure) { |
| assertNotCalled(); |
| mReportedPressure = pressure; |
| } |
| } |
| |
| private static class TestPressureSupplier implements Supplier<Integer> { |
| private @MemoryPressureLevel Integer mPressure; |
| private boolean mIsCalled; |
| |
| public TestPressureSupplier(@MemoryPressureLevel Integer pressure) { |
| mPressure = pressure; |
| } |
| |
| @Override |
| public @MemoryPressureLevel Integer get() { |
| assertNotCalled(); |
| mIsCalled = true; |
| return mPressure; |
| } |
| |
| public void assertCalled() { |
| Assert.assertTrue(mIsCalled); |
| } |
| |
| public void assertNotCalled() { |
| Assert.assertFalse(mIsCalled); |
| } |
| } |
| |
| private static final int THROTTLING_INTERVAL_MS = 1000; |
| |
| @Before |
| public void setUp() { |
| // Explicitly set main thread as UiThread. Other places rely on that. |
| ThreadUtils.setUiThread(Looper.getMainLooper()); |
| |
| // Pause main thread to get control over when tasks are run (see runUiThreadFor()). |
| ShadowLooper.pauseMainLooper(); |
| |
| mMonitor = new MemoryPressureMonitor(THROTTLING_INTERVAL_MS); |
| mMonitor.setCurrentPressureSupplierForTesting(null); |
| } |
| |
| /** |
| * Runs all UiThread tasks posted |delayMs| in the future. |
| * @param delayMs |
| */ |
| private void runUiThreadFor(long delayMs) { |
| ShadowLooper.idleMainLooper(delayMs, TimeUnit.MILLISECONDS); |
| } |
| |
| @Test |
| @SmallTest |
| public void testTrimLevelTranslation() { |
| Integer[][] trimLevelToPressureMap = {// |
| // Levels >= TRIM_MEMORY_COMPLETE map to CRITICAL. |
| {ComponentCallbacks2.TRIM_MEMORY_COMPLETE + 1, MemoryPressureLevel.CRITICAL}, |
| {ComponentCallbacks2.TRIM_MEMORY_COMPLETE, MemoryPressureLevel.CRITICAL}, |
| |
| // TRIM_MEMORY_RUNNING_CRITICAL maps to CRITICAL. |
| {ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL, MemoryPressureLevel.CRITICAL}, |
| |
| // Levels < TRIM_MEMORY_COMPLETE && >= TRIM_MEMORY_BACKGROUND map to MODERATE. |
| {ComponentCallbacks2.TRIM_MEMORY_COMPLETE - 1, MemoryPressureLevel.MODERATE}, |
| {ComponentCallbacks2.TRIM_MEMORY_BACKGROUND + 1, MemoryPressureLevel.MODERATE}, |
| {ComponentCallbacks2.TRIM_MEMORY_BACKGROUND, MemoryPressureLevel.MODERATE}, |
| |
| // Other levels are not mapped. |
| {ComponentCallbacks2.TRIM_MEMORY_BACKGROUND - 1, null}, |
| {ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW, null}, |
| {ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE, null}, |
| {ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN, null}}; |
| for (Integer[] trimLevelAndPressure : trimLevelToPressureMap) { |
| int trimLevel = trimLevelAndPressure[0]; |
| Integer expectedPressure = trimLevelAndPressure[1]; |
| Integer actualPressure = MemoryPressureMonitor.memoryPressureFromTrimLevel(trimLevel); |
| Assert.assertEquals(expectedPressure, actualPressure); |
| } |
| } |
| |
| @Test |
| @SmallTest |
| public void testThrottleInterval() { |
| TestPressureCallback callback = new TestPressureCallback(); |
| mMonitor.setReportingCallbackForTesting(callback); |
| |
| // First notification should go through. |
| mMonitor.notifyPressure(MemoryPressureLevel.NONE); |
| callback.assertCalledWith(MemoryPressureLevel.NONE); |
| |
| callback.reset(); |
| |
| // This one should be throttled. |
| mMonitor.notifyPressure(MemoryPressureLevel.NONE); |
| callback.assertNotCalled(); |
| |
| runUiThreadFor(THROTTLING_INTERVAL_MS - 1); |
| |
| // We're still within the throttling interval, so this notification should |
| // still be throttled. |
| mMonitor.notifyPressure(MemoryPressureLevel.NONE); |
| callback.assertNotCalled(); |
| |
| runUiThreadFor(1); |
| |
| // We're past the throttling interval at this point, so this notification |
| // should go through. |
| mMonitor.notifyPressure(MemoryPressureLevel.NONE); |
| callback.assertCalledWith(MemoryPressureLevel.NONE); |
| } |
| |
| @Test |
| @SmallTest |
| public void testChangeNotIgnored() { |
| TestPressureCallback callback = new TestPressureCallback(); |
| mMonitor.setReportingCallbackForTesting(callback); |
| |
| mMonitor.notifyPressure(MemoryPressureLevel.NONE); |
| callback.assertCalledWith(MemoryPressureLevel.NONE); |
| |
| callback.reset(); |
| |
| // Second notification is throttled, but should be reported after the |
| // throttling interval ends. |
| mMonitor.notifyPressure(MemoryPressureLevel.MODERATE); |
| callback.assertNotCalled(); |
| |
| runUiThreadFor(THROTTLING_INTERVAL_MS - 1); |
| |
| // Shouldn't be reported at this point. |
| callback.assertNotCalled(); |
| |
| runUiThreadFor(1); |
| |
| callback.assertCalledWith(MemoryPressureLevel.MODERATE); |
| } |
| |
| @Test |
| @SmallTest |
| public void testNoopChangeIgnored() { |
| TestPressureCallback callback = new TestPressureCallback(); |
| mMonitor.setReportingCallbackForTesting(callback); |
| |
| mMonitor.notifyPressure(MemoryPressureLevel.NONE); |
| callback.assertCalledWith(MemoryPressureLevel.NONE); |
| |
| callback.reset(); |
| |
| // Report MODERATE and then NONE, so that the throttling interval finishes with the |
| // same pressure that started it (i.e. NONE). In this case MODERATE should be ignored. |
| mMonitor.notifyPressure(MemoryPressureLevel.MODERATE); |
| mMonitor.notifyPressure(MemoryPressureLevel.NONE); |
| |
| runUiThreadFor(THROTTLING_INTERVAL_MS); |
| |
| callback.assertNotCalled(); |
| } |
| |
| @Test |
| @SmallTest |
| public void testPollingInitiallyDisabled() { |
| TestPressureSupplier pressureSupplier = |
| new TestPressureSupplier(MemoryPressureLevel.MODERATE); |
| mMonitor.setCurrentPressureSupplierForTesting(pressureSupplier); |
| |
| mMonitor.notifyPressure(MemoryPressureLevel.CRITICAL); |
| runUiThreadFor(THROTTLING_INTERVAL_MS); |
| |
| // We finished the interval with CRITICAL, but since polling is disabled, we shouldn't |
| // poll the current pressure. |
| pressureSupplier.assertNotCalled(); |
| } |
| |
| @Test |
| @SmallTest |
| public void testEnablePollingPolls() { |
| TestPressureCallback callback = new TestPressureCallback(); |
| mMonitor.setReportingCallbackForTesting(callback); |
| |
| TestPressureSupplier pressureSupplier = |
| new TestPressureSupplier(MemoryPressureLevel.MODERATE); |
| mMonitor.setCurrentPressureSupplierForTesting(pressureSupplier); |
| |
| mMonitor.enablePolling(); |
| |
| // When polling is enabled, current pressure should be retrieved and reported. |
| pressureSupplier.assertCalled(); |
| callback.assertCalledWith(MemoryPressureLevel.MODERATE); |
| } |
| |
| @Test |
| @SmallTest |
| public void testNullSupplierResultIgnored() { |
| TestPressureCallback callback = new TestPressureCallback(); |
| mMonitor.setReportingCallbackForTesting(callback); |
| |
| TestPressureSupplier pressureSupplier = new TestPressureSupplier(null); |
| mMonitor.setCurrentPressureSupplierForTesting(pressureSupplier); |
| |
| mMonitor.enablePolling(); |
| |
| // The pressure supplier should be called, but its null result should be ignored. |
| pressureSupplier.assertCalled(); |
| callback.assertNotCalled(); |
| } |
| |
| @Test |
| @SmallTest |
| public void testEnablePollingRespectsThrottling() { |
| TestPressureSupplier pressureSupplier = |
| new TestPressureSupplier(MemoryPressureLevel.MODERATE); |
| mMonitor.setCurrentPressureSupplierForTesting(pressureSupplier); |
| |
| mMonitor.notifyPressure(MemoryPressureLevel.NONE); |
| |
| // The notification above started a throttling interval, so we shouldn't ask for the |
| // current pressure when polling is enabled. |
| mMonitor.enablePolling(); |
| |
| pressureSupplier.assertNotCalled(); |
| } |
| |
| @Test |
| @SmallTest |
| public void testPollingIfCRITICAL() { |
| TestPressureCallback callback = new TestPressureCallback(); |
| mMonitor.setReportingCallbackForTesting(callback); |
| |
| TestPressureSupplier pressureSupplier = |
| new TestPressureSupplier(MemoryPressureLevel.MODERATE); |
| mMonitor.setCurrentPressureSupplierForTesting(pressureSupplier); |
| |
| mMonitor.notifyPressure(MemoryPressureLevel.CRITICAL); |
| callback.reset(); |
| |
| mMonitor.enablePolling(); |
| |
| runUiThreadFor(THROTTLING_INTERVAL_MS - 1); |
| |
| // Pressure should be polled after the interval ends, not before. |
| pressureSupplier.assertNotCalled(); |
| |
| runUiThreadFor(1); |
| |
| // We started and finished the throttling interval with CRITICAL pressure, so |
| // we should poll the current pressure at the end of the interval. |
| pressureSupplier.assertCalled(); |
| callback.assertCalledWith(MemoryPressureLevel.MODERATE); |
| } |
| |
| @Test |
| @SmallTest |
| public void testNoPollingIfNotCRITICAL() { |
| TestPressureSupplier pressureSupplier = new TestPressureSupplier(MemoryPressureLevel.NONE); |
| mMonitor.setCurrentPressureSupplierForTesting(pressureSupplier); |
| |
| mMonitor.notifyPressure(MemoryPressureLevel.MODERATE); |
| |
| mMonitor.enablePolling(); |
| |
| runUiThreadFor(THROTTLING_INTERVAL_MS); |
| |
| // We started and finished the throttling interval with non-CRITICAL pressure, |
| // so no polling should take place. |
| pressureSupplier.assertNotCalled(); |
| } |
| |
| @Test |
| @SmallTest |
| public void testNoPollingIfChangedToCRITICAL() { |
| TestPressureSupplier pressureSupplier = new TestPressureSupplier(MemoryPressureLevel.NONE); |
| mMonitor.setCurrentPressureSupplierForTesting(pressureSupplier); |
| |
| mMonitor.notifyPressure(MemoryPressureLevel.MODERATE); |
| mMonitor.notifyPressure(MemoryPressureLevel.CRITICAL); |
| |
| mMonitor.enablePolling(); |
| |
| runUiThreadFor(THROTTLING_INTERVAL_MS); |
| |
| // We finished the throttling interval with CRITITCAL, but started with MODERATE, |
| // so no polling should take place. |
| pressureSupplier.assertNotCalled(); |
| } |
| |
| @Test |
| @SmallTest |
| public void testDisablePolling() { |
| TestPressureSupplier pressureSupplier = new TestPressureSupplier(MemoryPressureLevel.NONE); |
| mMonitor.setCurrentPressureSupplierForTesting(pressureSupplier); |
| |
| mMonitor.notifyPressure(MemoryPressureLevel.CRITICAL); |
| |
| mMonitor.enablePolling(); |
| |
| runUiThreadFor(THROTTLING_INTERVAL_MS - 1); |
| |
| // Whether polling is enabled or not should be taken into account only after the interval |
| // finishes, so disabling it here should have the same affect as if it was never enabled. |
| mMonitor.disablePolling(); |
| |
| runUiThreadFor(1); |
| |
| pressureSupplier.assertNotCalled(); |
| } |
| } |