Parameterize kotlin test infra on CI (#1407)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index afea037..745b810 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -4,15 +4,15 @@
 
 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 @@
           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 }}'
diff --git a/kotlin/reflect/src/main/java/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapter.kt b/kotlin/reflect/src/main/java/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapter.kt
index 0db237e..f4a01ac 100644
--- a/kotlin/reflect/src/main/java/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapter.kt
+++ b/kotlin/reflect/src/main/java/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapter.kt
@@ -21,16 +21,19 @@
 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 @@
       }
 
       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()),
diff --git a/kotlin/tests/build.gradle.kts b/kotlin/tests/build.gradle.kts
index 4b808e2..e4e9fba 100644
--- a/kotlin/tests/build.gradle.kts
+++ b/kotlin/tests/build.gradle.kts
@@ -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 @@
   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 @@
 }
 
 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"))
diff --git a/kotlin/tests/codegen-only/build.gradle.kts b/kotlin/tests/codegen-only/build.gradle.kts
new file mode 100644
index 0000000..7c5acb2
--- /dev/null
+++ b/kotlin/tests/codegen-only/build.gradle.kts
@@ -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)
+}
diff --git a/kotlin/tests/codegen-only/src/test/kotlin/com/squareup/moshi/kotlin/codegen/CompileOnlyTests.kt b/kotlin/tests/codegen-only/src/test/kotlin/com/squareup/moshi/kotlin/codegen/CompileOnlyTests.kt
new file mode 100644
index 0000000..f231683
--- /dev/null
+++ b/kotlin/tests/codegen-only/src/test/kotlin/com/squareup/moshi/kotlin/codegen/CompileOnlyTests.kt
@@ -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>
diff --git a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/ComplexGenericsInheritanceTest.kt b/kotlin/tests/codegen-only/src/test/kotlin/com/squareup/moshi/kotlin/codegen/ComplexGenericsInheritanceTest.kt
similarity index 100%
rename from kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/ComplexGenericsInheritanceTest.kt
rename to kotlin/tests/codegen-only/src/test/kotlin/com/squareup/moshi/kotlin/codegen/ComplexGenericsInheritanceTest.kt
diff --git a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/DefaultConstructorTest.kt b/kotlin/tests/codegen-only/src/test/kotlin/com/squareup/moshi/kotlin/codegen/DefaultConstructorTest.kt
similarity index 89%
rename from kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/DefaultConstructorTest.kt
rename to kotlin/tests/codegen-only/src/test/kotlin/com/squareup/moshi/kotlin/codegen/DefaultConstructorTest.kt
index 4b21e85..a211c7a 100644
--- a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/DefaultConstructorTest.kt
+++ b/kotlin/tests/codegen-only/src/test/kotlin/com/squareup/moshi/kotlin/codegen/DefaultConstructorTest.kt
@@ -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 @@
   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
 }
diff --git a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/GeneratedAdaptersTest.kt b/kotlin/tests/codegen-only/src/test/kotlin/com/squareup/moshi/kotlin/codegen/GeneratedAdaptersTest.kt
similarity index 92%
rename from kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/GeneratedAdaptersTest.kt
rename to kotlin/tests/codegen-only/src/test/kotlin/com/squareup/moshi/kotlin/codegen/GeneratedAdaptersTest.kt
index 8d3d183..86a4bb2 100644
--- a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/GeneratedAdaptersTest.kt
+++ b/kotlin/tests/codegen-only/src/test/kotlin/com/squareup/moshi/kotlin/codegen/GeneratedAdaptersTest.kt
@@ -36,7 +36,6 @@
 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 @@
   @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 @@
   )
 }
 
-// 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 @@
   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>
diff --git a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/GeneratedAdaptersTest_CustomGeneratedClassJsonAdapter.kt b/kotlin/tests/codegen-only/src/test/kotlin/com/squareup/moshi/kotlin/codegen/GeneratedAdaptersTest_CustomGeneratedClassJsonAdapter.kt
similarity index 100%
rename from kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/GeneratedAdaptersTest_CustomGeneratedClassJsonAdapter.kt
rename to kotlin/tests/codegen-only/src/test/kotlin/com/squareup/moshi/kotlin/codegen/GeneratedAdaptersTest_CustomGeneratedClassJsonAdapter.kt
diff --git a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/LooksLikeAClass/ClassInPackageThatLooksLikeAClass.kt b/kotlin/tests/codegen-only/src/test/kotlin/com/squareup/moshi/kotlin/codegen/LooksLikeAClass/ClassInPackageThatLooksLikeAClass.kt
similarity index 100%
rename from kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/LooksLikeAClass/ClassInPackageThatLooksLikeAClass.kt
rename to kotlin/tests/codegen-only/src/test/kotlin/com/squareup/moshi/kotlin/codegen/LooksLikeAClass/ClassInPackageThatLooksLikeAClass.kt
diff --git a/kotlin/tests/codegen-only/src/test/kotlin/com/squareup/moshi/kotlin/codegen/MixingReflectAndCodeGen.kt b/kotlin/tests/codegen-only/src/test/kotlin/com/squareup/moshi/kotlin/codegen/MixingReflectAndCodeGen.kt
new file mode 100644
index 0000000..29510c9
--- /dev/null
+++ b/kotlin/tests/codegen-only/src/test/kotlin/com/squareup/moshi/kotlin/codegen/MixingReflectAndCodeGen.kt
@@ -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)
+}
diff --git a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/MoshiKspTest.kt b/kotlin/tests/codegen-only/src/test/kotlin/com/squareup/moshi/kotlin/codegen/MoshiKspTest.kt
similarity index 100%
rename from kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/MoshiKspTest.kt
rename to kotlin/tests/codegen-only/src/test/kotlin/com/squareup/moshi/kotlin/codegen/MoshiKspTest.kt
diff --git a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/MultipleMasksTest.kt b/kotlin/tests/codegen-only/src/test/kotlin/com/squareup/moshi/kotlin/codegen/MultipleMasksTest.kt
similarity index 100%
rename from kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/MultipleMasksTest.kt
rename to kotlin/tests/codegen-only/src/test/kotlin/com/squareup/moshi/kotlin/codegen/MultipleMasksTest.kt
diff --git a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/annotation/UppercaseInAnnotationPackage.kt b/kotlin/tests/codegen-only/src/test/kotlin/com/squareup/moshi/kotlin/codegen/annotation/UppercaseInAnnotationPackage.kt
similarity index 100%
rename from kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/annotation/UppercaseInAnnotationPackage.kt
rename to kotlin/tests/codegen-only/src/test/kotlin/com/squareup/moshi/kotlin/codegen/annotation/UppercaseInAnnotationPackage.kt
diff --git a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/DualKotlinTest.kt b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/DualKotlinTest.kt
index 82d20a2..5a1ff64 100644
--- a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/DualKotlinTest.kt
+++ b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/DualKotlinTest.kt
@@ -18,8 +18,6 @@
 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 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 @@
         }
       }
     }
+
     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)
diff --git a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapterTest.kt b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapterTest.kt
index 23d857c..3a0424b 100644
--- a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapterTest.kt
+++ b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapterTest.kt
@@ -886,28 +886,6 @@
     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
diff --git a/settings.gradle.kts b/settings.gradle.kts
index f3a138f..d3ff693 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -31,6 +31,7 @@
 include(":kotlin:reflect")
 include(":kotlin:codegen")
 include(":kotlin:tests")
+include(":kotlin:tests:codegen-only")
 include(":kotlin:tests:extra-moshi-test-module")
 
 enableFeaturePreview("VERSION_CATALOGS")