blob: 17e1b4b5bae1932e08b8e2ab2065012cace35679 [file] [log] [blame]
/*
* 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.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import com.facebook.buck.dalvik.ZipSplitter;
import com.facebook.buck.step.ExecutionContext;
import com.facebook.buck.util.ProjectFilesystem;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
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.CharStreams;
import org.easymock.EasyMock;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class SplitZipStepTest {
@Rule
public TemporaryFolder tempDir = new TemporaryFolder();
@Test
public void testMetaList() throws IOException {
File outJar = tempDir.newFile("test.jar");
ZipOutputStream zipOut = new ZipOutputStream(
new BufferedOutputStream(new FileOutputStream(outJar)));
Map<String, String> fileToClassName = ImmutableMap.of(
"com/facebook/foo.class", "com.facebook.foo",
"bar.class", "bar");
try {
for (String entry : fileToClassName.keySet()) {
zipOut.putNextEntry(new ZipEntry(entry));
zipOut.write(new byte[] { 0 });
}
} finally {
zipOut.close();
}
StringWriter stringWriter = new StringWriter();
BufferedWriter writer = new BufferedWriter(stringWriter);
try {
SplitZipStep.writeMetaList(writer, ImmutableList.of(outJar), DexStore.JAR);
} finally {
writer.close();
}
List<String> lines = CharStreams.readLines(new StringReader(stringWriter.toString()));
assertEquals(1, lines.size());
String line = Iterables.getFirst(lines, null);
String[] data = line.split(" ");
assertEquals(3, data.length);
// Note that we cannot test data[1] (the hash) because zip files change their hash each
// time they are written due to timestamps written into the file.
assertEquals(SmartDexingStep.transformInputToDexOutput(outJar, DexStore.JAR), data[0]);
assertTrue(String.format("Unexpected class: %s", data[2]),
fileToClassName.values().contains(data[2]));
}
@Test
public void testRequiredInPrimaryZipPredicate() throws IOException {
Path primaryDexClassesFile = Paths.get("the/manifest.txt");
SplitZipStep splitZipStep = new SplitZipStep(
/* inputPathsToSplit */ ImmutableSet.<String>of(),
/* secondaryJarMetaPath */ "",
/* primaryJarPath */ "",
/* secondaryJarDir */ "",
/* secondaryJarPattern */ "",
/* proguardMappingFile */ Optional.<Path>absent(),
/* primaryDexSubstrings */ ImmutableSet.of("List"),
Optional.of(primaryDexClassesFile),
ZipSplitter.DexSplitStrategy.MAXIMIZE_PRIMARY_DEX_SIZE,
DexStore.JAR,
/* pathToReportDir */ "",
/* useLinearAllocSplitDex */ true,
/* linearAllocHardLimit */ 4 * 1024 * 1024);
List<String> linesInManifestFile = ImmutableList.of(
"com/google/common/collect/ImmutableSortedSet",
" com/google/common/collect/ImmutableSet",
"# com/google/common/collect/ImmutableMap"
);
ProjectFilesystem projectFilesystem = EasyMock.createMock(ProjectFilesystem.class);
EasyMock.expect(projectFilesystem.readLines(primaryDexClassesFile))
.andReturn(linesInManifestFile);
ExecutionContext context = EasyMock.createMock(ExecutionContext.class);
EasyMock.expect(context.getProjectFilesystem()).andReturn(projectFilesystem);
EasyMock.replay(projectFilesystem, context);
Predicate<String> requiredInPrimaryZipPredicate = splitZipStep
.createRequiredInPrimaryZipPredicate(context);
assertTrue(
"All non-.class files should be accepted.",
requiredInPrimaryZipPredicate.apply("apples.txt"));
assertTrue(
"com/google/common/collect/ImmutableSortedSet.class is listed in the manifest verbatim.",
requiredInPrimaryZipPredicate.apply("com/google/common/collect/ImmutableSortedSet.class"));
assertTrue(
"com/google/common/collect/ImmutableSet.class is in the manifest with whitespace.",
requiredInPrimaryZipPredicate.apply("com/google/common/collect/ImmutableSet.class"));
assertFalse(
"com/google/common/collect/ImmutableSet.class cannot have whitespace as param.",
requiredInPrimaryZipPredicate.apply(" com/google/common/collect/ImmutableSet.class"));
assertFalse(
"com/google/common/collect/ImmutableMap.class is commented out.",
requiredInPrimaryZipPredicate.apply("com/google/common/collect/ImmutableMap.class"));
assertFalse(
"com/google/common/collect/Iterables.class is not even mentioned.",
requiredInPrimaryZipPredicate.apply("com/google/common/collect/Iterables.class"));
assertTrue(
"java/awt/List.class matches the substring 'List'.",
requiredInPrimaryZipPredicate.apply("java/awt/List.class"));
assertFalse(
"Substring matching is case-sensitive.",
requiredInPrimaryZipPredicate.apply("shiny/Glistener.class"));
EasyMock.verify(projectFilesystem, context);
}
@Test
public void testRequiredInPrimaryZipPredicateWithProguard() throws IOException {
Path proguardMappingFile = Paths.get("the/mapping.txt");
Path primaryDexClassesFile = Paths.get("the/manifest.txt");
SplitZipStep splitZipStep = new SplitZipStep(
/* inputPathsToSplit */ ImmutableSet.<String>of(),
/* secondaryJarMetaPath */ "",
/* primaryJarPath */ "",
/* secondaryJarDir */ "",
/* secondaryJarPattern */ "",
/* proguardMappingFile */ Optional.of(proguardMappingFile),
/* primaryDexSubstrings */ ImmutableSet.of("/primary/", "x/"),
Optional.of(primaryDexClassesFile),
ZipSplitter.DexSplitStrategy.MAXIMIZE_PRIMARY_DEX_SIZE,
DexStore.JAR,
/* pathToReportDir */ "",
/* useLinearAllocSplitDex */ true,
/* linearAllocHardLimit */ 4 * 1024 * 1024);
List<String> linesInMappingFile = ImmutableList.of(
"foo.bar.MappedPrimary -> foo.bar.a:",
"foo.bar.MappedSecondary -> foo.bar.b:",
"foo.bar.UnmappedPrimary -> foo.bar.UnmappedPrimary:",
"foo.bar.UnmappedSecondary -> foo.bar.UnmappedSecondary:",
"foo.primary.MappedPackage -> x.a:",
"foo.secondary.MappedPackage -> x.b:",
"foo.primary.UnmappedPackage -> foo.primary.UnmappedPackage:"
);
List<String> linesInManifestFile = ImmutableList.of(
// Actual primary dex classes.
"foo/bar/MappedPrimary",
"foo/bar/UnmappedPrimary",
// Red herrings!
"foo/bar/b",
"x/b"
);
ProjectFilesystem projectFilesystem = EasyMock.createMock(ProjectFilesystem.class);
EasyMock.expect(projectFilesystem.readLines(primaryDexClassesFile))
.andReturn(linesInManifestFile);
EasyMock.expect(projectFilesystem.exists(proguardMappingFile.toString()))
.andReturn(true);
EasyMock.expect(projectFilesystem.readLines(proguardMappingFile))
.andReturn(linesInMappingFile);
ExecutionContext context = EasyMock.createMock(ExecutionContext.class);
EasyMock.expect(context.getProjectFilesystem()).andReturn(projectFilesystem).anyTimes();
EasyMock.replay(projectFilesystem, context);
Predicate<String> requiredInPrimaryZipPredicate = splitZipStep
.createRequiredInPrimaryZipPredicate(context);
assertTrue(
"Mapped class from primary list should be in primary.",
requiredInPrimaryZipPredicate.apply("foo/bar/a.class"));
assertTrue(
"Unmapped class from primary list should be in primary.",
requiredInPrimaryZipPredicate.apply("foo/bar/UnmappedPrimary.class"));
assertTrue(
"Mapped class from substring should be in primary.",
requiredInPrimaryZipPredicate.apply("x/a.class"));
assertTrue(
"Unmapped class from substring should be in primary.",
requiredInPrimaryZipPredicate.apply("foo/primary/UnmappedPackage.class"));
assertFalse(
"Mapped class with obfuscated name match should not be in primary.",
requiredInPrimaryZipPredicate.apply("foo/bar/b.class"));
assertFalse(
"Unmapped class name should not randomly be in primary.",
requiredInPrimaryZipPredicate.apply("foo/bar/UnmappedSecondary.class"));
assertFalse(
"Map class with obfuscated name substring should not be in primary.",
requiredInPrimaryZipPredicate.apply("x/b.class"));
EasyMock.verify(projectFilesystem, context);
}
@Test
public void testClassFilePattern() {
assertTrue(SplitZipStep.classFilePattern.matcher(
"com/facebook/orca/threads/ParticipantInfo.class").matches());
assertTrue(SplitZipStep.classFilePattern.matcher(
"com/facebook/orca/threads/ParticipantInfo$1.class").matches());
}
}