[go: nahoru, domu]

blob: 62c1489d9dbed6b275ff285e669c0bcc47233e34 [file] [log] [blame]
Ben Schwab2d0f6bc2021-06-12 15:56:36 +00001/*
2 * 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
17package androidx.room.compiler.processing.ksp
18
Brad Corso47077332021-07-30 22:36:34 +000019import androidx.room.compiler.processing.XAnnotation
20import androidx.room.compiler.processing.XAnnotationValue
Ben Schwab2d0f6bc2021-06-12 15:56:36 +000021import androidx.room.compiler.processing.XElement
22import androidx.room.compiler.processing.XMessager
23import androidx.room.compiler.processing.addOriginatingElement
24import androidx.room.compiler.processing.util.Source
25import androidx.room.compiler.processing.util.runKspTest
Daniel Santiago Rivera433ac982023-07-19 17:39:44 +000026import com.google.common.truth.Truth.assertThat
Ben Schwab2d0f6bc2021-06-12 15:56:36 +000027import com.google.devtools.ksp.processing.CodeGenerator
28import com.google.devtools.ksp.processing.Dependencies
Jim Sproche1b4e862021-10-06 00:48:22 -070029import com.google.devtools.ksp.symbol.KSClassDeclaration
Ben Schwab2d0f6bc2021-06-12 15:56:36 +000030import com.google.devtools.ksp.symbol.KSFile
31import com.squareup.kotlinpoet.FileSpec
32import com.squareup.kotlinpoet.FunSpec
33import com.squareup.kotlinpoet.PropertySpec
34import com.squareup.kotlinpoet.TypeSpec
Ben Schwab2d0f6bc2021-06-12 15:56:36 +000035import java.io.File
36import java.io.OutputStream
37import javax.tools.Diagnostic
Daniel Santiago Riverafb435322022-11-21 08:54:41 -050038import kotlin.io.path.Path
39import org.junit.Test
Ben Schwab2d0f6bc2021-06-12 15:56:36 +000040
41class KspFilerTest {
42
43 @Test
44 fun originatingFileAddedForTopLevelFunction() {
45 runKspTest(sources = listOf(simpleKotlinClass)) { invocation ->
46 val sourceElement = invocation.processingEnv.requireTypeElement("foo.bar.Baz")
47 val fileWithTopLevelFun = FileSpec.builder("foo", "Bar.kt").apply {
48 addFunction(FunSpec.builder("baz").addOriginatingElement(sourceElement).build())
49 }.build()
50
51 val codeGenerator = DependencyTrackingCodeGenerator()
52 KspFiler(codeGenerator, TestMessager()).write(fileWithTopLevelFun)
53 codeGenerator.fileDependencies[fileWithTopLevelFun.name]
54 .containsExactlySimpleKotlinClass()
55 }
56 }
57
58 @Test
59 fun originatingFileAddedForTopLevelProperty() {
60 runKspTest(sources = listOf(simpleKotlinClass)) { invocation ->
61 val sourceElement = invocation.processingEnv.requireTypeElement("foo.bar.Baz")
62 val fileWithTopLevelProp = FileSpec.builder("foo", "Bar.kt").apply {
63 addProperty(
64 PropertySpec.builder("baz", String::class).apply {
65 initializer("%S", "")
66 addOriginatingElement(sourceElement)
67 }.build()
68 )
69 }.build()
70
71 val codeGenerator = DependencyTrackingCodeGenerator()
72 KspFiler(codeGenerator, TestMessager()).write(fileWithTopLevelProp)
73 codeGenerator.fileDependencies[fileWithTopLevelProp.name]
74 .containsExactlySimpleKotlinClass()
75 }
76 }
77
78 @Test
79 fun originatingFileAddedForTopLevelElement() {
80 runKspTest(sources = listOf(simpleKotlinClass)) { invocation ->
81 val sourceElement = invocation.processingEnv.requireTypeElement("foo.bar.Baz")
82 val fileWithType = FileSpec.builder("foo", "Bar.kt").apply {
83 addType(
84 TypeSpec.classBuilder("Bar").apply {
85 addOriginatingElement(sourceElement)
86 }.build()
87 )
88 }.build()
89
90 val codeGenerator = DependencyTrackingCodeGenerator()
91 KspFiler(codeGenerator, TestMessager()).write(fileWithType)
92 codeGenerator.fileDependencies[fileWithType.name]
93 .containsExactlySimpleKotlinClass()
94 }
95 }
96
Eli Hart4b910a5d2021-11-18 11:35:44 -080097 @Test
98 fun originatingClassAddedForClassPathAndFileType() {
99 runKspTest(sources = listOf(simpleKotlinClass)) { invocation ->
100 val sourceElement = invocation.processingEnv.requireTypeElement("foo.bar.Baz")
101 val classPathElement = invocation.processingEnv
102 .requireTypeElement("com.google.devtools.ksp.processing.SymbolProcessor")
103
104 val fileWithType = FileSpec.builder("foo", "Bar.kt").apply {
105 addType(
106 TypeSpec.classBuilder("Bar").apply {
107 addOriginatingElement(sourceElement)
108 addOriginatingElement(classPathElement)
109 }.build()
110 )
111 }.build()
112
113 val codeGenerator = DependencyTrackingCodeGenerator()
114 KspFiler(codeGenerator, TestMessager()).write(fileWithType)
115 codeGenerator.fileDependencies[fileWithType.name]
116 .containsExactlySimpleKotlinClass()
117 val (file, classDeclarations) = codeGenerator.classDependencies.entries.single()
118 assertThat(file).isEqualTo("Bar.kt")
119 assertThat(classDeclarations.single())
120 .isEqualTo((classPathElement as KspTypeElement).declaration)
121 }
122 }
123
124 @Test
125 fun originatingClassAddedForClassPathType() {
126 runKspTest(sources = listOf()) { invocation ->
127 val classPathElement = invocation.processingEnv
128 .requireTypeElement("com.google.devtools.ksp.processing.SymbolProcessor")
129
130 val fileWithType = FileSpec.builder("foo", "Bar.kt").apply {
131 addType(
132 TypeSpec.classBuilder("Bar").apply {
133 addOriginatingElement(classPathElement)
134 }.build()
135 )
136 }.build()
137
138 val codeGenerator = DependencyTrackingCodeGenerator()
139 KspFiler(codeGenerator, TestMessager()).write(fileWithType)
140 val (file, classDeclarations) = codeGenerator.classDependencies.entries.single()
141 assertThat(file).isEqualTo("Bar.kt")
142 assertThat(classDeclarations.single())
143 .isEqualTo((classPathElement as KspTypeElement).declaration)
144 }
145 }
146
Daniel Santiago Riverafb435322022-11-21 08:54:41 -0500147 @Test
148 fun writeResource() {
149 runKspTest(
150 sources = emptyList()
151 ) { invocation ->
152 invocation.processingEnv.filer.writeResource(
153 filePath = Path("test.log"),
154 originatingElements = emptyList()
155 ).bufferedWriter(Charsets.UTF_8).use {
156 it.write("Hello!")
157 }
158 invocation.processingEnv.filer.writeResource(
159 filePath = Path("META-INF/services/com.test.Foo"),
160 originatingElements = emptyList()
161 ).bufferedWriter(Charsets.UTF_8).use {
162 it.write("Not a real service...")
163 }
164 invocation.assertCompilationResult {
165 hasNoWarnings()
166 }
167 }
168 }
169
Ben Schwab2d0f6bc2021-06-12 15:56:36 +0000170 private fun Dependencies?.containsExactlySimpleKotlinClass() {
171 assertThat(this).isNotNull()
172 val originatingFiles = this!!.originatingFiles.map { it.fileName }
173 assertThat(originatingFiles).containsExactly("Baz.kt")
174 }
175
176 class TestMessager : XMessager() {
Brad Corso47077332021-07-30 22:36:34 +0000177 override fun onPrintMessage(
178 kind: Diagnostic.Kind,
179 msg: String,
180 element: XElement?,
181 annotation: XAnnotation?,
182 annotationValue: XAnnotationValue?
183 ) {
184 var errorMsg = "${kind.name} element: $element " +
185 "annotation: $annotation " +
186 "annotationValue: $annotationValue " +
187 "msg: $msg"
Ben Schwab2d0f6bc2021-06-12 15:56:36 +0000188 if (kind == Diagnostic.Kind.ERROR) {
Brad Corso47077332021-07-30 22:36:34 +0000189 error(errorMsg)
Ben Schwab2d0f6bc2021-06-12 15:56:36 +0000190 } else {
Brad Corso47077332021-07-30 22:36:34 +0000191 println(errorMsg)
Ben Schwab2d0f6bc2021-06-12 15:56:36 +0000192 }
193 }
194 }
195
196 class DependencyTrackingCodeGenerator : CodeGenerator {
197
198 val fileDependencies = mutableMapOf<String, Dependencies>()
Eli Hart4b910a5d2021-11-18 11:35:44 -0800199 val classDependencies = mutableMapOf<String, MutableSet<KSClassDeclaration>>()
Ben Schwab2d0f6bc2021-06-12 15:56:36 +0000200
201 override val generatedFile: Collection<File>
202 get() = emptyList()
203
204 override fun associate(
205 sources: List<KSFile>,
206 packageName: String,
207 fileName: String,
208 extensionName: String
209 ) {
210 // no-op for the sake of dependency tracking.
211 }
212
Jim Sproche1b4e862021-10-06 00:48:22 -0700213 override fun associateWithClasses(
214 classes: List<KSClassDeclaration>,
215 packageName: String,
216 fileName: String,
217 extensionName: String
218 ) {
Eli Hart4b910a5d2021-11-18 11:35:44 -0800219 classDependencies.getOrPut(fileName) { mutableSetOf() }.addAll(classes)
Jim Sproche1b4e862021-10-06 00:48:22 -0700220 }
221
Ben Schwab2d0f6bc2021-06-12 15:56:36 +0000222 override fun createNewFile(
223 dependencies: Dependencies,
224 packageName: String,
225 fileName: String,
226 extensionName: String
227 ): OutputStream {
228 fileDependencies[fileName] = dependencies
229 return OutputStream.nullOutputStream()
230 }
Jim Sprochd324f462022-11-09 01:42:05 -0800231
232 override fun associateByPath(
233 sources: List<KSFile>,
234 path: String,
235 extensionName: String
236 ) {
237 // no-op for the sake of dependency tracking.
238 }
239
240 override fun createNewFileByPath(
241 dependencies: Dependencies,
242 path: String,
243 extensionName: String
244 ): OutputStream {
245 val fileName = path.split(File.separator).last()
246 fileDependencies[fileName] = dependencies
247 return OutputStream.nullOutputStream()
248 }
Ben Schwab2d0f6bc2021-06-12 15:56:36 +0000249 }
250
251 companion object {
252 val simpleKotlinClass = Source.kotlin(
253 "Baz.kt",
254 """
255 package foo.bar;
256
257 class Baz
258 """.trimIndent()
259 )
260 }
261}