Add kapt incremental tests

This commit is contained in:
Alexey Tsvetkov
2016-04-25 17:53:10 +03:00
parent fe6e69ff89
commit 853b15ee98
28 changed files with 570 additions and 6 deletions
@@ -1,5 +1,7 @@
package org.jetbrains.kotlin.gradle
import org.jetbrains.kotlin.gradle.util.getFileByName
import org.jetbrains.kotlin.gradle.util.modify
import org.junit.Test
import java.io.File
@@ -123,4 +125,23 @@ fun getSomething() = 10
"args.moduleName = Android-compileFlavor2ReleaseKotlin")
}
}
@Test
fun testAndroidDaggerIC() {
val project = Project("AndroidDaggerProject", gradleVersion)
val options = defaultBuildOptions().copy(incremental = true)
project.build("assembleDebug", options = options) {
assertSuccessful()
}
val file = project.projectDir.getFileByName("AndroidModule.kt")
file.modify { it.replace("fun provideApplicationContext(): Context {",
"fun provideApplicationContext(): Context? {") }
project.build(":app:assembleDebug", options = options) {
assertSuccessful()
assertCompiledKotlinSources(project.relativizeToSubproject("app", file))
}
}
}
@@ -82,8 +82,16 @@ abstract class BaseGradleIT {
copyDirRecursively(File(resourcesRootFile, "GradleWrapper-$wrapperVersion"), projectDir)
}
fun relativePaths(files: Iterable<File>): List<String> =
fun relativize(files: Iterable<File>): List<String> =
files.map { it.relativeTo(projectDir).path }
fun relativize(vararg files: File): List<String> =
files.map { it.relativeTo(projectDir).path }
fun relativizeToSubproject(subproject: String, vararg files: File): List<String> {
val subprojectSir = File(projectDir, subproject)
return files.map { it.relativeTo(subprojectSir).path }
}
}
class CompiledProject(val project: Project, val output: String, val resultCode: Int) {
@@ -1,5 +1,6 @@
package org.jetbrains.kotlin.gradle
import org.jetbrains.kotlin.gradle.util.allJavaFiles
import org.jetbrains.kotlin.gradle.util.getFileByName
import org.junit.Test
@@ -52,6 +53,22 @@ class KaptIT: BaseGradleIT() {
}
}
@Test
fun testStubsWithoutJava() {
val project = Project("kaptStubs", GRADLE_VERSION)
project.setupWorkingDir()
project.projectDir.allJavaFiles().forEach { it.delete() }
project.build("build") {
assertSuccessful()
assertContains("kapt: Using class file stubs")
assertContains(":compileKotlin")
assertContains(":compileJava")
assertFileExists("build/classes/main/example/TestClass.class")
assertFileExists("build/classes/main/example/TestClassGenerated.class")
}
}
@Test
fun testSimpleIncrementalBuild() {
doTestIncrementalBuild("kaptSimple", arrayOf(":compileKotlin", ":compileJava"))
@@ -0,0 +1,189 @@
package org.jetbrains.kotlin.gradle
import org.jetbrains.kotlin.gradle.util.allKotlinFiles
import org.jetbrains.kotlin.gradle.util.findFileByName
import org.jetbrains.kotlin.gradle.util.getFileByName
import org.jetbrains.kotlin.gradle.util.modify
import org.junit.Test
class KaptIncrementalNoStubsIT : KaptIncrementalBaseIT(shouldUseStubs = false)
class KaptIncrementalWithStubsIT : KaptIncrementalBaseIT(shouldUseStubs = true)
abstract class KaptIncrementalBaseIT(val shouldUseStubs: Boolean): BaseGradleIT() {
companion object {
private const val GRADLE_VERSION = "2.10"
private val EXAMPLE_ANNOTATION_REGEX = "@(field:)?example.ExampleAnnotation".toRegex()
private const val GENERATE_STUBS_PLACEHOLDER = "GENERATE_STUBS_PLACEHOLDER"
}
private fun getProject() =
Project("kaptIncrementalCompilationProject", GRADLE_VERSION).apply {
setupWorkingDir()
val buildGradle = projectDir.parentFile.getFileByName("build.gradle")
buildGradle.modify { it.replace(GENERATE_STUBS_PLACEHOLDER, shouldUseStubs.toString()) }
}
private val annotatedElements =
arrayOf("A", "funA", "valA", "funUtil", "valUtil", "B", "funB", "valB", "useB")
override fun defaultBuildOptions(): BuildOptions =
super.defaultBuildOptions().copy(withDaemon = true, incremental = true)
@Test
fun testBasic() {
val project = getProject()
project.build("build") {
assertSuccessful()
checkStubUsage()
checkGenerated(*annotatedElements)
checkNotGenerated("notAnnotatedFun")
}
project.build("build") {
assertSuccessful()
assertContains(":compileKotlin UP-TO-DATE",
":compileJava UP-TO-DATE")
if (shouldUseStubs) {
assertContains(":compileKotlinAfterJava UP-TO-DATE")
}
}
}
@Test
fun testAddAnnotatedElement() {
val project = getProject()
project.build("build") {
assertSuccessful()
}
val utilKt = project.projectDir.getFileByName("util.kt")
utilKt.modify { oldContent ->
"""$oldContent
@example.ExampleAnnotation
fun newUtilFun() {}"""
}
project.build("build") {
assertSuccessful()
// todo: for kapt with stubs check compileKotlin and compileKotlinAfterJava separately
assertCompiledKotlinSources(project.relativize(utilKt))
checkGenerated(*(annotatedElements + arrayOf("newUtilFun")))
}
}
@Test
fun testAddAnnotation() {
val project = getProject()
project.build("build") {
assertSuccessful()
}
val utilKt = project.projectDir.getFileByName("util.kt")
utilKt.modify { it.replace("fun notAnnotatedFun", "@example.ExampleAnnotation fun notAnnotatedFun") }
project.build("build") {
assertSuccessful()
assertCompiledKotlinSources(project.relativize(utilKt))
checkGenerated(*(annotatedElements + arrayOf("notAnnotatedFun")))
}
}
@Test
fun testRemoveSourceFile() {
val project = getProject()
project.build("build") {
assertSuccessful()
}
with (project.projectDir) {
getFileByName("B.kt").delete()
getFileByName("useB.kt").delete()
}
project.build("build") {
assertFailed()
}
project.projectDir.getFileByName("JavaClass.java").delete()
project.build("build") {
assertSuccessful()
assertCompiledKotlinSources(project.relativize(project.projectDir.allKotlinFiles()))
val affectedElements = arrayOf("B", "funB", "valB", "useB")
checkGenerated(*(annotatedElements.toSet() - affectedElements).toTypedArray())
checkNotGenerated(*affectedElements)
}
}
@Test
fun testRemoveAnnotations() {
val project = getProject()
project.build("build") {
assertSuccessful()
}
val bKt = project.projectDir.getFileByName("B.kt")
bKt.modify { it.replace(EXAMPLE_ANNOTATION_REGEX, "") }
val affectedElements = arrayOf("B", "funB", "valB")
project.build("build") {
assertSuccessful()
val useBKt = project.projectDir.getFileByName("useB.kt")
assertCompiledKotlinSources(project.relativize(bKt, useBKt))
checkGenerated(*(annotatedElements.toSet() - affectedElements).toTypedArray())
checkNotGenerated(*affectedElements)
}
}
@Test
fun testChangeAnnotatedPropertyType() {
val project = getProject()
project.build("build") {
assertSuccessful()
}
val bKt = project.projectDir.getFileByName("B.kt")
val useBKt = project.projectDir.getFileByName("useB.kt")
bKt.modify { it.replace("val valB = \"text\"", "val valB = 4") }
project.build("build") {
assertSuccessful()
assertCompiledKotlinSources(project.relativize(bKt, useBKt))
checkGenerated(*annotatedElements)
}
}
private fun CompiledProject.checkGenerated(vararg annotatedElementNames: String) {
getGeneratedFileNames(*annotatedElementNames).forEach {
val file = project.projectDir.getFileByName(it)
assert(file.isFile) { "$file must exist" }
}
}
private fun CompiledProject.checkNotGenerated(vararg annotatedElementNames: String) {
getGeneratedFileNames(*annotatedElementNames).forEach {
val file = project.projectDir.findFileByName(it)
assert(file == null) { "$file must not exist" }
}
}
private fun getGeneratedFileNames(vararg annotatedElementNames: String): Iterable<String> {
val names = annotatedElementNames.map { it.capitalize() + "Generated" }
return names.map { it + ".java" }
}
private fun CompiledProject.checkStubUsage() {
val usingStubs = "kapt: Using class file stubs"
if (shouldUseStubs) {
assertContains(usingStubs)
}
else {
assertNotContains(usingStubs)
}
}
}
@@ -26,7 +26,7 @@ class TestRootAffectedIT : BaseGradleIT() {
project.build("build", options = buildOptions) {
assertSuccessful()
val expectedToCompile = project.relativePaths(listOf(kotlinGreetingJoinerFile) + project.allTestKotlinFiles())
val expectedToCompile = project.relativize(listOf(kotlinGreetingJoinerFile) + project.allTestKotlinFiles())
assertCompiledKotlinSources(expectedToCompile)
}
@@ -50,7 +50,7 @@ class TestRootAffectedIT : BaseGradleIT() {
project.build("build", options = buildOptions) {
assertSuccessful()
val expectedToCompile = project.relativePaths(project.allTestKotlinFiles())
val expectedToCompile = project.relativize(project.allTestKotlinFiles())
assertCompiledKotlinSources(expectedToCompile)
}
@@ -9,8 +9,15 @@ fun File.findFileByName(name: String): File? =
walk().filter { it.isFile && it.name.equals(name, ignoreCase = true) }.firstOrNull()
fun File.allKotlinFiles(): Iterable<File> =
walk().filter { it.isFile && it.extension.toLowerCase().equals("kt") }.toList()
allFilesWithExtension("kt")
fun File.allJavaFiles(): Iterable<File> =
allFilesWithExtension("java")
fun File.allFilesWithExtension(ext: String): Iterable<File> =
walk().filter { it.isFile && it.extension.equals(ext, ignoreCase = true) }.toList()
fun File.modify(transform: (String)->String) {
writeText(transform(readText()))
}
}
@@ -0,0 +1,53 @@
buildscript {
ext.kotlin_version = '0.1-SNAPSHOT'
repositories {
mavenLocal()
jcenter()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.android.tools.build:gradle:$androidToolsVersion"
classpath "com.jakewharton.sdkmanager:gradle-plugin:0.12.+"
}
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
defaultConfig {
applicationId "com.example.dagger.kotlin"
minSdkVersion 14
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
}
repositories {
mavenLocal()
jcenter()
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:23.1.1'
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile 'com.google.dagger:dagger:2.0.1'
kapt 'com.google.dagger:dagger-compiler:2.0'
provided 'org.glassfish:javax.annotation:10.0-b28'
}
kapt {
generateStubs = true
}
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.dagger.kotlin">
<application
android:allowBackup="true"
android:label="app_name"
android:name=".DemoApplication">
<activity
android:label="app_name"
android:name=".ui.HomeActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
</application>
</manifest>
@@ -0,0 +1,44 @@
/*
* Copyright (C) 2013 Square, Inc.
*
* 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.example.dagger.kotlin
import android.content.Context
import android.content.Context.LOCATION_SERVICE
import android.location.LocationManager
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
/**
* A module for Android-specific dependencies which require a [Context] or
* [android.app.Application] to create.
*/
@Module class AndroidModule(private val application: BaseApplication) {
/**
* Allow the application context to be injected but require that it be annotated with
* [@Annotation][ForApplication] to explicitly differentiate it from an activity context.
*/
@Provides @Singleton @ForApplication
fun provideApplicationContext(): Context {
return application
}
@Provides @Singleton
fun provideLocationManager(): LocationManager {
return application.getSystemService(LOCATION_SERVICE) as LocationManager
}
}
@@ -0,0 +1,13 @@
package com.example.dagger.kotlin
import com.example.dagger.kotlin.ui.HomeActivity
import dagger.Component
import javax.inject.Singleton
@Singleton
@Component(modules = arrayOf(AndroidModule::class))
interface ApplicationComponent {
fun inject(application: BaseApplication)
fun inject(homeActivity: HomeActivity)
fun inject(demoActivity: DemoActivity)
}
@@ -0,0 +1,11 @@
package com.example.dagger.kotlin
import android.app.Application
abstract class BaseApplication : Application() {
protected fun initDaggerComponent(): ApplicationComponent {
return DaggerApplicationComponent.builder().androidModule(AndroidModule(this)).build()
}
}
@@ -0,0 +1,12 @@
package com.example.dagger.kotlin
import android.app.Activity
import android.os.Bundle
abstract class DemoActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Perform injection so that when this call returns all dependencies will be available for use.
(application as DemoApplication).component.inject(this)
}
}
@@ -0,0 +1,13 @@
package com.example.dagger.kotlin
class DemoApplication : BaseApplication() {
lateinit var component: ApplicationComponent
override fun onCreate() {
super.onCreate()
val component = initDaggerComponent()
component.inject(this) // As of now, LocationManager should be injected into this.
this.component = component
}
}
@@ -0,0 +1,6 @@
package com.example.dagger.kotlin
import javax.inject.Qualifier
@Qualifier
annotation class ForApplication
@@ -0,0 +1,38 @@
/*
* Copyright (C) 2013 Square, Inc.
*
* 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.example.dagger.kotlin.ui
import android.location.LocationManager
import android.os.Bundle
import com.example.dagger.kotlin.DemoActivity
import com.example.dagger.kotlin.DemoApplication
import com.example.dagger.kotlin.R
import kotlinx.android.synthetic.main.activity_main.locationInfo
import javax.inject.Inject
class HomeActivity : DemoActivity() {
@Inject
lateinit var locationManager: LocationManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
(application as DemoApplication).component.inject(this)
// TODO do something with the injected dependencies here!
locationInfo.text = "Injected LocationManager:\n$locationManager"
}
}
@@ -0,0 +1,10 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" tools:context=".ui.HomeActivity">
<TextView android:id="@+id/locationInfo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="16dp" />
</RelativeLayout>
@@ -0,0 +1,3 @@
<resources>
<string name="app_name">kotlin</string>
</resources>
@@ -0,0 +1,8 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
</style>
</resources>
@@ -0,0 +1 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
@@ -0,0 +1 @@
org.gradle.jvmargs=-ea -XX:MaxPermSize=512m
@@ -0,0 +1,31 @@
buildscript {
repositories {
mavenCentral()
maven {
url 'file://' + pathToKotlinPlugin
}
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:0.1-SNAPSHOT"
}
}
apply plugin: "java"
apply plugin: "kotlin"
repositories {
maven {
url 'file://' + pathToKotlinPlugin
}
mavenCentral()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:0.1-SNAPSHOT"
compile "org.jetbrains.kotlin:annotation-processor-example:0.1-SNAPSHOT"
kapt "org.jetbrains.kotlin:annotation-processor-example:0.1-SNAPSHOT"
}
kapt {
generateStubs = GENERATE_STUBS_PLACEHOLDER
}
@@ -0,0 +1,14 @@
package bar
import foo.A
@example.ExampleAnnotation
class B {
@field:example.ExampleAnnotation
val valB = "text"
@example.ExampleAnnotation
fun funB() {}
fun useAfromB(a: A) {}
}
@@ -0,0 +1,4 @@
package bar
@example.ExampleAnnotation
fun useB(b: B) = b.valB
@@ -0,0 +1,9 @@
package baz
@field:example.ExampleAnnotation
val valUtil = 0
@example.ExampleAnnotation
fun funUtil() {}
fun notAnnotatedFun() {}
@@ -0,0 +1,10 @@
package foo
@example.ExampleAnnotation
class A {
@field:example.ExampleAnnotation
val valA: String = "text"
@example.ExampleAnnotation
fun funA() {}
}
@@ -0,0 +1,21 @@
package foo;
import bar.B;
import bar.UseBKt;
public class JavaClass {
public void test() {
A a = new A();
a.funA();
a.getValA();
B b = new B();
b.useAfromB(a);
b.funB();
b.getValB();
UseBKt.useB(b);
System.out.println(AGenerated.class.getName());
}
}
@@ -7,6 +7,6 @@ public class TestClass {
public val testVal: String = "text"
@example.ExampleAnnotation
public fun testFunction(): Class<*> = TestClassGenerated::class.java
public fun testFunction(): TestClassGenerated = TestClassGenerated()
}