| // Copyright 2020 The Bazel Authors. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| package com.google.devtools.build.android.r8; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| import static com.google.common.truth.Truth8.assertThat; |
| import static java.nio.charset.StandardCharsets.UTF_8; |
| import static java.util.stream.Collectors.toList; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import java.io.IOException; |
| import java.net.URI; |
| import java.nio.file.FileSystem; |
| import java.nio.file.FileSystems; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.nio.file.StandardCopyOption; |
| import java.nio.file.StandardOpenOption; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.stream.Stream; |
| import java.util.zip.ZipEntry; |
| import java.util.zip.ZipFile; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.rules.TemporaryFolder; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** Test for CompatDx. */ |
| @RunWith(JUnit4.class) |
| public class CompatDxTest { |
| private static final String EXAMPLE_JAR_FILE_1 = System.getProperty("CompatDxTests.arithmetic"); |
| private static final String EXAMPLE_JAR_FILE_2 = System.getProperty("CompatDxTests.barray"); |
| |
| private static final String NO_LOCALS = "--no-locals"; |
| private static final String NO_POSITIONS = "--positions=none"; |
| private static final String MULTIDEX = "--multi-dex"; |
| private static final String NUM_THREADS_5 = "--num-threads=5"; |
| |
| @Rule public TemporaryFolder temp = new TemporaryFolder(); |
| |
| @Test |
| public void noFilesTest() throws IOException { |
| assertThat(runDexer("--no-files")).isEmpty(); |
| } |
| |
| @Test |
| public void noOutputTest() throws IOException { |
| runDexerWithoutOutput(NO_POSITIONS, NO_LOCALS, MULTIDEX, EXAMPLE_JAR_FILE_1); |
| } |
| |
| @Test |
| public void singleJarInputFile() throws IOException { |
| assertThat( |
| runDexer(NO_POSITIONS, NO_LOCALS, MULTIDEX, EXAMPLE_JAR_FILE_1).stream() |
| .map(Path::getFileName) |
| .map(Path::toString)) |
| .containsExactly("classes.dex"); |
| } |
| |
| @Test |
| public void multipleJarInputFiles() throws IOException { |
| assertThat( |
| runDexer(NO_POSITIONS, NO_LOCALS, MULTIDEX, EXAMPLE_JAR_FILE_1, EXAMPLE_JAR_FILE_2) |
| .stream() |
| .map(Path::getFileName) |
| .map(Path::toString)) |
| .containsExactly("classes.dex"); |
| } |
| |
| @Test |
| public void outputZipFile() throws IOException { |
| List<Path> out = |
| runDexerWithOutput("foo.dex.zip", NO_POSITIONS, NO_LOCALS, MULTIDEX, EXAMPLE_JAR_FILE_1); |
| assertThat(out.stream().map(Path::getFileName).map(Path::toString)) |
| .containsExactly("foo.dex.zip"); |
| assertThat(archiveFiles(out.get(0))).containsExactly("classes.dex"); |
| } |
| |
| @Test |
| public void useMultipleThreads() throws IOException { |
| assertThat( |
| runDexer(NUM_THREADS_5, NO_POSITIONS, NO_LOCALS, EXAMPLE_JAR_FILE_1).stream() |
| .map(Path::getFileName) |
| .map(Path::toString)) |
| .containsExactly("classes.dex"); |
| } |
| |
| @Test |
| public void withPositions() throws IOException { |
| assertThat( |
| runDexer(NO_LOCALS, MULTIDEX, EXAMPLE_JAR_FILE_1).stream() |
| .map(Path::getFileName) |
| .map(Path::toString)) |
| .containsExactly("classes.dex"); |
| } |
| |
| @Test |
| public void withLocals() throws IOException { |
| assertThat( |
| runDexer(NO_POSITIONS, MULTIDEX, EXAMPLE_JAR_FILE_1).stream() |
| .map(Path::getFileName) |
| .map(Path::toString)) |
| .containsExactly("classes.dex"); |
| } |
| |
| @Test |
| public void withoutMultidex() throws IOException { |
| assertThat( |
| runDexer(NO_POSITIONS, NO_LOCALS, EXAMPLE_JAR_FILE_1).stream() |
| .map(Path::getFileName) |
| .map(Path::toString)) |
| .containsExactly("classes.dex"); |
| } |
| |
| @Test |
| public void writeToNamedDexFile() throws IOException { |
| assertThat( |
| runDexerWithOutput("named-output.dex", EXAMPLE_JAR_FILE_1).stream() |
| .map(Path::getFileName) |
| .map(Path::toString)) |
| .containsExactly("named-output.dex"); |
| } |
| |
| @Test |
| public void keepClassesSingleDexTest() throws IOException { |
| List<Path> out = runDexerWithOutput("out.zip", "--keep-classes", EXAMPLE_JAR_FILE_1); |
| assertThat(out.stream().map(Path::getFileName).map(Path::toString)).containsExactly("out.zip"); |
| assertThat(archiveFiles(out.get(0))) |
| .containsExactly( |
| "classes.dex", |
| "com/google/devtools/build/android/r8/testdata/arithmetic/Arithmetic.class"); |
| } |
| |
| @Test |
| public void keepClassesMultiDexTest() throws IOException { |
| List<Path> out = |
| runDexerWithOutput("out.zip", "--keep-classes", "--multi-dex", EXAMPLE_JAR_FILE_1); |
| assertThat(out.stream().map(Path::getFileName).map(Path::toString)).containsExactly("out.zip"); |
| assertThat(archiveFiles(out.get(0))) |
| .containsExactly( |
| "classes.dex", |
| "com/google/devtools/build/android/r8/testdata/arithmetic/Arithmetic.class"); |
| } |
| |
| @Test |
| public void ignoreDexInArchiveTest() throws IOException { |
| // Create a JAR with both a .class and a .dex file (the .dex file is just empty). |
| Path jarWithClassesAndDex = temp.newFile("test.jar").toPath(); |
| Files.copy( |
| Paths.get(EXAMPLE_JAR_FILE_1), jarWithClassesAndDex, StandardCopyOption.REPLACE_EXISTING); |
| jarWithClassesAndDex.toFile().setWritable(true); |
| URI uri = URI.create("jar:" + jarWithClassesAndDex.toUri()); |
| try (FileSystem fileSystem = |
| FileSystems.newFileSystem(uri, ImmutableMap.of("create", "true"))) { |
| Path dexFile = fileSystem.getPath("classes.dex"); |
| Files.newOutputStream(dexFile, StandardOpenOption.CREATE).close(); |
| } |
| |
| // Only test this with CompatDx, as dx does not like the empty .dex file. |
| List<String> d8Args = |
| ImmutableList.of("--output=" + temp.newFolder("out"), jarWithClassesAndDex.toString()); |
| CompatDx.main(d8Args.toArray(new String[0])); |
| } |
| |
| private List<Path> runDexer(String... args) throws IOException { |
| return runDexerWithOutput("", args); |
| } |
| |
| private void runDexerWithoutOutput(String... args) throws IOException { |
| runDexerWithOutput(null, args); |
| } |
| |
| private Path getOutputDir() throws IOException { |
| Path dir = temp.getRoot().toPath().resolve("out"); |
| Files.createDirectory(dir); |
| return dir; |
| } |
| |
| private List<Path> runDexerWithOutput(String out, String... args) throws IOException { |
| Path d8Out = null; |
| if (out != null) { |
| Path baseD8 = getOutputDir(); |
| d8Out = baseD8.resolve(out); |
| } |
| |
| List<String> d8Args = new ArrayList<>(); |
| d8Args.add("--dex"); |
| if (d8Out != null) { |
| d8Args.add("--output=" + d8Out); |
| } |
| Collections.addAll(d8Args, args); |
| CompatDx.main(d8Args.toArray(new String[0])); |
| |
| if (out == null) { |
| // Can't check output if explicitly not writing any. |
| return ImmutableList.of(); |
| } |
| |
| List<Path> d8Files; |
| try (Stream<Path> d8FilesStream = |
| Files.list(Files.isDirectory(d8Out) ? d8Out : d8Out.getParent())) { |
| d8Files = d8FilesStream.sorted().collect(toList()); |
| } |
| return d8Files; |
| } |
| |
| private static List<String> archiveFiles(Path path) throws IOException { |
| try (ZipFile zipFile = new ZipFile(path.toFile(), UTF_8)) { |
| return zipFile.stream().map(ZipEntry::getName).collect(toList()); |
| } |
| } |
| } |