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