• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
setUpAll()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 
getContext()107     private static Context getContext() {
108         return InstrumentationRegistry.getTargetContext();
109     }
110 
111     @Before
setUp()112     public void setUp() throws IOException {
113         File dataDir = getContext().getDataDir();
114         mBigFile = new File(dataDir, BIG_FILE);
115     }
116 
117     @After
tearDown()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.
inputStreamToString(InputStream is)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.
runShellCommand(String cmd)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.
runShellCommandSplitLines(String cmd)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.
getCompilerFilter(String pkg)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.
getDataDirUsableSpace()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.
fillUpStorage(long bytesRemaining)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.
fillUpToLowStorage()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
runBackgroundDexOpt()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.
setTimeFutureDays(long futureDays)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.
setTimeFutureMillis(long futureMillis)218     private static void setTimeFutureMillis(long futureMillis) {
219         long currentTime = System.currentTimeMillis();
220         setTime(currentTime + futureMillis);
221     }
222 
setTime(long time)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.
getStorageLowBytes()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.
logSpaceRemaining()236     private static void logSpaceRemaining() throws IOException {
237         runShellCommand("df -h /data");
238     }
239 
240     // Compile the given package with the given compiler filter.
compilePackageWithFilter(String pkg, String 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
testBackgroundDexOpt()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
testBackgroundDexOptDowngradeSkipRecentlyUsedPackage()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
testBackgroundDexOptDowngradeSuccessful()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