Skip to content

Commit

Permalink
Generalize encoding/decoding tests (#168)
Browse files Browse the repository at this point in the history
* removed duplicate decoding tests and generalized tests to be used for other decoders
* fixed decoding of short values
* fixed decoding of fixed values
  • Loading branch information
thake authored Jan 28, 2024
1 parent 53a9b7e commit 1723ebc
Show file tree
Hide file tree
Showing 59 changed files with 1,136 additions and 2,107 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import kotlinx.serialization.encoding.CompositeDecoder
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.modules.SerializersModule
import org.apache.avro.Schema
import org.apache.avro.generic.GenericFixed
import org.apache.avro.generic.GenericRecord
import java.nio.ByteBuffer

Expand Down Expand Up @@ -60,6 +61,7 @@ class RecordDecoder(
is Array<*> -> ByteArrayDecoder((value as Array<Byte>).toByteArray(), serializersModule)
is ByteArray -> ByteArrayDecoder(value, serializersModule)
is ByteBuffer -> ByteArrayDecoder(value.array(), serializersModule)
is GenericFixed -> ByteArrayDecoder(value.bytes(), serializersModule)
else -> this
}
} else {
Expand Down Expand Up @@ -160,6 +162,15 @@ class RecordDecoder(
}
}

override fun decodeShort(): Short {
return when (val v = fieldValue()) {
is Short -> v
is Int -> v.toShort()
null -> throw SerializationException("Cannot decode <null> as a Short")
else -> throw SerializationException("Unsupported type for Short ${v.javaClass}")
}
}

