blob: 60edc6d1758f63095c879910394415af4ad35f24 [file] [log] [blame]
// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.dexfilemerger;
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.CompilationException;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.D8Command;
import com.android.tools.r8.DexFileMergerHelper;
import com.android.tools.r8.ExtractMarker;
import com.android.tools.r8.OutputMode;
import com.android.tools.r8.ResourceException;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.dex.Marker;
import com.android.tools.r8.maindexlist.MainDexListTests;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.ExecutionException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
public class DexFileMergerTests {
private static final String CLASS_DIR = ToolHelper.EXAMPLES_BUILD_DIR + "classes/dexmergesample";
private static final String CLASS1_CLASS = CLASS_DIR + "/Class1.class";
private static final String CLASS2_CLASS = CLASS_DIR + "/Class2.class";
private static final int MAX_METHOD_COUNT = Constants.U16BIT_MAX;
@Rule public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
private Path createMergerInputWithTwoClasses(OutputMode outputMode, boolean dontCreateMarkerInD8)
throws CompilationFailedException, CompilationException, IOException {
// Compile Class1 and Class2
Path mergerInputZip = temp.newFolder().toPath().resolve("merger-input.zip");
D8Command command =
D8Command.builder()
.setOutput(mergerInputZip, outputMode)
.addProgramFiles(Paths.get(CLASS1_CLASS))
.addProgramFiles(Paths.get(CLASS2_CLASS))
.build();
DexFileMergerHelper.runForTesting(command, dontCreateMarkerInD8);
return mergerInputZip;
}
private void testMarkerPreservedOrNotAdded(boolean testNotAdding)
throws CompilationFailedException, CompilationException, IOException, ResourceException,
ExecutionException {
Path mergerInputZip = createMergerInputWithTwoClasses(OutputMode.DexIndexed, testNotAdding);
Marker inputMarker = ExtractMarker.extractMarkerFromDexFile(mergerInputZip);
assertEquals(inputMarker == null, testNotAdding);
Path mergerOutputZip = temp.getRoot().toPath().resolve("merger-out.zip");
DexFileMerger.main(
new String[] {
"--input", mergerInputZip.toString(), "--output", mergerOutputZip.toString()
});
Marker outputMarker = ExtractMarker.extractMarkerFromDexFile(mergerOutputZip);
assertEquals(outputMarker == null, testNotAdding);
}
@Test
public void testMarkerPreserved()
throws CompilationFailedException, CompilationException, IOException, ResourceException,
ExecutionException {
testMarkerPreservedOrNotAdded(false);
}
@Test
public void testMarkerNotAdded()
throws CompilationFailedException, CompilationException, IOException, ResourceException,
ExecutionException {
testMarkerPreservedOrNotAdded(true);
}
@Test
public void mergeTwoFiles() throws CompilationFailedException, CompilationException, IOException {
Path mergerInputZip = createMergerInputWithTwoClasses(OutputMode.DexFilePerClassFile, false);
Path mergerOutputZip = temp.getRoot().toPath().resolve("merger-out.zip");
DexFileMerger.main(
new String[] {
"--input", mergerInputZip.toString(), "--output", mergerOutputZip.toString()
});
// Test by running methods of Class1 and Class2
for (String className : new String[] {"Class1", "Class2"}) {
ArtCommandBuilder builder = new ArtCommandBuilder();
builder.appendClasspath(mergerOutputZip.toString());
builder.setMainClass("dexmergesample." + className);
String out = ToolHelper.runArt(builder);
assertEquals(out, className + "\n");
}
}
private void generateClassesAndTest(int extraMethodCount, int programResourcesSize)
throws IOException, ExecutionException, CompilationException, CompilationFailedException {
AndroidApp generatedApp =
MainDexListTests.generateApplication(
ImmutableList.of("A", "B"),
AndroidApiLevel.N.getLevel(),
MAX_METHOD_COUNT / 2 + 1 + extraMethodCount);
Path appDir = temp.newFolder().toPath().resolve("merger-input.zip");
assertEquals(programResourcesSize, generatedApp.getDexProgramResourcesForTesting().size());
generatedApp.write(appDir, OutputMode.DexIndexed);
Path outZip = temp.getRoot().toPath().resolve("out.zip");
DexFileMerger.run(
new String[] {
"--input", appDir.toString(), "--output", outZip.toString(), "--multidex=off"
});
}
@Test(expected = CompilationFailedException.class)
public void failIfTooBig()
throws IOException, ExecutionException, CompilationException, CompilationFailedException {
// Generates an application with two classes, each with the number of methods just enough not to
// fit into a single dex file.
generateClassesAndTest(1, 2);
}
@Test
public void failIfTooBigControl()
throws IOException, ExecutionException, CompilationException, CompilationFailedException {
// Control test for failIfTooBig to make sure we don't fail with less methods.
generateClassesAndTest(0, 1);
}
}