1 /* 2 * Copyright (C) 2022 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 android.compilation.cts; 18 19 import static android.compilation.cts.Utils.CompilationArtifacts; 20 21 import static com.google.common.truth.Truth.assertThat; 22 23 import static org.junit.Assert.assertThrows; 24 import static org.junit.Assume.assumeTrue; 25 26 import android.compilation.cts.annotation.CtsTestCase; 27 import android.platform.test.annotations.RequiresFlagsEnabled; 28 import android.platform.test.flag.junit.CheckFlagsRule; 29 import android.platform.test.flag.junit.host.HostFlagsValueProvider; 30 31 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; 32 import com.android.tradefed.testtype.junit4.DeviceParameterizedRunner; 33 import com.android.tradefed.testtype.junit4.DeviceTestRunOptions; 34 import com.android.tradefed.util.Pair; 35 36 import org.junit.After; 37 import org.junit.Before; 38 import org.junit.Rule; 39 import org.junit.Test; 40 import org.junit.runner.RunWith; 41 42 import java.io.File; 43 import java.util.List; 44 import java.util.regex.Pattern; 45 46 import junitparams.Parameters; 47 48 /** 49 * Compilation tests that don't require root access. 50 */ 51 @RunWith(DeviceParameterizedRunner.class) 52 @CtsTestCase 53 public class CompilationTest extends BaseHostJUnit4Test { 54 private static final String STATUS_CHECKER_PKG = "android.compilation.cts.statuscheckerapp"; 55 private static final String STATUS_CHECKER_CLASS = 56 "android.compilation.cts.statuscheckerapp.StatusCheckerAppTest"; 57 private static final String TEST_APP_PKG = "android.compilation.cts"; 58 private static final String TEST_APP_APK_RES = "/CtsCompilationApp.apk"; 59 private static final String TEST_APP_PROF_RES = "/CtsCompilationApp.prof"; 60 private static final String TEST_APP_DM_RES = "/CtsCompilationApp.dm"; 61 private static final String TEST_APP_WITH_GOOD_PROFILE_RES = 62 "/CtsCompilationApp_with_good_profile.apk"; 63 private static final String TEST_APP_WITH_BAD_PROFILE_RES = 64 "/CtsCompilationApp_with_bad_profile.apk"; 65 private static final String TEST_APP_DEBUGGABLE_RES = "/CtsCompilationApp_debuggable.apk"; 66 private static final String TEST_APP_2_PKG = "android.compilation.cts.appusedbyotherapp"; 67 private static final String TEST_APP_2_APK_RES = "/AppUsedByOtherApp.apk"; 68 private static final String TEST_APP_2_DM_RES = "/AppUsedByOtherApp_1.dm"; 69 private static final String TEST_APP_2_DISABLE_EMBEDDED_PROFILE_DM_RES = 70 "/AppUsedByOtherApp_1_disable_embedded_profile.dm"; 71 private static final String DISABLE_EMBEDDED_PROFILE_DM_RES = "/disable_embedded_profile.dm"; 72 private static final String EMPTY_CONFIG_DM_RES = "/empty_config.dm"; 73 74 @Rule 75 public final CheckFlagsRule mCheckFlagsRule = 76 HostFlagsValueProvider.createCheckFlagsRule(this::getDevice); 77 78 private Utils mUtils; 79 80 @Before setUp()81 public void setUp() throws Exception { 82 mUtils = new Utils(getTestInformation()); 83 } 84 85 @After tearDown()86 public void tearDown() throws Exception { 87 getDevice().uninstallPackage(TEST_APP_PKG); 88 getDevice().uninstallPackage(TEST_APP_2_PKG); 89 } 90 91 @Test testCompile()92 public void testCompile() throws Exception { 93 var options = new DeviceTestRunOptions(STATUS_CHECKER_PKG) 94 .setTestClassName(STATUS_CHECKER_CLASS) 95 .setTestMethodName("checkStatus") 96 .setDisableHiddenApiCheck(true); 97 98 mUtils.assertCommandSucceeds("pm compile -m speed -f " + STATUS_CHECKER_PKG); 99 options.addInstrumentationArg("compiler-filter", "speed") 100 .addInstrumentationArg("compilation-reason", "cmdline") 101 .addInstrumentationArg("is-verified", "true") 102 .addInstrumentationArg("is-optimized", "true") 103 .addInstrumentationArg("is-fully-compiled", "true"); 104 assertThat(runDeviceTests(options)).isTrue(); 105 106 mUtils.assertCommandSucceeds("pm compile -m verify -f " + STATUS_CHECKER_PKG); 107 options.addInstrumentationArg("compiler-filter", "verify") 108 .addInstrumentationArg("compilation-reason", "cmdline") 109 .addInstrumentationArg("is-verified", "true") 110 .addInstrumentationArg("is-optimized", "false") 111 .addInstrumentationArg("is-fully-compiled", "false"); 112 assertThat(runDeviceTests(options)).isTrue(); 113 114 mUtils.assertCommandSucceeds("pm delete-dexopt " + STATUS_CHECKER_PKG); 115 options.addInstrumentationArg("compiler-filter", "run-from-apk") 116 .addInstrumentationArg("compilation-reason", "unknown") 117 .addInstrumentationArg("is-verified", "false") 118 .addInstrumentationArg("is-optimized", "false") 119 .addInstrumentationArg("is-fully-compiled", "false"); 120 assertThat(runDeviceTests(options)).isTrue(); 121 } 122 123 // TODO(b/258223472): Remove this test once ART Service is the only dexopt implementation. 124 @Test testArtService()125 public void testArtService() throws Exception { 126 assertThat(getDevice().getProperty("dalvik.vm.useartservice")).isEqualTo("true"); 127 128 mUtils.installFromResources(getAbi(), TEST_APP_APK_RES, TEST_APP_DM_RES); 129 130 // Clear all user data, including the profile. 131 mUtils.assertCommandSucceeds("pm clear " + TEST_APP_PKG); 132 133 // Overwrite the artifacts compiled with the profile. 134 mUtils.assertCommandSucceeds("pm compile -m verify -f " + TEST_APP_PKG); 135 136 String dump = mUtils.assertCommandSucceeds("pm art dump " + TEST_APP_PKG); 137 assertThat(dump).contains("[status=verify]"); 138 assertThat(dump).doesNotContain("[status=speed-profile]"); 139 140 dump = mUtils.assertCommandSucceeds("dumpsys package " + TEST_APP_PKG); 141 assertThat(dump).contains("[status=verify]"); 142 assertThat(dump).doesNotContain("[status=speed-profile]"); 143 144 // Compile the app. It should use the profile in the DM file. 145 mUtils.assertCommandSucceeds("pm compile -m speed-profile " + TEST_APP_PKG); 146 147 dump = mUtils.assertCommandSucceeds("pm art dump " + TEST_APP_PKG); 148 assertThat(dump).doesNotContain("[status=verify]"); 149 assertThat(dump).contains("[status=speed-profile]"); 150 151 dump = mUtils.assertCommandSucceeds("dumpsys package " + TEST_APP_PKG); 152 assertThat(dump).doesNotContain("[status=verify]"); 153 assertThat(dump).contains("[status=speed-profile]"); 154 } 155 156 @Test 157 @Parameters({"secondary.jar", "secondary"}) testCompileSecondaryDex(String filename)158 public void testCompileSecondaryDex(String filename) throws Exception { 159 var options = new DeviceTestRunOptions(STATUS_CHECKER_PKG) 160 .setTestClassName(STATUS_CHECKER_CLASS) 161 .setTestMethodName("createAndLoadSecondaryDex") 162 .addInstrumentationArg("secondary-dex-filename", filename); 163 assertThat(runDeviceTests(options)).isTrue(); 164 165 // Verify that the secondary dex file is recorded. 166 String dump = mUtils.assertCommandSucceeds("dumpsys package " + STATUS_CHECKER_PKG); 167 checkDexoptStatus(dump, Pattern.quote(filename), ".*?"); 168 169 mUtils.assertCommandSucceeds( 170 "pm compile --secondary-dex -m speed -f " + STATUS_CHECKER_PKG); 171 dump = mUtils.assertCommandSucceeds("dumpsys package " + STATUS_CHECKER_PKG); 172 checkDexoptStatus(dump, Pattern.quote(filename), "speed"); 173 174 mUtils.assertCommandSucceeds( 175 "pm compile --secondary-dex -m verify -f " + STATUS_CHECKER_PKG); 176 dump = mUtils.assertCommandSucceeds("dumpsys package " + STATUS_CHECKER_PKG); 177 checkDexoptStatus(dump, Pattern.quote(filename), "verify"); 178 179 mUtils.assertCommandSucceeds("pm delete-dexopt " + STATUS_CHECKER_PKG); 180 dump = mUtils.assertCommandSucceeds("dumpsys package " + STATUS_CHECKER_PKG); 181 checkDexoptStatus(dump, Pattern.quote(filename), "run-from-apk"); 182 } 183 184 @Test testCompileSecondaryDexUnsupportedClassLoader()185 public void testCompileSecondaryDexUnsupportedClassLoader() throws Exception { 186 String filename = "secondary-unsupported-clc.jar"; 187 var options = new DeviceTestRunOptions(STATUS_CHECKER_PKG) 188 .setTestClassName(STATUS_CHECKER_CLASS) 189 .setTestMethodName("createAndLoadSecondaryDexUnsupportedClassLoader") 190 .addInstrumentationArg("secondary-dex-filename", filename); 191 assertThat(runDeviceTests(options)).isTrue(); 192 193 // "speed" should be downgraded to "verify" because the CLC is unsupported. 194 mUtils.assertCommandSucceeds( 195 "pm compile --secondary-dex -m speed -f " + STATUS_CHECKER_PKG); 196 String dump = mUtils.assertCommandSucceeds("dumpsys package " + STATUS_CHECKER_PKG); 197 checkDexoptStatus(dump, Pattern.quote(filename), "verify"); 198 } 199 200 @Test testSecondaryDexReporting()201 public void testSecondaryDexReporting() throws Exception { 202 var options = new DeviceTestRunOptions(STATUS_CHECKER_PKG) 203 .setTestClassName(STATUS_CHECKER_CLASS) 204 .setTestMethodName("testSecondaryDexReporting") 205 .setDisableHiddenApiCheck(true); 206 assertThat(runDeviceTests(options)).isTrue(); 207 208 String dump = mUtils.assertCommandSucceeds("dumpsys package " + STATUS_CHECKER_PKG); 209 Utils.dumpDoesNotContainDexFile(dump, "reported_bad_1.apk"); 210 Utils.dumpDoesNotContainDexFile(dump, "reported_bad_2.apk"); 211 Utils.dumpDoesNotContainDexFile(dump, "reported_bad_3.apk"); 212 Utils.dumpDoesNotContainDexFile(dump, "reported_bad_4.apk"); 213 Utils.dumpContainsDexFile(dump, "reported_good_1.apk"); 214 Utils.dumpContainsDexFile(dump, "reported_good_2.apk"); 215 Utils.dumpContainsDexFile(dump, "reported_good_3.apk"); 216 217 // Check that ART Service doesn't crash on various operations after invalid dex paths and 218 // class loader contexts are reported. 219 mUtils.assertCommandSucceeds( 220 "pm compile --secondary-dex -m verify -f " + STATUS_CHECKER_PKG); 221 mUtils.assertCommandSucceeds("pm art clear-app-profiles " + STATUS_CHECKER_PKG); 222 mUtils.assertCommandSucceeds("pm art cleanup"); 223 } 224 225 @Test testGetDexFileOutputPaths()226 public void testGetDexFileOutputPaths() throws Exception { 227 mUtils.assertCommandSucceeds("pm compile -m verify -f " + STATUS_CHECKER_PKG); 228 229 var options = new DeviceTestRunOptions(STATUS_CHECKER_PKG) 230 .setTestClassName(STATUS_CHECKER_CLASS) 231 .setTestMethodName("testGetDexFileOutputPaths") 232 .setDisableHiddenApiCheck(true); 233 assertThat(runDeviceTests(options)).isTrue(); 234 } 235 236 @Test testExternalProfileValidationOk()237 public void testExternalProfileValidationOk() throws Exception { 238 mUtils.installFromResources(getAbi(), TEST_APP_APK_RES, TEST_APP_DM_RES); 239 } 240 241 /** Verifies that adb install-multiple fails when the APK and the DM file don't match. */ 242 @Test testExternalProfileValidationFailed()243 public void testExternalProfileValidationFailed() throws Exception { 244 Throwable throwable = assertThrows(Throwable.class, () -> { 245 mUtils.installFromResources(getAbi(), TEST_APP_APK_RES, TEST_APP_2_DM_RES); 246 }); 247 assertThat(throwable).hasMessageThat().contains( 248 "Error occurred during dexopt when processing external profiles:"); 249 } 250 251 @Test testExternalProfileValidationMultiPackageOk()252 public void testExternalProfileValidationMultiPackageOk() throws Exception { 253 mUtils.installFromResourcesMultiPackage(getAbi(), 254 List.of(List.of(Pair.create(TEST_APP_APK_RES, TEST_APP_DM_RES)), 255 List.of(Pair.create(TEST_APP_2_APK_RES, TEST_APP_2_DM_RES)))); 256 } 257 258 /** 259 * Verifies that adb install-multi-package fails when the mismatch happens on one of the APK-DM 260 * pairs. 261 */ 262 @Test testExternalProfileValidationMultiPackageFailed()263 public void testExternalProfileValidationMultiPackageFailed() throws Exception { 264 Throwable throwable = assertThrows(Throwable.class, () -> { 265 mUtils.installFromResourcesMultiPackage(getAbi(), 266 List.of(List.of(Pair.create(TEST_APP_APK_RES, TEST_APP_DM_RES)), 267 List.of(Pair.create(TEST_APP_2_APK_RES, TEST_APP_DM_RES)))); 268 }); 269 270 assertThat(Utils.countSubstringOccurrence(throwable.getMessage(), 271 "Error occurred during dexopt when processing external profiles:")) 272 .isEqualTo(1); 273 } 274 275 @Test testEmbeddedProfileOk()276 public void testEmbeddedProfileOk() throws Exception { 277 mUtils.installFromResources(getAbi(), TEST_APP_WITH_GOOD_PROFILE_RES); 278 String dump = mUtils.assertCommandSucceeds("pm art dump " + TEST_APP_PKG); 279 checkDexoptStatus(dump, Pattern.quote("base.apk"), "speed-profile"); 280 } 281 282 @Test testEmbeddedProfileFailed()283 public void testEmbeddedProfileFailed() throws Exception { 284 Throwable throwable = assertThrows(Throwable.class, 285 () -> { mUtils.installFromResources(getAbi(), TEST_APP_WITH_BAD_PROFILE_RES); }); 286 assertThat(throwable).hasMessageThat().contains( 287 "Error occurred during dexopt when processing external profiles:"); 288 } 289 290 @Test testEmbeddedProfileEmptyConfig()291 public void testEmbeddedProfileEmptyConfig() throws Exception { 292 // A DM with a config file is provided, but it's empty, so it should have no impact on the 293 // embedded profile. 294 mUtils.installFromResources(getAbi(), TEST_APP_WITH_GOOD_PROFILE_RES, EMPTY_CONFIG_DM_RES); 295 String dump = mUtils.assertCommandSucceeds("pm art dump " + TEST_APP_PKG); 296 checkDexoptStatus(dump, Pattern.quote("base.apk"), "speed-profile"); 297 } 298 299 @Test testEmbeddedProfileConfigDisabledByConfig()300 public void testEmbeddedProfileConfigDisabledByConfig() throws Exception { 301 // A DM with a config file is provided, and it disables embedded profile. 302 mUtils.installFromResources( 303 getAbi(), TEST_APP_WITH_GOOD_PROFILE_RES, DISABLE_EMBEDDED_PROFILE_DM_RES); 304 String dump = mUtils.assertCommandSucceeds("pm art dump " + TEST_APP_PKG); 305 checkDexoptStatus(dump, Pattern.quote("base.apk"), "verify"); 306 } 307 308 /** 309 * Verifies that adb install-multi-package fails with multiple error messages when multiple 310 * APK-DM mismatches happen. 311 */ 312 @Test testExternalProfileValidationMultiPackageFailedMultipleErrors()313 public void testExternalProfileValidationMultiPackageFailedMultipleErrors() throws Exception { 314 Throwable throwable = assertThrows(Throwable.class, () -> { 315 mUtils.installFromResourcesMultiPackage(getAbi(), 316 List.of(List.of(Pair.create(TEST_APP_APK_RES, TEST_APP_2_DM_RES)), 317 List.of(Pair.create(TEST_APP_2_APK_RES, TEST_APP_DM_RES)))); 318 }); 319 320 assertThat(Utils.countSubstringOccurrence(throwable.getMessage(), 321 "Error occurred during dexopt when processing external profiles:")) 322 .isEqualTo(2); 323 } 324 325 @Test testIgnoreDexoptProfile()326 public void testIgnoreDexoptProfile() throws Exception { 327 // Both the APK and the DM have a good profile, but ART Service should use none of them. 328 mUtils.installFromResourcesWithArgs(getAbi(), List.of("--ignore-dexopt-profile"), 329 List.of(Pair.create(TEST_APP_WITH_GOOD_PROFILE_RES, TEST_APP_DM_RES))); 330 String dump = mUtils.assertCommandSucceeds("pm art dump " + TEST_APP_PKG); 331 checkDexoptStatus(dump, Pattern.quote("base.apk"), "verify"); 332 } 333 334 @Test testIgnoreDexoptProfileNoValidation()335 public void testIgnoreDexoptProfileNoValidation() throws Exception { 336 // Both the APK and the DM have a bad profile, but ART Service should not complain. 337 mUtils.installFromResourcesWithArgs(getAbi(), List.of("--ignore-dexopt-profile"), 338 List.of(Pair.create(TEST_APP_WITH_BAD_PROFILE_RES, TEST_APP_2_DM_RES))); 339 String dump = mUtils.assertCommandSucceeds("pm art dump " + TEST_APP_PKG); 340 checkDexoptStatus(dump, Pattern.quote("base.apk"), "verify"); 341 } 342 343 @Test testFallBackToEmbeddedProfile()344 public void testFallBackToEmbeddedProfile() throws Exception { 345 // The DM has a bad profile, so ART Service should fall back to the embedded profile. 346 assertThrows(Throwable.class, () -> { 347 mUtils.installFromResources( 348 getAbi(), TEST_APP_WITH_GOOD_PROFILE_RES, TEST_APP_2_DM_RES); 349 }); 350 String dump = mUtils.assertCommandSucceeds("pm art dump " + TEST_APP_PKG); 351 checkDexoptStatus(dump, Pattern.quote("base.apk"), "speed-profile"); 352 } 353 354 @Test testNoFallBackToEmbeddedProfile()355 public void testNoFallBackToEmbeddedProfile() throws Exception { 356 // The DM has a bad profile, but it also has a config that disables embedded profile, so ART 357 // Service should not fall back to the embedded profile. 358 assertThrows(Throwable.class, () -> { 359 mUtils.installFromResources(getAbi(), TEST_APP_WITH_GOOD_PROFILE_RES, 360 TEST_APP_2_DISABLE_EMBEDDED_PROFILE_DM_RES); 361 }); 362 String dump = mUtils.assertCommandSucceeds("pm art dump " + TEST_APP_PKG); 363 checkDexoptStatus(dump, Pattern.quote("base.apk"), "verify"); 364 } 365 366 @Test testInstallCompilerFilterDefault()367 public void testInstallCompilerFilterDefault() throws Exception { 368 assumeTrue(getDevice().getProperty("pm.dexopt.install").equals("speed-profile")); 369 370 mUtils.installFromResources(getAbi(), TEST_APP_WITH_GOOD_PROFILE_RES); 371 String dump = mUtils.assertCommandSucceeds("pm art dump " + TEST_APP_PKG); 372 checkDexoptStatus(dump, Pattern.quote("base.apk"), "speed-profile"); 373 } 374 375 @Test testInstallCompilerFilterDebuggable()376 public void testInstallCompilerFilterDebuggable() throws Exception { 377 mUtils.installFromResources(getAbi(), TEST_APP_DEBUGGABLE_RES); 378 String dump = mUtils.assertCommandSucceeds("pm art dump " + TEST_APP_PKG); 379 checkDexoptStatus(dump, Pattern.quote("base.apk"), "run-from-apk"); 380 } 381 382 @Test testInstallCompilerFilterOverride()383 public void testInstallCompilerFilterOverride() throws Exception { 384 mUtils.installFromResourcesWithArgs(getAbi(), List.of("--dexopt-compiler-filter", "verify"), 385 List.of(Pair.create(TEST_APP_DEBUGGABLE_RES, null))); 386 String dump = mUtils.assertCommandSucceeds("pm art dump " + TEST_APP_PKG); 387 checkDexoptStatus(dump, Pattern.quote("base.apk"), "verify"); 388 } 389 390 @Test testInstallCompilerFilterOverrideSkip()391 public void testInstallCompilerFilterOverrideSkip() throws Exception { 392 mUtils.installFromResourcesWithArgs(getAbi(), List.of("--dexopt-compiler-filter", "skip"), 393 List.of(Pair.create(TEST_APP_WITH_GOOD_PROFILE_RES, null))); 394 String dump = mUtils.assertCommandSucceeds("pm art dump " + TEST_APP_PKG); 395 checkDexoptStatus(dump, Pattern.quote("base.apk"), "run-from-apk"); 396 } 397 398 @Test testInstallCompilerFilterOverrideInvalid()399 public void testInstallCompilerFilterOverrideInvalid() throws Exception { 400 Throwable throwable = assertThrows(Throwable.class, () -> { 401 mUtils.installFromResourcesWithArgs(getAbi(), 402 List.of("--dexopt-compiler-filter", "bogus"), 403 List.of(Pair.create(TEST_APP_WITH_GOOD_PROFILE_RES, null))); 404 }); 405 406 assertThat(throwable.getMessage()).contains("Invalid compiler filter"); 407 } 408 409 @Test 410 @RequiresFlagsEnabled({android.content.pm.Flags.FLAG_CLOUD_COMPILATION_PM, 411 com.android.art.flags.Flags.FLAG_ART_SERVICE_V3}) testSdmOk()412 public void testSdmOk() throws Exception { 413 CompilationArtifacts artifacts = 414 mUtils.generateCompilationArtifacts(TEST_APP_APK_RES, TEST_APP_PROF_RES); 415 File dmFile = mUtils.createDm(TEST_APP_PROF_RES, artifacts.vdexFile()); 416 File sdmFile = mUtils.createSdm(artifacts.odexFile(), artifacts.artFile()); 417 418 mUtils.installFromResourcesWithSdm(getAbi(), TEST_APP_APK_RES, dmFile, sdmFile); 419 String dump = 420 mUtils.assertCommandSucceeds("pm art dump --verify-sdm-signatures " + TEST_APP_PKG); 421 assertThat(dump).contains("[sdm-signature=verified]"); 422 } 423 checkDexoptStatus(String dump, String dexfilePattern, String statusPattern)424 private void checkDexoptStatus(String dump, String dexfilePattern, String statusPattern) { 425 // Matches the dump output typically being: 426 // /data/user/0/android.compilation.cts.statuscheckerapp/secondary.jar 427 // x86_64: [status=speed] [reason=cmdline] [primary-abi] 428 // The pattern is intentionally minimized to be as forward compatible as possible. 429 // TODO(b/283447251): Use a machine-readable format. 430 assertThat(dump).containsMatch( 431 Pattern.compile(String.format("[\\s/](%s)(\\s[^\\n]*)?\\n[^\\n]*\\[status=(%s)\\]", 432 dexfilePattern, statusPattern))); 433 } 434 } 435