FileDocCategorySizeDatePackage
DexMergeTest.javaAPI DocAndroid 5.1 API8660Thu Mar 12 22:18:30 GMT 2015com.android.dx.merge

DexMergeTest

public final class DexMergeTest extends TestCase
Test that DexMerge works by merging dex files, and then loading them into the current VM.

Fields Summary
Constructors Summary
Methods Summary
private voidcopy(java.io.InputStream in, java.io.OutputStream out)

        byte[] buffer = new byte[1024];
        int count;
        while ((count = in.read(buffer)) != -1) {
            out.write(buffer, 0, count);
        }
        in.close();
    
private java.io.FiledexToJar(java.io.File dex)

        File result = File.createTempFile("DexMergeTest", ".jar");
        result.deleteOnExit();
        JarOutputStream jarOut = new JarOutputStream(new FileOutputStream(result));
        jarOut.putNextEntry(new JarEntry("classes.dex"));
        copy(new FileInputStream(dex), jarOut);
        jarOut.closeEntry();
        jarOut.close();
        return result;
    
public java.lang.ClassLoadermergeAndLoad(java.lang.String dexAResource, java.lang.String dexBResource)

        Dex dexA = resourceToDexBuffer(dexAResource);
        Dex dexB = resourceToDexBuffer(dexBResource);
        Dex merged = new DexMerger(dexA, dexB, CollisionPolicy.KEEP_FIRST).merge();
        File mergedDex = File.createTempFile("DexMergeTest", ".classes.dex");
        merged.writeTo(mergedDex);
        File mergedJar = dexToJar(mergedDex);
        // simplify the javac classpath by not depending directly on 'dalvik.system' classes
        return (ClassLoader) Class.forName("dalvik.system.PathClassLoader")
                .getConstructor(String.class, ClassLoader.class)
                .newInstance(mergedJar.getPath(), getClass().getClassLoader());
    
private com.android.dex.DexresourceToDexBuffer(java.lang.String resource)

        return new Dex(getClass().getResourceAsStream(resource));
    
public voidtestAnnotations()

        ClassLoader loader = mergeAndLoad(
                "/testdata/Basic.dex",
                "/testdata/Annotated.dex");

        Class<?> basic = loader.loadClass("testdata.Basic");
        assertEquals(1, basic.getDeclaredMethods().length);

        Class<?> annotated = loader.loadClass("testdata.Annotated");
        Method method = annotated.getMethod("method", String.class, String.class);
        Field field = annotated.getField("field");

        @SuppressWarnings("unchecked")
        Class<? extends Annotation> marker
                = (Class<? extends Annotation>) loader.loadClass("testdata.Annotated$Marker");

        assertEquals("@testdata.Annotated$Marker(a=on class, b=[A, B, C], "
                + "c=@testdata.Annotated$Nested(e=E1, f=1695938256, g=7264081114510713000), "
                + "d=[@testdata.Annotated$Nested(e=E2, f=1695938256, g=7264081114510713000)])",
                annotated.getAnnotation(marker).toString());
        assertEquals("@testdata.Annotated$Marker(a=on method, b=[], "
                + "c=@testdata.Annotated$Nested(e=, f=0, g=0), d=[])",
                method.getAnnotation(marker).toString());
        assertEquals("@testdata.Annotated$Marker(a=on field, b=[], "
                + "c=@testdata.Annotated$Nested(e=, f=0, g=0), d=[])",
                field.getAnnotation(marker).toString());
        assertEquals("@testdata.Annotated$Marker(a=on parameter, b=[], "
                + "c=@testdata.Annotated$Nested(e=, f=0, g=0), d=[])",
                method.getParameterAnnotations()[1][0].toString());
    
