kotlinx.serialization icon indicating copy to clipboard operation
kotlinx.serialization copied to clipboard

Cover all combinations of language and serialization features with tests

Open shanshin opened this issue 1 year ago • 3 comments

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.

shanshin avatar Jun 08 '23 11:06 shanshin

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

shanshin avatar Jun 13 '23 17:06 shanshin

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, delegated by, 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.

sandwwraith avatar Jun 14 '23 16:06 sandwwraith

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)

shanshin avatar Jun 14 '23 19:06 shanshin