• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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