xref: /aosp_15_r20/cts/hostsidetests/compilation/src/android/compilation/cts/CompilationTest.java (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
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