blob: 3c6aab68e5e68a060e7fbc8b93319f06e97c7021 [file] [log] [blame]
// Copyright (c) 2018, 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.classFiltering;
import com.android.tools.r8.ArchiveProgramResourceProvider;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.ProgramResource;
import com.android.tools.r8.ProgramResourceProvider;
import com.android.tools.r8.ResourceException;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class ClassFilteringTest extends TestBase {
private final TestParameters parameters;
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withDexRuntimes().withAllApiLevels().build();
}
public ClassFilteringTest(TestParameters parameters) {
this.parameters = parameters;
}
@Test
public void testNoFiltering() throws Exception {
List<Class<?>> input =
ImmutableList.of(TestClass.class, TestClass.Remove.class, TestClass.Keep.class);
// Run a test with normal providers, verify nothing is removed.
testForD8()
.addProgramClasses(input)
.setMinApi(parameters.getApiLevel())
.compile()
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput("Keep Remove ");
}
@Test
public void testFilterByChecksum() throws Exception {
// Step #1: Build the inputs with checksum encoded.
final Path output = testForD8()
.setMinApi(parameters.getApiLevel())
.addProgramClasses(TestClass.class, TestClass.Remove.class, TestClass.Keep.class)
.setIncludeClassesChecksum(true)
.compile()
.writeToZip();
// Step #2: "Re-compile" the output dex with a filter that removes a class by checksum.
// Remember the check some of Remove.class to search for in the ProgramResourceProvider
final long crc = ToolHelper.getClassByteCrc(TestClass.Remove.class);
testForD8()
.addProgramFiles(output)
.setMinApi(parameters.getApiLevel())
.apply(b -> b.getBuilder().setDexClassChecksumFilter(
(classDescriptor, checksum) -> !checksum.equals(crc)))
.compile()
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput("Keep No Remove ");
}
@Test
public void testDexMergingWithChecksum() throws Exception {
// Step #1: Build the dex file seperately as an incremental build tools usually do.
Set<Long> keepCrcs = Sets.newHashSet();
Path[] dexInput = new Path[] {
buildDex(TestClass.class,true, keepCrcs),
buildDex(TestClass.Keep.class,true, keepCrcs),
buildDex(TestClass.Remove.class, true, null)};
// Step #2: Now use D8 as a merging tool.
final Path merged = testForD8()
.setMinApi(parameters.getApiLevel())
.addProgramFiles(dexInput)
.setIncludeClassesChecksum(true)
.compile()
.writeToZip();
// Try it with and without checksum. Both should yield identical result.
testForD8()
.addProgramFiles(merged)
.setMinApi(parameters.getApiLevel())
.apply(b -> b.getBuilder().setDexClassChecksumFilter(
(classDescriptor, checksum) -> keepCrcs.contains(checksum)))
.compile()
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput("Keep No Remove ");
testForD8()
.addProgramFiles(merged)
.setMinApi(parameters.getApiLevel())
.apply(b -> b.getBuilder().setDexClassChecksumFilter(
(classDescriptor, checksum) -> keepCrcs.contains(checksum)))
.setIncludeClassesChecksum(true)
.compile()
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput("Keep No Remove ");
}
@Test
public void testDexMergingWithChecksumMissing() throws Exception {
// Step #1: Build the dex file seperately as an incremental build tools usually do but this time
// make one of the dex file missing checksum information.
Path[] dexInput = new Path[] {
buildDex(TestClass.class,true, null),
buildDex(TestClass.Keep.class,true, null),
buildDex(TestClass.Remove.class, false, null)};
// Step #2: Now use D8 as a merging tool and verify that the compilation fails as expect.
try {
testForD8()
.setMinApi(parameters.getApiLevel())
.addProgramFiles(dexInput)
.setIncludeClassesChecksum(true)
.compile()
.writeToZip();
Assert.fail("Compilation should fail.");
} catch (CompilationFailedException failure) {
Assert.assertTrue(failure.getCause().getMessage(), failure.getCause().getMessage().contains(
"has no checksum information while checksum encoding is requested"));
}
}
/*
@Test
public void testMultidexOutput()
throws CompilationFailedException, IOException, ExecutionException, ResourceException {
// Step #1: Build the program pretending to be multidex files with DexPerClass.
final Path outZip = testForD8()
.setMinApi(parameters.getApiLevel())
.addProgramClasses(TestClass.class, TestClass.Keep.class, TestClass.Remove.class)
.setIncludeClassesChecksum(true)
.setOutputMode(OutputMode.DexFilePerClass)
.compile()
.writeToZip();
final long crc = ToolHelper.getClassByteCrc(TestClass.Remove.class);
// Step #2: Verify that the checksums are present and filtering is working as expected.
ProgramResourceProvider filter = new ArchiveProvider(outZip) {
@Override
public boolean includeClassWithChecksum(String classDescriptor, Long checksum) {
return !checksum.equals(crc);
}
};
testForD8()
.addProgramResourceProvider(filter)
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput("Keep No Remove ");
}
*/
@Test
public void testLambdaChecksum() throws Exception {
final Path output = testForD8()
.setMinApi(parameters.getApiLevel())
.addProgramClasses(TestDesugar.class, TestDesugar.Consumer.class)
.setIncludeClassesChecksum(true)
.compile()
.writeToZip();
List<String> classesWithChecksum = Lists.newArrayList();
testForD8()
.addProgramFiles(output)
.setIncludeClassesChecksum(true)
.apply(b -> b.getBuilder().setDexClassChecksumFilter(
(classDescriptor, checksum) -> { classesWithChecksum.add(classDescriptor); return true; }))
.setMinApi(parameters.getApiLevel())
.compile()
.run(parameters.getRuntime(), TestDesugar.class)
.assertSuccessWithOutput("TestDesugar.consume");
// Make sure the provider saw all 3 checksums (class, innerclass and the lambda class).
Assert.assertEquals(3, classesWithChecksum.size());
}
/**
* Builds a given class into a dex file specified by output.
*
* @param checksum If true, the dex file will be encoded with checkout information.
* @param crcCollection If not null, add the CRC of the .class file to a collection after
* compilation.
* @return The CRC of the .class class file after compilation.
*/
private Path buildDex(Class c, boolean checksum, Collection<Long> crcCollection)
throws IOException, CompilationFailedException {
if (crcCollection != null) {
crcCollection.add(ToolHelper.getClassByteCrc(c));
}
return testForD8()
.setMinApi(parameters.getApiLevel())
.addProgramClasses(c)
.setIncludeClassesChecksum(checksum)
.compile()
.writeToZip();
}
}