Skip to content

Commit 80db5d7

Browse files
committed
fix: Adds test for nullable properties in data classes
Fixes #103
1 parent 21e4fdc commit 80db5d7

File tree

3 files changed

+167
-88
lines changed

3 files changed

+167
-88
lines changed

core/3.0/src/main/scala/org/apache/spark/sql/KotlinReflection.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -755,7 +755,7 @@ object KotlinReflection extends KotlinReflection {
755755
inputObject,
756756
maybeProp.get.getReadMethod.getName,
757757
inferExternalType(propClass),
758-
returnNullable = propDt.nullable
758+
returnNullable = structField.nullable
759759
)
760760
val newPath = walkedTypePath.recordField(propClass.getName, fieldName)
761761
(fieldName, serializerFor(fieldValue, getType(propClass), newPath, seenTypeSet, if (propDt.isInstanceOf[ComplexWrapper]) Some(propDt) else None))

kotlin-spark-api/2.4/src/test/kotlin/org/jetbrains/kotlinx/spark/api/TypeInferenceTest.kt

Lines changed: 81 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,13 @@ package org.jetbrains.kotlinx.spark.api/*-
2020
import ch.tutteli.atrium.api.fluent.en_GB.*
2121
import ch.tutteli.atrium.api.verbs.expect
2222
import ch.tutteli.atrium.creating.Expect
23+
import ch.tutteli.atrium.logic._logic
24+
import ch.tutteli.atrium.logic._logicAppend
25+
import ch.tutteli.atrium.logic.collect
2326
import io.kotest.core.spec.style.ShouldSpec
27+
import io.kotest.matchers.types.shouldBeTypeOf
28+
import org.apache.spark.sql.types.ArrayType
29+
import org.apache.spark.sql.types.IntegerType
2430
import org.jetbrains.kotlinx.spark.api.struct.model.DataType.StructType
2531
import org.jetbrains.kotlinx.spark.api.struct.model.DataType.TypeName
2632
import org.jetbrains.kotlinx.spark.api.struct.model.ElementType.ComplexElement
@@ -29,7 +35,6 @@ import org.jetbrains.kotlinx.spark.api.struct.model.Struct
2935
import org.jetbrains.kotlinx.spark.api.struct.model.StructField
3036
import kotlin.reflect.typeOf
3137

32-
3338
@OptIn(ExperimentalStdlibApi::class)
3439
class TypeInferenceTest : ShouldSpec({
3540
context("org.jetbrains.spark.api.org.jetbrains.spark.api.schema") {
@@ -39,21 +44,21 @@ class TypeInferenceTest : ShouldSpec({
3944
val struct = Struct.fromJson(schema(typeOf<Pair<String, Test<Int>>>()).prettyJson())!!
4045
should("contain correct typings") {
4146
expect(struct.fields).notToBeNull().contains.inAnyOrder.only.entries(
42-
hasField("first", "string"),
43-
hasStruct("second",
44-
hasField("vala", "integer"),
45-
hasStruct("tripl1",
46-
hasField("first", "integer"),
47-
hasStruct("second",
48-
hasField("vala2", "long"),
49-
hasStruct("para2",
50-
hasField("first", "long"),
51-
hasField("second", "string")
47+
hasField("first", "string"),
48+
hasStruct("second",
49+
hasField("vala", "integer"),
50+
hasStruct("tripl1",
51+
hasField("first", "integer"),
52+
hasStruct("second",
53+
hasField("vala2", "long"),
54+
hasStruct("para2",
55+
hasField("first", "long"),
56+
hasField("second", "string")
57+
)
58+
),
59+
hasField("third", "integer")
5260
)
53-
),
54-
hasField("third", "integer")
5561
)
56-
)
5762
)
5863
}
5964
}
@@ -65,23 +70,23 @@ class TypeInferenceTest : ShouldSpec({
6570
val struct = Struct.fromJson(schema(typeOf<Pair<String, Test<Int>>>()).prettyJson())!!
6671
should("contain correct typings") {
6772
expect(struct.fields).notToBeNull().contains.inAnyOrder.only.entries(
68-
hasField("first", "string"),
69-
hasStruct("second",
70-
hasField("vala", "integer"),
71-
hasStruct("tripl1",
72-
hasField("first", "integer"),
73-
hasStruct("second",
74-
hasField("vala2", "long"),
75-
hasStruct("para2",
76-
hasField("first", "long"),
77-
hasStruct("second",
78-
hasField("vala3", "double")
79-
)
73+
hasField("first", "string"),
74+
hasStruct("second",
75+
hasField("vala", "integer"),
76+
hasStruct("tripl1",
77+
hasField("first", "integer"),
78+
hasStruct("second",
79+
hasField("vala2", "long"),
80+
hasStruct("para2",
81+
hasField("first", "long"),
82+
hasStruct("second",
83+
hasField("vala3", "double")
84+
)
85+
)
86+
),
87+
hasField("third", "integer")
8088
)
81-
),
82-
hasField("third", "integer")
8389
)
84-
)
8590
)
8691
}
8792
}
@@ -91,9 +96,9 @@ class TypeInferenceTest : ShouldSpec({
9196
val struct = Struct.fromJson(schema(typeOf<Test>()).prettyJson())!!
9297
should("return correct types too") {
9398
expect(struct.fields).notToBeNull().contains.inAnyOrder.only.entries(
94-
hasField("a", "string"),
95-
hasField("b", "integer"),
96-
hasField("c", "double")
99+
hasField("a", "string"),
100+
hasField("b", "integer"),
101+
hasField("c", "double")
97102
)
98103
}
99104
}
@@ -113,8 +118,8 @@ class TypeInferenceTest : ShouldSpec({
113118
isOfType("array")
114119
feature { f(it::elementType) }.notToBeNull().isA<ComplexElement> {
115120
feature { f(it.value::fields) }.notToBeNull().contains.inAnyOrder.only.entries(
116-
hasField("first", "integer"),
117-
hasField("second", "long")
121+
hasField("first", "integer"),
122+
hasField("second", "long")
118123
)
119124
}
120125
}
@@ -129,7 +134,7 @@ class TypeInferenceTest : ShouldSpec({
129134
isOfType("array")
130135
feature { f(it::elementType) }.notToBeNull().isA<ComplexElement> {
131136
feature { f(it.value::fields) }.notToBeNull().contains.inAnyOrder.only.entries(
132-
hasField("e", "string")
137+
hasField("e", "string")
133138
)
134139
}
135140
}
@@ -166,15 +171,44 @@ class TypeInferenceTest : ShouldSpec({
166171
}
167172
}
168173
}
169-
context("data class with props in order lon → lat") {
170-
data class Test(val lon: Double, val lat: Double)
174+
context("data class with nullable list inside") {
175+
data class Sample(val optionList: List<Int>?)
171176

172-
val struct = Struct.fromJson(schema(typeOf<Test>()).prettyJson())!!
173-
should("Not change order of fields") {
174-
expect(struct.fields).notToBeNull().containsExactly(
175-
hasField("lon", "double"),
176-
hasField("lat", "double")
177-
)
177+
val struct = Struct.fromJson(schema(typeOf<Sample>()).prettyJson())!!
178+
should("show that list is nullable and element is not") {
179+
expect(struct)
180+
.feature("some", { fields }) {
181+
notToBeNull().contains.inOrder.only.entry {
182+
this
183+
.feature("field name", { name }) { toBe("optionList") }
184+
.feature("optionList is nullable", { nullable }) { toBe(true) }
185+
.feature("optionList", { type }) {
186+
this
187+
.isA<StructType>()
188+
.feature("element type of optionList", { value.elementType }) { toBe(SimpleElement("integer")) }
189+
.feature("optionList contains null", { value.containsNull }) { toBe(false) }
190+
.feature("optionList type", { value }) { isOfType("array") }
191+
}
192+
}
193+
}
194+
}
195+
should("generate valid serializer schema") {
196+
expect(encoder<Sample>().schema()) {
197+
this
198+
.feature("data type", { this.fields()?.toList() }) {
199+
this.notToBeNull().contains.inOrder.only.entry {
200+
this
201+
.feature("element name", { name() }) { toBe("optionList") }
202+
.feature("field type", { dataType() }) {
203+
this
204+
.isA<ArrayType>()
205+
.feature("element type", { elementType() }) { isA<IntegerType>() }
206+
.feature("element nullable", { containsNull() }) { toBe(false) }
207+
}
208+
.feature("optionList nullable", { nullable() }) { toBe(true) }
209+
}
210+
}
211+
}
178212
}
179213
}
180214
})
@@ -184,15 +218,15 @@ private fun Expect<Struct>.isOfType(type: String) {
184218
}
185219

186220
private fun hasStruct(
187-
name: String,
188-
expectedField: Expect<StructField>.() -> Unit,
189-
vararg expectedFields: Expect<StructField>.() -> Unit,
221+
name: String,
222+
expectedField: Expect<StructField>.() -> Unit,
223+
vararg expectedFields: Expect<StructField>.() -> Unit,
190224
): Expect<StructField>.() -> Unit {
191225
return {
192226
feature { f(it::name) }.toBe(name)
193227
feature { f(it::type) }.isA<StructType> {
194228
feature { f(it.value::fields) }.notToBeNull().contains.inAnyOrder.only.entries(expectedField,
195-
*expectedFields)
229+
*expectedFields)
196230
}
197231
}
198232
}

0 commit comments

Comments
 (0)