DexClassLoaderTest.java revision 58c130c1eb99cc72998150d813b3e31896e3bfaf
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    // 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