1 /* 2 * Copyright (C) 2016 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 com.google.common.truth.Truth.assertThat; 20 import static com.google.common.truth.Truth.assertWithMessage; 21 22 import static org.junit.Assert.assertEquals; 23 import static org.junit.Assert.assertNull; 24 import static org.junit.Assert.assertTrue; 25 import static org.junit.Assert.fail; 26 import static org.junit.Assume.assumeTrue; 27 28 import com.android.tradefed.device.DeviceNotAvailableException; 29 import com.android.tradefed.device.ITestDevice; 30 import com.android.tradefed.device.NativeDevice; 31 import com.android.tradefed.device.TestDeviceState; 32 import com.android.tradefed.log.LogUtil.CLog; 33 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; 34 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; 35 import com.android.tradefed.testtype.junit4.DeviceTestRunOptions; 36 import com.android.tradefed.util.CommandResult; 37 import com.android.tradefed.util.FileUtil; 38 39 import com.google.common.io.ByteStreams; 40 41 import org.junit.After; 42 import org.junit.Before; 43 import org.junit.Test; 44 import org.junit.runner.RunWith; 45 46 import java.io.File; 47 import java.io.FileInputStream; 48 import java.io.FileOutputStream; 49 import java.io.InputStream; 50 import java.io.OutputStream; 51 import java.util.ArrayList; 52 import java.util.Arrays; 53 import java.util.EnumSet; 54 import java.util.List; 55 import java.util.Locale; 56 import java.util.Objects; 57 import java.util.Set; 58 import java.util.regex.Matcher; 59 import java.util.regex.Pattern; 60 import java.util.zip.ZipEntry; 61 import java.util.zip.ZipOutputStream; 62 63 /** 64 * Various integration tests for dex to oat compilation, with or without profiles. 65 * When changing this test, make sure it still passes in each of the following 66 * configurations: 67 * <ul> 68 * <li>On a 'user' build</li> 69 * <li>On a 'userdebug' build with system property 'dalvik.vm.usejitprofiles' set to false</li> 70 * <li>On a 'userdebug' build with system property 'dalvik.vm.usejitprofiles' set to true</li> 71 * </ul> 72 */ 73 @RunWith(DeviceJUnit4ClassRunner.class) 74 public class AdbRootDependentCompilationTest extends BaseHostJUnit4Test { 75 private static final int ADB_ROOT_RETRY_ATTEMPTS = 3; 76 private static final String TEMP_DIR = "/data/local/tmp/AdbRootDependentCompilationTest"; 77 private static final String APPLICATION_PACKAGE = "android.compilation.cts"; 78 private static final String APP_USED_BY_OTHER_APP_PACKAGE = 79 "android.compilation.cts.appusedbyotherapp"; 80 private static final String APP_USING_OTHER_APP_PACKAGE = 81 "android.compilation.cts.appusingotherapp"; 82 private static final int PERMISSIONS_LENGTH = 10; 83 private static final int READ_OTHER = 7; 84 85 enum ProfileLocation { 86 CUR("/data/misc/profiles/cur/0/"), 87 REF("/data/misc/profiles/ref/"); 88 89 private String directory; 90 ProfileLocation(String directory)91 ProfileLocation(String directory) { 92 this.directory = directory; 93 } 94 getDirectory(String packageName)95 public String getDirectory(String packageName) { 96 return directory + packageName; 97 } 98 getPath(String packageName)99 public String getPath(String packageName) { 100 return directory + packageName + "/primary.prof"; 101 } 102 } 103 104 private ITestDevice mDevice; 105 private File mCtsCompilationAppApkFile; 106 private File mAppUsedByOtherAppApkFile; 107 private File mAppUsedByOtherAppDmFile; 108 private File mAppUsingOtherAppApkFile; 109 private boolean mWasAdbRoot = false; 110 private boolean mAdbRootEnabled = false; 111 112 @Before setUp()113 public void setUp() throws Exception { 114 mDevice = getDevice(); 115 116 mWasAdbRoot = mDevice.isAdbRoot(); 117 mAdbRootEnabled = mWasAdbRoot || enableAdbRoot(); 118 119 assumeTrue("The device does not allow root access", mAdbRootEnabled); 120 121 mCtsCompilationAppApkFile = copyResourceToFile( 122 "/CtsCompilationApp.apk", File.createTempFile("CtsCompilationApp", ".apk")); 123 mDevice.uninstallPackage(APPLICATION_PACKAGE); // in case it's still installed 124 String error = mDevice.installPackage(mCtsCompilationAppApkFile, false); 125 assertNull("Got install error: " + error, error); 126 127 mDevice.executeShellV2Command("rm -rf " + TEMP_DIR); // Make sure we have a clean state. 128 assertCommandSucceeds("mkdir", "-p", TEMP_DIR); 129 } 130 131 @After tearDown()132 public void tearDown() throws Exception { 133 mDevice.executeShellV2Command("rm -rf " + TEMP_DIR); 134 135 FileUtil.deleteFile(mCtsCompilationAppApkFile); 136 FileUtil.deleteFile(mAppUsedByOtherAppApkFile); 137 FileUtil.deleteFile(mAppUsedByOtherAppDmFile); 138 FileUtil.deleteFile(mAppUsingOtherAppApkFile); 139 mDevice.uninstallPackage(APPLICATION_PACKAGE); 140 mDevice.uninstallPackage(APP_USED_BY_OTHER_APP_PACKAGE); 141 mDevice.uninstallPackage(APP_USING_OTHER_APP_PACKAGE); 142 143 if (!mWasAdbRoot && mAdbRootEnabled) { 144 mDevice.disableAdbRoot(); 145 } 146 } 147 148 /** 149 * Tests compilation using {@code -r bg-dexopt -f}. 150 */ 151 @Test testCompile_bgDexopt()152 public void testCompile_bgDexopt() throws Exception { 153 resetProfileState(APPLICATION_PACKAGE); 154 155 // Copy the profile to the reference location so that the bg-dexopt 156 // can actually do work if it's configured to speed-profile. 157 for (ProfileLocation profileLocation : EnumSet.of(ProfileLocation.REF)) { 158 writeSystemManagedProfile("/primary.prof.txt", profileLocation, APPLICATION_PACKAGE); 159 } 160 161 // Usually "speed-profile" 162 String expectedInstallFilter = 163 Objects.requireNonNull(mDevice.getProperty("pm.dexopt.install")); 164 if (expectedInstallFilter.equals("speed-profile")) { 165 // If the filter is speed-profile but no profile is present, the compiler 166 // will change it to verify. 167 expectedInstallFilter = "verify"; 168 } 169 // Usually "speed-profile" 170 String expectedBgDexoptFilter = 171 Objects.requireNonNull(mDevice.getProperty("pm.dexopt.bg-dexopt")); 172 173 String odexPath = getOdexFilePath(APPLICATION_PACKAGE); 174 assertEquals(expectedInstallFilter, getCompilerFilter(odexPath)); 175 176 // Without -f, the compiler would only run if it judged the bg-dexopt filter to 177 // be "better" than the install filter. However manufacturers can change those 178 // values so we don't want to depend here on the resulting filter being better. 179 executeCompile(APPLICATION_PACKAGE, "-r", "bg-dexopt", "-f"); 180 181 assertEquals(expectedBgDexoptFilter, getCompilerFilter(odexPath)); 182 } 183 184 /* 185 The tests below test the remaining combinations of the "ref" (reference) and 186 "cur" (current) profile being available. The "cur" profile gets moved/merged 187 into the "ref" profile when it differs enough; as of 2016-05-10, "differs 188 enough" is based on number of methods and classes in profile_assistant.cc. 189 190 No nonempty profile exists right after an app is installed. 191 Once the app runs, a profile will get collected in "cur" first but 192 may make it to "ref" later. While the profile is being processed by 193 profile_assistant, it may only be available in "ref". 194 */ 195 196 @Test testCompile_noProfile()197 public void testCompile_noProfile() throws Exception { 198 compileWithProfilesAndCheckFilter(false /* expectOdexChange */, 199 EnumSet.noneOf(ProfileLocation.class)); 200 } 201 202 @Test testCompile_curProfile()203 public void testCompile_curProfile() throws Exception { 204 compileWithProfilesAndCheckFilter(true /* expectOdexChange */, 205 EnumSet.of(ProfileLocation.CUR)); 206 assertTrue("ref profile should have been created by the compiler", 207 mDevice.doesFileExist(ProfileLocation.REF.getPath(APPLICATION_PACKAGE))); 208 } 209 210 @Test testCompile_refProfile()211 public void testCompile_refProfile() throws Exception { 212 compileWithProfilesAndCheckFilter(true /* expectOdexChange */, 213 EnumSet.of(ProfileLocation.REF)); 214 // expect a change in odex because the of the change form 215 // verify -> speed-profile 216 } 217 218 @Test testCompile_curAndRefProfile()219 public void testCompile_curAndRefProfile() throws Exception { 220 compileWithProfilesAndCheckFilter(true /* expectOdexChange */, 221 EnumSet.of(ProfileLocation.CUR, ProfileLocation.REF)); 222 // expect a change in odex because the of the change form 223 // verify -> speed-profile 224 } 225 226 /** 227 * Tests how compilation of an app used by other apps is handled. 228 */ 229 @Test testCompile_usedByOtherApps()230 public void testCompile_usedByOtherApps() throws Exception { 231 mAppUsedByOtherAppApkFile = copyResourceToFile( 232 "/AppUsedByOtherApp.apk", File.createTempFile("AppUsedByOtherApp", ".apk")); 233 mAppUsedByOtherAppDmFile = constructDmFile( 234 "/app_used_by_other_app_1.prof.txt", mAppUsedByOtherAppApkFile); 235 // We cannot use `mDevice.installPackage` here because it doesn't support DM file. 236 String result = mDevice.executeAdbCommand( 237 "install-multiple", 238 mAppUsedByOtherAppApkFile.getAbsolutePath(), 239 mAppUsedByOtherAppDmFile.getAbsolutePath()); 240 assertWithMessage("Failed to install AppUsedByOtherApp").that(result).isNotNull(); 241 242 mAppUsingOtherAppApkFile = copyResourceToFile( 243 "/AppUsingOtherApp.apk", File.createTempFile("AppUsingOtherApp", ".apk")); 244 result = mDevice.installPackage(mAppUsingOtherAppApkFile, false /* reinstall */); 245 assertWithMessage(result).that(result).isNull(); 246 247 String odexFilePath = getOdexFilePath(APP_USED_BY_OTHER_APP_PACKAGE); 248 // Initially, the app should be compiled with the cloud profile, and the odex file should be 249 // public. 250 assertThat(getCompilerFilter(odexFilePath)).isEqualTo("speed-profile"); 251 assertFileIsPublic(odexFilePath); 252 assertThat(getCompiledMethods(odexFilePath)) 253 .containsExactly("android.compilation.cts.appusedbyotherapp.MyActivity.method2()"); 254 255 // Simulate that the app profile has changed. 256 resetProfileState(APP_USED_BY_OTHER_APP_PACKAGE); 257 writeSystemManagedProfile("/app_used_by_other_app_2.prof.txt", ProfileLocation.REF, 258 APP_USED_BY_OTHER_APP_PACKAGE); 259 260 executeCompile(APP_USED_BY_OTHER_APP_PACKAGE, "-m", "speed-profile", "-f"); 261 // Right now, the app hasn't been used by any other app yet. It should be compiled with the 262 // new profile, and the odex file should be private. 263 assertThat(getCompilerFilter(odexFilePath)).isEqualTo("speed-profile"); 264 assertFileIsPrivate(odexFilePath); 265 assertThat(getCompiledMethods(odexFilePath)).containsExactly( 266 "android.compilation.cts.appusedbyotherapp.MyActivity.method1()", 267 "android.compilation.cts.appusedbyotherapp.MyActivity.method2()"); 268 269 executeCompile(APP_USED_BY_OTHER_APP_PACKAGE, "-m", "verify"); 270 // The app should not be re-compiled with a worse compiler filter even if the odex file can 271 // be public after then. 272 assertThat(getCompilerFilter(odexFilePath)).isEqualTo("speed-profile"); 273 274 DeviceTestRunOptions options = new DeviceTestRunOptions(APP_USING_OTHER_APP_PACKAGE); 275 options.setTestClassName(APP_USING_OTHER_APP_PACKAGE + ".UsingOtherAppTest"); 276 options.setTestMethodName("useOtherApp"); 277 runDeviceTests(options); 278 279 executeCompile(APP_USED_BY_OTHER_APP_PACKAGE, "-m", "speed-profile"); 280 // Now, the app has been used by any other app. It should be compiled with the cloud 281 // profile, and the odex file should be public. 282 assertThat(getCompilerFilter(odexFilePath)).isEqualTo("speed-profile"); 283 assertFileIsPublic(odexFilePath); 284 assertThat(getCompiledMethods(odexFilePath)) 285 .containsExactly("android.compilation.cts.appusedbyotherapp.MyActivity.method2()"); 286 } 287 288 /** 289 * Places the profile in the specified locations, recompiles (without -f) 290 * and checks the compiler-filter in the odex file. 291 */ compileWithProfilesAndCheckFilter(boolean expectOdexChange, Set<ProfileLocation> profileLocations)292 private void compileWithProfilesAndCheckFilter(boolean expectOdexChange, 293 Set<ProfileLocation> profileLocations) throws Exception { 294 if (!profileLocations.isEmpty()) { 295 checkProfileSupport(); 296 } 297 298 resetProfileState(APPLICATION_PACKAGE); 299 300 executeCompile(APPLICATION_PACKAGE, "-m", "speed-profile", "-f"); 301 String odexFilePath = getOdexFilePath(APPLICATION_PACKAGE); 302 String initialOdexFileContents = mDevice.pullFileContents(odexFilePath); 303 // validity check 304 assertWithMessage("empty odex file").that(initialOdexFileContents.length()) 305 .isGreaterThan(0); 306 307 for (ProfileLocation profileLocation : profileLocations) { 308 writeSystemManagedProfile("/primary.prof.txt", profileLocation, APPLICATION_PACKAGE); 309 } 310 executeCompile(APPLICATION_PACKAGE, "-m", "speed-profile"); 311 312 // Confirm the compiler-filter used in creating the odex file 313 String compilerFilter = getCompilerFilter(odexFilePath); 314 315 // Without profiles, the compiler filter should be verify. 316 String expectedCompilerFilter = profileLocations.isEmpty() ? "verify" : "speed-profile"; 317 assertEquals("compiler-filter", expectedCompilerFilter, compilerFilter); 318 319 String odexFileContents = mDevice.pullFileContents(odexFilePath); 320 boolean odexChanged = !initialOdexFileContents.equals(odexFileContents); 321 if (odexChanged && !expectOdexChange) { 322 String msg = String.format(Locale.US, "Odex file without filters (%d bytes) " 323 + "unexpectedly different from odex file (%d bytes) compiled with filters: %s", 324 initialOdexFileContents.length(), odexFileContents.length(), profileLocations); 325 fail(msg); 326 } else if (!odexChanged && expectOdexChange) { 327 fail("odex file should have changed when recompiling with " + profileLocations); 328 } 329 } 330 resetProfileState(String packageName)331 private void resetProfileState(String packageName) throws Exception { 332 mDevice.executeShellV2Command("rm -f " + ProfileLocation.REF.getPath(packageName)); 333 mDevice.executeShellV2Command("truncate -s 0 " + ProfileLocation.CUR.getPath(packageName)); 334 } 335 336 /** 337 * Invokes the dex2oat compiler on the client. 338 * 339 * @param compileOptions extra options to pass to the compiler on the command line 340 */ executeCompile(String packageName, String... compileOptions)341 private void executeCompile(String packageName, String... compileOptions) throws Exception { 342 List<String> command = new ArrayList<>(Arrays.asList("cmd", "package", "compile")); 343 command.addAll(Arrays.asList(compileOptions)); 344 command.add(packageName); 345 String[] commandArray = command.toArray(new String[0]); 346 assertCommandSucceeds(commandArray); 347 } 348 349 /** 350 * Writes the given profile in binary format in a system-managed directory on the device, and 351 * sets appropriate owner. 352 */ writeSystemManagedProfile(String profileResourceName, ProfileLocation location, String packageName)353 private void writeSystemManagedProfile(String profileResourceName, ProfileLocation location, 354 String packageName) throws Exception { 355 String targetPath = location.getPath(packageName); 356 // Get the owner of the parent directory so we can set it on the file 357 String targetDir = location.getDirectory(packageName); 358 assertTrue("Directory " + targetDir + " not found", mDevice.doesFileExist(targetDir)); 359 // In format group:user so we can directly pass it to chown. 360 String owner = assertCommandOutputsLines(1, "stat", "-c", "%U:%g", targetDir)[0]; 361 362 String dexLocation = assertCommandOutputsLines(1, "pm", "path", packageName)[0]; 363 dexLocation = dexLocation.replace("package:", ""); 364 assertTrue("Failed to find APK " + dexLocation, mDevice.doesFileExist(dexLocation)); 365 366 writeProfile(profileResourceName, dexLocation, targetPath); 367 368 // Verify that the file was written successfully. 369 assertTrue("Failed to create profile file", mDevice.doesFileExist(targetPath)); 370 String result = assertCommandOutputsLines(1, "stat", "-c", "%s", targetPath)[0]; 371 assertWithMessage("profile " + targetPath + " is " + Integer.parseInt(result) + " bytes") 372 .that(Integer.parseInt(result)).isGreaterThan(0); 373 374 assertCommandSucceeds("chown", owner, targetPath); 375 } 376 constructDmFile(String profileResourceName, File apkFile)377 private File constructDmFile(String profileResourceName, File apkFile) throws Exception { 378 File binaryProfileFile = File.createTempFile("primary", ".prof"); 379 String binaryProfileFileOnDevice = TEMP_DIR + "/primary.prof"; 380 // When constructing a DM file, we don't have the real dex location because the app is not 381 // yet installed. We can use an arbitrary location. This is okay because installd will 382 // rewrite the dex location in the profile when the app is being installed. 383 String dexLocation = TEMP_DIR + "/app.apk"; 384 385 try { 386 assertTrue(mDevice.pushFile(apkFile, dexLocation)); 387 writeProfile(profileResourceName, dexLocation, binaryProfileFileOnDevice); 388 assertTrue(mDevice.pullFile(binaryProfileFileOnDevice, binaryProfileFile)); 389 390 // Construct the DM file from the binary profile file. The stem of the APK file and the 391 // DM file must match. 392 File dmFile = new File(apkFile.getAbsolutePath().replaceAll("\\.apk$", ".dm")); 393 try (ZipOutputStream outputStream = 394 new ZipOutputStream(new FileOutputStream(dmFile)); 395 InputStream inputStream = new FileInputStream(binaryProfileFile)) { 396 outputStream.putNextEntry(new ZipEntry("primary.prof")); 397 ByteStreams.copy(inputStream, outputStream); 398 outputStream.closeEntry(); 399 } 400 return dmFile; 401 } finally { 402 mDevice.executeShellV2Command("rm " + binaryProfileFileOnDevice); 403 mDevice.executeShellV2Command("rm " + dexLocation); 404 FileUtil.deleteFile(binaryProfileFile); 405 } 406 } 407 408 /** 409 * Writes the given profile in binary format on the device. 410 */ writeProfile(String profileResourceName, String dexLocation, String pathOnDevice)411 private void writeProfile(String profileResourceName, String dexLocation, String pathOnDevice) 412 throws Exception { 413 File textProfileFile = File.createTempFile("primary", ".prof.txt"); 414 String textProfileFileOnDevice = TEMP_DIR + "/primary.prof.txt"; 415 416 try { 417 copyResourceToFile(profileResourceName, textProfileFile); 418 assertTrue(mDevice.pushFile(textProfileFile, textProfileFileOnDevice)); 419 420 assertCommandSucceeds( 421 "profman", 422 "--create-profile-from=" + textProfileFileOnDevice, 423 "--apk=" + dexLocation, 424 "--dex-location=" + dexLocation, 425 "--reference-profile-file=" + pathOnDevice); 426 } finally { 427 mDevice.executeShellV2Command("rm " + textProfileFileOnDevice); 428 FileUtil.deleteFile(textProfileFile); 429 } 430 } 431 432 /** 433 * Parses the value for the key "compiler-filter" out of the output from 434 * {@code oatdump --header-only}. 435 */ getCompilerFilter(String odexFilePath)436 private String getCompilerFilter(String odexFilePath) throws DeviceNotAvailableException { 437 String[] response = assertCommandSucceeds( 438 "oatdump", "--header-only", "--oat-file=" + odexFilePath).split("\n"); 439 String prefix = "compiler-filter ="; 440 for (String line : response) { 441 line = line.trim(); 442 if (line.startsWith(prefix)) { 443 return line.substring(prefix.length()).trim(); 444 } 445 } 446 fail("No occurence of \"" + prefix + "\" in: " + Arrays.toString(response)); 447 return null; 448 } 449 450 /** 451 * Returns a list of methods that have native code in the odex file. 452 */ getCompiledMethods(String odexFilePath)453 private List<String> getCompiledMethods(String odexFilePath) 454 throws DeviceNotAvailableException { 455 // Matches " CODE: (code_offset=0x000010e0 size=198)...". 456 Pattern codePattern = Pattern.compile("^\\s*CODE:.*size=(\\d+)"); 457 458 // Matches 459 // " 0: void android.compilation.cts.appusedbyotherapp.R.<init>() (dex_method_idx=7)". 460 Pattern methodPattern = 461 Pattern.compile("((?:\\w+\\.)+[<>\\w]+\\(.*?\\)).*dex_method_idx=\\d+"); 462 463 String[] response = assertCommandSucceeds("oatdump", "--oat-file=" + odexFilePath) 464 .split("\n"); 465 ArrayList<String> compiledMethods = new ArrayList<>(); 466 String currentMethod = null; 467 int currentMethodIndent = -1; 468 for (int i = 0; i < response.length; i++) { 469 // While in a method block. 470 while (currentMethodIndent != -1 && i < response.length 471 && getIndent(response[i]) > currentMethodIndent) { 472 Matcher matcher = codePattern.matcher(response[i]); 473 // The method has code whose size > 0. 474 if (matcher.find() && Long.parseLong(matcher.group(1)) > 0) { 475 compiledMethods.add(currentMethod); 476 } 477 i++; 478 } 479 480 if (i >= response.length) { 481 break; 482 } 483 484 currentMethod = null; 485 currentMethodIndent = -1; 486 487 Matcher matcher = methodPattern.matcher(response[i]); 488 if (matcher.find()) { 489 currentMethod = matcher.group(1); 490 currentMethodIndent = getIndent(response[i]); 491 } 492 } 493 return compiledMethods; 494 } 495 496 /** 497 * Returns the number of leading spaces. 498 */ getIndent(String str)499 private int getIndent(String str) { 500 int indent = 0; 501 while (indent < str.length() && str.charAt(indent) == ' ') { 502 indent++; 503 } 504 return indent; 505 } 506 507 /** 508 * Returns the path to the application's base.odex file that should have 509 * been created by the compiler. 510 */ getOdexFilePath(String packageName)511 private String getOdexFilePath(String packageName) throws DeviceNotAvailableException { 512 // Something like "package:/data/app/android.compilation.cts-1/base.apk" 513 String pathSpec = assertCommandOutputsLines(1, "pm", "path", packageName)[0]; 514 Matcher matcher = Pattern.compile("^package:(.+/)base\\.apk$").matcher(pathSpec); 515 boolean found = matcher.find(); 516 assertTrue("Malformed spec: " + pathSpec, found); 517 String apkDir = matcher.group(1); 518 // E.g. /data/app/android.compilation.cts-1/oat/arm64/base.odex 519 String result = assertCommandOutputsLines(1, "find", apkDir, "-name", "base.odex")[0]; 520 assertTrue("odex file not found: " + result, mDevice.doesFileExist(result)); 521 return result; 522 } 523 524 /** 525 * Skips the test if it does not use JIT profiles. 526 */ checkProfileSupport()527 private void checkProfileSupport() throws Exception { 528 assumeTrue("The device does not use JIT profiles", isUseJitProfiles()); 529 } 530 isUseJitProfiles()531 private boolean isUseJitProfiles() throws Exception { 532 return Boolean.parseBoolean(assertCommandSucceeds("getprop", "dalvik.vm.usejitprofiles")); 533 } 534 assertCommandOutputsLines(int numLinesOutputExpected, String... command)535 private String[] assertCommandOutputsLines(int numLinesOutputExpected, String... command) 536 throws DeviceNotAvailableException { 537 String output = assertCommandSucceeds(command); 538 // "".split() returns { "" }, but we want an empty array 539 String[] lines = output.equals("") ? new String[0] : output.split("\n"); 540 assertEquals( 541 String.format(Locale.US, "Expected %d lines output, got %d running %s: %s", 542 numLinesOutputExpected, lines.length, Arrays.toString(command), 543 Arrays.toString(lines)), 544 numLinesOutputExpected, lines.length); 545 return lines; 546 } 547 assertCommandSucceeds(String... command)548 private String assertCommandSucceeds(String... command) throws DeviceNotAvailableException { 549 CommandResult result = mDevice.executeShellV2Command(String.join(" ", command)); 550 assertWithMessage(result.toString()).that(result.getExitCode()).isEqualTo(0); 551 // Remove trailing \n's. 552 return result.getStdout().trim(); 553 } 554 copyResourceToFile(String resourceName, File file)555 private File copyResourceToFile(String resourceName, File file) throws Exception { 556 try (OutputStream outputStream = new FileOutputStream(file); 557 InputStream inputStream = getClass().getResourceAsStream(resourceName)) { 558 assertThat(ByteStreams.copy(inputStream, outputStream)).isGreaterThan(0); 559 } 560 return file; 561 } 562 563 /** 564 * Turns on adb root. Returns true if successful. 565 * 566 * This is a workaround to run the test as root in CTS on userdebug/eng builds. We have to keep 567 * this test in CTS because it's the only integration test we have to verify platform's dexopt 568 * behavior. We cannot use `mDevice.enableAdbRoot()` because it does not allow enabling root in 569 * CTS, even on userdebug/eng builds. 570 * 571 * The implementation below is copied from {@link NativeDevice#enableAdbRoot()}. 572 */ enableAdbRoot()573 private boolean enableAdbRoot() throws DeviceNotAvailableException { 574 // adb root is a relatively intensive command, so do a brief check first to see 575 // if its necessary or not 576 if (mDevice.isAdbRoot()) { 577 CLog.i("adb is already running as root for AdbRootDependentCompilationTest on %s", 578 mDevice.getSerialNumber()); 579 // Still check for online, in some case we could see the root, but device could be 580 // very early in its cycle. 581 mDevice.waitForDeviceOnline(); 582 return true; 583 } 584 CLog.i("adb root for AdbRootDependentCompilationTest on device %s", 585 mDevice.getSerialNumber()); 586 int attempts = ADB_ROOT_RETRY_ATTEMPTS; 587 for (int i = 1; i <= attempts; i++) { 588 String output = mDevice.executeAdbCommand("root"); 589 // wait for device to disappear from adb 590 boolean res = mDevice.waitForDeviceNotAvailable(2 * 1000); 591 if (!res && TestDeviceState.ONLINE.equals(mDevice.getDeviceState())) { 592 if (mDevice.isAdbRoot()) { 593 return true; 594 } 595 } 596 597 if (mDevice instanceof NativeDevice) { 598 ((NativeDevice) mDevice).postAdbRootAction(); 599 } 600 601 // wait for device to be back online 602 mDevice.waitForDeviceOnline(); 603 604 if (mDevice.isAdbRoot()) { 605 return true; 606 } 607 CLog.w("'adb root' for AdbRootDependentCompilationTest on %s unsuccessful on attempt " 608 + "%d of %d. Output: '%s'", 609 mDevice.getSerialNumber(), i, attempts, output); 610 } 611 return false; 612 } 613 assertFileIsPublic(String path)614 private void assertFileIsPublic(String path) throws Exception { 615 String permissions = getPermissions(path); 616 assertWithMessage("Expected " + path + " to be public, got " + permissions) 617 .that(permissions.charAt(READ_OTHER)).isEqualTo('r'); 618 } 619 assertFileIsPrivate(String path)620 private void assertFileIsPrivate(String path) throws Exception { 621 String permissions = getPermissions(path); 622 assertWithMessage("Expected " + path + " to be private, got " + permissions) 623 .that(permissions.charAt(READ_OTHER)).isEqualTo('-'); 624 } 625 getPermissions(String path)626 private String getPermissions(String path) throws Exception { 627 String permissions = mDevice.getFileEntry(path).getPermissions(); 628 assertWithMessage("Invalid permissions string " + permissions).that(permissions.length()) 629 .isEqualTo(PERMISSIONS_LENGTH); 630 return permissions; 631 } 632 } 633