aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/test/java/com/google/devtools/build/android
diff options
context:
space:
mode:
authorGravatar ajmichael <ajmichael@google.com>2018-02-26 08:58:55 -0800
committerGravatar Copybara-Service <copybara-piper@google.com>2018-02-26 09:01:59 -0800
commit5a330002c57adaf8a6d4180618132c75bfcbb7fd (patch)
tree33c8116d50c70619a2634a16f70908417f0193bb /src/test/java/com/google/devtools/build/android
parentd4db94dd1d7ada64f4bbd1f0fc6f39f3ec0c6870 (diff)
Change android_library .aar timestamp and open-source AarGeneratorActionTest.
Diffstat (limited to 'src/test/java/com/google/devtools/build/android')
-rw-r--r--src/test/java/com/google/devtools/build/android/AarGeneratorActionTest.java680
-rw-r--r--src/test/java/com/google/devtools/build/android/BUILD15
2 files changed, 695 insertions, 0 deletions
diff --git a/src/test/java/com/google/devtools/build/android/AarGeneratorActionTest.java b/src/test/java/com/google/devtools/build/android/AarGeneratorActionTest.java
new file mode 100644
index 0000000000..347d38dde6
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/AarGeneratorActionTest.java
@@ -0,0 +1,680 @@
+// Copyright 2018 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 static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertNotNull;
+
+import com.android.builder.core.VariantType;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.android.AarGeneratorAction.AarGeneratorOptions;
+import com.google.devtools.common.options.OptionsParser;
+import com.google.devtools.common.options.OptionsParsingException;
+import java.io.ByteArrayOutputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link AarGeneratorAction}. */
+@RunWith(JUnit4.class)
+public class AarGeneratorActionTest {
+
+ private static class AarData {
+ /** Templates for resource files generation. */
+ enum ResourceType {
+ VALUE {
+ @Override public String create(String... lines) {
+ return String.format(
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>%s</resources>",
+ Joiner.on("\n").join(lines));
+ }
+ },
+ LAYOUT {
+ @Override public String create(String... lines) {
+ return String.format("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+ + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\""
+ + " android:layout_width=\"fill_parent\""
+ + " android:layout_height=\"fill_parent\">%s</LinearLayout>",
+ Joiner.on("\n").join(lines));
+ }
+ },
+ UNFORMATTED {
+ @Override public String create(String... lines) {
+ return String.format(Joiner.on("\n").join(lines));
+ }
+ };
+
+ public abstract String create(String... lines);
+ }
+
+ private static class Builder {
+
+ private final Path root;
+ private final Path assetDir;
+ private final Path resourceDir;
+ private Path manifest;
+ private Path rtxt;
+ private Path classes;
+ private Map<Path, String> filesToWrite = new HashMap<>();
+ private Map<String, String> classesToWrite = new HashMap<>();
+ private boolean withEmptyRes = false;
+ private boolean withEmptyAssets = false;
+
+ public Builder(Path root) {
+ this(root, "res", "assets");
+ }
+
+ public Builder(Path root, String resourceRoot, String assetRoot) {
+ this.root = root;
+ assetDir = root.resolve(assetRoot);
+ resourceDir = root.resolve(resourceRoot);
+ manifest = root.resolve("fake-manifest-path");
+ rtxt = root.resolve("fake-rtxt-path");
+ classes = root.resolve("fake-classes-path");
+ }
+
+ public Builder addResource(String path, ResourceType template, String... lines) {
+ filesToWrite.put(resourceDir.resolve(path), template.create(lines));
+ return this;
+ }
+
+ public Builder withEmptyResources(boolean isEmpty) {
+ this.withEmptyRes = isEmpty;
+ return this;
+ }
+
+ public Builder addAsset(String path, String... lines) {
+ filesToWrite.put(assetDir.resolve(path), Joiner.on("\n").join(lines));
+ return this;
+ }
+
+ public Builder withEmptyAssets(boolean isEmpty) {
+ this.withEmptyAssets = isEmpty;
+ return this;
+ }
+
+ public Builder createManifest(String path, String manifestPackage, String... lines) {
+ this.manifest = root.resolve(path);
+ filesToWrite.put(manifest, String.format("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+ + "<manifest xmlns:android='http://schemas.android.com/apk/res/android' package='%s'>"
+ + "%s</manifest>", manifestPackage, Joiner.on("\n").join(lines)));
+ return this;
+ }
+
+ public Builder createRtxt(String path, String... lines) {
+ this.rtxt = root.resolve(path);
+ filesToWrite.put(rtxt, String.format("%s", Joiner.on("\n").join(lines)));
+ return this;
+ }
+
+ public Builder createClassesJar(String path) {
+ this.classes = root.resolve(path);
+ classesToWrite.put("META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n");
+ return this;
+ }
+
+ public Builder addClassesFile(String filePackage, String filename, String... lines) {
+ classesToWrite.put(filePackage.replace(".", "/") + "/" + filename,
+ String.format("%s", Joiner.on("\n").join(lines)));
+ return this;
+ }
+
+ public AarData build() throws IOException {
+ writeFiles();
+ return new AarData(buildMerged(), manifest, rtxt, classes);
+ }
+
+ private MergedAndroidData buildMerged() {
+ return new MergedAndroidData(
+ resourceDir,
+ assetDir,
+ manifest);
+ }
+
+ private void writeFiles() throws IOException {
+ assertNotNull("A manifest is required.", manifest);
+ assertNotNull("A resource file is required.", rtxt);
+ assertNotNull("A classes jar is required.", classes);
+ if (withEmptyRes) {
+ Files.createDirectories(resourceDir);
+ }
+ if (withEmptyAssets) {
+ Files.createDirectories(assetDir);
+ }
+ for (Entry<Path, String> entry : filesToWrite.entrySet()) {
+ Path file = entry.getKey();
+ // only write files in assets if assets has not been set to empty and same for resources
+ if (!((file.startsWith(assetDir) && withEmptyAssets)
+ || (file.startsWith(resourceDir) && withEmptyRes))) {
+ Files.createDirectories(file.getParent());
+ Files.write(file, entry.getValue().getBytes(StandardCharsets.UTF_8));
+ assertThat(Files.exists(file)).isTrue();
+ }
+ }
+ if (!classesToWrite.isEmpty()) {
+ writeClassesJar();
+ }
+ }
+
+ private void writeClassesJar() throws IOException {
+ final ZipOutputStream zout = new ZipOutputStream(new FileOutputStream(classes.toFile()));
+
+ for (Entry<String, String> file : classesToWrite.entrySet()) {
+ ZipEntry entry = new ZipEntry(file.getKey());
+ zout.putNextEntry(entry);
+ zout.write(file.getValue().getBytes(UTF_8));
+ zout.closeEntry();
+ }
+
+ zout.close();
+
+ classes.toFile().setLastModified(AarGeneratorAction.DEFAULT_TIMESTAMP);
+ }
+ }
+
+ final MergedAndroidData data;
+ final Path manifest;
+ final Path rtxt;
+ final Path classes;
+
+ private AarData(MergedAndroidData data, Path manifest, Path rtxt, Path classes) {
+ this.data = data;
+ this.manifest = manifest;
+ this.rtxt = rtxt;
+ this.classes = classes;
+ }
+ }
+
+ /**
+ * Operation to perform on a file.
+ */
+ private interface FileOperation {
+ /**
+ * Performs the operation on a file, given its name, modificationTime and contents.
+ */
+ void perform(String name, long modificationTime, String contents);
+ }
+
+ /**
+ * Runs a {@link FileOperation} on every entry in a zip file.
+ *
+ * @param zip {@link Path} of the zip file to traverse.
+ * @param operation {@link FileOperation} to be run on every entry of the zip file.
+ * @throws IOException if there is an error reading the zip file.
+ */
+ private void traverseZipFile(Path zip, FileOperation operation) throws IOException {
+ ZipInputStream zis = new ZipInputStream(Files.newInputStream(zip));
+ ZipEntry z = zis.getNextEntry();
+ while (z != null) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ byte[] buffer = new byte[1024];
+ for (int count = 0; count != -1; count = zis.read(buffer)) {
+ baos.write(buffer);
+ }
+ // Replace Windows path separators so that test cases are consistent across platforms.
+ String name = z.getName().replace('\\', '/');
+ operation.perform(
+ name, z.getTime(), new String(baos.toByteArray(), StandardCharsets.UTF_8));
+ z = zis.getNextEntry();
+ }
+ }
+
+ private Set<String> getZipEntries(Path zip) throws IOException {
+ final Set<String> zipEntries = new HashSet<>();
+ traverseZipFile(zip, new FileOperation() {
+ @Override public void perform(String name, long modificationTime, String contents) {
+ zipEntries.add(name);
+ }
+ });
+ return zipEntries;
+ }
+
+ private Set<Long> getZipEntryTimestamps(Path zip) throws IOException {
+ final Set<Long> timestamps = new HashSet<>();
+ traverseZipFile(zip, new FileOperation() {
+ @Override public void perform(String name, long modificationTime, String contents) {
+ timestamps.add(modificationTime);
+ }
+ });
+ return timestamps;
+ }
+
+ private Path tempDir;
+
+ @Rule public ExpectedException thrown = ExpectedException.none();
+
+ @Before public void setUp() throws IOException {
+ tempDir = Files.createTempDirectory(toString());
+ tempDir.toFile().deleteOnExit();
+
+ }
+
+ @Test public void testCheckFlags() throws IOException, OptionsParsingException {
+ Path manifest = tempDir.resolve("AndroidManifest.xml");
+ Files.createFile(manifest);
+ Path rtxt = tempDir.resolve("R.txt");
+ Files.createFile(rtxt);
+ Path classes = tempDir.resolve("classes.jar");
+ Files.createFile(classes);
+
+ String[] args = new String[] {"--manifest", manifest.toString(), "--rtxt", rtxt.toString(),
+ "--classes", classes.toString()};
+ OptionsParser optionsParser = OptionsParser.newOptionsParser(AarGeneratorOptions.class);
+ optionsParser.parse(args);
+ AarGeneratorOptions options = optionsParser.getOptions(AarGeneratorOptions.class);
+ AarGeneratorAction.checkFlags(options);
+ }
+
+ @Test public void testCheckFlags_MissingClasses() throws IOException, OptionsParsingException {
+ Path manifest = tempDir.resolve("AndroidManifest.xml");
+ Files.createFile(manifest);
+ Path rtxt = tempDir.resolve("R.txt");
+ Files.createFile(rtxt);
+
+ String[] args = new String[] {"--manifest", manifest.toString(), "--rtxt", rtxt.toString()};
+ OptionsParser optionsParser = OptionsParser.newOptionsParser(AarGeneratorOptions.class);
+ optionsParser.parse(args);
+ AarGeneratorOptions options = optionsParser.getOptions(AarGeneratorOptions.class);
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("classes must be specified. Building an .aar without"
+ + " classes is unsupported.");
+ AarGeneratorAction.checkFlags(options);
+ }
+
+ @Test public void testCheckFlags_MissingMultiple() throws IOException, OptionsParsingException {
+ Path manifest = tempDir.resolve("AndroidManifest.xml");
+ Files.createFile(manifest);
+ String[] args = new String[] {"--manifest", manifest.toString()};
+ OptionsParser optionsParser = OptionsParser.newOptionsParser(AarGeneratorOptions.class);
+ optionsParser.parse(args);
+ AarGeneratorOptions options = optionsParser.getOptions(AarGeneratorOptions.class);
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("rtxt, classes must be specified. Building an .aar without"
+ + " rtxt, classes is unsupported.");
+ AarGeneratorAction.checkFlags(options);
+ }
+
+ @Test public void testWriteAar() throws Exception {
+ Path aar = tempDir.resolve("foo.aar");
+ AarData aarData = new AarData.Builder(tempDir.resolve("data"))
+ .createManifest("AndroidManifest.xml", "com.google.android.apps.foo.d1", "")
+ .createRtxt("R.txt",
+ "int string app_name 0x7f050001",
+ "int string hello_world 0x7f050002")
+ .addResource("values/ids.xml",
+ AarData.ResourceType.VALUE,
+ "<item name=\"id_name\" type=\"id\"/>")
+ .addAsset("some/other/ft/data.txt", "bar")
+ .createClassesJar("classes.jar")
+ .addClassesFile("com.google.android.apps.foo", "Test.class", "test file contents")
+ .build();
+
+ AarGeneratorAction.writeAar(aar,
+ aarData.data,
+ aarData.manifest,
+ aarData.rtxt,
+ aarData.classes);
+ }
+
+ @Test public void testWriteAar_DefaultTimestamps() throws Exception {
+ Path aar = tempDir.resolve("foo.aar");
+ AarData aarData = new AarData.Builder(tempDir.resolve("data"))
+ .createManifest("AndroidManifest.xml", "com.google.android.apps.foo.d1", "")
+ .createRtxt("R.txt",
+ "int string app_name 0x7f050001",
+ "int string hello_world 0x7f050002")
+ .addResource("values/ids.xml",
+ AarData.ResourceType.VALUE,
+ "<item name=\"id_name\" type=\"id\"/>")
+ .addAsset("some/other/ft/data.txt", "bar")
+ .createClassesJar("classes.jar")
+ .addClassesFile("com.google.android.apps.foo", "Test.class", "test file contents")
+ .build();
+
+ AarGeneratorAction.writeAar(aar,
+ aarData.data,
+ aarData.manifest,
+ aarData.rtxt,
+ aarData.classes);
+
+ assertThat(getZipEntryTimestamps(aar)).containsExactly(AarGeneratorAction.DEFAULT_TIMESTAMP);
+ assertThat(aar.toFile().lastModified()).isEqualTo(AarGeneratorAction.DEFAULT_TIMESTAMP);
+ }
+
+ @Test public void testAssetResourceSubdirs() throws Exception {
+ Path aar = tempDir.resolve("foo.aar");
+ AarData aarData = new AarData.Builder(tempDir.resolve("data"), "xyz", "assets")
+ .createManifest("AndroidManifest.xml", "com.google.android.apps.foo.d1", "")
+ .createRtxt("R.txt",
+ "int string app_name 0x7f050001",
+ "int string hello_world 0x7f050002")
+ .addResource("values/ids.xml",
+ AarData.ResourceType.VALUE,
+ "<item name=\"id_name\" type=\"id\"/>")
+ .addAsset("some/other/ft/data.txt", "bar")
+ .createClassesJar("classes.jar")
+ .addClassesFile("com.google.android.apps.foo", "Test.class", "test file contents")
+ .build();
+
+ AarGeneratorAction.writeAar(aar,
+ aarData.data,
+ aarData.manifest,
+ aarData.rtxt,
+ aarData.classes);
+
+ // verify aar archive
+ Set<String> zipEntries = getZipEntries(aar);
+ assertThat(zipEntries).contains("res/");
+ assertThat(zipEntries).contains("assets/");
+ }
+
+ @Test public void testMissingManifest() throws Exception {
+ Path aar = tempDir.resolve("foo.aar");
+ AarData aarData = new AarData.Builder(tempDir.resolve("data"))
+ .createRtxt("R.txt",
+ "int string app_name 0x7f050001",
+ "int string hello_world 0x7f050002")
+ .addResource("values/ids.xml",
+ AarData.ResourceType.VALUE,
+ "<item name=\"id_name\" type=\"id\"/>")
+ .addAsset("some/other/ft/data.txt", "bar")
+ .createClassesJar("classes.jar")
+ .addClassesFile("com.google.android.apps.foo", "Test.class", "test file contents")
+ .build();
+
+ thrown.expect(IOException.class);
+ thrown.expectMessage(containsString("fake-manifest-path"));
+ AarGeneratorAction.writeAar(aar,
+ aarData.data,
+ aarData.manifest,
+ aarData.rtxt,
+ aarData.classes);
+ }
+
+ @Test public void testMissingRtxt() throws Exception {
+ Path aar = tempDir.resolve("foo.aar");
+ AarData aarData = new AarData.Builder(tempDir.resolve("data"))
+ .createManifest("AndroidManifest.xml", "com.google.android.apps.foo.d1", "")
+ .addResource("values/ids.xml",
+ AarData.ResourceType.VALUE,
+ "<item name=\"id_name\" type=\"id\"/>")
+ .addAsset("some/other/ft/data.txt", "bar")
+ .createClassesJar("classes.jar")
+ .addClassesFile("com.google.android.apps.foo", "Test.class", "test file contents")
+ .build();
+
+ thrown.expect(IOException.class);
+ thrown.expectMessage(containsString("fake-rtxt-path"));
+ AarGeneratorAction.writeAar(aar,
+ aarData.data,
+ aarData.manifest,
+ aarData.rtxt,
+ aarData.classes);
+ }
+
+ @Test public void testMissingClasses() throws Exception {
+ Path aar = tempDir.resolve("foo.aar");
+ AarData aarData = new AarData.Builder(tempDir.resolve("data"))
+ .createManifest("AndroidManifest.xml", "com.google.android.apps.foo.d1", "")
+ .createRtxt("R.txt",
+ "int string app_name 0x7f050001",
+ "int string hello_world 0x7f050002")
+ .addResource("values/ids.xml",
+ AarData.ResourceType.VALUE,
+ "<item name=\"id_name\" type=\"id\"/>")
+ .addAsset("some/other/ft/data.txt", "bar")
+ .build();
+
+ thrown.expect(IOException.class);
+ thrown.expectMessage(containsString("fake-classes-path"));
+ AarGeneratorAction.writeAar(aar,
+ aarData.data,
+ aarData.manifest,
+ aarData.rtxt,
+ aarData.classes);
+ }
+
+ @Test public void testMissingResources() throws Exception {
+ Path aar = tempDir.resolve("foo.aar");
+ AarData aarData = new AarData.Builder(tempDir.resolve("data"))
+ .createManifest("AndroidManifest.xml", "com.google.android.apps.foo.d1", "")
+ .createRtxt("R.txt",
+ "int string app_name 0x7f050001",
+ "int string hello_world 0x7f050002")
+ .addAsset("some/other/ft/data.txt", "bar")
+ .createClassesJar("classes.jar")
+ .addClassesFile("com.google.android.apps.foo", "Test.class", "test file contents")
+ .build();
+
+ thrown.expect(IOException.class);
+ thrown.expectMessage(containsString("res"));
+ AarGeneratorAction.writeAar(aar,
+ aarData.data,
+ aarData.manifest,
+ aarData.rtxt,
+ aarData.classes);
+ }
+
+ @Test public void testEmptyResources() throws Exception {
+ Path aar = tempDir.resolve("foo.aar");
+ AarData aarData = new AarData.Builder(tempDir.resolve("data"))
+ .createManifest("AndroidManifest.xml", "com.google.android.apps.foo.d1", "")
+ .createRtxt("R.txt",
+ "int string app_name 0x7f050001",
+ "int string hello_world 0x7f050002")
+ .withEmptyResources(true)
+ .addResource("values/ids.xml",
+ AarData.ResourceType.VALUE,
+ "<item name=\"id_name\" type=\"id\"/>")
+ .addAsset("some/other/ft/data.txt", "bar")
+ .createClassesJar("classes.jar")
+ .addClassesFile("com.google.android.apps.foo", "Test.class", "test file contents")
+ .build();
+
+ AarGeneratorAction.writeAar(aar,
+ aarData.data,
+ aarData.manifest,
+ aarData.rtxt,
+ aarData.classes);
+ }
+
+ @Test public void testMissingAssets() throws Exception {
+ Path aar = tempDir.resolve("foo.aar");
+ AarData aarData = new AarData.Builder(tempDir.resolve("data"))
+ .createManifest("AndroidManifest.xml", "com.google.android.apps.foo.d1", "")
+ .createRtxt("R.txt",
+ "int string app_name 0x7f050001",
+ "int string hello_world 0x7f050002")
+ .addResource("values/ids.xml",
+ AarData.ResourceType.VALUE,
+ "<item name=\"id_name\" type=\"id\"/>")
+ .createClassesJar("classes.jar")
+ .addClassesFile("com.google.android.apps.foo", "Test.class", "test file contents")
+ .build();
+
+ AarGeneratorAction.writeAar(aar,
+ aarData.data,
+ aarData.manifest,
+ aarData.rtxt,
+ aarData.classes);
+ }
+
+ @Test public void testEmptyAssets() throws Exception {
+ Path aar = tempDir.resolve("foo.aar");
+ AarData aarData = new AarData.Builder(tempDir.resolve("data"))
+ .createManifest("AndroidManifest.xml", "com.google.android.apps.foo.d1", "")
+ .createRtxt("R.txt",
+ "int string app_name 0x7f050001",
+ "int string hello_world 0x7f050002")
+ .addResource("values/ids.xml",
+ AarData.ResourceType.VALUE,
+ "<item name=\"id_name\" type=\"id\"/>")
+ .withEmptyAssets(true)
+ .createClassesJar("classes.jar")
+ .addClassesFile("com.google.android.apps.foo", "Test.class", "test file contents")
+ .build();
+
+ AarGeneratorAction.writeAar(aar,
+ aarData.data,
+ aarData.manifest,
+ aarData.rtxt,
+ aarData.classes);
+ }
+
+ @Test public void testFullIntegration() throws Exception {
+ Path aar = tempDir.resolve("foo.aar");
+ AarData aarData = new AarData.Builder(tempDir.resolve("data"))
+ .createManifest("AndroidManifest.xml", "com.google.android.apps.foo", "")
+ .createRtxt("R.txt",
+ "int string app_name 0x7f050001",
+ "int string hello_world 0x7f050002")
+ .addResource("values/ids.xml",
+ AarData.ResourceType.VALUE,
+ "<item name=\"id\" type=\"id\"/>")
+ .addResource("layout/layout.xml",
+ AarData.ResourceType.LAYOUT,
+ "<TextView android:id=\"@+id/text2\""
+ + " android:layout_width=\"wrap_content\""
+ + " android:layout_height=\"wrap_content\""
+ + " android:text=\"Hello, I am a TextView\" />")
+ .addAsset("some/other/ft/data.txt",
+ "foo")
+ .createClassesJar("classes.jar")
+ .addClassesFile("com.google.android.apps.foo", "Test.class", "test file contents")
+ .build();
+
+ MergedAndroidData md1 = new AarData.Builder(tempDir.resolve("d1"))
+ .addResource("values/ids.xml",
+ AarData.ResourceType.VALUE,
+ "<item name=\"id\" type=\"id\"/>")
+ .addResource("layout/foo.xml",
+ AarData.ResourceType.LAYOUT,
+ "<TextView android:id=\"@+id/text\""
+ + " android:layout_width=\"wrap_content\""
+ + " android:layout_height=\"wrap_content\""
+ + " android:text=\"Hello, I am a TextView\" />")
+ .addAsset("some/other/ft/data1.txt",
+ "bar")
+ .createManifest("AndroidManifest.xml", "com.google.android.apps.foo.d1", "")
+ .build().data;
+
+ MergedAndroidData md2 = new AarData.Builder(tempDir.resolve("d2"))
+ .addResource("values/ids.xml",
+ AarData.ResourceType.VALUE,
+ "<item name=\"id2\" type=\"id\"/>")
+ .addResource("layout/bar.xml",
+ AarData.ResourceType.LAYOUT,
+ "<TextView android:id=\"@+id/textbar\""
+ + " android:layout_width=\"wrap_content\""
+ + " android:layout_height=\"wrap_content\""
+ + " android:text=\"Hello, I am a TextView\" />")
+ .addResource("drawable-mdpi/icon.png",
+ AarData.ResourceType.UNFORMATTED,
+ "Thttpt.")
+ .addResource("drawable-xxxhdpi/icon.png",
+ AarData.ResourceType.UNFORMATTED,
+ "Double Thttpt.")
+ .addAsset("some/other/ft/data2.txt",
+ "foo")
+ .createManifest("AndroidManifest.xml", "com.google.android.apps.foo.d2", "")
+ .build().data;
+
+ UnvalidatedAndroidData primary = new UnvalidatedAndroidData(
+ ImmutableList.of(aarData.data.getResourceDir()),
+ ImmutableList.of(aarData.data.getAssetDir()),
+ aarData.data.getManifest());
+
+ DependencyAndroidData d1 =
+ new DependencyAndroidData(
+ ImmutableList.of(md1.getResourceDir()),
+ ImmutableList.of(md1.getAssetDir()),
+ md1.getManifest(),
+ null,
+ null,
+ null);
+
+ DependencyAndroidData d2 =
+ new DependencyAndroidData(
+ ImmutableList.of(md2.getResourceDir()),
+ ImmutableList.of(md2.getAssetDir()),
+ md2.getManifest(),
+ null,
+ null,
+ null);
+
+ Path working = tempDir;
+
+ Path resourcesOut = working.resolve("resources");
+ Path assetsOut = working.resolve("assets");
+
+ MergedAndroidData mergedData =
+ AndroidResourceMerger.mergeData(
+ primary,
+ ImmutableList.of(d1, d2),
+ ImmutableList.<DependencyAndroidData>of(),
+ resourcesOut,
+ assetsOut,
+ null,
+ VariantType.LIBRARY,
+ null,
+ /* filteredResources= */ ImmutableList.of(),
+ true);
+
+ AarGeneratorAction.writeAar(aar, mergedData, aarData.manifest, aarData.rtxt, aarData.classes);
+
+ // verify aar archive
+ Set<String> zipEntries = getZipEntries(aar);
+ assertThat(zipEntries).containsExactly(
+ "AndroidManifest.xml",
+ "R.txt",
+ "classes.jar",
+ "res/",
+ "res/values/",
+ "res/values/values.xml",
+ "res/layout/",
+ "res/layout/layout.xml",
+ "res/layout/foo.xml",
+ "res/layout/bar.xml",
+ "res/drawable-mdpi-v4/",
+ "res/drawable-mdpi-v4/icon.png",
+ "res/drawable-xxxhdpi-v4/",
+ "res/drawable-xxxhdpi-v4/icon.png",
+ "assets/",
+ "assets/some/",
+ "assets/some/other/",
+ "assets/some/other/ft/",
+ "assets/some/other/ft/data.txt",
+ "assets/some/other/ft/data1.txt",
+ "assets/some/other/ft/data2.txt");
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/android/BUILD b/src/test/java/com/google/devtools/build/android/BUILD
index cb5f346c9b..af7082fcd6 100644
--- a/src/test/java/com/google/devtools/build/android/BUILD
+++ b/src/test/java/com/google/devtools/build/android/BUILD
@@ -13,6 +13,21 @@ filegroup(
)
java_test(
+ name = "AarGeneratorActionTest",
+ size = "small",
+ srcs = ["AarGeneratorActionTest.java"],
+ deps = [
+ ":test_utils",
+ "//src/main/java/com/google/devtools/common/options",
+ "//src/tools/android/java/com/google/devtools/build/android:android_builder_lib",
+ "//third_party:android_common_25_0_0",
+ "//third_party:guava",
+ "//third_party:junit4",
+ "//third_party:truth",
+ ],
+)
+
+java_test(
name = "AndroidResourceOutputsTest",
size = "small",
srcs = ["AndroidResourceOutputsTest.java"],