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