aboutsummaryrefslogtreecommitdiff
path: root/gradle-plugin/src/test/kotlin/com/google/devtools/ksp/gradle/testing/KspIntegrationTestRule.kt
blob: fdc64d57dcbf2c05bb6006405fce510a05c1cf6e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
/*
 * Copyright 2020 Google LLC
 * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.devtools.ksp.gradle.testing

import com.google.devtools.ksp.processing.SymbolProcessorProvider
import org.gradle.testkit.runner.GradleRunner
import org.junit.rules.TemporaryFolder
import org.junit.rules.TestWatcher
import org.junit.runner.Description
import kotlin.reflect.KClass

/**
 * JUnit test rule to setup a [TestProject] which contains a KSP processor module and an
 * application. The application can either be an android app or jvm app.
 * Test must call [setupAppAsAndroidApp] or [setupAppAsJvmApp] before using the [runner].
 */
class KspIntegrationTestRule(
    private val tmpFolder: TemporaryFolder
) : TestWatcher() {
    /**
     * Initialized when the test starts.
     */
    private lateinit var testProject: TestProject

    /**
     * The application module in the test project
     */
    val appModule
        get() = testProject.appModule

    /**
     * The processor module in the test project
     */
    val processorModule
        get() = testProject.processorModule

    /**
     * The configuration passed from the KSP's main build which includes important setup information
     * like KSP version, local maven repo etc.
     */
    val testConfig = TestConfig.read()

    /**
     * Returns a gradle runner that is ready to run tasks on the test project.
     */
    fun runner(): GradleRunner {
        testProject.writeFiles()
        return GradleRunner.create()
            .withProjectDir(testProject.rootDir)
    }

    /**
     * Adds the given [SymbolProcessorProvider] to the list of providers in the processor module.
     * The processors built with these providers will run on the test application.
     *
     * The passed argument must be a class with a name (e.g. not inline) as it will be added to
     * the classpath of the processor and will be re-loaded when the test runs. For this reason,
     * these classes cannot access to the rest of the test instance.
     */
    fun addProvider(provider: KClass<out SymbolProcessorProvider>) {
        val qName = checkNotNull(provider.java.name) {
            "Must provide a class that can be loaded by qualified name"
        }
        testProject.processorModule.kspServicesFile.appendText("$qName\n")
    }

    /**
     * Sets up the app module as a jvm app, adding necessary plugin dependencies.
     */
    fun setupAppAsJvmApp() {
        testProject.appModule.plugins.addAll(
            listOf(
                PluginDeclaration.kotlin("jvm", testConfig.kotlinBaseVersion),
                PluginDeclaration.id("com.google.devtools.ksp", testConfig.kspVersion)
            )
        )
    }

    /**
     * Sets up the app module as an android app, adding necessary plugin dependencies, a manifest
     * file and necessary gradle configuration.
     */
    fun setupAppAsAndroidApp() {
        testProject.appModule.plugins.addAll(
            listOf(
                PluginDeclaration.id("com.android.application", testConfig.androidBaseVersion),
                PluginDeclaration.kotlin("android", testConfig.kotlinBaseVersion),
                PluginDeclaration.id("com.google.devtools.ksp", testConfig.kspVersion)
            )
        )
        addAndroidBoilerplate()
    }

    /**
     * Sets up the app module as a multiplatform app with the specified [targets], wrapped in a kotlin { } block.
     */
    fun setupAppAsMultiplatformApp(targets: String) {
        testProject.appModule.plugins.addAll(
            listOf(
                PluginDeclaration.id("com.android.application", testConfig.androidBaseVersion),
                PluginDeclaration.kotlin("multiplatform", testConfig.kotlinBaseVersion),
                PluginDeclaration.id("com.google.devtools.ksp", testConfig.kspVersion)
            )
        )
        testProject.appModule.buildFileAdditions.add(targets)
        addAndroidBoilerplate()
    }

    private fun addAndroidBoilerplate() {
        testProject.appModule.buildFileAdditions.add(
            """
            android {
                compileSdkVersion="android-29"
            }
            """.trimIndent()
        )
        testProject.appModule.moduleRoot.resolve("src/main/AndroidManifest.xml")
            .also {
                it.parentFile.mkdirs()
            }.writeText(
                """
            <?xml version="1.0" encoding="utf-8"?>
            <manifest xmlns:android="http://schemas.android.com/apk/res/android"
                package="com.example.kspandroidtestapp">
            </manifest>
                """.trimIndent()
            )
    }

    override fun starting(description: Description) {
        super.starting(description)
        testProject = TestProject(tmpFolder.newFolder(), testConfig)
    }
}