| // 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.ToolHelper.EXAMPLES_BUILD_DIR; | |
| import static org.junit.Assert.assertEquals; | |
| import static org.junit.Assert.assertFalse; | |
| import static org.junit.Assert.assertNotEquals; | |
| import static org.junit.Assert.assertTrue; | |
| 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.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.Rule; | |
| import org.junit.Test; | |
| import org.junit.rules.TemporaryFolder; | |
| public class R8CommandTest { | |
| @Rule | |
| public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest(); | |
| @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 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 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("File not found", handler -> | |
| R8.run( | |
| R8Command.parse( | |
| new String[] { "@" + flags.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); | |
| } | |
| @Test(expected = CompilationFailedException.class) | |
| public void missingParameterForLastOption() throws CompilationFailedException { | |
| DiagnosticsChecker.checkErrorsContains( | |
| "Missing parameter", handler -> parse(handler, "--output")); | |
| } | |
| 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(); | |
| } | |
| } |