/* * Copyright 2012-present Facebook, Inc. * * 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.facebook.buck.android; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.oneOf; import static org.junit.Assert.assertThat; import com.facebook.buck.model.BuildTargetFactory; import com.facebook.buck.rules.TargetGraph; import com.facebook.buck.step.Step; import com.facebook.buck.testutil.FakeProjectFilesystem; import com.facebook.buck.util.HumanReadableException; import com.facebook.buck.util.sha1.Sha1HashCode; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableSet; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Optional; import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; public class PreDexedFilesSorterTest { @Rule public TemporaryFolder tempDir = new TemporaryFolder(); private static final String PRIMARY_DEX_PATTERN = "primary"; private static final long DEX_WEIGHT_LIMIT = 10 * 1024 * 1024; private static final int STANDARD_DEX_FILE_ESTIMATE = (int) DEX_WEIGHT_LIMIT / 10 - 1; private APKModuleGraph moduleGraph; private APKModule extraModule; @Before public void setUp() throws IOException { moduleGraph = new APKModuleGraph( TargetGraph.EMPTY, BuildTargetFactory.newInstance("//fakeTarget:yes"), Optional.empty()); extraModule = APKModule.builder().setName("extra").build(); } @Test public void testPrimaryOnly() throws IOException { int numberOfPrimaryDexes = 10; int numberOfSecondaryDexes = 0; int numberOfExtraDexes = 0; ImmutableMap<String, PreDexedFilesSorter.Result> sortResults = generatePreDexSorterResults( numberOfPrimaryDexes, numberOfSecondaryDexes, numberOfExtraDexes); for (String store : sortResults.keySet()) { assertThat(store, is(moduleGraph.getRootAPKModule().getName())); } assertThat(sortResults.size(), is(1)); } @Test(expected = HumanReadableException.class) public void testPrimaryOverFlow() throws IOException { int numberOfPrimaryDexes = 15; int numberOfSecondaryDexes = 0; int numberOfExtraDexes = 0; generatePreDexSorterResults(numberOfPrimaryDexes, numberOfSecondaryDexes, numberOfExtraDexes); } @Test public void testPrimaryAndSecondary() throws IOException { int numberOfPrimaryDexes = 10; int numberOfSecondaryDexes = 10; int numberOfExtraDexes = 0; ImmutableMap<String, PreDexedFilesSorter.Result> sortResults = generatePreDexSorterResults( numberOfPrimaryDexes, numberOfSecondaryDexes, numberOfExtraDexes); PreDexedFilesSorter.Result rootResult = sortResults.get(APKModuleGraph.ROOT_APKMODULE_NAME); for (String store : sortResults.keySet()) { assertThat(store, is(moduleGraph.getRootAPKModule().getName())); } assertThat(rootResult.primaryDexInputs.size(), is(numberOfPrimaryDexes)); assertThat(rootResult.secondaryOutputToInputs.keySet().size(), is(1)); assertThat(rootResult.secondaryOutputToInputs.size(), is(numberOfSecondaryDexes + 1)); } @Test public void testPrimaryAndMultipleSecondary() throws IOException { int numberOfPrimaryDexes = 10; int numberOfSecondaryDexes = 15; int numberOfExtraDexes = 0; ImmutableMap<String, PreDexedFilesSorter.Result> sortResults = generatePreDexSorterResults( numberOfPrimaryDexes, numberOfSecondaryDexes, numberOfExtraDexes); for (String store : sortResults.keySet()) { assertThat(store, is(moduleGraph.getRootAPKModule().getName())); } PreDexedFilesSorter.Result rootResult = sortResults.get(APKModuleGraph.ROOT_APKMODULE_NAME); assertThat(rootResult.metadataTxtDexEntries.size(), is(2)); for (DexWithClasses dexWithClasses : rootResult.metadataTxtDexEntries.values()) { assertThat(dexWithClasses.getClassNames().asList().get(0), Matchers.endsWith("/Canary")); } assertThat(rootResult.primaryDexInputs.size(), is(numberOfPrimaryDexes)); // check that we have 2 secondary stores assertThat(rootResult.secondaryOutputToInputs.keySet().size(), is(2)); // check that we have 11 secondary inputs + 2 from canaries assertThat(rootResult.secondaryOutputToInputs.size(), is(numberOfSecondaryDexes + 2)); } @Test public void testPrimaryAndExtraModule() throws IOException { int numberOfPrimaryDexes = 10; int numberOfSecondaryDexes = 0; int numberOfExtraDexes = 10; ImmutableMap<String, PreDexedFilesSorter.Result> sortResults = generatePreDexSorterResults( numberOfPrimaryDexes, numberOfSecondaryDexes, numberOfExtraDexes); for (String store : sortResults.keySet()) { assertThat(store, oneOf(moduleGraph.getRootAPKModule().getName(), extraModule.getName())); } assertThat(sortResults.size(), is(2)); PreDexedFilesSorter.Result rootResult = sortResults.get(APKModuleGraph.ROOT_APKMODULE_NAME); assertThat(rootResult.primaryDexInputs.size(), is(numberOfPrimaryDexes)); PreDexedFilesSorter.Result extraResult = sortResults.get(extraModule.getName()); assertThat(extraResult.metadataTxtDexEntries.size(), is(1)); assertThat(extraResult.secondaryOutputToInputs.size(), is(numberOfExtraDexes + 1)); for (DexWithClasses dexWithClasses : extraResult.metadataTxtDexEntries.values()) { assertThat(dexWithClasses.getClassNames().asList().get(0), Matchers.endsWith("/Canary")); } } @Test public void testPrimarySecondaryAndExtraModule() throws IOException { int numberOfPrimaryDexes = 10; int numberOfSecondaryDexes = 15; int numberOfExtraDexes = 15; ImmutableMap<String, PreDexedFilesSorter.Result> sortResults = generatePreDexSorterResults( numberOfPrimaryDexes, numberOfSecondaryDexes, numberOfExtraDexes); for (String store : sortResults.keySet()) { assertThat(store, oneOf(moduleGraph.getRootAPKModule().getName(), extraModule.getName())); } PreDexedFilesSorter.Result rootResult = sortResults.get(APKModuleGraph.ROOT_APKMODULE_NAME); PreDexedFilesSorter.Result extraResult = sortResults.get(extraModule.getName()); assertThat(sortResults.size(), is(2)); assertThat(rootResult.primaryDexInputs.size(), is(numberOfPrimaryDexes)); assertThat(rootResult.metadataTxtDexEntries.size(), is(2)); assertThat(extraResult.metadataTxtDexEntries.size(), is(2)); for (DexWithClasses dexWithClasses : rootResult.metadataTxtDexEntries.values()) { assertThat(dexWithClasses.getClassNames().asList().get(0), Matchers.endsWith("/Canary")); } for (DexWithClasses dexWithClasses : extraResult.metadataTxtDexEntries.values()) { assertThat(dexWithClasses.getClassNames().asList().get(0), Matchers.endsWith("/Canary")); } } private ImmutableMap<String, PreDexedFilesSorter.Result> generatePreDexSorterResults( int numberOfPrimaryDexes, int numberOfSecondaryDexes, int numberOfExtraDexes) throws IOException { ImmutableMultimap.Builder<APKModule, DexWithClasses> inputDexes = ImmutableMultimap.builder(); for (int i = 0; i < numberOfPrimaryDexes; i++) { inputDexes.put( moduleGraph.getRootAPKModule(), createFakeDexWithClasses( Paths.get("primary").resolve(String.format("/primary%d.dex", i)), ImmutableSet.of(String.format("primary.primary%d.class", i)), STANDARD_DEX_FILE_ESTIMATE)); } for (int i = 0; i < numberOfSecondaryDexes; i++) { inputDexes.put( moduleGraph.getRootAPKModule(), createFakeDexWithClasses( Paths.get("secondary").resolve(String.format("secondary%d.dex", i)), ImmutableSet.of(String.format("secondary.secondary%d.class", i)), STANDARD_DEX_FILE_ESTIMATE)); } for (int i = 0; i < numberOfExtraDexes; i++) { inputDexes.put( extraModule, createFakeDexWithClasses( Paths.get("extra").resolve(String.format("extra%d.dex", i)), ImmutableSet.of(String.format("extra.extra%d.class", i)), STANDARD_DEX_FILE_ESTIMATE)); } PreDexedFilesSorter sorter = new PreDexedFilesSorter( Optional.empty(), inputDexes.build(), ImmutableSet.of(PRIMARY_DEX_PATTERN), moduleGraph, tempDir.newFolder("scratch").toPath(), DEX_WEIGHT_LIMIT, DexStore.JAR, tempDir.newFolder("secondary").toPath(), tempDir.newFolder("additional").toPath()); ImmutableList.Builder<Step> steps = ImmutableList.builder(); FakeProjectFilesystem filesystem = new FakeProjectFilesystem(); return sorter.sortIntoPrimaryAndSecondaryDexes(filesystem, steps); } private DexWithClasses createFakeDexWithClasses( final Path pathToDex, final ImmutableSet<String> classNames, final int weightEstimate) { return new DexWithClasses() { @Override public Path getPathToDexFile() { return pathToDex; } @Override public ImmutableSet<String> getClassNames() { return classNames; } @Override public Sha1HashCode getClassesHash() { return Sha1HashCode.of(String.format("%040x", classNames.hashCode())); } @Override public int getWeightEstimate() { return weightEstimate; } }; } }