blob: 8bb9f304d2c03536a0e2487f88773a7016d5ff52 [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;
import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
import static com.android.tools.r8.MarkerMatcher.assertMarkersMatch;
import static com.android.tools.r8.MarkerMatcher.markerTool;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
import com.android.tools.r8.AssertionsConfiguration.AssertionTransformationScope;
import com.android.tools.r8.StringConsumer.FileConsumer;
import com.android.tools.r8.dex.Marker.Tool;
import com.android.tools.r8.origin.EmbeddedOrigin;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ThreadUtils;
import com.google.common.collect.ImmutableList;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
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 L8CommandTest extends CommandTestBase<L8Command> {
@Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withNoneRuntime().build();
}
public L8CommandTest(TestParameters parameters) {
parameters.assertNoneRuntime();
}
@Test(expected = CompilationFailedException.class)
public void emptyBuilder() throws Throwable {
verifyEmptyCommand(L8Command.builder().build());
}
@Test
public void emptyCommand() throws Throwable {
verifyEmptyCommand(
L8Command.builder()
.setProgramConsumer(DexIndexedConsumer.emptyConsumer())
.addDesugaredLibraryConfiguration(
StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting()))
.build());
}
private void verifyEmptyCommand(L8Command command) {
BaseCompilerCommand compilationCommand =
command.getD8Command() == null ? command.getR8Command() : command.getD8Command();
assertNotNull(compilationCommand);
assertTrue(command.getProgramConsumer() instanceof ClassFileConsumer);
assertTrue(compilationCommand.getProgramConsumer() instanceof DexIndexedConsumer);
}
@Test
public void testDexMarker() throws Throwable {
Path output = temp.newFolder().toPath().resolve("desugar_jdk_libs.zip");
L8.run(
L8Command.builder()
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
.addProgramFiles(ToolHelper.getDesugarJDKLibs())
.setMinApiLevel(20)
.addDesugaredLibraryConfiguration(
StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting()))
.setOutput(output, OutputMode.DexIndexed)
.build());
assertMarkersMatch(
ExtractMarker.extractMarkerFromDexFile(output),
ImmutableList.of(markerTool(Tool.L8), markerTool(Tool.D8)));
}
@Test
public void testClassFileMarker() throws Throwable {
Path output = temp.newFolder().toPath().resolve("desugar_jdk_libs.zip");
L8.run(
L8Command.builder()
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
.addProgramFiles(ToolHelper.getDesugarJDKLibs())
.setMinApiLevel(20)
.addDesugaredLibraryConfiguration(
StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting()))
.setOutput(output, OutputMode.ClassFile)
.build());
assertMarkersMatch(ExtractMarker.extractMarkerFromDexFile(output), markerTool(Tool.L8));
}
@Test
public void testDexMarkerCommandLine() throws Throwable {
Path output = temp.newFolder().toPath().resolve("desugar_jdk_libs.zip");
L8Command l8Command =
parse(
ToolHelper.getDesugarJDKLibs().toString(),
"--lib",
ToolHelper.getAndroidJar(AndroidApiLevel.P).toString(),
"--min-api",
"20",
"--desugared-lib",
ToolHelper.getDesugarLibJsonForTesting().toString(),
"--output",
output.toString());
L8.run(l8Command);
assertMarkersMatch(
ExtractMarker.extractMarkerFromDexFile(output),
ImmutableList.of(markerTool(Tool.L8), markerTool(Tool.D8)));
}
@Test
public void testClassFileMarkerCommandLine() throws Throwable {
Path output = temp.newFolder().toPath().resolve("desugar_jdk_libs.zip");
L8Command l8Command =
parse(
ToolHelper.getDesugarJDKLibs().toString(),
"--lib",
ToolHelper.getAndroidJar(AndroidApiLevel.P).toString(),
"--min-api",
"20",
"--desugared-lib",
ToolHelper.getDesugarLibJsonForTesting().toString(),
"--output",
output.toString(),
"--classfile");
L8.run(l8Command);
assertMarkersMatch(ExtractMarker.extractMarkerFromDexFile(output), markerTool(Tool.L8));
}
@Test
public void testFlagPgConf() throws Exception {
TestDiagnosticMessagesImpl diagnostics = new TestDiagnosticMessagesImpl();
Path pgconf = temp.newFolder().toPath().resolve("pg.conf");
FileUtils.writeTextFile(pgconf, "");
parse(
diagnostics,
"--desugared-lib",
ToolHelper.getDesugarLibJsonForTesting().toString(),
"--pg-conf",
pgconf.toString());
}
@Test
public void testFlagPgConfWithConsumer() throws Exception {
TestDiagnosticMessagesImpl diagnostics = new TestDiagnosticMessagesImpl();
Path pgconf = temp.newFolder().toPath().resolve("pg.conf");
Path pgMap = temp.newFolder().toPath().resolve("pg-map.txt");
FileUtils.writeTextFile(pgconf, "");
L8Command parsedCommand =
parse(
diagnostics,
"--desugared-lib",
ToolHelper.getDesugarLibJsonForTesting().toString(),
"--pg-conf",
pgconf.toString(),
"--pg-map-output",
pgMap.toString());
assertNotNull(parsedCommand.getR8Command());
InternalOptions internalOptions = parsedCommand.getR8Command().getInternalOptions();
assertNotNull(internalOptions);
assertTrue(internalOptions.proguardMapConsumer instanceof StringConsumer.FileConsumer);
FileConsumer proguardMapConsumer = (FileConsumer) internalOptions.proguardMapConsumer;
assertEquals(pgMap, proguardMapConsumer.getOutputPath());
}
@Test
public void testFlagPgConfWithClassFile() throws Exception {
TestDiagnosticMessagesImpl diagnostics = new TestDiagnosticMessagesImpl();
try {
Path pgconf = temp.newFolder().toPath().resolve("pg.conf");
FileUtils.writeTextFile(pgconf, "");
parse(
diagnostics,
"--desugared-lib",
ToolHelper.getDesugarLibJsonForTesting().toString(),
"--pg-conf",
pgconf.toString(),
"--classfile");
fail("Expected failure");
} catch (CompilationFailedException e) {
diagnostics.assertErrorsMatch(
diagnosticMessage(containsString("not support shrinking when generating class files")));
}
}
@Test
public void testFlagPgConfMissingParameter() {
TestDiagnosticMessagesImpl diagnostics = new TestDiagnosticMessagesImpl();
try {
parse(
diagnostics,
"--desugared-lib",
ToolHelper.getDesugarLibJsonForTesting().toString(),
"--pg-conf");
fail("Expected parse error");
} catch (CompilationFailedException e) {
diagnostics.assertErrorsMatch(diagnosticMessage(containsString("Missing parameter")));
}
}
private L8Command.Builder prepareBuilder(DiagnosticsHandler handler) {
return L8Command.builder(handler)
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
.addProgramFiles(ToolHelper.getDesugarJDKLibs())
.setMinApiLevel(20);
}
@Test(expected = CompilationFailedException.class)
public void mainDexListNotSupported() throws Throwable {
Path mainDexList = temp.newFile("main-dex-list.txt").toPath();
DiagnosticsChecker.checkErrorsContains(
"L8 does not support a main dex list",
(handler) ->
prepareBuilder(handler)
.setProgramConsumer(DexIndexedConsumer.emptyConsumer())
.addMainDexListFiles(mainDexList)
.build());
}
@Test(expected = CompilationFailedException.class)
public void dexPerClassNotSupported() throws Throwable {
DiagnosticsChecker.checkErrorsContains(
"L8 does not support compiling to dex per class",
(handler) ->
prepareBuilder(handler)
.setProgramConsumer(DexFilePerClassFileConsumer.emptyConsumer())
.build());
}
@Test(expected = CompilationFailedException.class)
public void desugaredLibraryConfigurationRequired() throws Throwable {
DiagnosticsChecker.checkErrorsContains(
"L8 requires a desugared library configuration",
(handler) ->
prepareBuilder(handler).setProgramConsumer(ClassFileConsumer.emptyConsumer()).build());
}
@Test(expected = CompilationFailedException.class)
public void proguardMapConsumerNotShrinking() throws Throwable {
DiagnosticsChecker.checkErrorsContains(
"L8 does not support defining a map consumer when not shrinking",
(handler) ->
prepareBuilder(handler)
.setProgramConsumer(DexIndexedConsumer.emptyConsumer())
.setProguardMapConsumer(StringConsumer.emptyConsumer())
.build());
}
@Test(expected = CompilationFailedException.class)
public void proguardMapOutputNotShrinking() throws Throwable {
Path pgMapOut = temp.newFile("pg-out.txt").toPath();
DiagnosticsChecker.checkErrorsContains(
"L8 does not support defining a map consumer when not shrinking",
(handler) ->
prepareBuilder(handler)
.setProgramConsumer(DexIndexedConsumer.emptyConsumer())
.setProguardMapOutputPath(pgMapOut)
.build());
}
private void addProguardConfigurationString(
DiagnosticsHandler diagnostics, ProgramConsumer programConsumer) throws Throwable {
String keepRule = "-keep class java.time.*";
List<String> keepRules = new ArrayList<>();
keepRules.add(keepRule);
L8Command.Builder builder =
prepareBuilder(diagnostics)
.setProgramConsumer(programConsumer)
.addDesugaredLibraryConfiguration(
StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting()))
.addProguardConfiguration(keepRules, Origin.unknown());
assertTrue(builder.isShrinking());
assertNotNull(builder.build().getR8Command());
}
@Test
public void addProguardConfigurationStringWithDex() throws Throwable {
addProguardConfigurationString(
new TestDiagnosticMessagesImpl(), DexIndexedConsumer.emptyConsumer());
}
@Test
public void addProguardConfigurationStringWithClassFile() throws Throwable {
TestDiagnosticMessagesImpl diagnostics = new TestDiagnosticMessagesImpl();
try {
addProguardConfigurationString(diagnostics, ClassFileConsumer.emptyConsumer());
fail("Expected failure");
} catch (CompilationFailedException e) {
diagnostics.assertErrorsMatch(
diagnosticMessage(containsString("not support shrinking when generating class files")));
}
}
private void addProguardConfigurationFile(
DiagnosticsHandler diagnostics, ProgramConsumer programConsumer) throws Throwable {
String keepRule = "-keep class java.time.*";
Path keepRuleFile = temp.newFile("keepRuleFile.txt").toPath();
Files.write(keepRuleFile, Collections.singletonList(keepRule), StandardCharsets.UTF_8);
L8Command.Builder builder1 =
prepareBuilder(diagnostics)
.setProgramConsumer(programConsumer)
.addDesugaredLibraryConfiguration(
StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting()))
.addProguardConfigurationFiles(keepRuleFile);
assertTrue(builder1.isShrinking());
assertNotNull(builder1.build().getR8Command());
List<Path> keepRuleFiles = new ArrayList<>();
keepRuleFiles.add(keepRuleFile);
L8Command.Builder builder2 =
prepareBuilder(new TestDiagnosticMessagesImpl())
.setProgramConsumer(DexIndexedConsumer.emptyConsumer())
.addDesugaredLibraryConfiguration(
StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting()))
.addProguardConfigurationFiles(keepRuleFiles);
assertTrue(builder2.isShrinking());
assertNotNull(builder2.build().getR8Command());
}
@Test
public void addProguardConfigurationFileDex() throws Throwable {
addProguardConfigurationFile(
new TestDiagnosticMessagesImpl(), DexIndexedConsumer.emptyConsumer());
}
@Test
public void addProguardConfigurationFileClassFile() throws Throwable {
TestDiagnosticMessagesImpl diagnostics = new TestDiagnosticMessagesImpl();
try {
addProguardConfigurationFile(diagnostics, ClassFileConsumer.emptyConsumer());
fail("Expected failure");
} catch (CompilationFailedException e) {
diagnostics.assertErrorsMatch(
diagnosticMessage(containsString("not support shrinking when generating class files")));
}
}
@Test
public void desugaredLibrary() throws CompilationFailedException {
L8Command l8Command =
parse("--desugared-lib", ToolHelper.getDesugarLibJsonForTesting().toString());
assertFalse(
l8Command.getInternalOptions().desugaredLibraryConfiguration.getRewritePrefix().isEmpty());
}
private void checkSingleForceAllAssertion(
List<AssertionsConfiguration> entries, AssertionTransformation transformation) {
assertEquals(1, entries.size());
assertEquals(transformation, entries.get(0).getTransformation());
assertEquals(AssertionTransformationScope.ALL, entries.get(0).getScope());
}
private void checkSingleForceClassAndPackageAssertion(
List<AssertionsConfiguration> entries, AssertionTransformation transformation) {
assertEquals(2, entries.size());
assertEquals(transformation, entries.get(0).getTransformation());
assertEquals(AssertionTransformationScope.CLASS, entries.get(0).getScope());
assertEquals("ClassName", entries.get(0).getValue());
assertEquals(transformation, entries.get(1).getTransformation());
assertEquals(AssertionTransformationScope.PACKAGE, entries.get(1).getScope());
assertEquals("PackageName", entries.get(1).getValue());
}
@Test
public void forceAssertionOption() throws Exception {
checkSingleForceAllAssertion(
parse(
"--force-enable-assertions",
"--desugared-lib",
ToolHelper.getDesugarLibJsonForTesting().toString())
.getAssertionsConfiguration(),
AssertionTransformation.ENABLE);
checkSingleForceAllAssertion(
parse(
"--force-disable-assertions",
"--desugared-lib",
ToolHelper.getDesugarLibJsonForTesting().toString())
.getAssertionsConfiguration(),
AssertionTransformation.DISABLE);
checkSingleForceAllAssertion(
parse(
"--force-passthrough-assertions",
"--desugared-lib",
ToolHelper.getDesugarLibJsonForTesting().toString())
.getAssertionsConfiguration(),
AssertionTransformation.PASSTHROUGH);
checkSingleForceClassAndPackageAssertion(
parse(
"--force-enable-assertions:ClassName",
"--force-enable-assertions:PackageName...",
"--desugared-lib",
ToolHelper.getDesugarLibJsonForTesting().toString())
.getAssertionsConfiguration(),
AssertionTransformation.ENABLE);
checkSingleForceClassAndPackageAssertion(
parse(
"--force-disable-assertions:ClassName",
"--force-disable-assertions:PackageName...",
"--desugared-lib",
ToolHelper.getDesugarLibJsonForTesting().toString())
.getAssertionsConfiguration(),
AssertionTransformation.DISABLE);
checkSingleForceClassAndPackageAssertion(
parse(
"--force-passthrough-assertions:ClassName",
"--force-passthrough-assertions:PackageName...",
"--desugared-lib",
ToolHelper.getDesugarLibJsonForTesting().toString())
.getAssertionsConfiguration(),
AssertionTransformation.PASSTHROUGH);
}
@Test
public void numThreadsOption() throws Exception {
assertEquals(
ThreadUtils.NOT_SPECIFIED,
parse("--desugared-lib", ToolHelper.getDesugarLibJsonForTesting().toString())
.getThreadCount());
assertEquals(
1,
parse(
"--thread-count",
"1",
"--desugared-lib",
ToolHelper.getDesugarLibJsonForTesting().toString())
.getThreadCount());
assertEquals(
2,
parse(
"--thread-count",
"2",
"--desugared-lib",
ToolHelper.getDesugarLibJsonForTesting().toString())
.getThreadCount());
assertEquals(
10,
parse(
"--thread-count",
"10",
"--desugared-lib",
ToolHelper.getDesugarLibJsonForTesting().toString())
.getThreadCount());
}
private void numThreadsOptionInvalid(String value) throws Exception {
final String expectedErrorContains = "Invalid argument to --thread-count";
try {
DiagnosticsChecker.checkErrorsContains(
expectedErrorContains,
handler ->
parse(
handler,
"--thread-count",
value,
"--desugared-lib",
ToolHelper.getDesugarLibJsonForTesting().toString()));
fail("Expected failure");
} catch (CompilationFailedException e) {
// Expected.
}
}
@Test
public void numThreadsOptionInvalid() throws Exception {
numThreadsOptionInvalid("0");
numThreadsOptionInvalid("-1");
numThreadsOptionInvalid("two");
}
@Override
String[] requiredArgsForTest() {
return new String[] {"--desugared-lib", ToolHelper.getDesugarLibJsonForTesting().toString()};
}
@Override
L8Command parse(String... args) throws CompilationFailedException {
return L8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
}
@Override
L8Command parse(DiagnosticsHandler handler, String... args) throws CompilationFailedException {
return L8Command.parse(args, EmbeddedOrigin.INSTANCE, handler).build();
}
}