blob: 6f609de417e79e995829422c0d82747914520722 [file] [log] [blame]
// Copyright (c) 2019, 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.dexsplitter;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertNotNull;
import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.ExtractMarker;
import com.android.tools.r8.FeatureSplit;
import com.android.tools.r8.ResourceException;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.dex.Marker;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import com.google.common.io.ByteStreams;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
import java.util.concurrent.ExecutionException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class R8FeatureSplitTest extends SplitterTestBase {
private static String EXPECTED = "Hello world";
@Parameters(name = "{0}")
public static TestParametersCollection params() {
return getTestParameters().withDexRuntimes().build();
}
private final TestParameters parameters;
public R8FeatureSplitTest(TestParameters parameters) {
this.parameters = parameters;
}
private static FeatureSplit emptySplitProvider(FeatureSplit.Builder builder) {
builder
.addProgramResourceProvider(ImmutableList::of)
.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
return builder.build();
}
@Test
public void simpleApiTest() throws CompilationFailedException, IOException, ExecutionException {
testForR8(parameters.getBackend())
.addProgramClasses(HelloWorld.class)
.setMinApi(parameters.getRuntime())
.addFeatureSplit(R8FeatureSplitTest::emptySplitProvider)
.addKeepMainRule(HelloWorld.class)
.compile()
.run(parameters.getRuntime(), HelloWorld.class)
.assertSuccessWithOutputLines(EXPECTED);
}
@Test
public void testTwoFeatures() throws CompilationFailedException, IOException, ExecutionException {
CompiledWithFeature compiledWithFeature = new CompiledWithFeature().invoke();
Path basePath = compiledWithFeature.getBasePath();
Path feature1Path = compiledWithFeature.getFeature1Path();
Path feature2Path = compiledWithFeature.getFeature2Path();
CodeInspector baseInspector = new CodeInspector(basePath);
assertTrue(baseInspector.clazz(BaseClass.class).isPresent());
CodeInspector feature1Inspector = new CodeInspector(feature1Path);
assertEquals(feature1Inspector.allClasses().size(), 1);
assertTrue(feature1Inspector.clazz(FeatureClass.class).isPresent());
CodeInspector feature2Inspector = new CodeInspector(feature2Path);
assertEquals(feature2Inspector.allClasses().size(), 1);
assertTrue(feature2Inspector.clazz(FeatureClass2.class).isPresent());
// Sanity check, we can't call the Feature from the base directly.
ProcessResult result =
runFeatureOnArt(BaseClass.class, basePath, feature1Path, parameters.getRuntime());
assertTrue(result.exitCode != 0);
result = runFeatureOnArt(FeatureClass.class, basePath, feature1Path, parameters.getRuntime());
assertEquals(result.exitCode, 0);
assertEquals(result.stdout, StringUtils.lines("Testing base", "Testing feature"));
result = runFeatureOnArt(FeatureClass2.class, basePath, feature2Path, parameters.getRuntime());
assertEquals(result.exitCode, 0);
assertEquals(result.stdout, StringUtils.lines("Testing second"));
}
@Test
public void testMarkerInFeatures()
throws IOException, CompilationFailedException, ExecutionException, ResourceException {
CompiledWithFeature compiledWithFeature = new CompiledWithFeature().invoke();
Path basePath = compiledWithFeature.getBasePath();
Path feature1Path = compiledWithFeature.getFeature1Path();
Path feature2Path = compiledWithFeature.getFeature2Path();
Collection<Marker> markers = ExtractMarker.extractMarkerFromDexFile(basePath);
Collection<Marker> feature1Markers = ExtractMarker.extractMarkerFromDexFile(feature1Path);
Collection<Marker> feature2Markers = ExtractMarker.extractMarkerFromDexFile(feature2Path);
assertEquals(markers.size(), 1);
assertEquals(feature1Markers.size(), 1);
assertEquals(feature2Markers.size(), 1);
assertEquals(markers.iterator().next(), feature1Markers.iterator().next());
assertEquals(markers.iterator().next(), feature2Markers.iterator().next());
}
@Test
public void testNonJavaPassThrough() throws IOException, CompilationFailedException {
Path basePath = temp.newFile("base.zip").toPath();
Path feature1Path = temp.newFile("feature1.zip").toPath();
Path feature2Path = temp.newFile("feature2.zip").toPath();
Collection<String> nonJavaFiles = ImmutableList.of("foobar", "barfoo");
testForR8(parameters.getBackend())
.addProgramClasses(BaseClass.class, RunInterface.class, SplitRunner.class)
.setMinApi(parameters.getRuntime())
.addFeatureSplit(
builder ->
splitWithNonJavaFile(
builder, feature1Path, temp, nonJavaFiles, true, FeatureClass.class))
.addFeatureSplit(
builder ->
splitWithNonJavaFile(
builder, feature2Path, temp, nonJavaFiles, true, FeatureClass2.class))
.addKeepAllClassesRule()
.compile()
.writeToZip(basePath);
for (Path feature : ImmutableList.of(feature1Path, feature2Path)) {
ZipFile zipFile = new ZipFile(feature.toFile());
for (String nonJavaFile : nonJavaFiles) {
ZipEntry entry = zipFile.getEntry(nonJavaFile);
assertNotNull(entry);
String content = new String(ByteStreams.toByteArray(zipFile.getInputStream(entry)));
assertEquals(content, nonJavaFile);
}
}
ZipFile zipFile = new ZipFile(basePath.toFile());
for (String nonJavaFile : nonJavaFiles) {
ZipEntry entry = zipFile.getEntry(nonJavaFile);
assertNull(entry);
}
}
@Test
public void testAdaptResourceContentInSplits() throws IOException, CompilationFailedException {
Path basePath = temp.newFile("base.zip").toPath();
Path feature1Path = temp.newFile("feature1.zip").toPath();
Path feature2Path = temp.newFile("feature2.zip").toPath();
// Make the content of the data resource be class names
Collection<String> nonJavaFiles =
ImmutableList.of(FeatureClass.class.getName(), FeatureClass2.class.getName());
testForR8(parameters.getBackend())
.addProgramClasses(BaseClass.class, RunInterface.class, SplitRunner.class)
.setMinApi(parameters.getRuntime())
.addFeatureSplit(
builder ->
splitWithNonJavaFile(
builder, feature1Path, temp, nonJavaFiles, false, FeatureClass.class))
.addFeatureSplit(
builder ->
splitWithNonJavaFile(
builder, feature2Path, temp, nonJavaFiles, false, FeatureClass2.class))
.addKeepClassRulesWithAllowObfuscation(
BaseClass.class, FeatureClass.class, FeatureClass2.class)
.addKeepRules("-adaptresourcefilecontents")
.compile()
.writeToZip(basePath);
for (Path feature : ImmutableList.of(feature1Path, feature2Path)) {
ZipFile zipFile = new ZipFile(feature.toFile());
for (String nonJavaFile : nonJavaFiles) {
ZipEntry entry = zipFile.getEntry(nonJavaFile);
assertNotNull(entry);
String content = new String(ByteStreams.toByteArray(zipFile.getInputStream(entry)));
assertNotEquals(content, nonJavaFile);
}
}
ZipFile zipFile = new ZipFile(basePath.toFile());
for (String nonJavaFile : nonJavaFiles) {
ZipEntry entry = zipFile.getEntry(nonJavaFile);
assertNull(entry);
}
}
public static class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello world");
}
}
public static class BaseClass implements RunInterface {
@Override
public void run() {
new FeatureClass().test();
}
public void test() {
System.out.println("Testing base");
}
}
public static class FeatureClass implements RunInterface {
public void test() {
System.out.println("Testing feature");
}
@Override
public void run() {
new BaseClass().test();
test();
}
}
public static class FeatureClass2 implements RunInterface {
public void test() {
System.out.println("Testing second");
}
@Override
public void run() {
test();
}
}
private class CompiledWithFeature {
private Path basePath;
private Path feature1Path;
private Path feature2Path;
public Path getBasePath() {
return basePath;
}
public Path getFeature1Path() {
return feature1Path;
}
public Path getFeature2Path() {
return feature2Path;
}
public CompiledWithFeature invoke() throws IOException, CompilationFailedException {
basePath = temp.newFile("base.zip").toPath();
feature1Path = temp.newFile("feature1.zip").toPath();
feature2Path = temp.newFile("feature2.zip").toPath();
testForR8(parameters.getBackend())
.addProgramClasses(BaseClass.class, RunInterface.class, SplitRunner.class)
.setMinApi(parameters.getRuntime())
.addFeatureSplit(
builder -> simpleSplitProvider(builder, feature1Path, temp, FeatureClass.class))
.addFeatureSplit(
builder -> simpleSplitProvider(builder, feature2Path, temp, FeatureClass2.class))
.addKeepAllClassesRule()
.compile()
.writeToZip(basePath);
return this;
}
}
}