1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.pm; 18 19 import android.app.AlarmManager; 20 import android.content.Context; 21 import android.os.Environment; 22 import android.os.ParcelFileDescriptor; 23 import android.os.PowerManager; 24 import android.os.SystemProperties; 25 import android.os.storage.StorageManager; 26 import android.util.Log; 27 28 import androidx.test.InstrumentationRegistry; 29 30 import org.junit.After; 31 import org.junit.Assert; 32 import org.junit.Before; 33 import org.junit.BeforeClass; 34 import org.junit.Test; 35 import org.junit.runner.RunWith; 36 import org.junit.runners.JUnit4; 37 38 import java.io.File; 39 import java.io.FileInputStream; 40 import java.io.IOException; 41 import java.io.InputStream; 42 import java.io.InputStreamReader; 43 import java.util.concurrent.TimeUnit; 44 45 /** 46 * Integration tests for {@link BackgroundDexOptService}. 47 * 48 * Tests various scenarios around BackgroundDexOptService. 49 * 1. Under normal conditions, check that dexopt upgrades test app to 50 * $(getprop pm.dexopt.bg-dexopt). 51 * 2. Under low storage conditions and package is unused, check 52 * that dexopt downgrades test app to $(getprop pm.dexopt.inactive). 53 * 3. Under low storage conditions and package is recently used, check 54 * that dexopt upgrades test app to $(getprop pm.dexopt.bg-dexopt). 55 * 56 * Each test case runs "cmd package bg-dexopt-job com.android.frameworks.bgdexopttest". 57 * 58 * The setup for these tests make sure this package has been configured to have been recently used 59 * plus installed far enough in the past. If a test case requires that this package has not been 60 * recently used, it sets the time forward more than 61 * `getprop pm.dexopt.downgrade_after_inactive_days` days. 62 * 63 * For tests that require low storage, the phone is filled up. 64 * 65 * Run with "atest BackgroundDexOptServiceIntegrationTests". 66 */ 67 @RunWith(JUnit4.class) 68 public final class BackgroundDexOptServiceIntegrationTests { 69 70 private static final String TAG = BackgroundDexOptServiceIntegrationTests.class.getSimpleName(); 71 72 // Name of package to test on. 73 private static final String PACKAGE_NAME = "com.android.frameworks.bgdexopttest"; 74 // Name of file used to fill up storage. 75 private static final String BIG_FILE = "bigfile"; 76 private static final String BG_DEXOPT_COMPILER_FILTER = SystemProperties.get( 77 "pm.dexopt.bg-dexopt"); 78 private static final String DOWNGRADE_COMPILER_FILTER = SystemProperties.get( 79 "pm.dexopt.inactive"); 80 private static final long DOWNGRADE_AFTER_DAYS = SystemProperties.getLong( 81 "pm.dexopt.downgrade_after_inactive_days", 0); 82 // Needs to be between 1.0 and 2.0. 83 private static final double LOW_STORAGE_MULTIPLIER = 1.5; 84 85 // The file used to fill up storage. 86 private File mBigFile; 87 88 // Remember start time. 89 @BeforeClass setUpAll()90 public static void setUpAll() { 91 if (!SystemProperties.getBoolean("pm.dexopt.disable_bg_dexopt", false)) { 92 throw new RuntimeException( 93 "bg-dexopt is not disabled (set pm.dexopt.disable_bg_dexopt to true)"); 94 } 95 if (DOWNGRADE_AFTER_DAYS < 1) { 96 throw new RuntimeException( 97 "pm.dexopt.downgrade_after_inactive_days must be at least 1"); 98 } 99 if ("quicken".equals(BG_DEXOPT_COMPILER_FILTER)) { 100 throw new RuntimeException("pm.dexopt.bg-dexopt should not be \"quicken\""); 101 } 102 if ("quicken".equals(DOWNGRADE_COMPILER_FILTER)) { 103 throw new RuntimeException("pm.dexopt.inactive should not be \"quicken\""); 104 } 105 } 106 107 getContext()108 private static Context getContext() { 109 return InstrumentationRegistry.getTargetContext(); 110 } 111 112 @Before setUp()113 public void setUp() throws IOException { 114 File dataDir = getContext().getDataDir(); 115 mBigFile = new File(dataDir, BIG_FILE); 116 } 117 118 @After tearDown()119 public void tearDown() { 120 if (mBigFile.exists()) { 121 boolean result = mBigFile.delete(); 122 if (!result) { 123 throw new RuntimeException("Couldn't delete big file"); 124 } 125 } 126 } 127 128 // Return the content of the InputStream as a String. inputStreamToString(InputStream is)129 private static String inputStreamToString(InputStream is) throws IOException { 130 char[] buffer = new char[1024]; 131 StringBuilder builder = new StringBuilder(); 132 try (InputStreamReader reader = new InputStreamReader(is)) { 133 for (; ; ) { 134 int count = reader.read(buffer, 0, buffer.length); 135 if (count < 0) { 136 break; 137 } 138 builder.append(buffer, 0, count); 139 } 140 } 141 return builder.toString(); 142 } 143 144 // Run the command and return the stdout. runShellCommand(String cmd)145 private static String runShellCommand(String cmd) throws IOException { 146 Log.i(TAG, String.format("running command: '%s'", cmd)); 147 ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation().getUiAutomation() 148 .executeShellCommand(cmd); 149 byte[] buf = new byte[512]; 150 int bytesRead; 151 FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd); 152 StringBuilder stdout = new StringBuilder(); 153 while ((bytesRead = fis.read(buf)) != -1) { 154 stdout.append(new String(buf, 0, bytesRead)); 155 } 156 fis.close(); 157 Log.i(TAG, "stdout"); 158 Log.i(TAG, stdout.toString()); 159 return stdout.toString(); 160 } 161 162 // Run the command and return the stdout split by lines. runShellCommandSplitLines(String cmd)163 private static String[] runShellCommandSplitLines(String cmd) throws IOException { 164 return runShellCommand(cmd).split("\n"); 165 } 166 167 // Return the compiler filter of a package. getCompilerFilter(String pkg)168 private static String getCompilerFilter(String pkg) throws IOException { 169 String cmd = String.format("dumpsys package %s", pkg); 170 String[] lines = runShellCommandSplitLines(cmd); 171 final String substr = "[status="; 172 for (String line : lines) { 173 int startIndex = line.indexOf(substr); 174 if (startIndex < 0) { 175 continue; 176 } 177 startIndex += substr.length(); 178 int endIndex = line.indexOf(']', startIndex); 179 return line.substring(startIndex, endIndex); 180 } 181 throw new RuntimeException("Couldn't find compiler filter in dumpsys package"); 182 } 183 184 // Return the number of bytes available in the data partition. getDataDirUsableSpace()185 private static long getDataDirUsableSpace() { 186 return Environment.getDataDirectory().getUsableSpace(); 187 } 188 189 // Fill up the storage until there are bytesRemaining number of bytes available in the data 190 // partition. Writes to the current package's data directory. fillUpStorage(long bytesRemaining)191 private void fillUpStorage(long bytesRemaining) throws IOException { 192 Log.i(TAG, String.format("Filling up storage with %d bytes remaining", bytesRemaining)); 193 logSpaceRemaining(); 194 long numBytesToAdd = getDataDirUsableSpace() - bytesRemaining; 195 String cmd = String.format("fallocate -l %d %s", numBytesToAdd, mBigFile.getAbsolutePath()); 196 runShellCommand(cmd); 197 logSpaceRemaining(); 198 } 199 200 // Fill up storage so that device is in low storage condition. fillUpToLowStorage()201 private void fillUpToLowStorage() throws IOException { 202 fillUpStorage((long) (getStorageLowBytes() * LOW_STORAGE_MULTIPLIER)); 203 } 204 runBackgroundDexOpt()205 private static void runBackgroundDexOpt() throws IOException { 206 runBackgroundDexOpt("Success"); 207 } 208 209 // TODO(aeubanks): figure out how to get scheduled bg-dexopt to run runBackgroundDexOpt(String expectedStatus)210 private static void runBackgroundDexOpt(String expectedStatus) throws IOException { 211 String result = runShellCommand("cmd package bg-dexopt-job " + PACKAGE_NAME); 212 if (!result.trim().equals(expectedStatus)) { 213 throw new IllegalStateException("Expected status: " + expectedStatus 214 + "; Received: " + result.trim()); 215 } 216 } 217 218 // Set the time ahead of the last use time of the test app in days. setTimeFutureDays(long futureDays)219 private static void setTimeFutureDays(long futureDays) { 220 setTimeFutureMillis(TimeUnit.DAYS.toMillis(futureDays)); 221 } 222 223 // Set the time ahead of the last use time of the test app in milliseconds. setTimeFutureMillis(long futureMillis)224 private static void setTimeFutureMillis(long futureMillis) { 225 long currentTime = System.currentTimeMillis(); 226 setTime(currentTime + futureMillis); 227 } 228 setTime(long time)229 private static void setTime(long time) { 230 AlarmManager am = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE); 231 am.setTime(time); 232 } 233 234 // Return the number of free bytes when the data partition is considered low on storage. getStorageLowBytes()235 private static long getStorageLowBytes() { 236 StorageManager storageManager = (StorageManager) getContext().getSystemService( 237 Context.STORAGE_SERVICE); 238 return storageManager.getStorageLowBytes(Environment.getDataDirectory()); 239 } 240 241 // Log the amount of space remaining in the data directory. logSpaceRemaining()242 private static void logSpaceRemaining() throws IOException { 243 runShellCommand("df -h /data"); 244 } 245 246 // Compile the given package with the given compiler filter. compilePackageWithFilter(String pkg, String filter)247 private static void compilePackageWithFilter(String pkg, String filter) throws IOException { 248 runShellCommand(String.format("cmd package compile -f -m %s %s", filter, pkg)); 249 } 250 251 // Override the thermal status of the device overrideThermalStatus(int status)252 public static void overrideThermalStatus(int status) throws IOException { 253 runShellCommand("cmd thermalservice override-status " + status); 254 } 255 256 // Reset the thermal status of the device resetThermalStatus()257 public static void resetThermalStatus() throws IOException { 258 runShellCommand("cmd thermalservice reset"); 259 } 260 261 // Test that background dexopt under normal conditions succeeds. 262 @Test testBackgroundDexOpt()263 public void testBackgroundDexOpt() throws IOException { 264 // Set filter to quicken. 265 compilePackageWithFilter(PACKAGE_NAME, "verify"); 266 Assert.assertEquals("verify", getCompilerFilter(PACKAGE_NAME)); 267 268 runBackgroundDexOpt(); 269 270 // Verify that bg-dexopt is successful. 271 Assert.assertEquals(BG_DEXOPT_COMPILER_FILTER, getCompilerFilter(PACKAGE_NAME)); 272 } 273 274 // Test that background dexopt under low storage conditions upgrades used packages. 275 @Test testBackgroundDexOptDowngradeSkipRecentlyUsedPackage()276 public void testBackgroundDexOptDowngradeSkipRecentlyUsedPackage() throws IOException { 277 // Should be less than DOWNGRADE_AFTER_DAYS. 278 long deltaDays = DOWNGRADE_AFTER_DAYS - 1; 279 try { 280 // Set time to future. 281 setTimeFutureDays(deltaDays); 282 283 // Set filter to verify. 284 compilePackageWithFilter(PACKAGE_NAME, "verify"); 285 Assert.assertEquals("verify", getCompilerFilter(PACKAGE_NAME)); 286 287 // Fill up storage to trigger low storage threshold. 288 fillUpToLowStorage(); 289 290 runBackgroundDexOpt(); 291 292 // Verify that downgrade did not happen. 293 Assert.assertEquals(BG_DEXOPT_COMPILER_FILTER, getCompilerFilter(PACKAGE_NAME)); 294 } finally { 295 // Reset time. 296 setTimeFutureDays(-deltaDays); 297 } 298 } 299 300 // Test that background dexopt under low storage conditions downgrades unused packages. 301 @Test testBackgroundDexOptDowngradeSuccessful()302 public void testBackgroundDexOptDowngradeSuccessful() throws IOException { 303 // Should be more than DOWNGRADE_AFTER_DAYS. 304 long deltaDays = DOWNGRADE_AFTER_DAYS + 1; 305 try { 306 // Set time to future. 307 setTimeFutureDays(deltaDays); 308 309 // Set filter to speed-profile. 310 compilePackageWithFilter(PACKAGE_NAME, "speed-profile"); 311 Assert.assertEquals("speed-profile", getCompilerFilter(PACKAGE_NAME)); 312 313 // Fill up storage to trigger low storage threshold. 314 fillUpToLowStorage(); 315 316 runBackgroundDexOpt(); 317 318 // Verify that downgrade is successful. 319 Assert.assertEquals(DOWNGRADE_COMPILER_FILTER, getCompilerFilter(PACKAGE_NAME)); 320 } finally { 321 // Reset time. 322 setTimeFutureDays(-deltaDays); 323 } 324 } 325 326 // Test that background dexopt job doesn't trigger if the device is under thermal throttling. 327 @Test testBackgroundDexOptThermalThrottling()328 public void testBackgroundDexOptThermalThrottling() throws IOException { 329 try { 330 compilePackageWithFilter(PACKAGE_NAME, "verify"); 331 overrideThermalStatus(PowerManager.THERMAL_STATUS_MODERATE); 332 // The bgdexopt task should fail when onStartJob is run 333 runBackgroundDexOpt("Failure"); 334 Assert.assertEquals("verify", getCompilerFilter(PACKAGE_NAME)); 335 } finally { 336 resetThermalStatus(); 337 } 338 } 339 } 340