override fun decodeLong(): Long {
return when (val v = fieldValue()) {
is Long -> v
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.github.avrokotlin.avro4k

import org.apache.avro.Schema
import org.apache.avro.generic.GenericData
import org.apache.avro.generic.GenericRecord

data class RecordBuilderForTest(
val fields: List<Any?>
) {
fun createRecord(schema: Schema): GenericRecord {
val record = GenericData.Record(schema)
fields.forEachIndexed { index, value ->
val fieldSchema = schema.fields[index].schema()
record.put(index, convertValue(value, fieldSchema))
}
return record
}

private fun convertValue(
value: Any?,
schema: Schema
): Any? {
return when (value) {
is RecordBuilderForTest -> value.createRecord(schema)
is Map<*, *> -> createMap(schema, value)
is List<*> -> createList(schema, value)
else -> value
}
}

fun createList(schema: Schema, value: List<*>): List<*> {
val valueSchema = schema.elementType
return value.map { convertValue(it, valueSchema) }
}

fun <K, V> createMap(schema: Schema, value: Map<K, V>): Map<K, *> {
val valueSchema = schema.valueType
return value.mapValues { convertValue(it.value, valueSchema) }
}
}

fun record(vararg fields: Any?): RecordBuilderForTest {
return RecordBuilderForTest(listOf(*fields))
}
246 changes: 85 additions & 161 deletions src/test/kotlin/com/github/avrokotlin/avro4k/decoder/ArrayDecoderTest.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.github.avrokotlin.avro4k.decoder

import com.github.avrokotlin.avro4k.Avro
import io.kotest.matchers.shouldBe
import io.kotest.core.spec.style.WordSpec
import io.kotest.matchers.shouldBe
import kotlinx.serialization.Serializable
import org.apache.avro.Schema
import org.apache.avro.generic.GenericData
Expand Down Expand Up @@ -30,166 +30,90 @@ data class Record(val str: String, val double: Double)

class ArrayDecoderTest : WordSpec({

"Decoder" should {

"support array for an Array of booleans" {
val schema = Avro.default.schema(TestArrayBooleans.serializer())
val record = GenericData.Record(schema)
record.put("booleans", arrayOf(true, false, true))
Avro.default.fromRecord(TestArrayBooleans.serializer(), record).booleans.toList() shouldBe
listOf(true, false, true)
}

"support list for an Array of booleans" {
val schema = Avro.default.schema(TestArrayBooleans.serializer())
val record = GenericData.Record(schema)
record.put("booleans", listOf(true, false, true))
Avro.default.fromRecord(TestArrayBooleans.serializer(), record).booleans.toList() shouldBe
listOf(true, false, true)
}

"support GenericData.Array for an Array of booleans" {
val schema = Avro.default.schema(TestArrayBooleans.serializer())
val record = GenericData.Record(schema)
record.put("booleans",
GenericData.Array(Schema.createArray(Schema.create(Schema.Type.BOOLEAN)), listOf(true, false, true)))
Avro.default.fromRecord(TestArrayBooleans.serializer(), record).booleans.toList() shouldBe
listOf(true, false, true)
}

"support array for a List of doubles" {

val schema = Avro.default.schema(TestListDoubles.serializer())
val record = GenericData.Record(schema)
record.put("doubles", arrayOf(12.54, 23.5, 9123.2314))
Avro.default.fromRecord(TestListDoubles.serializer(), record) shouldBe
TestListDoubles(listOf(12.54, 23.5, 9123.2314))
}

"support list for a List of doubles" {

val schema = Avro.default.schema(TestListDoubles.serializer())
val record = GenericData.Record(schema)
record.put("doubles", listOf(12.54, 23.5, 9123.2314))
Avro.default.fromRecord(TestListDoubles.serializer(), record) shouldBe
TestListDoubles(listOf(12.54, 23.5, 9123.2314))
}

"support GenericData.Array for a List of doubles" {

val schema = Avro.default.schema(TestListDoubles.serializer())
val record = GenericData.Record(schema)
record.put("doubles",
GenericData.Array(Schema.createArray(Schema.create(Schema.Type.DOUBLE)), listOf(12.54, 23.5, 9123.2314)))
Avro.default.fromRecord(TestListDoubles.serializer(), record) shouldBe
TestListDoubles(listOf(12.54, 23.5, 9123.2314))
}

"support array for a List of records" {

val containerSchema = Avro.default.schema(TestListRecords.serializer())
val recordSchema = Avro.default.schema(Record.serializer())

val record1 = GenericData.Record(recordSchema)
record1.put("str", "qwe")
record1.put("double", 123.4)

val record2 = GenericData.Record(recordSchema)
record2.put("str", "wer")
record2.put("double", 8234.324)

val container = GenericData.Record(containerSchema)
container.put("records", arrayOf(record1, record2))

Avro.default.fromRecord(TestListRecords.serializer(), container) shouldBe
TestListRecords(listOf(Record("qwe", 123.4), Record("wer", 8234.324)))
}

"support list for a List of records" {

val containerSchema = Avro.default.schema(TestListRecords.serializer())
val recordSchema = Avro.default.schema(Record.serializer())

val record1 = GenericData.Record(recordSchema)
record1.put("str", "qwe")
record1.put("double", 123.4)

val record2 = GenericData.Record(recordSchema)
record2.put("str", "wer")
record2.put("double", 8234.324)

val container = GenericData.Record(containerSchema)
container.put("records", listOf(record1, record2))

Avro.default.fromRecord(TestListRecords.serializer(), container) shouldBe
TestListRecords(listOf(Record("qwe", 123.4), Record("wer", 8234.324)))
}

"support array for a Set of records" {

val containerSchema = Avro.default.schema(TestSetRecords.serializer())
val recordSchema = Avro.default.schema(Record.serializer())

val record1 = GenericData.Record(recordSchema)
record1.put("str", "qwe")
record1.put("double", 123.4)

val record2 = GenericData.Record(recordSchema)
record2.put("str", "wer")
record2.put("double", 8234.324)

val container = GenericData.Record(containerSchema)
container.put("records", arrayOf(record1, record2))

Avro.default.fromRecord(TestSetRecords.serializer(), container) shouldBe
TestSetRecords(setOf(Record("qwe", 123.4), Record("wer", 8234.324)))
}

"support GenericData.Array for a Set of records" {

val containerSchema = Avro.default.schema(TestSetRecords.serializer())
val recordSchema = Avro.default.schema(Record.serializer())

val record1 = GenericData.Record(recordSchema)
record1.put("str", "qwe")
record1.put("double", 123.4)

val record2 = GenericData.Record(recordSchema)
record2.put("str", "wer")
record2.put("double", 8234.324)

val container = GenericData.Record(containerSchema)
container.put("records",
GenericData.Array(Schema.createArray(Schema.create(Schema.Type.STRING)), listOf(record1, record2)))

Avro.default.fromRecord(TestSetRecords.serializer(), container) shouldBe
TestSetRecords(setOf(Record("qwe", 123.4), Record("wer", 8234.324)))
}

"support array for a Set of strings" {
val schema = Avro.default.schema(TestSetString.serializer())
val record = GenericData.Record(schema)
record.put("strings", arrayOf("Qwe", "324", "q"))
Avro.default.fromRecord(TestSetString.serializer(), record) shouldBe
TestSetString(setOf("Qwe", "324", "q"))
}

"support list for a Set of strings" {
val schema = Avro.default.schema(TestSetString.serializer())
val record = GenericData.Record(schema)
record.put("strings", arrayOf("Qwe", "324", "q"))
Avro.default.fromRecord(TestSetString.serializer(), record) shouldBe
TestSetString(setOf("Qwe", "324", "q"))
}

"support GenericData.Array for a Set of strings" {
val schema = Avro.default.schema(TestSetString.serializer())
val record = GenericData.Record(schema)
record.put("strings",
GenericData.Array(Schema.createArray(Schema.create(Schema.Type.STRING)), listOf("Qwe", "324", "q")))
Avro.default.fromRecord(TestSetString.serializer(), record) shouldBe
TestSetString(setOf("Qwe", "324", "q"))
"Decoder" should {
listOf(
"array" to arrayOf(true, false, true),
"list" to listOf(true, false, true),
"GenericData.Array" to GenericData.Array(
Schema.createArray(Schema.create(Schema.Type.BOOLEAN)), listOf(true, false, true)
)
).forEach {
"support ${it.first} for an Array of booleans" {
val schema = Avro.default.schema(TestArrayBooleans.serializer())
val record = GenericData.Record(schema)
record.put("booleans", it.second)
Avro.default.fromRecord(TestArrayBooleans.serializer(), record).booleans.toList() shouldBe listOf(
true, false, true
)
}
}
listOf(
"array" to arrayOf(12.54, 23.5, 9123.2314),
"list" to listOf(12.54, 23.5, 9123.2314),
"GenericData.Array" to GenericData.Array(
Schema.createArray(Schema.create(Schema.Type.DOUBLE)), listOf(12.54, 23.5, 9123.2314)
)
).forEach {
"support ${it.first} for a List of doubles" {
val schema = Avro.default.schema(TestListDoubles.serializer())
val record = GenericData.Record(schema)
record.put("doubles", it.second)
Avro.default.fromRecord(TestListDoubles.serializer(), record) shouldBe TestListDoubles(
listOf(
12.54, 23.5, 9123.2314
)
)
}
}
val recordSchema = Avro.default.schema(Record.serializer())
val records = listOf(GenericData.Record(recordSchema).apply {
put("str", "qwe")
put("double", 123.4)
}, GenericData.Record(recordSchema).apply {
put("str", "wer")
put("double", 8234.324)
})
listOf(
"array" to records.toTypedArray(),
"list" to records,
"GenericData.Array" to GenericData.Array(
Schema.createArray(recordSchema), records
)
).forEach {
"support ${it.first} for a List of records" {
val containerSchema = Avro.default.schema(TestListRecords.serializer())
val container = GenericData.Record(containerSchema)
container.put("records", it.second)

Avro.default.fromRecord(
TestListRecords.serializer(), container
) shouldBe TestListRecords(listOf(Record("qwe", 123.4), Record("wer", 8234.324)))
}
"support ${it.first} for a Set of records" {
val containerSchema = Avro.default.schema(TestSetRecords.serializer())
val container = GenericData.Record(containerSchema)
container.put("records", it.second)

Avro.default.fromRecord(
TestSetRecords.serializer(), container
) shouldBe TestSetRecords(setOf(Record("qwe", 123.4), Record("wer", 8234.324)))
}
}

listOf(
"array" to arrayOf("Qwe", "324", "q"),
"list" to listOf("Qwe", "324", "q"),
"GenericData.Array" to GenericData.Array(
Schema.createArray(Schema.create(Schema.Type.STRING)), listOf("Qwe", "324", "q")
)
).forEach {
"support ${it.first} for a Set of strings" {
val schema = Avro.default.schema(TestSetString.serializer())
val record = GenericData.Record(schema)
record.put("strings", it.second)
Avro.default.fromRecord(TestSetString.serializer(), record) shouldBe TestSetString(setOf("Qwe", "324", "q"))
}
}
}
}

})
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package com.github.avrokotlin.avro4k.decoder

import com.github.avrokotlin.avro4k.Avro
import com.github.avrokotlin.avro4k.AvroDefault
import com.github.avrokotlin.avro4k.AvroName
import com.github.avrokotlin.avro4k.ScalePrecision
import com.github.avrokotlin.avro4k.*
import com.github.avrokotlin.avro4k.io.AvroDecodeFormat
import com.github.avrokotlin.avro4k.serializer.BigDecimalSerializer
import io.kotest.core.spec.style.FunSpec
Expand Down Expand Up @@ -51,6 +48,23 @@ data class ContainerWithDefaultFields(
@Serializable(BigDecimalSerializer::class)
val bigDecimal : BigDecimal
)
@Serializable
@AvroEnumDefault("UNKNOWN")
enum class EnumWithDefault {
UNKNOWN, A
}

@Serializable
@AvroEnumDefault("UNKNOWN")
enum class FutureEnumWithDefault {
UNKNOWN, A, C
}

@Serializable
data class Wrap(val value: EnumWithDefault)

@Serializable
data class FutureWrap(val value: FutureEnumWithDefault)

class AvroDefaultValuesDecoderTest : FunSpec({
test("test default values correctly decoded") {
Expand All @@ -73,5 +87,15 @@ class AvroDefaultValuesDecoderTest : FunSpec({
deserialized.emptyFooList.shouldBeEmpty()
deserialized.filledFooList.shouldContainExactly(FooElement("bar"))
deserialized.bigDecimal.shouldBe(BigDecimal.ZERO)

}
test("Decoding enum with an unknown or future value uses default value") {
val encoded = Avro.default.encodeToByteArray(
FutureWrap.serializer(),
FutureWrap(FutureEnumWithDefault.C)
)
val decoded = Avro.default.decodeFromByteArray(Wrap.serializer(), encoded)

decoded shouldBe Wrap(EnumWithDefault.UNKNOWN)
}
})
Loading

0 comments on commit 1723ebc

Please sign in to comment.