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