aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/test/java/com/google/devtools/build/android/resources/RClassWriterTest.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/java/com/google/devtools/build/android/resources/RClassWriterTest.java')
-rw-r--r--src/test/java/com/google/devtools/build/android/resources/RClassWriterTest.java324
1 files changed, 324 insertions, 0 deletions
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);
+ }
+
+}