kotlinx.serialization
kotlinx.serialization copied to clipboard
Cover all combinations of language and serialization features with tests
A matrix should be drawn up between language features (value classes, abstract classes, sealed classes, nested classes, etc.) and serialization features (generated serializer, custom serializer, contextual serializer, etc.) and simple serialization/deserialization tests should be written for all combinations.
All tests should be implemented in serialization Kotlin plugin, if there is such a possibility.
This set of tests involves testing only the modification of user classes declared in the source code of the application.
So the modified code should be compiled, instances of objects should be created without errors, their descriptors should be filled in correctly, as well as the search for serializers in any way should be carried out correctly.
These tests are not intended to fully test the lookup, serialization/deserialization, runtime, modules.
Each class that is run through the test cases is a set of values on each of the dimensions. It is allowed that several non-mutually exclusive elements from the same dimension can be written in each class (if it reduces the number of tests and classes, but does not interfere with the perception).
If some set of measurement elements is not compatible, then the test should check for a compilation error.
Dimensions for testing
Type syntax enum value class final class open class object sealed interface interface abstract class sealed class
plain inherited class open inherited class abstract inherited class sealed inherited class interface inherited class sealed inherited class
... inherited object
Type locations file-level local class nested class inner class
Type parametrization Parametrized Not parametrized
Serializable
Generated serializer (@Serializable
)
Custom serializer on class (@Serializable(CustomSerializer::class)
)
Custom serializer on property (@Serializable(CustomSerializer::class)
)
Serializable by default (without annotations: enum
, interface
, sealed interface
)
Contextual by SerialModule
Serializable by UseSerializers
Properties
plain property
nullable property
typealias in property
parametrized typealias in property
annotated @Contextual
contextual property
implicitly polymorphic (interface)
annotated @Polymorphic
generic property with @Contextual
parameter
generic property with @Serializable(with)
parameter
generic property with unserializable parameter
Property location primary constructor class body
Descriptor accessing from init block from companion init from companion property initializer
Self-reference
in property (Type)
in parametrized property (List<Type>
)
in double parametrized property (List<List<Type>>
)
Use cases
Instantiate manually
Serializer for particular class Type.serializer() serializer<Type>() module.serializer<Type>() serializer(typeOf<Type>()) module.serializer(typeOf<Type>())
Serializer for polymorphic class serializer<ParentType>() module.serializer<ParentType>() serializer(typeOf<ParentType>()) module.serializer(typeOf<ParentType>())
Descriptors check
check elements descriptor (by toString()
)
check elements nullability
Several notes:
- IIRC
sealed interface
requires@Serializable
on it to work as sealed (auto-registering children). Otherwise, it works as a regular interface (requires modules). Both of these cases likely should be tested. - interface inherited class - you mean 'class implementing interface' or what? Interfaces couldn't extend classes last time I've checked)
- For
@Serializable
and@Contextual
on property: they also can be applied directly to types, e.g.val foo: List<@Serializable(Custom::class) X>
- 'Contextual by SerialModule' - not clear what that means.
@Contextual
can be used on property and type, and we also have@file:UseContextualSerialization
- All section 'properties' should probably be renamed to 'property types'. They all should be verified, however, the properties itself can come in different flavors:
public/private
,val/var
,@Transient
,@Required
, delegatedby
, etc. It is important to test this flavors. - 'implicitly contextual property' - do we have those? IIRC there isn't any types for which
@Contextual
applied automatically. - Descriptors/companions also likely can be accessed from class' own
init {}
block:
@Serializable
class X(val f: Foo, val b: Bar) {
init {
println(X.serializer().descriptor)
}
companion object {
init {
println(X.serializer().descriptor)
}
}
}
- 'Property accessing' - what do you mean by that? Plugin shouldn't affect user-declared properties.
IIRC sealed interface requires @Serializable on it to work as sealed (auto-registering children). Otherwise, it works as a regular interface (requires modules). Both of these cases likely should be tested.
Yes, this is covered by points Serializable
: Generated serializer (@Serializable)
and Serializable by default (without annotations: enum, interface, sealed interface)
interface inherited class - you mean 'class implementing interface' or what? Interfaces couldn't extend classes last time I've checked)
Yes, class implementing interface, I used a common word for uniformity. Maybe an ... , extending ...
will do? E.g. class, extending interface
For @Serializable and @Contextual on property: they also can be applied directly to types, e.g. val foo: List<@Serializable(Custom::class) X>
ok
'Contextual by SerialModule' - not clear what that means. @Contextual can be used on property and type, and we also have @file:UseContextualSerialization 'implicitly contextual property' - do we have those? IIRC there isn't any types for which @Contextual applied automatically.
indeed, hurried
Descriptors/companions also likely can be accessed from class' own init {} block:
ok
'Property accessing' - what do you mean by that? Plugin shouldn't affect user-declared properties.
ok, I'll remove this
They all should be verified, however, the properties itself can come in different flavors: public/private, val/var, @Transient, @Required, delegated by, etc. It is important to test this flavors.
-
public/private
I can't imagine how the plugin explicitly affects this -
val/var
the finality of the property does not affect the plugin, especially in these test cases it will be difficult to verify the correctness -
delegated
I think delegated properties should be checked by another type of tests (which will perform serialization and deserialization) -
@Transient, @Required
this only affects the contents of the descriptor. @sandwwraith, should we create tests that completely cover descriptors? (then we need to add annotations, serial names, kinds, optionality, inlining)