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.lang.reflect.InvocationTargetException;
20import java.lang.reflect.Method;
21import java.io.File;
22import java.io.FileOutputStream;
23import java.io.IOException;
24import java.io.InputStream;
25import libcore.io.Streams;
26import junit.framework.TestCase;
27
28/**
29 * Tests for the class {@link DexClassLoader}.
30 */
31public 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