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            JavaFileObjects.forSourceLines("android.compat.annotation.EnabledSince",
81                     "package android.compat.annotation;",
82                     "import static java.lang.annotation.ElementType.FIELD;",
83                     "import static java.lang.annotation.RetentionPolicy.SOURCE;",
84                     "import java.lang.annotation.Retention;",
85                     "import java.lang.annotation.Target;",
86                     "@Retention(SOURCE)",
87                     "@Target({FIELD})",
88                     "public @interface EnabledSince {",
89                     "int targetSdkVersion();",
90                     "}"),
91             JavaFileObjects.forSourceLines("android.compat.annotation.Overridable",
92                     "package android.compat.annotation;",
93                     "import static java.lang.annotation.ElementType.FIELD;",
94                     "import static java.lang.annotation.RetentionPolicy.SOURCE;",
95                     "import java.lang.annotation.Retention;",
96                     "import java.lang.annotation.Target;",
97                     "@Retention(SOURCE)",
98                     "@Target({FIELD})",
99                     "public @interface Overridable {",
100                     "}")
101 
102     };
103 
104     private static final String HEADER =
105             "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>";
106 
107     @Test
testCompatConfigXmlOutput()108     public void testCompatConfigXmlOutput() {
109         JavaFileObject[] source = {
110                 JavaFileObjects.forSourceLines(
111                         "libcore.util.Compat",
112                         "package libcore.util;",
113                         "import android.compat.annotation.ChangeId;",
114                         "import android.compat.annotation.EnabledAfter;",
115                         "import android.compat.annotation.EnabledSince;",
116                         "import android.compat.annotation.Disabled;",
117                         "import android.compat.annotation.Overridable;",
118                         "public class Compat {",
119                         "    /**",
120                         "    * description of",
121                         "    * MY_CHANGE_ID",
122                         "    */",
123                         "    @EnabledAfter(targetSdkVersion=29)",
124                         "    @ChangeId",
125                         "    static final long MY_CHANGE_ID = 123456789l;",
126                         "    /** description of ANOTHER_CHANGE **/",
127                         "    @ChangeId",
128                         "    @Disabled",
129                         "    public static final long ANOTHER_CHANGE = 23456700l;",
130                         "    /** description of LAST_CHANGE **/",
131                         "    @ChangeId",
132                         "    @EnabledSince(targetSdkVersion=30)",
133                         "    public static final long LAST_CHANGE = 23456701l;",
134                         "    /** description of OVERRIDABLE_CHANGE **/",
135                         "    @ChangeId",
136                         "    @Overridable",
137                         "    public static final long OVERRIDABLE_CHANGE = 23456702l;",
138                         "}")
139         };
140         String expectedFile = HEADER + "<config>" +
141                 "<compat-change description=\"description of MY_CHANGE_ID\" "
142                 + "enableAfterTargetSdk=\"29\" id=\"123456789\" name=\"MY_CHANGE_ID\">"
143                 + "<meta-data definedIn=\"libcore.util.Compat\" "
144                 + "sourcePosition=\"libcore/util/Compat.java:13\"/></compat-change>"
145                 + "<compat-change description=\"description of ANOTHER_CHANGE\" disabled=\"true\" "
146                 + "id=\"23456700\" name=\"ANOTHER_CHANGE\"><meta-data definedIn=\"libcore.util"
147                 + ".Compat\" sourcePosition=\"libcore/util/Compat.java:16\"/></compat-change>"
148                 + "<compat-change description=\"description of LAST_CHANGE\" "
149                 + "enableSinceTargetSdk=\"30\" id=\"23456701\" name=\"LAST_CHANGE\">"
150                 + "<meta-data definedIn=\"libcore.util.Compat\" "
151                 + "sourcePosition=\"libcore/util/Compat.java:20\"/></compat-change>"
152                 + "<compat-change description=\"description of OVERRIDABLE_CHANGE\" "
153                 + "id=\"23456702\" name=\"OVERRIDABLE_CHANGE\" overridable=\"true\">"
154                 + "<meta-data definedIn=\"libcore.util.Compat\" "
155                 + "sourcePosition=\"libcore/util/Compat.java:24\"/></compat-change>"
156                 + "</config>";
157         Compilation compilation =
158                 Compiler.javac()
159                         .withProcessors(new ChangeIdProcessor())
160                         .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class));
161         CompilationSubject.assertThat(compilation).succeeded();
162         CompilationSubject.assertThat(compilation).generatedFile(CLASS_OUTPUT, "libcore.util",
163                 "Compat_compat_config.xml").contentsAsString(UTF_8).isEqualTo(expectedFile);
164     }
165 
166     @Test
testCompatConfigXmlOutput_multiplePackages()167     public void testCompatConfigXmlOutput_multiplePackages() {
168         JavaFileObject[] source = {
169                 JavaFileObjects.forSourceLines(
170                         "libcore.util.Compat",
171                         "package libcore.util;",
172                         "import android.compat.annotation.ChangeId;",
173                         "import android.compat.annotation.EnabledAfter;",
174                         "import android.compat.annotation.Disabled;",
175                         "public class Compat {",
176                         "    /**",
177                         "    * description of",
178                         "    * MY_CHANGE_ID",
179                         "    */",
180                         "    @ChangeId",
181                         "    static final long MY_CHANGE_ID = 123456789l;",
182                         "}"),
183                 JavaFileObjects.forSourceLines("android.util.SomeClass",
184                         "package android.util;",
185                         "import android.compat.annotation.ChangeId;",
186                         "import android.compat.annotation.EnabledAfter;",
187                         "import android.compat.annotation.Disabled;",
188                         "public class SomeClass {",
189                         "    /** description of ANOTHER_CHANGE **/",
190                         "    @ChangeId",
191                         "    public static final long ANOTHER_CHANGE = 23456700l;",
192                         "}")
193         };
194         String libcoreExpectedFile = HEADER + "<config>" +
195                 "<compat-change description=\"description of MY_CHANGE_ID\" "
196                 + "id=\"123456789\" name=\"MY_CHANGE_ID\">"
197                 + "<meta-data definedIn=\"libcore.util.Compat\" "
198                 + "sourcePosition=\"libcore/util/Compat.java:10\"/></compat-change>"
199                 + "</config>";
200         String androidExpectedFile = HEADER + "<config>" +
201                 "<compat-change description=\"description of ANOTHER_CHANGE\" "
202                 + "id=\"23456700\" name=\"ANOTHER_CHANGE\"><meta-data definedIn=\"android.util"
203                 + ".SomeClass\" sourcePosition=\"android/util/SomeClass.java:7\"/></compat-change>"
204                 + "</config>";
205         Compilation compilation =
206                 Compiler.javac()
207                         .withProcessors(new ChangeIdProcessor())
208                         .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class));
209         CompilationSubject.assertThat(compilation).succeeded();
210         CompilationSubject.assertThat(compilation).generatedFile(CLASS_OUTPUT, "libcore.util",
211                 "Compat_compat_config.xml").contentsAsString(UTF_8).isEqualTo(libcoreExpectedFile);
212         CompilationSubject.assertThat(compilation).generatedFile(CLASS_OUTPUT, "android.util",
213                 "SomeClass_compat_config.xml").contentsAsString(UTF_8).isEqualTo(
214                 androidExpectedFile);
215     }
216 
217     @Test
testCompatConfigXmlOutput_innerClass()218     public void testCompatConfigXmlOutput_innerClass() {
219         JavaFileObject[] source = {
220                 JavaFileObjects.forSourceLines(
221                         "libcore.util.Compat",
222                         "package libcore.util;",
223                         "import android.compat.annotation.ChangeId;",
224                         "import android.compat.annotation.EnabledAfter;",
225                         "import android.compat.annotation.Disabled;",
226                         "public class Compat {",
227                         "    public class Inner {",
228                         "        /**",
229                         "        * description of",
230                         "        * MY_CHANGE_ID",
231                         "        */",
232                         "        @ChangeId",
233                         "        static final long MY_CHANGE_ID = 123456789l;",
234                         "    }",
235                         "}"),
236         };
237         String expectedFile = HEADER + "<config>" +
238                 "<compat-change description=\"description of MY_CHANGE_ID\" "
239                 + "id=\"123456789\" name=\"MY_CHANGE_ID\"><meta-data definedIn=\"libcore.util"
240                 + ".Compat.Inner\" sourcePosition=\"libcore/util/Compat.java:11\"/>"
241                 + "</compat-change></config>";
242         Compilation compilation =
243                 Compiler.javac()
244                         .withProcessors(new ChangeIdProcessor())
245                         .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class));
246         CompilationSubject.assertThat(compilation).succeeded();
247         CompilationSubject.assertThat(compilation).generatedFile(CLASS_OUTPUT, "libcore.util",
248                 "Compat.Inner_compat_config.xml").contentsAsString(UTF_8).isEqualTo(expectedFile);
249     }
250 
251     @Test
testCompatConfigXmlOutput_interface()252     public void testCompatConfigXmlOutput_interface() {
253         JavaFileObject[] source = {
254                 JavaFileObjects.forSourceLines(
255                         "libcore.util.Compat",
256                         "package libcore.util;",
257                         "import android.compat.annotation.ChangeId;",
258                         "import android.compat.annotation.EnabledAfter;",
259                         "import android.compat.annotation.Disabled;",
260                         "public interface Compat {",
261                         "    /**",
262                         "     * description of",
263                         "     * MY_CHANGE_ID",
264                         "     */",
265                         "    @ChangeId",
266                         "    static final long MY_CHANGE_ID = 123456789l;",
267                         "}"),
268         };
269         String expectedFile = HEADER + "<config>" +
270                 "<compat-change description=\"description of MY_CHANGE_ID\" "
271                 + "id=\"123456789\" name=\"MY_CHANGE_ID\"><meta-data definedIn=\"libcore.util"
272                 + ".Compat\" sourcePosition=\"libcore/util/Compat.java:10\"/>"
273                 + "</compat-change></config>";
274         Compilation compilation =
275                 Compiler.javac()
276                         .withProcessors(new ChangeIdProcessor())
277                         .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class));
278         CompilationSubject.assertThat(compilation).succeeded();
279         CompilationSubject.assertThat(compilation).generatedFile(CLASS_OUTPUT, "libcore.util",
280                 "Compat_compat_config.xml").contentsAsString(UTF_8).isEqualTo(expectedFile);
281     }
282 
283     @Test
testCompatConfigXmlOutput_enum()284     public void testCompatConfigXmlOutput_enum() {
285         JavaFileObject[] source = {
286                 JavaFileObjects.forSourceLines(
287                         "libcore.util.Compat",
288                         "package libcore.util;",
289                         "import android.compat.annotation.ChangeId;",
290                         "import android.compat.annotation.EnabledAfter;",
291                         "import android.compat.annotation.Disabled;",
292                         "public enum Compat {",
293                         "    ENUM_CONSTANT;",
294                         "    /**",
295                         "     * description of",
296                         "     * MY_CHANGE_ID",
297                         "     */",
298                         "    @ChangeId",
299                         "    private static final long MY_CHANGE_ID = 123456789l;",
300                         "}"),
301         };
302         String expectedFile = HEADER + "<config>" +
303                 "<compat-change description=\"description of MY_CHANGE_ID\" "
304                 + "id=\"123456789\" name=\"MY_CHANGE_ID\"><meta-data definedIn=\"libcore.util"
305                 + ".Compat\" sourcePosition=\"libcore/util/Compat.java:11\"/>"
306                 + "</compat-change></config>";
307         Compilation compilation =
308                 Compiler.javac()
309                         .withProcessors(new ChangeIdProcessor())
310                         .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class));
311         CompilationSubject.assertThat(compilation).succeeded();
312         CompilationSubject.assertThat(compilation).generatedFile(CLASS_OUTPUT, "libcore.util",
313                 "Compat_compat_config.xml").contentsAsString(UTF_8).isEqualTo(expectedFile);
314     }
315 
316     @Test
testBothDisabledAndEnabledAfter()317     public void testBothDisabledAndEnabledAfter() {
318         JavaFileObject[] source = {
319                 JavaFileObjects.forSourceLines(
320                         "libcore.util.Compat",
321                         "package libcore.util;",
322                         "import android.compat.annotation.ChangeId;",
323                         "import android.compat.annotation.EnabledAfter;",
324                         "import android.compat.annotation.Disabled;",
325                         "public class Compat {",
326                         "    @EnabledAfter(targetSdkVersion=29)",
327                         "    @Disabled",
328                         "    @ChangeId",
329                         "    static final long MY_CHANGE_ID = 123456789l;",
330                         "}")
331         };
332         Compilation compilation =
333                 Compiler.javac()
334                         .withProcessors(new ChangeIdProcessor())
335                         .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class));
336         CompilationSubject.assertThat(compilation).hadErrorContaining(
337                 "ChangeId cannot be annotated with both @Disabled and "
338                         + "(@EnabledAfter | @EnabledSince).");
339     }
340 
341     @Test
testBothDisabledAndEnabledSince()342     public void testBothDisabledAndEnabledSince() {
343         JavaFileObject[] source = {
344                 JavaFileObjects.forSourceLines(
345                         "libcore.util.Compat",
346                         "package libcore.util;",
347                         "import android.compat.annotation.ChangeId;",
348                         "import android.compat.annotation.EnabledSince;",
349                         "import android.compat.annotation.Disabled;",
350                         "public class Compat {",
351                         "    @EnabledSince(targetSdkVersion=29)",
352                         "    @Disabled",
353                         "    @ChangeId",
354                         "    static final long MY_CHANGE_ID = 123456789l;",
355                         "}")
356         };
357         Compilation compilation =
358                 Compiler.javac()
359                         .withProcessors(new ChangeIdProcessor())
360                         .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class));
361         CompilationSubject.assertThat(compilation).hadErrorContaining(
362                 "ChangeId cannot be annotated with both @Disabled and "
363                         + "(@EnabledAfter | @EnabledSince).");
364     }
365 
366 
367     @Test
testBothLoggingOnlyAndEnabledAfter()368     public void testBothLoggingOnlyAndEnabledAfter() {
369         JavaFileObject[] source = {
370                 JavaFileObjects.forSourceLines(
371                         "libcore.util.Compat",
372                         "package libcore.util;",
373                         "import android.compat.annotation.ChangeId;",
374                         "import android.compat.annotation.EnabledAfter;",
375                         "import android.compat.annotation.LoggingOnly;",
376                         "public class Compat {",
377                         "    @EnabledAfter(targetSdkVersion=29)",
378                         "    @LoggingOnly",
379                         "    @ChangeId",
380                         "    static final long MY_CHANGE_ID = 123456789l;",
381                         "}")
382         };
383         Compilation compilation =
384                 Compiler.javac()
385                         .withProcessors(new ChangeIdProcessor())
386                         .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class));
387         CompilationSubject.assertThat(compilation).hadErrorContaining(
388                 "ChangeId cannot be annotated with both @LoggingOnly and "
389                         + "(@EnabledAfter | @EnabledSince | @Disabled).");
390     }
391 
392     @Test
testBothLoggingOnlyAndEnabledSince()393     public void testBothLoggingOnlyAndEnabledSince() {
394         JavaFileObject[] source = {
395                 JavaFileObjects.forSourceLines(
396                         "libcore.util.Compat",
397                         "package libcore.util;",
398                         "import android.compat.annotation.ChangeId;",
399                         "import android.compat.annotation.EnabledSince;",
400                         "import android.compat.annotation.LoggingOnly;",
401                         "public class Compat {",
402                         "    @EnabledSince(targetSdkVersion=29)",
403                         "    @LoggingOnly",
404                         "    @ChangeId",
405                         "    static final long MY_CHANGE_ID = 123456789l;",
406                         "}")
407         };
408         Compilation compilation =
409                 Compiler.javac()
410                         .withProcessors(new ChangeIdProcessor())
411                         .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class));
412         CompilationSubject.assertThat(compilation).hadErrorContaining(
413                 "ChangeId cannot be annotated with both @LoggingOnly and "
414                         + "(@EnabledAfter | @EnabledSince | @Disabled).");
415     }
416 
417     @Test
testBothLoggingOnlyAndDisabled()418     public void testBothLoggingOnlyAndDisabled() {
419         JavaFileObject[] source = {
420                 JavaFileObjects.forSourceLines(
421                         "libcore.util.Compat",
422                         "package libcore.util;",
423                         "import android.compat.annotation.ChangeId;",
424                         "import android.compat.annotation.LoggingOnly;",
425                         "import android.compat.annotation.Disabled;",
426                         "public class Compat {",
427                         "    @LoggingOnly",
428                         "    @Disabled",
429                         "    @ChangeId",
430                         "    static final long MY_CHANGE_ID = 123456789l;",
431                         "}")
432         };
433         Compilation compilation =
434                 Compiler.javac()
435                         .withProcessors(new ChangeIdProcessor())
436                         .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class));
437         CompilationSubject.assertThat(compilation).hadErrorContaining(
438                 "ChangeId cannot be annotated with both @LoggingOnly and "
439                         + "(@EnabledAfter | @EnabledSince | @Disabled).");
440     }
441 
442     @Test
testBothEnabledAfterAndEnabledSince()443     public void testBothEnabledAfterAndEnabledSince() {
444         JavaFileObject[] source = {
445                 JavaFileObjects.forSourceLines(
446                         "libcore.util.Compat",
447                         "package libcore.util;",
448                         "import android.compat.annotation.ChangeId;",
449                         "import android.compat.annotation.EnabledAfter;",
450                         "import android.compat.annotation.EnabledSince;",
451                         "public class Compat {",
452                         "    @EnabledAfter(targetSdkVersion=29)",
453                         "    @EnabledSince(targetSdkVersion=30)",
454                         "    @ChangeId",
455                         "    static final long MY_CHANGE_ID = 123456789l;",
456                         "}")
457         };
458         Compilation compilation =
459                 Compiler.javac()
460                         .withProcessors(new ChangeIdProcessor())
461                         .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class));
462         CompilationSubject.assertThat(compilation).hadErrorContaining(
463                 "ChangeId cannot be annotated with both @EnabledAfter and "
464                         + "@EnabledSince. Prefer using the latter.");
465     }
466 
467     @Test
testLoggingOnly()468     public void testLoggingOnly() {
469         JavaFileObject[] source = {
470                 JavaFileObjects.forSourceLines(
471                         "libcore.util.Compat",
472                         "package libcore.util;",
473                         "import android.compat.annotation.ChangeId;",
474                         "import android.compat.annotation.LoggingOnly;",
475                         "public class Compat {",
476                         "    @LoggingOnly",
477                         "    @ChangeId",
478                         "    static final long MY_CHANGE_ID = 123456789l;",
479                         "}")
480         };
481         String expectedFile = HEADER + "<config>" +
482                 "<compat-change id=\"123456789\" loggingOnly=\"true\" name=\"MY_CHANGE_ID\">" +
483                 "<meta-data definedIn=\"libcore.util.Compat\" " +
484                 "sourcePosition=\"libcore/util/Compat.java:6\"/>" +
485                 "</compat-change></config>";
486         Compilation compilation =
487                 Compiler.javac()
488                         .withProcessors(new ChangeIdProcessor())
489                         .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class));
490         CompilationSubject.assertThat(compilation).succeeded();
491         CompilationSubject.assertThat(compilation).generatedFile(CLASS_OUTPUT, "libcore.util",
492                 "Compat_compat_config.xml").contentsAsString(UTF_8).isEqualTo(expectedFile);
493     }
494 
495     @Test
testIgnoredParams()496     public void testIgnoredParams() {
497         JavaFileObject[] source = {
498                 JavaFileObjects.forSourceLines(
499                         "android.compat.Compatibility",
500                         "package android.compat;",
501                         "import android.compat.annotation.ChangeId;",
502                         "public final class Compatibility {",
503                         "   public static void reportUnconditionalChange(@ChangeId long changeId) {}",
504                         "   public static boolean isChangeEnabled(@ChangeId long changeId) {",
505                         "       return true;",
506                         "   }",
507                         "}")
508         };
509         Compilation compilation =
510                 Compiler.javac()
511                         .withProcessors(new ChangeIdProcessor())
512                         .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class));
513         CompilationSubject.assertThat(compilation).succeeded();
514     }
515 
516     @Test
testOtherClassParams()517     public void testOtherClassParams() {
518         JavaFileObject[] source = {
519                 JavaFileObjects.forSourceLines(
520                         "android.compat.OtherClass",
521                         "package android.compat;",
522                         "import android.compat.annotation.ChangeId;",
523                         "public final class OtherClass {",
524                         "   public static void reportUnconditionalChange(@ChangeId long changeId) {}",
525                         "}")
526         };
527         Compilation compilation =
528                 Compiler.javac()
529                         .withProcessors(new ChangeIdProcessor())
530                         .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class));
531         CompilationSubject.assertThat(compilation).hadErrorContaining(
532                 "Non FIELD element annotated with @ChangeId.");
533     }
534 
535     @Test
testOtherMethodParams()536     public void testOtherMethodParams() {
537         JavaFileObject[] source = {
538                 JavaFileObjects.forSourceLines(
539                         "android.compat.Compatibility",
540                         "package android.compat;",
541                         "import android.compat.annotation.ChangeId;",
542                         "public final class Compatibility {",
543                         "   public static void otherMethod(@ChangeId long changeId) {}",
544                         "}")
545         };
546         Compilation compilation =
547                 Compiler.javac()
548                         .withProcessors(new ChangeIdProcessor())
549                         .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class));
550         CompilationSubject.assertThat(compilation).hadErrorContaining(
551                 "Non FIELD element annotated with @ChangeId.");
552     }
553 
554     @Test
testNonFinal()555     public void testNonFinal() {
556         JavaFileObject[] source = {
557                 JavaFileObjects.forSourceLines(
558                         "libcore.util.Compat",
559                         "package libcore.util;",
560                         "import android.compat.annotation.ChangeId;",
561                         "import android.compat.annotation.EnabledAfter;",
562                         "public class Compat {",
563                         "    @EnabledAfter(targetSdkVersion=29)",
564                         "    @ChangeId",
565                         "    static long MY_CHANGE_ID = 123456789l;",
566                         "}")
567         };
568         Compilation compilation =
569                 Compiler.javac()
570                         .withProcessors(new ChangeIdProcessor())
571                         .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class));
572         CompilationSubject.assertThat(compilation).hadErrorContaining(
573                 "Non constant/final variable annotated with @ChangeId.");
574     }
575 
576     @Test
testNonLong()577     public void testNonLong() {
578         JavaFileObject[] source = {
579                 JavaFileObjects.forSourceLines(
580                         "libcore.util.Compat",
581                         "package libcore.util;",
582                         "import android.compat.annotation.ChangeId;",
583                         "import android.compat.annotation.EnabledAfter;",
584                         "public class Compat {",
585                         "    @EnabledAfter(targetSdkVersion=29)",
586                         "    @ChangeId",
587                         "    static final int MY_CHANGE_ID = 12345;",
588                         "}")
589         };
590         Compilation compilation =
591                 Compiler.javac()
592                         .withProcessors(new ChangeIdProcessor())
593                         .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class));
594         CompilationSubject.assertThat(compilation).hadErrorContaining(
595                 "Variables annotated with @ChangeId must be of type long.");
596     }
597 
598     @Test
testNonStatic()599     public void testNonStatic() {
600         JavaFileObject[] source = {
601                 JavaFileObjects.forSourceLines(
602                         "libcore.util.Compat",
603                         "package libcore.util;",
604                         "import android.compat.annotation.ChangeId;",
605                         "import android.compat.annotation.EnabledAfter;",
606                         "public class Compat {",
607                         "    @EnabledAfter(targetSdkVersion=29)",
608                         "    @ChangeId",
609                         "    final long MY_CHANGE_ID = 123456789l;",
610                         "}")
611         };
612         Compilation compilation =
613                 Compiler.javac()
614                         .withProcessors(new ChangeIdProcessor())
615                         .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class));
616         CompilationSubject.assertThat(compilation).hadErrorContaining(
617                 "Non static variable annotated with @ChangeId.");
618     }
619 
620     @Test
testCompatConfigXmlOutput_hideTag()621     public void testCompatConfigXmlOutput_hideTag() {
622         JavaFileObject[] source = {
623                 JavaFileObjects.forSourceLines(
624                         "libcore.util.Compat",
625                         "package libcore.util;",
626                         "import android.compat.annotation.ChangeId;",
627                         "import android.compat.annotation.EnabledAfter;",
628                         "import android.compat.annotation.Disabled;",
629                         "public class Compat {",
630                         "    public class Inner {",
631                         "        /**",
632                         "        * description of MY_CHANGE_ID.",
633                         "        * @hide",
634                         "        */",
635                         "        @ChangeId",
636                         "        static final long MY_CHANGE_ID = 123456789l;",
637                         "    }",
638                         "}"),
639         };
640         String expectedFile = HEADER + "<config>" +
641                 "<compat-change description=\"description of MY_CHANGE_ID.\" "
642                 + "id=\"123456789\" name=\"MY_CHANGE_ID\"><meta-data definedIn=\"libcore.util"
643                 + ".Compat.Inner\" sourcePosition=\"libcore/util/Compat.java:11\"/>"
644                 + "</compat-change></config>";
645         Compilation compilation =
646                 Compiler.javac()
647                         .withProcessors(new ChangeIdProcessor())
648                         .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class));
649         CompilationSubject.assertThat(compilation).succeeded();
650         CompilationSubject.assertThat(compilation).generatedFile(CLASS_OUTPUT, "libcore.util",
651                 "Compat.Inner_compat_config.xml").contentsAsString(UTF_8).isEqualTo(expectedFile);
652     }
653 
654 }
655