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 82 Configuration(int expectedFiles, File... files) { 83 this(DEFAULT_OPTIMIZED_DIR, expectedFiles, files); 84 } 85 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 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 112 protected void tearDown() { 113 cleanUpDir(DEFAULT_OPTIMIZED_DIR); 114 cleanUpDir(INIT1_OPTIMIZED_DIR); 115 cleanUpDir(INIT2_OPTIMIZED_DIR); 116 } 117 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 */ 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 */ 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 */ 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 */ 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 */ 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 210 private static void test_constructor(Configuration config) 211 throws Exception { 212 createInstanceAndCallStaticMethod( 213 config, "test.TestMethods", "test_constructor"); 214 } 215 216 private static void test_callStaticMethod(Configuration config) 217 throws Exception { 218 createInstanceAndCallStaticMethod( 219 config, "test.TestMethods", "test_callStaticMethod"); 220 } 221 222 private static void test_getStaticVariable(Configuration config) 223 throws Exception { 224 createInstanceAndCallStaticMethod( 225 config, "test.TestMethods", "test_getStaticVariable"); 226 } 227 228 private static void test_callInstanceMethod(Configuration config) 229 throws Exception { 230 createInstanceAndCallStaticMethod( 231 config, "test.TestMethods", "test_callInstanceMethod"); 232 } 233 234 private static void test_getInstanceVariable(Configuration config) 235 throws Exception { 236 createInstanceAndCallStaticMethod( 237 config, "test.TestMethods", "test_getInstanceVariable"); 238 } 239 240 private static void test_diff_constructor(Configuration config) 241 throws Exception { 242 createInstanceAndCallStaticMethod( 243 config, "test.TestMethods", "test_diff_constructor"); 244 } 245 246 private static void test_diff_callStaticMethod(Configuration config) 247 throws Exception { 248 createInstanceAndCallStaticMethod( 249 config, "test.TestMethods", "test_diff_callStaticMethod"); 250 } 251 252 private static void test_diff_getStaticVariable(Configuration config) 253 throws Exception { 254 createInstanceAndCallStaticMethod( 255 config, "test.TestMethods", "test_diff_getStaticVariable"); 256 } 257 258 private static void test_diff_callInstanceMethod(Configuration config) 259 throws Exception { 260 createInstanceAndCallStaticMethod( 261 config, "test.TestMethods", "test_diff_callInstanceMethod"); 262 } 263 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 277 public void test_oneJar_init() throws Exception { 278 test_init(Configuration.ONE_JAR_INIT); 279 } 280 281 public void test_oneJar_simpleUse() throws Exception { 282 test_simpleUse(Configuration.ONE_JAR); 283 } 284 285 public void test_oneJar_constructor() throws Exception { 286 test_constructor(Configuration.ONE_JAR); 287 } 288 289 public void test_oneJar_callStaticMethod() throws Exception { 290 test_callStaticMethod(Configuration.ONE_JAR); 291 } 292 293 public void test_oneJar_getStaticVariable() throws Exception { 294 test_getStaticVariable(Configuration.ONE_JAR); 295 } 296 297 public void test_oneJar_callInstanceMethod() throws Exception { 298 test_callInstanceMethod(Configuration.ONE_JAR); 299 } 300 301 public void test_oneJar_getInstanceVariable() throws Exception { 302 test_getInstanceVariable(Configuration.ONE_JAR); 303 } 304 305 // ONE_DEX 306 307 public void test_oneDex_init() throws Exception { 308 test_init(Configuration.ONE_DEX_INIT); 309 } 310 311 public void test_oneDex_simpleUse() throws Exception { 312 test_simpleUse(Configuration.ONE_DEX); 313 } 314 315 public void test_oneDex_constructor() throws Exception { 316 test_constructor(Configuration.ONE_DEX); 317 } 318 319 public void test_oneDex_callStaticMethod() throws Exception { 320 test_callStaticMethod(Configuration.ONE_DEX); 321 } 322 323 public void test_oneDex_getStaticVariable() throws Exception { 324 test_getStaticVariable(Configuration.ONE_DEX); 325 } 326 327 public void test_oneDex_callInstanceMethod() throws Exception { 328 test_callInstanceMethod(Configuration.ONE_DEX); 329 } 330 331 public void test_oneDex_getInstanceVariable() throws Exception { 332 test_getInstanceVariable(Configuration.ONE_DEX); 333 } 334 335 // TWO_JAR 336 337 public void test_twoJar_init() throws Exception { 338 test_init(Configuration.TWO_JAR_INIT); 339 } 340 341 public void test_twoJar_simpleUse() throws Exception { 342 test_simpleUse(Configuration.TWO_JAR); 343 } 344 345 public void test_twoJar_constructor() throws Exception { 346 test_constructor(Configuration.TWO_JAR); 347 } 348 349 public void test_twoJar_callStaticMethod() throws Exception { 350 test_callStaticMethod(Configuration.TWO_JAR); 351 } 352 353 public void test_twoJar_getStaticVariable() throws Exception { 354 test_getStaticVariable(Configuration.TWO_JAR); 355 } 356 357 public void test_twoJar_callInstanceMethod() throws Exception { 358 test_callInstanceMethod(Configuration.TWO_JAR); 359 } 360 361 public void test_twoJar_getInstanceVariable() throws Exception { 362 test_getInstanceVariable(Configuration.TWO_JAR); 363 } 364 365 public static void test_twoJar_diff_constructor() throws Exception { 366 test_diff_constructor(Configuration.TWO_JAR); 367 } 368 369 public static void test_twoJar_diff_callStaticMethod() throws Exception { 370 test_diff_callStaticMethod(Configuration.TWO_JAR); 371 } 372 373 public static void test_twoJar_diff_getStaticVariable() throws Exception { 374 test_diff_getStaticVariable(Configuration.TWO_JAR); 375 } 376 377 public static void test_twoJar_diff_callInstanceMethod() 378 throws Exception { 379 test_diff_callInstanceMethod(Configuration.TWO_JAR); 380 } 381 382 public static void test_twoJar_diff_getInstanceVariable() 383 throws Exception { 384 test_diff_getInstanceVariable(Configuration.TWO_JAR); 385 } 386 387 // TWO_DEX 388 389 public void test_twoDex_init() throws Exception { 390 test_init(Configuration.TWO_DEX_INIT); 391 } 392 393 public void test_twoDex_simpleUse() throws Exception { 394 test_simpleUse(Configuration.TWO_DEX); 395 } 396 397 public void test_twoDex_constructor() throws Exception { 398 test_constructor(Configuration.TWO_DEX); 399 } 400 401 public void test_twoDex_callStaticMethod() throws Exception { 402 test_callStaticMethod(Configuration.TWO_DEX); 403 } 404 405 public void test_twoDex_getStaticVariable() throws Exception { 406 test_getStaticVariable(Configuration.TWO_DEX); 407 } 408 409 public void test_twoDex_callInstanceMethod() throws Exception { 410 test_callInstanceMethod(Configuration.TWO_DEX); 411 } 412 413 public void test_twoDex_getInstanceVariable() throws Exception { 414 test_getInstanceVariable(Configuration.TWO_DEX); 415 } 416 417 public static void test_twoDex_diff_constructor() throws Exception { 418 test_diff_constructor(Configuration.TWO_DEX); 419 } 420 421 public static void test_twoDex_diff_callStaticMethod() throws Exception { 422 test_diff_callStaticMethod(Configuration.TWO_DEX); 423 } 424 425 public static void test_twoDex_diff_getStaticVariable() throws Exception { 426 test_diff_getStaticVariable(Configuration.TWO_DEX); 427 } 428 429 public static void test_twoDex_diff_callInstanceMethod() 430 throws Exception { 431 test_diff_callInstanceMethod(Configuration.TWO_DEX); 432 } 433 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 */ 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 */ 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 */ 475 private static void test_getResourceAsStream(Configuration config) 476 throws Exception { 477 createInstanceAndCallStaticMethod( 478 config, "test.TestMethods", "test_getResourceAsStream"); 479 } 480 481 public void test_oneJar_directGetResourceAsStream() throws Exception { 482 test_directGetResourceAsStream(Configuration.ONE_JAR); 483 } 484 485 public void test_oneJar_getResourceAsStream() throws Exception { 486 test_getResourceAsStream(Configuration.ONE_JAR); 487 } 488 489 public void test_twoJar_directGetResourceAsStream() throws Exception { 490 test_directGetResourceAsStream(Configuration.TWO_JAR); 491 } 492 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 */ 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 */ 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