Parameterize kotlin test infra on CI (#1407)

This commit is contained in:
Zac Sweers
2021-10-25 11:00:56 -04:00
committed by GitHub
parent 7dd3b39376
commit 313683fa98
17 changed files with 328 additions and 183 deletions

View File

@@ -14,6 +14,9 @@
* limitations under the License.
*/
import Build_gradle.TestMode.KAPT
import Build_gradle.TestMode.KSP
import Build_gradle.TestMode.REFLECT
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
@@ -22,11 +25,24 @@ plugins {
id("com.google.devtools.ksp") apply false
}
val useKsp = hasProperty("useKsp")
if (useKsp) {
apply(plugin = "com.google.devtools.ksp")
} else {
apply(plugin = "org.jetbrains.kotlin.kapt")
enum class TestMode {
REFLECT, KAPT, KSP
}
val testMode = findProperty("kotlinTestMode")?.toString()
?.let(TestMode::valueOf)
?: REFLECT
when (testMode) {
REFLECT -> {
// Do nothing!
}
KAPT -> {
apply(plugin = "org.jetbrains.kotlin.kapt")
}
KSP -> {
apply(plugin = "com.google.devtools.ksp")
}
}
tasks.withType<Test>().configureEach {
@@ -48,10 +64,16 @@ tasks.withType<KotlinCompile>().configureEach {
}
dependencies {
if (useKsp) {
"kspTest"(project(":kotlin:codegen"))
} else {
"kaptTest"(project(":kotlin:codegen"))
when (testMode) {
REFLECT -> {
// Do nothing
}
KAPT -> {
"kaptTest"(project(":kotlin:codegen"))
}
KSP -> {
"kspTest"(project(":kotlin:codegen"))
}
}
testImplementation(project(":moshi"))
testImplementation(project(":kotlin:reflect"))

View File

@@ -0,0 +1,87 @@
/*
* Copyright (C) 2021 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
*
* https://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.
*/
import Build_gradle.TestMode.KAPT
import Build_gradle.TestMode.KSP
import Build_gradle.TestMode.REFLECT
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm")
kotlin("kapt") apply false
id("com.google.devtools.ksp") apply false
}
enum class TestMode {
REFLECT, KAPT, KSP
}
val testMode = findProperty("kotlinTestMode")?.toString()
?.let(TestMode::valueOf)
?: KSP
when (testMode) {
REFLECT -> {
// Default to KSP. This is a CI-only thing
apply(plugin = "com.google.devtools.ksp")
}
KAPT -> {
apply(plugin = "org.jetbrains.kotlin.kapt")
}
KSP -> {
apply(plugin = "com.google.devtools.ksp")
}
}
tasks.withType<Test>().configureEach {
// ExtendsPlatformClassWithProtectedField tests a case where we set a protected ByteArrayOutputStream.buf field
jvmArgs("--add-opens=java.base/java.io=ALL-UNNAMED")
}
val useWError = findProperty("kotlinLanguageVersion")?.toString()
?.startsWith("1.5")
?: false
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions {
allWarningsAsErrors = useWError
@Suppress("SuspiciousCollectionReassignment")
freeCompilerArgs += listOf(
"-Xopt-in=kotlin.ExperimentalStdlibApi"
)
}
}
dependencies {
when (testMode) {
REFLECT -> {
// Default to KSP in this case, this is a CI-only thing
"kspTest"(project(":kotlin:codegen"))
}
KAPT -> {
"kaptTest"(project(":kotlin:codegen"))
}
KSP -> {
"kspTest"(project(":kotlin:codegen"))
}
}
testImplementation(project(":moshi"))
testImplementation(project(":kotlin:reflect"))
testImplementation(project(":kotlin:tests:extra-moshi-test-module"))
testImplementation(kotlin("reflect"))
testImplementation(libs.junit)
testImplementation(libs.assertj)
testImplementation(libs.truth)
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright (C) 2021 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
*
* https://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.squareup.moshi.kotlin.codegen
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import kotlin.annotation.AnnotationTarget.TYPE
/*
* These are classes that need only compile.
*/
// Regression test for https://github.com/square/moshi/issues/905
@JsonClass(generateAdapter = true)
data class GenericTestClassWithDefaults<T>(
val input: String = "",
val genericInput: T
)
@Target(TYPE)
annotation class TypeAnnotation
/**
* Compilation-only test to ensure we don't render types with their annotations.
* Regression test for https://github.com/square/moshi/issues/1033
*/
@JsonClass(generateAdapter = true)
data class TypeAnnotationClass(
val propertyWithAnnotatedType: @TypeAnnotation String = "",
val generic: List<@TypeAnnotation String>
)
// Regression test for https://github.com/square/moshi/issues/1277
@JsonClass(generateAdapter = true)
data class OtherTestModel(val TestModel: TestModel? = null)
@JsonClass(generateAdapter = true)
data class TestModel(
val someVariable: Int,
val anotherVariable: String
)
// Regression test for https://github.com/square/moshi/issues/1022
@JsonClass(generateAdapter = true)
internal data class MismatchParentAndNestedClassVisibility(
val type: Int,
val name: String? = null
) {
@JsonClass(generateAdapter = true)
data class NestedClass(
val nestedProperty: String
)
}
// Regression test for https://github.com/square/moshi/issues/1052
@JsonClass(generateAdapter = true)
data class KeysWithSpaces(
@Json(name = "1. Information") val information: String,
@Json(name = "2. Symbol") val symbol: String,
@Json(name = "3. Last Refreshed") val lastRefreshed: String,
@Json(name = "4. Interval") val interval: String,
@Json(name = "5. Output Size") val size: String,
@Json(name = "6. Time Zone") val timeZone: String
)
// Regression test for https://github.com/square/moshi/issues/848
@JsonClass(generateAdapter = true)
data class Hotwords(
val `class`: List<String>?
)
/**
* This is here mostly just to ensure it still compiles. Covers variance, @Json, default values,
* nullability, primitive arrays, and some wacky generics.
*/
@JsonClass(generateAdapter = true)
data class SmokeTestType(
@Json(name = "first_name") val firstName: String,
@Json(name = "last_name") val lastName: String,
val age: Int,
val nationalities: List<String> = emptyList(),
val weight: Float,
val tattoos: Boolean = false,
val race: String?,
val hasChildren: Boolean = false,
val favoriteFood: String? = null,
val favoriteDrink: String? = "Water",
val wildcardOut: MutableList<out String> = mutableListOf(),
val nullableWildcardOut: MutableList<out String?> = mutableListOf(),
val wildcardIn: Array<in String>,
val any: List<*>,
val anyTwo: List<Any>,
val anyOut: MutableList<out Any>,
val nullableAnyOut: MutableList<out Any?>,
val favoriteThreeNumbers: IntArray,
val favoriteArrayValues: Array<String>,
val favoriteNullableArrayValues: Array<String?>,
val nullableSetListMapArrayNullableIntWithDefault: Set<List<Map<String, Array<IntArray?>>>>? = null,
val aliasedName: TypeAliasName = "Woah",
val genericAlias: GenericTypeAlias = listOf("Woah"),
// Regression test for https://github.com/square/moshi/issues/1272
val nestedArray: Array<Map<String, Any>>? = null
)
typealias TypeAliasName = String
typealias GenericTypeAlias = List<String>

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.moshi.kotlin
package com.squareup.moshi.kotlin.codegen
import com.squareup.moshi.JsonClass
import com.squareup.moshi.Moshi
@@ -66,14 +66,6 @@ data class TestClass(
val dynamicInlineOptional: Int = createInlineInt()
)
// Regression test for https://github.com/square/moshi/issues/905
// Just needs to compile
@JsonClass(generateAdapter = true)
data class GenericTestClassWithDefaults<T>(
val input: String = "",
val genericInput: T
)
private fun createInt(): Int {
return 3
}

View File

@@ -36,7 +36,6 @@ import org.junit.Assert.fail
import org.junit.Ignore
import org.junit.Test
import java.util.Locale
import kotlin.annotation.AnnotationTarget.TYPE
import kotlin.properties.Delegates
import kotlin.reflect.full.memberProperties
@@ -1342,19 +1341,6 @@ class GeneratedAdaptersTest {
@JsonClass(generateAdapter = true)
data class DeprecatedProperty(@Deprecated("Deprecated for reasons") val foo: String)
@Target(TYPE)
annotation class TypeAnnotation
/**
* Compilation-only test to ensure we don't render types with their annotations.
* Regression test for https://github.com/square/moshi/issues/1033
*/
@JsonClass(generateAdapter = true)
data class TypeAnnotationClass(
val propertyWithAnnotatedType: @TypeAnnotation String = "",
val generic: List<@TypeAnnotation String>
)
@Test fun typesSizeCheckMessages_noArgs() {
try {
// Note: This is impossible to do if you use the reified adapter extension!
@@ -1401,42 +1387,6 @@ class GeneratedAdaptersTest {
)
}
// Regression test for https://github.com/square/moshi/issues/1277
// Compile-only test
@JsonClass(generateAdapter = true)
data class OtherTestModel(val TestModel: TestModel? = null)
@JsonClass(generateAdapter = true)
data class TestModel(
val someVariable: Int,
val anotherVariable: String
)
// Regression test for https://github.com/square/moshi/issues/1022
// Compile-only test
@JsonClass(generateAdapter = true)
internal data class MismatchParentAndNestedClassVisibility(
val type: Int,
val name: String? = null
) {
@JsonClass(generateAdapter = true)
data class NestedClass(
val nestedProperty: String
)
}
// Regression test for https://github.com/square/moshi/issues/1052
// Compile-only test
@JsonClass(generateAdapter = true)
data class KeysWithSpaces(
@Json(name = "1. Information") val information: String,
@Json(name = "2. Symbol") val symbol: String,
@Json(name = "3. Last Refreshed") val lastRefreshed: String,
@Json(name = "4. Interval") val interval: String,
@Json(name = "5. Output Size") val size: String,
@Json(name = "6. Time Zone") val timeZone: String
)
// Has to be outside to avoid Types seeing an owning class
@JsonClass(generateAdapter = true)
data class NullableTypeParams<T>(
@@ -1446,45 +1396,3 @@ data class NullableTypeParams<T>(
val nullableT: T?,
val nonNullT: T
)
/**
* This is here mostly just to ensure it still compiles. Covers variance, @Json, default values,
* nullability, primitive arrays, and some wacky generics.
*/
@JsonClass(generateAdapter = true)
data class SmokeTestType(
@Json(name = "first_name") val firstName: String,
@Json(name = "last_name") val lastName: String,
val age: Int,
val nationalities: List<String> = emptyList(),
val weight: Float,
val tattoos: Boolean = false,
val race: String?,
val hasChildren: Boolean = false,
val favoriteFood: String? = null,
val favoriteDrink: String? = "Water",
val wildcardOut: MutableList<out String> = mutableListOf(),
val nullableWildcardOut: MutableList<out String?> = mutableListOf(),
val wildcardIn: Array<in String>,
val any: List<*>,
val anyTwo: List<Any>,
val anyOut: MutableList<out Any>,
val nullableAnyOut: MutableList<out Any?>,
val favoriteThreeNumbers: IntArray,
val favoriteArrayValues: Array<String>,
val favoriteNullableArrayValues: Array<String?>,
val nullableSetListMapArrayNullableIntWithDefault: Set<List<Map<String, Array<IntArray?>>>>? = null,
val aliasedName: TypeAliasName = "Woah",
val genericAlias: GenericTypeAlias = listOf("Woah"),
// Regression test for https://github.com/square/moshi/issues/1272
val nestedArray: Array<Map<String, Any>>? = null
)
// Compile only, regression test for https://github.com/square/moshi/issues/848
@JsonClass(generateAdapter = true)
data class Hotwords(
val `class`: List<String>?
)
typealias TypeAliasName = String
typealias GenericTypeAlias = List<String>

View File

@@ -0,0 +1,48 @@
/*
* Copyright (C) 2021 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
*
* https://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.squareup.moshi.kotlin.codegen
import com.google.common.truth.Truth.assertThat
import com.squareup.moshi.JsonClass
import com.squareup.moshi.Moshi
import com.squareup.moshi.adapter
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import org.junit.Test
class MixingReflectAndCodeGen {
@Test
fun mixingReflectionAndCodegen() {
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
val generatedAdapter = moshi.adapter<UsesGeneratedAdapter>()
val reflectionAdapter = moshi.adapter<UsesReflectionAdapter>()
assertThat(generatedAdapter.toString())
.isEqualTo("GeneratedJsonAdapter(MixingReflectAndCodeGen.UsesGeneratedAdapter).nullSafe()")
assertThat(reflectionAdapter.toString())
.isEqualTo(
"KotlinJsonAdapter(com.squareup.moshi.kotlin.codegen.MixingReflectAndCodeGen" +
".UsesReflectionAdapter).nullSafe()"
)
}
@JsonClass(generateAdapter = true)
class UsesGeneratedAdapter(var a: Int, var b: Int)
@JsonClass(generateAdapter = false)
class UsesReflectionAdapter(var a: Int, var b: Int)
}

View File

@@ -18,8 +18,6 @@ package com.squareup.moshi.kotlin
import com.google.common.truth.Truth.assertThat
import com.squareup.moshi.FromJson
import com.squareup.moshi.Json
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonAdapter.Factory
import com.squareup.moshi.JsonClass
import com.squareup.moshi.JsonDataException
import com.squareup.moshi.JsonQualifier
@@ -32,53 +30,15 @@ import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import org.intellij.lang.annotations.Language
import org.junit.Assert.fail
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.junit.runners.Parameterized.Parameters
import java.lang.reflect.Type
import kotlin.annotation.AnnotationRetention.RUNTIME
/**
* Parameterized tests that test serialization with both [KotlinJsonAdapterFactory] and code gen.
*/
@RunWith(Parameterized::class)
class DualKotlinTest(useReflection: Boolean) {
companion object {
@Parameters(name = "reflective={0}")
@JvmStatic
fun parameters(): List<Array<*>> {
return listOf(
arrayOf(true),
arrayOf(false)
)
}
}
class DualKotlinTest {
@Suppress("UNCHECKED_CAST")
private val moshi = Moshi.Builder()
.apply {
if (useReflection) {
add(KotlinJsonAdapterFactory())
add(
object : Factory {
override fun create(
type: Type,
annotations: MutableSet<out Annotation>,
moshi: Moshi
): JsonAdapter<*>? {
// Prevent falling back to generated adapter lookup
val rawType = Types.getRawType(type)
val metadataClass = Class.forName("kotlin.Metadata") as Class<out Annotation>
check(rawType.isEnum || !rawType.isAnnotationPresent(metadataClass)) {
"Unhandled Kotlin type in reflective test! $rawType"
}
return moshi.nextAdapter<Any>(this, type, annotations)
}
}
)
}
}
// If code gen ran, the generated adapter will be tried first. If it can't find it, it will
// gracefully fall back to the KotlinJsonAdapter. This allows us to easily test both.
.addLast(KotlinJsonAdapterFactory())
.build()
@Test fun requiredValueAbsent() {
@@ -366,19 +326,22 @@ class DualKotlinTest(useReflection: Boolean) {
}
}
}
assertThat(adapter.toJson(data))
//language=JSON
.isEqualTo(
"""
{"text":"root","t":{"text":"child 1"},"r":{"number":0,"t":{"number":1},"r":{"text":"grand child 1"}}}
{"text":"root","r":{"number":0,"r":{"text":"grand child 1"},"t":{"number":1}},"t":{"text":"child 1"}}
""".trimIndent()
)
}
@JsonClass(generateAdapter = true)
open class Node<T : Node<T, R>, R : Node<R, T>> {
var t: T? = null
// kotlin-reflect doesn't preserve ordering, so put these in alphabetical order so that
// both reflective and code gen tests work the same
var r: R? = null
var t: T? = null
}
@JsonClass(generateAdapter = true)

View File

@@ -886,28 +886,6 @@ class KotlinJsonAdapterTest {
assertThat(adapter.toJson(value)).isEqualTo(json)
}
@Test fun mixingReflectionAndCodegen() {
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
val generatedAdapter = moshi.adapter<UsesGeneratedAdapter>()
val reflectionAdapter = moshi.adapter<UsesReflectionAdapter>()
assertThat(generatedAdapter.toString())
.isEqualTo("GeneratedJsonAdapter(KotlinJsonAdapterTest.UsesGeneratedAdapter).nullSafe()")
assertThat(reflectionAdapter.toString())
.isEqualTo(
"KotlinJsonAdapter(com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterTest" +
".UsesReflectionAdapter).nullSafe()"
)
}
@JsonClass(generateAdapter = true)
class UsesGeneratedAdapter(var a: Int, var b: Int)
@JsonClass(generateAdapter = false)
class UsesReflectionAdapter(var a: Int, var b: Int)
@Retention(RUNTIME)
@JsonQualifier
annotation class Uppercase