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.statuscheckerapp;
18 
19 import static dalvik.system.DexFile.OptimizationInfo;
20 
21 import static com.google.common.truth.Truth.assertThat;
22 
23 import android.content.Context;
24 import android.content.pm.ApplicationInfo;
25 import android.os.Bundle;
26 
27 import androidx.test.core.app.ApplicationProvider;
28 import androidx.test.filters.SmallTest;
29 import androidx.test.platform.app.InstrumentationRegistry;
30 import androidx.test.runner.AndroidJUnit4;
31 
32 import dalvik.system.ApplicationRuntime;
33 import dalvik.system.BaseDexClassLoader;
34 import dalvik.system.DexFile;
35 import dalvik.system.PathClassLoader;
36 import dalvik.system.VMRuntime;
37 
38 import com.google.common.io.ByteStreams;
39 import com.google.common.truth.Correspondence;
40 
41 import org.junit.Test;
42 import org.junit.runner.RunWith;
43 
44 import java.io.File;
45 import java.io.FileOutputStream;
46 import java.io.InputStream;
47 import java.io.OutputStream;
48 import java.nio.file.Paths;
49 import java.util.Map;
50 import java.util.function.BiFunction;
51 
52 /**
53  * An instrumentation test that checks optimization status.
54  */
55 @SmallTest
56 @RunWith(AndroidJUnit4.class)
57 public class StatusCheckerAppTest {
58     private static final String TAG = "StatusCheckerAppTest";
59     private static final String SECONDARY_DEX_RES = "/StatusCheckerApp_Secondary.jar";
60 
61     @Test
checkStatus()62     public void checkStatus() throws Exception {
63         Bundle bundle = InstrumentationRegistry.getArguments();
64         OptimizationInfo info = ApplicationRuntime.getBaseApkOptimizationInfo();
65         assertThat(info.getStatus()).isEqualTo(bundle.getString("compiler-filter"));
66         assertThat(info.getReason()).isEqualTo(bundle.getString("compilation-reason"));
67         assertThat(info.isVerified()).isEqualTo(bundle.getString("is-verified").equals("true"));
68         assertThat(info.isOptimized()).isEqualTo(bundle.getString("is-optimized").equals("true"));
69         assertThat(info.isFullyCompiled())
70                 .isEqualTo(bundle.getString("is-fully-compiled").equals("true"));
71     }
72 
73     @Test
createAndLoadSecondaryDex()74     public void createAndLoadSecondaryDex() throws Exception {
75         Bundle bundle = InstrumentationRegistry.getArguments();
76         String secondaryDexFilename = bundle.getString("secondary-dex-filename");
77         createAndLoadSecondaryDex(secondaryDexFilename, PathClassLoader::new);
78     }
79 
80     @Test
createAndLoadSecondaryDexUnsupportedClassLoader()81     public void createAndLoadSecondaryDexUnsupportedClassLoader() throws Exception {
82         Bundle bundle = InstrumentationRegistry.getArguments();
83         String secondaryDexFilename = bundle.getString("secondary-dex-filename");
84         createAndLoadSecondaryDex(secondaryDexFilename, CustomClassLoader::new);
85     }
86 
createAndLoadSecondaryDex(String secondaryDexFilename, BiFunction<String, ClassLoader, ClassLoader> classLoaderCtor)87     private String createAndLoadSecondaryDex(String secondaryDexFilename,
88             BiFunction<String, ClassLoader, ClassLoader> classLoaderCtor) throws Exception {
89         File secondaryDexFile =
90                 Paths.get(getApplicationInfo().dataDir, secondaryDexFilename).toFile();
91         if (secondaryDexFile.exists()) {
92             secondaryDexFile.delete();
93         }
94         copyResourceToFile(SECONDARY_DEX_RES, secondaryDexFile);
95         assertThat(secondaryDexFile.setReadOnly()).isTrue();
96         classLoaderCtor.apply(secondaryDexFile.getAbsolutePath(), this.getClass().getClassLoader());
97         return secondaryDexFile.getAbsolutePath();
98     }
99 
getApplicationInfo()100     private ApplicationInfo getApplicationInfo() {
101         Context context = ApplicationProvider.getApplicationContext();
102         return context.getApplicationInfo();
103     }
104 
105     @Test
testSecondaryDexReporting()106     public void testSecondaryDexReporting() throws Exception {
107         String dataDir = getApplicationInfo().dataDir;
108         var reporter =
109                 (BaseDexClassLoader.Reporter) BaseDexClassLoader.class.getMethod("getReporter")
110                         .invoke(null);
111 
112         // Invalid dex paths. The binder calls should be rejected, though we won't see any failure
113         // on the client side because the calls are oneway.
114         reporter.report(Map.of("relative/reported_bad_1.apk", "PCL[]"));
115         reporter.report(
116                 Map.of(Paths.get(dataDir, "non-normal/./reported_bad_2.apk").toString(), "PCL[]"));
117 
118         // Invalid class loader contexts. The binder calls should be rejected too.
119         reporter.report(Map.of(Paths.get(dataDir, "reported_bad_3.apk").toString(), "ABC"));
120         reporter.report(
121                 Map.of(Paths.get(dataDir, "reported_bad_4.apk").toString(), "PCL[./bar.jar]"));
122 
123         // Valid paths and class loader contexts.
124         reporter.report(Map.of(Paths.get(dataDir, "reported_good_1.apk").toString(), "PCL[]"));
125         reporter.report(
126                 Map.of(Paths.get(dataDir, "reported_good_2.apk").toString(), "PCL[bar.jar]"));
127         reporter.report(Map.of(Paths.get(dataDir, "reported_good_3.apk").toString(),
128                 "=UnsupportedClassLoaderContext="));
129     }
130 
131     @Test
testGetDexFileOutputPaths()132     public void testGetDexFileOutputPaths() throws Exception {
133         String[] paths = DexFile.getDexFileOutputPaths(
134                 getApplicationInfo().sourceDir, VMRuntime.getRuntime().vmInstructionSet());
135 
136         // We can't be too specific because the paths are ART-internal and are subject to change.
137         assertThat(paths)
138                 .asList()
139                 .comparingElementsUsing(Correspondence.from(String::endsWith, "ends with"))
140                 .containsAtLeast(".odex", ".vdex");
141     }
142 
copyResourceToFile(String resourceName, File file)143     public File copyResourceToFile(String resourceName, File file) throws Exception {
144         try (OutputStream outputStream = new FileOutputStream(file);
145                 InputStream inputStream = getClass().getResourceAsStream(resourceName)) {
146             assertThat(ByteStreams.copy(inputStream, outputStream)).isGreaterThan(0);
147         }
148         return file;
149     }
150 
151     // A custom class loader that is unsupported by CLC encoding.
152     public class CustomClassLoader extends PathClassLoader {
CustomClassLoader(String dexPath, ClassLoader parent)153         public CustomClassLoader(String dexPath, ClassLoader parent) {
154             super(dexPath, parent);
155         }
156     }
157 }
158