blob: 489d8b2efa55ab68ef023831a5e5560a3ec280c5 [file] [log] [blame]
// Copyright (c) 2020, 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.tracereferences;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.DiagnosticsChecker;
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.android.tools.r8.origin.Origin;
import com.android.tools.r8.tracereferences.TraceReferencesCommandParser.OutputFormat;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.ZipUtils;
import com.google.common.collect.ImmutableList;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import kotlin.text.Charsets;
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 TraceReferencesCommandTest extends TestBase {
@Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withNoneRuntime().build();
}
public TraceReferencesCommandTest(TestParameters parameters) {}
@Test
public void emptyBuilder() throws Throwable {
verifyEmptyCommand(TraceReferencesCommand.builder().build());
}
private void verifyEmptyCommand(TraceReferencesCommand command) {
assertEquals(0, command.getLibrary().size());
assertEquals(0, command.getTarget().size());
assertEquals(0, command.getSource().size());
assertEquals(TraceReferencesCommandParser.OutputFormat.PRINTUSAGE, command.getOutputFormat());
assertNull(command.getOutput());
}
@Test(expected = CompilationFailedException.class)
public void emptyRun() throws Throwable {
DiagnosticsChecker.checkErrorsContains(
"No library specified",
handler -> {
TraceReferences.run(TraceReferencesCommand.builder(handler).build());
});
}
@Test(expected = CompilationFailedException.class)
public void emptyRunCommandLine() throws Throwable {
DiagnosticsChecker.checkErrorsContains(
"No library specified",
handler -> {
TraceReferences.run(
TraceReferencesCommand.parse(new String[] {""}, Origin.unknown(), handler).build());
});
}
@Test(expected = CompilationFailedException.class)
public void onlyLibrarySpecified() throws Throwable {
DiagnosticsChecker.checkErrorsContains(
"No target specified",
handler -> {
TraceReferences.run(
TraceReferencesCommand.builder(handler)
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
.build());
});
}
@Test(expected = CompilationFailedException.class)
public void onlyLibrarySpecifiedCommandLine() throws Throwable {
DiagnosticsChecker.checkErrorsContains(
"No target specified",
handler -> {
TraceReferences.run(
TraceReferencesCommand.parse(
new String[] {
"--lib", ToolHelper.getAndroidJar(AndroidApiLevel.P).toString()
},
Origin.unknown(),
handler)
.build());
});
}
private String formatName(OutputFormat format) {
if (format == TraceReferencesCommandParser.OutputFormat.PRINTUSAGE) {
return "printuses";
}
if (format == TraceReferencesCommandParser.OutputFormat.KEEP_RULES) {
return "keep";
}
assertSame(format, TraceReferencesCommandParser.OutputFormat.KEEP_RULES_WITH_ALLOWOBFUSCATION);
return "keepallowobfuscation";
}
public void runAndCheckOutput(
Path targetJar, Path sourceJar, OutputFormat format, String expected) throws Throwable {
Path dir = temp.newFolder().toPath();
Path output = dir.resolve("output.txt");
TraceReferences.run(
TraceReferencesCommand.builder()
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
.addTargetFiles(targetJar)
.addSourceFiles(sourceJar)
.setOutputPath(output)
.setOutputFormat(format)
.build());
assertEquals(expected, FileUtils.readTextFile(output, Charsets.UTF_8));
TraceReferences.run(
TraceReferencesCommand.parse(
new String[] {
"--lib", ToolHelper.getAndroidJar(AndroidApiLevel.P).toString(),
"--target", targetJar.toString(),
"--source", sourceJar.toString(),
"--output", output.toString(),
"--format", formatName(format)
},
Origin.unknown())
.build());
assertEquals(expected, FileUtils.readTextFile(output, Charsets.UTF_8));
}
public void runAndCheckOutput(
List<Class<?>> targetClasses,
List<Class<?>> sourceClasses,
OutputFormat format,
String expected)
throws Throwable {
Path dir = temp.newFolder().toPath();
Path targetJar = dir.resolve("target.jar");
Path sourceJar = dir.resolve("source.jar");
ZipUtils.zip(
targetJar,
ToolHelper.getClassPathForTests(),
targetClasses.stream()
.map(ToolHelper::getClassFileForTestClass)
.collect(Collectors.toList()));
ZipUtils.zip(
sourceJar,
ToolHelper.getClassPathForTests(),
sourceClasses.stream()
.map(ToolHelper::getClassFileForTestClass)
.collect(Collectors.toList()));
runAndCheckOutput(targetJar, sourceJar, format, expected);
}
@Test
public void test_printUses() throws Throwable {
runAndCheckOutput(
ImmutableList.of(Target.class),
ImmutableList.of(Source.class),
TraceReferencesCommandParser.OutputFormat.PRINTUSAGE,
StringUtils.lines(
ImmutableList.of(
"com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target",
"com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target: void"
+ " target(int)",
"com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target: int"
+ " field")));
}
@Test
public void test_keepRules() throws Throwable {
runAndCheckOutput(
ImmutableList.of(Target.class),
ImmutableList.of(Source.class),
TraceReferencesCommandParser.OutputFormat.KEEP_RULES,
StringUtils.lines(
ImmutableList.of(
"-keep class com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target"
+ " {",
" public static void target(int);",
" int field;",
"}",
"-keeppackagenames com.android.tools.r8.tracereferences")));
}
@Test
public void test_keepRulesAllowObfuscation() throws Throwable {
runAndCheckOutput(
ImmutableList.of(Target.class),
ImmutableList.of(Source.class),
TraceReferencesCommandParser.OutputFormat.KEEP_RULES_WITH_ALLOWOBFUSCATION,
StringUtils.lines(
ImmutableList.of(
"-keep,allowobfuscation class"
+ " com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target {",
" public static void target(int);",
" int field;",
"}",
"-keeppackagenames com.android.tools.r8.tracereferences")));
}
@Test
public void testNoReferences_printUses() throws Throwable {
runAndCheckOutput(
ImmutableList.of(OtherTarget.class),
ImmutableList.of(Source.class),
TraceReferencesCommandParser.OutputFormat.PRINTUSAGE,
StringUtils.lines(ImmutableList.of()));
}
@Test
public void testMissingReference_keepRules() throws Throwable {
runAndCheckOutput(
ImmutableList.of(OtherTarget.class),
ImmutableList.of(Source.class),
TraceReferencesCommandParser.OutputFormat.KEEP_RULES,
StringUtils.lines(ImmutableList.of()));
}
@Test
public void testNoReferences_keepRulesAllowObfuscation() throws Throwable {
runAndCheckOutput(
ImmutableList.of(OtherTarget.class),
ImmutableList.of(Source.class),
TraceReferencesCommandParser.OutputFormat.KEEP_RULES_WITH_ALLOWOBFUSCATION,
StringUtils.lines(ImmutableList.of()));
}
public static void zip(Path zipFile, String path, byte[] data) throws IOException {
try (ZipOutputStream stream =
new ZipOutputStream(new BufferedOutputStream(Files.newOutputStream(zipFile)))) {
ZipEntry zipEntry = new ZipEntry(path);
stream.putNextEntry(zipEntry);
stream.write(data);
stream.closeEntry();
}
}
@Test
public void testMissingDefinition_printUses() throws Throwable {
Path dir = temp.newFolder().toPath();
Path targetJar = dir.resolve("target.jar");
Path sourceJar = dir.resolve("source.jar");
zip(targetJar, DescriptorUtils.getPathFromJavaType(Target.class), getClassWithTargetRemoved());
ZipUtils.zip(
sourceJar,
ToolHelper.getClassPathForTests(),
ToolHelper.getClassFileForTestClass(Source.class));
runAndCheckOutput(
targetJar,
sourceJar,
TraceReferencesCommandParser.OutputFormat.PRINTUSAGE,
StringUtils.lines(
ImmutableList.of(
"com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target",
"# Error: Could not find definition for method void"
+ " com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target"
+ ".target(int)",
"# Error: Could not find definition for field int"
+ " com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target"
+ ".field")));
}
@Test
public void testMissingDefinition_keepRules() throws Throwable {
Path dir = temp.newFolder().toPath();
Path targetJar = dir.resolve("target.jar");
Path sourceJar = dir.resolve("source.jar");
zip(targetJar, DescriptorUtils.getPathFromJavaType(Target.class), getClassWithTargetRemoved());
ZipUtils.zip(
sourceJar,
ToolHelper.getClassPathForTests(),
ToolHelper.getClassFileForTestClass(Source.class));
runAndCheckOutput(
targetJar,
sourceJar,
TraceReferencesCommandParser.OutputFormat.KEEP_RULES,
StringUtils.lines(
ImmutableList.of(
"-keep class com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target"
+ " {",
"# Error: Could not find definition for method void"
+ " com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target"
+ ".target(int)",
"# Error: Could not find definition for field int"
+ " com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target"
+ ".field",
"}",
"-keeppackagenames com.android.tools.r8.tracereferences")));
}
@Test
public void testMissingDefinition_keepRulesAllowObfuscation() throws Throwable {
Path dir = temp.newFolder().toPath();
Path targetJar = dir.resolve("target.jar");
Path sourceJar = dir.resolve("source.jar");
zip(targetJar, DescriptorUtils.getPathFromJavaType(Target.class), getClassWithTargetRemoved());
ZipUtils.zip(
sourceJar,
ToolHelper.getClassPathForTests(),
ToolHelper.getClassFileForTestClass(Source.class));
runAndCheckOutput(
targetJar,
sourceJar,
TraceReferencesCommandParser.OutputFormat.KEEP_RULES_WITH_ALLOWOBFUSCATION,
StringUtils.lines(
ImmutableList.of(
"-keep,allowobfuscation class"
+ " com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target {",
"# Error: Could not find definition for method void"
+ " com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target.target(int)",
"# Error: Could not find definition for field int"
+ " com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target.field",
"}",
"-keeppackagenames com.android.tools.r8.tracereferences")));
}
private byte[] getClassWithTargetRemoved() throws IOException {
return transformer(Target.class)
.removeMethods((access, name, descriptor, signature, exceptions) -> name.equals("target"))
.removeFields((access, name, descriptor, signature, value) -> name.equals("field"))
.transform();
}
static class Target {
public static int field;
public static void target(int i) {}
}
static class OtherTarget {
public static void target() {}
}
static class Source {
public static void source() {
Target.target(Target.field);
}
}
}