diff options
Diffstat (limited to 'src/test/java/com/google/devtools/build/android')
4 files changed, 572 insertions, 0 deletions
diff --git a/src/test/java/com/google/devtools/build/android/AndroidResourceCompilationActionTest.java b/src/test/java/com/google/devtools/build/android/AndroidResourceCompilationActionTest.java new file mode 100644 index 0000000000..17b4ab48a3 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/AndroidResourceCompilationActionTest.java @@ -0,0 +1,225 @@ +// Copyright 2016 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 com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.FileTime; +import java.util.Collections; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** + * Tests for {@link AndroidResourceCompilationAction}. + */ +@RunWith(JUnit4.class) +public class AndroidResourceCompilationActionTest { + + private Path tempDir; + + @Before + public void setUp() throws IOException { + tempDir = Files.createTempDirectory(toString()); + } + + /** + * TODO(jvoung): use {@link AndroidDataBuilder} instead, once that's moved to this source tree. + * This is a slimmed down version used to avoid dependencies. + */ + private static class ManifestBuilder { + + private final Path root; + + private ManifestBuilder(Path root) { + this.root = root; + } + + public static ManifestBuilder of(Path root) { + return new ManifestBuilder(root); + } + + public Path createManifest(String path, String manifestPackage, String... lines) + throws IOException { + Path manifest = root.resolve(path); + Files.createDirectories(root); + Files.write(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)).getBytes(StandardCharsets.UTF_8)); + return manifest; + } + } + + @Test + public void withBinaryAndLibraries() throws Exception { + Path binaryManifest = ManifestBuilder.of(tempDir.resolve("binary")) + .createManifest("AndroidManifest.xml", "com.google.app", + "<application android:name=\"com.google.app\">", + "<activity android:name=\"com.google.bar.activityFoo\" />", + "</application>"); + Path libFooManifest = ManifestBuilder.of(tempDir.resolve("libFoo")) + .createManifest("AndroidManifest.xml", "com.google.foo", ""); + Path libBarManifest = ManifestBuilder.of(tempDir.resolve("libBar")) + .createManifest("AndroidManifest.xml", "com.google.bar", ""); + + Path binarySymbols = createFile("R.txt", + "int attr agility 0x7f010000", + "int attr dexterity 0x7f010001", + "int drawable heart 0x7f020000", + "int id someTextView 0x7f080000", + "int integer maxNotifications 0x7f090000", + "int string alphabet 0x7f100000", + "int string ok 0x7f100001"); + Path libFooSymbols = createFile("libFoo.R.txt", + "int attr agility 0x1", + "int id someTextView 0x1", + "int string ok 0x1"); + Path libBarSymbols = createFile("libBar.R.txt", + "int attr dexterity 0x1", + "int drawable heart 0x1"); + + Path jarPath = tempDir.resolve("app_resources.jar"); + + AndroidResourceCompilationAction.main(ImmutableList.<String>of( + "--primaryRTxt", binarySymbols.toString(), + "--primaryManifest", binaryManifest.toString(), + "--libraries", + libFooSymbols + ":" + libFooManifest + "," + libBarSymbols + ":" + libBarManifest, + "--classJarOutput", jarPath.toString() + ).toArray(new String[0])); + + assertThat(Files.exists(jarPath)).isTrue(); + assertThat(Files.getLastModifiedTime(jarPath)).isEqualTo(FileTime.fromMillis(0)); + + try (ZipFile zip = new ZipFile(jarPath.toFile())) { + List<? extends ZipEntry> zipEntries = Collections.list(zip.entries()); + Iterable<String> entries = getZipFilenames(zipEntries); + assertThat(entries).containsExactly( + "com/google/foo/R$attr.class", + "com/google/foo/R$id.class", + "com/google/foo/R$string.class", + "com/google/foo/R.class", + "com/google/bar/R$attr.class", + "com/google/bar/R$drawable.class", + "com/google/bar/R.class", + "com/google/app/R$attr.class", + "com/google/app/R$drawable.class", + "com/google/app/R$id.class", + "com/google/app/R$integer.class", + "com/google/app/R$string.class", + "com/google/app/R.class", + "META-INF/MANIFEST.MF" + ); + } + } + + @Test + public void withBinaryNoLibraries() throws Exception { + Path binaryManifest = ManifestBuilder.of(tempDir.resolve("binary")) + .createManifest("AndroidManifest.xml", "com.google.app", + "<application android:name=\"com.google.app\">", + "<activity android:name=\"com.google.bar.activityFoo\" />", + "</application>"); + + Path binarySymbols = createFile("R.txt", + "int attr agility 0x7f010000", + "int attr dexterity 0x7f010001", + "int drawable heart 0x7f020000", + "int id someTextView 0x7f080000", + "int integer maxNotifications 0x7f090000", + "int string alphabet 0x7f100000", + "int string ok 0x7f100001"); + + Path jarPath = tempDir.resolve("app_resources.jar"); + + AndroidResourceCompilationAction.main(ImmutableList.<String>of( + "--primaryRTxt", binarySymbols.toString(), + "--primaryManifest", binaryManifest.toString(), + "--classJarOutput", jarPath.toString() + ).toArray(new String[0])); + + assertThat(Files.exists(jarPath)).isTrue(); + assertThat(Files.getLastModifiedTime(jarPath)).isEqualTo(FileTime.fromMillis(0)); + + try (ZipFile zip = new ZipFile(jarPath.toFile())) { + List<? extends ZipEntry> zipEntries = Collections.list(zip.entries()); + Iterable<String> entries = getZipFilenames(zipEntries); + assertThat(entries).containsExactly( + "com/google/app/R$attr.class", + "com/google/app/R$drawable.class", + "com/google/app/R$id.class", + "com/google/app/R$integer.class", + "com/google/app/R$string.class", + "com/google/app/R.class", + "META-INF/MANIFEST.MF" + ); + } + } + + @Test + public void noBinary() throws Exception { + Path jarPath = tempDir.resolve("app_resources.jar"); + AndroidResourceCompilationAction.main(ImmutableList.<String>of( + "--classJarOutput", jarPath.toString() + ).toArray(new String[0])); + + assertThat(Files.exists(jarPath)).isTrue(); + assertThat(Files.getLastModifiedTime(jarPath)).isEqualTo(FileTime.fromMillis(0)); + + try (ZipFile zip = new ZipFile(jarPath.toFile())) { + List<? extends ZipEntry> zipEntries = Collections.list(zip.entries()); + Iterable<String> entries = getZipFilenames(zipEntries); + assertThat(entries).containsExactly( + "META-INF/MANIFEST.MF" + ); + } + } + + private Path createFile(String name, String... contents) throws IOException { + Path path = tempDir.resolve(name); + Files.createDirectories(path.getParent()); + Files.newOutputStream(path).write( + Joiner.on("\n").join(contents).getBytes(StandardCharsets.UTF_8)); + return path; + } + + private Iterable<String> getZipFilenames(Iterable<? extends ZipEntry> entries) { + return Iterables.transform(entries, + new Function<ZipEntry, String>() { + @Override + public String apply(ZipEntry input) { + return input.getName(); + } + }); + } +} diff --git a/src/test/java/com/google/devtools/build/android/BUILD b/src/test/java/com/google/devtools/build/android/BUILD new file mode 100644 index 0000000000..fd1ad295f7 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/BUILD @@ -0,0 +1,11 @@ +java_test( + name = "AndroidResourceCompilationActionTest", + size = "medium", + srcs = glob(["*.java"]), + deps = [ + "//src/tools/android/java/com/google/devtools/build/android:android_builder_lib", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +) diff --git a/src/test/java/com/google/devtools/build/android/resources/BUILD b/src/test/java/com/google/devtools/build/android/resources/BUILD new file mode 100644 index 0000000000..5ea3ac5d6b --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/resources/BUILD @@ -0,0 +1,12 @@ +java_test( + name = "RClassWriterTest", + size = "medium", + srcs = glob(["*.java"]), + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/resources", + "//third_party:android_common", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +) diff --git a/src/test/java/com/google/devtools/build/android/resources/RClassWriterTest.java b/src/test/java/com/google/devtools/build/android/resources/RClassWriterTest.java new file mode 100644 index 0000000000..67b1b505da --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/resources/RClassWriterTest.java @@ -0,0 +1,324 @@ +// Copyright 2016 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.resources; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; + +import com.android.builder.internal.SymbolLoader; +import com.android.utils.ILogger; +import com.android.utils.StdLogger; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +/** + * Tests for {@link RClassWriter}. + */ +@RunWith(JUnit4.class) +public class RClassWriterTest { + + private Path temp; + private ILogger stdLogger; + + @Before + public void setUp() throws Exception { + temp = Files.createTempDirectory(toString()); + stdLogger = new StdLogger(StdLogger.Level.VERBOSE); + } + + @Test + public void plainInts() throws Exception { + checkSimpleInts(true); + } + + @Test + public void nonFinalFields() throws Exception { + checkSimpleInts(false); + } + + private void checkSimpleInts(boolean finalFields) throws Exception { + // R.txt with the real IDs after linking together libraries. + SymbolLoader symbolValues = createSymbolFile("R.txt", + "int attr agility 0x7f010000", + "int attr dexterity 0x7f010001", + "int drawable heart 0x7f020000", + "int id someTextView 0x7f080000", + "int integer maxNotifications 0x7f090000", + "int string alphabet 0x7f100000", + "int string ok 0x7f100001"); + // R.txt for the library, where the values are not the final ones (so ignore them). We only use + // this to keep the # of inner classes small (exactly the set needed by the library). + SymbolLoader symbolsInLibrary = createSymbolFile("lib.R.txt", + "int attr agility 0x1", + "int id someTextView 0x1", + "int string ok 0x1"); + Path out = temp.resolve("classes"); + Files.createDirectories(out); + RClassWriter writer = new RClassWriter(out.toFile(), "com.bar", symbolValues, finalFields); + writer.addSymbolsToWrite(symbolsInLibrary); + writer.write(); + + Path packageDir = out.resolve("com/bar"); + checkFilesInPackage(packageDir, "R.class", "R$attr.class", "R$id.class", "R$string.class"); + Class<?> outerClass = checkTopLevelClass(out, + "com.bar.R", + "com.bar.R$attr", + "com.bar.R$id", + "com.bar.R$string"); + checkInnerClass(out, + "com.bar.R$attr", + outerClass, + ImmutableMap.of("agility", 0x7f010000), + ImmutableMap.<String, List<Integer>>of(), + finalFields + ); + checkInnerClass(out, + "com.bar.R$id", + outerClass, + ImmutableMap.of("someTextView", 0x7f080000), + ImmutableMap.<String, List<Integer>>of(), + finalFields + ); + checkInnerClass(out, + "com.bar.R$string", + outerClass, + ImmutableMap.of("ok", 0x7f100001), + ImmutableMap.<String, List<Integer>>of(), + finalFields + ); + } + + @Test + public void emptyIntArrays() throws Exception { + boolean finalFields = true; + // Make sure we parse an empty array the way the R.txt writes it. + SymbolLoader symbolValues = createSymbolFile("R.txt", + "int[] styleable ActionMenuView { }"); + SymbolLoader symbolsInLibrary = symbolValues; + Path out = temp.resolve("classes"); + Files.createDirectories(out); + RClassWriter writer = new RClassWriter(out.toFile(), "com.testEmptyIntArray", symbolValues, + finalFields); + writer.addSymbolsToWrite(symbolsInLibrary); + writer.write(); + + Path packageDir = out.resolve("com/testEmptyIntArray"); + checkFilesInPackage(packageDir, "R.class", "R$styleable.class"); + Class<?> outerClass = checkTopLevelClass(out, + "com.testEmptyIntArray.R", + "com.testEmptyIntArray.R$styleable"); + checkInnerClass(out, + "com.testEmptyIntArray.R$styleable", + outerClass, + ImmutableMap.<String, Integer>of(), + ImmutableMap.<String, List<Integer>>of( + "ActionMenuView", ImmutableList.<Integer>of() + ), + finalFields + ); + } + + @Test + public void intArraysFinal() throws Exception { + checkIntArrays(true); + } + + @Test + public void intArraysNonFinal() throws Exception { + checkIntArrays(false); + } + + public void checkIntArrays(boolean finalFields) throws Exception { + SymbolLoader symbolValues = createSymbolFile("R.txt", + "int attr android_layout 0x010100f2", + "int attr bar 0x7f010001", + "int attr baz 0x7f010002", + "int attr fox 0x7f010003", + "int attr attr 0x7f010004", + "int attr another_attr 0x7f010005", + "int attr zoo 0x7f010006", + // Test several > 5 elements, so that clinit must use bytecodes other than iconst_0 to 5. + "int[] styleable ActionButton { 0x010100f2, 0x7f010001, 0x7f010002, 0x7f010003, " + + "0x7f010004, 0x7f010005, 0x7f010006 }", + // The array indices of each attribute. + "int styleable ActionButton_android_layout 0", + "int styleable ActionButton_another_attr 5", + "int styleable ActionButton_attr 4", + "int styleable ActionButton_bar 1", + "int styleable ActionButton_baz 2", + "int styleable ActionButton_fox 3", + "int styleable ActionButton_zoo 6" + ); + SymbolLoader symbolsInLibrary = symbolValues; + Path out = temp.resolve("classes"); + Files.createDirectories(out); + RClassWriter writer = new RClassWriter(out.toFile(), "com.intArray", symbolValues, + finalFields); + writer.addSymbolsToWrite(symbolsInLibrary); + writer.write(); + + Path packageDir = out.resolve("com/intArray"); + checkFilesInPackage(packageDir, "R.class", "R$attr.class", "R$styleable.class"); + Class<?> outerClass = checkTopLevelClass(out, + "com.intArray.R", + "com.intArray.R$attr", + "com.intArray.R$styleable"); + checkInnerClass(out, + "com.intArray.R$attr", + outerClass, + ImmutableMap.<String, Integer>builder() + .put("android_layout", 0x010100f2) + .put("bar", 0x7f010001) + .put("baz", 0x7f010002) + .put("fox", 0x7f010003) + .put("attr", 0x7f010004) + .put("another_attr", 0x7f010005) + .put("zoo", 0x7f010006) + .build(), + ImmutableMap.<String, List<Integer>>of(), + finalFields + ); + checkInnerClass(out, + "com.intArray.R$styleable", + outerClass, + ImmutableMap.<String, Integer>builder() + .put("ActionButton_android_layout", 0) + .put("ActionButton_bar", 1) + .put("ActionButton_baz", 2) + .put("ActionButton_fox", 3) + .put("ActionButton_attr", 4) + .put("ActionButton_another_attr", 5) + .put("ActionButton_zoo", 6) + .build(), + ImmutableMap.<String, List<Integer>>of( + "ActionButton", + ImmutableList.of(0x010100f2, 0x7f010001, 0x7f010002, + 0x7f010003, 0x7f010004, 0x7f010005, 0x7f010006) + ), + finalFields + ); + } + + // Test utilities + + private Path createFile(String name, String... contents) throws IOException { + Path path = temp.resolve(name); + Files.createDirectories(path.getParent()); + Files.newOutputStream(path).write( + Joiner.on("\n").join(contents).getBytes(StandardCharsets.UTF_8)); + return path; + } + + private SymbolLoader createSymbolFile(String name, String... contents) throws IOException { + Path path = createFile(name, contents); + SymbolLoader symbolFile = new SymbolLoader(path.toFile(), stdLogger); + symbolFile.load(); + return symbolFile; + } + + private static void checkFilesInPackage(Path packageDir, String... expectedFiles) + throws IOException { + ImmutableList<String> filesInPackage = ImmutableList + .copyOf(Iterables.transform(Files.newDirectoryStream(packageDir), + new Function<Path, String>() { + @Override + public String apply(Path path) { + return path.getFileName().toString(); + } + } + )); + assertThat(filesInPackage).containsExactly((Object[]) expectedFiles); + } + + private static Class<?> checkTopLevelClass( + Path baseDir, + String expectedClassName, + String... expectedInnerClasses) + throws Exception { + URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{baseDir.toUri().toURL()}); + Class<?> toplevelClass = urlClassLoader.loadClass(expectedClassName); + assertThat(toplevelClass.getSuperclass()).isEqualTo(Object.class); + int outerModifiers = toplevelClass.getModifiers(); + assertThat(Modifier.isFinal(outerModifiers)).isTrue(); + assertThat(Modifier.isPublic(outerModifiers)).isTrue(); + ImmutableList.Builder<String> actualClasses = ImmutableList.builder(); + for (Class<?> innerClass : toplevelClass.getClasses()) { + assertThat(innerClass.getDeclaredClasses()).isEmpty(); + int modifiers = innerClass.getModifiers(); + assertThat(Modifier.isFinal(modifiers)).isTrue(); + assertThat(Modifier.isPublic(modifiers)).isTrue(); + assertThat(Modifier.isStatic(modifiers)).isTrue(); + actualClasses.add(innerClass.getName()); + } + assertThat(actualClasses.build()).containsExactly((Object[]) expectedInnerClasses); + return toplevelClass; + } + + private void checkInnerClass( + Path baseDir, + String expectedClassName, + Class<?> outerClass, + ImmutableMap<String, Integer> intFields, + ImmutableMap<String, List<Integer>> intArrayFields, + boolean areFieldsFinal) throws Exception { + URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{baseDir.toUri().toURL()}); + Class<?> innerClass = urlClassLoader.loadClass(expectedClassName); + assertThat(innerClass.getSuperclass()).isEqualTo(Object.class); + assertThat(innerClass.getEnclosingClass().toString()) + .isEqualTo(outerClass.toString()); + ImmutableMap.Builder<String, Integer> actualIntFields = ImmutableMap.builder(); + ImmutableMap.Builder<String, List<Integer>> actualIntArrayFields = ImmutableMap.builder(); + for (Field f : innerClass.getFields()) { + int fieldModifiers = f.getModifiers(); + assertThat(Modifier.isFinal(fieldModifiers)).isEqualTo(areFieldsFinal); + assertThat(Modifier.isPublic(fieldModifiers)).isTrue(); + assertThat(Modifier.isStatic(fieldModifiers)).isTrue(); + + Class<?> fieldType = f.getType(); + if (fieldType.isPrimitive()) { + assertThat(fieldType).isEqualTo(Integer.TYPE); + actualIntFields.put(f.getName(), (Integer) f.get(null)); + } else { + assertThat(fieldType.isArray()).isTrue(); + int[] asArray = (int[]) f.get(null); + ImmutableList.Builder<Integer> list = ImmutableList.builder(); + for (int i : asArray) { + list.add(i); + } + actualIntArrayFields.put(f.getName(), list.build()); + } + } + assertThat(actualIntFields.build()).containsExactlyEntriesIn(intFields); + assertThat(actualIntArrayFields.build()).containsExactlyEntriesIn(intArrayFields); + } + +} |