| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package dalvik.system; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import libcore.io.Streams; |
| import junit.framework.TestCase; |
| |
| /** |
| * Tests for the class {@link DexClassLoader}. |
| */ |
| public class DexClassLoaderTest extends TestCase { |
| private static final File TMP_DIR = |
| new File(System.getProperty("java.io.tmpdir"), "loading-test"); |
| private static final String PACKAGE_PATH = "dalvik/system/"; |
| private static final String JAR_NAME = "loading-test.jar"; |
| private static final String DEX_NAME = "loading-test.dex"; |
| private static final String JAR2_NAME = "loading-test2.jar"; |
| private static final String DEX2_NAME = "loading-test2.dex"; |
| private static final File JAR_FILE = new File(TMP_DIR, JAR_NAME); |
| private static final File DEX_FILE = new File(TMP_DIR, DEX_NAME); |
| private static final File JAR2_FILE = new File(TMP_DIR, JAR2_NAME); |
| private static final File DEX2_FILE = new File(TMP_DIR, DEX2_NAME); |
| private static final File OPTIMIZED_DIR = new File(TMP_DIR, "optimized"); |
| |
| private static enum Configuration { |
| /** just one classpath element, a raw dex file */ |
| ONE_DEX(1), |
| |
| /** just one classpath element, a jar file */ |
| ONE_JAR(1), |
| |
| /** two classpath elements, both raw dex files */ |
| TWO_DEX(2), |
| |
| /** two classpath elements, both jar files */ |
| TWO_JAR(2); |
| |
| public final int expectedFiles; |
| |
| Configuration(int expectedFiles) { |
| this.expectedFiles = expectedFiles; |
| } |
| } |
| |
| protected void setUp() throws IOException { |
| TMP_DIR.mkdirs(); |
| |
| ClassLoader cl = DexClassLoaderTest.class.getClassLoader(); |
| copyResource(cl, JAR_NAME, JAR_FILE); |
| copyResource(cl, DEX_NAME, DEX_FILE); |
| copyResource(cl, JAR2_NAME, JAR2_FILE); |
| copyResource(cl, DEX2_NAME, DEX2_FILE); |
| |
| OPTIMIZED_DIR.mkdirs(); |
| File[] files = OPTIMIZED_DIR.listFiles(); |
| for (File file : files) { |
| file.delete(); |
| } |
| } |
| |
| /** |
| * Copy a resource in the package directory to the indicated |
| * target file, but only if the target file doesn't exist. |
| */ |
| private static void copyResource(ClassLoader loader, String resourceName, |
| File destination) throws IOException { |
| if (destination.exists()) { |
| return; |
| } |
| |
| InputStream in = |
| loader.getResourceAsStream(PACKAGE_PATH + resourceName); |
| FileOutputStream out = new FileOutputStream(destination); |
| Streams.copy(in, out); |
| in.close(); |
| out.close(); |
| } |
| |
| /** |
| * Helper to construct an instance to test. |
| * |
| * @param config how to configure the classpath |
| */ |
| private static DexClassLoader createInstance(Configuration config) { |
| File file1; |
| File file2; |
| |
| switch (config) { |
| case ONE_DEX: file1 = DEX_FILE; file2 = null; break; |
| case ONE_JAR: file1 = JAR_FILE; file2 = null; break; |
| case TWO_DEX: file1 = DEX_FILE; file2 = DEX2_FILE; break; |
| case TWO_JAR: file1 = JAR_FILE; file2 = JAR2_FILE; break; |
| default: throw new AssertionError("shouldn't happen"); |
| } |
| |
| String path = file1.getAbsolutePath(); |
| if (file2 != null) { |
| path += File.pathSeparator + file2.getAbsolutePath(); |
| } |
| |
| return new DexClassLoader( |
| path, OPTIMIZED_DIR.getAbsolutePath(), null, |
| ClassLoader.getSystemClassLoader()); |
| } |
| |
| /** |
| * Helper to construct an instance to test, using the jar file as |
| * the source, and call a named no-argument static method on a |
| * named class. |
| * |
| * @param config how to configure the classpath |
| */ |
| public static Object createInstanceAndCallStaticMethod( |
| Configuration config, String className, String methodName) |
| throws ClassNotFoundException, NoSuchMethodException, |
| IllegalAccessException, InvocationTargetException { |
| DexClassLoader dcl = createInstance(config); |
| Class c = dcl.loadClass(className); |
| Method m = c.getMethod(methodName, (Class[]) null); |
| return m.invoke(null, (Object[]) null); |
| } |
| |
| /* |
| * Tests that are parametric with respect to whether to use a jar |
| * file or a dex file as the source of the code |
| */ |
| |
| /** |
| * Just a trivial test of construction. This one merely makes |
| * sure that a valid construction doesn't fail. It doesn't try |
| * to verify anything about the constructed instance, other than |
| * checking for the existence of optimized dex files. |
| */ |
| private static void test_init(Configuration config) { |
| createInstance(config); |
| |
| int expectedFiles = config.expectedFiles; |
| int actualFiles = OPTIMIZED_DIR.listFiles().length; |
| |
| assertEquals(expectedFiles, actualFiles); |
| } |
| |
| /** |
| * Check that a class in the jar/dex file may be used successfully. In this |
| * case, a trivial static method is called. |
| */ |
| private static void test_simpleUse(Configuration config) throws Exception { |
| String result = (String) |
| createInstanceAndCallStaticMethod(config, "test.Test1", "test"); |
| |
| assertSame("blort", result); |
| } |
| |
| /* |
| * All the following tests are just pass-throughs to test code |
| * that lives inside the loading-test dex/jar file. |
| */ |
| |
| private static void test_constructor(Configuration config) |
| throws Exception { |
| createInstanceAndCallStaticMethod( |
| config, "test.TestMethods", "test_constructor"); |
| } |
| |
| private static void test_callStaticMethod(Configuration config) |
| throws Exception { |
| createInstanceAndCallStaticMethod( |
| config, "test.TestMethods", "test_callStaticMethod"); |
| } |
| |
| private static void test_getStaticVariable(Configuration config) |
| throws Exception { |
| createInstanceAndCallStaticMethod( |
| config, "test.TestMethods", "test_getStaticVariable"); |
| } |
| |
| private static void test_callInstanceMethod(Configuration config) |
| throws Exception { |
| createInstanceAndCallStaticMethod( |
| config, "test.TestMethods", "test_callInstanceMethod"); |
| } |
| |
| private static void test_getInstanceVariable(Configuration config) |
| throws Exception { |
| createInstanceAndCallStaticMethod( |
| config, "test.TestMethods", "test_getInstanceVariable"); |
| } |
| |
| private static void test_diff_constructor(Configuration config) |
| throws Exception { |
| createInstanceAndCallStaticMethod( |
| config, "test.TestMethods", "test_diff_constructor"); |
| } |
| |
| private static void test_diff_callStaticMethod(Configuration config) |
| throws Exception { |
| createInstanceAndCallStaticMethod( |
| config, "test.TestMethods", "test_diff_callStaticMethod"); |
| } |
| |
| private static void test_diff_getStaticVariable(Configuration config) |
| throws Exception { |
| createInstanceAndCallStaticMethod( |
| config, "test.TestMethods", "test_diff_getStaticVariable"); |
| } |
| |
| private static void test_diff_callInstanceMethod(Configuration config) |
| throws Exception { |
| createInstanceAndCallStaticMethod( |
| config, "test.TestMethods", "test_diff_callInstanceMethod"); |
| } |
| |
| private static void test_diff_getInstanceVariable(Configuration config) |
| throws Exception { |
| createInstanceAndCallStaticMethod( |
| config, "test.TestMethods", "test_diff_getInstanceVariable"); |
| } |
| |
| /* |
| * These methods are all essentially just calls to the |
| * parametrically-defined tests above. |
| */ |
| |
| // ONE_JAR |
| |
| public void test_oneJar_init() throws Exception { |
| test_init(Configuration.ONE_JAR); |
| } |
| |
| public void test_oneJar_simpleUse() throws Exception { |
| test_simpleUse(Configuration.ONE_JAR); |
| } |
| |
| public void test_oneJar_constructor() throws Exception { |
| test_constructor(Configuration.ONE_JAR); |
| } |
| |
| public void test_oneJar_callStaticMethod() throws Exception { |
| test_callStaticMethod(Configuration.ONE_JAR); |
| } |
| |
| public void test_oneJar_getStaticVariable() throws Exception { |
| test_getStaticVariable(Configuration.ONE_JAR); |
| } |
| |
| public void test_oneJar_callInstanceMethod() throws Exception { |
| test_callInstanceMethod(Configuration.ONE_JAR); |
| } |
| |
| public void test_oneJar_getInstanceVariable() throws Exception { |
| test_getInstanceVariable(Configuration.ONE_JAR); |
| } |
| |
| // ONE_DEX |
| |
| public void test_oneDex_init() throws Exception { |
| test_init(Configuration.ONE_DEX); |
| } |
| |
| public void test_oneDex_simpleUse() throws Exception { |
| test_simpleUse(Configuration.ONE_DEX); |
| } |
| |
| public void test_oneDex_constructor() throws Exception { |
| test_constructor(Configuration.ONE_DEX); |
| } |
| |
| public void test_oneDex_callStaticMethod() throws Exception { |
| test_callStaticMethod(Configuration.ONE_DEX); |
| } |
| |
| public void test_oneDex_getStaticVariable() throws Exception { |
| test_getStaticVariable(Configuration.ONE_DEX); |
| } |
| |
| public void test_oneDex_callInstanceMethod() throws Exception { |
| test_callInstanceMethod(Configuration.ONE_DEX); |
| } |
| |
| public void test_oneDex_getInstanceVariable() throws Exception { |
| test_getInstanceVariable(Configuration.ONE_DEX); |
| } |
| |
| // TWO_JAR |
| |
| public void test_twoJar_init() throws Exception { |
| test_init(Configuration.TWO_JAR); |
| } |
| |
| public void test_twoJar_simpleUse() throws Exception { |
| test_simpleUse(Configuration.TWO_JAR); |
| } |
| |
| public void test_twoJar_constructor() throws Exception { |
| test_constructor(Configuration.TWO_JAR); |
| } |
| |
| public void test_twoJar_callStaticMethod() throws Exception { |
| test_callStaticMethod(Configuration.TWO_JAR); |
| } |
| |
| public void test_twoJar_getStaticVariable() throws Exception { |
| test_getStaticVariable(Configuration.TWO_JAR); |
| } |
| |
| public void test_twoJar_callInstanceMethod() throws Exception { |
| test_callInstanceMethod(Configuration.TWO_JAR); |
| } |
| |
| public void test_twoJar_getInstanceVariable() throws Exception { |
| test_getInstanceVariable(Configuration.TWO_JAR); |
| } |
| |
| public static void test_twoJar_diff_constructor() throws Exception { |
| test_diff_constructor(Configuration.TWO_JAR); |
| } |
| |
| public static void test_twoJar_diff_callStaticMethod() throws Exception { |
| test_diff_callStaticMethod(Configuration.TWO_JAR); |
| } |
| |
| public static void test_twoJar_diff_getStaticVariable() throws Exception { |
| test_diff_getStaticVariable(Configuration.TWO_JAR); |
| } |
| |
| public static void test_twoJar_diff_callInstanceMethod() |
| throws Exception { |
| test_diff_callInstanceMethod(Configuration.TWO_JAR); |
| } |
| |
| public static void test_twoJar_diff_getInstanceVariable() |
| throws Exception { |
| test_diff_getInstanceVariable(Configuration.TWO_JAR); |
| } |
| |
| // TWO_DEX |
| |
| public void test_twoDex_init() throws Exception { |
| test_init(Configuration.TWO_DEX); |
| } |
| |
| public void test_twoDex_simpleUse() throws Exception { |
| test_simpleUse(Configuration.TWO_DEX); |
| } |
| |
| public void test_twoDex_constructor() throws Exception { |
| test_constructor(Configuration.TWO_DEX); |
| } |
| |
| public void test_twoDex_callStaticMethod() throws Exception { |
| test_callStaticMethod(Configuration.TWO_DEX); |
| } |
| |
| public void test_twoDex_getStaticVariable() throws Exception { |
| test_getStaticVariable(Configuration.TWO_DEX); |
| } |
| |
| public void test_twoDex_callInstanceMethod() throws Exception { |
| test_callInstanceMethod(Configuration.TWO_DEX); |
| } |
| |
| public void test_twoDex_getInstanceVariable() throws Exception { |
| test_getInstanceVariable(Configuration.TWO_DEX); |
| } |
| |
| public static void test_twoDex_diff_constructor() throws Exception { |
| test_diff_constructor(Configuration.TWO_DEX); |
| } |
| |
| public static void test_twoDex_diff_callStaticMethod() throws Exception { |
| test_diff_callStaticMethod(Configuration.TWO_DEX); |
| } |
| |
| public static void test_twoDex_diff_getStaticVariable() throws Exception { |
| test_diff_getStaticVariable(Configuration.TWO_DEX); |
| } |
| |
| public static void test_twoDex_diff_callInstanceMethod() |
| throws Exception { |
| test_diff_callInstanceMethod(Configuration.TWO_DEX); |
| } |
| |
| public static void test_twoDex_diff_getInstanceVariable() |
| throws Exception { |
| test_diff_getInstanceVariable(Configuration.TWO_DEX); |
| } |
| |
| /* |
| * Tests specifically for resource-related functionality. Since |
| * raw dex files don't contain resources, these test only work |
| * with jar files. The first couple methods here are helpers, |
| * and they are followed by the tests per se. |
| */ |
| |
| /** |
| * Check that a given resource (by name) is retrievable and contains |
| * the given expected contents. |
| */ |
| private static void test_directGetResourceAsStream(Configuration config, |
| String resourceName, String expectedContents) |
| throws Exception { |
| DexClassLoader dcl = createInstance(config); |
| InputStream in = dcl.getResourceAsStream(resourceName); |
| byte[] contents = Streams.readFully(in); |
| String s = new String(contents, "UTF-8"); |
| |
| assertEquals(expectedContents, s); |
| } |
| |
| /** |
| * Check that a resource in the jar file is retrievable and contains |
| * the expected contents. |
| */ |
| private static void test_directGetResourceAsStream(Configuration config) |
| throws Exception { |
| test_directGetResourceAsStream( |
| config, "test/Resource1.txt", "Muffins are tasty!\n"); |
| } |
| |
| /** |
| * Check that a resource in the jar file can be retrieved from |
| * a class within that jar file. |
| */ |
| private static void test_getResourceAsStream(Configuration config) |
| throws Exception { |
| createInstanceAndCallStaticMethod( |
| config, "test.TestMethods", "test_getResourceAsStream"); |
| } |
| |
| public void test_oneJar_directGetResourceAsStream() throws Exception { |
| test_directGetResourceAsStream(Configuration.ONE_JAR); |
| } |
| |
| public void test_oneJar_getResourceAsStream() throws Exception { |
| test_getResourceAsStream(Configuration.ONE_JAR); |
| } |
| |
| public void test_twoJar_directGetResourceAsStream() throws Exception { |
| test_directGetResourceAsStream(Configuration.TWO_JAR); |
| } |
| |
| public void test_twoJar_getResourceAsStream() throws Exception { |
| test_getResourceAsStream(Configuration.TWO_JAR); |
| } |
| |
| /** |
| * Check that a resource in the second jar file is retrievable and |
| * contains the expected contents. |
| */ |
| public void test_twoJar_diff_directGetResourceAsStream() |
| throws Exception { |
| test_directGetResourceAsStream( |
| Configuration.TWO_JAR, "test2/Resource2.txt", |
| "Who doesn't like a good biscuit?\n"); |
| } |
| |
| /** |
| * Check that a resource in a jar file can be retrieved from |
| * a class within the other jar file. |
| */ |
| public void test_twoJar_diff_getResourceAsStream() |
| throws Exception { |
| createInstanceAndCallStaticMethod( |
| Configuration.TWO_JAR, "test.TestMethods", |
| "test_diff_getResourceAsStream"); |
| } |
| } |