blob: 7d826f7172da067db119b4804e5ac19cd122c8a6 [file] [log] [blame]
Arthur Eubanks1ea7ed02017-09-15 09:28:51 -07001/*
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
17package com.android.server.pm;
18
19import android.app.AlarmManager;
20import android.content.Context;
21import android.os.Environment;
Jorim Jaggi7804f7f2018-08-09 16:23:22 +020022import android.os.ParcelFileDescriptor;
Arthur Eubanks1ea7ed02017-09-15 09:28:51 -070023import android.os.SystemProperties;
24import android.os.storage.StorageManager;
Arthur Eubanks1ea7ed02017-09-15 09:28:51 -070025import android.util.Log;
26
Brett Chabot502ec7a2019-03-01 14:43:20 -080027import androidx.test.InstrumentationRegistry;
28
Arthur Eubanks1ea7ed02017-09-15 09:28:51 -070029import org.junit.After;
Arthur Eubanks1ea7ed02017-09-15 09:28:51 -070030import org.junit.Assert;
31import org.junit.Before;
32import org.junit.BeforeClass;
33import org.junit.Test;
34import org.junit.runner.RunWith;
35import org.junit.runners.JUnit4;
36
37import java.io.File;
Jorim Jaggi7804f7f2018-08-09 16:23:22 +020038import java.io.FileInputStream;
Arthur Eubanks1ea7ed02017-09-15 09:28:51 -070039import java.io.IOException;
40import java.io.InputStream;
41import java.io.InputStreamReader;
42import 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)
67public 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));
Jorim Jaggi7804f7f2018-08-09 16:23:22 +0200146 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));
Arthur Eubanks1ea7ed02017-09-15 09:28:51 -0700154 }
Jorim Jaggi7804f7f2018-08-09 16:23:22 +0200155 fis.close();
Andreas Gampefa8b57d2018-08-31 15:47:01 -0700156 Log.i(TAG, "stdout");
157 Log.i(TAG, stdout.toString());
Jorim Jaggi7804f7f2018-08-09 16:23:22 +0200158 return stdout.toString();
Arthur Eubanks1ea7ed02017-09-15 09:28:51 -0700159 }
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);
Calin Juravle7fc0f632018-03-30 12:38:59 -0700170 final String substr = "[status=";
Arthur Eubanks1ea7ed02017-09-15 09:28:51 -0700171 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 {
Andreas Gampefa8b57d2018-08-31 15:47:01 -0700206 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 }
Arthur Eubanks1ea7ed02017-09-15 09:28:51 -0700210 }
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}