1 /*
<lambda>null2  * Copyright 2020 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 androidx.room.compiler.processing
18 
19 import androidx.kruth.assertThat
20 import androidx.kruth.assertWithMessage
21 import androidx.room.compiler.codegen.XClassName
22 import androidx.room.compiler.codegen.XTypeName
23 import androidx.room.compiler.codegen.asClassName
24 import androidx.room.compiler.processing.util.Source
25 import androidx.room.compiler.processing.util.getDeclaredField
26 import androidx.room.compiler.processing.util.runProcessorTest
27 import com.squareup.javapoet.ClassName
28 import com.squareup.javapoet.JavaFile
29 import com.squareup.javapoet.TypeName
30 import com.squareup.javapoet.TypeSpec
31 import com.squareup.kotlinpoet.javapoet.JClassName
32 import javax.lang.model.element.Modifier
33 import javax.tools.Diagnostic
34 import org.junit.Test
35 import org.junit.runner.RunWith
36 import org.junit.runners.JUnit4
37 
38 @RunWith(JUnit4::class)
39 class XProcessingEnvTest {
40     @Test
41     fun getElement() {
42         runProcessorTest(
43             listOf(
44                 Source.java(
45                     "foo.bar.Baz",
46                     """
47                     package foo.bar;
48                     public class Baz {
49                     }
50                     """
51                         .trimIndent()
52                 )
53             )
54         ) {
55             val qName = "java.util.List"
56             val jClassName = JClassName.get("java.util", "List")
57             val klass = List::class
58             val element = it.processingEnv.requireTypeElement(qName)
59             assertThat(element).isNotNull()
60             assertThat(element.asClassName().java).isEqualTo(jClassName)
61 
62             val type = element.type
63 
64             assertThat(it.processingEnv.findTypeElement(qName)).isEqualTo(element)
65             assertThat(it.processingEnv.findTypeElement(jClassName.toString())).isEqualTo(element)
66             assertThat(it.processingEnv.findTypeElement(klass)).isEqualTo(element)
67 
68             assertThat(it.processingEnv.requireTypeElement(jClassName.toString()))
69                 .isEqualTo(element)
70             assertThat(it.processingEnv.requireTypeElement(klass)).isEqualTo(element)
71 
72             assertThat(it.processingEnv.findType(qName)).isEqualTo(type)
73             assertThat(it.processingEnv.findType(jClassName.toString())).isEqualTo(type)
74             assertThat(it.processingEnv.findType(klass)).isEqualTo(type)
75 
76             assertThat(it.processingEnv.requireType(jClassName.toString())).isEqualTo(type)
77             assertThat(it.processingEnv.requireType(klass)).isEqualTo(type)
78             assertThat(it.processingEnv.requireType(qName)).isEqualTo(type)
79         }
80     }
81 
82     @Test
83     fun basic() {
84         runProcessorTest(
85             listOf(
86                 Source.java(
87                     "foo.bar.Baz",
88                     """
89                 package foo.bar;
90                 public class Baz {
91                     private void foo() {}
92                     public int bar(int param1) {
93                         return 3;
94                     }
95                 }
96                     """
97                         .trimIndent()
98                 )
99             )
100         ) {
101             val element = it.processingEnv.requireTypeElement("foo.bar.Baz")
102             assertThat(element.packageName).isEqualTo("foo.bar")
103             assertThat(element.name).isEqualTo("Baz")
104             assertThat(element.asClassName()).isEqualTo(XClassName.get("foo.bar", "Baz"))
105             assertThat(element.findPrimaryConstructor()).isNull()
106             assertThat(element.getConstructors()).hasSize(1)
107             assertThat(element.getDeclaredMethods()).hasSize(2)
108             assertThat(element.kindName()).isEqualTo("class")
109             assertThat(element.isInterface()).isFalse()
110             assertThat(element.superClass?.typeName).isEqualTo(TypeName.OBJECT)
111         }
112     }
113 
114     @Test
115     fun getPrimitives() {
116         val source =
117             Source.java(
118                 "foo.bar.Baz",
119                 """
120             package foo.bar;
121             class Baz {
122             }
123             """
124                     .trimIndent()
125             )
126         runProcessorTest(listOf(source)) { invocation ->
127             PRIMITIVE_TYPES.zip(BOXED_PRIMITIVE_TYPES).forEach { (primitive, boxed) ->
128                 val targetType = invocation.processingEnv.requireType(primitive)
129                 assertThat(targetType.asTypeName()).isEqualTo(primitive)
130                 assertThat(targetType.boxed().asTypeName()).isEqualTo(boxed)
131             }
132             BOXED_PRIMITIVE_TYPES.forEach { boxed ->
133                 val targetType = invocation.processingEnv.requireType(boxed)
134                 assertThat(targetType.asTypeName()).isEqualTo(boxed)
135                 assertThat(targetType.boxed().asTypeName()).isEqualTo(boxed)
136             }
137         }
138     }
139 
140     @Test
141     fun nestedType() {
142         val src =
143             Source.java(
144                 "foo.bar.Outer",
145                 """
146             package foo.bar;
147             public class Outer {
148                 public static class Inner {
149                 }
150             }
151             """
152                     .trimIndent()
153             )
154         runProcessorTest(sources = listOf(src)) {
155             it.processingEnv.requireTypeElement("foo.bar.Outer.Inner").let {
156                 val className = it.asClassName()
157                 assertThat(className.packageName).isEqualTo("foo.bar")
158                 assertThat(className.simpleNames).containsExactly("Outer", "Inner")
159             }
160         }
161     }
162 
163     @Test
164     fun findGeneratedAnnotation() {
165 
166         fun validateGeneratedAnnotation(jvmTarget: String, fqn: String) {
167             runProcessorTest(
168                 sources = emptyList(),
169                 classpath = emptyList(),
170                 javacArguments = listOf("-target", jvmTarget, "-source", jvmTarget),
171                 kotlincArguments = listOf("-jvm-target=$jvmTarget")
172             ) { invocation ->
173                 val generatedAnnotation = invocation.processingEnv.findGeneratedAnnotation()
174                 assertWithMessage("On jvmTarget=$jvmTarget")
175                     .that(generatedAnnotation?.qualifiedName)
176                     .isEqualTo(fqn)
177             }
178         }
179 
180         validateGeneratedAnnotation("1.8", "javax.annotation.Generated")
181         validateGeneratedAnnotation("9", "javax.annotation.processing.Generated")
182         validateGeneratedAnnotation("11", "javax.annotation.processing.Generated")
183     }
184 
185     @Test
186     fun generateCode() {
187         val javaSrc =
188             Source.java(
189                 "foo.bar.AccessGenerated",
190                 """
191             package foo.bar;
192             public class AccessGenerated {
193                 ToBeGenerated x;
194             }
195             """
196                     .trimIndent()
197             )
198         val kotlinSrc =
199             Source.kotlin(
200                 "AccessGenerated.kt",
201                 """
202             package foo.bar;
203             public class AccessGenerated(x: ToBeGenerated)
204             """
205                     .trimIndent()
206             )
207         listOf(javaSrc, kotlinSrc).forEach { src ->
208             runProcessorTest(sources = listOf(src)) { invocation ->
209                 val className = ClassName.get("foo.bar", "ToBeGenerated")
210                 if (invocation.processingEnv.findTypeElement(className.toString()) == null) {
211                     // generate only if it doesn't exist to handle multi-round
212                     val spec =
213                         TypeSpec.classBuilder(className).addModifiers(Modifier.PUBLIC).build()
214                     JavaFile.builder(className.packageName(), spec)
215                         .build()
216                         .writeTo(invocation.processingEnv.filer)
217                 }
218             }
219         }
220     }
221 
222     @Test
223     fun errorLogFailsCompilation() {
224         val src =
225             Source.java(
226                 "Foo",
227                 """
228             class Foo {}
229             """
230                     .trimIndent()
231             )
232         runProcessorTest(sources = listOf(src)) {
233             it.processingEnv.messager.printMessage(Diagnostic.Kind.ERROR, "intentional failure")
234             it.assertCompilationResult {
235                 compilationDidFail()
236                 hasError("intentional failure")
237             }
238         }
239     }
240 
241     @Test
242     fun typeElementsAreCached() {
243         val src =
244             Source.java(
245                 "JavaSubject",
246                 """
247             class JavaSubject {
248                 NestedClass nestedClass;
249                 class NestedClass {
250                     int x;
251                 }
252             }
253             """
254                     .trimIndent()
255             )
256         runProcessorTest(sources = listOf(src)) { invocation ->
257             val parent = invocation.processingEnv.requireTypeElement("JavaSubject")
258             val nested = invocation.processingEnv.requireTypeElement("JavaSubject.NestedClass")
259             assertThat(nested.enclosingTypeElement).isSameInstanceAs(parent)
260         }
261     }
262 
263     @Test
264     fun jvmVersion() {
265         runProcessorTest(
266             sources =
267                 listOf(
268                     Source.java(
269                         "foo.bar.Baz",
270                         """
271                 package foo.bar;
272                 public class Baz {
273                 }
274                     """
275                             .trimIndent()
276                     )
277                 ),
278             javacArguments = listOf("-source", "11"),
279             kotlincArguments = listOf("-jvm-target=11")
280         ) {
281             assertThat(it.processingEnv.jvmVersion).isEqualTo(11)
282         }
283     }
284 
285     @Test
286     fun requireTypeWithXTypeName() {
287         runProcessorTest { invocation ->
288             invocation.processingEnv.requireType(String::class.asClassName()).let {
289                 val name = it.typeElement!!.qualifiedName
290                 if (invocation.isKsp) {
291                     assertThat(name).isEqualTo("kotlin.String")
292                 } else {
293                     assertThat(name).isEqualTo("java.lang.String")
294                 }
295             }
296             invocation.processingEnv.requireType(Int::class.asClassName()).let {
297                 val name = it.typeElement!!.qualifiedName
298                 if (invocation.isKsp) {
299                     assertThat(name).isEqualTo("kotlin.Int")
300                 } else {
301                     assertThat(name).isEqualTo("java.lang.Integer")
302                 }
303             }
304             invocation.processingEnv.requireType(XTypeName.PRIMITIVE_INT).let {
305                 assertThat(it.typeElement).isNull() // No element is an indicator of primitive type
306                 assertThat(it.asTypeName().java.toString()).isEqualTo("int")
307                 if (invocation.isKsp) {
308                     assertThat(it.asTypeName().kotlin.toString()).isEqualTo("kotlin.Int")
309                 }
310             }
311         }
312     }
313 
314     @Test
315     fun testInteropTypesByNameFirst() {
316         runProcessorTest(
317             sources =
318                 listOf(
319                     Source.kotlin(
320                         "test.Subject.kt",
321                         """
322                         package test
323                         class Subject {
324                             val javaString: java.lang.String = TODO()
325                             val kotlinString: String = TODO()
326                         }
327                         """
328                             .trimIndent()
329                     )
330                 )
331         ) { invocation ->
332             val processingEnv = invocation.processingEnv
333             assertThat(processingEnv.requireTypeElement("java.lang.String").asClassName())
334                 .isEqualTo(XTypeName.STRING)
335             if (invocation.isKsp) {
336                 assertThat(processingEnv.requireTypeElement("kotlin.String").asClassName())
337                     .isEqualTo(XTypeName.STRING)
338             }
339             val subject = processingEnv.requireTypeElement("test.Subject")
340             assertThat(subject.getDeclaredField("javaString").type.typeElement!!.asClassName())
341                 .isEqualTo(XClassName.get("java.lang", "String"))
342             assertThat(subject.getDeclaredField("kotlinString").type.typeElement!!.asClassName())
343                 .isEqualTo(XTypeName.STRING)
344         }
345     }
346 
347     @Test
348     fun testInteropTypesByDeclarationFirst() {
349         runProcessorTest(
350             sources =
351                 listOf(
352                     Source.kotlin(
353                         "test.Subject.kt",
354                         """
355                         package test
356                         class Subject {
357                             val javaString: java.lang.String = TODO()
358                             val kotlinString: String = TODO()
359                         }
360                         """
361                             .trimIndent()
362                     )
363                 )
364         ) { invocation ->
365             val processingEnv = invocation.processingEnv
366             val subject = processingEnv.requireTypeElement("test.Subject")
367             assertThat(subject.getDeclaredField("javaString").type.typeElement!!.asClassName())
368                 .isEqualTo(XClassName.get("java.lang", "String"))
369             assertThat(subject.getDeclaredField("kotlinString").type.typeElement!!.asClassName())
370                 .isEqualTo(XTypeName.STRING)
371             assertThat(processingEnv.requireTypeElement("java.lang.String").asClassName())
372                 .isEqualTo(XTypeName.STRING)
373             if (invocation.isKsp) {
374                 assertThat(processingEnv.requireTypeElement("kotlin.String").asClassName())
375                     .isEqualTo(XTypeName.STRING)
376             }
377         }
378     }
379 
380     companion object {
381         val PRIMITIVE_TYPES =
382             listOf(
383                 XTypeName.PRIMITIVE_BOOLEAN,
384                 XTypeName.PRIMITIVE_BYTE,
385                 XTypeName.PRIMITIVE_SHORT,
386                 XTypeName.PRIMITIVE_INT,
387                 XTypeName.PRIMITIVE_LONG,
388                 XTypeName.PRIMITIVE_CHAR,
389                 XTypeName.PRIMITIVE_FLOAT,
390                 XTypeName.PRIMITIVE_DOUBLE,
391             )
392 
393         val BOXED_PRIMITIVE_TYPES =
394             listOf(
395                 XTypeName.BOXED_BOOLEAN,
396                 XTypeName.BOXED_BYTE,
397                 XTypeName.BOXED_SHORT,
398                 XTypeName.BOXED_INT,
399                 XTypeName.BOXED_LONG,
400                 XTypeName.BOXED_CHAR,
401                 XTypeName.BOXED_FLOAT,
402                 XTypeName.BOXED_DOUBLE,
403             )
404     }
405 }
406