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