blob: bce2e8b55de2092c119cf3f1677dbc354d82258f [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.shaking;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.OutputMode;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.shaking.PrintUsageTest.PrintUsageInspector.ClassSubject;
import com.android.tools.r8.utils.ListUtils;
import com.google.common.collect.ImmutableList;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.stream.Stream;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class PrintUsageTest {
private static final String PRINT_USAGE_FILE_SUFFIX = "-print-usage.txt";
private final String test;
private final String programFile;
private final List<String> keepRulesFiles;
private final Consumer<PrintUsageInspector> inspection;
@Rule
public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
public PrintUsageTest(
String test,
List<String> keepRulesFiles,
Consumer<PrintUsageInspector> inspection) {
this.test = test;
this.programFile = ToolHelper.EXAMPLES_BUILD_DIR + test + ".jar";
this.keepRulesFiles = keepRulesFiles;
this.inspection = inspection;
}
@Before
public void runR8andGetPrintUsage() throws Exception {
Path out = temp.getRoot().toPath();
R8Command command =
ToolHelper.addProguardConfigurationConsumer(
R8Command.builder(),
pgConfig -> {
pgConfig.setPrintUsage(true);
pgConfig.setPrintUsageFile(out.resolve(test + PRINT_USAGE_FILE_SUFFIX));
})
.setOutput(out, OutputMode.DexIndexed)
.addProgramFiles(Paths.get(programFile))
.addProguardConfigurationFiles(ListUtils.map(keepRulesFiles, Paths::get))
.addLibraryFiles(ToolHelper.getDefaultAndroidJar())
.build();
ToolHelper.runR8(command, options -> {
// Disable inlining to make this test not depend on inlining decisions.
options.enableInlining = false;
});
}
@Test
public void printUsageTest() throws IOException, ExecutionException {
Path out = temp.getRoot().toPath();
Path printUsageFile = out.resolve(test + PRINT_USAGE_FILE_SUFFIX);
if (inspection != null) {
PrintUsageInspector inspector = new PrintUsageInspector(printUsageFile);
inspection.accept(inspector);
}
}
@Parameters(name = "test: {0} keep: {1}")
public static Collection<Object[]> data() {
List<String> tests = Arrays.asList(
"shaking1", "shaking2", "shaking4", "shaking8", "shaking9", "shaking12");
Map<String, Consumer<PrintUsageInspector>> inspections = new HashMap<>();
inspections.put("shaking1:keep-rules-printusage.txt", PrintUsageTest::inspectShaking1);
inspections.put("shaking2:keep-rules-printusage.txt", PrintUsageTest::inspectShaking2);
inspections.put("shaking4:keep-rules-printusage.txt", PrintUsageTest::inspectShaking4);
inspections.put("shaking8:keep-rules-printusage.txt", PrintUsageTest::inspectShaking8);
inspections.put("shaking9:keep-rules-printusage.txt", PrintUsageTest::inspectShaking9);
inspections.put("shaking12:keep-rules-printusage.txt", PrintUsageTest::inspectShaking12);
List<Object[]> testCases = new ArrayList<>();
Set<String> usedInspections = new HashSet<>();
for (String test : tests) {
File[] keepFiles = new File(ToolHelper.EXAMPLES_DIR + test)
.listFiles(file -> file.isFile() && file.getName().endsWith(".txt"));
for (File keepFile : keepFiles) {
String keepName = keepFile.getName();
Consumer<PrintUsageInspector> inspection =
getTestOptionalParameter(inspections, usedInspections, test, keepName);
if (inspection != null) {
testCases.add(new Object[]{test, ImmutableList.of(keepFile.getPath()), inspection});
}
}
}
assert usedInspections.size() == inspections.size();
return testCases;
}
private static <T> T getTestOptionalParameter(
Map<String, T> specifications, Set<String> usedSpecifications, String test, String keepName) {
T parameter = specifications.get(test);
if (parameter == null) {
parameter = specifications.get(test + ":" + keepName);
if (parameter != null) {
usedSpecifications.add(test + ":" + keepName);
}
} else {
usedSpecifications.add(test);
}
return parameter;
}
private static void inspectShaking1(PrintUsageInspector inspector) {
assertTrue(inspector.clazz("shaking1.Unused").isPresent());
assertTrue(inspector.clazz("shaking1.Used").isPresent());
ClassSubject used = inspector.clazz("shaking1.Used").get();
assertTrue(used.method("void", "<clinit>", ImmutableList.of()));
}
private static void inspectShaking2(PrintUsageInspector inspector) {
Optional<ClassSubject> staticFields = inspector.clazz("shaking2.StaticFields");
assertTrue(staticFields.isPresent());
assertTrue(staticFields.get().field("int", "completelyUnused"));
assertTrue(staticFields.get().field("int", "unused"));
Optional<ClassSubject> subClass1 = inspector.clazz("shaking2.SubClass1");
assertTrue(subClass1.isPresent());
assertTrue(subClass1.get().method("void", "unusedVirtualMethod", Collections.emptyList()));
Optional<ClassSubject> superClass = inspector.clazz("shaking2.SuperClass");
assertTrue(superClass.isPresent());
assertTrue(superClass.get().method("void", "unusedStaticMethod", Collections.emptyList()));
}
private static void inspectShaking4(PrintUsageInspector inspector) {
assertTrue(inspector.clazz("shaking4.Interface").isPresent());
}
private static void inspectShaking8(PrintUsageInspector inspector) {
Optional<ClassSubject> thing = inspector.clazz("shaking8.Thing");
assertTrue(thing.isPresent());
assertTrue(thing.get().field("int", "aField"));
assertFalse(inspector.clazz("shaking8.OtherThing").isPresent());
assertTrue(inspector.clazz("shaking8.YetAnotherThing").isPresent());
}
private static void inspectShaking9(PrintUsageInspector inspector) {
Optional<ClassSubject> superClass = inspector.clazz("shaking9.Superclass");
assertFalse(superClass.isPresent());
Optional<ClassSubject> subClass = inspector.clazz("shaking9.Subclass");
assertTrue(subClass.isPresent());
assertTrue(subClass.get().method("void", "aMethod", Collections.emptyList()));
assertFalse(subClass.get().method("void", "<init>", Collections.emptyList()));
}
private static void inspectShaking12(PrintUsageInspector inspector) {
assertFalse(inspector.clazz("shaking12.PeopleClass").isPresent());
Optional<ClassSubject> animal = inspector.clazz("shaking12.AnimalClass");
assertTrue(animal.isPresent());
assertTrue(animal.get().method("java.lang.String", "getName", Collections.emptyList()));
}
static class PrintUsageInspector {
private Map<String, ClassSubject> printedUsage;
PrintUsageInspector(Path printUsageFile) throws IOException {
printedUsage = new HashMap<>();
try (Stream<String> lines = Files.lines(printUsageFile)) {
lines.forEach(line -> {
if (line.startsWith(" ")) {
if (line.contains("(") && line.contains(")")) {
readMethod(line);
} else {
readField(line);
}
} else {
readClazz(line);
}
});
}
}
private ClassSubject lastClazz = null;
private void readClazz(String line) {
if (printedUsage.containsKey(line)) {
lastClazz = printedUsage.get(line);
} else {
lastClazz = new ClassSubject();
printedUsage.put(line, lastClazz);
}
}
private void readMethod(String line) {
assert lastClazz != null;
lastClazz.putMethod(line);
}
private void readField(String line) {
assert lastClazz != null;
lastClazz.putField(line);
}
public Optional<ClassSubject> clazz(String name) {
if (printedUsage.containsKey(name)) {
return Optional.of(printedUsage.get(name));
}
return Optional.empty();
}
static class ClassSubject {
private Set<String> methods;
private Set<String> fields;
public ClassSubject() {
methods = new HashSet<>();
fields = new HashSet<>();
}
void putMethod(String line) {
String[] tokens = line.split(" ");
assert tokens.length >= 2;
methods.add(tokens[tokens.length - 2] + " " + tokens[tokens.length - 1]);
}
void putField(String line) {
String[] tokens = line.split(" ");
assert tokens.length >= 2;
fields.add(tokens[tokens.length - 2] + " " + tokens[tokens.length - 1]);
}
public boolean method(String returnType, String name, List<String> parameters) {
StringBuilder builder = new StringBuilder();
builder.append(returnType).append(" ").append(name);
builder.append("(");
for (int i = 0; i < parameters.size(); i++) {
if (i != 0) {
builder.append(",");
}
builder.append(parameters.get(i));
}
builder.append(")");
return methods.contains(builder.toString());
}
public boolean field(String type, String name) {
return fields.contains(type + " " + name);
}
}
}
}