- make tests multi-platform

- fix polymorphism finally???
This commit is contained in:
Adam 2022-01-09 14:01:48 +01:00
parent 1be9841d8b
commit beb73d1584
9 changed files with 471 additions and 243 deletions

View file

@ -11,6 +11,7 @@ plugins {
kotlin {
js(IR) {
binaries.executable()
browser()
nodejs()
}
jvm {
@ -48,6 +49,7 @@ kotlin {
implementation(kotlin("test"))
implementation(project.dependencies.platform(libs.kotest.bom))
implementation("io.kotest:kotest-framework-engine")
implementation("io.kotest:kotest-assertions-core")
implementation("io.kotest:kotest-property")
implementation("io.kotest:kotest-assertions-json")

View file

@ -1,17 +1,23 @@
package dev.adamko.kafkatorio.events.schema
import kotlinx.serialization.EncodeDefault
import kotlinx.serialization.Required
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
@Serializable
@SerialName("CONFIG")
//@SerialName("CONFIG")
data class FactorioConfigurationUpdate(
override val modVersion: String,
val factorioData: FactorioGameDataUpdate,
val allMods: List<FactorioModInfo>,
val prototypes: List<FactorioPrototype>,
) : KafkatorioPacket() {
// @Transient
@EncodeDefault
override val packetType = PacketType.CONFIG
}

View file

@ -1,62 +1,56 @@
package dev.adamko.kafkatorio.events.schema
import dev.adamko.kafkatorio.events.schema.FactorioObjectData.ObjectName
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.SerialName
import kotlinx.serialization.EncodeDefault
import kotlinx.serialization.Required
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonContentPolymorphicSerializer
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.modules.SerializersModule
@Serializable
@SerialName("EVENT")
data class FactorioEvent<out T : FactorioObjectData>(
//@SerialName("EVENT")
data class FactorioEvent(
override val modVersion: String,
/** the initial Factorio event (`defines.events`) trigger */
val eventType: String,
/** game time */
val tick: UInt,
val data: T,
val data: FactorioObjectData,
) : KafkatorioPacket() {
// @Transient
@EncodeDefault
override val packetType: PacketType = PacketType.EVENT
companion object {
val kxsModule = SerializersModule {
contextual(FactorioEvent::class) { types -> serializer(types[0]) }
}
}
// companion object {
// val kxsModule = SerializersModule {
// contextual(FactorioEvent::class) { types -> serializer(types[0]) }
// }
// }
}
object FactorioEventSerializer : JsonContentPolymorphicSerializer<FactorioEvent<*>>(
FactorioEvent::class
) {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<out FactorioEvent<*>> {
val objectName: ObjectName? = element
.jsonObject[FactorioEvent<*>::data.name]
?.jsonObject
?.get(FactorioObjectData.discriminatorKey)
?.jsonPrimitive
?.contentOrNull
?.let(ObjectName::valueOf)
val sub = when (objectName) {
ObjectName.LuaPlayer -> PlayerData.serializer()
ObjectName.LuaEntity -> EntityData.serializer()
ObjectName.LuaSurface -> SurfaceData.serializer()
ObjectName.MapChunk -> MapChunk.serializer()
ObjectName.LuaTile -> MapTile.serializer()
ObjectName.ConsoleChatMessage -> ConsoleChatMessage.serializer()
null -> throw IllegalStateException("unknown type $objectName")
}
return FactorioEvent.serializer(sub)
}
}
//object FactorioEventSerializer : JsonContentPolymorphicSerializer<FactorioEvent<*>>(
// FactorioEvent::class
//) {
//
// override fun selectDeserializer(element: JsonElement): DeserializationStrategy<out FactorioEvent<*>> {
//
// val objectName: ObjectName? = element
// .jsonObject[FactorioEvent<*>::data.name]
// ?.jsonObject
// ?.get(FactorioObjectData.discriminatorKey)
// ?.jsonPrimitive
// ?.contentOrNull
// ?.let(ObjectName::valueOf)
//
// val sub = when (objectName) {
// ObjectName.LuaPlayer -> PlayerData.serializer()
// ObjectName.LuaEntity -> EntityData.serializer()
// ObjectName.LuaSurface -> SurfaceData.serializer()
// ObjectName.MapChunk -> MapChunk.serializer()
// ObjectName.LuaTile -> MapTile.serializer()
// ObjectName.ConsoleChatMessage -> ConsoleChatMessage.serializer()
// null -> throw IllegalStateException("unknown type $objectName")
// }
// return FactorioEvent.serializer(sub)
// }
//}

View file

@ -1,14 +1,22 @@
package dev.adamko.kafkatorio.events.schema
import kotlinx.serialization.SerialName
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.EncodeDefault
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonClassDiscriminator
import kotlinx.serialization.json.JsonContentPolymorphicSerializer
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
@Serializable
@JsonClassDiscriminator(FactorioObjectData.discriminatorKey)
//@Serializable
@Serializable(with = FactorioObjectDataSerializer::class)
//@JsonClassDiscriminator(FactorioObjectData.discriminatorKey)
sealed class FactorioObjectData {
/** @see FactorioObjectData.discriminatorKey */
@EncodeDefault
abstract val objectName: ObjectName
// workaround - https://github.com/Kotlin/kotlinx.serialization/issues/1664
@ -43,9 +51,39 @@ sealed class FactorioObjectData {
}
}
object FactorioObjectDataSerializer : JsonContentPolymorphicSerializer<FactorioObjectData>(
FactorioObjectData::class
) {
private val key =
FactorioObjectData::objectName.name
// "objectName"
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<out FactorioObjectData> {
val type = element
.jsonObject[key]
?.jsonPrimitive
?.contentOrNull
?.let { json ->
FactorioObjectData.ObjectName.values().firstOrNull { it.name == json }
}
return when (type) {
FactorioObjectData.ObjectName.LuaPlayer -> PlayerData.serializer()
FactorioObjectData.ObjectName.LuaEntity -> EntityData.serializer()
FactorioObjectData.ObjectName.LuaSurface -> SurfaceData.serializer()
FactorioObjectData.ObjectName.MapChunk -> MapChunk.serializer()
FactorioObjectData.ObjectName.LuaTile -> MapTile.serializer()
FactorioObjectData.ObjectName.ConsoleChatMessage -> ConsoleChatMessage.serializer()
null ->
throw Exception("Unknown FactorioObjectData $key: '$type' ")
}
}
}
@Serializable
@SerialName("LuaPlayer")
//@SerialName("LuaPlayer")
data class PlayerData(
@Serializable(with = ListAsObjectSerializer::class)
val associatedCharactersUnitNumbers: List<UInt>,
@ -56,11 +94,12 @@ data class PlayerData(
val chatColour: Colour,
val lastOnline: UInt,
) : FactorioObjectData() {
@EncodeDefault
override val objectName = ObjectName.LuaPlayer
}
@Serializable
@SerialName("LuaEntity")
//@SerialName("LuaEntity")
data class EntityData(
val active: Boolean,
val health: Double?,
@ -72,43 +111,48 @@ data class EntityData(
val unitNumber: UInt? = null,
val playerIndex: UInt? = null,
) : FactorioObjectData() {
@EncodeDefault
override val objectName = ObjectName.LuaEntity
}
@Serializable
@SerialName("LuaSurface")
//@SerialName("LuaSurface")
data class SurfaceData(
val daytime: Double,
val index: UInt,
val name: String,
) : FactorioObjectData() {
@EncodeDefault
override val objectName = ObjectName.LuaSurface
}
/** Quasi-object. This isn't a Factorio Lua type, but a helpful collection of [MapTile]s */
@Serializable
@SerialName("MapChunk")
//@SerialName("MapChunk")
data class MapChunk(
val tiles: List<MapTile>,
) : FactorioObjectData() {
@EncodeDefault
override val objectName = ObjectName.MapChunk
}
@Serializable
@SerialName("LuaTile")
//@SerialName("LuaTile")
data class MapTile(
val prototypeName: String,
val position: PositionData,
val surfaceIndex: Int,
) : FactorioObjectData() {
@EncodeDefault
override val objectName = ObjectName.LuaTile
}
@Serializable
@SerialName("ConsoleChatMessage")
//@SerialName("ConsoleChatMessage")
data class ConsoleChatMessage(
val authorPlayerIndex: UInt?,
val content: String,
) : FactorioObjectData() {
@EncodeDefault
override val objectName = ObjectName.ConsoleChatMessage
}

View file

@ -29,7 +29,7 @@ internal class ListAsObjectSerializer<T>(dataSerializer: KSerializer<T>) :
val jsonMapperKafkatorio = Json {
prettyPrint = true
prettyPrintIndent = " "
serializersModule = FactorioEvent.kxsModule
// serializersModule = FactorioEvent.kxsModule
// serializersModule = KafkatorioPacket.kxsModule + FactorioEvent.kxsModule
// serializersModule = SerializersModule { }
}

View file

@ -1,6 +1,8 @@
package dev.adamko.kafkatorio.events.schema;
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.EncodeDefault
import kotlinx.serialization.Required
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonContentPolymorphicSerializer
import kotlinx.serialization.json.JsonElement
@ -8,24 +10,34 @@ import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
//@Serializable
@Serializable(with = KafkatorioPacketSerializer::class)
//@JsonClassDiscriminator("packetType")
sealed class KafkatorioPacket {
/** Schema versioning */
abstract val modVersion: String
@EncodeDefault
abstract val packetType: PacketType
enum class PacketType {
EVENT,
CONFIG,
}
}
companion object {
// val kxsModule = SerializersModule {
// polym
// }
}
}
object KafkatorioPacketSerializer : JsonContentPolymorphicSerializer<KafkatorioPacket>(
KafkatorioPacket::class
) {
private const val key = "packetType"
private val key =
KafkatorioPacket::packetType.name
// "packetType"
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<out KafkatorioPacket> {
@ -33,13 +45,15 @@ object KafkatorioPacketSerializer : JsonContentPolymorphicSerializer<KafkatorioP
.jsonObject[key]
?.jsonPrimitive
?.contentOrNull
?.let(KafkatorioPacket.PacketType::valueOf)
?.let { json ->
KafkatorioPacket.PacketType.values().firstOrNull { it.name == json }
}
return when (type) {
KafkatorioPacket.PacketType.EVENT -> FactorioEventSerializer
KafkatorioPacket.PacketType.EVENT -> FactorioEvent.serializer()
KafkatorioPacket.PacketType.CONFIG -> FactorioConfigurationUpdate.serializer()
else ->
throw Exception("Unknown Packet: key '$key' ")
null ->
throw Exception("Unknown KafkatorioPacket ${key}: '$type' ")
}
}
}

View file

@ -0,0 +1,200 @@
package dev.adamko.kafkatorio.events.schema
import io.kotest.assertions.json.shouldEqualJson
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
class FactorioEventTest : FunSpec({
context("Given: json on_player_joined_game") {
// language=JSON
val json = """
{
"data": {
"objectName": "LuaPlayer",
"name": "fredthedeadhead",
"characterUnitNumber": 1,
"associatedCharactersUnitNumbers": {},
"position": {
"type": "MAP",
"x": 30.65625,
"y": 84.07421875
},
"colour": {
"red": 0.869000017642974853515625,
"green": 0.5,
"blue": 0.12999999523162841796875,
"alpha": 0.5
},
"chatColour": {
"red": 1,
"green": 0.62999999523162841796875,
"blue": 0.259000003337860107421875,
"alpha": 1
},
"lastOnline": 2287061
},
"packetType": "EVENT",
"eventType": "on_player_joined_game",
"modVersion": "0.2.1",
"tick": 2287072
}
""".trimIndent()
context("When: decoded") {
val actual: KafkatorioPacket = jsonMapperKafkatorio.decodeFromString(json)
test("Then: parse") {
val expected = FactorioEvent(
data = PlayerData(
name = "fredthedeadhead",
characterUnitNumber = 1u,
associatedCharactersUnitNumbers = emptyList(),
position = PositionData(
type = PositionType.MAP,
x = 30.65625,
y = 84.07421875,
),
colour = Colour(
red = 0.869000017642974853515625f,
green = 0.5f,
blue = 0.12999999523162841796875f,
alpha = 0.5f
),
chatColour = Colour(
red = 1f,
green = 0.62999999523162841796875f,
blue = 0.259000003337860107421875f,
alpha = 1f
),
lastOnline = 2287061u
),
eventType = "on_player_joined_game",
modVersion = "0.2.1",
tick = 2287072u
)
actual shouldBe expected
expected.data.objectName shouldBe FactorioObjectData.ObjectName.LuaPlayer
}
test("Then: expect encode equals json") {
val encoded = jsonMapperKafkatorio.encodeToString(actual)
encoded.shouldEqualJson(json)
}
}
}
context("Given: json LuaSurface") {
// language=JSON
val json = """
{
"data": {
"objectName": "LuaSurface",
"name": "nauvis",
"index": 1,
"daytime": 0.749480000092057618843455202295444905757904052734375
},
"packetType": "EVENT",
"eventType": "on_tick",
"modVersion": "0.2.1",
"tick": 2301240
}
""".trimIndent()
context("When: decoded") {
val actual: KafkatorioPacket = jsonMapperKafkatorio.decodeFromString(json)
test("should equal obj") {
val expected = FactorioEvent(
data = SurfaceData(
name = "nauvis",
index = 1u,
daytime = 0.749480000092057618843455202295444905757904052734375
),
eventType = "on_tick",
modVersion = "0.2.1",
tick = 2301240u
)
actual shouldBe expected
expected.data.objectName shouldBe FactorioObjectData.ObjectName.LuaSurface
}
test("Then: expect encode equals json") {
val encoded = jsonMapperKafkatorio.encodeToString(actual)
encoded.shouldEqualJson(json)
}
}
}
context("Given: LuaEntity on_player_changed_position") {
// language=JSON
val json = """
{
"data": {
"objectName": "LuaEntity",
"name": "character",
"type": "character",
"active": true,
"health": 250,
"healthRatio": 1,
"surfaceIndex": 1,
"unitNumber": 1,
"position": {
"type": "MAP",
"x": 37.859375,
"y": 81.14453125
}
},
"packetType": "EVENT",
"eventType": "on_player_changed_position",
"modVersion": "0.2.1",
"tick": 2301231
}
""".trimIndent()
context("When: decoded") {
val actual: KafkatorioPacket = jsonMapperKafkatorio.decodeFromString(json)
test("parse") {
val expected = FactorioEvent(
data = EntityData(
name = "character",
type = "character",
active = true,
health = 250.0,
healthRatio = 1.0,
surfaceIndex = 1,
unitNumber = 1u,
playerIndex = null,
position = PositionData(
x = 37.859375,
y = 81.14453125,
type = PositionType.MAP,
)
),
eventType = "on_player_changed_position",
modVersion = "0.2.1",
tick = 2301231u
)
actual shouldBe expected
expected.data.objectName shouldBe FactorioObjectData.ObjectName.LuaEntity
}
test("Then: expect encode equals json") {
val encoded = jsonMapperKafkatorio.encodeToString(actual)
encoded.shouldEqualJson(json)
}
}
}
})

View file

@ -1,176 +0,0 @@
package dev.adamko.kafkatorio.events.schema
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.equality.shouldBeEqualToComparingFields
import io.kotest.matchers.shouldBe
import kotlinx.serialization.decodeFromString
import org.intellij.lang.annotations.Language
class FactorioEventTest : BehaviorSpec({
Given("json on_player_joined_game") {
@Language("JSON")
val json = """
{
"data": {
"objectName": "LuaPlayer",
"name": "fredthedeadhead",
"characterUnitNumber": 1,
"associatedCharactersUnitNumbers": {},
"position": {
"type": "MAP",
"x": 30.65625,
"y": 84.07421875
},
"colour": {
"red": 0.869000017642974853515625,
"green": 0.5,
"blue": 0.12999999523162841796875,
"alpha": 0.5
},
"chatColour": {
"red": 1,
"green": 0.62999999523162841796875,
"blue": 0.259000003337860107421875,
"alpha": 1
},
"lastOnline": 2287061
},
"packetType": "EVENT",
"eventType": "on_player_joined_game",
"modVersion": "0.2.1",
"tick": 2287072
}
""".trimIndent()
Then("parse") {
val actual: KafkatorioPacket = jsonMapperKafkatorio.decodeFromString(json)
val expected = FactorioEvent(
data = PlayerData(
name = "fredthedeadhead",
characterUnitNumber = 1u,
associatedCharactersUnitNumbers = emptyList(),
position = PositionData(
type = PositionType.MAP,
x = 30.65625,
y = 84.07421875,
),
colour = Colour(
red = 0.869000017642974853515625f,
green = 0.5f,
blue = 0.12999999523162841796875f,
alpha = 0.5f
),
chatColour = Colour(
red = 1f,
green = 0.62999999523162841796875f,
blue = 0.259000003337860107421875f,
alpha = 1f
),
lastOnline = 2287061u
),
eventType = "on_player_joined_game",
modVersion = "0.2.1",
tick = 2287072u
)
actual shouldBeEqualToComparingFields expected
expected.data.objectName shouldBe FactorioObjectData.ObjectName.LuaPlayer
}
}
Given("json surface") {
@Language("JSON")
val json = """
{
"data": {
"objectName": "LuaSurface",
"name": "nauvis",
"index": 1,
"daytime": 0.749480000092057618843455202295444905757904052734375
},
"packetType": "EVENT",
"eventType": "on_tick",
"modVersion": "0.2.1",
"tick": 2301240
}
""".trimIndent()
Then("parse") {
val actual: KafkatorioPacket = jsonMapperKafkatorio.decodeFromString(json)
val expected = FactorioEvent(
data = SurfaceData(
name = "nauvis",
index = 1u,
daytime = 0.749480000092057618843455202295444905757904052734375
),
eventType = "on_tick",
modVersion = "0.2.1",
tick = 2301240u
)
actual shouldBeEqualToComparingFields expected
expected.data.objectName shouldBe FactorioObjectData.ObjectName.LuaSurface
}
}
Given("LuaEntity on_player_changed_position") {
@Language("JSON")
val json = """
{
"data": {
"objectName": "LuaEntity",
"name": "character",
"type": "character",
"active": true,
"health": 250,
"healthRatio": 1,
"surfaceIndex": 1,
"unitNumber": 1,
"position": {
"type": "MAP",
"x": 37.859375,
"y": 81.14453125
}
},
"packetType": "EVENT",
"eventType": "on_player_changed_position",
"modVersion": "0.2.1",
"tick": 2301231
}
""".trimIndent()
Then("parse") {
val actual: KafkatorioPacket = jsonMapperKafkatorio.decodeFromString(json)
val expected = FactorioEvent(
data = EntityData(
name = "character",
type = "character",
active = true,
health = 250.0,
healthRatio = 1.0,
surfaceIndex = 1,
unitNumber = 1u,
playerIndex = null,
position = PositionData(
x = 37.859375,
y = 81.14453125,
type = PositionType.MAP,
)
),
eventType = "on_player_changed_position",
modVersion = "0.2.1",
tick = 2301231u
)
actual shouldBeEqualToComparingFields expected
expected.data.objectName shouldBe FactorioObjectData.ObjectName.LuaEntity
}
}
})