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