public voidtestFillArrayData()

        ClassLoader loader = mergeAndLoad(
                "/testdata/Basic.dex",
                "/testdata/FillArrayData.dex");

        Class<?> basic = loader.loadClass("testdata.Basic");
        assertEquals(1, basic.getDeclaredMethods().length);

        Class<?> fillArrayData = loader.loadClass("testdata.FillArrayData");
        assertTrue(Arrays.equals(
                new byte[] { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, -112, -23, 121 },
                (byte[]) fillArrayData.getMethod("newByteArray").invoke(null)));
        assertTrue(Arrays.equals(
                new char[] { 0xFFFF, 0x4321, 0xABCD, 0, 'a", 'b", 'c" },
                (char[]) fillArrayData.getMethod("newCharArray").invoke(null)));
        assertTrue(Arrays.equals(
                new long[] { 4660046610375530309L, 7540113804746346429L, -6246583658587674878L },
                (long[]) fillArrayData.getMethod("newLongArray").invoke(null)));
    
public voidtestMergedOutputSizeIsBounded()
Merging dex files uses pessimistic sizes that naturally leave gaps in the output files. If those gaps grow too large, the merger is supposed to compact the result. This exercises that by repeatedly merging a dex with itself.

        /*
         * At the time this test was written, the output would grow ~25% with
         * each merge. Setting a low 1KiB ceiling on the maximum size caused
         * the file to be compacted every four merges.
         */
        int steps = 100;
        int compactWasteThreshold = 1024;

        Dex dexA = resourceToDexBuffer("/testdata/Basic.dex");
        Dex dexB = resourceToDexBuffer("/testdata/TryCatchFinally.dex");
        Dex merged = new DexMerger(dexA, dexB, CollisionPolicy.KEEP_FIRST).merge();

        int maxLength = 0;
        for (int i = 0; i < steps; i++) {
            DexMerger dexMerger = new DexMerger(dexA, merged, CollisionPolicy.KEEP_FIRST);
            dexMerger.setCompactWasteThreshold(compactWasteThreshold);
            merged = dexMerger.merge();
            maxLength = Math.max(maxLength, merged.getLength());
        }

        int maxExpectedLength = dexA.getLength() + dexB.getLength() + compactWasteThreshold;
        assertTrue(maxLength + " < " + maxExpectedLength, maxLength < maxExpectedLength);
    
public voidtestStaticValues()

        ClassLoader loader = mergeAndLoad(
                "/testdata/Basic.dex",
                "/testdata/StaticValues.dex");

        Class<?> basic = loader.loadClass("testdata.Basic");
        assertEquals(1, basic.getDeclaredMethods().length);

        Class<?> staticValues = loader.loadClass("testdata.StaticValues");
        assertEquals((byte) 1, staticValues.getField("a").get(null));
        assertEquals((short) 2, staticValues.getField("b").get(null));
        assertEquals('C", staticValues.getField("c").get(null));
        assertEquals(0xabcd1234, staticValues.getField("d").get(null));
        assertEquals(4660046610375530309L,staticValues.getField("e").get(null));
        assertEquals(0.5f, staticValues.getField("f").get(null));
        assertEquals(-0.25, staticValues.getField("g").get(null));
        assertEquals("this is a String", staticValues.getField("h").get(null));
        assertEquals(String.class, staticValues.getField("i").get(null));
        assertEquals("[0, 1]", Arrays.toString((int[]) staticValues.getField("j").get(null)));
        assertEquals(null, staticValues.getField("k").get(null));
        assertEquals(true, staticValues.getField("l").get(null));
        assertEquals(false, staticValues.getField("m").get(null));
    
public voidtestTryCatchFinally()

        ClassLoader loader = mergeAndLoad(
                "/testdata/Basic.dex",
                "/testdata/TryCatchFinally.dex");

        Class<?> basic = loader.loadClass("testdata.Basic");
        assertEquals(1, basic.getDeclaredMethods().length);

        Class<?> tryCatchFinally = loader.loadClass("testdata.TryCatchFinally");
        tryCatchFinally.getDeclaredMethod("method").invoke(null);