/* * 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.easymock.EasyMock.expect; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import com.facebook.buck.android.SmartDexingStep.DxPseudoRule; import com.facebook.buck.io.ProjectFilesystem; import com.facebook.buck.step.ExecutionContext; import com.facebook.buck.step.Step; import com.facebook.buck.step.TestExecutionContext; import com.facebook.buck.testutil.FakeProjectFilesystem; import com.facebook.buck.testutil.MoreAsserts; import com.facebook.buck.util.sha1.Sha1HashCode; import com.google.common.base.Charsets; import com.google.common.base.Joiner; import com.google.common.base.Strings; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.io.Files; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.EnumSet; import java.util.Optional; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.easymock.EasyMockSupport; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; public class SmartDexingStepTest extends EasyMockSupport { @Rule public TemporaryFolder tmpDir = new TemporaryFolder(); /** Tests whether pseudo rule cache detection is working properly. */ @Test public void testDxPseudoRuleCaching() throws InterruptedException, IOException { File testIn = new File(tmpDir.getRoot(), "testIn"); try (ZipOutputStream zipOut = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(testIn)))) { zipOut.putNextEntry(new ZipEntry("foobar")); zipOut.write(new byte[] {0}); } File outputFile = tmpDir.newFile("out.dex"); Path outputHashFile = new File(tmpDir.getRoot(), "out.dex.hash").toPath(); Files.write("dummy", outputHashFile.toFile(), Charsets.UTF_8); ProjectFilesystem filesystem = new ProjectFilesystem(tmpDir.getRoot().toPath()); Sha1HashCode actualHashCode = Sha1HashCode.of(Strings.repeat("a", 40)); DxPseudoRule rule = new DxPseudoRule( filesystem, ImmutableMap.of(testIn.toPath(), actualHashCode), ImmutableSet.of(testIn.toPath()), outputFile.toPath(), outputHashFile, EnumSet.of(DxStep.Option.NO_OPTIMIZE), Optional.empty(), Optional.empty()); assertFalse("'dummy' is not a matching input hash", rule.checkIsCached()); // Write the real hash into the output hash file and ensure that checkIsCached now // yields true. String actualHash = rule.hashInputs(); assertFalse(actualHash.isEmpty()); Files.write(actualHash, outputHashFile.toFile(), Charsets.UTF_8); assertTrue("Matching input hash should be considered cached", rule.checkIsCached()); } @Test public void testCreateDxStepForDxPseudoRuleWithXzOutput() throws InterruptedException, IOException { ProjectFilesystem filesystem = FakeProjectFilesystem.createJavaOnlyFilesystem(); ImmutableList<Path> filesToDex = ImmutableList.of(Paths.get("foo.dex.jar"), Paths.get("bar.dex.jar")); Path outputPath = Paths.get("classes.dex.jar.xz"); EnumSet<DxStep.Option> dxOptions = EnumSet.noneOf(DxStep.Option.class); ImmutableList.Builder<Step> steps = new ImmutableList.Builder<>(); SmartDexingStep.createDxStepForDxPseudoRule( steps, filesystem, filesToDex, outputPath, dxOptions, Optional.empty(), Optional.empty()); MoreAsserts.assertSteps( "Steps should repack zip entries and then compress using xz.", ImmutableList.of( Joiner.on(" ") .join( "(cd", filesystem.getRootPath(), "&&", Paths.get("/usr/bin/dx"), "--dex --output", filesystem.resolve("classes.dex.tmp.jar"), filesystem.resolve("foo.dex.jar"), filesystem.resolve("bar.dex.jar") + ")"), "repack classes.dex.tmp.jar in classes.dex.jar", "rm -f " + filesystem.resolve("classes.dex.tmp.jar"), "dex_meta dexPath:classes.dex.jar dexMetaPath:classes.dex.jar.meta", "xz -z -4 --check=crc32 classes.dex.jar"), steps.build(), createMockedExecutionContext()); verifyAll(); } @Test public void testCreateDxStepForDxPseudoRuleWithXzOutputNonDefaultCompression() throws InterruptedException, IOException { ProjectFilesystem filesystem = FakeProjectFilesystem.createJavaOnlyFilesystem(); ImmutableList<Path> filesToDex = ImmutableList.of(Paths.get("foo.dex.jar"), Paths.get("bar.dex.jar")); Path outputPath = Paths.get("classes.dex.jar.xz"); EnumSet<DxStep.Option> dxOptions = EnumSet.noneOf(DxStep.Option.class); ImmutableList.Builder<Step> steps = new ImmutableList.Builder<>(); SmartDexingStep.createDxStepForDxPseudoRule( steps, filesystem, filesToDex, outputPath, dxOptions, Optional.of(9), Optional.empty()); MoreAsserts.assertSteps( "Steps should repack zip entries and then compress using xz.", ImmutableList.of( Joiner.on(" ") .join( "(cd", filesystem.getRootPath(), "&&", Paths.get("/usr/bin/dx"), "--dex --output", filesystem.resolve("classes.dex.tmp.jar"), filesystem.resolve("foo.dex.jar"), filesystem.resolve("bar.dex.jar") + ")"), "repack classes.dex.tmp.jar in classes.dex.jar", "rm -f " + filesystem.resolve("classes.dex.tmp.jar"), "dex_meta dexPath:classes.dex.jar dexMetaPath:classes.dex.jar.meta", "xz -z -9 --check=crc32 classes.dex.jar"), steps.build(), createMockedExecutionContext()); verifyAll(); } @Test public void testCreateDxStepForDxPseudoRuleWithDexOutput() throws InterruptedException, IOException { ProjectFilesystem filesystem = FakeProjectFilesystem.createJavaOnlyFilesystem(); ImmutableList<Path> filesToDex = ImmutableList.of(Paths.get("foo.dex.jar"), Paths.get("bar.dex.jar")); Path outputPath = Paths.get("classes.dex"); EnumSet<DxStep.Option> dxOptions = EnumSet.noneOf(DxStep.Option.class); ImmutableList.Builder<Step> steps = new ImmutableList.Builder<>(); SmartDexingStep.createDxStepForDxPseudoRule( steps, filesystem, filesToDex, outputPath, dxOptions, Optional.empty(), Optional.empty()); assertEquals( Joiner.on(" ") .join( "(cd", filesystem.getRootPath(), "&&", Paths.get("/usr/bin/dx"), "--dex --output", filesystem.resolve("classes.dex"), filesystem.resolve("foo.dex.jar"), filesystem.resolve("bar.dex.jar") + ")"), Iterables.getOnlyElement(steps.build()).getDescription(createMockedExecutionContext())); verifyAll(); } @Test public void testCreateDxStepForDxPseudoRuleWithDexJarOutput() throws InterruptedException, IOException { ProjectFilesystem filesystem = FakeProjectFilesystem.createJavaOnlyFilesystem(); ImmutableList<Path> filesToDex = ImmutableList.of(Paths.get("foo.dex.jar"), Paths.get("bar.dex.jar")); Path outputPath = Paths.get("classes.dex.jar"); EnumSet<DxStep.Option> dxOptions = EnumSet.noneOf(DxStep.Option.class); ImmutableList.Builder<Step> steps = new ImmutableList.Builder<>(); SmartDexingStep.createDxStepForDxPseudoRule( steps, filesystem, filesToDex, outputPath, dxOptions, Optional.empty(), Optional.empty()); MoreAsserts.assertSteps( "Wrong steps", ImmutableList.of( Joiner.on(" ") .join( "(cd", filesystem.getRootPath(), "&&", Paths.get("/usr/bin/dx"), "--dex --output", filesystem.resolve("classes.dex.jar"), filesystem.resolve("foo.dex.jar"), filesystem.resolve("bar.dex.jar") + ")"), "dex_meta dexPath:classes.dex.jar dexMetaPath:classes.dex.jar.meta"), steps.build(), createMockedExecutionContext()); verifyAll(); } @Test(expected = IllegalArgumentException.class) public void testCreateDxStepForDxPseudoRuleWithUnrecognizedOutput() throws InterruptedException { ProjectFilesystem filesystem = new ProjectFilesystem(tmpDir.getRoot().toPath()); ImmutableList<Path> filesToDex = ImmutableList.of(Paths.get("foo.dex.jar"), Paths.get("bar.dex.jar")); Path outputPath = Paths.get("classes.flex"); EnumSet<DxStep.Option> dxOptions = EnumSet.noneOf(DxStep.Option.class); SmartDexingStep.createDxStepForDxPseudoRule( new ImmutableList.Builder<>(), filesystem, filesToDex, outputPath, dxOptions, Optional.empty(), Optional.empty()); } private ExecutionContext createMockedExecutionContext() throws IOException { AndroidPlatformTarget androidPlatformTarget = createMock(AndroidPlatformTarget.class); expect(androidPlatformTarget.getDxExecutable()).andStubReturn(Paths.get("/usr/bin/dx")); replayAll(); return TestExecutionContext.newBuilder() .setAndroidPlatformTargetSupplier(Suppliers.ofInstance(androidPlatformTarget)) .build(); } }