| // Copyright (c) 2018, 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.cf; |
| |
| import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION; |
| import static com.google.common.io.ByteStreams.toByteArray; |
| import static org.junit.Assert.assertArrayEquals; |
| import static org.junit.Assert.assertEquals; |
| |
| import com.android.tools.r8.ArchiveClassFileProvider; |
| import com.android.tools.r8.ClassFileConsumer; |
| import com.android.tools.r8.CompilationMode; |
| import com.android.tools.r8.R8Command; |
| import com.android.tools.r8.TestBase; |
| import com.android.tools.r8.ToolHelper; |
| import com.android.tools.r8.ToolHelper.ProcessResult; |
| import com.android.tools.r8.utils.AndroidApp; |
| import com.android.tools.r8.utils.FileUtils; |
| import com.android.tools.r8.utils.codeinspector.ClassHierarchyVerifier; |
| import com.android.tools.r8.utils.codeinspector.CodeInspector; |
| import com.google.common.base.Charsets; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import org.junit.Test; |
| |
| public class BootstrapTest extends TestBase { |
| |
| private static final Path R8_STABLE_JAR = Paths.get("third_party/r8/r8.jar"); |
| |
| private static final String R8_NAME = "com.android.tools.r8.R8"; |
| private static final String[] KEEP_R8 = { |
| "-keep public class " + R8_NAME + " {", |
| " public static void main(...);", |
| "}", |
| }; |
| |
| private static final String HELLO_NAME = "hello.Hello"; |
| private static final String[] KEEP_HELLO = { |
| "-keep class " + HELLO_NAME + " {", |
| " public static void main(...);", |
| "}", |
| }; |
| |
| private class R8Result { |
| |
| final ProcessResult processResult; |
| final Path outputJar; |
| final String pgMap; |
| |
| R8Result(ProcessResult processResult, Path outputJar, String pgMap) { |
| this.processResult = processResult; |
| this.outputJar = outputJar; |
| this.pgMap = pgMap; |
| } |
| |
| @Override |
| public String toString() { |
| return processResult.toString() + "\n\n" + pgMap; |
| } |
| } |
| |
| @Test |
| public void test() throws Exception { |
| // Run hello.jar to ensure it exists and is valid. |
| Path hello = Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "hello" + JAR_EXTENSION); |
| ProcessResult runHello = ToolHelper.runJava(hello, "hello.Hello"); |
| assertEquals(0, runHello.exitCode); |
| |
| // Run r8.jar on hello.jar to ensure that r8.jar is a working compiler. |
| R8Result runInputR8 = runExternalR8(R8_STABLE_JAR, hello, "input", KEEP_HELLO, "--debug"); |
| ProcessResult runHelloR8 = ToolHelper.runJava(runInputR8.outputJar, "hello.Hello"); |
| assertEquals(runHello.toString(), runHelloR8.toString()); |
| |
| compareR8(hello, runInputR8, CompilationMode.RELEASE, "r8-r8-rel", "--release", "output-rel"); |
| compareR8(hello, runInputR8, CompilationMode.DEBUG, "r8-r8", "--debug", "output"); |
| } |
| |
| private void compareR8( |
| Path hello, |
| R8Result runInputR8, |
| CompilationMode internalMode, |
| String internalOutput, |
| String externalMode, |
| String externalOutput) |
| throws Exception { |
| // Run R8 on r8.jar. |
| Path output = runR8(R8_STABLE_JAR, internalOutput, KEEP_R8, internalMode); |
| // Check that all non-abstract classes in the R8'd R8 implement all abstract/interface methods |
| // from their supertypes. This is a sanity check for the tree shaking and minification. |
| AndroidApp app = AndroidApp.builder().addProgramFile(output).build(); |
| new ClassHierarchyVerifier(new CodeInspector(app)).run(); |
| // Run the resulting compiler on hello.jar. |
| R8Result runR8R8 = runExternalR8(output, hello, externalOutput, KEEP_HELLO, externalMode); |
| // Check that the process outputs (exit code, stdout, stderr) are the same. |
| assertEquals(runInputR8.toString(), runR8R8.toString()); |
| // Check that the output jars are the same. |
| assertProgramsEqual(runInputR8.outputJar, runR8R8.outputJar); |
| } |
| |
| private Path runR8(Path inputJar, String outputFolder, String[] keepRules, CompilationMode mode) |
| throws Exception { |
| Path outputPath = temp.newFolder(outputFolder).toPath(); |
| Path outputJar = outputPath.resolve("output.jar"); |
| Path pgConfigFile = outputPath.resolve("keep.rules"); |
| FileUtils.writeTextFile(pgConfigFile, keepRules); |
| ToolHelper.runR8( |
| R8Command.builder() |
| .setMode(mode) |
| .addLibraryFiles(ToolHelper.getJava8RuntimeJar()) |
| .setProgramConsumer(new ClassFileConsumer.ArchiveConsumer(outputJar, true)) |
| .addProgramFiles(inputJar) |
| .addProguardConfigurationFiles(pgConfigFile) |
| .build()); |
| return outputJar; |
| } |
| |
| private R8Result runExternalR8( |
| Path r8Jar, Path inputJar, String outputFolder, String[] keepRules, String mode) |
| throws Exception { |
| Path outputPath = temp.newFolder(outputFolder).toPath(); |
| Path pgConfigFile = outputPath.resolve("keep.rules"); |
| Path outputJar = outputPath.resolve("output.jar"); |
| Path pgMapFile = outputPath.resolve("map.txt"); |
| FileUtils.writeTextFile(pgConfigFile, keepRules); |
| ProcessResult processResult = |
| ToolHelper.runJava( |
| r8Jar, |
| R8_NAME, |
| "--lib", |
| ToolHelper.JAVA_8_RUNTIME, |
| "--classfile", |
| inputJar.toString(), |
| "--output", |
| outputJar.toString(), |
| "--pg-conf", |
| pgConfigFile.toString(), |
| mode, |
| "--pg-map-output", |
| pgMapFile.toString()); |
| if (processResult.exitCode != 0) { |
| System.out.println(processResult); |
| } |
| assertEquals(0, processResult.exitCode); |
| String pgMap = FileUtils.readTextFile(pgMapFile, Charsets.UTF_8); |
| return new R8Result(processResult, outputJar, pgMap); |
| } |
| |
| private static void assertProgramsEqual(Path expectedJar, Path actualJar) throws Exception { |
| ArchiveClassFileProvider expected = new ArchiveClassFileProvider(expectedJar); |
| ArchiveClassFileProvider actual = new ArchiveClassFileProvider(actualJar); |
| assertEquals(getSortedDescriptorList(expected), getSortedDescriptorList(actual)); |
| for (String descriptor : expected.getClassDescriptors()) { |
| assertArrayEquals( |
| "Class " + descriptor + " differs", |
| getClassAsBytes(expected, descriptor), |
| getClassAsBytes(actual, descriptor)); |
| } |
| } |
| |
| private static List<String> getSortedDescriptorList(ArchiveClassFileProvider inputJar) { |
| ArrayList<String> descriptorList = new ArrayList<>(inputJar.getClassDescriptors()); |
| Collections.sort(descriptorList); |
| return descriptorList; |
| } |
| |
| private static byte[] getClassAsBytes(ArchiveClassFileProvider inputJar, String descriptor) |
| throws Exception { |
| return toByteArray(inputJar.getProgramResource(descriptor).getByteStream()); |
| } |
| } |