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