1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.processor.compat.changeid; 18 19 import static java.nio.charset.StandardCharsets.UTF_8; 20 21 import static javax.tools.StandardLocation.CLASS_OUTPUT; 22 23 import com.google.common.collect.ObjectArrays; 24 import com.google.testing.compile.Compilation; 25 import com.google.testing.compile.CompilationSubject; 26 import com.google.testing.compile.Compiler; 27 import com.google.testing.compile.JavaFileObjects; 28 29 import org.junit.Test; 30 31 import javax.tools.JavaFileObject; 32 33 34 public class ChangeIdProcessorTest { 35 36 // Hard coding the annotation definitions to avoid dependency on libcore, where they're defined. 37 private static final JavaFileObject[] mAnnotations = { 38 JavaFileObjects.forSourceLines("android.compat.annotation.ChangeId", 39 "package android.compat.annotation;", 40 "import static java.lang.annotation.ElementType.FIELD;", 41 "import static java.lang.annotation.ElementType.PARAMETER;", 42 "import static java.lang.annotation.RetentionPolicy.SOURCE;", 43 "import java.lang.annotation.Retention;", 44 "import java.lang.annotation.Target;", 45 "@Retention(SOURCE)", 46 "@Target({FIELD, PARAMETER})", 47 "public @interface ChangeId {", 48 "}"), 49 JavaFileObjects.forSourceLines("android.compat.annotation.Disabled", 50 "package android.compat.annotation;", 51 "import static java.lang.annotation.ElementType.FIELD;", 52 "import static java.lang.annotation.RetentionPolicy.SOURCE;", 53 "import java.lang.annotation.Retention;", 54 "import java.lang.annotation.Target;", 55 "@Retention(SOURCE)", 56 "@Target({FIELD})", 57 "public @interface Disabled {", 58 "}"), 59 JavaFileObjects.forSourceLines("android.compat.annotation.LoggingOnly", 60 "package android.compat.annotation;", 61 "import static java.lang.annotation.ElementType.FIELD;", 62 "import static java.lang.annotation.RetentionPolicy.SOURCE;", 63 "import java.lang.annotation.Retention;", 64 "import java.lang.annotation.Target;", 65 "@Retention(SOURCE)", 66 "@Target({FIELD})", 67 "public @interface LoggingOnly {", 68 "}"), 69 JavaFileObjects.forSourceLines("android.compat.annotation.EnabledAfter", 70 "package android.compat.annotation;", 71 "import static java.lang.annotation.ElementType.FIELD;", 72 "import static java.lang.annotation.RetentionPolicy.SOURCE;", 73 "import java.lang.annotation.Retention;", 74 "import java.lang.annotation.Target;", 75 "@Retention(SOURCE)", 76 "@Target({FIELD})", 77 "public @interface EnabledAfter {", 78 "int targetSdkVersion();", 79 "}") 80 81 }; 82 83 private static final String HEADER = 84 "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>"; 85 86 @Test testCompatConfigXmlOutput()87 public void testCompatConfigXmlOutput() { 88 JavaFileObject[] source = { 89 JavaFileObjects.forSourceLines( 90 "libcore.util.Compat", 91 "package libcore.util;", 92 "import android.compat.annotation.ChangeId;", 93 "import android.compat.annotation.EnabledAfter;", 94 "import android.compat.annotation.Disabled;", 95 "public class Compat {", 96 " /**", 97 " * description of", 98 " * MY_CHANGE_ID", 99 " */", 100 " @EnabledAfter(targetSdkVersion=29)", 101 " @ChangeId", 102 " static final long MY_CHANGE_ID = 123456789l;", 103 " /** description of ANOTHER_CHANGE **/", 104 " @ChangeId", 105 " @Disabled", 106 " public static final long ANOTHER_CHANGE = 23456700l;", 107 "}") 108 }; 109 String expectedFile = HEADER + "<config>" + 110 "<compat-change description=\"description of MY_CHANGE_ID\" " 111 + "enableAfterTargetSdk=\"29\" id=\"123456789\" name=\"MY_CHANGE_ID\">" 112 + "<meta-data definedIn=\"libcore.util.Compat\" " 113 + "sourcePosition=\"libcore/util/Compat.java:11\"/></compat-change>" 114 + "<compat-change description=\"description of ANOTHER_CHANGE\" disabled=\"true\" " 115 + "id=\"23456700\" name=\"ANOTHER_CHANGE\"><meta-data definedIn=\"libcore.util" 116 + ".Compat\" sourcePosition=\"libcore/util/Compat.java:14\"/></compat-change>" 117 + "</config>"; 118 Compilation compilation = 119 Compiler.javac() 120 .withProcessors(new ChangeIdProcessor()) 121 .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class)); 122 CompilationSubject.assertThat(compilation).succeeded(); 123 CompilationSubject.assertThat(compilation).generatedFile(CLASS_OUTPUT, "libcore.util", 124 "Compat_compat_config.xml").contentsAsString(UTF_8).isEqualTo(expectedFile); 125 } 126 127 @Test testCompatConfigXmlOutput_multiplePackages()128 public void testCompatConfigXmlOutput_multiplePackages() { 129 JavaFileObject[] source = { 130 JavaFileObjects.forSourceLines( 131 "libcore.util.Compat", 132 "package libcore.util;", 133 "import android.compat.annotation.ChangeId;", 134 "import android.compat.annotation.EnabledAfter;", 135 "import android.compat.annotation.Disabled;", 136 "public class Compat {", 137 " /**", 138 " * description of", 139 " * MY_CHANGE_ID", 140 " */", 141 " @ChangeId", 142 " static final long MY_CHANGE_ID = 123456789l;", 143 "}"), 144 JavaFileObjects.forSourceLines("android.util.SomeClass", 145 "package android.util;", 146 "import android.compat.annotation.ChangeId;", 147 "import android.compat.annotation.EnabledAfter;", 148 "import android.compat.annotation.Disabled;", 149 "public class SomeClass {", 150 " /** description of ANOTHER_CHANGE **/", 151 " @ChangeId", 152 " public static final long ANOTHER_CHANGE = 23456700l;", 153 "}") 154 }; 155 String libcoreExpectedFile = HEADER + "<config>" + 156 "<compat-change description=\"description of MY_CHANGE_ID\" " 157 + "id=\"123456789\" name=\"MY_CHANGE_ID\">" 158 + "<meta-data definedIn=\"libcore.util.Compat\" " 159 + "sourcePosition=\"libcore/util/Compat.java:10\"/></compat-change>" 160 + "</config>"; 161 String androidExpectedFile = HEADER + "<config>" + 162 "<compat-change description=\"description of ANOTHER_CHANGE\" " 163 + "id=\"23456700\" name=\"ANOTHER_CHANGE\"><meta-data definedIn=\"android.util" 164 + ".SomeClass\" sourcePosition=\"android/util/SomeClass.java:7\"/></compat-change>" 165 + "</config>"; 166 Compilation compilation = 167 Compiler.javac() 168 .withProcessors(new ChangeIdProcessor()) 169 .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class)); 170 CompilationSubject.assertThat(compilation).succeeded(); 171 CompilationSubject.assertThat(compilation).generatedFile(CLASS_OUTPUT, "libcore.util", 172 "Compat_compat_config.xml").contentsAsString(UTF_8).isEqualTo(libcoreExpectedFile); 173 CompilationSubject.assertThat(compilation).generatedFile(CLASS_OUTPUT, "android.util", 174 "SomeClass_compat_config.xml").contentsAsString(UTF_8).isEqualTo( 175 androidExpectedFile); 176 } 177 178 @Test testCompatConfigXmlOutput_innerClass()179 public void testCompatConfigXmlOutput_innerClass() { 180 JavaFileObject[] source = { 181 JavaFileObjects.forSourceLines( 182 "libcore.util.Compat", 183 "package libcore.util;", 184 "import android.compat.annotation.ChangeId;", 185 "import android.compat.annotation.EnabledAfter;", 186 "import android.compat.annotation.Disabled;", 187 "public class Compat {", 188 " public class Inner {", 189 " /**", 190 " * description of", 191 " * MY_CHANGE_ID", 192 " */", 193 " @ChangeId", 194 " static final long MY_CHANGE_ID = 123456789l;", 195 " }", 196 "}"), 197 }; 198 String expectedFile = HEADER + "<config>" + 199 "<compat-change description=\"description of MY_CHANGE_ID\" " 200 + "id=\"123456789\" name=\"MY_CHANGE_ID\"><meta-data definedIn=\"libcore.util" 201 + ".Compat.Inner\" sourcePosition=\"libcore/util/Compat.java:11\"/>" 202 + "</compat-change></config>"; 203 Compilation compilation = 204 Compiler.javac() 205 .withProcessors(new ChangeIdProcessor()) 206 .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class)); 207 CompilationSubject.assertThat(compilation).succeeded(); 208 CompilationSubject.assertThat(compilation).generatedFile(CLASS_OUTPUT, "libcore.util", 209 "Compat.Inner_compat_config.xml").contentsAsString(UTF_8).isEqualTo(expectedFile); 210 } 211 212 @Test testCompatConfigXmlOutput_interface()213 public void testCompatConfigXmlOutput_interface() { 214 JavaFileObject[] source = { 215 JavaFileObjects.forSourceLines( 216 "libcore.util.Compat", 217 "package libcore.util;", 218 "import android.compat.annotation.ChangeId;", 219 "import android.compat.annotation.EnabledAfter;", 220 "import android.compat.annotation.Disabled;", 221 "public interface Compat {", 222 " /**", 223 " * description of", 224 " * MY_CHANGE_ID", 225 " */", 226 " @ChangeId", 227 " static final long MY_CHANGE_ID = 123456789l;", 228 "}"), 229 }; 230 String expectedFile = HEADER + "<config>" + 231 "<compat-change description=\"description of MY_CHANGE_ID\" " 232 + "id=\"123456789\" name=\"MY_CHANGE_ID\"><meta-data definedIn=\"libcore.util" 233 + ".Compat\" sourcePosition=\"libcore/util/Compat.java:10\"/>" 234 + "</compat-change></config>"; 235 Compilation compilation = 236 Compiler.javac() 237 .withProcessors(new ChangeIdProcessor()) 238 .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class)); 239 CompilationSubject.assertThat(compilation).succeeded(); 240 CompilationSubject.assertThat(compilation).generatedFile(CLASS_OUTPUT, "libcore.util", 241 "Compat_compat_config.xml").contentsAsString(UTF_8).isEqualTo(expectedFile); 242 } 243 244 @Test testCompatConfigXmlOutput_enum()245 public void testCompatConfigXmlOutput_enum() { 246 JavaFileObject[] source = { 247 JavaFileObjects.forSourceLines( 248 "libcore.util.Compat", 249 "package libcore.util;", 250 "import android.compat.annotation.ChangeId;", 251 "import android.compat.annotation.EnabledAfter;", 252 "import android.compat.annotation.Disabled;", 253 "public enum Compat {", 254 " ENUM_CONSTANT;", 255 " /**", 256 " * description of", 257 " * MY_CHANGE_ID", 258 " */", 259 " @ChangeId", 260 " private static final long MY_CHANGE_ID = 123456789l;", 261 "}"), 262 }; 263 String expectedFile = HEADER + "<config>" + 264 "<compat-change description=\"description of MY_CHANGE_ID\" " 265 + "id=\"123456789\" name=\"MY_CHANGE_ID\"><meta-data definedIn=\"libcore.util" 266 + ".Compat\" sourcePosition=\"libcore/util/Compat.java:11\"/>" 267 + "</compat-change></config>"; 268 Compilation compilation = 269 Compiler.javac() 270 .withProcessors(new ChangeIdProcessor()) 271 .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class)); 272 CompilationSubject.assertThat(compilation).succeeded(); 273 CompilationSubject.assertThat(compilation).generatedFile(CLASS_OUTPUT, "libcore.util", 274 "Compat_compat_config.xml").contentsAsString(UTF_8).isEqualTo(expectedFile); 275 } 276 277 @Test testBothDisabledAndEnabledAfter()278 public void testBothDisabledAndEnabledAfter() { 279 JavaFileObject[] source = { 280 JavaFileObjects.forSourceLines( 281 "libcore.util.Compat", 282 "package libcore.util;", 283 "import android.compat.annotation.ChangeId;", 284 "import android.compat.annotation.EnabledAfter;", 285 "import android.compat.annotation.Disabled;", 286 "public class Compat {", 287 " @EnabledAfter(targetSdkVersion=29)", 288 " @Disabled", 289 " @ChangeId", 290 " static final long MY_CHANGE_ID = 123456789l;", 291 "}") 292 }; 293 Compilation compilation = 294 Compiler.javac() 295 .withProcessors(new ChangeIdProcessor()) 296 .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class)); 297 CompilationSubject.assertThat(compilation).hadErrorContaining( 298 "ChangeId cannot be annotated with both @Disabled and @EnabledAfter."); 299 } 300 301 302 @Test testBothLoggingOnlyAndEnabledAfter()303 public void testBothLoggingOnlyAndEnabledAfter() { 304 JavaFileObject[] source = { 305 JavaFileObjects.forSourceLines( 306 "libcore.util.Compat", 307 "package libcore.util;", 308 "import android.compat.annotation.ChangeId;", 309 "import android.compat.annotation.EnabledAfter;", 310 "import android.compat.annotation.LoggingOnly;", 311 "public class Compat {", 312 " @EnabledAfter(targetSdkVersion=29)", 313 " @LoggingOnly", 314 " @ChangeId", 315 " static final long MY_CHANGE_ID = 123456789l;", 316 "}") 317 }; 318 Compilation compilation = 319 Compiler.javac() 320 .withProcessors(new ChangeIdProcessor()) 321 .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class)); 322 CompilationSubject.assertThat(compilation).hadErrorContaining( 323 "ChangeId cannot be annotated with both @LoggingOnly and " 324 + "(@EnabledAfter | @Disabled)."); 325 } 326 327 @Test testBothLoggingOnlyAndDisabled()328 public void testBothLoggingOnlyAndDisabled() { 329 JavaFileObject[] source = { 330 JavaFileObjects.forSourceLines( 331 "libcore.util.Compat", 332 "package libcore.util;", 333 "import android.compat.annotation.ChangeId;", 334 "import android.compat.annotation.LoggingOnly;", 335 "import android.compat.annotation.Disabled;", 336 "public class Compat {", 337 " @LoggingOnly", 338 " @Disabled", 339 " @ChangeId", 340 " static final long MY_CHANGE_ID = 123456789l;", 341 "}") 342 }; 343 Compilation compilation = 344 Compiler.javac() 345 .withProcessors(new ChangeIdProcessor()) 346 .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class)); 347 CompilationSubject.assertThat(compilation).hadErrorContaining( 348 "ChangeId cannot be annotated with both @LoggingOnly and " 349 + "(@EnabledAfter | @Disabled)."); 350 } 351 352 @Test testLoggingOnly()353 public void testLoggingOnly() { 354 JavaFileObject[] source = { 355 JavaFileObjects.forSourceLines( 356 "libcore.util.Compat", 357 "package libcore.util;", 358 "import android.compat.annotation.ChangeId;", 359 "import android.compat.annotation.LoggingOnly;", 360 "public class Compat {", 361 " @LoggingOnly", 362 " @ChangeId", 363 " static final long MY_CHANGE_ID = 123456789l;", 364 "}") 365 }; 366 String expectedFile = HEADER + "<config>" + 367 "<compat-change id=\"123456789\" loggingOnly=\"true\" name=\"MY_CHANGE_ID\">" + 368 "<meta-data definedIn=\"libcore.util.Compat\" " + 369 "sourcePosition=\"libcore/util/Compat.java:6\"/>" + 370 "</compat-change></config>"; 371 Compilation compilation = 372 Compiler.javac() 373 .withProcessors(new ChangeIdProcessor()) 374 .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class)); 375 CompilationSubject.assertThat(compilation).succeeded(); 376 CompilationSubject.assertThat(compilation).generatedFile(CLASS_OUTPUT, "libcore.util", 377 "Compat_compat_config.xml").contentsAsString(UTF_8).isEqualTo(expectedFile); 378 } 379 380 @Test testIgnoredParams()381 public void testIgnoredParams() { 382 JavaFileObject[] source = { 383 JavaFileObjects.forSourceLines( 384 "android.compat.Compatibility", 385 "package android.compat;", 386 "import android.compat.annotation.ChangeId;", 387 "public final class Compatibility {", 388 " public static void reportChange(@ChangeId long changeId) {}", 389 " public static boolean isChangeEnabled(@ChangeId long changeId) {", 390 " return true;", 391 " }", 392 "}") 393 }; 394 Compilation compilation = 395 Compiler.javac() 396 .withProcessors(new ChangeIdProcessor()) 397 .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class)); 398 CompilationSubject.assertThat(compilation).succeeded(); 399 } 400 401 @Test testOtherClassParams()402 public void testOtherClassParams() { 403 JavaFileObject[] source = { 404 JavaFileObjects.forSourceLines( 405 "android.compat.OtherClass", 406 "package android.compat;", 407 "import android.compat.annotation.ChangeId;", 408 "public final class OtherClass {", 409 " public static void reportChange(@ChangeId long changeId) {}", 410 "}") 411 }; 412 Compilation compilation = 413 Compiler.javac() 414 .withProcessors(new ChangeIdProcessor()) 415 .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class)); 416 CompilationSubject.assertThat(compilation).hadErrorContaining( 417 "Non FIELD element annotated with @ChangeId."); 418 } 419 420 @Test testOtherMethodParams()421 public void testOtherMethodParams() { 422 JavaFileObject[] source = { 423 JavaFileObjects.forSourceLines( 424 "android.compat.Compatibility", 425 "package android.compat;", 426 "import android.compat.annotation.ChangeId;", 427 "public final class Compatibility {", 428 " public static void otherMethod(@ChangeId long changeId) {}", 429 "}") 430 }; 431 Compilation compilation = 432 Compiler.javac() 433 .withProcessors(new ChangeIdProcessor()) 434 .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class)); 435 CompilationSubject.assertThat(compilation).hadErrorContaining( 436 "Non FIELD element annotated with @ChangeId."); 437 } 438 439 @Test testNonFinal()440 public void testNonFinal() { 441 JavaFileObject[] source = { 442 JavaFileObjects.forSourceLines( 443 "libcore.util.Compat", 444 "package libcore.util;", 445 "import android.compat.annotation.ChangeId;", 446 "import android.compat.annotation.EnabledAfter;", 447 "public class Compat {", 448 " @EnabledAfter(targetSdkVersion=29)", 449 " @ChangeId", 450 " static long MY_CHANGE_ID = 123456789l;", 451 "}") 452 }; 453 Compilation compilation = 454 Compiler.javac() 455 .withProcessors(new ChangeIdProcessor()) 456 .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class)); 457 CompilationSubject.assertThat(compilation).hadErrorContaining( 458 "Non constant/final variable annotated with @ChangeId."); 459 } 460 461 @Test testNonLong()462 public void testNonLong() { 463 JavaFileObject[] source = { 464 JavaFileObjects.forSourceLines( 465 "libcore.util.Compat", 466 "package libcore.util;", 467 "import android.compat.annotation.ChangeId;", 468 "import android.compat.annotation.EnabledAfter;", 469 "public class Compat {", 470 " @EnabledAfter(targetSdkVersion=29)", 471 " @ChangeId", 472 " static final int MY_CHANGE_ID = 12345;", 473 "}") 474 }; 475 Compilation compilation = 476 Compiler.javac() 477 .withProcessors(new ChangeIdProcessor()) 478 .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class)); 479 CompilationSubject.assertThat(compilation).hadErrorContaining( 480 "Variables annotated with @ChangeId must be of type long."); 481 } 482 483 @Test testNonStatic()484 public void testNonStatic() { 485 JavaFileObject[] source = { 486 JavaFileObjects.forSourceLines( 487 "libcore.util.Compat", 488 "package libcore.util;", 489 "import android.compat.annotation.ChangeId;", 490 "import android.compat.annotation.EnabledAfter;", 491 "public class Compat {", 492 " @EnabledAfter(targetSdkVersion=29)", 493 " @ChangeId", 494 " final long MY_CHANGE_ID = 123456789l;", 495 "}") 496 }; 497 Compilation compilation = 498 Compiler.javac() 499 .withProcessors(new ChangeIdProcessor()) 500 .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class)); 501 CompilationSubject.assertThat(compilation).hadErrorContaining( 502 "Non static variable annotated with @ChangeId."); 503 } 504 505 @Test testCompatConfigXmlOutput_hideTag()506 public void testCompatConfigXmlOutput_hideTag() { 507 JavaFileObject[] source = { 508 JavaFileObjects.forSourceLines( 509 "libcore.util.Compat", 510 "package libcore.util;", 511 "import android.compat.annotation.ChangeId;", 512 "import android.compat.annotation.EnabledAfter;", 513 "import android.compat.annotation.Disabled;", 514 "public class Compat {", 515 " public class Inner {", 516 " /**", 517 " * description of MY_CHANGE_ID.", 518 " * @hide", 519 " */", 520 " @ChangeId", 521 " static final long MY_CHANGE_ID = 123456789l;", 522 " }", 523 "}"), 524 }; 525 String expectedFile = HEADER + "<config>" + 526 "<compat-change description=\"description of MY_CHANGE_ID.\" " 527 + "id=\"123456789\" name=\"MY_CHANGE_ID\"><meta-data definedIn=\"libcore.util" 528 + ".Compat.Inner\" sourcePosition=\"libcore/util/Compat.java:11\"/>" 529 + "</compat-change></config>"; 530 Compilation compilation = 531 Compiler.javac() 532 .withProcessors(new ChangeIdProcessor()) 533 .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class)); 534 CompilationSubject.assertThat(compilation).succeeded(); 535 CompilationSubject.assertThat(compilation).generatedFile(CLASS_OUTPUT, "libcore.util", 536 "Compat.Inner_compat_config.xml").contentsAsString(UTF_8).isEqualTo(expectedFile); 537 } 538 539 } 540