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