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.lang.reflect.InvocationTargetException; 20 import java.lang.reflect.Method; 21 import java.io.File; 22 import java.io.FileOutputStream; 23 import java.io.IOException; 24 import java.io.InputStream; 25 import libcore.io.Streams; 26 import junit.framework.TestCase; 27 28 /** 29 * Tests for the class {@link DexClassLoader}. 30 */ 31 public class DexClassLoaderTest extends TestCase { 32 // Use /data not /sdcard because optimized cannot be noexec mounted 33 private static final File WORKING_DIR; 34 static { 35 // First try to use the test runner directory for cts, fall back to 36 // shell-writable directory for vogar 37 File runner_dir = new File("/data/data/android.core.tests.runner"); 38 if (runner_dir.exists()) { 39 WORKING_DIR = runner_dir; 40 } else { 41 WORKING_DIR = new File("/data/local/tmp"); 42 } 43 } 44 private static final File TMP_DIR = new File(WORKING_DIR, "loading-test"); 45 private static final String PACKAGE_PATH = "dalvik/system/"; 46 private static final String JAR_NAME = "loading-test.jar"; 47 private static final String DEX_NAME = "loading-test.dex"; 48 private static final String JAR2_NAME = "loading-test2.jar"; 49 private static final String DEX2_NAME = "loading-test2.dex"; 50 private static final File JAR_FILE = new File(TMP_DIR, JAR_NAME); 51 private static final File DEX_FILE = new File(TMP_DIR, DEX_NAME); 52 private static final File JAR2_FILE = new File(TMP_DIR, JAR2_NAME); 53 private static final File DEX2_FILE = new File(TMP_DIR, DEX2_NAME); 54 private static final File DEFAULT_OPTIMIZED_DIR = new File(TMP_DIR, "optimized"); 55 // Init tests need to use different optimized directories because the tests are executed in the 56 // same runtime. This means we can't reliably count the number of generated file since they 57 // might be cached by the runtime. 58 private static final File INIT1_OPTIMIZED_DIR = new File(TMP_DIR, "optimized_init1"); 59 private static final File INIT2_OPTIMIZED_DIR = new File(TMP_DIR, "optimized_init2"); 60 61 private static enum Configuration { 62 /** just one classpath element, a raw dex file */ 63 ONE_DEX(1, DEX_FILE), 64 ONE_DEX_INIT(INIT1_OPTIMIZED_DIR, 1, DEX_FILE), 65 66 /** just one classpath element, a jar file */ 67 ONE_JAR(1, JAR_FILE), 68 ONE_JAR_INIT(INIT1_OPTIMIZED_DIR, 1, JAR_FILE), 69 70 /** two classpath elements, both raw dex files */ 71 TWO_DEX(2, DEX_FILE, DEX2_FILE), 72 TWO_DEX_INIT(INIT2_OPTIMIZED_DIR, 2, DEX_FILE, DEX2_FILE), 73 74 /** two classpath elements, both jar files */ 75 TWO_JAR(2, JAR_FILE, JAR2_FILE), 76 TWO_JAR_INIT(INIT2_OPTIMIZED_DIR, 2, JAR_FILE, JAR2_FILE); 77 78 public final int expectedFiles; 79 public final File optimizedDir; 80 public final String path; 81 Configuration(int expectedFiles, File... files)82 Configuration(int expectedFiles, File... files) { 83 this(DEFAULT_OPTIMIZED_DIR, expectedFiles, files); 84 } 85 Configuration(File optimizedDir, int expectedFiles, File... files)86 Configuration(File optimizedDir, int expectedFiles, File... files) { 87 assertTrue(files != null && files.length > 0); 88 89 this.expectedFiles = expectedFiles; 90 this.optimizedDir = optimizedDir; 91 String path = files[0].getAbsolutePath(); 92 for (int i = 1; i < files.length; i++) { 93 path += File.pathSeparator + files[i].getAbsolutePath(); 94 } 95 this.path = path; 96 } 97 } 98 setUp()99 protected void setUp() throws Exception { 100 assertTrue(TMP_DIR.exists() || TMP_DIR.mkdirs()); 101 assertTrue(DEFAULT_OPTIMIZED_DIR.exists() || DEFAULT_OPTIMIZED_DIR.mkdirs()); 102 assertTrue(INIT1_OPTIMIZED_DIR.exists() || INIT1_OPTIMIZED_DIR.mkdirs()); 103 assertTrue(INIT2_OPTIMIZED_DIR.exists() || INIT2_OPTIMIZED_DIR.mkdirs()); 104 105 ClassLoader cl = DexClassLoaderTest.class.getClassLoader(); 106 copyResource(cl, JAR_NAME, JAR_FILE); 107 copyResource(cl, DEX_NAME, DEX_FILE); 108 copyResource(cl, JAR2_NAME, JAR2_FILE); 109 copyResource(cl, DEX2_NAME, DEX2_FILE); 110 } 111 tearDown()112 protected void tearDown() { 113 cleanUpDir(DEFAULT_OPTIMIZED_DIR); 114 cleanUpDir(INIT1_OPTIMIZED_DIR); 115 cleanUpDir(INIT2_OPTIMIZED_DIR); 116 } 117 cleanUpDir(File dir)118 private void cleanUpDir(File dir) { 119 if (!dir.isDirectory()) { 120 return; 121 } 122 File[] files = dir.listFiles(); 123 for (File file : files) { 124 assertTrue(file.delete()); 125 } 126 } 127 128 /** 129 * Copy a resource in the package directory to the indicated 130 * target file, but only if the target file doesn't exist. 131 */ copyResource(ClassLoader loader, String resourceName, File destination)132 private static void copyResource(ClassLoader loader, String resourceName, 133 File destination) throws IOException { 134 if (destination.exists()) { 135 return; 136 } 137 138 InputStream in = 139 loader.getResourceAsStream(PACKAGE_PATH + resourceName); 140 FileOutputStream out = new FileOutputStream(destination); 141 Streams.copy(in, out); 142 in.close(); 143 out.close(); 144 } 145 146 /** 147 * Helper to construct an instance to test. 148 * 149 * @param config how to configure the classpath 150 */ createInstance(Configuration config)151 private static DexClassLoader createInstance(Configuration config) { 152 return new DexClassLoader( 153 config.path, config.optimizedDir.getAbsolutePath(), null, 154 ClassLoader.getSystemClassLoader()); 155 } 156 157 /** 158 * Helper to construct an instance to test, using the jar file as 159 * the source, and call a named no-argument static method on a 160 * named class. 161 * 162 * @param config how to configure the classpath 163 */ createInstanceAndCallStaticMethod( Configuration config, String className, String methodName)164 public static Object createInstanceAndCallStaticMethod( 165 Configuration config, String className, String methodName) 166 throws ClassNotFoundException, NoSuchMethodException, 167 IllegalAccessException, InvocationTargetException { 168 DexClassLoader dcl = createInstance(config); 169 Class c = dcl.loadClass(className); 170 Method m = c.getMethod(methodName, (Class[]) null); 171 return m.invoke(null, (Object[]) null); 172 } 173 174 /* 175 * Tests that are parametric with respect to whether to use a jar 176 * file or a dex file as the source of the code 177 */ 178 179 /** 180 * Just a trivial test of construction. This one merely makes 181 * sure that a valid construction doesn't fail. It doesn't try 182 * to verify anything about the constructed instance, other than 183 * checking for the existence of optimized dex files. 184 */ test_init(Configuration config)185 private static void test_init(Configuration config) { 186 createInstance(config); 187 188 int expectedFiles = config.expectedFiles; 189 int actualFiles = config.optimizedDir.listFiles().length; 190 191 assertEquals(expectedFiles, actualFiles); 192 } 193 194 /** 195 * Check that a class in the jar/dex file may be used successfully. In this 196 * case, a trivial static method is called. 197 */ test_simpleUse(Configuration config)198 private static void test_simpleUse(Configuration config) throws Exception { 199 String result = (String) 200 createInstanceAndCallStaticMethod(config, "test.Test1", "test"); 201 202 assertSame("blort", result); 203 } 204 205 /* 206 * All the following tests are just pass-throughs to test code 207 * that lives inside the loading-test dex/jar file. 208 */ 209 test_constructor(Configuration config)210 private static void test_constructor(Configuration config) 211 throws Exception { 212 createInstanceAndCallStaticMethod( 213 config, "test.TestMethods", "test_constructor"); 214 } 215 test_callStaticMethod(Configuration config)216 private static void test_callStaticMethod(Configuration config) 217 throws Exception { 218 createInstanceAndCallStaticMethod( 219 config, "test.TestMethods", "test_callStaticMethod"); 220 } 221 test_getStaticVariable(Configuration config)222 private static void test_getStaticVariable(Configuration config) 223 throws Exception { 224 createInstanceAndCallStaticMethod( 225 config, "test.TestMethods", "test_getStaticVariable"); 226 } 227 test_callInstanceMethod(Configuration config)228 private static void test_callInstanceMethod(Configuration config) 229 throws Exception { 230 createInstanceAndCallStaticMethod( 231 config, "test.TestMethods", "test_callInstanceMethod"); 232 } 233 test_getInstanceVariable(Configuration config)234 private static void test_getInstanceVariable(Configuration config) 235 throws Exception { 236 createInstanceAndCallStaticMethod( 237 config, "test.TestMethods", "test_getInstanceVariable"); 238 } 239 test_diff_constructor(Configuration config)240 private static void test_diff_constructor(Configuration config) 241 throws Exception { 242 createInstanceAndCallStaticMethod( 243 config, "test.TestMethods", "test_diff_constructor"); 244 } 245 test_diff_callStaticMethod(Configuration config)246 private static void test_diff_callStaticMethod(Configuration config) 247 throws Exception { 248 createInstanceAndCallStaticMethod( 249 config, "test.TestMethods", "test_diff_callStaticMethod"); 250 } 251 test_diff_getStaticVariable(Configuration config)252 private static void test_diff_getStaticVariable(Configuration config) 253 throws Exception { 254 createInstanceAndCallStaticMethod( 255 config, "test.TestMethods", "test_diff_getStaticVariable"); 256 } 257 test_diff_callInstanceMethod(Configuration config)258 private static void test_diff_callInstanceMethod(Configuration config) 259 throws Exception { 260 createInstanceAndCallStaticMethod( 261 config, "test.TestMethods", "test_diff_callInstanceMethod"); 262 } 263 test_diff_getInstanceVariable(Configuration config)264 private static void test_diff_getInstanceVariable(Configuration config) 265 throws Exception { 266 createInstanceAndCallStaticMethod( 267 config, "test.TestMethods", "test_diff_getInstanceVariable"); 268 } 269 270 /* 271 * These methods are all essentially just calls to the 272 * parametrically-defined tests above. 273 */ 274 275 // ONE_JAR 276 test_oneJar_init()277 public void test_oneJar_init() throws Exception { 278 test_init(Configuration.ONE_JAR_INIT); 279 } 280 test_oneJar_simpleUse()281 public void test_oneJar_simpleUse() throws Exception { 282 test_simpleUse(Configuration.ONE_JAR); 283 } 284 test_oneJar_constructor()285 public void test_oneJar_constructor() throws Exception { 286 test_constructor(Configuration.ONE_JAR); 287 } 288 test_oneJar_callStaticMethod()289 public void test_oneJar_callStaticMethod() throws Exception { 290 test_callStaticMethod(Configuration.ONE_JAR); 291 } 292 test_oneJar_getStaticVariable()293 public void test_oneJar_getStaticVariable() throws Exception { 294 test_getStaticVariable(Configuration.ONE_JAR); 295 } 296 test_oneJar_callInstanceMethod()297 public void test_oneJar_callInstanceMethod() throws Exception { 298 test_callInstanceMethod(Configuration.ONE_JAR); 299 } 300 test_oneJar_getInstanceVariable()301 public void test_oneJar_getInstanceVariable() throws Exception { 302 test_getInstanceVariable(Configuration.ONE_JAR); 303 } 304 305 // ONE_DEX 306 test_oneDex_init()307 public void test_oneDex_init() throws Exception { 308 test_init(Configuration.ONE_DEX_INIT); 309 } 310 test_oneDex_simpleUse()311 public void test_oneDex_simpleUse() throws Exception { 312 test_simpleUse(Configuration.ONE_DEX); 313 } 314 test_oneDex_constructor()315 public void test_oneDex_constructor() throws Exception { 316 test_constructor(Configuration.ONE_DEX); 317 } 318 test_oneDex_callStaticMethod()319 public void test_oneDex_callStaticMethod() throws Exception { 320 test_callStaticMethod(Configuration.ONE_DEX); 321 } 322 test_oneDex_getStaticVariable()323 public void test_oneDex_getStaticVariable() throws Exception { 324 test_getStaticVariable(Configuration.ONE_DEX); 325 } 326 test_oneDex_callInstanceMethod()327 public void test_oneDex_callInstanceMethod() throws Exception { 328 test_callInstanceMethod(Configuration.ONE_DEX); 329 } 330 test_oneDex_getInstanceVariable()331 public void test_oneDex_getInstanceVariable() throws Exception { 332 test_getInstanceVariable(Configuration.ONE_DEX); 333 } 334 335 // TWO_JAR 336 test_twoJar_init()337 public void test_twoJar_init() throws Exception { 338 test_init(Configuration.TWO_JAR_INIT); 339 } 340 test_twoJar_simpleUse()341 public void test_twoJar_simpleUse() throws Exception { 342 test_simpleUse(Configuration.TWO_JAR); 343 } 344 test_twoJar_constructor()345 public void test_twoJar_constructor() throws Exception { 346 test_constructor(Configuration.TWO_JAR); 347 } 348 test_twoJar_callStaticMethod()349 public void test_twoJar_callStaticMethod() throws Exception { 350 test_callStaticMethod(Configuration.TWO_JAR); 351 } 352 test_twoJar_getStaticVariable()353 public void test_twoJar_getStaticVariable() throws Exception { 354 test_getStaticVariable(Configuration.TWO_JAR); 355 } 356 test_twoJar_callInstanceMethod()357 public void test_twoJar_callInstanceMethod() throws Exception { 358 test_callInstanceMethod(Configuration.TWO_JAR); 359 } 360 test_twoJar_getInstanceVariable()361 public void test_twoJar_getInstanceVariable() throws Exception { 362 test_getInstanceVariable(Configuration.TWO_JAR); 363 } 364 test_twoJar_diff_constructor()365 public static void test_twoJar_diff_constructor() throws Exception { 366 test_diff_constructor(Configuration.TWO_JAR); 367 } 368 test_twoJar_diff_callStaticMethod()369 public static void test_twoJar_diff_callStaticMethod() throws Exception { 370 test_diff_callStaticMethod(Configuration.TWO_JAR); 371 } 372 test_twoJar_diff_getStaticVariable()373 public static void test_twoJar_diff_getStaticVariable() throws Exception { 374 test_diff_getStaticVariable(Configuration.TWO_JAR); 375 } 376 test_twoJar_diff_callInstanceMethod()377 public static void test_twoJar_diff_callInstanceMethod() 378 throws Exception { 379 test_diff_callInstanceMethod(Configuration.TWO_JAR); 380 } 381 test_twoJar_diff_getInstanceVariable()382 public static void test_twoJar_diff_getInstanceVariable() 383 throws Exception { 384 test_diff_getInstanceVariable(Configuration.TWO_JAR); 385 } 386 387 // TWO_DEX 388 test_twoDex_init()389 public void test_twoDex_init() throws Exception { 390 test_init(Configuration.TWO_DEX_INIT); 391 } 392 test_twoDex_simpleUse()393 public void test_twoDex_simpleUse() throws Exception { 394 test_simpleUse(Configuration.TWO_DEX); 395 } 396 test_twoDex_constructor()397 public void test_twoDex_constructor() throws Exception { 398 test_constructor(Configuration.TWO_DEX); 399 } 400 test_twoDex_callStaticMethod()401 public void test_twoDex_callStaticMethod() throws Exception { 402 test_callStaticMethod(Configuration.TWO_DEX); 403 } 404 test_twoDex_getStaticVariable()405 public void test_twoDex_getStaticVariable() throws Exception { 406 test_getStaticVariable(Configuration.TWO_DEX); 407 } 408 test_twoDex_callInstanceMethod()409 public void test_twoDex_callInstanceMethod() throws Exception { 410 test_callInstanceMethod(Configuration.TWO_DEX); 411 } 412 test_twoDex_getInstanceVariable()413 public void test_twoDex_getInstanceVariable() throws Exception { 414 test_getInstanceVariable(Configuration.TWO_DEX); 415 } 416 test_twoDex_diff_constructor()417 public static void test_twoDex_diff_constructor() throws Exception { 418 test_diff_constructor(Configuration.TWO_DEX); 419 } 420 test_twoDex_diff_callStaticMethod()421 public static void test_twoDex_diff_callStaticMethod() throws Exception { 422 test_diff_callStaticMethod(Configuration.TWO_DEX); 423 } 424 test_twoDex_diff_getStaticVariable()425 public static void test_twoDex_diff_getStaticVariable() throws Exception { 426 test_diff_getStaticVariable(Configuration.TWO_DEX); 427 } 428 test_twoDex_diff_callInstanceMethod()429 public static void test_twoDex_diff_callInstanceMethod() 430 throws Exception { 431 test_diff_callInstanceMethod(Configuration.TWO_DEX); 432 } 433 test_twoDex_diff_getInstanceVariable()434 public static void test_twoDex_diff_getInstanceVariable() 435 throws Exception { 436 test_diff_getInstanceVariable(Configuration.TWO_DEX); 437 } 438 439 /* 440 * Tests specifically for resource-related functionality. Since 441 * raw dex files don't contain resources, these test only work 442 * with jar files. The first couple methods here are helpers, 443 * and they are followed by the tests per se. 444 */ 445 446 /** 447 * Check that a given resource (by name) is retrievable and contains 448 * the given expected contents. 449 */ test_directGetResourceAsStream(Configuration config, String resourceName, String expectedContents)450 private static void test_directGetResourceAsStream(Configuration config, 451 String resourceName, String expectedContents) 452 throws Exception { 453 DexClassLoader dcl = createInstance(config); 454 InputStream in = dcl.getResourceAsStream(resourceName); 455 byte[] contents = Streams.readFully(in); 456 String s = new String(contents, "UTF-8"); 457 458 assertEquals(expectedContents, s); 459 } 460 461 /** 462 * Check that a resource in the jar file is retrievable and contains 463 * the expected contents. 464 */ test_directGetResourceAsStream(Configuration config)465 private static void test_directGetResourceAsStream(Configuration config) 466 throws Exception { 467 test_directGetResourceAsStream( 468 config, "test/Resource1.txt", "Muffins are tasty!\n"); 469 } 470 471 /** 472 * Check that a resource in the jar file can be retrieved from 473 * a class within that jar file. 474 */ test_getResourceAsStream(Configuration config)475 private static void test_getResourceAsStream(Configuration config) 476 throws Exception { 477 createInstanceAndCallStaticMethod( 478 config, "test.TestMethods", "test_getResourceAsStream"); 479 } 480 test_oneJar_directGetResourceAsStream()481 public void test_oneJar_directGetResourceAsStream() throws Exception { 482 test_directGetResourceAsStream(Configuration.ONE_JAR); 483 } 484 test_oneJar_getResourceAsStream()485 public void test_oneJar_getResourceAsStream() throws Exception { 486 test_getResourceAsStream(Configuration.ONE_JAR); 487 } 488 test_twoJar_directGetResourceAsStream()489 public void test_twoJar_directGetResourceAsStream() throws Exception { 490 test_directGetResourceAsStream(Configuration.TWO_JAR); 491 } 492 test_twoJar_getResourceAsStream()493 public void test_twoJar_getResourceAsStream() throws Exception { 494 test_getResourceAsStream(Configuration.TWO_JAR); 495 } 496 497 /** 498 * Check that a resource in the second jar file is retrievable and 499 * contains the expected contents. 500 */ test_twoJar_diff_directGetResourceAsStream()501 public void test_twoJar_diff_directGetResourceAsStream() 502 throws Exception { 503 test_directGetResourceAsStream( 504 Configuration.TWO_JAR, "test2/Resource2.txt", 505 "Who doesn't like a good biscuit?\n"); 506 } 507 508 /** 509 * Check that a resource in a jar file can be retrieved from 510 * a class within the other jar file. 511 */ test_twoJar_diff_getResourceAsStream()512 public void test_twoJar_diff_getResourceAsStream() 513 throws Exception { 514 createInstanceAndCallStaticMethod( 515 Configuration.TWO_JAR, "test.TestMethods", 516 "test_diff_getResourceAsStream"); 517 } 518 } 519