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