Merge pull request from corda/kat/feature/testingDisableEvolver

Disable Evolver / Generics fingerprinting bugfix
This commit is contained in:
Katelyn Baker 2018-01-10 17:26:38 +00:00 committed by GitHub
commit d846011cc4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 502 additions and 109 deletions

1
.gitignore vendored
View File

@ -88,6 +88,7 @@ crashlytics-build.properties
# docs related
docs/virtualenv/
virtualenv/
# bft-smart
**/config/currentView

View File

@ -54,7 +54,6 @@ class DeserializationInput(internal val serializerFactory: SerializerFactory) {
inline fun <reified T : Any> deserialize(bytes: SerializedBytes<T>): T =
deserialize(bytes, T::class.java)
@Throws(NotSerializableException::class)
inline internal fun <reified T : Any> deserializeAndReturnEnvelope(bytes: SerializedBytes<T>): ObjectAndEnvelope<T> =
deserializeAndReturnEnvelope(bytes, T::class.java)

View File

@ -106,7 +106,7 @@ class EnumEvolutionSerializer(
// to the name as it exists. We want to test any new constants have been added to the end
// of the enum class
val serialisedOrds = ((schemas.schema.types.find { it.name == old.name } as RestrictedType).choices
.associateBy ({ it.value.toInt() }, { conversions[it.name] }))
.associateBy({ it.value.toInt() }, { conversions[it.name] }))
if (ordinals.filterNot { serialisedOrds[it.value] == it.key }.isNotEmpty()) {
throw NotSerializableException("Constants have been reordered, additions must be appended to the end")

View File

@ -130,3 +130,34 @@ class EvolutionSerializer(
}
}
/**
* Instances of this type are injected into a [SerializerFactory] at creation time to dictate the
* behaviour of evolution within that factory. Under normal circumstances this will simply
* be an object that returns an [EvolutionSerializer]. Of course, any implementation that
* extends this class can be written to invoke whatever behaviour is desired.
*/
abstract class EvolutionSerializerGetterBase {
abstract fun getEvolutionSerializer(
factory: SerializerFactory,
typeNotation: TypeNotation,
newSerializer: AMQPSerializer<Any>,
schemas: SerializationSchemas): AMQPSerializer<Any>
}
/**
* The normal use case for generating an [EvolutionSerializer]'s based on the differences
* between the received schema and the class as it exists now on the class path,
*/
class EvolutionSerializerGetter : EvolutionSerializerGetterBase() {
override fun getEvolutionSerializer(factory: SerializerFactory,
typeNotation: TypeNotation,
newSerializer: AMQPSerializer<Any>,
schemas: SerializationSchemas): AMQPSerializer<Any> =
factory.getSerializersByDescriptor().computeIfAbsent(typeNotation.descriptor.name!!) {
when (typeNotation) {
is CompositeType -> EvolutionSerializer.make(typeNotation, newSerializer as ObjectSerializer, factory)
is RestrictedType -> EnumEvolutionSerializer.make(typeNotation, newSerializer, factory, schemas)
}
}
}

View File

@ -346,19 +346,54 @@ private fun Hasher.fingerprintWithCustomSerializerOrElse(factory: SerializerFact
}
}
// This method concatentates various elements of the types recursively as unencoded strings into the hasher, effectively
// This method concatenates various elements of the types recursively as unencoded strings into the hasher, effectively
// creating a unique string for a type which we then hash in the calling function above.
private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: MutableSet<Type>, hasher: Hasher, factory: SerializerFactory): Hasher {
return if (type in alreadySeen) {
private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: MutableSet<Type>,
hasher: Hasher, factory: SerializerFactory, offset: Int = 4): Hasher {
// We don't include Example<?> and Example<T> where type is ? or T in this otherwise we
// generate different fingerprints for class Outer<T>(val a: Inner<T>) when serialising
// and deserializing (assuming deserialization is occurring in a factory that didn't
// serialise the object in the first place (and thus the cache lookup fails). This is also
// true of Any, where we need Example<A, B> and Example<?, ?> to have the same fingerprint
return if (type in alreadySeen && (type !is SerializerFactory.AnyType) && (type !is TypeVariable<*>)) {
hasher.putUnencodedChars(ALREADY_SEEN_HASH)
} else {
alreadySeen += type
try {
when (type) {
is SerializerFactory.AnyType -> hasher.putUnencodedChars(ANY_TYPE_HASH)
is ParameterizedType -> {
// Hash the rawType + params
val clazz = type.rawType as Class<*>
val startingHash = if (isCollectionOrMap(clazz)) {
hasher.putUnencodedChars(clazz.name)
} else {
hasher.fingerprintWithCustomSerializerOrElse(factory, clazz, type) {
fingerprintForObject(type, type, alreadySeen, hasher, factory, offset+4)
}
}
// ... and concatenate the type data for each parameter type.
type.actualTypeArguments.fold(startingHash) { orig, paramType ->
fingerprintForType(paramType, type, alreadySeen, orig, factory, offset+4)
}
}
// Treat generic types as "any type" to prevent fingerprint mismatch. This case we fall into when
// looking at A and B from Example<A, B> (remember we call this function recursively). When
// serialising a concrete example of the type we have A and B which are TypeVariables<*>'s but
// when deserializing we only have the wildcard placeholder ?, or AnyType
//
// Note, TypeVariable<*> used to be encoded as TYPE_VARIABLE_HASH but that again produces a
// differing fingerprint on serialisation and deserialization
is SerializerFactory.AnyType,
is TypeVariable<*> -> {
hasher.putUnencodedChars("?").putUnencodedChars(ANY_TYPE_HASH)
}
is Class<*> -> {
if (type.isArray) {
fingerprintForType(type.componentType, contextType, alreadySeen, hasher, factory).putUnencodedChars(ARRAY_HASH)
fingerprintForType(type.componentType, contextType, alreadySeen, hasher, factory, offset+4)
.putUnencodedChars(ARRAY_HASH)
} else if (SerializerFactory.isPrimitive(type)) {
hasher.putUnencodedChars(type.name)
} else if (isCollectionOrMap(type)) {
@ -377,32 +412,18 @@ private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: Muta
// to the CorDapp but maybe reference to the JAR in the short term.
hasher.putUnencodedChars(type.name)
} else {
fingerprintForObject(type, type, alreadySeen, hasher, factory)
fingerprintForObject(type, type, alreadySeen, hasher, factory, offset+4)
}
}
}
}
is ParameterizedType -> {
// Hash the rawType + params
val clazz = type.rawType as Class<*>
val startingHash = if (isCollectionOrMap(clazz)) {
hasher.putUnencodedChars(clazz.name)
} else {
hasher.fingerprintWithCustomSerializerOrElse(factory, clazz, type) {
fingerprintForObject(type, type, alreadySeen, hasher, factory)
}
}
// ... and concatentate the type data for each parameter type.
type.actualTypeArguments.fold(startingHash) { orig, paramType ->
fingerprintForType(paramType, type, alreadySeen, orig, factory)
}
}
// Hash the element type + some array hash
is GenericArrayType -> fingerprintForType(type.genericComponentType, contextType, alreadySeen,
hasher, factory).putUnencodedChars(ARRAY_HASH)
hasher, factory, offset+4).putUnencodedChars(ARRAY_HASH)
// TODO: include bounds
is TypeVariable<*> -> hasher.putUnencodedChars(type.name).putUnencodedChars(TYPE_VARIABLE_HASH)
is WildcardType -> hasher.putUnencodedChars(type.typeName).putUnencodedChars(WILDCARD_TYPE_HASH)
is WildcardType -> {
hasher.putUnencodedChars(type.typeName).putUnencodedChars(WILDCARD_TYPE_HASH)
}
else -> throw NotSerializableException("Don't know how to hash")
}
} catch (e: NotSerializableException) {
@ -416,15 +437,21 @@ private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: Muta
private fun isCollectionOrMap(type: Class<*>) = (Collection::class.java.isAssignableFrom(type) || Map::class.java.isAssignableFrom(type)) &&
!EnumSet::class.java.isAssignableFrom(type)
private fun fingerprintForObject(type: Type, contextType: Type?, alreadySeen: MutableSet<Type>, hasher: Hasher, factory: SerializerFactory): Hasher {
private fun fingerprintForObject(
type: Type,
contextType: Type?,
alreadySeen: MutableSet<Type>,
hasher: Hasher,
factory: SerializerFactory,
offset: Int = 0): Hasher {
// Hash the class + properties + interfaces
val name = type.asClass()?.name ?: throw NotSerializableException("Expected only Class or ParameterizedType but found $type")
propertiesForSerialization(constructorForDeserialization(type), contextType ?: type, factory).getters
.fold(hasher.putUnencodedChars(name)) { orig, prop ->
fingerprintForType(prop.resolvedType, type, alreadySeen, orig, factory)
fingerprintForType(prop.resolvedType, type, alreadySeen, orig, factory, offset+4)
.putUnencodedChars(prop.name)
.putUnencodedChars(if (prop.mandatory) NOT_NULLABLE_HASH else NULLABLE_HASH)
}
interfacesForSerialization(type, factory).map { fingerprintForType(it, type, alreadySeen, hasher, factory) }
interfacesForSerialization(type, factory).map { fingerprintForType(it, type, alreadySeen, hasher, factory, offset+4) }
return hasher
}

View File

@ -20,6 +20,10 @@ data class FactorySchemaAndDescriptor(val schemas: SerializationSchemas, val typ
/**
* Factory of serializers designed to be shared across threads and invocations.
*
* @property evolutionSerializerGetter controls how evolution serializers are generated by the factory. The normal
* use case is an [EvolutionSerializer] type is returned. However, in some scenarios, primarily testing, this
* can be altered to fit the requirements of the test.
*/
// TODO: support for intern-ing of deserialized objects for some core types (e.g. PublicKey) for memory efficiency
// TODO: maybe support for caching of serialized form of some core types for performance
@ -33,28 +37,27 @@ data class FactorySchemaAndDescriptor(val schemas: SerializationSchemas, val typ
// TODO: need to rethink matching of constructor to properties in relation to implementing interfaces and needing those properties etc.
// TODO: need to support super classes as well as interfaces with our current code base... what's involved? If we continue to ban, what is the impact?
@ThreadSafe
open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
open class SerializerFactory(
val whitelist: ClassWhitelist,
cl: ClassLoader,
private val evolutionSerializerGetter: EvolutionSerializerGetterBase = EvolutionSerializerGetter()) {
private val serializersByType = ConcurrentHashMap<Type, AMQPSerializer<Any>>()
private val serializersByDescriptor = ConcurrentHashMap<Any, AMQPSerializer<Any>>()
private val customSerializers = CopyOnWriteArrayList<SerializerFor>()
val transformsCache = ConcurrentHashMap<String, EnumMap<TransformTypes, MutableList<Transform>>>()
private val transformsCache = ConcurrentHashMap<String, EnumMap<TransformTypes, MutableList<Transform>>>()
open val classCarpenter = ClassCarpenter(cl, whitelist)
val classloader: ClassLoader
get() = classCarpenter.classloader
private fun getEvolutionSerializer(
typeNotation: TypeNotation,
newSerializer: AMQPSerializer<Any>,
schemas: SerializationSchemas): AMQPSerializer<Any> {
return serializersByDescriptor.computeIfAbsent(typeNotation.descriptor.name!!) {
when (typeNotation) {
is CompositeType -> EvolutionSerializer.make(typeNotation, newSerializer as ObjectSerializer, this)
is RestrictedType -> EnumEvolutionSerializer.make(typeNotation, newSerializer, this, schemas)
}
}
}
private fun getEvolutionSerializer(typeNotation: TypeNotation, newSerializer: AMQPSerializer<Any>,
schemas: SerializationSchemas)
= evolutionSerializerGetter.getEvolutionSerializer(this, typeNotation, newSerializer, schemas)
fun getSerializersByDescriptor() = serializersByDescriptor
fun getTransformsCache() = transformsCache
/**
* Look up, and manufacture if necessary, a serializer for the given type.
@ -93,7 +96,9 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
whitelist.requireWhitelisted(actualType)
EnumSerializer(actualType, actualClass ?: declaredClass, this)
}
else -> makeClassSerializer(actualClass ?: declaredClass, actualType, declaredType)
else -> {
makeClassSerializer(actualClass ?: declaredClass, actualType, declaredType)
}
}
serializersByDescriptor.putIfAbsent(serializer.typeDescriptor, serializer)
@ -102,12 +107,12 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
}
/**
* Try and infer concrete types for any generics type variables for the actual class encountered, based on the declared
* type.
* Try and infer concrete types for any generics type variables for the actual class encountered,
* based on the declared type.
*/
// TODO: test GenericArrayType
private fun inferTypeVariables(actualClass: Class<*>?, declaredClass: Class<*>, declaredType: Type): Type? =
when (declaredType) {
private fun inferTypeVariables(actualClass: Class<*>?, declaredClass: Class<*>,
declaredType: Type) : Type? = when (declaredType) {
is ParameterizedType -> inferTypeVariables(actualClass, declaredClass, declaredType)
// Nothing to infer, otherwise we'd have ParameterizedType
is Class<*> -> actualClass
@ -214,9 +219,9 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
try {
val serialiser = processSchemaEntry(typeNotation)
// if we just successfully built a serialiser for the type but the type fingerprint
// if we just successfully built a serializer for the type but the type fingerprint
// doesn't match that of the serialised object then we are dealing with different
// instance of the class, as such we need to build an EvolutionSerialiser
// instance of the class, as such we need to build an EvolutionSerializer
if (serialiser.typeDescriptor != typeNotation.descriptor.name) {
getEvolutionSerializer(typeNotation, serialiser, schemaAndDescriptor.schemas)
}
@ -337,7 +342,9 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
"${nameForType(type.componentType)}${if (type.componentType.isPrimitive) "[p]" else "[]"}"
} else type.name
}
is ParameterizedType -> "${nameForType(type.rawType)}<${type.actualTypeArguments.joinToString { nameForType(it) }}>"
is ParameterizedType -> {
"${nameForType(type.rawType)}<${type.actualTypeArguments.joinToString { nameForType(it) }}>"
}
is GenericArrayType -> "${nameForType(type.genericComponentType)}[]"
is WildcardType -> "?"
is TypeVariable<*> -> "?"

View File

@ -200,7 +200,7 @@ data class TransformsSchema(val types: Map<String, EnumMap<TransformTypes, Mutab
* @param sf the [SerializerFactory] building this transform set. Needed as each can define it's own
* class loader and this dictates which classes we can and cannot see
*/
fun get(name: String, sf: SerializerFactory) = sf.transformsCache.computeIfAbsent(name) {
fun get(name: String, sf: SerializerFactory) = sf.getTransformsCache().computeIfAbsent(name) {
val transforms = EnumMap<TransformTypes, MutableList<Transform>>(TransformTypes::class.java)
try {
val clazz = sf.classloader.loadClass(name)

View File

@ -31,7 +31,12 @@ public class ErrorMessageTests {
@Test
public void testJavaConstructorAnnotations() {
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
SerializerFactory factory1 = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter);
SerializationOutput ser = new SerializationOutput(factory1);
Assertions.assertThatThrownBy(() -> ser.serialize(new C(1)))

View File

@ -0,0 +1,93 @@
package net.corda.nodeapi.internal.serialization.amqp;
import net.corda.core.serialization.SerializedBytes;
import net.corda.nodeapi.internal.serialization.AllWhitelist;
import org.junit.Test;
import java.io.NotSerializableException;
import static org.jgroups.util.Util.assertEquals;
public class JavaGenericsTest {
private static class Inner {
private final Integer v;
private Inner(Integer v) { this.v = v; }
public Integer getV() { return v; }
}
private static class A<T> {
private final T t;
private A(T t) { this.t = t; }
public T getT() { return t; }
}
@Test
public void basicGeneric() throws NotSerializableException {
A a1 = new A(1);
SerializerFactory factory = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter());
SerializationOutput ser = new SerializationOutput(factory);
SerializedBytes<?> bytes = ser.serialize(a1);
DeserializationInput des = new DeserializationInput(factory);
A a2 = des.deserialize(bytes, A.class);
assertEquals(1, a2.getT());
}
private SerializedBytes<?> forceWildcardSerialize(A<?> a) throws NotSerializableException {
SerializerFactory factory = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter());
return (new SerializationOutput(factory)).serialize(a);
}
private SerializedBytes<?> forceWildcardSerializeFactory(
A<?> a,
SerializerFactory factory) throws NotSerializableException {
return (new SerializationOutput(factory)).serialize(a);
}
private A<?> forceWildcardDeserialize(SerializedBytes<?> bytes) throws NotSerializableException {
SerializerFactory factory = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter());
DeserializationInput des = new DeserializationInput(factory);
return des.deserialize(bytes, A.class);
}
private A<?> forceWildcardDeserializeFactory(
SerializedBytes<?> bytes,
SerializerFactory factory) throws NotSerializableException {
return (new DeserializationInput(factory)).deserialize(bytes, A.class);
}
@Test
public void forceWildcard() throws NotSerializableException {
SerializedBytes<?> bytes = forceWildcardSerialize(new A(new Inner(29)));
Inner i = (Inner)forceWildcardDeserialize(bytes).getT();
assertEquals(29, i.getV());
}
@Test
public void forceWildcardSharedFactory() throws NotSerializableException {
SerializerFactory factory = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter());
SerializedBytes<?> bytes = forceWildcardSerializeFactory(new A(new Inner(29)), factory);
Inner i = (Inner)forceWildcardDeserializeFactory(bytes, factory).getT();
assertEquals(29, i.getV());
}
}

View File

@ -25,7 +25,9 @@ public class JavaPrivatePropertyTests {
@Test
public void singlePrivateWithConstructor() throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
EvolutionSerializerGetterBase evolutionSerializerGetter = new EvolutionSerializerGetter();
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
evolutionSerializerGetter);
SerializationOutput ser = new SerializationOutput(factory);
DeserializationInput des = new DeserializationInput(factory);
@ -53,7 +55,9 @@ public class JavaPrivatePropertyTests {
@Test
public void singlePrivateWithConstructorAndGetter()
throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
EvolutionSerializerGetterBase evolutionSerializerGetter = new EvolutionSerializerGetter();
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(), evolutionSerializerGetter);
SerializationOutput ser = new SerializationOutput(factory);
DeserializationInput des = new DeserializationInput(factory);

View File

@ -29,7 +29,9 @@ public class JavaSerialiseEnumTests {
public void testJavaConstructorAnnotations() throws NotSerializableException {
Bra bra = new Bra(Bras.UNDERWIRE);
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter);
SerializationOutput ser = new SerializationOutput(factory1);
SerializedBytes<Object> bytes = ser.serialize(bra);
}

View File

@ -172,8 +172,11 @@ public class JavaSerializationOutputTests {
}
private Object serdes(Object obj) throws NotSerializableException {
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
SerializerFactory factory2 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter);
SerializerFactory factory2 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter);
SerializationOutput ser = new SerializationOutput(factory1);
SerializedBytes<Object> bytes = ser.serialize(obj);

View File

@ -125,7 +125,9 @@ public class ListsSerializationJavaTest {
// Have to have own version as Kotlin inline functions cannot be easily called from Java
private static <T> void assertEqualAfterRoundTripSerialization(T container, Class<T> clazz) throws Exception {
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter);
SerializationOutput ser = new SerializationOutput(factory1);
SerializedBytes<Object> bytes = ser.serialize(container);
DeserializationInput des = new DeserializationInput(factory1);

View File

@ -109,7 +109,12 @@ public class SetterConstructorTests {
// despite having no constructor we should still be able to serialise an instance of C
@Test
public void serialiseC() throws NotSerializableException {
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
SerializerFactory factory1 = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter);
SerializationOutput ser = new SerializationOutput(factory1);
C c1 = new C();
@ -178,7 +183,11 @@ public class SetterConstructorTests {
@Test
public void deserialiseC() throws NotSerializableException {
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
SerializerFactory factory1 = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter);
C cPre1 = new C();
@ -241,7 +250,11 @@ public class SetterConstructorTests {
@Test
public void serialiseOuterAndInner() throws NotSerializableException {
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
SerializerFactory factory1 = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter);
Inner1 i1 = new Inner1("Hello");
Inner2 i2 = new Inner2();
@ -263,7 +276,11 @@ public class SetterConstructorTests {
@Test
public void typeMistmatch() throws NotSerializableException {
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
SerializerFactory factory1 = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter);
TypeMismatch tm = new TypeMismatch();
tm.setA(10);
@ -279,7 +296,11 @@ public class SetterConstructorTests {
@Test
public void typeMistmatch2() throws NotSerializableException {
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
SerializerFactory factory1 = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter);
TypeMismatch2 tm = new TypeMismatch2();
tm.setA("10");

View File

@ -5,6 +5,8 @@ import net.corda.nodeapi.internal.serialization.AllWhitelist
import net.corda.nodeapi.internal.serialization.EmptyWhitelist
fun testDefaultFactory() = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
fun testDefaultFactoryNoEvolution() = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader(),
EvolutionSerializerGetterTesting())
fun testDefaultFactoryWithWhitelist() = SerializerFactory(EmptyWhitelist, ClassLoader.getSystemClassLoader())
class TestSerializationOutput(

View File

@ -11,13 +11,14 @@ class DeserializeAndReturnEnvelopeTests {
@Suppress("NOTHING_TO_INLINE")
inline private fun classTestName(clazz: String) = "${this.javaClass.name}\$${testName()}\$$clazz"
val factory = testDefaultFactoryNoEvolution()
@Test
fun oneType() {
data class A(val a: Int, val b: String)
val a = A(10, "20")
val factory = testDefaultFactory()
fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
@ -33,7 +34,6 @@ class DeserializeAndReturnEnvelopeTests {
val b = B(A(10, "20"), 30.0F)
val factory = testDefaultFactory()
fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b))

View File

@ -12,7 +12,7 @@ class DeserializeMapTests {
private const val VERBOSE = false
}
private val sf = testDefaultFactory()
private val sf = testDefaultFactoryNoEvolution()
@Test
fun mapTest() {

View File

@ -18,7 +18,7 @@ class DeserializeNeedingCarpentryOfEnumsTest : AmqpCarpenterBase(AllWhitelist) {
//
// Setup the test
//
val setupFactory = testDefaultFactory()
val setupFactory = testDefaultFactoryNoEvolution()
val enumConstants = listOf("AAA", "BBB", "CCC", "DDD", "EEE", "FFF",
"GGG", "HHH", "III", "JJJ").associateBy({ it }, { EnumField() })
@ -57,7 +57,7 @@ class DeserializeNeedingCarpentryOfEnumsTest : AmqpCarpenterBase(AllWhitelist) {
//
// Setup the test
//
val setupFactory = testDefaultFactory()
val setupFactory = testDefaultFactoryNoEvolution()
val enumConstants = listOf("AAA", "BBB", "CCC", "DDD", "EEE", "FFF",
"GGG", "HHH", "III", "JJJ").associateBy({ it }, { EnumField() })

View File

@ -17,8 +17,8 @@ class DeserializeNeedingCarpentrySimpleTypesTest : AmqpCarpenterBase(AllWhitelis
private const val VERBOSE = false
}
private val sf = testDefaultFactory()
private val sf2 = testDefaultFactory()
private val sf = testDefaultFactoryNoEvolution()
private val sf2 = testDefaultFactoryNoEvolution()
@Test
fun singleInt() {

View File

@ -27,7 +27,7 @@ class DeserializeNeedingCarpentryTests : AmqpCarpenterBase(AllWhitelist) {
private const val VERBOSE = false
}
private val sf1 = testDefaultFactory()
private val sf1 = testDefaultFactoryNoEvolution()
// Deserialize with whitelisting on to check that `CordaSerializable` annotation present.
private val sf2 = testDefaultFactoryWithWhitelist()

View File

@ -16,8 +16,8 @@ class DeserializeSimpleTypesTests {
private const val VERBOSE = false
}
val sf1 = testDefaultFactory()
val sf2 = testDefaultFactory()
val sf1 = testDefaultFactoryNoEvolution()
val sf2 = testDefaultFactoryNoEvolution()
@Test
fun testChar() {

View File

@ -6,6 +6,8 @@ import org.assertj.core.api.Assertions
import org.junit.Test
import java.io.File
import java.io.NotSerializableException
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import kotlin.test.assertEquals
import kotlin.test.assertTrue
@ -387,21 +389,25 @@ class EnumEvolvabilityTests {
data class C1(val annotatedEnum: AnnotatedEnumOnce)
val sf = testDefaultFactory()
val f = sf.javaClass.getDeclaredField("transformsCache")
f.isAccessible = true
assertEquals(0, sf.transformsCache.size)
val transformsCache = f.get(sf) as ConcurrentHashMap<String, EnumMap<TransformTypes, MutableList<Transform>>>
assertEquals(0, transformsCache.size)
val sb1 = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C1(AnnotatedEnumOnce.D))
assertEquals(2, sf.transformsCache.size)
assertTrue(sf.transformsCache.containsKey(C1::class.java.name))
assertTrue(sf.transformsCache.containsKey(AnnotatedEnumOnce::class.java.name))
assertEquals(2, transformsCache.size)
assertTrue(transformsCache.containsKey(C1::class.java.name))
assertTrue(transformsCache.containsKey(AnnotatedEnumOnce::class.java.name))
val sb2 = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C2(AnnotatedEnumOnce.D))
assertEquals(3, sf.transformsCache.size)
assertTrue(sf.transformsCache.containsKey(C1::class.java.name))
assertTrue(sf.transformsCache.containsKey(C2::class.java.name))
assertTrue(sf.transformsCache.containsKey(AnnotatedEnumOnce::class.java.name))
assertEquals(3, transformsCache.size)
assertTrue(transformsCache.containsKey(C1::class.java.name))
assertTrue(transformsCache.containsKey(C2::class.java.name))
assertTrue(transformsCache.containsKey(AnnotatedEnumOnce::class.java.name))
assertEquals(sb1.transformsSchema.types[AnnotatedEnumOnce::class.java.name],
sb2.transformsSchema.types[AnnotatedEnumOnce::class.java.name])

View File

@ -65,7 +65,7 @@ class EnumTests {
@Suppress("NOTHING_TO_INLINE")
inline private fun classTestName(clazz: String) = "${this.javaClass.name}\$${testName()}\$$clazz"
private val sf1 = testDefaultFactory()
private val sf1 = testDefaultFactoryNoEvolution()
@Test
fun serialiseSimpleTest() {

View File

@ -0,0 +1,22 @@
package net.corda.nodeapi.internal.serialization.amqp
import java.io.NotSerializableException
/**
* An implementation of [EvolutionSerializerGetterBase] that disables all evolution within a
* [SerializerFactory]. This is most useful in testing where it is known that evolution should not be
* occurring and where bugs may be hidden by transparent invocation of an [EvolutionSerializer]. This
* prevents that by simply throwing an exception whenever such a serializer is requested.
*/
class EvolutionSerializerGetterTesting : EvolutionSerializerGetterBase() {
override fun getEvolutionSerializer(factory: SerializerFactory,
typeNotation: TypeNotation,
newSerializer: AMQPSerializer<Any>,
schemas: SerializationSchemas): AMQPSerializer<Any> {
throw NotSerializableException("No evolution should be occurring\n" +
" ${typeNotation.name}\n" +
" ${typeNotation.descriptor.name}\n" +
" ${newSerializer.type.typeName}\n" +
" ${newSerializer.typeDescriptor}\n\n${schemas.schema}")
}
}

View File

@ -2,26 +2,90 @@ package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.serialization.SerializedBytes
import net.corda.nodeapi.internal.serialization.AllWhitelist
import net.corda.testing.common.internal.ProjectStructure.projectRootDir
import org.junit.Test
import java.io.File
import java.net.URI
import java.util.concurrent.ConcurrentHashMap
import kotlin.test.assertEquals
class GenericsTests {
companion object {
val VERBOSE = false
@Suppress("UNUSED")
var localPath = projectRootDir.toUri().resolve(
"node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp")
}
private fun printSeparator() = if (VERBOSE) println("\n\n-------------------------------------------\n\n") else Unit
private fun <T : Any> BytesAndSchemas<T>.printSchema() = if (VERBOSE) println("${this.schema}\n") else Unit
private fun ConcurrentHashMap<Any, AMQPSerializer<Any>>.printKeyToType() {
if (!VERBOSE) return
forEach {
println("Key = ${it.key} - ${it.value.type.typeName}")
}
println()
}
@Test
fun twoDifferentTypesSameParameterizedOuter() {
data class G<A>(val a: A)
val factory = testDefaultFactoryNoEvolution()
val bytes1 = SerializationOutput(factory).serializeAndReturnSchema(G("hi")).apply { printSchema() }
factory.getSerializersByDescriptor().printKeyToType()
val bytes2 = SerializationOutput(factory).serializeAndReturnSchema(G(121)).apply { printSchema() }
factory.getSerializersByDescriptor().printKeyToType()
listOf(factory, testDefaultFactory()).forEach { f ->
DeserializationInput(f).deserialize(bytes1.obj).apply { assertEquals("hi", this.a) }
DeserializationInput(f).deserialize(bytes2.obj).apply { assertEquals(121, this.a) }
}
}
@Test
fun doWeIgnoreMultipleParams() {
data class G1<out T>(val a: T)
data class G2<out T>(val a: T)
data class Wrapper<out T>(val a: Int, val b: G1<T>, val c: G2<T>)
val factory = testDefaultFactoryNoEvolution()
val factory2 = testDefaultFactoryNoEvolution()
val bytes = SerializationOutput(factory).serializeAndReturnSchema(Wrapper(1, G1("hi"), G2("poop"))).apply { printSchema() }
printSeparator()
DeserializationInput(factory2).deserialize(bytes.obj)
}
@Test
fun nestedSerializationOfGenerics() {
data class G<T>(val a: T)
data class Wrapper<T>(val a: Int, val b: G<T>)
data class G<out T>(val a: T)
data class Wrapper<out T>(val a: Int, val b: G<T>)
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
val altContextFactory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
val factory = testDefaultFactoryNoEvolution()
val altContextFactory = testDefaultFactoryNoEvolution()
val ser = SerializationOutput(factory)
val bytes = ser.serializeAndReturnSchema(G("hi"))
val bytes = ser.serializeAndReturnSchema(G("hi")).apply { printSchema() }
factory.getSerializersByDescriptor().printKeyToType()
assertEquals("hi", DeserializationInput(factory).deserialize(bytes.obj).a)
assertEquals("hi", DeserializationInput(altContextFactory).deserialize(bytes.obj).a)
val bytes2 = ser.serializeAndReturnSchema(Wrapper(1, G("hi")))
val bytes2 = ser.serializeAndReturnSchema(Wrapper(1, G("hi"))).apply { printSchema() }
factory.getSerializersByDescriptor().printKeyToType()
printSeparator()
DeserializationInput(factory).deserialize(bytes2.obj).apply {
assertEquals(1, a)
@ -36,7 +100,7 @@ class GenericsTests {
@Test
fun nestedGenericsReferencesByteArrayViaSerializedBytes() {
data class G(val a : Int)
data class G(val a: Int)
data class Wrapper<T : Any>(val a: Int, val b: SerializedBytes<T>)
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
@ -71,27 +135,30 @@ class GenericsTests {
ser.serialize(Wrapper(Container(InnerA(1)))).apply {
factories.forEach {
DeserializationInput(it).deserialize(this).apply { assertEquals(1, c.b.a_a) }
it.getSerializersByDescriptor().printKeyToType(); printSeparator()
}
}
ser.serialize(Wrapper(Container(InnerB(1)))).apply {
factories.forEach {
DeserializationInput(it).deserialize(this).apply { assertEquals(1, c.b.a_b) }
it.getSerializersByDescriptor().printKeyToType(); printSeparator()
}
}
ser.serialize(Wrapper(Container(InnerC("Ho ho ho")))).apply {
factories.forEach {
DeserializationInput(it).deserialize(this).apply { assertEquals("Ho ho ho", c.b.a_c) }
it.getSerializersByDescriptor().printKeyToType(); printSeparator()
}
}
}
@Test
fun nestedSerializationWhereGenericDoesntImpactFingerprint() {
data class Inner(val a : Int)
data class Inner(val a: Int)
data class Container<T : Any>(val b: Inner)
data class Wrapper<T: Any>(val c: Container<T>)
data class Wrapper<T : Any>(val c: Container<T>)
val factorys = listOf(
SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()),
@ -111,4 +178,103 @@ class GenericsTests {
}
}
}
data class ForceWildcard<out T>(val t: T)
private fun forceWildcardSerialize(
a: ForceWildcard<*>,
factory: SerializerFactory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())): SerializedBytes<*> {
val bytes = SerializationOutput(factory).serializeAndReturnSchema(a)
factory.getSerializersByDescriptor().printKeyToType()
bytes.printSchema()
return bytes.obj
}
@Suppress("UNCHECKED_CAST")
private fun forceWildcardDeserializeString(
bytes: SerializedBytes<*>,
factory: SerializerFactory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())) {
DeserializationInput(factory).deserialize(bytes as SerializedBytes<ForceWildcard<String>>)
}
@Suppress("UNCHECKED_CAST")
private fun forceWildcardDeserializeDouble(
bytes: SerializedBytes<*>,
factory: SerializerFactory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())) {
DeserializationInput(factory).deserialize(bytes as SerializedBytes<ForceWildcard<Double>>)
}
@Suppress("UNCHECKED_CAST")
private fun forceWildcardDeserialize(
bytes: SerializedBytes<*>,
factory: SerializerFactory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())) {
DeserializationInput(factory).deserialize(bytes as SerializedBytes<ForceWildcard<*>>)
}
@Test
fun forceWildcard() {
forceWildcardDeserializeString(forceWildcardSerialize(ForceWildcard("hello")))
forceWildcardDeserializeDouble(forceWildcardSerialize(ForceWildcard(3.0)))
}
@Test
fun forceWildcardSharedFactory() {
val f = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
forceWildcardDeserializeString(forceWildcardSerialize(ForceWildcard("hello"), f), f)
forceWildcardDeserializeDouble(forceWildcardSerialize(ForceWildcard(3.0), f), f)
}
@Test
fun forceWildcardDeserialize() {
forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard("hello")))
forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard(10)))
forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard(20.0)))
}
@Test
fun forceWildcardDeserializeSharedFactory() {
val f = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard("hello"), f), f)
forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard(10), f), f)
forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard(20.0), f), f)
}
@Test
fun loadGenericFromFile() {
val resource = "${javaClass.simpleName}.${testName()}"
val sf = testDefaultFactory()
// Uncomment to re-generate test files, needs to be done in three stages
// File(URI("$localPath/$resource")).writeBytes(forceWildcardSerialize(ForceWildcard("wibble")).bytes)
assertEquals("wibble",
DeserializationInput(sf).deserialize(SerializedBytes<ForceWildcard<*>>(
File(GenericsTests::class.java.getResource(resource).toURI()).readBytes())).t)
}
interface DifferentBounds {
fun go()
}
@Test
fun differentBounds() {
data class A (val a: Int): DifferentBounds {
override fun go() {
println(a)
}
}
data class G<out T : DifferentBounds>(val b: T)
val factorys = listOf(
SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()),
SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()))
val ser = SerializationOutput(factorys[0])
ser.serialize(G(A(10))).apply {
factorys.forEach {
}
}
}
}

View File

@ -169,9 +169,11 @@ class SerializationOutputTests {
private inline fun <reified T : Any> serdes(obj: T,
factory: SerializerFactory = SerializerFactory(
AllWhitelist, ClassLoader.getSystemClassLoader()),
AllWhitelist, ClassLoader.getSystemClassLoader(),
EvolutionSerializerGetterTesting()),
freshDeserializationFactory: SerializerFactory = SerializerFactory(
AllWhitelist, ClassLoader.getSystemClassLoader()),
AllWhitelist, ClassLoader.getSystemClassLoader(),
EvolutionSerializerGetterTesting()),
expectedEqual: Boolean = true,
expectDeserializedEqual: Boolean = true): T {
val ser = SerializationOutput(factory)

View File

@ -10,7 +10,7 @@ class SerializeAndReturnSchemaTest {
@Suppress("NOTHING_TO_INLINE")
inline private fun classTestName(clazz: String) = "${this.javaClass.name}\$${testName()}\$$clazz"
val factory = testDefaultFactory()
val factory = testDefaultFactoryNoEvolution()
// just a simple test to verify the internal test extension for serialize does
// indeed give us the correct schema back. This is more useful in support of other

View File

@ -45,7 +45,7 @@ class StaticInitialisationOfSerializedObjectTest {
}
@Test
fun KotlinObjectWithCompanionObject() {
fun kotlinObjectWithCompanionObject() {
data class D(val c: C)
val sf = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
@ -104,7 +104,7 @@ class StaticInitialisationOfSerializedObjectTest {
override val classCarpenter = ClassCarpenter(ClassLoader.getSystemClassLoader(), wl2)
}
// This time have the serilization factory and the carpenter use different whitelists
// This time have the serialization factory and the carpenter use different whitelists
@Test
fun deserializeTest2() {
data class D(val c: C2)