diff options
author | 2017-10-25 23:40:30 +0200 | |
---|---|---|
committer | 2017-10-26 10:59:31 +0200 | |
commit | b47a62ed03a529319ec79806672ee57ea6819b30 (patch) | |
tree | 0494832fb0844b2ed64d939cb20d32e35cd91776 /src/test/java/com/google/devtools/build/android/dexer | |
parent | 0356a506ceb02da557c0acbc7c50b598bc37620e (diff) |
Internal change
PiperOrigin-RevId: 173451767
Diffstat (limited to 'src/test/java/com/google/devtools/build/android/dexer')
4 files changed, 358 insertions, 15 deletions
diff --git a/src/test/java/com/google/devtools/build/android/dexer/BUILD b/src/test/java/com/google/devtools/build/android/dexer/BUILD index 2f70378fee..c60e7474d7 100644 --- a/src/test/java/com/google/devtools/build/android/dexer/BUILD +++ b/src/test/java/com/google/devtools/build/android/dexer/BUILD @@ -22,6 +22,8 @@ java_library( exclude = [ "NoAndroidSdkStubTest.java", "AllTests.java", + # TODO(kmb,ajmichael): need 2nd Java 7 Jar for this test + "DexFileSplitterTest.java", ], ), "//conditions:default": ["NoAndroidSdkStubTest.java"], diff --git a/src/test/java/com/google/devtools/build/android/dexer/DexFileMergerTest.java b/src/test/java/com/google/devtools/build/android/dexer/DexFileMergerTest.java index d4fb9a33f2..c772e427e8 100644 --- a/src/test/java/com/google/devtools/build/android/dexer/DexFileMergerTest.java +++ b/src/test/java/com/google/devtools/build/android/dexer/DexFileMergerTest.java @@ -1,16 +1,16 @@ // Copyright 2017 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. +// +// 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.dexer; import static com.google.common.truth.Truth.assertThat; @@ -91,7 +91,7 @@ public class DexFileMergerTest { @Test public void testMergeDexArchive_multidex() throws Exception { Path dexArchive = buildDexArchive(); - Path outputArchive = runDexFileMerger(dexArchive, 20, "multidex_from_dex_archive.dex.zip"); + Path outputArchive = runDexFileMerger(dexArchive, 200, "multidex_from_dex_archive.dex.zip"); int expectedClassCount = matchingFileCount(dexArchive, ".*\\.class.dex$"); assertMultidexOutput(expectedClassCount, outputArchive, ImmutableSet.<String>of()); @@ -228,7 +228,7 @@ public class DexFileMergerTest { Set<String> shard = dexFiles.get(expectedDexFileName(i)); for (String c1 : prev) { for (String c2 : shard) { - assertThat(DexFileMerger.compareClassNames(c2, c1)) + assertThat(ZipEntryComparator.compareClassNames(c2, c1)) .named(c2 + " in shard " + i + " should compare as larger than " + c1 + "; list of all shards for reference: " + dexFiles) .isGreaterThan(0); @@ -251,7 +251,7 @@ public class DexFileMergerTest { } Multimap<String, String> dexFiles = assertMultidexOutput(expectedClassCount, outputArchive, mainDexList); - assertThat(dexFiles.keySet()).hasSize(2); + assertThat(dexFiles.keySet().size()).isAtLeast(2); if (minimalMainDex) { assertThat(dexFiles.get("classes.dex")).containsExactlyElementsIn(mainDexList); } else { diff --git a/src/test/java/com/google/devtools/build/android/dexer/DexFileSplitterTest.java b/src/test/java/com/google/devtools/build/android/dexer/DexFileSplitterTest.java new file mode 100644 index 0000000000..c2354ca134 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/dexer/DexFileSplitterTest.java @@ -0,0 +1,245 @@ +// Copyright 2017 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.dexer; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; + +import com.android.dx.command.dexer.DxContext; +import com.android.dx.dex.code.PositionList; +import com.google.common.base.Function; +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Iterators; +import java.io.IOException; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashSet; +import java.util.Set; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import javax.annotation.Nullable; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link DexFileSplitter}. */ +@RunWith(JUnit4.class) +public class DexFileSplitterTest { + + private static final Path WORKING_DIR = Paths.get(System.getProperty("user.dir")); + private static final Path INPUT_JAR = WORKING_DIR.resolve(System.getProperty("testinputjar")); + private static final Path INPUT_JAR2 = WORKING_DIR.resolve(System.getProperty("testinputjar2")); + private static final Path MAIN_DEX_LIST_FILE = + WORKING_DIR.resolve(System.getProperty("testmaindexlist")); + static final String DEX_PREFIX = "classes"; + + @Test + public void testSingleInputSingleOutput() throws Exception { + Path dexArchive = buildDexArchive(); + ImmutableList<Path> outputArchives = runDexSplitter(256 * 256, "from_single", dexArchive); + assertThat(outputArchives).hasSize(1); + + ImmutableSet<String> expectedFiles = dexEntries(dexArchive); + assertThat(dexEntries(outputArchives.get(0))).containsExactlyElementsIn(expectedFiles); + } + + @Test + public void testDuplicateInputIgnored() throws Exception { + Path dexArchive = buildDexArchive(); + ImmutableList<Path> outputArchives = + runDexSplitter(256 * 256, "from_duplicate", dexArchive, dexArchive); + assertThat(outputArchives).hasSize(1); + + ImmutableSet<String> expectedFiles = dexEntries(dexArchive); + assertThat(dexEntries(outputArchives.get(0))).containsExactlyElementsIn(expectedFiles); + } + + @Test + public void testSingleInputMultidexOutput() throws Exception { + Path dexArchive = buildDexArchive(); + ImmutableList<Path> outputArchives = runDexSplitter(200, "multidex_from_single", dexArchive); + assertThat(outputArchives.size()).isGreaterThan(1); // test sanity + + ImmutableSet<String> expectedEntries = dexEntries(dexArchive); + assertExpectedEntries(outputArchives, expectedEntries); + } + + @Test + public void testMultipleInputsMultidexOutput() throws Exception { + Path dexArchive = buildDexArchive(); + Path dexArchive2 = buildDexArchive(INPUT_JAR2, "jar2.dex.zip"); + ImmutableList<Path> outputArchives = runDexSplitter(200, "multidex", dexArchive, dexArchive2); + assertThat(outputArchives.size()).isGreaterThan(1); // test sanity + + HashSet<String> expectedEntries = new HashSet<>(); + expectedEntries.addAll(dexEntries(dexArchive)); + expectedEntries.addAll(dexEntries(dexArchive2)); + assertExpectedEntries(outputArchives, expectedEntries); + } + + @Test + public void testMainDexList() throws Exception { + Path dexArchive = buildDexArchive(); + ImmutableList<Path> outputArchives = + runDexSplitter( + 200, + "main_dex_list", + MAIN_DEX_LIST_FILE, + /*minimalMainDex=*/ false, + dexArchive); + + ImmutableSet<String> expectedEntries = dexEntries(dexArchive); + assertThat(outputArchives.size()).isGreaterThan(1); // test sanity + assertThat(dexEntries(outputArchives.get(0))) + .containsAllIn(expectedMainDexEntries()); + assertExpectedEntries(outputArchives, expectedEntries); + } + + @Test + public void testMinimalMainDex() throws Exception { + Path dexArchive = buildDexArchive(); + ImmutableList<Path> outputArchives = + runDexSplitter( + 256 * 256, + "minimal_main_dex", + MAIN_DEX_LIST_FILE, + /*minimalMainDex=*/ true, + dexArchive); + + ImmutableSet<String> expectedEntries = dexEntries(dexArchive); + assertThat(outputArchives.size()).isGreaterThan(1); // test sanity + assertThat(dexEntries(outputArchives.get(0))) + .containsExactlyElementsIn(expectedMainDexEntries()); + assertExpectedEntries(outputArchives, expectedEntries); + } + + private static Iterable<String> expectedMainDexEntries() throws IOException { + return Iterables.transform( + Files.readAllLines(MAIN_DEX_LIST_FILE), + new Function<String, String>() { + @Override + public String apply(String input) { + return input + ".dex"; + } + }); + } + + @Test + public void testMultidexOffWithMultidexFlags() throws Exception { + Path dexArchive = buildDexArchive(); + try { + runDexSplitter( + 200, + "should_fail", + /*mainDexList=*/ null, + /*minimalMainDex=*/ true, + dexArchive); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertThat(e) + .hasMessageThat() + .isEqualTo("--minimal-main-dex not allowed without --main-dex-list"); + } + } + + private void assertExpectedEntries( + ImmutableList<Path> outputArchives, Set<String> expectedEntries) throws IOException { + ImmutableSet.Builder<String> actualFiles = ImmutableSet.builder(); + for (Path outputArchive : outputArchives) { + actualFiles.addAll(dexEntries(outputArchive)); + } + // ImmutableSet.Builder.build would fail if there were duplicates. Additionally we make sure + // all expected files are here + assertThat(actualFiles.build()).containsExactlyElementsIn(expectedEntries); + } + + private ImmutableSet<String> dexEntries(Path dexArchive) throws IOException { + try (ZipFile input = new ZipFile(dexArchive.toFile())) { + ImmutableSet<String> result = ImmutableSet.copyOf(Iterators.filter( + Iterators.transform(Iterators.forEnumeration(input.entries()), ZipEntryName.INSTANCE), + Predicates.containsPattern(".*\\.class.dex$"))); + assertThat(result).isNotEmpty(); // test sanity + return result; + } + } + + private ImmutableList<Path> runDexSplitter(int maxNumberOfIdxPerDex, String outputRoot, + Path... dexArchives) throws IOException { + return runDexSplitter( + maxNumberOfIdxPerDex, + outputRoot, + /*mainDexList=*/ null, + /*minimalMainDex=*/ false, + dexArchives); + } + + private ImmutableList<Path> runDexSplitter( + int maxNumberOfIdxPerDex, + String outputRoot, + @Nullable Path mainDexList, + boolean minimalMainDex, + Path... dexArchives) + throws IOException { + DexFileSplitter.Options options = new DexFileSplitter.Options(); + options.inputArchives = ImmutableList.copyOf(dexArchives); + options.outputDirectory = + FileSystems.getDefault().getPath(System.getenv("TEST_TMPDIR"), outputRoot); + options.maxNumberOfIdxPerDex = maxNumberOfIdxPerDex; + options.mainDexListFile = mainDexList; + options.minimalMainDex = minimalMainDex; + DexFileSplitter.splitIntoShards(options); + assertThat(options.outputDirectory.toFile().exists()).isTrue(); + ImmutableSet<Path> files = + ImmutableSet.copyOf(Files.newDirectoryStream(options.outputDirectory, "*.zip")); + ImmutableList.Builder<Path> result = ImmutableList.builder(); + for (int i = 1; i <= files.size(); ++i) { + Path path = options.outputDirectory.resolve(i + ".shard.zip"); + assertThat(files).contains(path); + result.add(path); + } + return result.build(); // return expected files in sorted order + } + + private Path buildDexArchive() throws Exception { + return buildDexArchive(INPUT_JAR, "libtests.dex.zip"); + } + + private Path buildDexArchive(Path inputJar, String outputZip) throws Exception { + DexBuilder.Options options = new DexBuilder.Options(); + // Use Jar file that has this test in it as the input Jar + options.inputJar = inputJar; + options.outputZip = + FileSystems.getDefault().getPath(System.getenv("TEST_TMPDIR"), outputZip); + options.maxThreads = 1; + Dexing.DexingOptions dexingOptions = new Dexing.DexingOptions(); + dexingOptions.optimize = true; + dexingOptions.positionInfo = PositionList.LINES; + DexBuilder.buildDexArchive(options, new Dexing(new DxContext(), dexingOptions)); + return options.outputZip; + } + + // Can't use lambda for Java 7 compatibility so we can run this Jar through dx without desugaring. + private enum ZipEntryName implements Function<ZipEntry, String> { + INSTANCE; + @Override + public String apply(ZipEntry input) { + return input.getName(); + } + } +} diff --git a/src/test/java/com/google/devtools/build/android/dexer/DexLimitTrackerTest.java b/src/test/java/com/google/devtools/build/android/dexer/DexLimitTrackerTest.java new file mode 100644 index 0000000000..a1e6668ac9 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/dexer/DexLimitTrackerTest.java @@ -0,0 +1,96 @@ +// Copyright 2017 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.dexer; + +import static com.google.common.truth.Truth.assertThat; + +import com.android.dex.Dex; +import com.android.dx.command.dexer.DxContext; +import com.android.dx.dex.DexOptions; +import com.android.dx.dex.cf.CfOptions; +import com.android.dx.dex.file.DexFile; +import com.google.common.io.ByteStreams; +import java.io.IOException; +import java.io.InputStream; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link DexLimitTracker}. */ +@RunWith(JUnit4.class) +public class DexLimitTrackerTest { + + private Dex dex; + + @Before + public void setUp() throws IOException { + dex = DexFiles.toDex(convertClass(DexLimitTrackerTest.class)); + } + + @Test + public void testUnderLimit() { + DexLimitTracker tracker = + new DexLimitTracker(Math.max(dex.methodIds().size(), dex.fieldIds().size())); + assertThat(tracker.track(dex)).isFalse(); + } + + @Test + public void testOverLimit() throws IOException { + DexLimitTracker tracker = + new DexLimitTracker(Math.max(dex.methodIds().size(), dex.fieldIds().size()) - 1); + assertThat(tracker.track(dex)).isTrue(); + assertThat(tracker.track(dex)).isTrue(); + assertThat(tracker.track(DexFiles.toDex(convertClass(DexLimitTracker.class)))).isTrue(); + } + + @Test + public void testRepeatedReferencesDeduped() throws IOException { + DexLimitTracker tracker = + new DexLimitTracker(Math.max(dex.methodIds().size(), dex.fieldIds().size())); + assertThat(tracker.track(dex)).isFalse(); + assertThat(tracker.track(dex)).isFalse(); + assertThat(tracker.track(dex)).isFalse(); + assertThat(tracker.track(dex)).isFalse(); + assertThat(tracker.track(DexFiles.toDex(convertClass(DexLimitTracker.class)))).isTrue(); + assertThat(tracker.track(dex)).isTrue(); + } + + @Test + public void testGoOverLimit() throws IOException { + DexLimitTracker tracker = + new DexLimitTracker(Math.max(dex.methodIds().size(), dex.fieldIds().size())); + assertThat(tracker.track(dex)).isFalse(); + assertThat(tracker.track(DexFiles.toDex(convertClass(DexLimitTracker.class)))).isTrue(); + } + + @Test + public void testClear() throws IOException { + DexLimitTracker tracker = + new DexLimitTracker(Math.max(dex.methodIds().size(), dex.fieldIds().size())); + assertThat(tracker.track(dex)).isFalse(); + assertThat(tracker.track(DexFiles.toDex(convertClass(DexLimitTracker.class)))).isTrue(); + tracker.clear(); + assertThat(tracker.track(dex)).isFalse(); + } + + private static DexFile convertClass(Class<?> clazz) throws IOException { + String path = clazz.getName().replace('.', '/') + ".class"; + try (InputStream in = + Thread.currentThread().getContextClassLoader().getResourceAsStream(path)) { + return new DexConverter(new Dexing(new DxContext(), new DexOptions(), new CfOptions())) + .toDexFile(ByteStreams.toByteArray(in), path); + } + } +} |