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

@@ -4,15 +4,15 @@ on: [push, pull_request]
jobs:
build:
name: 'Java ${{ matrix.java-version }} | Kotlin ${{ matrix.kotlin-version }} | KSP ${{ matrix.use-ksp }}'
name: 'Kotlin ${{ matrix.kotlin-version }} | Test Mode ${{ matrix.kotlin-test-mode }}'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
use-ksp: [ true, false ]
kotlin-version: [ '1.5.31', '1.6.0-RC' ]
ksp-version: [ '1.5.31-1.0.0', '1.6.0-RC-1.0.0' ]
kotlin-test-mode: [ 'REFLECT', 'KSP', 'KAPT' ]
exclude:
- kotlin-version: '1.5.31'
ksp-version: '1.6.0-RC-1.0.0'
@@ -47,10 +47,10 @@ jobs:
java-version: '17'
- name: Test
run: ./gradlew build check --stacktrace -PuseKsp=${{ matrix.use-ksp }} -PkotlinVersion=${{ matrix.kotlin-version }}
run: ./gradlew build check --stacktrace -PkotlinTestMode=${{ matrix.kotlin-test-mode }} -PkotlinVersion=${{ matrix.kotlin-version }}
- name: Publish (default branch only)
if: github.repository == 'square/moshi' && github.ref == 'refs/heads/master' && matrix.kotlin-version == '1.5.31' && matrix.use-ksp == 'false'
if: github.repository == 'square/moshi' && github.ref == 'refs/heads/master' && matrix.kotlin-version == '1.5.31' && matrix.kotlin-test-mode == 'reflect'
run: ./gradlew publish
env:
ORG_GRADLE_PROJECT_mavenCentralUsername: '${{ secrets.SONATYPE_NEXUS_USERNAME }}'

View File

@@ -21,16 +21,19 @@ import com.squareup.moshi.JsonDataException
import com.squareup.moshi.JsonReader
import com.squareup.moshi.JsonWriter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import com.squareup.moshi.internal.Util
import com.squareup.moshi.internal.Util.generatedAdapter
import com.squareup.moshi.internal.Util.resolve
import com.squareup.moshi.rawType
import java.lang.reflect.Modifier
import java.lang.reflect.Type
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KMutableProperty1
import kotlin.reflect.KParameter
import kotlin.reflect.KProperty1
import kotlin.reflect.KTypeParameter
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.primaryConstructor
@@ -257,7 +260,31 @@ public class KotlinJsonAdapterFactory : JsonAdapter.Factory {
}
val name = jsonAnnotation?.name ?: property.name
val resolvedPropertyType = resolve(type, rawType, property.returnType.javaType)
val propertyType = when (val propertyTypeClassifier = property.returnType.classifier) {
is KClass<*> -> {
if (propertyTypeClassifier.isValue) {
// When it's a value class, we need to resolve the type ourselves because the javaType
// function will return its inlined type
val rawClassifierType = propertyTypeClassifier.java
if (property.returnType.arguments.isEmpty()) {
rawClassifierType
} else {
Types.newParameterizedType(
rawClassifierType,
*property.returnType.arguments.mapNotNull { it.type?.javaType }.toTypedArray()
)
}
} else {
// This is safe when it's not a value class!
property.returnType.javaType
}
}
is KTypeParameter -> {
property.returnType.javaType
}
else -> error("Not possible!")
}
val resolvedPropertyType = resolve(type, rawType, propertyType)
val adapter = moshi.adapter<Any>(
resolvedPropertyType,
Util.jsonAnnotations(allAnnotations.toTypedArray()),

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,12 +25,25 @@ plugins {
id("com.google.devtools.ksp") apply false
}
val useKsp = hasProperty("useKsp")
if (useKsp) {
apply(plugin = "com.google.devtools.ksp")
} else {
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 {
// ExtendsPlatformClassWithProtectedField tests a case where we set a protected ByteArrayOutputStream.buf field
@@ -48,11 +64,17 @@ tasks.withType<KotlinCompile>().configureEach {
}
dependencies {
if (useKsp) {
"kspTest"(project(":kotlin:codegen"))
} else {
when (testMode) {
REFLECT -> {
// Do nothing
}
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"))

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

View File

@@ -31,6 +31,7 @@ include(":examples")
include(":kotlin:reflect")
include(":kotlin:codegen")
include(":kotlin:tests")
include(":kotlin:tests:codegen-only")
include(":kotlin:tests:extra-moshi-test-module")
enableFeaturePreview("VERSION_CATALOGS")