1 package com.android.tools.rmtypedefs; 2 3 import com.google.common.base.Charsets; 4 import com.google.common.collect.Lists; 5 import com.google.common.io.Files; 6 7 import junit.framework.TestCase; 8 9 import org.eclipse.jdt.core.compiler.batch.BatchCompiler; 10 11 import java.io.File; 12 import java.io.IOException; 13 import java.io.PrintWriter; 14 import java.security.Permission; 15 import java.util.Collections; 16 import java.util.Comparator; 17 import java.util.List; 18 19 import static com.google.common.base.Charsets.UTF_8; 20 import static java.io.File.separatorChar; 21 22 @SuppressWarnings("SpellCheckingInspection") 23 public class RmTypeDefsTest extends TestCase { test()24 public void test() throws IOException { 25 // Creates a test class containing various typedefs, as well as the @IntDef annotation 26 // itself (to make the test case independent of the SDK), and compiles this using 27 // ECJ. It then runs the RmTypeDefs tool on the resulting output directory, and 28 // finally verifies that the tool exits with a 0 exit code. 29 30 File dir = Files.createTempDir(); 31 String testClass = "" 32 + "package test.pkg;\n" 33 + "\n" 34 + "import android.annotation.IntDef;\n" 35 + "\n" 36 + "import java.lang.annotation.Retention;\n" 37 + "import java.lang.annotation.RetentionPolicy;\n" 38 + "\n" 39 + "@SuppressWarnings({\"UnusedDeclaration\",\"JavaDoc\"})\n" 40 + "public class TestClass {\n" 41 + " /** @hide */\n" 42 + " @Retention(RetentionPolicy.SOURCE)\n" 43 + " @IntDef(flag = true,\n" 44 + " value = {\n" 45 + " DISPLAY_USE_LOGO,\n" 46 + " DISPLAY_SHOW_HOME,\n" 47 + " DISPLAY_HOME_AS_UP,\n" 48 + " DISPLAY_SHOW_TITLE,\n" 49 + " DISPLAY_SHOW_CUSTOM,\n" 50 + " DISPLAY_TITLE_MULTIPLE_LINES\n" 51 + " })\n" 52 + " public @interface DisplayOptions {}\n" 53 + "\n" 54 + " public static final int DISPLAY_USE_LOGO = 0x1;\n" 55 + " public static final int DISPLAY_SHOW_HOME = 0x2;\n" 56 + " public static final int DISPLAY_HOME_AS_UP = 0x4;\n" 57 + " public static final int DISPLAY_SHOW_TITLE = 0x8;\n" 58 + " public static final int DISPLAY_SHOW_CUSTOM = 0x10;\n" 59 + " public static final int DISPLAY_TITLE_MULTIPLE_LINES = 0x20;\n" 60 + "\n" 61 + " public void setDisplayOptions(@DisplayOptions int options) {\n" 62 + " System.out.println(\"setDisplayOptions \" + options);\n" 63 + " }\n" 64 + " public void setDisplayOptions(@DisplayOptions int options, @DisplayOptions int mask) {\n" 65 + " System.out.println(\"setDisplayOptions \" + options + \", mask=\" + mask);\n" 66 + " }\n" 67 + "\n" 68 + " public static class StaticInnerClass {\n" 69 + " int mViewFlags = 0;\n" 70 + " static final int VISIBILITY_MASK = 0x0000000C;\n" 71 + "\n" 72 + " /** @hide */\n" 73 + " @IntDef({VISIBLE, INVISIBLE, GONE})\n" 74 + " @Retention(RetentionPolicy.SOURCE)\n" 75 + " public @interface Visibility {}\n" 76 + "\n" 77 + " public static final int VISIBLE = 0x00000000;\n" 78 + " public static final int INVISIBLE = 0x00000004;\n" 79 + " public static final int GONE = 0x00000008;\n" 80 + "\n" 81 + " @Visibility\n" 82 + " public int getVisibility() {\n" 83 + " return mViewFlags & VISIBILITY_MASK;\n" 84 + " }\n" 85 + " }\n" 86 + "\n" 87 + " public static class Inherits extends StaticInnerClass {\n" 88 + " @Override\n" 89 + " @Visibility\n" 90 + " public int getVisibility() {\n" 91 + " return 0;\n" 92 + " }\n" 93 + " }\n" 94 + "}\n"; 95 String intdef = "" 96 + "package android.annotation;\n" 97 + "@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS)\n" 98 + "@java.lang.annotation.Target({java.lang.annotation.ElementType.ANNOTATION_TYPE})\n" 99 + "public @interface IntDef {\n" 100 + " long[] value() default {};\n" 101 + " boolean flag() default false;\n" 102 + "}"; 103 104 File srcDir = new File(dir, "test" + File.separator + "pkg"); 105 boolean mkdirs = srcDir.mkdirs(); 106 assertTrue(mkdirs); 107 File srcFile1 = new File(srcDir, "TestClass.java"); 108 Files.write(testClass, srcFile1, Charsets.UTF_8); 109 110 srcDir = new File(dir, "android" + File.separator + "annotation"); 111 mkdirs = srcDir.mkdirs(); 112 assertTrue(mkdirs); 113 File srcFile2 = new File(srcDir, "IntDef.java"); 114 Files.write(intdef, srcFile2, Charsets.UTF_8); 115 116 boolean compileSuccessful = BatchCompiler.compile(srcFile1 + " " + srcFile2 + 117 " -source 1.6 -target 1.6 -nowarn", 118 new PrintWriter(System.out), 119 new PrintWriter(System.err), null); 120 assertTrue(compileSuccessful); 121 122 assertEquals("" 123 + "testDir/\n" 124 + " testDir/android/\n" 125 + " testDir/android/annotation/\n" 126 + " testDir/android/annotation/IntDef.class\n" 127 + " testDir/android/annotation/IntDef.java\n" 128 + " testDir/test/\n" 129 + " testDir/test/pkg/\n" 130 + " testDir/test/pkg/TestClass$DisplayOptions.class\n" 131 + " testDir/test/pkg/TestClass$Inherits.class\n" 132 + " testDir/test/pkg/TestClass$StaticInnerClass$Visibility.class\n" 133 + " testDir/test/pkg/TestClass$StaticInnerClass.class\n" 134 + " testDir/test/pkg/TestClass.class\n" 135 + " testDir/test/pkg/TestClass.java\n", 136 getDirectoryContents(dir)); 137 138 // Trap System.exit calls: 139 System.setSecurityManager(new SecurityManager() { 140 @Override 141 public void checkPermission(Permission perm) { 142 } 143 @Override 144 public void checkPermission(Permission perm, Object context) { 145 } 146 @Override 147 public void checkExit(int status) { 148 throw new ExitException(status); 149 } 150 }); 151 try { 152 RmTypeDefs.main(new String[]{"--verbose", dir.getPath()}); 153 } catch (ExitException e) { 154 assertEquals(0, e.getStatus()); 155 } 156 System.setSecurityManager(null); 157 158 // TODO: check that the classes are identical 159 // BEFORE removal 160 161 assertEquals("" 162 + "testDir/\n" 163 + " testDir/android/\n" 164 + " testDir/android/annotation/\n" 165 + " testDir/android/annotation/IntDef.class\n" 166 + " testDir/android/annotation/IntDef.java\n" 167 + " testDir/test/\n" 168 + " testDir/test/pkg/\n" 169 + " testDir/test/pkg/TestClass$Inherits.class\n" 170 + " testDir/test/pkg/TestClass$StaticInnerClass.class\n" 171 + " testDir/test/pkg/TestClass.class\n" 172 + " testDir/test/pkg/TestClass.java\n", 173 getDirectoryContents(dir)); 174 175 // Make sure the Visibility symbol is completely gone from the outer class 176 assertDoesNotContainBytes(new File(dir, 177 "test/pkg/TestClass$StaticInnerClass.class".replace('/', separatorChar)), 178 "Visibility"); 179 180 deleteDir(dir); 181 } 182 assertDoesNotContainBytes(File file, String sub)183 private void assertDoesNotContainBytes(File file, String sub) throws IOException { 184 byte[] contents = Files.toByteArray(file); 185 // Like the strings command, look for 4 or more consecutive printable characters 186 for (int i = 0, n = contents.length; i < n; i++) { 187 if (Character.isJavaIdentifierStart(contents[i])) { 188 for (int j = i + 1; j < n; j++) { 189 if (!Character.isJavaIdentifierPart(contents[j])) { 190 if (j > i + 4) { 191 int length = j - i - 1; 192 if (length == sub.length()) { 193 String symbol = new String(contents, i, length, UTF_8); 194 assertFalse("Found " + sub + " in class file " + file, 195 sub.equals(symbol)); 196 } 197 } 198 i = j; 199 break; 200 } 201 } 202 } 203 } 204 } 205 getDirectoryContents(File root)206 String getDirectoryContents(File root) { 207 StringBuilder sb = new StringBuilder(); 208 list(sb, root, "", 0, "testDir"); 209 return sb.toString(); 210 } 211 list(StringBuilder sb, File file, String prefix, int depth, String rootName)212 private void list(StringBuilder sb, File file, String prefix, int depth, String rootName) { 213 for (int i = 0; i < depth; i++) { 214 sb.append(" "); 215 } 216 217 if (!prefix.isEmpty()) { 218 sb.append(prefix); 219 } 220 String fileName = file.getName(); 221 if (depth == 0 && rootName != null) { // avoid temp-name 222 fileName = rootName; 223 } 224 sb.append(fileName); 225 if (file.isDirectory()) { 226 sb.append('/'); 227 sb.append('\n'); 228 File[] files = file.listFiles(); 229 if (files != null) { 230 List<File> children = Lists.newArrayList(); 231 Collections.addAll(children, files); 232 Collections.sort(children, new Comparator<File>() { 233 @Override 234 public int compare(File o1, File o2) { 235 return o1.getName().compareTo(o2.getName()); 236 } 237 }); 238 prefix = prefix + fileName + "/"; 239 for (File child : children) { 240 list(sb, child, prefix, depth + 1, rootName); 241 } 242 } 243 } else { 244 sb.append('\n'); 245 } 246 } 247 248 /** 249 * Recursive delete directory. Mostly for fake SDKs. 250 * 251 * @param root directory to delete 252 */ 253 @SuppressWarnings("ResultOfMethodCallIgnored") deleteDir(File root)254 private static void deleteDir(File root) { 255 if (root.exists()) { 256 File[] files = root.listFiles(); 257 if (files != null) { 258 for (File file : files) { 259 if (file.isDirectory()) { 260 deleteDir(file); 261 } else { 262 file.delete(); 263 } 264 } 265 } 266 root.delete(); 267 } 268 } 269 270 private static class ExitException extends SecurityException { 271 private static final long serialVersionUID = 1L; 272 273 private final int mStatus; 274 ExitException(int status)275 public ExitException(int status) { 276 super("Unit test"); 277 mStatus = status; 278 } 279 getStatus()280 public int getStatus() { 281 return mStatus; 282 } 283 } 284 } 285