/*
* Copyright 2014-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.assertNotEquals;
import com.facebook.buck.artifact_cache.ArtifactCache;
import com.facebook.buck.artifact_cache.DirArtifactCacheTestUtil;
import com.facebook.buck.artifact_cache.TestArtifactCaches;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.model.BuildTargetFactory;
import com.facebook.buck.model.BuildTargets;
import com.facebook.buck.rules.RuleKey;
import com.facebook.buck.testutil.integration.BuckBuildLog;
import com.facebook.buck.testutil.integration.ProjectWorkspace;
import com.facebook.buck.testutil.integration.TemporaryPaths;
import com.facebook.buck.testutil.integration.TestDataHelper;
import com.facebook.buck.testutil.integration.ZipInspector;
import com.facebook.buck.util.ProcessExecutor;
import com.facebook.buck.util.VersionStringComparator;
import com.facebook.buck.util.sha1.Sha1HashCode;
import com.google.common.base.Splitter;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableMap;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import java.util.regex.Pattern;
import org.junit.Assume;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
public class AndroidResourceFilterIntegrationTest {
private static boolean isBuildToolsNew;
private static Path pathToAapt;
@Rule public TemporaryPaths tmpFolder = new TemporaryPaths();
private ProjectWorkspace workspace;
private ProjectFilesystem filesystem;
@BeforeClass
public static void findBuildToolsVersion() throws InterruptedException {
AssumeAndroidPlatform.assumeSdkIsAvailable();
ProjectFilesystem filesystem = new ProjectFilesystem(Paths.get(".").toAbsolutePath());
AndroidDirectoryResolver resolver =
new DefaultAndroidDirectoryResolver(
filesystem.getRootPath().getFileSystem(),
ImmutableMap.copyOf(System.getenv()),
Optional.empty(),
Optional.empty());
pathToAapt =
AndroidPlatformTarget.getDefaultPlatformTarget(resolver, Optional.empty(), Optional.empty())
.getAaptExecutable();
String buildToolsVersion = pathToAapt.getParent().getFileName().toString();
isBuildToolsNew = new VersionStringComparator().compare(buildToolsVersion, "21") >= 0;
}
@Before
public void setUp() throws InterruptedException, IOException {
workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "android_project", tmpFolder);
workspace.setUp();
filesystem = new ProjectFilesystem(workspace.getDestPath());
}
@Test
public void testApkWithoutResourceFilter() throws IOException {
String target = "//apps/sample:app";
ProjectWorkspace.ProcessResult result = workspace.runBuckCommand("build", target);
result.assertSuccess();
Path apkFile =
workspace.getPath(
BuildTargets.getGenPath(filesystem, BuildTargetFactory.newInstance(target), "%s.apk"));
ZipInspector zipInspector = new ZipInspector(apkFile);
if (isBuildToolsNew) {
zipInspector.assertFileExists("res/drawable-mdpi-v4/app_icon.png");
zipInspector.assertFileExists("res/drawable-hdpi-v4/app_icon.png");
zipInspector.assertFileExists("res/drawable-xhdpi-v4/app_icon.png");
} else {
zipInspector.assertFileExists("res/drawable-mdpi/app_icon.png");
zipInspector.assertFileExists("res/drawable-hdpi/app_icon.png");
zipInspector.assertFileExists("res/drawable-xhdpi/app_icon.png");
}
}
@Test
public void testApkWithMdpiFilter() throws IOException {
String target = "//apps/sample:app_mdpi";
ProjectWorkspace.ProcessResult result = workspace.runBuckCommand("build", target);
result.assertSuccess();
Path apkFile =
workspace.getPath(
BuildTargets.getGenPath(filesystem, BuildTargetFactory.newInstance(target), "%s.apk"));
ZipInspector zipInspector = new ZipInspector(apkFile);
if (isBuildToolsNew) {
zipInspector.assertFileExists("res/drawable-mdpi-v4/app_icon.png");
zipInspector.assertFileDoesNotExist("res/drawable-hdpi-v4/app_icon.png");
zipInspector.assertFileDoesNotExist("res/drawable-xhdpi-v4/app_icon.png");
} else {
zipInspector.assertFileExists("res/drawable-mdpi/app_icon.png");
zipInspector.assertFileDoesNotExist("res/drawable-hdpi/app_icon.png");
zipInspector.assertFileDoesNotExist("res/drawable-xhdpi/app_icon.png");
}
}
@Test
public void testModifyingImageRebuildsResourcesFilter() throws IOException {
String target = "//apps/sample:app_mdpi";
ProjectWorkspace.ProcessResult result = workspace.runBuckBuild(target);
result.assertSuccess();
Path apkFile =
workspace.getPath(
BuildTargets.getGenPath(filesystem, BuildTargetFactory.newInstance(target), "%s.apk"));
String iconPath =
isBuildToolsNew ? "res/drawable-mdpi-v4/app_icon.png" : "res/drawable-mdpi/app_icon.png";
long firstImageCrc = new ZipInspector(apkFile).getCrc(iconPath);
workspace.copyFile(
"res/com/sample/base/res/drawable-hdpi/app_icon.png",
"res/com/sample/base/res/drawable-mdpi/app_icon.png");
workspace.resetBuildLogFile();
result = workspace.runBuckBuild(target);
result.assertSuccess();
BuckBuildLog buildLog = workspace.getBuildLog();
buildLog.assertTargetBuiltLocally(target);
apkFile =
workspace.getPath(
BuildTargets.getGenPath(filesystem, BuildTargetFactory.newInstance(target), "%s.apk"));
long secondImageCrc = new ZipInspector(apkFile).getCrc(iconPath);
assertNotEquals(firstImageCrc, secondImageCrc);
}
@Test
public void testApkWithXhdpiAndHdpiFilter() throws IOException {
String target = "//apps/sample:app_hdpi_xhdpi";
ProjectWorkspace.ProcessResult result = workspace.runBuckCommand("build", target);
result.assertSuccess();
Path apkFile =
workspace.getPath(
BuildTargets.getGenPath(filesystem, BuildTargetFactory.newInstance(target), "%s.apk"));
ZipInspector zipInspector = new ZipInspector(apkFile);
if (isBuildToolsNew) {
zipInspector.assertFileDoesNotExist("res/drawable-mdpi-v4/app_icon.png");
zipInspector.assertFileExists("res/drawable-hdpi-v4/app_icon.png");
zipInspector.assertFileExists("res/drawable-xhdpi-v4/app_icon.png");
} else {
zipInspector.assertFileDoesNotExist("res/drawable-mdpi/app_icon.png");
zipInspector.assertFileExists("res/drawable-hdpi/app_icon.png");
zipInspector.assertFileExists("res/drawable-xhdpi/app_icon.png");
}
}
@Test
public void testApkWithStringsAsAssets() throws IOException {
String target = "//apps/sample:app_comp_str";
ProjectWorkspace.ProcessResult result = workspace.runBuckCommand("build", target);
result.assertSuccess();
Path apkFile =
workspace.getPath(
BuildTargets.getGenPath(filesystem, BuildTargetFactory.newInstance(target), "%s.apk"));
ZipInspector zipInspector = new ZipInspector(apkFile);
zipInspector.assertFileExists("assets/strings/fr.fbstr");
}
@Test
public void testStringArtifactsAreCached() throws InterruptedException, IOException {
Assume.assumeFalse(true);
workspace.enableDirCache();
workspace.runBuckBuild("//apps/sample:app_comp_str").assertSuccess();
BuckBuildLog buildLog = workspace.getBuildLog();
Sha1HashCode androidBinaryRuleKey = buildLog.getRuleKey("//apps/sample:app_comp_str");
ArtifactCache cache =
TestArtifactCaches.createDirCacheForTest(
workspace.getPath("."), filesystem.getBuckPaths().getCacheDir());
Path cachedFile =
DirArtifactCacheTestUtil.getPathForRuleKey(
cache, new RuleKey(androidBinaryRuleKey.getHash()), Optional.empty());
Files.delete(workspace.resolve(cachedFile));
workspace.runBuckCommand("clean").assertSuccess();
workspace.runBuckBuild("//apps/sample:app_comp_str").assertSuccess();
}
@Test
public void testApkWithStringsAsAssetsAndResourceFilter() throws IOException {
String target = "//apps/sample:app_comp_str_xhdpi";
ProjectWorkspace.ProcessResult result = workspace.runBuckBuild(target);
result.assertSuccess();
Path apkFile =
workspace.getPath(
BuildTargets.getGenPath(filesystem, BuildTargetFactory.newInstance(target), "%s.apk"));
ZipInspector zipInspector = new ZipInspector(apkFile);
zipInspector.assertFileExists("assets/strings/fr.fbstr");
if (isBuildToolsNew) {
zipInspector.assertFileExists("res/drawable-xhdpi-v4/app_icon.png");
zipInspector.assertFileDoesNotExist("res/drawable-hdpi-v4/app_icon.png");
zipInspector.assertFileDoesNotExist("res/drawable-mdpi-v4/app_icon.png");
} else {
zipInspector.assertFileExists("res/drawable-xhdpi/app_icon.png");
zipInspector.assertFileDoesNotExist("res/drawable-hdpi/app_icon.png");
zipInspector.assertFileDoesNotExist("res/drawable-mdpi/app_icon.png");
}
}
@Test
public void testAsset() throws IOException {
workspace.enableDirCache();
String target = "//apps/sample:app";
workspace.runBuckBuild(target).assertSuccess();
Path apkFile =
workspace.getPath(
BuildTargets.getGenPath(filesystem, BuildTargetFactory.newInstance(target), "%s.apk"));
ZipInspector zipInspector = new ZipInspector(apkFile);
long firstCrc = zipInspector.getCrc("assets/asset_file.txt");
workspace.replaceFileContents(
"res/com/sample/asset_only/assets/asset_file.txt", "Hello", "Bye");
workspace.runBuckBuild(target).assertSuccess();
apkFile =
workspace.getPath(
BuildTargets.getGenPath(filesystem, BuildTargetFactory.newInstance(target), "%s.apk"));
zipInspector = new ZipInspector(apkFile);
long secondCrc = zipInspector.getCrc("assets/asset_file.txt");
assertNotEquals("Rebuilt APK file must include the new asset file.", firstCrc, secondCrc);
}
@Test
public void testEnglishBuildDoesntContainFrenchStrings()
throws IOException, InterruptedException {
String target = "//apps/sample:app";
workspace.runBuckBuild(target).assertSuccess();
Path apkFile =
workspace.getPath(
BuildTargets.getGenPath(filesystem, BuildTargetFactory.newInstance(target), "%s.apk"));
int matchingLines = runAaptDumpResources(apkFile);
assertEquals(2, matchingLines);
target = "//apps/sample:app_en";
workspace.runBuckBuild(target).assertSuccess();
apkFile =
workspace.getPath(
BuildTargets.getGenPath(filesystem, BuildTargetFactory.newInstance(target), "%s.apk"));
matchingLines = runAaptDumpResources(apkFile);
assertEquals(1, matchingLines);
}
private int runAaptDumpResources(Path apkFile) throws IOException, InterruptedException {
final Pattern pattern = Pattern.compile(".*com.example:string/base_button: t=.*");
ProcessExecutor.Result result =
workspace.runCommand(
pathToAapt.toAbsolutePath().toString(),
"dump",
"resources",
apkFile.toAbsolutePath().toString());
assertEquals(0, result.getExitCode());
return FluentIterable.from(Splitter.on('\n').split(result.getStdout().orElse("")))
.filter(input -> pattern.matcher(input).matches())
.size();
}
}