1 /*
<lambda>null2  * Copyright 2021 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.room.compiler.processing.ksp.KspTypeElement
21 import androidx.room.compiler.processing.util.Source
22 import androidx.room.compiler.processing.util.XTestInvocation
23 import androidx.room.compiler.processing.util.compileFiles
24 import androidx.room.compiler.processing.util.getAllFieldNames
25 import androidx.room.compiler.processing.util.runKspTest
26 import androidx.room.compiler.processing.util.runProcessorTest
27 import com.google.devtools.ksp.symbol.Origin
28 import org.junit.Test
29 import org.junit.runner.RunWith
30 import org.junit.runners.Parameterized
31 
32 /** see: https://github.com/google/ksp/issues/250 */
33 @RunWith(Parameterized::class)
34 class KspClassFileUtilityTest(val preCompile: Boolean) {
35     @Test
36     fun outOfOrderKotlin_fields() {
37         val libSource =
38             Source.kotlin(
39                 "lib.kt",
40                 """
41             class KotlinClass(
42                 val consA: String,
43                 val consB: String,
44             ) {
45                 val b: String = TODO()
46                 val a: String = TODO()
47                 val c: String = TODO()
48                 val isB:String = TODO()
49                 val isA:String = TODO()
50                 val isC:String = TODO()
51             }
52             """
53                     .trimIndent()
54             )
55         runTest(sources = listOf(libSource)) { invocation ->
56             val element = invocation.processingEnv.requireTypeElement("KotlinClass")
57             assertThat(element.getAllFieldNames())
58                 .containsExactly("consA", "consB", "b", "a", "c", "isB", "isA", "isC")
59                 .inOrder()
60         }
61     }
62 
63     @Test
64     fun outOfOrderJava_fields() {
65         val libSource =
66             Source.java(
67                 "JavaClass",
68                 """
69             class JavaClass {
70                 String b;
71                 String a;
72                 String c;
73                 String isB;
74                 String isA;
75                 String isC;
76             }
77             """
78                     .trimIndent()
79             )
80         runTest(
81             sources = listOf(libSource),
82         ) { invocation ->
83             val element = invocation.processingEnv.requireTypeElement("JavaClass")
84             assertThat(element.getAllFieldNames())
85                 .containsExactly("b", "a", "c", "isB", "isA", "isC")
86                 .inOrder()
87         }
88     }
89 
90     @Test
91     fun outOfOrderKotlin_methods() {
92         val libSource =
93             Source.kotlin(
94                 "lib.kt",
95                 """
96             class KotlinClass {
97                 fun b(): String = TODO()
98                 fun a(): String = TODO()
99                 fun c(): String = TODO()
100                 fun isB(): String = TODO()
101                 fun isA(): String = TODO()
102                 fun isC(): String = TODO()
103             }
104             """
105                     .trimIndent()
106             )
107         runTest(sources = listOf(libSource)) { invocation ->
108             val element = invocation.processingEnv.requireTypeElement("KotlinClass")
109             assertThat(element.getDeclaredMethods().map { it.jvmName })
110                 .containsExactly("b", "a", "c", "isB", "isA", "isC")
111                 .inOrder()
112         }
113     }
114 
115     @Test
116     fun outOfOrderJava_methods() {
117         val libSource =
118             Source.java(
119                 "JavaClass",
120                 """
121             class JavaClass {
122                 String b() { return ""; }
123                 String a() { return ""; }
124                 String c() { return ""; }
125                 String isB() { return ""; }
126                 String isA() { return ""; }
127                 String isC() { return ""; }
128             }
129             """
130                     .trimIndent()
131             )
132         runTest(
133             sources = listOf(libSource),
134         ) { invocation ->
135             val element = invocation.processingEnv.requireTypeElement("JavaClass")
136             assertThat(element.getDeclaredMethods().map { it.jvmName })
137                 .containsExactly("b", "a", "c", "isB", "isA", "isC")
138                 .inOrder()
139         }
140     }
141 
142     @Test
143     fun trueOrigin() {
144         // this test is kind of testing ksp itself but it is still good to keep it in case it
145         // breaks.
146         fun createSources(pkg: String) =
147             listOf(
148                 Source.java(
149                     "$pkg.JavaClass",
150                     """
151                 package $pkg;
152                 public class JavaClass {}
153                 """
154                         .trimIndent()
155                 ),
156                 Source.kotlin(
157                     "$pkg/KotlinClass.kt",
158                     """
159                 package $pkg;
160                 class KotlinClass {}
161                 """
162                         .trimIndent()
163                 )
164             )
165 
166         fun XTestInvocation.findOrigin(qName: String) =
167             (processingEnv.requireTypeElement(qName) as KspTypeElement).declaration.origin
168 
169         val preCompiled = compileFiles(createSources("lib"))
170         runKspTest(sources = createSources("main"), classpath = preCompiled) { invocation ->
171             assertThat(invocation.findOrigin("lib.JavaClass")).isEqualTo(Origin.JAVA_LIB)
172             assertThat(invocation.findOrigin("lib.KotlinClass")).isEqualTo(Origin.KOTLIN_LIB)
173             assertThat(invocation.findOrigin("main.JavaClass")).isEqualTo(Origin.JAVA)
174             assertThat(invocation.findOrigin("main.KotlinClass")).isEqualTo(Origin.KOTLIN)
175         }
176     }
177 
178     @Suppress("NAME_SHADOWING") // intentional
179     private fun runTest(sources: List<Source>, handler: (XTestInvocation) -> Unit) {
180         val (sources, classpath) =
181             if (preCompile) {
182                 emptyList<Source>() to compileFiles(sources)
183             } else {
184                 sources to emptyList()
185             }
186         runProcessorTest(
187             sources = sources + Source.kotlin("Placeholder.kt", ""),
188             classpath = classpath,
189             handler = handler
190         )
191     }
192 
193     companion object {
194         @JvmStatic
195         @Parameterized.Parameters(name = "preCompile_{0}")
196         fun params() = arrayOf(false, true)
197     }
198 }
199