diff options
author | Arthur Eubanks <aeubanks@google.com> | 2017-09-15 09:28:51 -0700 |
---|---|---|
committer | Arthur Eubanks <aeubanks@google.com> | 2017-12-01 23:32:39 +0000 |
commit | 1ea7ed0278ea9b8bcfbd2e0e6ec5277c356f2a57 (patch) | |
tree | a70c9a7caf785180d9cde0056df10675eba21392 /tests/BackgroundDexOptServiceIntegrationTests | |
parent | ace4014402bf6bbaf900b425da61e2b87677de60 (diff) |
Add integration tests for BackgroundDexOptService
Add three tests:
1. Under normal conditions, check that dexopt upgrades test app to
$(getprop pm.dexopt.bg-dexopt).
2. Under low storage conditions and package is unused, check
that dexopt downgrades test app to $(getprop pm.dexopt.inactive).
3. Under low storage conditions and package is recently used, check
that dexopt upgrades test app to $(getprop pm.dexopt.bg-dexopt).
Test: atest -v BackgroundDexOptServiceIntegrationTests
BUG: 64807719
Change-Id: Iaa50d5120ea0255b38226bda0452e7e47f1ff5d0
Diffstat (limited to 'tests/BackgroundDexOptServiceIntegrationTests')
4 files changed, 442 insertions, 0 deletions
diff --git a/tests/BackgroundDexOptServiceIntegrationTests/Android.mk b/tests/BackgroundDexOptServiceIntegrationTests/Android.mk new file mode 100644 index 000000000000..da1a08b79a8f --- /dev/null +++ b/tests/BackgroundDexOptServiceIntegrationTests/Android.mk @@ -0,0 +1,34 @@ +# +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +# We only want this apk build for tests. +LOCAL_MODULE_TAGS := tests + +# Include all test java files. +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_STATIC_JAVA_LIBRARIES := \ + android-support-test \ + +LOCAL_PACKAGE_NAME := BackgroundDexOptServiceIntegrationTests +LOCAL_COMPATIBILITY_SUITE := device-tests + +LOCAL_CERTIFICATE := platform + +include $(BUILD_PACKAGE) diff --git a/tests/BackgroundDexOptServiceIntegrationTests/AndroidManifest.xml b/tests/BackgroundDexOptServiceIntegrationTests/AndroidManifest.xml new file mode 100644 index 000000000000..afae155f88fe --- /dev/null +++ b/tests/BackgroundDexOptServiceIntegrationTests/AndroidManifest.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.frameworks.bgdexopttest"> + + + <!-- Uses API introduced in O (26) --> + <uses-sdk + android:minSdkVersion="1" + android:targetSdkVersion="26" /> + + <uses-permission android:name="android.permission.DUMP" /> + <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" /> + <uses-permission android:name="android.permission.SET_TIME" /> + <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" /> + <uses-permission android:name="android.permission.WAKE_LOCK" /> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation + android:name="android.support.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.frameworks.bgdexopttest" + android:label="Integration test for BackgroundDexOptService" /> +</manifest> diff --git a/tests/BackgroundDexOptServiceIntegrationTests/AndroidTest.xml b/tests/BackgroundDexOptServiceIntegrationTests/AndroidTest.xml new file mode 100644 index 000000000000..9bb1e280b861 --- /dev/null +++ b/tests/BackgroundDexOptServiceIntegrationTests/AndroidTest.xml @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="Runs BackgroundDexOptService Integration Tests"> + <!--DeviceSetup should go before TimeSetter because it stops automatic update of time--> + <target_preparer + class="com.android.tradefed.targetprep.DeviceSetup"> + <option name="auto-update-time" value="OFF"/> + <option name="auto-update-timezone" value="OFF"/> + <option name="set-property" key="pm.dexopt.downgrade_after_inactive_days" value="2"/> + <option name="set-property" key="pm.dexopt.disable_bg_dexopt" value="true"/> + <option name="set-property" key="pm.dexopt.inactive" value="verify"/> + <option name="set-property" key="pm.dexopt.bg-dexopt" value="speed"/> + <option name="restore-settings" value="true"/> + <option name="restore-properties" value="true"/> + </target_preparer> + + <!--Test app needs to be installed when we change its settings below--> + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="test-file-name" value="BackgroundDexOptServiceIntegrationTests.apk"/> + <option name="cleanup-apks" value="true"/> + </target_preparer> + + <target_preparer class="com.android.tradefed.targetprep.SetPackagesRecentlyUsed"> + <option name="package-recently-used-time" value="0d"/> + <option name="package-recently-used-name" value="com.android.frameworks.bgdexopttest"/> + </target_preparer> + + <target_preparer class="com.android.tradefed.targetprep.RestartSystemServerTargetPreparer"/> + + <target_preparer class="com.android.tradefed.targetprep.DeviceStorageFiller"> + <!--32GB--> + <!--necessary because a package cannot create a file larger than 100GB--> + <option name="free-bytes" value="34359738368"/> + </target_preparer> + + <option name="test-suite-tag" value="apct"/> + <option name="test-tag" value="BackgroundDexOptServiceIntegrationTests"/> + <test class="com.android.tradefed.testtype.AndroidJUnitTest"> + <option name="package" value="com.android.frameworks.bgdexopttest"/> + <option name="runner" value="android.support.test.runner.AndroidJUnitRunner"/> + </test> +</configuration> diff --git a/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java b/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java new file mode 100644 index 000000000000..3734412981d8 --- /dev/null +++ b/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import android.app.AlarmManager; +import android.content.Context; +import android.os.Environment; +import android.os.SystemProperties; +import android.os.storage.StorageManager; +import android.support.test.InstrumentationRegistry; +import android.util.Log; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.concurrent.TimeUnit; + +/** + * Integration tests for {@link BackgroundDexOptService}. + * + * Tests various scenarios around BackgroundDexOptService. + * 1. Under normal conditions, check that dexopt upgrades test app to + * $(getprop pm.dexopt.bg-dexopt). + * 2. Under low storage conditions and package is unused, check + * that dexopt downgrades test app to $(getprop pm.dexopt.inactive). + * 3. Under low storage conditions and package is recently used, check + * that dexopt upgrades test app to $(getprop pm.dexopt.bg-dexopt). + * + * Each test case runs "cmd package bg-dexopt-job com.android.frameworks.bgdexopttest". + * + * The setup for these tests make sure this package has been configured to have been recently used + * plus installed far enough in the past. If a test case requires that this package has not been + * recently used, it sets the time forward more than + * `getprop pm.dexopt.downgrade_after_inactive_days` days. + * + * For tests that require low storage, the phone is filled up. + * + * Run with "atest BackgroundDexOptServiceIntegrationTests". + */ +@RunWith(JUnit4.class) +public final class BackgroundDexOptServiceIntegrationTests { + + private static final String TAG = BackgroundDexOptServiceIntegrationTests.class.getSimpleName(); + + // Name of package to test on. + private static final String PACKAGE_NAME = "com.android.frameworks.bgdexopttest"; + // Name of file used to fill up storage. + private static final String BIG_FILE = "bigfile"; + private static final String BG_DEXOPT_COMPILER_FILTER = SystemProperties.get( + "pm.dexopt.bg-dexopt"); + private static final String DOWNGRADE_COMPILER_FILTER = SystemProperties.get( + "pm.dexopt.inactive"); + private static final long DOWNGRADE_AFTER_DAYS = SystemProperties.getLong( + "pm.dexopt.downgrade_after_inactive_days", 0); + // Needs to be between 1.0 and 2.0. + private static final double LOW_STORAGE_MULTIPLIER = 1.5; + + // The file used to fill up storage. + private File mBigFile; + + // Remember start time. + @BeforeClass + public static void setUpAll() { + if (!SystemProperties.getBoolean("pm.dexopt.disable_bg_dexopt", false)) { + throw new RuntimeException( + "bg-dexopt is not disabled (set pm.dexopt.disable_bg_dexopt to true)"); + } + if (DOWNGRADE_AFTER_DAYS < 1) { + throw new RuntimeException( + "pm.dexopt.downgrade_after_inactive_days must be at least 1"); + } + if ("quicken".equals(BG_DEXOPT_COMPILER_FILTER)) { + throw new RuntimeException("pm.dexopt.bg-dexopt should not be \"quicken\""); + } + if ("quicken".equals(DOWNGRADE_COMPILER_FILTER)) { + throw new RuntimeException("pm.dexopt.inactive should not be \"quicken\""); + } + } + + + private static Context getContext() { + return InstrumentationRegistry.getTargetContext(); + } + + @Before + public void setUp() throws IOException { + File dataDir = getContext().getDataDir(); + mBigFile = new File(dataDir, BIG_FILE); + } + + @After + public void tearDown() { + if (mBigFile.exists()) { + boolean result = mBigFile.delete(); + if (!result) { + throw new RuntimeException("Couldn't delete big file"); + } + } + } + + // Return the content of the InputStream as a String. + private static String inputStreamToString(InputStream is) throws IOException { + char[] buffer = new char[1024]; + StringBuilder builder = new StringBuilder(); + try (InputStreamReader reader = new InputStreamReader(is)) { + for (; ; ) { + int count = reader.read(buffer, 0, buffer.length); + if (count < 0) { + break; + } + builder.append(buffer, 0, count); + } + } + return builder.toString(); + } + + // Run the command and return the stdout. + private static String runShellCommand(String cmd) throws IOException { + Log.i(TAG, String.format("running command: '%s'", cmd)); + long startTime = System.nanoTime(); + Process p = Runtime.getRuntime().exec(cmd); + int res; + try { + res = p.waitFor(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + String stdout = inputStreamToString(p.getInputStream()); + String stderr = inputStreamToString(p.getErrorStream()); + long elapsedTime = System.nanoTime() - startTime; + Log.i(TAG, String.format("ran command: '%s' in %d ms with return code %d", cmd, + TimeUnit.NANOSECONDS.toMillis(elapsedTime), res)); + Log.i(TAG, "stdout"); + Log.i(TAG, stdout); + Log.i(TAG, "stderr"); + Log.i(TAG, stderr); + if (res != 0) { + throw new RuntimeException(String.format("failed command: '%s'", cmd)); + } + return stdout; + } + + // Run the command and return the stdout split by lines. + private static String[] runShellCommandSplitLines(String cmd) throws IOException { + return runShellCommand(cmd).split("\n"); + } + + // Return the compiler filter of a package. + private static String getCompilerFilter(String pkg) throws IOException { + String cmd = String.format("dumpsys package %s", pkg); + String[] lines = runShellCommandSplitLines(cmd); + final String substr = "compilation_filter="; + for (String line : lines) { + int startIndex = line.indexOf(substr); + if (startIndex < 0) { + continue; + } + startIndex += substr.length(); + int endIndex = line.indexOf(']', startIndex); + return line.substring(startIndex, endIndex); + } + throw new RuntimeException("Couldn't find compiler filter in dumpsys package"); + } + + // Return the number of bytes available in the data partition. + private static long getDataDirUsableSpace() { + return Environment.getDataDirectory().getUsableSpace(); + } + + // Fill up the storage until there are bytesRemaining number of bytes available in the data + // partition. Writes to the current package's data directory. + private void fillUpStorage(long bytesRemaining) throws IOException { + Log.i(TAG, String.format("Filling up storage with %d bytes remaining", bytesRemaining)); + logSpaceRemaining(); + long numBytesToAdd = getDataDirUsableSpace() - bytesRemaining; + String cmd = String.format("fallocate -l %d %s", numBytesToAdd, mBigFile.getAbsolutePath()); + runShellCommand(cmd); + logSpaceRemaining(); + } + + // Fill up storage so that device is in low storage condition. + private void fillUpToLowStorage() throws IOException { + fillUpStorage((long) (getStorageLowBytes() * LOW_STORAGE_MULTIPLIER)); + } + + // TODO(aeubanks): figure out how to get scheduled bg-dexopt to run + private static void runBackgroundDexOpt() throws IOException { + runShellCommand("cmd package bg-dexopt-job " + PACKAGE_NAME); + } + + // Set the time ahead of the last use time of the test app in days. + private static void setTimeFutureDays(long futureDays) { + setTimeFutureMillis(TimeUnit.DAYS.toMillis(futureDays)); + } + + // Set the time ahead of the last use time of the test app in milliseconds. + private static void setTimeFutureMillis(long futureMillis) { + long currentTime = System.currentTimeMillis(); + setTime(currentTime + futureMillis); + } + + private static void setTime(long time) { + AlarmManager am = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE); + am.setTime(time); + } + + // Return the number of free bytes when the data partition is considered low on storage. + private static long getStorageLowBytes() { + StorageManager storageManager = (StorageManager) getContext().getSystemService( + Context.STORAGE_SERVICE); + return storageManager.getStorageLowBytes(Environment.getDataDirectory()); + } + + // Log the amount of space remaining in the data directory. + private static void logSpaceRemaining() throws IOException { + runShellCommand("df -h /data"); + } + + // Compile the given package with the given compiler filter. + private static void compilePackageWithFilter(String pkg, String filter) throws IOException { + runShellCommand(String.format("cmd package compile -f -m %s %s", filter, pkg)); + } + + // Test that background dexopt under normal conditions succeeds. + @Test + public void testBackgroundDexOpt() throws IOException { + // Set filter to quicken. + compilePackageWithFilter(PACKAGE_NAME, "verify"); + Assert.assertEquals("verify", getCompilerFilter(PACKAGE_NAME)); + + runBackgroundDexOpt(); + + // Verify that bg-dexopt is successful. + Assert.assertEquals(BG_DEXOPT_COMPILER_FILTER, getCompilerFilter(PACKAGE_NAME)); + } + + // Test that background dexopt under low storage conditions upgrades used packages. + @Test + public void testBackgroundDexOptDowngradeSkipRecentlyUsedPackage() throws IOException { + // Should be less than DOWNGRADE_AFTER_DAYS. + long deltaDays = DOWNGRADE_AFTER_DAYS - 1; + try { + // Set time to future. + setTimeFutureDays(deltaDays); + + // Set filter to quicken. + compilePackageWithFilter(PACKAGE_NAME, "quicken"); + Assert.assertEquals("quicken", getCompilerFilter(PACKAGE_NAME)); + + // Fill up storage to trigger low storage threshold. + fillUpToLowStorage(); + + runBackgroundDexOpt(); + + // Verify that downgrade did not happen. + Assert.assertEquals(BG_DEXOPT_COMPILER_FILTER, getCompilerFilter(PACKAGE_NAME)); + } finally { + // Reset time. + setTimeFutureDays(-deltaDays); + } + } + + // Test that background dexopt under low storage conditions downgrades unused packages. + @Test + public void testBackgroundDexOptDowngradeSuccessful() throws IOException { + // Should be more than DOWNGRADE_AFTER_DAYS. + long deltaDays = DOWNGRADE_AFTER_DAYS + 1; + try { + // Set time to future. + setTimeFutureDays(deltaDays); + + // Set filter to quicken. + compilePackageWithFilter(PACKAGE_NAME, "quicken"); + Assert.assertEquals("quicken", getCompilerFilter(PACKAGE_NAME)); + + // Fill up storage to trigger low storage threshold. + fillUpToLowStorage(); + + runBackgroundDexOpt(); + + // Verify that downgrade is successful. + Assert.assertEquals(DOWNGRADE_COMPILER_FILTER, getCompilerFilter(PACKAGE_NAME)); + } finally { + // Reset time. + setTimeFutureDays(-deltaDays); + } + } + +} |