blob: 149c3c36dc63a90325972daf73650f42051258b4 [file] [log] [blame]
// Copyright (c) 2016, 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.ToolHelper.EXAMPLES_BUILD_DIR;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertSame;
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.ProgramResource.Kind;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.dex.Marker;
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.ThreadUtils;
import com.android.tools.r8.utils.ZipUtils;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
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 R8CommandTest extends TestBase {
@Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withNoneRuntime().build();
}
public R8CommandTest(TestParameters parameters) {}
@Test(expected = CompilationFailedException.class)
public void emptyBuilder() throws Throwable {
// The builder must have a program consumer.
R8Command.builder().build();
}
@Test
public void emptyCommand() throws Throwable {
verifyEmptyCommand(
// In the API we must set a consumer.
R8Command.builder().setProgramConsumer(DexIndexedConsumer.emptyConsumer()).build());
verifyEmptyCommand(parse());
verifyEmptyCommand(parse(""));
verifyEmptyCommand(parse("", ""));
verifyEmptyCommand(parse(" "));
verifyEmptyCommand(parse(" ", " "));
verifyEmptyCommand(parse("\t"));
verifyEmptyCommand(parse("\t", "\t"));
}
private void verifyEmptyCommand(R8Command command) throws Throwable {
assertEquals(0, ToolHelper.getApp(command).getDexProgramResourcesForTesting().size());
assertEquals(0, ToolHelper.getApp(command).getClassProgramResourcesForTesting().size());
assertTrue(command.getEnableMinification());
assertTrue(command.getEnableTreeShaking());
assertEquals(CompilationMode.RELEASE, command.getMode());
assertTrue(command.getProgramConsumer() instanceof DexIndexedConsumer);
}
@Test(expected = CompilationFailedException.class)
public void disallowDexFilePerClassFileBuilder() throws Throwable {
R8Command.builder().setProgramConsumer(DexFilePerClassFileConsumer.emptyConsumer()).build();
}
@Test
public void allowClassFileConsumer() throws Throwable {
assertTrue(
R8Command.builder()
.setProgramConsumer(ClassFileConsumer.emptyConsumer())
.build()
.getProgramConsumer()
instanceof ClassFileConsumer);
}
@Test
public void desugaredLibraryKeepRuleConsumer() throws Exception {
StringConsumer stringConsumer = StringConsumer.emptyConsumer();
D8Command command =
D8Command.builder()
.setProgramConsumer(DexIndexedConsumer.emptyConsumer())
.setDesugaredLibraryKeepRuleConsumer(stringConsumer)
.build();
assertSame(command.getInternalOptions().desugaredLibraryKeepRuleConsumer, stringConsumer);
}
@Test
public void defaultOutIsCwd() throws Throwable {
Path working = temp.getRoot().toPath();
Path input = Paths.get(EXAMPLES_BUILD_DIR, "arithmetic.jar").toAbsolutePath();
Path library = ToolHelper.getDefaultAndroidJar();
Path output = working.resolve("classes.dex");
assertFalse(Files.exists(output));
ProcessResult result =
ToolHelper.forkR8(
working,
input.toString(),
"--lib",
library.toAbsolutePath().toString(),
"--no-tree-shaking");
assertEquals("R8 run failed: " + result.stderr, 0, result.exitCode);
assertTrue(Files.exists(output));
}
@Test
public void passFeatureSplit() throws Throwable {
Path working = temp.getRoot().toPath();
Path input = Paths.get(EXAMPLES_BUILD_DIR, "arithmetic.jar").toAbsolutePath();
Path inputFeature = Paths.get(EXAMPLES_BUILD_DIR, "arrayaccess.jar").toAbsolutePath();
Path library = ToolHelper.getDefaultAndroidJar();
Path output = working.resolve("classes.dex");
Path featureOutput = working.resolve("feature.zip");
assertFalse(Files.exists(output));
assertFalse(Files.exists(featureOutput));
ProcessResult result =
ToolHelper.forkR8(
working,
input.toString(),
"--lib",
library.toAbsolutePath().toString(),
"--feature",
inputFeature.toAbsolutePath().toString(),
featureOutput.toAbsolutePath().toString(),
"--no-tree-shaking");
assertEquals("R8 run failed: " + result.stderr, 0, result.exitCode);
assertTrue(Files.exists(output));
assertTrue(Files.exists(featureOutput));
}
@Test
public void featureOnlyOneArgument() throws Throwable {
Path working = temp.getRoot().toPath();
Path input = Paths.get(EXAMPLES_BUILD_DIR, "arithmetic.jar").toAbsolutePath();
Path inputFeature = Paths.get(EXAMPLES_BUILD_DIR, "arrayaccess.jar").toAbsolutePath();
Path library = ToolHelper.getDefaultAndroidJar();
Path output = working.resolve("classes.dex");
assertFalse(Files.exists(output));
ProcessResult result =
ToolHelper.forkR8(
working,
input.toString(),
"--lib",
library.toAbsolutePath().toString(),
"--no-tree-shaking",
"--feature",
inputFeature.toAbsolutePath().toString());
assertNotEquals("R8 run failed: " + result.stderr, 0, result.exitCode);
assertTrue(result.stderr.contains("Missing parameter for"));
}
@Test
public void flagsFile() throws Throwable {
Path working = temp.getRoot().toPath();
Path library = ToolHelper.getDefaultAndroidJar();
Path input = Paths.get(EXAMPLES_BUILD_DIR + "/arithmetic.jar").toAbsolutePath();
Path output = working.resolve("output.zip");
Path flagsFile = working.resolve("flags.txt");
FileUtils.writeTextFile(
flagsFile,
"--output",
"output.zip",
"--min-api",
"24",
"--lib",
library.toAbsolutePath().toString(),
"--no-tree-shaking",
input.toString());
assertEquals(0, ToolHelper.forkR8(working, "@flags.txt").exitCode);
assertTrue(Files.exists(output));
Collection<Marker> markers = ExtractMarker.extractMarkerFromDexFile(output);
assertEquals(1, markers.size());
Marker marker = markers.iterator().next();
assertEquals(24, marker.getMinApi().intValue());
assertEquals(Tool.R8, marker.getTool());
}
@Test(expected=CompilationFailedException.class)
public void nonExistingFlagsFile() throws Throwable {
Path working = temp.getRoot().toPath();
Path flags = working.resolve("flags.txt").toAbsolutePath();
assertNotEquals(0, ToolHelper.forkR8(working, "@flags.txt").exitCode);
DiagnosticsChecker.checkErrorsContains(
"NoSuchFileException",
handler ->
R8.run(
R8Command.parse(
new String[] {"@" + flags.toString()}, EmbeddedOrigin.INSTANCE, handler)
.build()));
}
@Test(expected = CompilationFailedException.class)
public void recursiveFlagsFile() throws Throwable {
Path working = temp.getRoot().toPath();
Path flagsFile = working.resolve("flags.txt");
Path recursiveFlagsFile = working.resolve("recursive_flags.txt");
Path input = Paths.get(EXAMPLES_BUILD_DIR + "/arithmetic.jar").toAbsolutePath();
FileUtils.writeTextFile(recursiveFlagsFile, "--output", "output.zip");
FileUtils.writeTextFile(
flagsFile, "--min-api", "24", input.toString(), "@" + recursiveFlagsFile);
DiagnosticsChecker.checkErrorsContains(
"Recursive @argfiles are not supported",
handler ->
R8.run(
R8Command.parse(
new String[] {"@" + flagsFile.toString()}, EmbeddedOrigin.INSTANCE, handler)
.build()));
}
@Test
public void printsHelpOnNoInput() throws Throwable {
ProcessResult result = ToolHelper.forkR8(temp.getRoot().toPath());
assertFalse(result.exitCode == 0);
assertTrue(result.stderr.contains("Usage"));
assertFalse(result.stderr.contains("R8_foobar")); // Sanity check
}
@Test
public void validOutputPath() throws Throwable {
Path existingDir = temp.getRoot().toPath();
Path nonExistingZip = existingDir.resolve("a-non-existing-archive.zip");
assertEquals(
existingDir,
getOutputPath(R8Command.builder().setOutput(existingDir, OutputMode.DexIndexed).build()));
assertEquals(
nonExistingZip,
getOutputPath(
R8Command.builder().setOutput(nonExistingZip, OutputMode.DexIndexed).build()));
assertEquals(existingDir, getOutputPath(parse("--output", existingDir.toString())));
assertEquals(nonExistingZip, getOutputPath(parse("--output", nonExistingZip.toString())));
}
static Path getOutputPath(BaseCompilerCommand command) {
ProgramConsumer consumer = command.getProgramConsumer();
if (consumer instanceof InternalProgramOutputPathConsumer) {
return ((InternalProgramOutputPathConsumer) consumer).internalGetOutputPath();
}
return null;
}
@Test
public void classFileOutputModeOption() throws Throwable {
assertTrue(parse("--classfile").getProgramConsumer() instanceof ClassFileConsumer);
}
@Test
public void classFileOutputModeAPI() throws Throwable {
assertTrue(
R8Command.builder()
.setOutput(Paths.get("."), OutputMode.ClassFile)
.build()
.getProgramConsumer()
instanceof ClassFileConsumer);
}
@Test
public void mainDexRules() throws Throwable {
Path mainDexRules1 = temp.newFile("main-dex-1.rules").toPath();
Path mainDexRules2 = temp.newFile("main-dex-2.rules").toPath();
parse("--main-dex-rules", mainDexRules1.toString());
parse(
"--main-dex-rules", mainDexRules1.toString(), "--main-dex-rules", mainDexRules2.toString());
}
@Test(expected = CompilationFailedException.class)
public void nonExistingMainDexRules() throws Throwable {
Path mainDexRules = temp.getRoot().toPath().resolve("main-dex.rules");
parse("--main-dex-rules", mainDexRules.toString());
}
@Test
public void mainDexList() throws Throwable {
Path mainDexList1 = temp.newFile("main-dex-list-1.txt").toPath();
Path mainDexList2 = temp.newFile("main-dex-list-2.txt").toPath();
parse("--main-dex-list", mainDexList1.toString());
parse("--main-dex-list", mainDexList1.toString(), "--main-dex-list", mainDexList2.toString());
}
@Test(expected = CompilationFailedException.class)
public void nonExistingMainDexList() throws Throwable {
Path mainDexList = temp.getRoot().toPath().resolve("main-dex-list.txt");
parse("--main-dex-list", mainDexList.toString());
}
@Test
public void mainDexListOutput() throws Throwable {
Path mainDexRules = temp.newFile("main-dex.rules").toPath();
Path mainDexList = temp.newFile("main-dex-list.txt").toPath();
Path mainDexListOutput = temp.newFile("main-dex-out.txt").toPath();
parse("--main-dex-rules", mainDexRules.toString(),
"--main-dex-list-output", mainDexListOutput.toString());
parse("--main-dex-list", mainDexList.toString(),
"--main-dex-list-output", mainDexListOutput.toString());
}
@Test(expected = CompilationFailedException.class)
public void mainDexListOutputWithoutAnyMainDexSpecification() throws Throwable {
Path mainDexListOutput = temp.newFile("main-dex-out.txt").toPath();
parse("--main-dex-list-output", mainDexListOutput.toString());
}
@Test(expected = CompilationFailedException.class)
public void mainDexRulesWithNonLegacyMinApi() throws Throwable {
Path mainDexRules = temp.newFile("main-dex.rules").toPath();
DiagnosticsChecker.checkErrorsContains(
"does not support main-dex",
(handler) ->
R8Command.builder(handler)
.setProgramConsumer(DexIndexedConsumer.emptyConsumer())
.setMinApiLevel(AndroidApiLevel.L.getLevel())
.addMainDexRulesFiles(mainDexRules)
.build());
}
@Test(expected = CompilationFailedException.class)
public void mainDexListWithNonLegacyMinApi() throws Throwable {
Path mainDexList = temp.newFile("main-dex-list.txt").toPath();
DiagnosticsChecker.checkErrorsContains(
"does not support main-dex",
(handler) ->
R8Command.builder(handler)
.setProgramConsumer(DexIndexedConsumer.emptyConsumer())
.setMinApiLevel(AndroidApiLevel.L.getLevel())
.addMainDexListFiles(mainDexList)
.build());
}
@Test
public void existingOutputDirWithDexFiles() throws Throwable {
Path existingDir = temp.newFolder().toPath();
List<Path> classesFiles = ImmutableList.of(
existingDir.resolve("classes.dex"),
existingDir.resolve("classes2.dex"),
existingDir.resolve("Classes3.dex"), // ignore case.
existingDir.resolve("classes10.dex"),
existingDir.resolve("classes999.dex"));
List<Path> otherFiles = ImmutableList.of(
existingDir.resolve("classes0.dex"),
existingDir.resolve("classes1.dex"),
existingDir.resolve("classes010.dex"),
existingDir.resolve("classesN.dex"),
existingDir.resolve("other.dex"));
for (Path file : classesFiles) {
Files.createFile(file);
assertTrue(Files.exists(file));
}
for (Path file : otherFiles) {
Files.createFile(file);
assertTrue(Files.exists(file));
}
Path input = Paths.get(EXAMPLES_BUILD_DIR, "arithmetic.jar");
ProcessResult result =
ToolHelper.forkR8(
Paths.get("."),
"--no-tree-shaking",
"--no-minification",
input.toString(),
"--output",
existingDir.toString(),
"--lib",
ToolHelper.getDefaultAndroidJar().toString());
assertEquals(0, result.exitCode);
assertTrue(Files.exists(classesFiles.get(0)));
for (int i = 1; i < classesFiles.size(); i++) {
Path file = classesFiles.get(i);
assertFalse("Expected stale file to be gone: " + file, Files.exists(file));
}
for (Path file : otherFiles) {
assertTrue("Expected non-classes file to remain: " + file, Files.exists(file));
}
}
@Test(expected = CompilationFailedException.class)
public void nonExistingOutputDir() throws Throwable {
Path nonExistingDir = temp.getRoot().toPath().resolve("a/path/that/does/not/exist");
R8Command.builder().setOutput(nonExistingDir, OutputMode.DexIndexed).build();
}
@Test
public void existingOutputZip() throws Throwable {
Path existingZip = temp.newFile("an-existing-archive.zip").toPath();
R8Command.builder().setOutput(existingZip, OutputMode.DexIndexed).build();
}
@Test(expected = CompilationFailedException.class)
public void invalidOutputFileType() throws Throwable {
Path invalidType = temp.getRoot().toPath().resolve("an-invalid-output-file-type.foobar");
R8Command.builder().setOutput(invalidType, OutputMode.DexIndexed).build();
}
@Test(expected = CompilationFailedException.class)
public void nonExistingOutputDirParse() throws Throwable {
Path nonExistingDir = temp.getRoot().toPath().resolve("a/path/that/does/not/exist");
parse("--output", nonExistingDir.toString());
}
@Test
public void existingOutputZipParse() throws Throwable {
Path existingZip = temp.newFile("an-existing-archive.zip").toPath();
parse("--output", existingZip.toString());
}
@Test(expected = CompilationFailedException.class)
public void invalidOutputFileTypeParse() throws Throwable {
Path invalidType = temp.getRoot().toPath().resolve("an-invalid-output-file-type.foobar");
parse("--output", invalidType.toString());
}
@Test
public void nonExistingOutputJar() throws Throwable {
Path nonExistingJar = temp.getRoot().toPath().resolve("non-existing-archive.jar");
R8Command.builder().setOutput(nonExistingJar, OutputMode.DexIndexed).build();
}
@Test(expected = CompilationFailedException.class)
public void dexFileUnsupported() throws Throwable {
Path dexFile = temp.newFile("test.dex").toPath();
DiagnosticsChecker.checkErrorsContains("DEX input", handler ->
R8Command
.builder(handler)
.setProgramConsumer(DexIndexedConsumer.emptyConsumer())
.addProgramFiles(dexFile)
.build());
}
@Test(expected = CompilationFailedException.class)
public void dexProviderUnsupported() throws Throwable {
Path dexFile = temp.newFile("test.dex").toPath();
DiagnosticsChecker.checkErrorsContains("DEX input", handler ->
R8.run(R8Command
.builder(handler)
.setProgramConsumer(DexIndexedConsumer.emptyConsumer())
.addProgramResourceProvider(new ProgramResourceProvider() {
@Override
public Collection<ProgramResource> getProgramResources() throws ResourceException {
return Collections.singleton(ProgramResource.fromFile(Kind.DEX, dexFile));
}
})
.build()));
}
@Test
public void dexDataUnsupported() throws Throwable {
for (Method method : R8Command.Builder.class.getMethods()) {
assertNotEquals("addDexProgramData", method.getName());
}
}
@Test(expected = CompilationFailedException.class)
public void vdexFileUnsupported() throws Throwable {
Path vdexFile = temp.newFile("test.vdex").toPath();
R8Command.builder()
.setProgramConsumer(DexIndexedConsumer.emptyConsumer())
.addProgramFiles(vdexFile)
.build();
}
@Test(expected = CompilationFailedException.class)
public void duplicateApiLevel() throws CompilationFailedException {
DiagnosticsChecker.checkErrorsContains(
"multiple --min-api", handler -> parse(handler, "--min-api", "19", "--min-api", "21"));
}
@Test(expected = CompilationFailedException.class)
public void invalidApiLevel() throws CompilationFailedException {
DiagnosticsChecker.checkErrorsContains(
"Invalid argument to --min-api", handler -> parse(handler, "--min-api", "foobar"));
}
@Test(expected = CompilationFailedException.class)
public void negativeApiLevel() throws CompilationFailedException {
DiagnosticsChecker.checkErrorsContains(
"Invalid argument to --min-api", handler -> parse(handler, "--min-api", "-21"));
}
@Test(expected = CompilationFailedException.class)
public void zeroApiLevel() throws CompilationFailedException {
DiagnosticsChecker.checkErrorsContains(
"Invalid argument to --min-api", handler -> parse(handler, "--min-api", "0"));
}
@Test
public void disableDesugaringCli() throws CompilationFailedException {
BaseCompilerCommandTest.assertDesugaringDisabled(parse("--no-desugaring"));
}
@Test
public void disableDesugaringApi() throws CompilationFailedException {
BaseCompilerCommandTest.assertDesugaringDisabled(R8Command.builder()
.setProgramConsumer(DexIndexedConsumer.emptyConsumer())
.setDisableDesugaring(true)
.build());
}
private ProcessResult runR8OnShaking1(Path additionalProguardConfiguration) throws Throwable {
Path input = Paths.get(EXAMPLES_BUILD_DIR, "shaking1.jar").toAbsolutePath();
Path proguardConfiguration =
Paths.get(ToolHelper.EXAMPLES_DIR, "shaking1", "keep-rules.txt").toAbsolutePath();
return ToolHelper.forkR8(temp.getRoot().toPath(),
"--pg-conf", proguardConfiguration.toString(),
"--pg-conf", additionalProguardConfiguration.toString(),
"--lib", ToolHelper.getDefaultAndroidJar().toAbsolutePath().toString(),
input.toString());
}
@Test
public void printsConfigurationOnStdout() throws Throwable {
Path proguardPrintConfigurationConfiguration =
temp.newFile("printconfiguration.txt").toPath().toAbsolutePath();
FileUtils.writeTextFile(
proguardPrintConfigurationConfiguration, ImmutableList.of("-printconfiguration"));
ProcessResult result = runR8OnShaking1(proguardPrintConfigurationConfiguration);
assertEquals("R8 run failed: " + result.stderr, 0, result.exitCode);
assertTrue(result.stdout.contains("-printconfiguration"));
}
@Test
public void printsPrintSeedsOnStdout() throws Throwable {
Path proguardPrintSeedsConfiguration = temp.newFile("printseeds.txt").toPath().toAbsolutePath();
FileUtils.writeTextFile(proguardPrintSeedsConfiguration, ImmutableList.of("-printseeds"));
ProcessResult result = runR8OnShaking1(proguardPrintSeedsConfiguration);
assertEquals("R8 run failed: " + result.stderr, 0, result.exitCode);
assertTrue(result.stdout.contains("void main(java.lang.String[])"));
}
@Test
public void printsPrintUsageOnStdout() throws Throwable {
Path proguardPrintUsageConfiguration = temp.newFile("printusage.txt").toPath().toAbsolutePath();
FileUtils.writeTextFile(proguardPrintUsageConfiguration, ImmutableList.of("-printusage"));
ProcessResult result = runR8OnShaking1(proguardPrintUsageConfiguration);
assertEquals("R8 run failed: " + result.stderr, 0, result.exitCode);
assertTrue(result.stdout.contains("shaking1.Unused"));
}
@Test
public void printsPrintSeedsAndPrintUsageOnStdout() throws Throwable {
Path proguardPrintSeedsConfiguration =
temp.newFile("printseedsandprintusage.txt").toPath().toAbsolutePath();
FileUtils.writeTextFile(
proguardPrintSeedsConfiguration, ImmutableList.of("-printseeds", "-printusage"));
ProcessResult result = runR8OnShaking1(proguardPrintSeedsConfiguration);
assertEquals("R8 run failed: " + result.stderr, 0, result.exitCode);
assertTrue(result.stdout.contains("void main(java.lang.String[])"));
assertTrue(result.stdout.contains("shaking1.Unused"));
}
@Test
public void printsPrintSeedsAndPrintUsageAndPrintConfigurationOnStdout() throws Throwable {
Path proguardPrintSeedsConfiguration =
temp.newFile("printseedsandprintusageandprintconfiguration.txt").toPath().toAbsolutePath();
FileUtils.writeTextFile(proguardPrintSeedsConfiguration,
ImmutableList.of("-printseeds", "-printusage", "-printconfiguration"));
ProcessResult result = runR8OnShaking1(proguardPrintSeedsConfiguration);
assertEquals("R8 run failed: " + result.stderr, 0, result.exitCode);
assertTrue(result.stdout.contains("void main(java.lang.String[])"));
assertTrue(result.stdout.contains("shaking1.Unused"));
assertTrue(result.stdout.contains("-printseeds"));
assertTrue(result.stdout.contains("-printusage"));
assertTrue(result.stdout.contains("-printconfiguration"));
}
@Test
public void noInputOutputsEmptyZip() throws CompilationFailedException, IOException {
Path emptyZip = temp.getRoot().toPath().resolve("empty.zip");
R8.run(
R8Command.builder()
.setOutput(emptyZip, OutputMode.DexIndexed)
.build());
assertTrue(Files.exists(emptyZip));
assertEquals(0, new ZipFile(emptyZip.toFile(), StandardCharsets.UTF_8).size());
}
private Path writeZipWithDataResource(String name) throws Exception {
Path dataResourceZip = temp.newFolder().toPath().resolve(name);
try (ZipOutputStream out =
new ZipOutputStream(
Files.newOutputStream(
dataResourceZip,
StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING))) {
// Write a directory entry and a normal entry.
ZipUtils.writeToZipStream(out, "org/", new byte[] {}, ZipEntry.STORED);
ZipUtils.writeToZipStream(
out, "org/resource.txt", "Hello world!".getBytes(), ZipEntry.STORED);
}
return dataResourceZip;
}
@Test
public void defaultResourceProcessing() throws Exception {
Path dataResourceZip = writeZipWithDataResource("dataResource.zip");
Path outputZip = temp.getRoot().toPath().resolve("output.zip");
R8.run(
R8Command.builder()
.addProgramFiles(dataResourceZip)
.setOutput(outputZip, OutputMode.ClassFile)
.build());
assertTrue(Files.exists(outputZip));
assertEquals(1, new ZipFile(outputZip.toFile(), StandardCharsets.UTF_8).size());
}
public void runCustomResourceProcessing(
boolean includeDataResources, boolean keepDirectories, int expectedZipEntries)
throws Exception {
Path dataResourceZip = writeZipWithDataResource("dataResource.zip");
Path outputZip = temp.newFolder().toPath().resolve("output.zip");
R8.run(
R8Command.builder()
.addProgramFiles(dataResourceZip)
.setOutput(outputZip, OutputMode.ClassFile, includeDataResources)
.addProguardConfiguration(
ImmutableList.of(keepDirectories ? "-keepdirectories" : ""), Origin.unknown())
.build());
assertTrue(Files.exists(outputZip));
assertEquals(
expectedZipEntries, new ZipFile(outputZip.toFile(), StandardCharsets.UTF_8).size());
}
private Path simpleProguardConfiguration() throws Exception {
Path proguardConfiguration = temp.newFile("printseedsandprintusage.txt").toPath();
FileUtils.writeTextFile(proguardConfiguration, ImmutableList.of("-keep class A { *; }"));
return proguardConfiguration;
}
@Test
public void noTreeShakingOption() throws Throwable {
// Default "keep all" rule implies no tree shaking.
assertTrue(parse().getEnableTreeShaking());
assertFalse(parse("--no-tree-shaking").getEnableTreeShaking());
// With a Proguard configuration --no-tree-shaking takes effect.
String proguardConfiguration = simpleProguardConfiguration().toAbsolutePath().toString();
assertTrue(parse("--pg-conf", proguardConfiguration).getEnableTreeShaking());
assertFalse(
parse("--no-tree-shaking", "--pg-conf", proguardConfiguration).getEnableTreeShaking());
}
@Test
public void noMinificationOption() throws Throwable {
// Default "keep all" rule implies no tree minification.
assertTrue(parse().getEnableMinification());
assertFalse(parse("--no-minification").getEnableMinification());
// With a Proguard configuration --no-tree-shaking takes effect.
String proguardConfiguration = simpleProguardConfiguration().toAbsolutePath().toString();
assertTrue(parse("--pg-conf", proguardConfiguration).getEnableMinification());
assertFalse(
parse("--no-minification", "--pg-conf", proguardConfiguration).getEnableMinification());
}
@Test
public void defaultDataResourcesOption() throws Throwable {
Path dataResourceZip = writeZipWithDataResource("dataResource.zip");
Path outputZip = temp.newFolder().toPath().resolve("output.zip");
R8.run(
parse(
dataResourceZip.toAbsolutePath().toString(),
"--output",
outputZip.toAbsolutePath().toString()));
assertTrue(Files.exists(outputZip));
assertEquals(1, new ZipFile(outputZip.toFile(), StandardCharsets.UTF_8).size());
}
@Test
public void noDataResourcesOption() throws Throwable {
Path dataResourceZip = writeZipWithDataResource("dataResource.zip");
Path outputZip = temp.newFolder().toPath().resolve("output.zip");
R8.run(
parse(
"--no-data-resources",
dataResourceZip.toAbsolutePath().toString(),
"--output",
outputZip.toAbsolutePath().toString()));
assertTrue(Files.exists(outputZip));
assertEquals(0, new ZipFile(outputZip.toFile(), StandardCharsets.UTF_8).size());
}
@Test
public void customResourceProcessing() throws Exception {
runCustomResourceProcessing(true, true, 2);
runCustomResourceProcessing(true, false, 1);
runCustomResourceProcessing(false, false, 0);
}
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").getAssertionsConfiguration(),
AssertionTransformation.ENABLE);
checkSingleForceAllAssertion(
parse("--force-disable-assertions").getAssertionsConfiguration(),
AssertionTransformation.DISABLE);
checkSingleForceAllAssertion(
parse("--force-passthrough-assertions").getAssertionsConfiguration(),
AssertionTransformation.PASSTHROUGH);
checkSingleForceClassAndPackageAssertion(
parse("--force-enable-assertions:ClassName", "--force-enable-assertions:PackageName...")
.getAssertionsConfiguration(),
AssertionTransformation.ENABLE);
checkSingleForceClassAndPackageAssertion(
parse("--force-disable-assertions:ClassName", "--force-disable-assertions:PackageName...")
.getAssertionsConfiguration(),
AssertionTransformation.DISABLE);
checkSingleForceClassAndPackageAssertion(
parse(
"--force-passthrough-assertions:ClassName",
"--force-passthrough-assertions:PackageName...")
.getAssertionsConfiguration(),
AssertionTransformation.PASSTHROUGH);
}
@Test(expected = CompilationFailedException.class)
public void missingParameterForLastOption() throws CompilationFailedException {
DiagnosticsChecker.checkErrorsContains(
"Missing parameter", handler -> parse(handler, "--output"));
}
@Test
public void desugaredLibrary() throws CompilationFailedException {
R8Command r8Command = parse("--desugared-lib", "src/library_desugar/desugar_jdk_libs.json");
assertFalse(
r8Command.getInternalOptions().desugaredLibraryConfiguration.getRewritePrefix().isEmpty());
}
@Test
public void desugaredLibraryWithOutputConf() throws CompilationFailedException {
Path pgout = temp.getRoot().toPath().resolve("pgout.conf");
R8Command r8Command =
parse(
"--desugared-lib",
"src/library_desugar/desugar_jdk_libs.json",
"--desugared-lib-pg-conf-output",
pgout.toString());
assertFalse(
r8Command.getInternalOptions().desugaredLibraryConfiguration.getRewritePrefix().isEmpty());
}
@Test
public void desugaredLibraryWithOutputConfMissingArg() {
TestDiagnosticMessagesImpl diagnostics = new TestDiagnosticMessagesImpl();
try {
parse(
diagnostics,
"--desugared-lib",
"src/library_desugar/desugar_jdk_libs.json",
"--desugared-lib-pg-conf-output");
fail("Expected parse error");
} catch (CompilationFailedException e) {
diagnostics.assertErrorsMatch(diagnosticMessage(containsString("Missing parameter")));
}
}
@Test
public void numThreadsOption() throws Exception {
assertEquals(ThreadUtils.NOT_SPECIFIED, parse().getThreadCount());
assertEquals(1, parse("--thread-count", "1").getThreadCount());
assertEquals(2, parse("--thread-count", "2").getThreadCount());
assertEquals(10, parse("--thread-count", "10").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));
fail("Expected failure");
} catch (CompilationFailedException e) {
// Expected.
}
}
@Test
public void numThreadsOptionInvalid() throws Exception {
numThreadsOptionInvalid("0");
numThreadsOptionInvalid("-1");
numThreadsOptionInvalid("two");
}
private R8Command parse(String... args) throws CompilationFailedException {
return R8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
}
private R8Command parse(DiagnosticsHandler handler, String... args)
throws CompilationFailedException {
return R8Command.parse(args, EmbeddedOrigin.INSTANCE, handler).build();
}
}