1 /*
2  * Copyright 2020 Google LLC
3  * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 package com.google.devtools.ksp.gradle.testing
18 
19 import com.google.devtools.ksp.processing.SymbolProcessorProvider
20 import org.gradle.testkit.runner.GradleRunner
21 import org.junit.rules.TemporaryFolder
22 import org.junit.rules.TestWatcher
23 import org.junit.runner.Description
24 import kotlin.reflect.KClass
25 
26 /**
27  * JUnit test rule to setup a [TestProject] which contains a KSP processor module and an
28  * application. The application can either be an android app or jvm app.
29  * Test must call [setupAppAsAndroidApp] or [setupAppAsJvmApp] before using the [runner].
30  */
31 class KspIntegrationTestRule(
32     private val tmpFolder: TemporaryFolder
33 ) : TestWatcher() {
34     /**
35      * Initialized when the test starts.
36      */
37     private lateinit var testProject: TestProject
38 
39     /**
40      * The application module in the test project
41      */
42     val appModule
43         get() = testProject.appModule
44 
45     /**
46      * The processor module in the test project
47      */
48     val processorModule
49         get() = testProject.processorModule
50 
51     /**
52      * The configuration passed from the KSP's main build which includes important setup information
53      * like KSP version, local maven repo etc.
54      */
55     val testConfig = TestConfig.read()
56 
57     /**
58      * Returns a gradle runner that is ready to run tasks on the test project.
59      */
runnernull60     fun runner(): GradleRunner {
61         testProject.writeFiles()
62         return GradleRunner.create()
63             .withProjectDir(testProject.rootDir)
64     }
65 
66     /**
67      * Adds the given [SymbolProcessorProvider] to the list of providers in the processor module.
68      * The processors built with these providers will run on the test application.
69      *
70      * The passed argument must be a class with a name (e.g. not inline) as it will be added to
71      * the classpath of the processor and will be re-loaded when the test runs. For this reason,
72      * these classes cannot access to the rest of the test instance.
73      */
addProvidernull74     fun addProvider(provider: KClass<out SymbolProcessorProvider>) {
75         val qName = checkNotNull(provider.java.name) {
76             "Must provide a class that can be loaded by qualified name"
77         }
78         testProject.processorModule.kspServicesFile.appendText("$qName\n")
79     }
80 
81     /**
82      * Sets up the app module as a jvm app, adding necessary plugin dependencies.
83      */
setupAppAsJvmAppnull84     fun setupAppAsJvmApp() {
85         testProject.appModule.plugins.addAll(
86             listOf(
87                 PluginDeclaration.kotlin("jvm", testConfig.kotlinBaseVersion),
88                 PluginDeclaration.id("com.google.devtools.ksp", testConfig.kspVersion)
89             )
90         )
91     }
92 
93     /**
94      * Sets up the app module as an android app, adding necessary plugin dependencies, a manifest
95      * file and necessary gradle configuration.
96      */
setupAppAsAndroidAppnull97     fun setupAppAsAndroidApp() {
98         testProject.appModule.plugins.addAll(
99             listOf(
100                 PluginDeclaration.id("com.android.application", testConfig.androidBaseVersion),
101                 PluginDeclaration.kotlin("android", testConfig.kotlinBaseVersion),
102                 PluginDeclaration.id("com.google.devtools.ksp", testConfig.kspVersion)
103             )
104         )
105         addAndroidBoilerplate()
106     }
107 
108     /**
109      * Sets up the app module as a multiplatform app with the specified [targets], wrapped in a kotlin { } block.
110      */
setupAppAsMultiplatformAppnull111     fun setupAppAsMultiplatformApp(targets: String) {
112         testProject.appModule.plugins.addAll(
113             listOf(
114                 PluginDeclaration.id("com.android.application", testConfig.androidBaseVersion),
115                 PluginDeclaration.kotlin("multiplatform", testConfig.kotlinBaseVersion),
116                 PluginDeclaration.id("com.google.devtools.ksp", testConfig.kspVersion)
117             )
118         )
119         testProject.appModule.buildFileAdditions.add(targets)
120         addAndroidBoilerplate()
121     }
122 
addAndroidBoilerplatenull123     private fun addAndroidBoilerplate() {
124         testProject.writeAndroidGradlePropertiesFile()
125         testProject.appModule.buildFileAdditions.add(
126             """
127             android {
128                 compileSdkVersion(31)
129                 defaultConfig {
130                     minSdkVersion(24)
131                 }
132             }
133             """.trimIndent()
134         )
135         testProject.appModule.moduleRoot.resolve("src/main/AndroidManifest.xml")
136             .also {
137                 it.parentFile.mkdirs()
138             }.writeText(
139                 """
140             <?xml version="1.0" encoding="utf-8"?>
141             <manifest xmlns:android="http://schemas.android.com/apk/res/android"
142                 package="com.example.kspandroidtestapp">
143             </manifest>
144                 """.trimIndent()
145             )
146     }
147 
startingnull148     override fun starting(description: Description) {
149         super.starting(description)
150         testProject = TestProject(tmpFolder.newFolder(), testConfig)
151     }
152 }
153