| // 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); |
| } |
| } |
| } |
| } |