blob: 423d22b54bed486afd77ba7a5175f801d2673335 [file] [log] [blame]
package com.android.tools.rmtypedefs;
import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import com.google.common.io.Files;
import junit.framework.TestCase;
import org.eclipse.jdt.core.compiler.batch.BatchCompiler;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.Permission;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import static com.google.common.base.Charsets.UTF_8;
import static java.io.File.separatorChar;
@SuppressWarnings("SpellCheckingInspection")
public class RmTypeDefsTest extends TestCase {
public void test() throws IOException {
// Creates a test class containing various typedefs, as well as the @IntDef annotation
// itself (to make the test case independent of the SDK), and compiles this using
// ECJ. It then runs the RmTypeDefs tool on the resulting output directory, and
// finally verifies that the tool exits with a 0 exit code.
File dir = Files.createTempDir();
String testClass = ""
+ "package test.pkg;\n"
+ "\n"
+ "import android.annotation.IntDef;\n"
+ "\n"
+ "import java.lang.annotation.Retention;\n"
+ "import java.lang.annotation.RetentionPolicy;\n"
+ "\n"
+ "@SuppressWarnings({\"UnusedDeclaration\",\"JavaDoc\"})\n"
+ "public class TestClass {\n"
+ " /** @hide */\n"
+ " @Retention(RetentionPolicy.SOURCE)\n"
+ " @IntDef(flag = true,\n"
+ " value = {\n"
+ " DISPLAY_USE_LOGO,\n"
+ " DISPLAY_SHOW_HOME,\n"
+ " DISPLAY_HOME_AS_UP,\n"
+ " DISPLAY_SHOW_TITLE,\n"
+ " DISPLAY_SHOW_CUSTOM,\n"
+ " DISPLAY_TITLE_MULTIPLE_LINES\n"
+ " })\n"
+ " public @interface DisplayOptions {}\n"
+ "\n"
+ " public static final int DISPLAY_USE_LOGO = 0x1;\n"
+ " public static final int DISPLAY_SHOW_HOME = 0x2;\n"
+ " public static final int DISPLAY_HOME_AS_UP = 0x4;\n"
+ " public static final int DISPLAY_SHOW_TITLE = 0x8;\n"
+ " public static final int DISPLAY_SHOW_CUSTOM = 0x10;\n"
+ " public static final int DISPLAY_TITLE_MULTIPLE_LINES = 0x20;\n"
+ "\n"
+ " public void setDisplayOptions(@DisplayOptions int options) {\n"
+ " System.out.println(\"setDisplayOptions \" + options);\n"
+ " }\n"
+ " public void setDisplayOptions(@DisplayOptions int options, @DisplayOptions int mask) {\n"
+ " System.out.println(\"setDisplayOptions \" + options + \", mask=\" + mask);\n"
+ " }\n"
+ "\n"
+ " public static class StaticInnerClass {\n"
+ " int mViewFlags = 0;\n"
+ " static final int VISIBILITY_MASK = 0x0000000C;\n"
+ "\n"
+ " /** @hide */\n"
+ " @IntDef({VISIBLE, INVISIBLE, GONE})\n"
+ " @Retention(RetentionPolicy.SOURCE)\n"
+ " public @interface Visibility {}\n"
+ "\n"
+ " public static final int VISIBLE = 0x00000000;\n"
+ " public static final int INVISIBLE = 0x00000004;\n"
+ " public static final int GONE = 0x00000008;\n"
+ "\n"
+ " @Visibility\n"
+ " public int getVisibility() {\n"
+ " return mViewFlags & VISIBILITY_MASK;\n"
+ " }\n"
+ " }\n"
+ "\n"
+ " public static class Inherits extends StaticInnerClass {\n"
+ " @Override\n"
+ " @Visibility\n"
+ " public int getVisibility() {\n"
+ " return 0;\n"
+ " }\n"
+ " }\n"
+ "}\n";
String intdef = ""
+ "package android.annotation;\n"
+ "@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS)\n"
+ "@java.lang.annotation.Target({java.lang.annotation.ElementType.ANNOTATION_TYPE})\n"
+ "public @interface IntDef {\n"
+ " long[] value() default {};\n"
+ " boolean flag() default false;\n"
+ "}";
File srcDir = new File(dir, "test" + File.separator + "pkg");
boolean mkdirs = srcDir.mkdirs();
assertTrue(mkdirs);
File srcFile1 = new File(srcDir, "TestClass.java");
Files.write(testClass, srcFile1, Charsets.UTF_8);
srcDir = new File(dir, "android" + File.separator + "annotation");
mkdirs = srcDir.mkdirs();
assertTrue(mkdirs);
File srcFile2 = new File(srcDir, "IntDef.java");
Files.write(intdef, srcFile2, Charsets.UTF_8);
boolean compileSuccessful = BatchCompiler.compile(srcFile1 + " " + srcFile2 +
" -source 1.6 -target 1.6 -nowarn",
new PrintWriter(System.out),
new PrintWriter(System.err), null);
assertTrue(compileSuccessful);
assertEquals(""
+ "testDir/\n"
+ " testDir/android/\n"
+ " testDir/android/annotation/\n"
+ " testDir/android/annotation/IntDef.class\n"
+ " testDir/android/annotation/IntDef.java\n"
+ " testDir/test/\n"
+ " testDir/test/pkg/\n"
+ " testDir/test/pkg/TestClass$DisplayOptions.class\n"
+ " testDir/test/pkg/TestClass$Inherits.class\n"
+ " testDir/test/pkg/TestClass$StaticInnerClass$Visibility.class\n"
+ " testDir/test/pkg/TestClass$StaticInnerClass.class\n"
+ " testDir/test/pkg/TestClass.class\n"
+ " testDir/test/pkg/TestClass.java\n",
getDirectoryContents(dir));
// Trap System.exit calls:
System.setSecurityManager(new SecurityManager() {
@Override
public void checkPermission(Permission perm) {
}
@Override
public void checkPermission(Permission perm, Object context) {
}
@Override
public void checkExit(int status) {
throw new ExitException(status);
}
});
try {
RmTypeDefs.main(new String[]{"--verbose", dir.getPath()});
} catch (ExitException e) {
assertEquals(0, e.getStatus());
}
System.setSecurityManager(null);
// TODO: check that the classes are identical
// BEFORE removal
assertEquals(""
+ "testDir/\n"
+ " testDir/android/\n"
+ " testDir/android/annotation/\n"
+ " testDir/android/annotation/IntDef.class\n"
+ " testDir/android/annotation/IntDef.java\n"
+ " testDir/test/\n"
+ " testDir/test/pkg/\n"
+ " testDir/test/pkg/TestClass$Inherits.class\n"
+ " testDir/test/pkg/TestClass$StaticInnerClass.class\n"
+ " testDir/test/pkg/TestClass.class\n"
+ " testDir/test/pkg/TestClass.java\n",
getDirectoryContents(dir));
// Make sure the Visibility symbol is completely gone from the outer class
assertDoesNotContainBytes(new File(dir,
"test/pkg/TestClass$StaticInnerClass.class".replace('/', separatorChar)),
"Visibility");
deleteDir(dir);
}
private void assertDoesNotContainBytes(File file, String sub) throws IOException {
byte[] contents = Files.toByteArray(file);
// Like the strings command, look for 4 or more consecutive printable characters
for (int i = 0, n = contents.length; i < n; i++) {
if (Character.isJavaIdentifierStart(contents[i])) {
for (int j = i + 1; j < n; j++) {
if (!Character.isJavaIdentifierPart(contents[j])) {
if (j > i + 4) {
int length = j - i - 1;
if (length == sub.length()) {
String symbol = new String(contents, i, length, UTF_8);
assertFalse("Found " + sub + " in class file " + file,
sub.equals(symbol));
}
}
i = j;
break;
}
}
}
}
}
String getDirectoryContents(File root) {
StringBuilder sb = new StringBuilder();
list(sb, root, "", 0, "testDir");
return sb.toString();
}
private void list(StringBuilder sb, File file, String prefix, int depth, String rootName) {
for (int i = 0; i < depth; i++) {
sb.append(" ");
}
if (!prefix.isEmpty()) {
sb.append(prefix);
}
String fileName = file.getName();
if (depth == 0 && rootName != null) { // avoid temp-name
fileName = rootName;
}
sb.append(fileName);
if (file.isDirectory()) {
sb.append('/');
sb.append('\n');
File[] files = file.listFiles();
if (files != null) {
List<File> children = Lists.newArrayList();
Collections.addAll(children, files);
Collections.sort(children, new Comparator<File>() {
@Override
public int compare(File o1, File o2) {
return o1.getName().compareTo(o2.getName());
}
});
prefix = prefix + fileName + "/";
for (File child : children) {
list(sb, child, prefix, depth + 1, rootName);
}
}
} else {
sb.append('\n');
}
}
/**
* Recursive delete directory. Mostly for fake SDKs.
*
* @param root directory to delete
*/
@SuppressWarnings("ResultOfMethodCallIgnored")
private static void deleteDir(File root) {
if (root.exists()) {
File[] files = root.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
deleteDir(file);
} else {
file.delete();
}
}
}
root.delete();
}
}
private static class ExitException extends SecurityException {
private static final long serialVersionUID = 1L;
private final int mStatus;
public ExitException(int status) {
super("Unit test");
mStatus = status;
}
public int getStatus() {
return mStatus;
}
}
}