1 /* 2 * Copyright (C) 2011 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 dalvik.system; 18 19 import java.io.FilenameFilter; 20 import java.lang.reflect.InvocationTargetException; 21 import java.lang.reflect.Method; 22 import java.io.File; 23 import java.io.FileOutputStream; 24 import java.io.IOException; 25 import java.io.InputStream; 26 import libcore.io.Streams; 27 import junit.framework.TestCase; 28 29 /** 30 * Tests for the class {@link DexClassLoader}. 31 */ 32 public class DexClassLoaderTest extends TestCase { 33 private static final String PACKAGE_PATH = "dalvik/system/"; 34 35 private File srcDir; 36 private File dex1; 37 private File dex2; 38 private File jar1; 39 private File jar2; 40 private File optimizedDir; 41 42 protected void setUp() throws Exception { 43 srcDir = File.createTempFile("src", ""); 44 assertTrue(srcDir.delete()); 45 assertTrue(srcDir.mkdirs()); 46 47 dex1 = new File(srcDir, "loading-test.dex"); 48 dex2 = new File(srcDir, "loading-test2.dex"); 49 jar1 = new File(srcDir, "loading-test.jar"); 50 jar2 = new File(srcDir, "loading-test2.jar"); 51 52 copyResource("loading-test.dex", dex1); 53 copyResource("loading-test2.dex", dex2); 54 copyResource("loading-test.jar", jar1); 55 copyResource("loading-test2.jar", jar2); 56 57 optimizedDir = File.createTempFile("optimized", ""); 58 assertTrue(optimizedDir.delete()); 59 assertTrue(optimizedDir.mkdirs()); 60 } 61 62 protected void tearDown() { 63 cleanUpDir(srcDir); 64 cleanUpDir(optimizedDir); 65 } 66 67 private static void cleanUpDir(File dir) { 68 if (!dir.isDirectory()) { 69 return; 70 } 71 File[] files = dir.listFiles(); 72 for (File file : files) { 73 if (file.isDirectory()) { 74 cleanUpDir(file); 75 } else { 76 assertTrue(file.delete()); 77 } 78 } 79 } 80 81 /** 82 * Copy a resource in the package directory to the indicated 83 * target file. 84 */ 85 private static void copyResource(String resourceName, 86 File destination) throws IOException { 87 ClassLoader loader = DexClassLoaderTest.class.getClassLoader(); 88 assertFalse(destination.exists()); 89 InputStream in = loader.getResourceAsStream(PACKAGE_PATH + resourceName); 90 if (in == null) { 91 throw new IllegalStateException("Resource not found: " + PACKAGE_PATH + resourceName); 92 } 93 94 try (FileOutputStream out = new FileOutputStream(destination)) { 95 Streams.copy(in, out); 96 } finally { 97 in.close(); 98 } 99 } 100 101 static final FilenameFilter DEX_FILE_NAME_FILTER = new FilenameFilter() { 102 @Override 103 public boolean accept(File file, String s) { 104 return s.endsWith(".dex"); 105 } 106 }; 107 108 /** 109 * Helper to construct a DexClassLoader instance to test. 110 * 111 * @param files The .dex or .jar files to use for the class path. 112 */ 113 private ClassLoader createLoader(File... files) { 114 assertNotNull(files); 115 assertTrue(files.length > 0); 116 String path = files[0].getAbsolutePath(); 117 for (int i = 1; i < files.length; i++) { 118 path += File.pathSeparator + files[i].getAbsolutePath(); 119 } 120 return new DexClassLoader(path, optimizedDir.getAbsolutePath(), null, 121 ClassLoader.getSystemClassLoader()); 122 } 123 124 /** 125 * Helper to construct a new DexClassLoader instance to test, using the 126 * given files as the class path, and call a named no-argument static 127 * method on a named class. 128 * 129 * @param className The name of the class of the method to call. 130 * @param methodName The name of the method to call. 131 * @param files The .dex or .jar files to use for the class path. 132 */ 133 public Object createLoaderAndCallMethod( 134 String className, String methodName, File... files) 135 throws ReflectiveOperationException { 136 ClassLoader cl = createLoader(files); 137 Class c = cl.loadClass(className); 138 Method m = c.getMethod(methodName, (Class[]) null); 139 return m.invoke(null, (Object[]) null); 140 } 141 142 /** 143 * Helper to construct a new DexClassLoader instance to test, using the 144 * given files as the class path, and read the contents of the named 145 * resource as a String. 146 * 147 * @param resourceName The name of the resource to get. 148 * @param files The .dex or .jar files to use for the class path. 149 */ 150 private String createLoaderAndGetResource(String resourceName, File... files) throws Exception { 151 ClassLoader cl = createLoader(files); 152 InputStream in = cl.getResourceAsStream(resourceName); 153 if (in == null) { 154 throw new IllegalStateException("Resource not found: " + resourceName); 155 } 156 157 byte[] contents = Streams.readFully(in); 158 return new String(contents, "UTF-8"); 159 } 160 161 // ONE_JAR 162 163 /** 164 * Just a trivial test of construction. This one merely makes 165 * sure that a valid construction doesn't fail. It doesn't try 166 * to verify anything about the constructed instance, other than 167 * checking for the existence of optimized dex files. 168 */ 169 public void test_oneJar_init() throws Exception { 170 ClassLoader cl = createLoader(jar1); 171 File[] files = optimizedDir.listFiles(DEX_FILE_NAME_FILTER); 172 assertNotNull(files); 173 assertEquals(1, files.length); 174 } 175 176 /** 177 * Check that a class in the jar/dex file may be used successfully. In this 178 * case, a trivial static method is called. 179 */ 180 public void test_oneJar_simpleUse() throws Exception { 181 String result = (String) createLoaderAndCallMethod("test.Test1", "test", jar1); 182 assertSame("blort", result); 183 } 184 185 /* 186 * All the following tests are just pass-throughs to test code 187 * that lives inside the loading-test dex/jar file. 188 */ 189 190 public void test_oneJar_constructor() throws Exception { 191 createLoaderAndCallMethod("test.TestMethods", "test_constructor", jar1); 192 } 193 194 public void test_oneJar_callStaticMethod() throws Exception { 195 createLoaderAndCallMethod("test.TestMethods", "test_callStaticMethod", jar1); 196 } 197 198 public void test_oneJar_getStaticVariable() throws Exception { 199 createLoaderAndCallMethod("test.TestMethods", "test_getStaticVariable", jar1); 200 } 201 202 public void test_oneJar_callInstanceMethod() throws Exception { 203 createLoaderAndCallMethod("test.TestMethods", "test_callInstanceMethod", jar1); 204 } 205 206 public void test_oneJar_getInstanceVariable() throws Exception { 207 createLoaderAndCallMethod("test.TestMethods", "test_getInstanceVariable", jar1); 208 } 209 210 // ONE_DEX 211 212 public void test_oneDex_init() throws Exception { 213 ClassLoader cl = createLoader(dex1); 214 File[] files = optimizedDir.listFiles(DEX_FILE_NAME_FILTER); 215 assertNotNull(files); 216 assertEquals(1, files.length); 217 } 218 219 public void test_oneDex_simpleUse() throws Exception { 220 String result = (String) createLoaderAndCallMethod("test.Test1", "test", dex1); 221 assertSame("blort", result); 222 } 223 224 public void test_oneDex_constructor() throws Exception { 225 createLoaderAndCallMethod("test.TestMethods", "test_constructor", dex1); 226 } 227 228 public void test_oneDex_callStaticMethod() throws Exception { 229 createLoaderAndCallMethod("test.TestMethods", "test_callStaticMethod", dex1); 230 } 231 232 public void test_oneDex_getStaticVariable() throws Exception { 233 createLoaderAndCallMethod("test.TestMethods", "test_getStaticVariable", dex1); 234 } 235 236 public void test_oneDex_callInstanceMethod() throws Exception { 237 createLoaderAndCallMethod("test.TestMethods", "test_callInstanceMethod", dex1); 238 } 239 240 public void test_oneDex_getInstanceVariable() throws Exception { 241 createLoaderAndCallMethod("test.TestMethods", "test_getInstanceVariable", dex1); 242 } 243 244 // TWO_JAR 245 246 public void test_twoJar_init() throws Exception { 247 ClassLoader cl = createLoader(jar1, jar2); 248 File[] files = optimizedDir.listFiles(DEX_FILE_NAME_FILTER); 249 assertNotNull(files); 250 assertEquals(2, files.length); 251 } 252 253 public void test_twoJar_simpleUse() throws Exception { 254 String result = (String) createLoaderAndCallMethod("test.Test1", "test", jar1, jar2); 255 assertSame("blort", result); 256 } 257 258 public void test_twoJar_constructor() throws Exception { 259 createLoaderAndCallMethod("test.TestMethods", "test_constructor", jar1, jar2); 260 } 261 262 public void test_twoJar_callStaticMethod() throws Exception { 263 createLoaderAndCallMethod("test.TestMethods", "test_callStaticMethod", jar1, jar2); 264 } 265 266 public void test_twoJar_getStaticVariable() throws Exception { 267 createLoaderAndCallMethod("test.TestMethods", "test_getStaticVariable", jar1, jar2); 268 } 269 270 public void test_twoJar_callInstanceMethod() throws Exception { 271 createLoaderAndCallMethod("test.TestMethods", "test_callInstanceMethod", jar1, jar2); 272 } 273 274 public void test_twoJar_getInstanceVariable() throws Exception { 275 createLoaderAndCallMethod("test.TestMethods", "test_getInstanceVariable", jar1, jar2); 276 } 277 278 public void test_twoJar_diff_constructor() throws Exception { 279 createLoaderAndCallMethod("test.TestMethods", "test_diff_constructor", jar1, jar2); 280 } 281 282 public void test_twoJar_diff_callStaticMethod() throws Exception { 283 createLoaderAndCallMethod("test.TestMethods", "test_diff_callStaticMethod", jar1, jar2); 284 } 285 286 public void test_twoJar_diff_getStaticVariable() throws Exception { 287 createLoaderAndCallMethod("test.TestMethods", "test_diff_getStaticVariable", jar1, jar2); 288 } 289 290 public void test_twoJar_diff_callInstanceMethod() throws Exception { 291 createLoaderAndCallMethod("test.TestMethods", "test_diff_callInstanceMethod", jar1, jar2); 292 } 293 294 public void test_twoJar_diff_getInstanceVariable() throws Exception { 295 createLoaderAndCallMethod("test.TestMethods", "test_diff_getInstanceVariable", jar1, jar2); 296 } 297 298 // TWO_DEX 299 300 public void test_twoDex_init() throws Exception { 301 ClassLoader cl = createLoader(dex1, dex2); 302 File[] files = optimizedDir.listFiles(DEX_FILE_NAME_FILTER); 303 assertNotNull(files); 304 assertEquals(2, files.length); 305 } 306 307 public void test_twoDex_simpleUse() throws Exception { 308 String result = (String) createLoaderAndCallMethod("test.Test1", "test", dex1, dex2); 309 assertSame("blort", result); 310 } 311 312 public void test_twoDex_constructor() throws Exception { 313 createLoaderAndCallMethod("test.TestMethods", "test_constructor", dex1, dex2); 314 } 315 316 public void test_twoDex_callStaticMethod() throws Exception { 317 createLoaderAndCallMethod("test.TestMethods", "test_callStaticMethod", dex1, dex2); 318 } 319 320 public void test_twoDex_getStaticVariable() throws Exception { 321 createLoaderAndCallMethod("test.TestMethods", "test_getStaticVariable", dex1, dex2); 322 } 323 324 public void test_twoDex_callInstanceMethod() throws Exception { 325 createLoaderAndCallMethod("test.TestMethods", "test_callInstanceMethod", dex1, dex2); 326 } 327 328 public void test_twoDex_getInstanceVariable() throws Exception { 329 createLoaderAndCallMethod("test.TestMethods", "test_getInstanceVariable", dex1, dex2); 330 } 331 332 public void test_twoDex_diff_constructor() throws Exception { 333 createLoaderAndCallMethod("test.TestMethods", "test_diff_constructor", dex1, dex2); 334 } 335 336 public void test_twoDex_diff_callStaticMethod() throws Exception { 337 createLoaderAndCallMethod("test.TestMethods", "test_diff_callStaticMethod", dex1, dex2); 338 } 339 340 public void test_twoDex_diff_getStaticVariable() throws Exception { 341 createLoaderAndCallMethod("test.TestMethods", "test_diff_getStaticVariable", dex1, dex2); 342 } 343 344 public void test_twoDex_diff_callInstanceMethod() throws Exception { 345 createLoaderAndCallMethod("test.TestMethods", "test_diff_callInstanceMethod", dex1, dex2); 346 } 347 348 public void test_twoDex_diff_getInstanceVariable() throws Exception { 349 createLoaderAndCallMethod("test.TestMethods", "test_diff_getInstanceVariable", dex1, dex2); 350 } 351 352 /* 353 * Tests specifically for resource-related functionality. Since 354 * raw dex files don't contain resources, these test only work 355 * with jar files. 356 */ 357 358 /** 359 * Check that a resource in the jar file is retrievable and contains 360 * the expected contents. 361 */ 362 public void test_oneJar_directGetResourceAsStream() throws Exception { 363 String result = createLoaderAndGetResource("test/Resource1.txt", jar1); 364 assertEquals("Muffins are tasty!\n", result); 365 } 366 367 /** 368 * Check that a resource in the jar file can be retrieved from 369 * a class within that jar file. 370 */ 371 public void test_oneJar_getResourceAsStream() throws Exception { 372 createLoaderAndCallMethod("test.TestMethods", "test_getResourceAsStream", jar1); 373 } 374 375 public void test_twoJar_directGetResourceAsStream() throws Exception { 376 String result = createLoaderAndGetResource("test/Resource1.txt", jar1, jar2); 377 assertEquals("Muffins are tasty!\n", result); 378 } 379 380 public void test_twoJar_getResourceAsStream() throws Exception { 381 createLoaderAndCallMethod("test.TestMethods", "test_getResourceAsStream", jar1, jar2); 382 } 383 384 /** 385 * Check that a resource in the second jar file is retrievable and 386 * contains the expected contents. 387 */ 388 public void test_twoJar_diff_directGetResourceAsStream() throws Exception { 389 String result = createLoaderAndGetResource("test2/Resource2.txt", jar1, jar2); 390 assertEquals("Who doesn't like a good biscuit?\n", result); 391 } 392 393 /** 394 * Check that a resource in a jar file can be retrieved from 395 * a class within the other jar file. 396 */ 397 public void test_twoJar_diff_getResourceAsStream() throws Exception { 398 createLoaderAndCallMethod("test.TestMethods", "test_diff_getResourceAsStream", jar1, jar2); 399 } 400 401 /** 402 * Test that a DexClassLoader can be used to generate optimized code, then 403 * a subsequent PathClassLoader can be used to load the optimized code. 404 * (b/19937016). 405 */ 406 public void testDexThenPathClassLoader() throws Exception { 407 // Use a DexClassLoader to create optimized code. 408 File dex = new File(srcDir, "dex-then-path.dex"); 409 copyResource("loading-test.dex", dex); 410 411 File oatDir = new File(new File(srcDir, "oat"), VMRuntime.getCurrentInstructionSet()); 412 assertTrue(oatDir.mkdirs()); 413 414 DexClassLoader dexloader = new DexClassLoader(dex.getAbsolutePath(), 415 oatDir.getAbsolutePath(), null, ClassLoader.getSystemClassLoader()); 416 Class c1 = dexloader.loadClass("test.Test1"); 417 Method m1 = c1.getMethod("test", (Class[]) null); 418 assertSame("blort", m1.invoke(null, (Object[]) null)); 419 420 // Move the optimized code to the right location to be used by a 421 // PathClassLoader. 422 File odexForDexClassLoader = new File(oatDir, "dex-then-path.dex"); 423 File odexForPathClassLoader = new File(oatDir, "dex-then-path.odex"); 424 assertTrue(DexFile.isDexOptNeeded(dex.getAbsolutePath())); 425 assertTrue(odexForDexClassLoader.renameTo(odexForPathClassLoader)); 426 assertFalse(DexFile.isDexOptNeeded(dex.getAbsolutePath())); 427 428 // Use a PathClassLoader that loads and runs the optimized code. 429 PathClassLoader pathloader = new PathClassLoader(dex.getAbsolutePath(), 430 ClassLoader.getSystemClassLoader()); 431 Class c2 = pathloader.loadClass("test.Test1"); 432 Method m2 = c2.getMethod("test", (Class[]) null); 433 assertSame("blort", m2.invoke(null, (Object[]) null)); 434 } 435 } 436