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