KotlinToolingMetadata: Handle incompatible schemaVersions during json parsing

This commit is contained in:
sebastian.sellmair
2021-03-24 10:17:00 +01:00
committed by TeamCityServer
parent 0406659ac8
commit 37aedba7ef
4 changed files with 153 additions and 3 deletions
@@ -118,8 +118,14 @@ fun KotlinToolingMetadata.Companion.parseJsonOrThrow(value: String): KotlinTooli
}
private fun JsonObject.toKotlinToolingMetadataOrThrow(): KotlinToolingMetadata {
val schemaVersion = SchemaVersion.parseStringOrThrow(getOrThrow("schemaVersion").asString)
if (!SchemaVersion.current.isCompatible(schemaVersion)) {
throw IllegalArgumentException(
"Incompatible schemaVersion='$schemaVersion' found. Current schemaVersion='${SchemaVersion.current}'"
)
}
return KotlinToolingMetadata(
schemaVersion = getOrThrow("schemaVersion").asString,
schemaVersion = schemaVersion.toString(),
buildSystem = getOrThrow("buildSystem").asString,
buildSystemVersion = getOrThrow("buildSystemVersion").asString,
buildPlugin = getOrThrow("buildPlugin").asString,
@@ -0,0 +1,45 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.tooling
internal data class SchemaVersion(val major: Int, val minor: Int, val patch: Int) : Comparable<SchemaVersion> {
override fun compareTo(other: SchemaVersion): Int {
(this.major - other.major).takeIf { it != 0 }?.let { return it }
(this.minor - other.minor).takeIf { it != 0 }?.let { return it }
return this.patch - other.patch
}
override fun toString(): String {
return "$major.$minor.$patch"
}
companion object {
val current by lazy { SchemaVersion.parseStringOrThrow(KotlinToolingMetadata.currentSchemaVersion) }
}
}
internal fun SchemaVersion.Companion.parseStringOrThrow(schemaVersion: String): SchemaVersion {
fun throwIllegalSchemaVersion(): Nothing = throw IllegalArgumentException(
"Illegal schemaVersion $schemaVersion. Expected {major}.{minor}.{patch}"
)
val parts = schemaVersion.split(".")
if (parts.size != 3) throwIllegalSchemaVersion()
return SchemaVersion(
major = parts[0].toIntOrNull() ?: throwIllegalSchemaVersion(),
minor = parts[1].toIntOrNull() ?: throwIllegalSchemaVersion(),
patch = parts[2].toIntOrNull() ?: throwIllegalSchemaVersion()
)
}
internal fun SchemaVersion.isCompatible(to: SchemaVersion): Boolean {
if (this.major != to.major) {
return false
}
return (this.minor >= to.minor)
}
@@ -24,10 +24,11 @@ class DeserializationFailureTest {
}
@Test
fun `sample2 missing build pluginVersion`() {
fun `sample2 missing buildPluginVersion`() {
@Language("JSON") val json =
"""
{
"schemaVersion": ${SchemaVersion.current},
"buildSystem": "Gradle",
"buildSystemVersion": "6.7",
"buildPlugin": "org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper",
@@ -45,6 +46,30 @@ class DeserializationFailureTest {
"Expected parsing failure, because of missing pluginVersion. Actual: $result"
)
assertFailsWith<IllegalArgumentException> { KotlinToolingMetadata.parseJsonOrThrow(json) }
val exception = assertFailsWith<IllegalArgumentException> { KotlinToolingMetadata.parseJsonOrThrow(json) }
assertTrue(
"buildPluginVersion" in exception.message.orEmpty(),
"Expected 'buildPluginVersion' mentioned in error message\n${exception.message}"
)
}
@Test
fun `sample3 incompatible schemaVersion`() {
val majorUpgradeVersion = SchemaVersion.current.copy(major = SchemaVersion.current.major + 1)
val metadataString = KotlinToolingMetadata(
schemaVersion = majorUpgradeVersion.toString(),
buildSystem = "",
buildSystemVersion = "",
buildPlugin = "",
buildPluginVersion = "",
projectSettings = KotlinToolingMetadata.ProjectSettings(false, false),
projectTargets = emptyList()
).toJsonString()
val exception = assertFailsWith<IllegalArgumentException> { KotlinToolingMetadata.parseJsonOrThrow(metadataString) }
assertTrue(
majorUpgradeVersion.toString() in exception.message.orEmpty(),
"Expected bad schemaVersion mentioned in error message\n${exception.message}"
)
}
}
@@ -0,0 +1,74 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.tooling
import kotlin.test.*
class SchemaVersionTest {
@Test
fun `compare same version`() {
assertTrue(SchemaVersion(1, 0, 0) <= SchemaVersion(1, 0, 0), "Expected 1.0.0 <= 1.0.0")
assertTrue(SchemaVersion(1, 0, 0) >= SchemaVersion(1, 0, 0), "Expected 1.0.0 >= 1.0.0")
assertFalse(SchemaVersion(1, 0, 0) > SchemaVersion(1, 0, 0), "*NOT* Expected 1.0.0 > 1.0.0")
}
@Test
fun `compare higher version`() {
assertTrue(SchemaVersion(2, 2, 2) > SchemaVersion(2, 2, 1))
assertTrue(SchemaVersion(2, 2, 2) > SchemaVersion(2, 1, 2))
assertTrue(SchemaVersion(2, 2, 2) > SchemaVersion(1, 3, 3))
}
@Test
fun `compare lower version`() {
assertTrue(SchemaVersion(2, 2, 2) < SchemaVersion(2, 2, 3))
assertTrue(SchemaVersion(2, 2, 2) < SchemaVersion(2, 3, 1))
assertTrue(SchemaVersion(2, 2, 2) < SchemaVersion(3, 1, 1))
}
@Test
fun parseSchemaVersion() {
assertEquals(
SchemaVersion(2, 15, 100), SchemaVersion.parseStringOrThrow("2.15.100")
)
assertEquals(
SchemaVersion(0, 0, 0), SchemaVersion.parseStringOrThrow("0.0.0")
)
assertEquals(
SchemaVersion(1, 0, 0), SchemaVersion.parseStringOrThrow("1.0.0")
)
}
@Test
fun `parseSchemaVersion failure`() {
assertFailsWith<IllegalArgumentException> { SchemaVersion.parseStringOrThrow("1.0") }
assertFailsWith<IllegalArgumentException> { SchemaVersion.parseStringOrThrow("1.0.0.0") }
assertFailsWith<IllegalArgumentException> { SchemaVersion.parseStringOrThrow("a.b.c") }
}
@Test
fun `toString and parse`() {
assertEquals(
SchemaVersion(1, 0, 0), SchemaVersion.parseStringOrThrow(SchemaVersion(1, 0, 0).toString())
)
assertEquals(
SchemaVersion(28, 100, 900), SchemaVersion.parseStringOrThrow(SchemaVersion(28, 100, 900).toString())
)
}
@Test
fun isCompatible() {
assertTrue(SchemaVersion(1, 1, 0).isCompatible(SchemaVersion(1, 0, 0)))
assertTrue(SchemaVersion(1, 0, 0).isCompatible(SchemaVersion(1, 0, 0)))
assertFalse(SchemaVersion(1, 0, 0).isCompatible(SchemaVersion(1, 1, 0)))
assertFalse(SchemaVersion(1, 0, 0).isCompatible(SchemaVersion(2, 0, 0)))
assertFalse(SchemaVersion(2, 0, 0).isCompatible(SchemaVersion(1, 0, 0)))
}
}