blob: 75857ae43ce9c72120302707e45e7790aa01cded [file] [log] [blame]
// 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;
import static com.google.common.truth.Truth.assertThat;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.PatternFilenameFilter;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Tests for {@link ManifestMergerAction}. */
@RunWith(JUnit4.class)
public class ManifestMergerActionTest {
private Path working;
/**
* Returns a runfile's path.
*
* <p>The `path` must specify a valid runfile, meaning on Windows (where $RUNFILES_MANIFEST_ONLY
* is 1) the `path` must exactly match a runfiles manifest entry, and on Linux/MacOS `path` must
* point to a valid file in the runfiles directory.
*/
@Nullable
private static Path rlocation(String path) throws IOException {
FileSystem fs = FileSystems.getDefault();
if (fs.getPath(path).isAbsolute()) {
return fs.getPath(path);
}
if ("1".equals(System.getenv("RUNFILES_MANIFEST_ONLY"))) {
String manifest = System.getenv("RUNFILES_MANIFEST_FILE");
assertThat(manifest).isNotNull();
try (BufferedReader r =
Files.newBufferedReader(Paths.get(manifest), Charset.defaultCharset())) {
Splitter splitter = Splitter.on(' ').limit(2);
String line = null;
while ((line = r.readLine()) != null) {
List<String> tokens = splitter.splitToList(line);
if (tokens.size() == 2) {
if (tokens.get(0).equals(path)) {
return fs.getPath(tokens.get(1));
}
}
}
}
return null;
} else {
String runfiles = System.getenv("RUNFILES_DIR");
if (runfiles == null) {
runfiles = System.getenv("JAVA_RUNFILES");
assertThat(runfiles).isNotNull();
}
Path result = fs.getPath(runfiles).resolve(path);
assertThat(result.toFile().exists()).isTrue(); // comply with function's contract
return result;
}
}
@Before public void setup() throws Exception {
working = Files.createTempDirectory(toString());
working.toFile().deleteOnExit();
}
@Test
public void testMerge_GenerateDummyManifest() throws Exception {
Files.createDirectories(working.resolve("output"));
Path mergedManifest = working.resolve("output/mergedManifest.xml");
ManifestMergerAction.main(
new String[] {
"--customPackage",
"foo.bar.baz",
"--mergeType",
"LIBRARY",
"--manifestOutput",
mergedManifest.toString()
});
assertThat(
Joiner.on(" ")
.join(Files.readAllLines(mergedManifest, UTF_8))
.replaceAll("\\s+", " ")
.trim())
.isEqualTo(
"<?xml version=\"1.0\" encoding=\"utf-8\"?> "
+ "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" "
+ "package=\"foo.bar.baz\" />");
}
@Test public void testMerge() throws Exception {
String dataDir =
Paths.get(System.getenv("TEST_WORKSPACE"), System.getenv("TEST_BINARY"))
.resolveSibling("testing/manifestmerge")
.toString()
.replace("\\", "/");
final Path mergerManifest = rlocation(dataDir + "/merger/AndroidManifest.xml");
final Path mergeeManifestOne = rlocation(dataDir + "/mergeeOne/AndroidManifest.xml");
final Path mergeeManifestTwo = rlocation(dataDir + "/mergeeTwo/AndroidManifest.xml");
assertThat(mergerManifest.toFile().exists()).isTrue();
assertThat(mergeeManifestOne.toFile().exists()).isTrue();
assertThat(mergeeManifestTwo.toFile().exists()).isTrue();
// The following code retrieves the path of the only AndroidManifest.xml in the expected/
// manifests directory. Unfortunately, this test runs internally and externally and the files
// have different names.
final File expectedManifestDirectory =
mergerManifest.getParent().resolveSibling("expected").toFile();
final String[] debug =
expectedManifestDirectory.list(new PatternFilenameFilter(".*AndroidManifest\\.xml$"));
assertThat(debug).isNotNull();
final File[] expectedManifestDirectoryManifests =
expectedManifestDirectory.listFiles((File dir, String name) -> true);
assertThat(expectedManifestDirectoryManifests).isNotNull();
assertThat(expectedManifestDirectoryManifests).hasLength(1);
final Path expectedManifest = expectedManifestDirectoryManifests[0].toPath();
Files.createDirectories(working.resolve("output"));
final Path mergedManifest = working.resolve("output/mergedManifest.xml");
List<String> args =
generateArgs(
mergerManifest,
ImmutableMap.of(mergeeManifestOne, "mergeeOne", mergeeManifestTwo, "mergeeTwo"),
false, /* isLibrary */
ImmutableMap.of("applicationId", "com.google.android.apps.testapp"),
"", /* custom_package */
mergedManifest);
ManifestMergerAction.main(args.toArray(new String[0]));
assertThat(
Joiner.on(" ")
.join(Files.readAllLines(mergedManifest, UTF_8))
.replaceAll("\\s+", " ")
.trim())
.isEqualTo(
Joiner.on(" ")
.join(Files.readAllLines(expectedManifest, UTF_8))
.replaceAll("\\s+", " ")
.trim());
}
@Test public void fullIntegration() throws Exception {
Files.createDirectories(working.resolve("output"));
final Path binaryOutput = working.resolve("output/binaryManifest.xml");
final Path libFooOutput = working.resolve("output/libFooManifest.xml");
final Path libBarOutput = working.resolve("output/libBarManifest.xml");
final Path binaryManifest = AndroidDataBuilder.of(working.resolve("binary"))
.createManifest("AndroidManifest.xml", "com.google.app", "")
.buildUnvalidated()
.getManifest();
final Path libFooManifest = AndroidDataBuilder.of(working.resolve("libFoo"))
.createManifest("AndroidManifest.xml", "com.google.foo",
" <application android:name=\"${applicationId}\" />")
.buildUnvalidated()
.getManifest();
final Path libBarManifest = AndroidDataBuilder.of(working.resolve("libBar"))
.createManifest("AndroidManifest.xml", "com.google.bar",
"<application android:name=\"${applicationId}\">",
"<activity android:name=\".activityFoo\" />",
"</application>")
.buildUnvalidated()
.getManifest();
// libFoo manifest merging
List<String> args = generateArgs(libFooManifest, ImmutableMap.<Path, String>of(), true,
ImmutableMap.<String, String>of(), "", libFooOutput);
ManifestMergerAction.main(args.toArray(new String[0]));
assertThat(Joiner.on(" ")
.join(Files.readAllLines(libFooOutput, UTF_8))
.replaceAll("\\s+", " ").trim()).contains(
"<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+ "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\""
+ " package=\"com.google.foo\">"
+ " <application android:name=\"${applicationId}\" />"
+ "</manifest>");
// libBar manifest merging
args = generateArgs(libBarManifest, ImmutableMap.<Path, String>of(), true,
ImmutableMap.<String, String>of(), "com.google.libbar", libBarOutput);
ManifestMergerAction.main(args.toArray(new String[0]));
assertThat(Joiner.on(" ")
.join(Files.readAllLines(libBarOutput, UTF_8))
.replaceAll("\\s+", " ").trim()).contains(
"<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+ " <manifest xmlns:android=\"http://schemas.android.com/apk/res/android\""
+ " package=\"com.google.libbar\" >"
+ " <application android:name=\"${applicationId}\" >"
+ " <activity android:name=\"com.google.bar.activityFoo\" />"
+ " </application>"
+ " </manifest>");
// binary manifest merging
args =
generateArgs(
binaryManifest,
ImmutableMap.of(libFooOutput, "libFoo", libBarOutput, "libBar"),
/* library= */ false,
ImmutableMap.of(
"applicationId", "com.google.android.app",
"foo", "this \\\\: is \"a, \"bad string"),
/* customPackage= */ "",
binaryOutput);
ManifestMergerAction.main(args.toArray(new String[0]));
assertThat(Joiner.on(" ")
.join(Files.readAllLines(binaryOutput, UTF_8))
.replaceAll("\\s+", " ").trim()).contains(
"<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+ " <manifest xmlns:android=\"http://schemas.android.com/apk/res/android\""
+ " package=\"com.google.android.app\" >"
+ " <application android:name=\"com.google.android.app\" >"
+ " <activity android:name=\"com.google.bar.activityFoo\" />"
+ " </application>"
+ " </manifest>");
}
private List<String> generateArgs(
Path manifest,
Map<Path, String> mergeeManifests,
boolean library,
Map<String, String> manifestValues,
String customPackage,
Path manifestOutput) {
return ImmutableList.of(
"--manifest", manifest.toString(),
"--mergeeManifests", mapToDictionaryString(mergeeManifests),
"--mergeType", library ? "LIBRARY" : "APPLICATION",
"--manifestValues", mapToDictionaryString(manifestValues),
"--customPackage", customPackage,
"--manifestOutput", manifestOutput.toString());
}
private <K, V> String mapToDictionaryString(Map<K, V> map) {
StringBuilder sb = new StringBuilder();
Iterator<Map.Entry<K, V>> iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<K, V> entry = iter.next();
sb.append(entry.getKey().toString().replace(":", "\\:").replace(",", "\\,"));
sb.append(':');
sb.append(entry.getValue().toString().replace(":", "\\:").replace(",", "\\,"));
if (iter.hasNext()) {
sb.append(',');
}
}
return sb.toString();
}
}