mirror of
https://github.com/corda/corda.git
synced 2025-06-23 01:19:00 +00:00
CORDA-2099 serialisation rewrite (#4257)
* Type model first draft * Introduce TypeIdentifier * Attempting to retrofit fingerprinter with type model * Complete retrofitting typemodel to fingerprinter * Ensure component types are resolved correctly * Fixes and tests * Move resolveAgainst to TypeIdentifier * Remote type modelling and reflection * Convert TypeIdentifiers back into types * Translate AMQP type strings to type identifiers * Start replacing DeserializedParameterizedType * Start roundtripping types through AMQP serialization * Comments on type modelling fingerprinter * kdocs and interface reorganisation * Lots and lots of kdocs, bugfix for cyclic references * Separate SerializerFactory construction from concrete implementation * Fewer build methods * Method naming that doesn't fatally confuse determinisation * Extract SerializerFactory interface * Reset to master's version of compiler.xml * Un-ignore flickering test * Enums don't have superclasses * Break out custom serializer registry * Refactor to separate remote and local serializer factories * Shrink interfaces * Further interface narrowing * Fingerprinting local type model * LocalSerializerFactory uses LocalTypeInformation * Resolve wildcards to their upper bounds * Actually cache custom serializers * Fix various bugs * Remove print statements * There are no cycles in type identifiers * Drive class carpentry from RemoteTypeInformation * Refactor and comment * Comments * Comments and pretty-printer extraction * Format long methods with braces * Serialise composable types using LocalTypeInformation * Warnings if a type is non-composable * Rename lookup -> findOrBuild * Evolution serialisation (no enums yet) * Eliminate old ObjectSerializer and evolver * Enum evolution * Opacity claims types less greedily * Fix type notation and type erasure bug * Clean up unused code paths * Delete unused codepaths * Move whitelist based type model configuration to own file * Move opaque type list * Make all evolution serialisers in one go when schema received * Minor tweaks * Commenting and tidying * Comments * Rebase against master * Make flag for controlling evolution behaviour visible * propertiesOrEmptyMap * Restore error messages * Test for CORDA-4107 * PR fixes * Patch cycles in remote type information after creation * Fix line breaks in unit test on Windows * This time for sure * EvolutionSerializerFactoryTests * Fix some pretty-printing issues, and a carpenter bug * PR fixes * Clarify evolution constructor ordering * Remote TODO comment, which has been moved to a JIRA story
This commit is contained in:
@ -1,5 +1,6 @@
|
||||
package net.corda.serialization.internal.amqp;
|
||||
|
||||
import net.corda.serialization.internal.amqp.testutils.TestDescriptorBasedSerializerRegistry;
|
||||
import net.corda.serialization.internal.amqp.testutils.TestSerializationContext;
|
||||
import org.junit.Test;
|
||||
|
||||
@ -133,8 +134,9 @@ public class JavaPrivatePropertyTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void singlePrivateWithConstructor() throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
|
||||
SerializerFactory factory = testDefaultFactory();
|
||||
public void singlePrivateWithConstructor() throws NotSerializableException {
|
||||
TestDescriptorBasedSerializerRegistry registry = new TestDescriptorBasedSerializerRegistry();
|
||||
SerializerFactory factory = testDefaultFactory(registry);
|
||||
|
||||
SerializationOutput ser = new SerializationOutput(factory);
|
||||
DeserializationInput des = new DeserializationInput(factory);
|
||||
@ -144,22 +146,14 @@ public class JavaPrivatePropertyTests {
|
||||
|
||||
assertEquals (c.a, c2.a);
|
||||
|
||||
//
|
||||
// Now ensure we actually got a private property serializer
|
||||
//
|
||||
Map<Object, AMQPSerializer<Object>> serializersByDescriptor = factory.getSerializersByDescriptor();
|
||||
|
||||
assertEquals(1, serializersByDescriptor.size());
|
||||
ObjectSerializer cSerializer = ((ObjectSerializer)serializersByDescriptor.values().toArray()[0]);
|
||||
assertEquals(1, cSerializer.getPropertySerializers().getSerializationOrder().size());
|
||||
Object[] propertyReaders = cSerializer.getPropertySerializers().getSerializationOrder().toArray();
|
||||
assertTrue (((PropertyAccessor)propertyReaders[0]).getSerializer().getPropertyReader() instanceof PrivatePropertyReader);
|
||||
assertEquals(1, registry.getContents().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void singlePrivateWithConstructorAndGetter()
|
||||
throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
|
||||
SerializerFactory factory = testDefaultFactory();
|
||||
throws NotSerializableException {
|
||||
TestDescriptorBasedSerializerRegistry registry = new TestDescriptorBasedSerializerRegistry();
|
||||
SerializerFactory factory = testDefaultFactory(registry);
|
||||
|
||||
SerializationOutput ser = new SerializationOutput(factory);
|
||||
DeserializationInput des = new DeserializationInput(factory);
|
||||
@ -169,15 +163,6 @@ public class JavaPrivatePropertyTests {
|
||||
|
||||
assertEquals (c.a, c2.a);
|
||||
|
||||
//
|
||||
// Now ensure we actually got a private property serializer
|
||||
//
|
||||
Map<Object, AMQPSerializer<Object>> serializersByDescriptor = factory.getSerializersByDescriptor();
|
||||
|
||||
assertEquals(1, serializersByDescriptor.size());
|
||||
ObjectSerializer cSerializer = ((ObjectSerializer)serializersByDescriptor.values().toArray()[0]);
|
||||
assertEquals(1, cSerializer.getPropertySerializers().getSerializationOrder().size());
|
||||
Object[] propertyReaders = cSerializer.getPropertySerializers().getSerializationOrder().toArray();
|
||||
assertTrue (((PropertyAccessor)propertyReaders[0]).getSerializer().getPropertyReader() instanceof PublicPropertyReader);
|
||||
assertEquals(1, registry.getContents().size());
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,8 @@ import net.corda.core.serialization.SerializedBytes;
|
||||
import net.corda.serialization.internal.AllWhitelist;
|
||||
import net.corda.serialization.internal.amqp.*;
|
||||
import net.corda.serialization.internal.amqp.Schema;
|
||||
import net.corda.serialization.internal.model.RemoteTypeInformation;
|
||||
import net.corda.serialization.internal.model.TypeIdentifier;
|
||||
import net.corda.testing.core.SerializationEnvironmentRule;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
@ -66,42 +68,23 @@ public class JavaCalculatedValuesToClassCarpenterTest extends AmqpCarpenterBase
|
||||
ObjectAndEnvelope<C> objAndEnv = new DeserializationInput(factory)
|
||||
.deserializeAndReturnEnvelope(serialized, C.class, context);
|
||||
|
||||
C amqpObj = objAndEnv.getObj();
|
||||
Schema schema = objAndEnv.getEnvelope().getSchema();
|
||||
|
||||
assertEquals(2, amqpObj.getI());
|
||||
assertEquals("4", amqpObj.getSquared());
|
||||
assertEquals(2, schema.getTypes().size());
|
||||
assertTrue(schema.getTypes().get(0) instanceof CompositeType);
|
||||
|
||||
CompositeType concrete = (CompositeType) schema.getTypes().get(0);
|
||||
assertEquals(3, concrete.getFields().size());
|
||||
assertEquals("doubled", concrete.getFields().get(0).getName());
|
||||
assertEquals("int", concrete.getFields().get(0).getType());
|
||||
assertEquals("i", concrete.getFields().get(1).getName());
|
||||
assertEquals("int", concrete.getFields().get(1).getType());
|
||||
assertEquals("squared", concrete.getFields().get(2).getName());
|
||||
assertEquals("string", concrete.getFields().get(2).getType());
|
||||
|
||||
assertEquals(0, AMQPSchemaExtensions.carpenterSchema(schema, ClassLoader.getSystemClassLoader()).getSize());
|
||||
Schema mangledSchema = ClassCarpenterTestUtilsKt.mangleNames(schema, singletonList(C.class.getTypeName()));
|
||||
CarpenterMetaSchema l2 = AMQPSchemaExtensions.carpenterSchema(mangledSchema, ClassLoader.getSystemClassLoader());
|
||||
String mangledClassName = ClassCarpenterTestUtilsKt.mangleName(C.class.getTypeName());
|
||||
|
||||
assertEquals(1, l2.getSize());
|
||||
net.corda.serialization.internal.carpenter.Schema carpenterSchema = l2.getCarpenterSchemas().stream()
|
||||
.filter(s -> s.getName().equals(mangledClassName))
|
||||
TypeIdentifier typeToMangle = TypeIdentifier.Companion.forClass(C.class);
|
||||
Envelope env = objAndEnv.getEnvelope();
|
||||
RemoteTypeInformation typeInformation = getTypeInformation(env).values().stream()
|
||||
.filter(it -> it.getTypeIdentifier().equals(typeToMangle))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalStateException("No schema found for mangled class name " + mangledClassName));
|
||||
.orElseThrow(IllegalStateException::new);
|
||||
|
||||
Class<?> pinochio = new ClassCarpenterImpl(AllWhitelist.INSTANCE).build(carpenterSchema);
|
||||
RemoteTypeInformation renamed = rename(typeInformation, typeToMangle, mangle(typeToMangle));
|
||||
|
||||
Class<?> pinochio = load(renamed);
|
||||
Object p = pinochio.getConstructors()[0].newInstance(4, 2, "4");
|
||||
|
||||
assertEquals(pinochio.getMethod("getI").invoke(p), amqpObj.getI());
|
||||
assertEquals(pinochio.getMethod("getSquared").invoke(p), amqpObj.getSquared());
|
||||
assertEquals(pinochio.getMethod("getDoubled").invoke(p), amqpObj.getDoubled());
|
||||
assertEquals(2, pinochio.getMethod("getI").invoke(p));
|
||||
assertEquals("4", pinochio.getMethod("getSquared").invoke(p));
|
||||
assertEquals(4, pinochio.getMethod("getDoubled").invoke(p));
|
||||
|
||||
Parent upcast = (Parent) p;
|
||||
assertEquals(upcast.getDoubled(), amqpObj.getDoubled());
|
||||
assertEquals(4, upcast.getDoubled());
|
||||
}
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ class ListsSerializationTest {
|
||||
payload.add(2)
|
||||
val wrongPayloadType = WrongPayloadType(payload)
|
||||
Assertions.assertThatThrownBy { wrongPayloadType.serialize() }
|
||||
.isInstanceOf(NotSerializableException::class.java).hasMessageContaining("Cannot derive collection type for declaredType")
|
||||
.isInstanceOf(NotSerializableException::class.java).hasMessageContaining("Cannot derive collection type for declared type")
|
||||
}
|
||||
|
||||
@CordaSerializable
|
||||
@ -107,7 +107,9 @@ class ListsSerializationTest {
|
||||
val container = CovariantContainer(payload)
|
||||
|
||||
fun verifyEnvelopeBody(envelope: Envelope) {
|
||||
envelope.schema.types.single { typeNotation -> typeNotation.name == java.util.List::class.java.name + "<?>" }
|
||||
envelope.schema.types.single { typeNotation ->
|
||||
typeNotation.name == "java.util.List<${Parent::class.java.name}>"
|
||||
}
|
||||
}
|
||||
|
||||
assertEqualAfterRoundTripSerialization(container, { bytes -> verifyEnvelope(bytes, ::verifyEnvelopeBody) })
|
||||
|
@ -37,7 +37,7 @@ class AbstractAMQPSerializationSchemeTest {
|
||||
null)
|
||||
|
||||
|
||||
val factory = TestSerializerFactory(TESTING_CONTEXT.whitelist, TESTING_CONTEXT.deserializationClassLoader)
|
||||
val factory = SerializerFactoryBuilder.build(TESTING_CONTEXT.whitelist, TESTING_CONTEXT.deserializationClassLoader)
|
||||
val maxFactories = 512
|
||||
val backingMap = AccessOrderLinkedHashMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>({ maxFactories })
|
||||
val scheme = object : AbstractAMQPSerializationScheme(emptySet(), backingMap, createSerializerFactoryFactory()) {
|
||||
@ -55,7 +55,6 @@ class AbstractAMQPSerializationSchemeTest {
|
||||
|
||||
}
|
||||
|
||||
|
||||
IntStream.range(0, 2048).parallel().forEach {
|
||||
val context = if (ThreadLocalRandom.current().nextBoolean()) {
|
||||
genesisContext.withClassLoader(URLClassLoader(emptyArray()))
|
||||
|
@ -13,16 +13,13 @@ import kotlin.test.assertEquals
|
||||
class CorDappSerializerTests {
|
||||
data class NeedsProxy(val a: String)
|
||||
|
||||
private fun proxyFactory(serializers: List<SerializationCustomSerializer<*, *>>): SerializerFactory {
|
||||
val factory = SerializerFactoryBuilder.build(AllWhitelist,
|
||||
ClassCarpenterImpl(AllWhitelist, ClassLoader.getSystemClassLoader()),
|
||||
DefaultEvolutionSerializerProvider)
|
||||
|
||||
private fun proxyFactory(
|
||||
serializers: List<SerializationCustomSerializer<*, *>>
|
||||
) = SerializerFactoryBuilder.build(AllWhitelist,
|
||||
ClassCarpenterImpl(AllWhitelist, ClassLoader.getSystemClassLoader())).apply {
|
||||
serializers.forEach {
|
||||
factory.registerExternal(CorDappCustomSerializer(it, factory))
|
||||
registerExternal(CorDappCustomSerializer(it, this))
|
||||
}
|
||||
|
||||
return factory
|
||||
}
|
||||
|
||||
class NeedsProxyProxySerializer : SerializationCustomSerializer<NeedsProxy, NeedsProxyProxySerializer.Proxy> {
|
||||
|
@ -20,15 +20,15 @@ class DeserializeNeedingCarpentryOfEnumsTest : AmqpCarpenterBase(AllWhitelist) {
|
||||
// Setup the test
|
||||
//
|
||||
val setupFactory = testDefaultFactoryNoEvolution()
|
||||
|
||||
val classCarpenter = ClassCarpenterImpl(AllWhitelist, ClassLoader.getSystemClassLoader())
|
||||
val enumConstants = listOf("AAA", "BBB", "CCC", "DDD", "EEE", "FFF",
|
||||
"GGG", "HHH", "III", "JJJ").associateBy({ it }, { EnumField() })
|
||||
|
||||
// create the enum
|
||||
val testEnumType = setupFactory.classCarpenter.build(EnumSchema("test.testEnumType", enumConstants))
|
||||
val testEnumType = classCarpenter.build(EnumSchema("test.testEnumType", enumConstants))
|
||||
|
||||
// create the class that has that enum as an element
|
||||
val testClassType = setupFactory.classCarpenter.build(ClassSchema("test.testClassType",
|
||||
val testClassType = classCarpenter.build(ClassSchema("test.testClassType",
|
||||
mapOf("a" to NonNullableField(testEnumType))))
|
||||
|
||||
// create an instance of the class we can then serialise
|
||||
@ -59,16 +59,16 @@ class DeserializeNeedingCarpentryOfEnumsTest : AmqpCarpenterBase(AllWhitelist) {
|
||||
// Setup the test
|
||||
//
|
||||
val setupFactory = testDefaultFactoryNoEvolution()
|
||||
|
||||
val classCarpenter = ClassCarpenterImpl(AllWhitelist, ClassLoader.getSystemClassLoader())
|
||||
val enumConstants = listOf("AAA", "BBB", "CCC", "DDD", "EEE", "FFF",
|
||||
"GGG", "HHH", "III", "JJJ").associateBy({ it }, { EnumField() })
|
||||
|
||||
// create the enum
|
||||
val testEnumType1 = setupFactory.classCarpenter.build(EnumSchema("test.testEnumType1", enumConstants))
|
||||
val testEnumType2 = setupFactory.classCarpenter.build(EnumSchema("test.testEnumType2", enumConstants))
|
||||
val testEnumType1 = classCarpenter.build(EnumSchema("test.testEnumType1", enumConstants))
|
||||
val testEnumType2 = classCarpenter.build(EnumSchema("test.testEnumType2", enumConstants))
|
||||
|
||||
// create the class that has that enum as an element
|
||||
val testClassType = setupFactory.classCarpenter.build(ClassSchema("test.testClassType",
|
||||
val testClassType = classCarpenter.build(ClassSchema("test.testClassType",
|
||||
mapOf(
|
||||
"a" to NonNullableField(testEnumType1),
|
||||
"b" to NonNullableField(testEnumType2),
|
||||
|
@ -441,7 +441,4 @@ class DeserializeNeedingCarpentrySimpleTypesTest : AmqpCarpenterBase(AllWhitelis
|
||||
assertEquals(0b1010.toByte(), deserializedObj::class.java.getMethod("getByteB").invoke(deserializedObj))
|
||||
assertEquals(null, deserializedObj::class.java.getMethod("getByteC").invoke(deserializedObj))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -74,7 +74,7 @@ class DeserializeSimpleTypesTests {
|
||||
val ia = IA(arrayOf(1, 2, 3))
|
||||
|
||||
assertEquals("class [Ljava.lang.Integer;", ia.ia::class.java.toString())
|
||||
assertEquals(SerializerFactory.nameForType(ia.ia::class.java), "int[]")
|
||||
assertEquals(AMQPTypeIdentifiers.nameForType(ia.ia::class.java), "int[]")
|
||||
|
||||
val serialisedIA = TestSerializationOutput(VERBOSE, sf1).serialize(ia)
|
||||
val deserializedIA = DeserializationInput(sf1).deserialize(serialisedIA)
|
||||
@ -93,7 +93,7 @@ class DeserializeSimpleTypesTests {
|
||||
val ia = IA(arrayOf(Integer(1), Integer(2), Integer(3)))
|
||||
|
||||
assertEquals("class [Ljava.lang.Integer;", ia.ia::class.java.toString())
|
||||
assertEquals(SerializerFactory.nameForType(ia.ia::class.java), "int[]")
|
||||
assertEquals(AMQPTypeIdentifiers.nameForType(ia.ia::class.java), "int[]")
|
||||
|
||||
val serialisedIA = TestSerializationOutput(VERBOSE, sf1).serialize(ia)
|
||||
val deserializedIA = DeserializationInput(sf1).deserialize(serialisedIA)
|
||||
@ -116,7 +116,7 @@ class DeserializeSimpleTypesTests {
|
||||
val ia = IA(v)
|
||||
|
||||
assertEquals("class [I", ia.ia::class.java.toString())
|
||||
assertEquals(SerializerFactory.nameForType(ia.ia::class.java), "int[p]")
|
||||
assertEquals(AMQPTypeIdentifiers.nameForType(ia.ia::class.java), "int[p]")
|
||||
|
||||
val serialisedIA = TestSerializationOutput(VERBOSE, sf1).serialize(ia)
|
||||
val deserializedIA = DeserializationInput(sf1).deserialize(serialisedIA)
|
||||
@ -134,7 +134,7 @@ class DeserializeSimpleTypesTests {
|
||||
val c = C(arrayOf('a', 'b', 'c'))
|
||||
|
||||
assertEquals("class [Ljava.lang.Character;", c.c::class.java.toString())
|
||||
assertEquals(SerializerFactory.nameForType(c.c::class.java), "char[]")
|
||||
assertEquals(AMQPTypeIdentifiers.nameForType(c.c::class.java), "char[]")
|
||||
|
||||
val serialisedC = TestSerializationOutput(VERBOSE, sf1).serialize(c)
|
||||
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
|
||||
@ -154,7 +154,7 @@ class DeserializeSimpleTypesTests {
|
||||
val c = C(v)
|
||||
|
||||
assertEquals("class [C", c.c::class.java.toString())
|
||||
assertEquals(SerializerFactory.nameForType(c.c::class.java), "char[p]")
|
||||
assertEquals(AMQPTypeIdentifiers.nameForType(c.c::class.java), "char[p]")
|
||||
|
||||
val serialisedC = TestSerializationOutput(VERBOSE, sf1).serialize(c)
|
||||
var deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
|
||||
@ -183,7 +183,7 @@ class DeserializeSimpleTypesTests {
|
||||
val c = C(arrayOf(true, false, false, true))
|
||||
|
||||
assertEquals("class [Ljava.lang.Boolean;", c.c::class.java.toString())
|
||||
assertEquals(SerializerFactory.nameForType(c.c::class.java), "boolean[]")
|
||||
assertEquals(AMQPTypeIdentifiers.nameForType(c.c::class.java), "boolean[]")
|
||||
|
||||
val serialisedC = TestSerializationOutput(VERBOSE, sf1).serialize(c)
|
||||
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
|
||||
@ -203,7 +203,7 @@ class DeserializeSimpleTypesTests {
|
||||
c.c[0] = true; c.c[1] = false; c.c[2] = false; c.c[3] = true
|
||||
|
||||
assertEquals("class [Z", c.c::class.java.toString())
|
||||
assertEquals(SerializerFactory.nameForType(c.c::class.java), "boolean[p]")
|
||||
assertEquals(AMQPTypeIdentifiers.nameForType(c.c::class.java), "boolean[p]")
|
||||
|
||||
val serialisedC = TestSerializationOutput(VERBOSE, sf1).serialize(c)
|
||||
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
|
||||
@ -222,7 +222,7 @@ class DeserializeSimpleTypesTests {
|
||||
val c = C(arrayOf(0b0001, 0b0101, 0b1111))
|
||||
|
||||
assertEquals("class [Ljava.lang.Byte;", c.c::class.java.toString())
|
||||
assertEquals(SerializerFactory.nameForType(c.c::class.java), "byte[]")
|
||||
assertEquals(AMQPTypeIdentifiers.nameForType(c.c::class.java), "byte[]")
|
||||
|
||||
val serialisedC = TestSerializationOutput(VERBOSE, sf1).serialize(c)
|
||||
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
|
||||
@ -241,7 +241,7 @@ class DeserializeSimpleTypesTests {
|
||||
c.c[0] = 0b0001; c.c[1] = 0b0101; c.c[2] = 0b1111
|
||||
|
||||
assertEquals("class [B", c.c::class.java.toString())
|
||||
assertEquals(SerializerFactory.nameForType(c.c::class.java), "binary")
|
||||
assertEquals("binary", AMQPTypeIdentifiers.nameForType(c.c::class.java))
|
||||
|
||||
val serialisedC = TestSerializationOutput(VERBOSE, sf1).serialize(c)
|
||||
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
|
||||
@ -267,7 +267,7 @@ class DeserializeSimpleTypesTests {
|
||||
val c = C(arrayOf(1, 2, 3))
|
||||
|
||||
assertEquals("class [Ljava.lang.Short;", c.c::class.java.toString())
|
||||
assertEquals(SerializerFactory.nameForType(c.c::class.java), "short[]")
|
||||
assertEquals(AMQPTypeIdentifiers.nameForType(c.c::class.java), "short[]")
|
||||
|
||||
val serialisedC = TestSerializationOutput(VERBOSE, sf1).serialize(c)
|
||||
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
|
||||
@ -286,7 +286,7 @@ class DeserializeSimpleTypesTests {
|
||||
c.c[0] = 1; c.c[1] = 2; c.c[2] = 5
|
||||
|
||||
assertEquals("class [S", c.c::class.java.toString())
|
||||
assertEquals(SerializerFactory.nameForType(c.c::class.java), "short[p]")
|
||||
assertEquals(AMQPTypeIdentifiers.nameForType(c.c::class.java), "short[p]")
|
||||
|
||||
val serialisedC = TestSerializationOutput(VERBOSE, sf1).serialize(c)
|
||||
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
|
||||
@ -304,7 +304,7 @@ class DeserializeSimpleTypesTests {
|
||||
val c = C(arrayOf(2147483650, -2147483800, 10))
|
||||
|
||||
assertEquals("class [Ljava.lang.Long;", c.c::class.java.toString())
|
||||
assertEquals(SerializerFactory.nameForType(c.c::class.java), "long[]")
|
||||
assertEquals(AMQPTypeIdentifiers.nameForType(c.c::class.java), "long[]")
|
||||
|
||||
val serialisedC = TestSerializationOutput(VERBOSE, sf1).serialize(c)
|
||||
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
|
||||
@ -323,7 +323,7 @@ class DeserializeSimpleTypesTests {
|
||||
c.c[0] = 2147483650; c.c[1] = -2147483800; c.c[2] = 10
|
||||
|
||||
assertEquals("class [J", c.c::class.java.toString())
|
||||
assertEquals(SerializerFactory.nameForType(c.c::class.java), "long[p]")
|
||||
assertEquals(AMQPTypeIdentifiers.nameForType(c.c::class.java), "long[p]")
|
||||
|
||||
val serialisedC = TestSerializationOutput(VERBOSE, sf1).serialize(c)
|
||||
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
|
||||
@ -341,7 +341,7 @@ class DeserializeSimpleTypesTests {
|
||||
val c = C(arrayOf(10F, 100.023232F, -1455.433400F))
|
||||
|
||||
assertEquals("class [Ljava.lang.Float;", c.c::class.java.toString())
|
||||
assertEquals(SerializerFactory.nameForType(c.c::class.java), "float[]")
|
||||
assertEquals("float[]", AMQPTypeIdentifiers.nameForType(c.c::class.java))
|
||||
|
||||
val serialisedC = TestSerializationOutput(VERBOSE, sf1).serialize(c)
|
||||
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
|
||||
@ -360,7 +360,7 @@ class DeserializeSimpleTypesTests {
|
||||
c.c[0] = 10F; c.c[1] = 100.023232F; c.c[2] = -1455.433400F
|
||||
|
||||
assertEquals("class [F", c.c::class.java.toString())
|
||||
assertEquals(SerializerFactory.nameForType(c.c::class.java), "float[p]")
|
||||
assertEquals(AMQPTypeIdentifiers.nameForType(c.c::class.java), "float[p]")
|
||||
|
||||
val serialisedC = TestSerializationOutput(VERBOSE, sf1).serialize(c)
|
||||
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
|
||||
@ -378,7 +378,7 @@ class DeserializeSimpleTypesTests {
|
||||
val c = C(arrayOf(10.0, 100.2, -1455.2))
|
||||
|
||||
assertEquals("class [Ljava.lang.Double;", c.c::class.java.toString())
|
||||
assertEquals(SerializerFactory.nameForType(c.c::class.java), "double[]")
|
||||
assertEquals(AMQPTypeIdentifiers.nameForType(c.c::class.java), "double[]")
|
||||
|
||||
val serialisedC = TestSerializationOutput(VERBOSE, sf1).serialize(c)
|
||||
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
|
||||
@ -397,7 +397,7 @@ class DeserializeSimpleTypesTests {
|
||||
c.c[0] = 10.0; c.c[1] = 100.2; c.c[2] = -1455.2
|
||||
|
||||
assertEquals("class [D", c.c::class.java.toString())
|
||||
assertEquals(SerializerFactory.nameForType(c.c::class.java), "double[p]")
|
||||
assertEquals(AMQPTypeIdentifiers.nameForType(c.c::class.java), "double[p]")
|
||||
|
||||
val serialisedC = TestSerializationOutput(VERBOSE, sf1).serialize(c)
|
||||
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
|
||||
|
@ -1,105 +0,0 @@
|
||||
package net.corda.serialization.internal.amqp
|
||||
|
||||
import org.junit.Test
|
||||
import java.io.NotSerializableException
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class DeserializedParameterizedTypeTests {
|
||||
private fun normalise(string: String): String {
|
||||
return string.replace(" ", "")
|
||||
}
|
||||
|
||||
private fun verify(typeName: String) {
|
||||
val type = DeserializedParameterizedType.make(typeName)
|
||||
assertEquals(normalise(type.typeName), normalise(typeName))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test nested`() {
|
||||
verify(" java.util.Map < java.util.Map< java.lang.String, java.lang.Integer >, java.util.Map < java.lang.Long , java.lang.String > >")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test simple`() {
|
||||
verify("java.util.List<java.lang.String>")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test multiple args`() {
|
||||
verify("java.util.Map<java.lang.String,java.lang.Integer>")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test trailing whitespace`() {
|
||||
verify("java.util.Map<java.lang.String, java.lang.Integer> ")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test list of commands`() {
|
||||
verify("java.util.List<net.corda.core.contracts.Command<?>>")
|
||||
}
|
||||
|
||||
@Test(expected = NotSerializableException::class)
|
||||
fun `test trailing text`() {
|
||||
verify("java.util.Map<java.lang.String, java.lang.Integer>foo")
|
||||
}
|
||||
|
||||
@Test(expected = NotSerializableException::class)
|
||||
fun `test trailing comma`() {
|
||||
verify("java.util.Map<java.lang.String, java.lang.Integer,>")
|
||||
}
|
||||
|
||||
@Test(expected = NotSerializableException::class)
|
||||
fun `test leading comma`() {
|
||||
verify("java.util.Map<,java.lang.String, java.lang.Integer>")
|
||||
}
|
||||
|
||||
@Test(expected = NotSerializableException::class)
|
||||
fun `test middle comma`() {
|
||||
verify("java.util.Map<,java.lang.String,, java.lang.Integer>")
|
||||
}
|
||||
|
||||
@Test(expected = NotSerializableException::class)
|
||||
fun `test trailing close`() {
|
||||
verify("java.util.Map<java.lang.String, java.lang.Integer>>")
|
||||
}
|
||||
|
||||
@Test(expected = NotSerializableException::class)
|
||||
fun `test empty params`() {
|
||||
verify("java.util.Map<>")
|
||||
}
|
||||
|
||||
@Test(expected = NotSerializableException::class)
|
||||
fun `test mid whitespace`() {
|
||||
verify("java.u til.List<java.lang.String>")
|
||||
}
|
||||
|
||||
@Test(expected = NotSerializableException::class)
|
||||
fun `test mid whitespace2`() {
|
||||
verify("java.util.List<java.l ng.String>")
|
||||
}
|
||||
|
||||
@Test(expected = NotSerializableException::class)
|
||||
fun `test wrong number of parameters`() {
|
||||
verify("java.util.List<java.lang.String, java.lang.Integer>")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test no parameters`() {
|
||||
verify("java.lang.String")
|
||||
}
|
||||
|
||||
@Test(expected = NotSerializableException::class)
|
||||
fun `test parameters on non-generic type`() {
|
||||
verify("java.lang.String<java.lang.Integer>")
|
||||
}
|
||||
|
||||
@Test(expected = NotSerializableException::class)
|
||||
fun `test excessive nesting`() {
|
||||
var nested = "java.lang.Integer"
|
||||
for (i in 1..DeserializedParameterizedType.MAX_DEPTH) {
|
||||
nested = "java.util.List<$nested>"
|
||||
}
|
||||
verify(nested)
|
||||
}
|
||||
}
|
@ -392,27 +392,10 @@ class EnumEvolvabilityTests {
|
||||
data class C1(val annotatedEnum: AnnotatedEnumOnce)
|
||||
|
||||
val sf = testDefaultFactory()
|
||||
val f = sf.javaClass.getDeclaredField("transformsCache")
|
||||
f.isAccessible = true
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
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, 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, 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])
|
||||
}
|
||||
|
@ -0,0 +1,55 @@
|
||||
package net.corda.serialization.internal.amqp
|
||||
|
||||
import net.corda.serialization.internal.amqp.testutils.deserializeAndReturnEnvelope
|
||||
import net.corda.serialization.internal.amqp.testutils.serialize
|
||||
import net.corda.serialization.internal.amqp.testutils.testDefaultFactory
|
||||
import net.corda.serialization.internal.model.RemoteTypeInformation
|
||||
import net.corda.serialization.internal.model.TypeIdentifier
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertNull
|
||||
|
||||
class EvolutionSerializerFactoryTests {
|
||||
|
||||
private val factory = testDefaultFactory()
|
||||
|
||||
@Test
|
||||
fun preservesDataWhenFlagSet() {
|
||||
val nonStrictEvolutionSerializerFactory = DefaultEvolutionSerializerFactory(
|
||||
factory,
|
||||
ClassLoader.getSystemClassLoader(),
|
||||
false)
|
||||
|
||||
val strictEvolutionSerializerFactory = DefaultEvolutionSerializerFactory(
|
||||
factory,
|
||||
ClassLoader.getSystemClassLoader(),
|
||||
true)
|
||||
|
||||
@Suppress("unused")
|
||||
class C(val importantFieldA: Int)
|
||||
val (_, env) = DeserializationInput(factory).deserializeAndReturnEnvelope(
|
||||
SerializationOutput(factory).serialize(C(1)))
|
||||
|
||||
val remoteTypeInformation = AMQPRemoteTypeModel().interpret(SerializationSchemas(env.schema, env.transformsSchema))
|
||||
.values.find { it.typeIdentifier == TypeIdentifier.forClass(C::class.java) }
|
||||
as RemoteTypeInformation.Composable
|
||||
|
||||
val withAddedField = remoteTypeInformation.copy(properties = remoteTypeInformation.properties.plus(
|
||||
"importantFieldB" to remoteTypeInformation.properties["importantFieldA"]!!))
|
||||
|
||||
val localTypeInformation = factory.getTypeInformation(C::class.java)
|
||||
|
||||
// No evolution required with original fields.
|
||||
assertNull(strictEvolutionSerializerFactory.getEvolutionSerializer(remoteTypeInformation, localTypeInformation))
|
||||
|
||||
// Returns an evolution serializer if the fields have changed.
|
||||
assertNotNull(nonStrictEvolutionSerializerFactory.getEvolutionSerializer(withAddedField, localTypeInformation))
|
||||
|
||||
// Fails in strict mode if the remote type information includes a field not included in the local type.
|
||||
assertFailsWith<EvolutionSerializationException> {
|
||||
strictEvolutionSerializerFactory.getEvolutionSerializer(withAddedField, localTypeInformation)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package net.corda.serialization.internal.amqp
|
||||
|
||||
import java.io.NotSerializableException
|
||||
|
||||
/**
|
||||
* An implementation of [EvolutionSerializerProvider] 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.
|
||||
*/
|
||||
object FailIfEvolutionAttempted : EvolutionSerializerProvider {
|
||||
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}")
|
||||
}
|
||||
}
|
@ -461,6 +461,17 @@ class EvolvabilityTests {
|
||||
assertEquals(oa, outer.a)
|
||||
assertEquals(ia, outer.b.a)
|
||||
assertEquals(null, outer.b.b)
|
||||
|
||||
// Repeat, but receiving a message with the newer version of Inner
|
||||
val newVersion = SerializationOutput(sf).serializeAndReturnSchema(Outer(oa, Inner(ia, "new value")))
|
||||
val model = AMQPRemoteTypeModel()
|
||||
val remoteTypeInfo = model.interpret(SerializationSchemas(newVersion.schema, newVersion.transformsSchema))
|
||||
println(remoteTypeInfo)
|
||||
|
||||
val newOuter = DeserializationInput(sf).deserialize(SerializedBytes<Outer>(newVersion.obj.bytes))
|
||||
assertEquals(oa, newOuter.a)
|
||||
assertEquals(ia, newOuter.b.a)
|
||||
assertEquals("new value", newOuter.b.b)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1,23 +1,25 @@
|
||||
package net.corda.serialization.internal.amqp
|
||||
|
||||
import org.junit.Test
|
||||
import java.lang.reflect.Type
|
||||
import kotlin.test.assertEquals
|
||||
import net.corda.serialization.internal.AllWhitelist
|
||||
import net.corda.serialization.internal.amqp.testutils.TestSerializationOutput
|
||||
import net.corda.serialization.internal.amqp.testutils.serializeAndReturnSchema
|
||||
import net.corda.serialization.internal.carpenter.ClassCarpenterImpl
|
||||
import net.corda.serialization.internal.model.ConfigurableLocalTypeModel
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation
|
||||
import net.corda.serialization.internal.model.FingerPrinter
|
||||
|
||||
class FingerPrinterTesting : FingerPrinter {
|
||||
private var index = 0
|
||||
private val cache = mutableMapOf<Type, String>()
|
||||
private val cache = mutableMapOf<LocalTypeInformation, String>()
|
||||
|
||||
override fun fingerprint(type: Type): String {
|
||||
return cache.computeIfAbsent(type) { index++.toString() }
|
||||
override fun fingerprint(typeInformation: LocalTypeInformation): String {
|
||||
return cache.computeIfAbsent(typeInformation) { index++.toString() }
|
||||
}
|
||||
|
||||
@Suppress("UNUSED")
|
||||
fun changeFingerprint(type: Type) {
|
||||
fun changeFingerprint(type: LocalTypeInformation) {
|
||||
cache.computeIfAbsent(type) { "" }.apply { index++.toString() }
|
||||
}
|
||||
}
|
||||
@ -30,10 +32,14 @@ class FingerPrinterTestingTests {
|
||||
@Test
|
||||
fun testingTest() {
|
||||
val fpt = FingerPrinterTesting()
|
||||
assertEquals("0", fpt.fingerprint(Integer::class.java))
|
||||
assertEquals("1", fpt.fingerprint(String::class.java))
|
||||
assertEquals("0", fpt.fingerprint(Integer::class.java))
|
||||
assertEquals("1", fpt.fingerprint(String::class.java))
|
||||
val descriptorBasedSerializerRegistry = DefaultDescriptorBasedSerializerRegistry()
|
||||
val customSerializerRegistry: CustomSerializerRegistry = CachingCustomSerializerRegistry(descriptorBasedSerializerRegistry)
|
||||
val typeModel = ConfigurableLocalTypeModel(WhitelistBasedTypeModelConfiguration(AllWhitelist, customSerializerRegistry))
|
||||
|
||||
assertEquals("0", fpt.fingerprint(typeModel.inspect(Integer::class.java)))
|
||||
assertEquals("1", fpt.fingerprint(typeModel.inspect(String::class.java)))
|
||||
assertEquals("0", fpt.fingerprint(typeModel.inspect(Integer::class.java)))
|
||||
assertEquals("1", fpt.fingerprint(typeModel.inspect(String::class.java)))
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -42,7 +48,7 @@ class FingerPrinterTestingTests {
|
||||
|
||||
val factory = SerializerFactoryBuilder.build(AllWhitelist,
|
||||
ClassCarpenterImpl(AllWhitelist, ClassLoader.getSystemClassLoader()),
|
||||
fingerPrinterProvider = { _ -> FingerPrinterTesting() })
|
||||
overrideFingerPrinter = FingerPrinterTesting())
|
||||
|
||||
val blob = TestSerializationOutput(VERBOSE, factory).serializeAndReturnSchema(C(1, 2L))
|
||||
|
||||
|
@ -40,15 +40,6 @@ class GenericsTests {
|
||||
|
||||
private fun <T : Any> BytesAndSchemas<T>.printSchema() = if (VERBOSE) println("${this.schema}\n") else Unit
|
||||
|
||||
private fun MutableMap<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)
|
||||
@ -57,12 +48,8 @@ class GenericsTests {
|
||||
|
||||
val bytes1 = SerializationOutput(factory).serializeAndReturnSchema(G("hi")).apply { printSchema() }
|
||||
|
||||
factory.serializersByDescriptor.printKeyToType()
|
||||
|
||||
val bytes2 = SerializationOutput(factory).serializeAndReturnSchema(G(121)).apply { printSchema() }
|
||||
|
||||
factory.serializersByDescriptor.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) }
|
||||
@ -94,15 +81,11 @@ class GenericsTests {
|
||||
|
||||
val bytes = ser.serializeAndReturnSchema(G("hi")).apply { printSchema() }
|
||||
|
||||
factory.serializersByDescriptor.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"))).apply { printSchema() }
|
||||
|
||||
factory.serializersByDescriptor.printKeyToType()
|
||||
|
||||
printSeparator()
|
||||
|
||||
DeserializationInput(factory).deserialize(bytes2.obj).apply {
|
||||
@ -161,21 +144,18 @@ class GenericsTests {
|
||||
ser.serialize(Wrapper(Container(InnerA(1)))).apply {
|
||||
factories.forEach {
|
||||
DeserializationInput(it).deserialize(this).apply { assertEquals(1, c.b.a_a) }
|
||||
it.serializersByDescriptor.printKeyToType(); printSeparator()
|
||||
}
|
||||
}
|
||||
|
||||
ser.serialize(Wrapper(Container(InnerB(1)))).apply {
|
||||
factories.forEach {
|
||||
DeserializationInput(it).deserialize(this).apply { assertEquals(1, c.b.a_b) }
|
||||
it.serializersByDescriptor.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.serializersByDescriptor.printKeyToType(); printSeparator()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -217,7 +197,6 @@ class GenericsTests {
|
||||
ClassCarpenterImpl(AllWhitelist, ClassLoader.getSystemClassLoader())
|
||||
)): SerializedBytes<*> {
|
||||
val bytes = SerializationOutput(factory).serializeAndReturnSchema(a)
|
||||
factory.serializersByDescriptor.printKeyToType()
|
||||
bytes.printSchema()
|
||||
return bytes.obj
|
||||
}
|
||||
|
@ -3,19 +3,20 @@ package net.corda.serialization.internal.amqp
|
||||
import junit.framework.TestCase.assertTrue
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import net.corda.core.serialization.ConstructorForDeserialization
|
||||
import net.corda.serialization.internal.amqp.testutils.deserialize
|
||||
import net.corda.serialization.internal.amqp.testutils.serializeAndReturnSchema
|
||||
import net.corda.serialization.internal.amqp.testutils.serialize
|
||||
import net.corda.serialization.internal.amqp.testutils.testDefaultFactoryNoEvolution
|
||||
import net.corda.serialization.internal.amqp.testutils.*
|
||||
import net.corda.serialization.internal.model.ConfigurableLocalTypeModel
|
||||
import net.corda.serialization.internal.model.LocalPropertyInformation
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation
|
||||
import org.junit.Test
|
||||
import org.apache.qpid.proton.amqp.Symbol
|
||||
import org.assertj.core.api.Assertions
|
||||
import java.io.NotSerializableException
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.*
|
||||
|
||||
class PrivatePropertyTests {
|
||||
private val factory = testDefaultFactoryNoEvolution()
|
||||
|
||||
private val registry = TestDescriptorBasedSerializerRegistry()
|
||||
private val factory = testDefaultFactoryNoEvolution(registry)
|
||||
val typeModel = ConfigurableLocalTypeModel(WhitelistBasedTypeModelConfiguration(factory.whitelist, factory))
|
||||
|
||||
@Test
|
||||
fun testWithOnePrivateProperty() {
|
||||
@ -125,21 +126,13 @@ class PrivatePropertyTests {
|
||||
val schemaAndBlob = SerializationOutput(factory).serializeAndReturnSchema(c1)
|
||||
assertEquals(1, schemaAndBlob.schema.types.size)
|
||||
|
||||
val serializersByDescriptor = factory.serializersByDescriptor
|
||||
val typeInformation = typeModel.inspect(C::class.java)
|
||||
assertTrue(typeInformation is LocalTypeInformation.Composable)
|
||||
typeInformation as LocalTypeInformation.Composable
|
||||
|
||||
val schemaDescriptor = schemaAndBlob.schema.types.first().descriptor.name
|
||||
serializersByDescriptor.filterKeys { (it as Symbol) == schemaDescriptor }.values.apply {
|
||||
assertEquals(1, this.size)
|
||||
assertTrue(this.first() is ObjectSerializer)
|
||||
val propertySerializers = (this.first() as ObjectSerializer).propertySerializers.serializationOrder.map { it.serializer }
|
||||
assertEquals(2, propertySerializers.size)
|
||||
// a was public so should have a synthesised getter
|
||||
assertTrue(propertySerializers[0].propertyReader is PublicPropertyReader)
|
||||
|
||||
// b is private and thus won't have teh getter so we'll have reverted
|
||||
// to using reflection to remove the inaccessible property
|
||||
assertTrue(propertySerializers[1].propertyReader is PrivatePropertyReader)
|
||||
}
|
||||
assertEquals(2, typeInformation.properties.size)
|
||||
assertTrue(typeInformation.properties["a"] is LocalPropertyInformation.ConstructorPairedProperty)
|
||||
assertTrue(typeInformation.properties["b"] is LocalPropertyInformation.PrivateConstructorPairedProperty)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -153,22 +146,14 @@ class PrivatePropertyTests {
|
||||
val schemaAndBlob = SerializationOutput(factory).serializeAndReturnSchema(c1)
|
||||
assertEquals(1, schemaAndBlob.schema.types.size)
|
||||
|
||||
val serializersByDescriptor = factory.serializersByDescriptor
|
||||
|
||||
val schemaDescriptor = schemaAndBlob.schema.types.first().descriptor.name
|
||||
serializersByDescriptor.filterKeys { (it as Symbol) == schemaDescriptor }.values.apply {
|
||||
assertEquals(1, this.size)
|
||||
assertTrue(this.first() is ObjectSerializer)
|
||||
val propertySerializers = (this.first() as ObjectSerializer).propertySerializers.serializationOrder.map { it.serializer }
|
||||
assertEquals(2, propertySerializers.size)
|
||||
val typeInformation = typeModel.inspect(C::class.java)
|
||||
assertTrue(typeInformation is LocalTypeInformation.Composable)
|
||||
typeInformation as LocalTypeInformation.Composable
|
||||
|
||||
// as before, a is public so we'll use the getter method
|
||||
assertTrue(propertySerializers[0].propertyReader is PublicPropertyReader)
|
||||
|
||||
// the getB() getter explicitly added means we should use the "normal" public
|
||||
// method reader rather than the private oen
|
||||
assertTrue(propertySerializers[1].propertyReader is PublicPropertyReader)
|
||||
}
|
||||
assertEquals(2, typeInformation.properties.size)
|
||||
assertTrue(typeInformation.properties["a"] is LocalPropertyInformation.ConstructorPairedProperty)
|
||||
assertTrue(typeInformation.properties["b"] is LocalPropertyInformation.ConstructorPairedProperty)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@ -179,9 +164,8 @@ class PrivatePropertyTests {
|
||||
|
||||
val c1 = Outer(Inner(1010101))
|
||||
val output = SerializationOutput(factory).serializeAndReturnSchema(c1)
|
||||
println (output.schema)
|
||||
|
||||
val serializersByDescriptor = factory.serializersByDescriptor
|
||||
val serializersByDescriptor = registry.contents
|
||||
|
||||
// Inner and Outer
|
||||
assertEquals(2, serializersByDescriptor.size)
|
||||
@ -198,24 +182,13 @@ class PrivatePropertyTests {
|
||||
@Test
|
||||
fun allCapsProprtyNotPrivate() {
|
||||
data class C (val CCC: String)
|
||||
val typeInformation = typeModel.inspect(C::class.java)
|
||||
|
||||
val output = SerializationOutput(factory).serializeAndReturnSchema(C("this is nice"))
|
||||
assertTrue(typeInformation is LocalTypeInformation.Composable)
|
||||
typeInformation as LocalTypeInformation.Composable
|
||||
|
||||
val serializersByDescriptor = factory.serializersByDescriptor
|
||||
|
||||
val schemaDescriptor = output.schema.types.first().descriptor.name
|
||||
serializersByDescriptor.filterKeys { (it as Symbol) == schemaDescriptor }.values.apply {
|
||||
assertEquals(1, size)
|
||||
|
||||
assertTrue(this.first() is ObjectSerializer)
|
||||
val propertySerializers = (this.first() as ObjectSerializer).propertySerializers.serializationOrder.map { it.serializer }
|
||||
|
||||
// CCC is the only property to be serialised
|
||||
assertEquals(1, propertySerializers.size)
|
||||
|
||||
// and despite being all caps it should still be a public getter
|
||||
assertTrue(propertySerializers[0].propertyReader is PublicPropertyReader)
|
||||
}
|
||||
assertEquals(1, typeInformation.properties.size)
|
||||
assertTrue(typeInformation.properties["CCC"] is LocalPropertyInformation.ConstructorPairedProperty)
|
||||
}
|
||||
|
||||
}
|
@ -21,7 +21,6 @@ import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.node.serialization.amqp.AMQPServerSerializationScheme
|
||||
import net.corda.nodeapi.internal.crypto.ContentSignerBuilder
|
||||
import net.corda.serialization.internal.*
|
||||
import net.corda.serialization.internal.amqp.SerializerFactory.Companion.isPrimitive
|
||||
import net.corda.serialization.internal.amqp.testutils.*
|
||||
import net.corda.serialization.internal.carpenter.ClassCarpenterImpl
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
@ -210,7 +209,7 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi
|
||||
private fun defaultFactory(): SerializerFactory {
|
||||
return SerializerFactoryBuilder.build(AllWhitelist,
|
||||
ClassCarpenterImpl(AllWhitelist, ClassLoader.getSystemClassLoader()),
|
||||
evolutionSerializerProvider = FailIfEvolutionAttempted
|
||||
allowEvolution = false
|
||||
)
|
||||
}
|
||||
|
||||
@ -258,27 +257,27 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi
|
||||
|
||||
@Test
|
||||
fun isPrimitive() {
|
||||
assertTrue(isPrimitive(Character::class.java))
|
||||
assertTrue(isPrimitive(Boolean::class.java))
|
||||
assertTrue(isPrimitive(Byte::class.java))
|
||||
assertTrue(isPrimitive(UnsignedByte::class.java))
|
||||
assertTrue(isPrimitive(Short::class.java))
|
||||
assertTrue(isPrimitive(UnsignedShort::class.java))
|
||||
assertTrue(isPrimitive(Int::class.java))
|
||||
assertTrue(isPrimitive(UnsignedInteger::class.java))
|
||||
assertTrue(isPrimitive(Long::class.java))
|
||||
assertTrue(isPrimitive(UnsignedLong::class.java))
|
||||
assertTrue(isPrimitive(Float::class.java))
|
||||
assertTrue(isPrimitive(Double::class.java))
|
||||
assertTrue(isPrimitive(Decimal32::class.java))
|
||||
assertTrue(isPrimitive(Decimal64::class.java))
|
||||
assertTrue(isPrimitive(Decimal128::class.java))
|
||||
assertTrue(isPrimitive(Char::class.java))
|
||||
assertTrue(isPrimitive(Date::class.java))
|
||||
assertTrue(isPrimitive(UUID::class.java))
|
||||
assertTrue(isPrimitive(ByteArray::class.java))
|
||||
assertTrue(isPrimitive(String::class.java))
|
||||
assertTrue(isPrimitive(Symbol::class.java))
|
||||
assertTrue(AMQPTypeIdentifiers.isPrimitive(Character::class.java))
|
||||
assertTrue(AMQPTypeIdentifiers.isPrimitive(Boolean::class.java))
|
||||
assertTrue(AMQPTypeIdentifiers.isPrimitive(Byte::class.java))
|
||||
assertTrue(AMQPTypeIdentifiers.isPrimitive(UnsignedByte::class.java))
|
||||
assertTrue(AMQPTypeIdentifiers.isPrimitive(Short::class.java))
|
||||
assertTrue(AMQPTypeIdentifiers.isPrimitive(UnsignedShort::class.java))
|
||||
assertTrue(AMQPTypeIdentifiers.isPrimitive(Int::class.java))
|
||||
assertTrue(AMQPTypeIdentifiers.isPrimitive(UnsignedInteger::class.java))
|
||||
assertTrue(AMQPTypeIdentifiers.isPrimitive(Long::class.java))
|
||||
assertTrue(AMQPTypeIdentifiers.isPrimitive(UnsignedLong::class.java))
|
||||
assertTrue(AMQPTypeIdentifiers.isPrimitive(Float::class.java))
|
||||
assertTrue(AMQPTypeIdentifiers.isPrimitive(Double::class.java))
|
||||
assertTrue(AMQPTypeIdentifiers.isPrimitive(Decimal32::class.java))
|
||||
assertTrue(AMQPTypeIdentifiers.isPrimitive(Decimal64::class.java))
|
||||
assertTrue(AMQPTypeIdentifiers.isPrimitive(Decimal128::class.java))
|
||||
assertTrue(AMQPTypeIdentifiers.isPrimitive(Char::class.java))
|
||||
assertTrue(AMQPTypeIdentifiers.isPrimitive(Date::class.java))
|
||||
assertTrue(AMQPTypeIdentifiers.isPrimitive(UUID::class.java))
|
||||
assertTrue(AMQPTypeIdentifiers.isPrimitive(ByteArray::class.java))
|
||||
assertTrue(AMQPTypeIdentifiers.isPrimitive(String::class.java))
|
||||
assertTrue(AMQPTypeIdentifiers.isPrimitive(Symbol::class.java))
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -475,10 +474,11 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi
|
||||
@Test
|
||||
fun `class constructor is invoked on deserialisation`() {
|
||||
compression == null || return // Manipulation of serialized bytes is invalid if they're compressed.
|
||||
val ser = SerializationOutput(SerializerFactoryBuilder.build(AllWhitelist,
|
||||
val serializerFactory = SerializerFactoryBuilder.build(AllWhitelist,
|
||||
ClassCarpenterImpl(AllWhitelist, ClassLoader.getSystemClassLoader())
|
||||
))
|
||||
val des = DeserializationInput(ser.serializerFactory)
|
||||
)
|
||||
val ser = SerializationOutput(serializerFactory)
|
||||
val des = DeserializationInput(serializerFactory)
|
||||
val serialisedOne = ser.serialize(NonZeroByte(1), compression).bytes
|
||||
val serialisedTwo = ser.serialize(NonZeroByte(2), compression).bytes
|
||||
|
||||
|
@ -1,12 +1,8 @@
|
||||
package net.corda.serialization.internal.amqp
|
||||
|
||||
import net.corda.core.serialization.ConstructorForDeserialization
|
||||
import net.corda.serialization.internal.amqp.testutils.TestSerializationOutput
|
||||
import net.corda.serialization.internal.amqp.testutils.deserialize
|
||||
import net.corda.serialization.internal.amqp.testutils.serializeAndReturnSchema
|
||||
import net.corda.serialization.internal.amqp.testutils.testDefaultFactoryNoEvolution
|
||||
import net.corda.serialization.internal.amqp.testutils.*
|
||||
import org.junit.Test
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.test.assertEquals
|
||||
import org.apache.qpid.proton.amqp.Symbol
|
||||
import java.lang.reflect.Method
|
||||
@ -17,7 +13,8 @@ class SerializationPropertyOrdering {
|
||||
companion object {
|
||||
val VERBOSE get() = false
|
||||
|
||||
val sf = testDefaultFactoryNoEvolution()
|
||||
val registry = TestDescriptorBasedSerializerRegistry()
|
||||
val sf = testDefaultFactoryNoEvolution(registry)
|
||||
}
|
||||
|
||||
// Force object references to be ued to ensure we go through that code path
|
||||
@ -100,25 +97,6 @@ class SerializationPropertyOrdering {
|
||||
assertEquals("e", this.fields[4].name)
|
||||
}
|
||||
|
||||
// Test needs to look at a bunch of private variables, change the access semantics for them
|
||||
val fields : Map<String, java.lang.reflect.Field> = mapOf (
|
||||
"setter" to PropertyAccessorGetterSetter::class.java.getDeclaredField("setter")).apply {
|
||||
this.values.forEach {
|
||||
it.isAccessible = true
|
||||
}
|
||||
}
|
||||
|
||||
val serializersByDescriptor = sf.serializersByDescriptor
|
||||
val schemaDescriptor = output.schema.types.first().descriptor.name
|
||||
|
||||
// make sure that each property accessor has a setter to ensure we're using getter / setter instantiation
|
||||
serializersByDescriptor.filterKeys { (it as Symbol) == schemaDescriptor }.values.apply {
|
||||
assertEquals(1, this.size)
|
||||
assertTrue(this.first() is ObjectSerializer)
|
||||
val propertyAccessors = (this.first() as ObjectSerializer).propertySerializers.serializationOrder as List<PropertyAccessorGetterSetter>
|
||||
propertyAccessors.forEach { property -> assertNotNull(fields["setter"]!!.get(property) as Method?) }
|
||||
}
|
||||
|
||||
val input = DeserializationInput(sf).deserialize(output.obj)
|
||||
assertEquals(100, input.a)
|
||||
assertEquals(200, input.b)
|
||||
|
@ -17,87 +17,4 @@ val TESTING_CONTEXT = SerializationContextImpl(amqpMagic,
|
||||
emptyMap(),
|
||||
true,
|
||||
SerializationContext.UseCase.Testing,
|
||||
null)
|
||||
|
||||
// Test factory that lets us count the number of serializer registration attempts
|
||||
class TestSerializerFactory(
|
||||
wl: ClassWhitelist,
|
||||
cl: ClassLoader
|
||||
) : DefaultSerializerFactory(wl, ClassCarpenterImpl(wl, cl, false), DefaultEvolutionSerializerProvider, ::SerializerFingerPrinter) {
|
||||
var registerCount = 0
|
||||
|
||||
override fun register(customSerializer: CustomSerializer<out Any>) {
|
||||
++registerCount
|
||||
return super.register(customSerializer)
|
||||
}
|
||||
}
|
||||
|
||||
// Instance of our test factory counting registration attempts. Sucks its global, but for testing purposes this
|
||||
// is the easiest way of getting access to the object.
|
||||
val testFactory = TestSerializerFactory(TESTING_CONTEXT.whitelist, TESTING_CONTEXT.deserializationClassLoader)
|
||||
|
||||
// Serializer factory factory, plugs into the SerializationScheme and controls which factory type
|
||||
// we make for each use case. For our tests we need to make sure if its the Testing use case we return
|
||||
// the global factory object created above that counts registrations.
|
||||
class TestSerializerFactoryFactory : SerializerFactoryFactoryImpl() {
|
||||
override fun make(context: SerializationContext) =
|
||||
when (context.useCase) {
|
||||
SerializationContext.UseCase.Testing -> testFactory
|
||||
else -> super.make(context)
|
||||
}
|
||||
}
|
||||
|
||||
class AMQPTestSerializationScheme : AbstractAMQPSerializationScheme(emptySet(), AccessOrderLinkedHashMap { 128 }, TestSerializerFactoryFactory()) {
|
||||
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase) = true
|
||||
}
|
||||
|
||||
// Test SerializationFactory that wraps a serialization scheme that just allows us to call <OBJ>.serialize.
|
||||
// Returns the testing scheme we created above that wraps the testing factory.
|
||||
class TestSerializationFactory : SerializationFactory() {
|
||||
private val scheme = AMQPTestSerializationScheme()
|
||||
|
||||
override fun <T : Any> deserialize(
|
||||
byteSequence: ByteSequence,
|
||||
clazz: Class<T>, context:
|
||||
SerializationContext
|
||||
): T {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun <T : Any> deserializeWithCompatibleContext(
|
||||
byteSequence: ByteSequence,
|
||||
clazz: Class<T>,
|
||||
context: SerializationContext
|
||||
): ObjectWithCompatibleContext<T> {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun <T : Any> serialize(obj: T, context: SerializationContext) = scheme.serialize(obj, context)
|
||||
}
|
||||
|
||||
// The actual test
|
||||
class SerializationSchemaTests {
|
||||
@Test
|
||||
fun onlyRegisterCustomSerializersOnce() {
|
||||
@CordaSerializable
|
||||
data class C(val a: Int)
|
||||
|
||||
val c = C(1)
|
||||
val testSerializationFactory = TestSerializationFactory()
|
||||
val expectedCustomSerializerCount = 41
|
||||
|
||||
assertEquals(0, testFactory.registerCount)
|
||||
c.serialize(testSerializationFactory, TESTING_CONTEXT)
|
||||
assertEquals(expectedCustomSerializerCount, testFactory.registerCount)
|
||||
c.serialize(testSerializationFactory, TESTING_CONTEXT)
|
||||
assertEquals(expectedCustomSerializerCount, testFactory.registerCount)
|
||||
}
|
||||
}
|
||||
null)
|
@ -6,6 +6,7 @@ import net.corda.serialization.internal.AllWhitelist
|
||||
import net.corda.serialization.internal.amqp.testutils.deserialize
|
||||
import net.corda.serialization.internal.carpenter.ClassCarpenterImpl
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import java.io.NotSerializableException
|
||||
import java.lang.reflect.Type
|
||||
@ -44,6 +45,7 @@ class StaticInitialisationOfSerializedObjectTest {
|
||||
C()
|
||||
}
|
||||
|
||||
@Ignore("Suppressing this, as it depends on obtaining internal access to serialiser cache")
|
||||
@Test
|
||||
fun kotlinObjectWithCompanionObject() {
|
||||
data class D(val c: C)
|
||||
@ -63,7 +65,7 @@ class StaticInitialisationOfSerializedObjectTest {
|
||||
|
||||
// build a serializer for type D without an instance of it to serialise, since
|
||||
// we can't actually construct one
|
||||
sf.get(null, D::class.java)
|
||||
sf.get(D::class.java)
|
||||
|
||||
// post creation of the serializer we should have two elements in the map, this
|
||||
// proves we didn't statically construct an instance of C when building the serializer
|
||||
|
@ -18,20 +18,45 @@ import java.io.File.separatorChar
|
||||
import java.io.NotSerializableException
|
||||
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
|
||||
|
||||
fun testDefaultFactory() = SerializerFactoryBuilder.build(AllWhitelist,
|
||||
ClassCarpenterImpl(AllWhitelist, ClassLoader.getSystemClassLoader())
|
||||
)
|
||||
/**
|
||||
* For tests that want to see inside the serializer registry
|
||||
*/
|
||||
class TestDescriptorBasedSerializerRegistry : DescriptorBasedSerializerRegistry {
|
||||
val contents = mutableMapOf<String, AMQPSerializer<Any>>()
|
||||
|
||||
fun testDefaultFactoryNoEvolution(): SerializerFactory {
|
||||
return SerializerFactoryBuilder.build(
|
||||
AllWhitelist,
|
||||
ClassCarpenterImpl(AllWhitelist, ClassLoader.getSystemClassLoader()),
|
||||
FailIfEvolutionAttempted)
|
||||
override fun get(descriptor: String): AMQPSerializer<Any>? = contents[descriptor]
|
||||
|
||||
override fun set(descriptor: String, serializer: AMQPSerializer<Any>) {
|
||||
contents.putIfAbsent(descriptor, serializer)
|
||||
}
|
||||
|
||||
override fun getOrBuild(descriptor: String, builder: () -> AMQPSerializer<Any>): AMQPSerializer<Any> =
|
||||
get(descriptor) ?: builder().also { set(descriptor, it) }
|
||||
}
|
||||
|
||||
fun testDefaultFactoryWithWhitelist() = SerializerFactoryBuilder.build(EmptyWhitelist,
|
||||
ClassCarpenterImpl(EmptyWhitelist, ClassLoader.getSystemClassLoader())
|
||||
)
|
||||
@JvmOverloads
|
||||
fun testDefaultFactory(descriptorBasedSerializerRegistry: DescriptorBasedSerializerRegistry =
|
||||
DefaultDescriptorBasedSerializerRegistry()) =
|
||||
SerializerFactoryBuilder.build(
|
||||
AllWhitelist,
|
||||
ClassCarpenterImpl(AllWhitelist, ClassLoader.getSystemClassLoader()),
|
||||
descriptorBasedSerializerRegistry = descriptorBasedSerializerRegistry)
|
||||
|
||||
@JvmOverloads
|
||||
fun testDefaultFactoryNoEvolution(descriptorBasedSerializerRegistry: DescriptorBasedSerializerRegistry =
|
||||
DefaultDescriptorBasedSerializerRegistry()): SerializerFactory =
|
||||
SerializerFactoryBuilder.build(
|
||||
AllWhitelist,
|
||||
ClassCarpenterImpl(AllWhitelist, ClassLoader.getSystemClassLoader()),
|
||||
descriptorBasedSerializerRegistry = descriptorBasedSerializerRegistry,
|
||||
allowEvolution = false)
|
||||
|
||||
@JvmOverloads
|
||||
fun testDefaultFactoryWithWhitelist(descriptorBasedSerializerRegistry: DescriptorBasedSerializerRegistry =
|
||||
DefaultDescriptorBasedSerializerRegistry()) =
|
||||
SerializerFactoryBuilder.build(EmptyWhitelist,
|
||||
ClassCarpenterImpl(EmptyWhitelist, ClassLoader.getSystemClassLoader()),
|
||||
descriptorBasedSerializerRegistry = descriptorBasedSerializerRegistry)
|
||||
|
||||
class TestSerializationOutput(
|
||||
private val verbose: Boolean,
|
||||
|
@ -1,101 +0,0 @@
|
||||
package net.corda.serialization.internal.carpenter
|
||||
|
||||
import net.corda.core.serialization.SerializableCalculatedProperty
|
||||
import net.corda.serialization.internal.AllWhitelist
|
||||
import net.corda.serialization.internal.amqp.CompositeType
|
||||
import net.corda.serialization.internal.amqp.DeserializationInput
|
||||
import net.corda.serialization.internal.amqp.testutils.deserializeAndReturnEnvelope
|
||||
import net.corda.serialization.internal.amqp.testutils.testDefaultFactoryNoEvolution
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class CalculatedValuesToClassCarpenterTests : AmqpCarpenterBase(AllWhitelist) {
|
||||
|
||||
interface Parent {
|
||||
@get:SerializableCalculatedProperty
|
||||
val doubled: Int
|
||||
}
|
||||
|
||||
@Test
|
||||
fun calculatedValues() {
|
||||
data class C(val i: Int): Parent {
|
||||
@get:SerializableCalculatedProperty
|
||||
val squared = (i * i).toString()
|
||||
|
||||
override val doubled get() = i * 2
|
||||
}
|
||||
|
||||
val factory = testDefaultFactoryNoEvolution()
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(C(2)))
|
||||
val amqpObj = obj.obj
|
||||
val serSchema = obj.envelope.schema
|
||||
|
||||
assertEquals(2, amqpObj.i)
|
||||
assertEquals("4", amqpObj.squared)
|
||||
assertEquals(2, serSchema.types.size)
|
||||
require(serSchema.types[0] is CompositeType)
|
||||
|
||||
val concrete = serSchema.types[0] as CompositeType
|
||||
assertEquals(3, concrete.fields.size)
|
||||
assertEquals("doubled", concrete.fields[0].name)
|
||||
assertEquals("int", concrete.fields[0].type)
|
||||
assertEquals("i", concrete.fields[1].name)
|
||||
assertEquals("int", concrete.fields[1].type)
|
||||
assertEquals("squared", concrete.fields[2].name)
|
||||
assertEquals("string", concrete.fields[2].type)
|
||||
|
||||
val l1 = serSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
|
||||
assertEquals(0, l1.size)
|
||||
val mangleSchema = serSchema.mangleNames(listOf((classTestName("C"))))
|
||||
val l2 = mangleSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
|
||||
val aName = mangleName(classTestName("C"))
|
||||
|
||||
assertEquals(1, l2.size)
|
||||
val aSchema = l2.carpenterSchemas.find { it.name == aName }!!
|
||||
|
||||
val pinochio = ClassCarpenterImpl(whitelist = AllWhitelist).build(aSchema)
|
||||
val p = pinochio.constructors[0].newInstance(4, 2, "4")
|
||||
|
||||
assertEquals(pinochio.getMethod("getI").invoke(p), amqpObj.i)
|
||||
assertEquals(pinochio.getMethod("getSquared").invoke(p), amqpObj.squared)
|
||||
assertEquals(pinochio.getMethod("getDoubled").invoke(p), amqpObj.doubled)
|
||||
|
||||
val upcast = p as Parent
|
||||
assertEquals(upcast.doubled, amqpObj.doubled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun implementingClassDoesNotCalculateValue() {
|
||||
class C(override val doubled: Int): Parent
|
||||
|
||||
val factory = testDefaultFactoryNoEvolution()
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(C(5)))
|
||||
val amqpObj = obj.obj
|
||||
val serSchema = obj.envelope.schema
|
||||
|
||||
assertEquals(2, serSchema.types.size)
|
||||
require(serSchema.types[0] is CompositeType)
|
||||
|
||||
val concrete = serSchema.types[0] as CompositeType
|
||||
assertEquals(1, concrete.fields.size)
|
||||
assertEquals("doubled", concrete.fields[0].name)
|
||||
assertEquals("int", concrete.fields[0].type)
|
||||
|
||||
val l1 = serSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
|
||||
assertEquals(0, l1.size)
|
||||
val mangleSchema = serSchema.mangleNames(listOf((classTestName("C"))))
|
||||
val l2 = mangleSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
|
||||
val aName = mangleName(classTestName("C"))
|
||||
|
||||
assertEquals(1, l2.size)
|
||||
val aSchema = l2.carpenterSchemas.find { it.name == aName }!!
|
||||
|
||||
val pinochio = ClassCarpenterImpl(whitelist = AllWhitelist).build(aSchema)
|
||||
val p = pinochio.constructors[0].newInstance(5)
|
||||
|
||||
assertEquals(pinochio.getMethod("getDoubled").invoke(p), amqpObj.doubled)
|
||||
|
||||
val upcast = p as Parent
|
||||
assertEquals(upcast.doubled, amqpObj.doubled)
|
||||
}
|
||||
}
|
@ -27,7 +27,7 @@ class ClassCarpenterTest {
|
||||
|
||||
@Test
|
||||
fun empty() {
|
||||
val clazz = cc.build(ClassSchema("gen.EmptyClass", emptyMap(), null))
|
||||
val clazz = cc.build(ClassSchema("gen.EmptyClass", emptyMap()))
|
||||
assertEquals(0, clazz.nonSyntheticFields.size)
|
||||
assertEquals(2, clazz.nonSyntheticMethods.size) // get, toString
|
||||
assertEquals(0, clazz.declaredConstructors[0].parameterCount)
|
||||
@ -97,8 +97,8 @@ class ClassCarpenterTest {
|
||||
|
||||
@Test(expected = DuplicateNameException::class)
|
||||
fun duplicates() {
|
||||
cc.build(ClassSchema("gen.EmptyClass", emptyMap(), null))
|
||||
cc.build(ClassSchema("gen.EmptyClass", emptyMap(), null))
|
||||
cc.build(ClassSchema("gen.EmptyClass", emptyMap()))
|
||||
cc.build(ClassSchema("gen.EmptyClass", emptyMap()))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1,41 +1,14 @@
|
||||
package net.corda.serialization.internal.carpenter
|
||||
|
||||
import com.google.common.reflect.TypeToken
|
||||
import net.corda.core.serialization.ClassWhitelist
|
||||
import net.corda.core.serialization.SerializedBytes
|
||||
import net.corda.serialization.internal.amqp.*
|
||||
import net.corda.serialization.internal.amqp.Field
|
||||
import net.corda.serialization.internal.amqp.Schema
|
||||
import net.corda.serialization.internal.amqp.testutils.deserializeAndReturnEnvelope
|
||||
import net.corda.serialization.internal.amqp.testutils.serialize
|
||||
import net.corda.serialization.internal.amqp.testutils.testName
|
||||
|
||||
fun mangleName(name: String) = "${name}__carpenter"
|
||||
|
||||
/**
|
||||
* given a list of class names work through the amqp envelope schema and alter any that
|
||||
* match in the fashion defined above
|
||||
*/
|
||||
fun Schema.mangleNames(names: List<String>): Schema {
|
||||
val newTypes: MutableList<TypeNotation> = mutableListOf()
|
||||
|
||||
for (type in types) {
|
||||
val newName = if (type.name in names) mangleName(type.name) else type.name
|
||||
val newProvides = type.provides.map { if (it in names) mangleName(it) else it }
|
||||
val newFields = mutableListOf<Field>()
|
||||
|
||||
(type as CompositeType).fields.forEach {
|
||||
val fieldType = if (it.type in names) mangleName(it.type) else it.type
|
||||
val requires =
|
||||
if (it.requires.isNotEmpty() && (it.requires[0] in names)) listOf(mangleName(it.requires[0]))
|
||||
else it.requires
|
||||
|
||||
newFields.add(it.copy(type = fieldType, requires = requires))
|
||||
}
|
||||
|
||||
newTypes.add(type.copy(name = newName, provides = newProvides, fields = newFields))
|
||||
}
|
||||
|
||||
return Schema(types = newTypes)
|
||||
}
|
||||
import net.corda.serialization.internal.model.*
|
||||
import org.junit.Assert.assertTrue
|
||||
|
||||
/**
|
||||
* Custom implementation of a [SerializerFactory] where we need to give it a class carpenter
|
||||
@ -48,7 +21,78 @@ open class AmqpCarpenterBase(whitelist: ClassWhitelist) {
|
||||
var cc = ClassCarpenterImpl(whitelist = whitelist)
|
||||
var factory = serializerFactoryExternalCarpenter(cc)
|
||||
|
||||
fun <T: Any> serialise(obj: T): SerializedBytes<T> = SerializationOutput(factory).serialize(obj)
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun classTestName(clazz: String) = "${this.javaClass.name}\$${testName()}\$$clazz"
|
||||
protected val remoteTypeModel = AMQPRemoteTypeModel()
|
||||
protected val typeLoader = ClassCarpentingTypeLoader(SchemaBuildingRemoteTypeCarpenter(cc), cc.classloader)
|
||||
|
||||
protected inline fun <reified T: Any> T.roundTrip(): ObjectAndEnvelope<T> =
|
||||
DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(this))
|
||||
|
||||
protected val Envelope.typeInformation: Map<TypeDescriptor, RemoteTypeInformation> get() =
|
||||
remoteTypeModel.interpret(SerializationSchemas(schema, transformsSchema))
|
||||
|
||||
protected inline fun <reified T: Any> Envelope.typeInformationFor(): RemoteTypeInformation {
|
||||
val interpreted = typeInformation
|
||||
val type = object : TypeToken<T>() {}.type
|
||||
return interpreted.values.find { it.typeIdentifier == TypeIdentifier.forGenericType(type) }
|
||||
as RemoteTypeInformation
|
||||
}
|
||||
|
||||
protected inline fun <reified T: Any> Envelope.getMangled(): RemoteTypeInformation =
|
||||
typeInformationFor<T>().mangle<T>()
|
||||
|
||||
protected fun <T: Any> serialise(obj: T): SerializedBytes<T> = SerializationOutput(factory).serialize(obj)
|
||||
|
||||
protected inline fun <reified T: Any> RemoteTypeInformation.mangle(): RemoteTypeInformation {
|
||||
val from = TypeIdentifier.forGenericType(object : TypeToken<T>() {}.type)
|
||||
return rename(from, from.mangle())
|
||||
}
|
||||
|
||||
protected fun TypeIdentifier.mangle(): TypeIdentifier = when(this) {
|
||||
is TypeIdentifier.Unparameterised -> copy(name = name + "_carpenter")
|
||||
is TypeIdentifier.Parameterised -> copy(name = name + "_carpenter")
|
||||
is TypeIdentifier.Erased -> copy(name = name + "_carpenter")
|
||||
is TypeIdentifier.ArrayOf -> copy(componentType = componentType.mangle())
|
||||
else -> this
|
||||
}
|
||||
|
||||
protected fun TypeIdentifier.rename(from: TypeIdentifier, to: TypeIdentifier): TypeIdentifier = when(this) {
|
||||
from -> to.rename(from, to)
|
||||
is TypeIdentifier.Parameterised -> copy(parameters = parameters.map { it.rename(from, to) })
|
||||
is TypeIdentifier.ArrayOf -> copy(componentType = componentType.rename(from, to))
|
||||
else -> this
|
||||
}
|
||||
|
||||
protected fun RemoteTypeInformation.rename(from: TypeIdentifier, to: TypeIdentifier): RemoteTypeInformation = when(this) {
|
||||
is RemoteTypeInformation.Composable -> copy(
|
||||
typeIdentifier = typeIdentifier.rename(from, to),
|
||||
properties = properties.mapValues { (_, property) -> property.copy(type = property.type.rename(from, to)) },
|
||||
interfaces = interfaces.map { it.rename(from, to) },
|
||||
typeParameters = typeParameters.map { it.rename(from, to) })
|
||||
is RemoteTypeInformation.Unparameterised -> copy(typeIdentifier = typeIdentifier.rename(from, to))
|
||||
is RemoteTypeInformation.Parameterised -> copy(
|
||||
typeIdentifier = typeIdentifier.rename(from, to),
|
||||
typeParameters = typeParameters.map { it.rename(from, to) })
|
||||
is RemoteTypeInformation.AnInterface -> copy(
|
||||
typeIdentifier = typeIdentifier.rename(from, to),
|
||||
properties = properties.mapValues { (_, property) -> property.copy(type = property.type.rename(from, to)) },
|
||||
interfaces = interfaces.map { it.rename(from, to) },
|
||||
typeParameters = typeParameters.map { it.rename(from, to) })
|
||||
is RemoteTypeInformation.AnArray -> copy(componentType = componentType.rename(from, to))
|
||||
is RemoteTypeInformation.AnEnum -> copy(
|
||||
typeIdentifier = typeIdentifier.rename(from, to))
|
||||
else -> this
|
||||
}
|
||||
|
||||
protected fun RemoteTypeInformation.load(): Class<*> =
|
||||
typeLoader.load(listOf(this))[typeIdentifier]!!.asClass()
|
||||
|
||||
protected fun assertCanLoadAll(vararg types: RemoteTypeInformation) {
|
||||
assertTrue(typeLoader.load(types.asList()).keys.containsAll(types.map { it.typeIdentifier }))
|
||||
}
|
||||
|
||||
protected fun Class<*>.new(vararg constructorParams: Any?) =
|
||||
constructors[0].newInstance(*constructorParams)!!
|
||||
|
||||
protected fun Any.get(propertyName: String): Any =
|
||||
this::class.java.getMethod("get${propertyName.capitalize()}").invoke(this)
|
||||
}
|
||||
|
@ -2,13 +2,11 @@ package net.corda.serialization.internal.carpenter
|
||||
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.serialization.internal.AllWhitelist
|
||||
import net.corda.serialization.internal.amqp.CompositeType
|
||||
import net.corda.serialization.internal.amqp.DeserializationInput
|
||||
import net.corda.serialization.internal.amqp.testutils.deserializeAndReturnEnvelope
|
||||
import org.junit.Test
|
||||
import java.io.NotSerializableException
|
||||
import java.util.*
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
@CordaSerializable
|
||||
interface I_ {
|
||||
@ -16,258 +14,96 @@ interface I_ {
|
||||
}
|
||||
|
||||
class CompositeMembers : AmqpCarpenterBase(AllWhitelist) {
|
||||
@Test
|
||||
fun bothKnown() {
|
||||
val testA = 10
|
||||
val testB = 20
|
||||
|
||||
@Test
|
||||
fun parentIsUnknown() {
|
||||
@CordaSerializable
|
||||
data class A(val a: Int)
|
||||
|
||||
@CordaSerializable
|
||||
data class B(val a: A, var b: Int)
|
||||
|
||||
val b = B(A(testA), testB)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b))
|
||||
val (_, envelope) = B(A(10), 20).roundTrip()
|
||||
|
||||
val amqpObj = obj.obj
|
||||
|
||||
assertEquals(testB, amqpObj.b)
|
||||
assertEquals(testA, amqpObj.a.a)
|
||||
assertEquals(2, obj.envelope.schema.types.size)
|
||||
require(obj.envelope.schema.types[0] is CompositeType)
|
||||
require(obj.envelope.schema.types[1] is CompositeType)
|
||||
|
||||
var amqpSchemaA: CompositeType? = null
|
||||
var amqpSchemaB: CompositeType? = null
|
||||
|
||||
for (type in obj.envelope.schema.types) {
|
||||
when (type.name.split("$").last()) {
|
||||
"A" -> amqpSchemaA = type as CompositeType
|
||||
"B" -> amqpSchemaB = type as CompositeType
|
||||
}
|
||||
}
|
||||
|
||||
require(amqpSchemaA != null)
|
||||
require(amqpSchemaB != null)
|
||||
|
||||
// Just ensure the amqp schema matches what we want before we go messing
|
||||
// around with the internals
|
||||
assertEquals(1, amqpSchemaA?.fields?.size)
|
||||
assertEquals("a", amqpSchemaA!!.fields[0].name)
|
||||
assertEquals("int", amqpSchemaA.fields[0].type)
|
||||
|
||||
assertEquals(2, amqpSchemaB?.fields?.size)
|
||||
assertEquals("a", amqpSchemaB!!.fields[0].name)
|
||||
assertEquals(classTestName("A"), amqpSchemaB.fields[0].type)
|
||||
assertEquals("b", amqpSchemaB.fields[1].name)
|
||||
assertEquals("int", amqpSchemaB.fields[1].type)
|
||||
|
||||
val metaSchema = obj.envelope.schema.carpenterSchema(ClassLoader.getSystemClassLoader())
|
||||
|
||||
// if we know all the classes there is nothing to really achieve here
|
||||
require(metaSchema.carpenterSchemas.isEmpty())
|
||||
require(metaSchema.dependsOn.isEmpty())
|
||||
require(metaSchema.dependencies.isEmpty())
|
||||
// We load an unknown class, B_mangled, which includes a reference to a known class, A.
|
||||
assertCanLoadAll(envelope.getMangled<B>())
|
||||
}
|
||||
|
||||
// you cannot have an element of a composite class we know about
|
||||
// that is unknown as that should be impossible. If we have the containing
|
||||
// class in the class path then we must have all of it's constituent elements
|
||||
@Test(expected = UncarpentableException::class)
|
||||
fun nestedIsUnknown() {
|
||||
val testA = 10
|
||||
val testB = 20
|
||||
|
||||
@Test
|
||||
fun bothAreUnknown() {
|
||||
@CordaSerializable
|
||||
data class A(override val a: Int) : I_
|
||||
|
||||
@CordaSerializable
|
||||
data class B(val a: A, var b: Int)
|
||||
|
||||
val b = B(A(testA), testB)
|
||||
val (_, envelope) = B(A(10), 20).roundTrip()
|
||||
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b))
|
||||
val amqpSchema = obj.envelope.schema.mangleNames(listOf(classTestName("A")))
|
||||
|
||||
amqpSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
|
||||
// We load an unknown class, B_mangled, which includes a reference to an unknown class, A_mangled.
|
||||
// For this to work, we must include A_mangled in our set of classes to load.
|
||||
assertCanLoadAll(envelope.getMangled<B>().mangle<A>(), envelope.getMangled<A>())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun ParentIsUnknown() {
|
||||
val testA = 10
|
||||
val testB = 20
|
||||
|
||||
fun oneIsUnknown() {
|
||||
@CordaSerializable
|
||||
data class A(override val a: Int) : I_
|
||||
|
||||
@CordaSerializable
|
||||
data class B(val a: A, var b: Int)
|
||||
|
||||
val b = B(A(testA), testB)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b))
|
||||
val (_, envelope) = B(A(10), 20).roundTrip()
|
||||
|
||||
val amqpSchema = obj.envelope.schema.mangleNames(listOf(classTestName("B")))
|
||||
val carpenterSchema = amqpSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
|
||||
// We load an unknown class, B_mangled, which includes a reference to an unknown class, A_mangled.
|
||||
// This will fail, because A_mangled is not included in our set of classes to load.
|
||||
assertFailsWith<NotSerializableException> { assertCanLoadAll(envelope.getMangled<B>().mangle<A>()) }
|
||||
}
|
||||
|
||||
assertEquals(1, carpenterSchema.size)
|
||||
// See https://github.com/corda/corda/issues/4107
|
||||
@Test
|
||||
fun withUUID() {
|
||||
@CordaSerializable
|
||||
data class IOUStateData(
|
||||
val value: Int,
|
||||
val ref: UUID,
|
||||
val newValue: String? = null
|
||||
)
|
||||
|
||||
val metaCarpenter = MetaCarpenter(carpenterSchema, ClassCarpenterImpl(whitelist = AllWhitelist))
|
||||
|
||||
metaCarpenter.build()
|
||||
|
||||
require(mangleName(classTestName("B")) in metaCarpenter.objects)
|
||||
val uuid = UUID.randomUUID()
|
||||
val(_, envelope) = IOUStateData(10, uuid, "new value").roundTrip()
|
||||
val recarpented = envelope.getMangled<IOUStateData>().load()
|
||||
val instance = recarpented.new(null, uuid, 10)
|
||||
assertEquals(uuid, instance.get("ref"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun BothUnknown() {
|
||||
val testA = 10
|
||||
val testB = 20
|
||||
fun mapWithUnknown() {
|
||||
data class C(val a: Int)
|
||||
data class D(val m: Map<String, C>)
|
||||
val (_, envelope) = D(mapOf("c" to C(1))).roundTrip()
|
||||
|
||||
@CordaSerializable
|
||||
data class A(override val a: Int) : I_
|
||||
val infoForD = envelope.typeInformationFor<D>().mangle<C>()
|
||||
val mangledMap = envelope.typeInformation.values.find { it.typeIdentifier.name == "java.util.Map" }!!.mangle<C>()
|
||||
val mangledC = envelope.getMangled<C>()
|
||||
|
||||
@CordaSerializable
|
||||
data class B(val a: A, var b: Int)
|
||||
assertEquals(
|
||||
"java.util.Map<java.lang.String, ${mangledC.typeIdentifier.prettyPrint(false)}>",
|
||||
mangledMap.prettyPrint(false))
|
||||
|
||||
val b = B(A(testA), testB)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b))
|
||||
val amqpSchema = obj.envelope.schema.mangleNames(listOf(classTestName("A"), classTestName("B")))
|
||||
val carpenterSchema = amqpSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
|
||||
|
||||
// just verify we're in the expected initial state, A is carpentable, B is not because
|
||||
// it depends on A and the dependency chains are in place
|
||||
assertEquals(1, carpenterSchema.size)
|
||||
assertEquals(mangleName(classTestName("A")), carpenterSchema.carpenterSchemas.first().name)
|
||||
assertEquals(1, carpenterSchema.dependencies.size)
|
||||
require(mangleName(classTestName("B")) in carpenterSchema.dependencies)
|
||||
assertEquals(1, carpenterSchema.dependsOn.size)
|
||||
require(mangleName(classTestName("A")) in carpenterSchema.dependsOn)
|
||||
|
||||
val metaCarpenter = TestMetaCarpenter(carpenterSchema, ClassCarpenterImpl(whitelist = AllWhitelist))
|
||||
|
||||
assertEquals(0, metaCarpenter.objects.size)
|
||||
|
||||
// first iteration, carpent A, resolve deps and mark B as carpentable
|
||||
metaCarpenter.build()
|
||||
|
||||
// one build iteration should have carpetned up A and worked out that B is now buildable
|
||||
// given it's depedencies have been satisfied
|
||||
assertTrue(mangleName(classTestName("A")) in metaCarpenter.objects)
|
||||
assertFalse(mangleName(classTestName("B")) in metaCarpenter.objects)
|
||||
|
||||
assertEquals(1, carpenterSchema.carpenterSchemas.size)
|
||||
assertEquals(mangleName(classTestName("B")), carpenterSchema.carpenterSchemas.first().name)
|
||||
assertTrue(carpenterSchema.dependencies.isEmpty())
|
||||
assertTrue(carpenterSchema.dependsOn.isEmpty())
|
||||
|
||||
// second manual iteration, will carpent B
|
||||
metaCarpenter.build()
|
||||
require(mangleName(classTestName("A")) in metaCarpenter.objects)
|
||||
require(mangleName(classTestName("B")) in metaCarpenter.objects)
|
||||
|
||||
// and we must be finished
|
||||
assertTrue(carpenterSchema.carpenterSchemas.isEmpty())
|
||||
assertCanLoadAll(infoForD, mangledMap, mangledC)
|
||||
}
|
||||
|
||||
@Test(expected = UncarpentableException::class)
|
||||
@Suppress("UNUSED")
|
||||
fun nestedIsUnknownInherited() {
|
||||
val testA = 10
|
||||
val testB = 20
|
||||
val testC = 30
|
||||
|
||||
@CordaSerializable
|
||||
open class A(val a: Int)
|
||||
|
||||
@CordaSerializable
|
||||
class B(a: Int, var b: Int) : A(a)
|
||||
|
||||
@CordaSerializable
|
||||
data class C(val b: B, var c: Int)
|
||||
|
||||
val c = C(B(testA, testB), testC)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(c))
|
||||
|
||||
val amqpSchema = obj.envelope.schema.mangleNames(listOf(classTestName("A"), classTestName("B")))
|
||||
|
||||
amqpSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
|
||||
}
|
||||
|
||||
@Test(expected = UncarpentableException::class)
|
||||
@Suppress("UNUSED")
|
||||
fun nestedIsUnknownInheritedUnknown() {
|
||||
val testA = 10
|
||||
val testB = 20
|
||||
val testC = 30
|
||||
|
||||
@CordaSerializable
|
||||
open class A(val a: Int)
|
||||
|
||||
@CordaSerializable
|
||||
class B(a: Int, var b: Int) : A(a)
|
||||
|
||||
@CordaSerializable
|
||||
data class C(val b: B, var c: Int)
|
||||
|
||||
val c = C(B(testA, testB), testC)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(c))
|
||||
|
||||
val amqpSchema = obj.envelope.schema.mangleNames(listOf(classTestName("A"), classTestName("B")))
|
||||
|
||||
amqpSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
|
||||
}
|
||||
|
||||
@Suppress("UNUSED")
|
||||
@Test(expected = UncarpentableException::class)
|
||||
fun parentsIsUnknownWithUnknownInheritedMember() {
|
||||
val testA = 10
|
||||
val testB = 20
|
||||
val testC = 30
|
||||
|
||||
@CordaSerializable
|
||||
open class A(val a: Int)
|
||||
|
||||
@CordaSerializable
|
||||
class B(a: Int, var b: Int) : A(a)
|
||||
|
||||
@CordaSerializable
|
||||
data class C(val b: B, var c: Int)
|
||||
|
||||
val c = C(B(testA, testB), testC)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(c))
|
||||
|
||||
val carpenterSchema = obj.envelope.schema.mangleNames(listOf(classTestName("A"), classTestName("B")))
|
||||
TestMetaCarpenter(carpenterSchema.carpenterSchema(
|
||||
ClassLoader.getSystemClassLoader()), ClassCarpenterImpl(whitelist = AllWhitelist))
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO serializer doesn't support inheritnace at the moment, when it does this should work
|
||||
@Test
|
||||
fun `inheritance`() {
|
||||
val testA = 10
|
||||
val testB = 20
|
||||
fun parameterisedNonCollectionWithUnknown() {
|
||||
data class C(val a: Int)
|
||||
data class NotAMap<K, V>(val key: K, val value: V)
|
||||
data class D(val m: NotAMap<String, C>)
|
||||
val (_, envelope) = D(NotAMap("c" , C(1))).roundTrip()
|
||||
|
||||
@CordaSerializable
|
||||
open class A(open val a: Int)
|
||||
val infoForD = envelope.typeInformationFor<D>().mangle<C>()
|
||||
val mangledNotAMap = envelope.typeInformationFor<NotAMap<String, C>>().mangle<C>()
|
||||
val mangledC = envelope.getMangled<C>()
|
||||
|
||||
@CordaSerializable
|
||||
class B(override val a: Int, val b: Int) : A (a)
|
||||
|
||||
val b = B(testA, testB)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b))
|
||||
|
||||
require(obj.obj is B)
|
||||
|
||||
val carpenterSchema = obj.envelope.schema.mangleNames(listOf(classTestName("A"), classTestName("B")))
|
||||
val metaCarpenter = TestMetaCarpenter(carpenterSchema.carpenterSchema())
|
||||
|
||||
assertEquals(1, metaCarpenter.schemas.carpenterSchemas.size)
|
||||
assertEquals(mangleNames(classTestName("B")), metaCarpenter.schemas.carpenterSchemas.first().name)
|
||||
assertEquals(1, metaCarpenter.schemas.dependencies.size)
|
||||
assertTrue(mangleNames(classTestName("A")) in metaCarpenter.schemas.dependencies)
|
||||
assertCanLoadAll(infoForD, mangledNotAMap, mangledC)
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
|
@ -2,10 +2,9 @@ package net.corda.serialization.internal.carpenter
|
||||
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.serialization.internal.AllWhitelist
|
||||
import net.corda.serialization.internal.amqp.DeserializationInput
|
||||
import org.junit.Test
|
||||
import kotlin.test.*
|
||||
import net.corda.serialization.internal.amqp.testutils.deserializeAndReturnEnvelope
|
||||
import java.io.NotSerializableException
|
||||
|
||||
@CordaSerializable
|
||||
interface J {
|
||||
@ -39,172 +38,68 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhitelist) {
|
||||
fun interfaceParent1() {
|
||||
class A(override val j: Int) : J
|
||||
|
||||
val testJ = 20
|
||||
val a = A(testJ)
|
||||
val (_, env) = A(20).roundTrip()
|
||||
val mangledA = env.getMangled<A>()
|
||||
|
||||
assertEquals(testJ, a.j)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
|
||||
val serSchema = obj.envelope.schema
|
||||
assertEquals(2, serSchema.types.size)
|
||||
val l1 = serSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
|
||||
val carpentedA = mangledA.load()
|
||||
val carpentedInstance = carpentedA.new(20)
|
||||
|
||||
// since we're using an envelope generated by seilaising classes defined locally
|
||||
// it's extremely unlikely we'd need to carpent any classes
|
||||
assertEquals(0, l1.size)
|
||||
assertEquals(20, carpentedInstance.get("j"))
|
||||
|
||||
val mangleSchema = serSchema.mangleNames(listOf(classTestName("A")))
|
||||
val l2 = mangleSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
|
||||
assertEquals(1, l2.size)
|
||||
|
||||
val aSchema = l2.carpenterSchemas.find { it.name == mangleName(classTestName("A")) }
|
||||
assertNotEquals(null, aSchema)
|
||||
assertEquals(mangleName(classTestName("A")), aSchema!!.name)
|
||||
assertEquals(1, aSchema.interfaces.size)
|
||||
assertEquals(J::class.java, aSchema.interfaces[0])
|
||||
|
||||
val aBuilder = ClassCarpenterImpl(whitelist = AllWhitelist).build(aSchema)
|
||||
val objJ = aBuilder.constructors[0].newInstance(testJ)
|
||||
val j = objJ as J
|
||||
|
||||
assertEquals(aBuilder.getMethod("getJ").invoke(objJ), testJ)
|
||||
assertEquals(a.j, j.j)
|
||||
val asJ = carpentedInstance as J
|
||||
assertEquals(20, asJ.j)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun interfaceParent2() {
|
||||
class A(override val j: Int, val jj: Int) : J
|
||||
|
||||
val testJ = 20
|
||||
val testJJ = 40
|
||||
val a = A(testJ, testJJ)
|
||||
val (_, env) = A(23, 42).roundTrip()
|
||||
val carpentedA = env.getMangled<A>().load()
|
||||
val carpetedInstance = carpentedA.constructors[0].newInstance(23, 42)
|
||||
|
||||
assertEquals(testJ, a.j)
|
||||
assertEquals(testJJ, a.jj)
|
||||
assertEquals(23, carpetedInstance.get("j"))
|
||||
assertEquals(42, carpetedInstance.get("jj"))
|
||||
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
|
||||
|
||||
val serSchema = obj.envelope.schema
|
||||
|
||||
assertEquals(2, serSchema.types.size)
|
||||
|
||||
val l1 = serSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
|
||||
|
||||
assertEquals(0, l1.size)
|
||||
|
||||
val mangleSchema = serSchema.mangleNames(listOf(classTestName("A")))
|
||||
val aName = mangleName(classTestName("A"))
|
||||
val l2 = mangleSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
|
||||
|
||||
assertEquals(1, l2.size)
|
||||
|
||||
val aSchema = l2.carpenterSchemas.find { it.name == aName }
|
||||
|
||||
assertNotEquals(null, aSchema)
|
||||
|
||||
assertEquals(aName, aSchema!!.name)
|
||||
assertEquals(1, aSchema.interfaces.size)
|
||||
assertEquals(J::class.java, aSchema.interfaces[0])
|
||||
|
||||
val aBuilder = ClassCarpenterImpl(whitelist = AllWhitelist).build(aSchema)
|
||||
val objJ = aBuilder.constructors[0].newInstance(testJ, testJJ)
|
||||
val j = objJ as J
|
||||
|
||||
assertEquals(aBuilder.getMethod("getJ").invoke(objJ), testJ)
|
||||
assertEquals(aBuilder.getMethod("getJj").invoke(objJ), testJJ)
|
||||
|
||||
assertEquals(a.j, j.j)
|
||||
val asJ = carpetedInstance as J
|
||||
assertEquals(23, asJ.j)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun multipleInterfaces() {
|
||||
val testI = 20
|
||||
val testII = 40
|
||||
|
||||
class A(override val i: Int, override val ii: Int) : I, II
|
||||
|
||||
val a = A(testI, testII)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
|
||||
val (_, env) = A(23, 42).roundTrip()
|
||||
val carpentedA = env.getMangled<A>().load()
|
||||
val carpetedInstance = carpentedA.constructors[0].newInstance(23, 42)
|
||||
|
||||
val serSchema = obj.envelope.schema
|
||||
assertEquals(23, carpetedInstance.get("i"))
|
||||
assertEquals(42, carpetedInstance.get("ii"))
|
||||
|
||||
assertEquals(3, serSchema.types.size)
|
||||
val i = carpetedInstance as I
|
||||
val ii = carpetedInstance as II
|
||||
|
||||
val l1 = serSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
|
||||
|
||||
// since we're using an envelope generated by serialising classes defined locally
|
||||
// it's extremely unlikely we'd need to carpent any classes
|
||||
assertEquals(0, l1.size)
|
||||
|
||||
// pretend we don't know the class we've been sent, i.e. it's unknown to the class loader, and thus
|
||||
// needs some carpentry
|
||||
val mangleSchema = serSchema.mangleNames(listOf(classTestName("A")))
|
||||
val l2 = mangleSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
|
||||
val aName = mangleName(classTestName("A"))
|
||||
|
||||
assertEquals(1, l2.size)
|
||||
|
||||
val aSchema = l2.carpenterSchemas.find { it.name == aName }
|
||||
|
||||
assertNotEquals(null, aSchema)
|
||||
assertEquals(aName, aSchema!!.name)
|
||||
assertEquals(2, aSchema.interfaces.size)
|
||||
assertTrue(I::class.java in aSchema.interfaces)
|
||||
assertTrue(II::class.java in aSchema.interfaces)
|
||||
|
||||
val aBuilder = ClassCarpenterImpl(whitelist = AllWhitelist).build(aSchema)
|
||||
val objA = aBuilder.constructors[0].newInstance(testI, testII)
|
||||
val i = objA as I
|
||||
val ii = objA as II
|
||||
|
||||
assertEquals(aBuilder.getMethod("getI").invoke(objA), testI)
|
||||
assertEquals(aBuilder.getMethod("getIi").invoke(objA), testII)
|
||||
assertEquals(a.i, i.i)
|
||||
assertEquals(a.ii, ii.ii)
|
||||
assertEquals(23, i.i)
|
||||
assertEquals(42, ii.ii)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nestedInterfaces() {
|
||||
class A(override val i: Int, override val iii: Int) : III
|
||||
|
||||
val testI = 20
|
||||
val testIII = 60
|
||||
val a = A(testI, testIII)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
|
||||
val (_, env) = A(23, 42).roundTrip()
|
||||
val carpentedA = env.getMangled<A>().load()
|
||||
val carpetedInstance = carpentedA.constructors[0].newInstance(23, 42)
|
||||
|
||||
val serSchema = obj.envelope.schema
|
||||
assertEquals(23, carpetedInstance.get("i"))
|
||||
assertEquals(42, carpetedInstance.get("iii"))
|
||||
|
||||
assertEquals(3, serSchema.types.size)
|
||||
val i = carpetedInstance as I
|
||||
val iii = carpetedInstance as III
|
||||
|
||||
val l1 = serSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
|
||||
|
||||
// since we're using an envelope generated by serialising classes defined locally
|
||||
// it's extremely unlikely we'd need to carpent any classes
|
||||
assertEquals(0, l1.size)
|
||||
|
||||
val mangleSchema = serSchema.mangleNames(listOf(classTestName("A")))
|
||||
val l2 = mangleSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
|
||||
val aName = mangleName(classTestName("A"))
|
||||
|
||||
assertEquals(1, l2.size)
|
||||
|
||||
val aSchema = l2.carpenterSchemas.find { it.name == aName }
|
||||
|
||||
assertNotEquals(null, aSchema)
|
||||
assertEquals(aName, aSchema!!.name)
|
||||
assertEquals(2, aSchema.interfaces.size)
|
||||
assertTrue(I::class.java in aSchema.interfaces)
|
||||
assertTrue(III::class.java in aSchema.interfaces)
|
||||
|
||||
val aBuilder = ClassCarpenterImpl(whitelist = AllWhitelist).build(aSchema)
|
||||
val objA = aBuilder.constructors[0].newInstance(testI, testIII)
|
||||
val i = objA as I
|
||||
val iii = objA as III
|
||||
|
||||
assertEquals(aBuilder.getMethod("getI").invoke(objA), testI)
|
||||
assertEquals(aBuilder.getMethod("getIii").invoke(objA), testIII)
|
||||
assertEquals(a.i, i.i)
|
||||
assertEquals(a.i, iii.i)
|
||||
assertEquals(a.iii, iii.iii)
|
||||
assertEquals(23, i.i)
|
||||
assertEquals(23, iii.i)
|
||||
assertEquals(42, iii.iii)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -212,237 +107,60 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhitelist) {
|
||||
class A(override val i: Int) : I
|
||||
class B(override val i: I, override val iiii: Int) : IIII
|
||||
|
||||
val testI = 25
|
||||
val testIIII = 50
|
||||
val a = A(testI)
|
||||
val b = B(a, testIIII)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b))
|
||||
val (_, env) = B(A(23), 42).roundTrip()
|
||||
val carpentedA = env.getMangled<A>().load()
|
||||
val carpentedB = env.getMangled<B>().load()
|
||||
|
||||
val serSchema = obj.envelope.schema
|
||||
val carpentedAInstance = carpentedA.new(23)
|
||||
val carpentedBInstance = carpentedB.new(carpentedAInstance, 42)
|
||||
|
||||
// Expected classes are
|
||||
// * class A
|
||||
// * class A's interface (class I)
|
||||
// * class B
|
||||
// * class B's interface (class IIII)
|
||||
assertEquals(4, serSchema.types.size)
|
||||
|
||||
val mangleSchema = serSchema.mangleNames(listOf(classTestName("A"), classTestName("B")))
|
||||
val cSchema = mangleSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
|
||||
val aName = mangleName(classTestName("A"))
|
||||
val bName = mangleName(classTestName("B"))
|
||||
|
||||
assertEquals(2, cSchema.size)
|
||||
|
||||
val aCarpenterSchema = cSchema.carpenterSchemas.find { it.name == aName }
|
||||
val bCarpenterSchema = cSchema.carpenterSchemas.find { it.name == bName }
|
||||
|
||||
assertNotEquals(null, aCarpenterSchema)
|
||||
assertNotEquals(null, bCarpenterSchema)
|
||||
|
||||
val cc = ClassCarpenterImpl(whitelist = AllWhitelist)
|
||||
val cc2 = ClassCarpenterImpl(whitelist = AllWhitelist)
|
||||
val bBuilder = cc.build(bCarpenterSchema!!)
|
||||
bBuilder.constructors[0].newInstance(a, testIIII)
|
||||
|
||||
val aBuilder = cc.build(aCarpenterSchema!!)
|
||||
val objA = aBuilder.constructors[0].newInstance(testI)
|
||||
|
||||
// build a second B this time using our constructed instance of A and not the
|
||||
// local one we pre defined
|
||||
bBuilder.constructors[0].newInstance(objA, testIIII)
|
||||
|
||||
// whittle and instantiate a different A with a new class loader
|
||||
val aBuilder2 = cc2.build(aCarpenterSchema)
|
||||
val objA2 = aBuilder2.constructors[0].newInstance(testI)
|
||||
|
||||
bBuilder.constructors[0].newInstance(objA2, testIIII)
|
||||
val iiii = carpentedBInstance as IIII
|
||||
assertEquals(23, iiii.i.i)
|
||||
assertEquals(42, iiii.iiii)
|
||||
}
|
||||
|
||||
// if we remove the nested interface we should get an error as it's impossible
|
||||
// to have a concrete class loaded without having access to all of it's elements
|
||||
@Test(expected = UncarpentableException::class)
|
||||
@Test
|
||||
fun memberInterface2() {
|
||||
class A(override val i: Int) : I
|
||||
class B(override val i: I, override val iiii: Int) : IIII
|
||||
|
||||
val testI = 25
|
||||
val testIIII = 50
|
||||
val a = A(testI)
|
||||
val b = B(a, testIIII)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b))
|
||||
val (_, env) = A(23).roundTrip()
|
||||
|
||||
val serSchema = obj.envelope.schema
|
||||
|
||||
// The classes we're expecting to find:
|
||||
// * class A
|
||||
// * class A's interface (class I)
|
||||
// * class B
|
||||
// * class B's interface (class IIII)
|
||||
assertEquals(4, serSchema.types.size)
|
||||
|
||||
// ignore the return as we expect this to throw
|
||||
serSchema.mangleNames(listOf(
|
||||
classTestName("A"), "${this.javaClass.`package`.name}.I")).carpenterSchema(ClassLoader.getSystemClassLoader())
|
||||
// if we remove the nested interface we should get an error as it's impossible
|
||||
// to have a concrete class loaded without having access to all of it's elements
|
||||
assertFailsWith<NotSerializableException> { assertCanLoadAll(env.getMangled<A>().mangle<I>()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun interfaceAndImplementation() {
|
||||
class A(override val i: Int) : I
|
||||
|
||||
val testI = 25
|
||||
val a = A(testI)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
|
||||
val (_, env) = A(23).roundTrip()
|
||||
|
||||
val serSchema = obj.envelope.schema
|
||||
|
||||
// The classes we're expecting to find:
|
||||
// * class A
|
||||
// * class A's interface (class I)
|
||||
assertEquals(2, serSchema.types.size)
|
||||
|
||||
val amqpSchema = serSchema.mangleNames(listOf(classTestName("A"), "${this.javaClass.`package`.name}.I"))
|
||||
val aName = mangleName(classTestName("A"))
|
||||
val iName = mangleName("${this.javaClass.`package`.name}.I")
|
||||
val carpenterSchema = amqpSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
|
||||
|
||||
// whilst there are two unknown classes within the envelope A depends on I so we can't construct a
|
||||
// schema for A until we have for I
|
||||
assertEquals(1, carpenterSchema.size)
|
||||
assertNotEquals(null, carpenterSchema.carpenterSchemas.find { it.name == iName })
|
||||
|
||||
// since we can't build A it should list I as a dependency
|
||||
assertTrue(aName in carpenterSchema.dependencies)
|
||||
assertEquals(1, carpenterSchema.dependencies[aName]!!.second.size)
|
||||
assertEquals(iName, carpenterSchema.dependencies[aName]!!.second[0])
|
||||
|
||||
// and conversly I should have A listed as a dependent
|
||||
assertTrue(iName in carpenterSchema.dependsOn)
|
||||
assertEquals(1, carpenterSchema.dependsOn[iName]!!.size)
|
||||
assertEquals(aName, carpenterSchema.dependsOn[iName]!![0])
|
||||
|
||||
val mc = MetaCarpenter(carpenterSchema, ClassCarpenterImpl(whitelist = AllWhitelist))
|
||||
mc.build()
|
||||
|
||||
assertEquals(0, mc.schemas.carpenterSchemas.size)
|
||||
assertEquals(0, mc.schemas.dependencies.size)
|
||||
assertEquals(0, mc.schemas.dependsOn.size)
|
||||
assertEquals(2, mc.objects.size)
|
||||
assertTrue(aName in mc.objects)
|
||||
assertTrue(iName in mc.objects)
|
||||
|
||||
mc.objects[aName]!!.constructors[0].newInstance(testI)
|
||||
// This time around we will succeed, because the mangled I is included in the type information to be loaded.
|
||||
assertCanLoadAll(env.getMangled<A>().mangle<I>(), env.getMangled<I>())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun twoInterfacesAndImplementation() {
|
||||
class A(override val i: Int, override val ii: Int) : I, II
|
||||
|
||||
val testI = 69
|
||||
val testII = 96
|
||||
val a = A(testI, testII)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
|
||||
|
||||
val amqpSchema = obj.envelope.schema.mangleNames(listOf(
|
||||
classTestName("A"),
|
||||
"${this.javaClass.`package`.name}.I",
|
||||
"${this.javaClass.`package`.name}.II"))
|
||||
|
||||
val aName = mangleName(classTestName("A"))
|
||||
val iName = mangleName("${this.javaClass.`package`.name}.I")
|
||||
val iiName = mangleName("${this.javaClass.`package`.name}.II")
|
||||
val carpenterSchema = amqpSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
|
||||
|
||||
// there is nothing preventing us from carpenting up the two interfaces so
|
||||
// our initial list should contain both interface with A being dependent on both
|
||||
// and each having A as a dependent
|
||||
assertEquals(2, carpenterSchema.carpenterSchemas.size)
|
||||
assertNotNull(carpenterSchema.carpenterSchemas.find { it.name == iName })
|
||||
assertNotNull(carpenterSchema.carpenterSchemas.find { it.name == iiName })
|
||||
assertNull(carpenterSchema.carpenterSchemas.find { it.name == aName })
|
||||
|
||||
assertTrue(iName in carpenterSchema.dependsOn)
|
||||
assertEquals(1, carpenterSchema.dependsOn[iName]?.size)
|
||||
assertNotNull(carpenterSchema.dependsOn[iName]?.find({ it == aName }))
|
||||
|
||||
assertTrue(iiName in carpenterSchema.dependsOn)
|
||||
assertEquals(1, carpenterSchema.dependsOn[iiName]?.size)
|
||||
assertNotNull(carpenterSchema.dependsOn[iiName]?.find { it == aName })
|
||||
|
||||
assertTrue(aName in carpenterSchema.dependencies)
|
||||
assertEquals(2, carpenterSchema.dependencies[aName]!!.second.size)
|
||||
assertNotNull(carpenterSchema.dependencies[aName]!!.second.find { it == iName })
|
||||
assertNotNull(carpenterSchema.dependencies[aName]!!.second.find { it == iiName })
|
||||
|
||||
val mc = MetaCarpenter(carpenterSchema, ClassCarpenterImpl(whitelist = AllWhitelist))
|
||||
mc.build()
|
||||
|
||||
assertEquals(0, mc.schemas.carpenterSchemas.size)
|
||||
assertEquals(0, mc.schemas.dependencies.size)
|
||||
assertEquals(0, mc.schemas.dependsOn.size)
|
||||
assertEquals(3, mc.objects.size)
|
||||
assertTrue(aName in mc.objects)
|
||||
assertTrue(iName in mc.objects)
|
||||
assertTrue(iiName in mc.objects)
|
||||
val (_, env) = A(23, 42).roundTrip()
|
||||
assertCanLoadAll(
|
||||
env.getMangled<A>().mangle<I>().mangle<II>(),
|
||||
env.getMangled<I>(),
|
||||
env.getMangled<II>()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nestedInterfacesAndImplementation() {
|
||||
class A(override val i: Int, override val iii: Int) : III
|
||||
|
||||
val testI = 7
|
||||
val testIII = 11
|
||||
val a = A(testI, testIII)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
|
||||
|
||||
val amqpSchema = obj.envelope.schema.mangleNames(listOf(
|
||||
classTestName("A"),
|
||||
"${this.javaClass.`package`.name}.I",
|
||||
"${this.javaClass.`package`.name}.III"))
|
||||
|
||||
val aName = mangleName(classTestName("A"))
|
||||
val iName = mangleName("${this.javaClass.`package`.name}.I")
|
||||
val iiiName = mangleName("${this.javaClass.`package`.name}.III")
|
||||
val carpenterSchema = amqpSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
|
||||
|
||||
// Since A depends on III and III extends I we will have to construct them
|
||||
// in that reverse order (I -> III -> A)
|
||||
assertEquals(1, carpenterSchema.carpenterSchemas.size)
|
||||
assertNotNull(carpenterSchema.carpenterSchemas.find { it.name == iName })
|
||||
assertNull(carpenterSchema.carpenterSchemas.find { it.name == iiiName })
|
||||
assertNull(carpenterSchema.carpenterSchemas.find { it.name == aName })
|
||||
|
||||
// I has III as a direct dependent and A as an indirect one
|
||||
assertTrue(iName in carpenterSchema.dependsOn)
|
||||
assertEquals(2, carpenterSchema.dependsOn[iName]?.size)
|
||||
assertNotNull(carpenterSchema.dependsOn[iName]?.find({ it == iiiName }))
|
||||
assertNotNull(carpenterSchema.dependsOn[iName]?.find({ it == aName }))
|
||||
|
||||
// III has A as a dependent
|
||||
assertTrue(iiiName in carpenterSchema.dependsOn)
|
||||
assertEquals(1, carpenterSchema.dependsOn[iiiName]?.size)
|
||||
assertNotNull(carpenterSchema.dependsOn[iiiName]?.find { it == aName })
|
||||
|
||||
// conversly III depends on I
|
||||
assertTrue(iiiName in carpenterSchema.dependencies)
|
||||
assertEquals(1, carpenterSchema.dependencies[iiiName]!!.second.size)
|
||||
assertNotNull(carpenterSchema.dependencies[iiiName]!!.second.find { it == iName })
|
||||
|
||||
// and A depends on III and I
|
||||
assertTrue(aName in carpenterSchema.dependencies)
|
||||
assertEquals(2, carpenterSchema.dependencies[aName]!!.second.size)
|
||||
assertNotNull(carpenterSchema.dependencies[aName]!!.second.find { it == iiiName })
|
||||
assertNotNull(carpenterSchema.dependencies[aName]!!.second.find { it == iName })
|
||||
|
||||
val mc = MetaCarpenter(carpenterSchema, ClassCarpenterImpl(whitelist = AllWhitelist))
|
||||
mc.build()
|
||||
|
||||
assertEquals(0, mc.schemas.carpenterSchemas.size)
|
||||
assertEquals(0, mc.schemas.dependencies.size)
|
||||
assertEquals(0, mc.schemas.dependsOn.size)
|
||||
assertEquals(3, mc.objects.size)
|
||||
assertTrue(aName in mc.objects)
|
||||
assertTrue(iName in mc.objects)
|
||||
assertTrue(iiiName in mc.objects)
|
||||
val (_, env) = A(23, 42).roundTrip()
|
||||
assertCanLoadAll(
|
||||
env.getMangled<A>().mangle<I>().mangle<III>(),
|
||||
env.getMangled<I>(),
|
||||
env.getMangled<III>().mangle<I>()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1,57 +1,24 @@
|
||||
package net.corda.serialization.internal.carpenter
|
||||
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.SerializableCalculatedProperty
|
||||
import net.corda.serialization.internal.AllWhitelist
|
||||
import net.corda.serialization.internal.amqp.CompositeType
|
||||
import net.corda.serialization.internal.amqp.DeserializationInput
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotEquals
|
||||
import net.corda.serialization.internal.amqp.testutils.deserializeAndReturnEnvelope
|
||||
|
||||
class MultiMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhitelist) {
|
||||
|
||||
@Test
|
||||
fun twoInts() {
|
||||
fun anIntAndALong() {
|
||||
@CordaSerializable
|
||||
data class A(val a: Int, val b: Int)
|
||||
data class A(val a: Int, val b: Long)
|
||||
|
||||
val testA = 10
|
||||
val testB = 20
|
||||
val a = A(testA, testB)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
|
||||
val (_, env) = A(23, 42).roundTrip()
|
||||
val carpentedInstance = env.getMangled<A>().load().new(23, 42)
|
||||
|
||||
val amqpObj = obj.obj
|
||||
|
||||
assertEquals(testA, amqpObj.a)
|
||||
assertEquals(testB, amqpObj.b)
|
||||
assertEquals(1, obj.envelope.schema.types.size)
|
||||
require(obj.envelope.schema.types[0] is CompositeType)
|
||||
|
||||
val amqpSchema = obj.envelope.schema.types[0] as CompositeType
|
||||
|
||||
assertEquals(2, amqpSchema.fields.size)
|
||||
assertEquals("a", amqpSchema.fields[0].name)
|
||||
assertEquals("int", amqpSchema.fields[0].type)
|
||||
assertEquals("b", amqpSchema.fields[1].name)
|
||||
assertEquals("int", amqpSchema.fields[1].type)
|
||||
|
||||
val carpenterSchema = CarpenterMetaSchema.newInstance()
|
||||
amqpSchema.carpenterSchema(
|
||||
classloader = ClassLoader.getSystemClassLoader(),
|
||||
carpenterSchemas = carpenterSchema,
|
||||
force = true)
|
||||
|
||||
assertEquals(1, carpenterSchema.size)
|
||||
val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }
|
||||
|
||||
assertNotEquals(null, aSchema)
|
||||
|
||||
val pinochio = ClassCarpenterImpl(whitelist = AllWhitelist).build(aSchema!!)
|
||||
val p = pinochio.constructors[0].newInstance(testA, testB)
|
||||
|
||||
assertEquals(pinochio.getMethod("getA").invoke(p), amqpObj.a)
|
||||
assertEquals(pinochio.getMethod("getB").invoke(p), amqpObj.b)
|
||||
assertEquals(23, carpentedInstance.get("a"))
|
||||
assertEquals(42L, carpentedInstance.get("b"))
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -59,42 +26,65 @@ class MultiMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhi
|
||||
@CordaSerializable
|
||||
data class A(val a: Int, val b: String)
|
||||
|
||||
val testA = 10
|
||||
val testB = "twenty"
|
||||
val a = A(testA, testB)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
|
||||
val (_, env) = A(23, "skidoo").roundTrip()
|
||||
val carpentedInstance = env.getMangled<A>().load().new(23, "skidoo")
|
||||
|
||||
val amqpObj = obj.obj
|
||||
assertEquals(23, carpentedInstance.get("a"))
|
||||
assertEquals("skidoo", carpentedInstance.get("b"))
|
||||
}
|
||||
|
||||
assertEquals(testA, amqpObj.a)
|
||||
assertEquals(testB, amqpObj.b)
|
||||
assertEquals(1, obj.envelope.schema.types.size)
|
||||
require(obj.envelope.schema.types[0] is CompositeType)
|
||||
interface Parent {
|
||||
@get:SerializableCalculatedProperty
|
||||
val doubled: Int
|
||||
}
|
||||
|
||||
val amqpSchema = obj.envelope.schema.types[0] as CompositeType
|
||||
@Test
|
||||
fun calculatedValues() {
|
||||
data class C(val i: Int): Parent {
|
||||
@get:SerializableCalculatedProperty
|
||||
val squared = (i * i).toString()
|
||||
|
||||
assertEquals(2, amqpSchema.fields.size)
|
||||
assertEquals("a", amqpSchema.fields[0].name)
|
||||
assertEquals("int", amqpSchema.fields[0].type)
|
||||
assertEquals("b", amqpSchema.fields[1].name)
|
||||
assertEquals("string", amqpSchema.fields[1].type)
|
||||
override val doubled get() = i * 2
|
||||
}
|
||||
|
||||
val carpenterSchema = CarpenterMetaSchema.newInstance()
|
||||
amqpSchema.carpenterSchema(
|
||||
classloader = ClassLoader.getSystemClassLoader(),
|
||||
carpenterSchemas = carpenterSchema,
|
||||
force = true)
|
||||
val (amqpObj, envelope) = C(2).roundTrip()
|
||||
val remoteTypeInformation = envelope.typeInformationFor<C>()
|
||||
|
||||
assertEquals(1, carpenterSchema.size)
|
||||
val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }
|
||||
assertEquals("""
|
||||
C: Parent
|
||||
doubled: int
|
||||
i: int
|
||||
squared: String
|
||||
""".trimIndent(), remoteTypeInformation.prettyPrint())
|
||||
|
||||
assertNotEquals(null, aSchema)
|
||||
val pinochio = remoteTypeInformation.mangle<C>().load()
|
||||
assertNotEquals(pinochio.name, C::class.java.name)
|
||||
assertNotEquals(pinochio, C::class.java)
|
||||
|
||||
val pinochio = ClassCarpenterImpl(whitelist = AllWhitelist).build(aSchema!!)
|
||||
val p = pinochio.constructors[0].newInstance(testA, testB)
|
||||
// Note that params are given in alphabetical order: doubled, i, squared
|
||||
val p = pinochio.new(4, 2, "4")
|
||||
|
||||
assertEquals(pinochio.getMethod("getA").invoke(p), amqpObj.a)
|
||||
assertEquals(pinochio.getMethod("getB").invoke(p), amqpObj.b)
|
||||
assertEquals(2, p.get("i"))
|
||||
assertEquals("4", p.get("squared"))
|
||||
assertEquals(4, p.get("doubled"))
|
||||
|
||||
val upcast = p as Parent
|
||||
assertEquals(upcast.doubled, amqpObj.doubled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun implementingClassDoesNotCalculateValue() {
|
||||
class C(override val doubled: Int): Parent
|
||||
|
||||
val (_, env) = C(5).roundTrip()
|
||||
|
||||
val pinochio = env.getMangled<C>().load()
|
||||
val p = pinochio.new(5)
|
||||
|
||||
assertEquals(5, p.get("doubled"))
|
||||
|
||||
val upcast = p as Parent
|
||||
assertEquals(5, upcast.doubled)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,205 +0,0 @@
|
||||
package net.corda.serialization.internal.carpenter
|
||||
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.serialization.internal.AllWhitelist
|
||||
import net.corda.serialization.internal.amqp.CompositeType
|
||||
import net.corda.serialization.internal.amqp.DeserializationInput
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
import net.corda.serialization.internal.amqp.testutils.deserializeAndReturnEnvelope
|
||||
|
||||
class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhitelist) {
|
||||
@Test
|
||||
fun singleInteger() {
|
||||
@CordaSerializable
|
||||
data class A(val a: Int)
|
||||
|
||||
val test = 10
|
||||
val a = A(test)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
|
||||
val amqpObj = obj.obj
|
||||
|
||||
assertEquals(test, amqpObj.a)
|
||||
assertEquals(1, obj.envelope.schema.types.size)
|
||||
require(obj.envelope.schema.types[0] is CompositeType)
|
||||
|
||||
val amqpSchema = obj.envelope.schema.types[0] as CompositeType
|
||||
|
||||
assertEquals(1, amqpSchema.fields.size)
|
||||
assertEquals("a", amqpSchema.fields[0].name)
|
||||
assertEquals("int", amqpSchema.fields[0].type)
|
||||
|
||||
val carpenterSchema = CarpenterMetaSchema.newInstance()
|
||||
amqpSchema.carpenterSchema(
|
||||
classloader = ClassLoader.getSystemClassLoader(),
|
||||
carpenterSchemas = carpenterSchema,
|
||||
force = true)
|
||||
|
||||
val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!!
|
||||
val aBuilder = ClassCarpenterImpl(whitelist = AllWhitelist).build(aSchema)
|
||||
val p = aBuilder.constructors[0].newInstance(test)
|
||||
|
||||
assertEquals(aBuilder.getMethod("getA").invoke(p), amqpObj.a)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun singleString() {
|
||||
@CordaSerializable
|
||||
data class A(val a: String)
|
||||
|
||||
val test = "ten"
|
||||
val a = A(test)
|
||||
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
|
||||
val amqpObj = obj.obj
|
||||
|
||||
assertEquals(test, amqpObj.a)
|
||||
assertEquals(1, obj.envelope.schema.types.size)
|
||||
require(obj.envelope.schema.types[0] is CompositeType)
|
||||
|
||||
val amqpSchema = obj.envelope.schema.types[0] as CompositeType
|
||||
val carpenterSchema = CarpenterMetaSchema.newInstance()
|
||||
amqpSchema.carpenterSchema(
|
||||
classloader = ClassLoader.getSystemClassLoader(),
|
||||
carpenterSchemas = carpenterSchema,
|
||||
force = true)
|
||||
|
||||
val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!!
|
||||
val aBuilder = ClassCarpenterImpl(whitelist = AllWhitelist).build(aSchema)
|
||||
val p = aBuilder.constructors[0].newInstance(test)
|
||||
|
||||
assertEquals(aBuilder.getMethod("getA").invoke(p), amqpObj.a)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun singleLong() {
|
||||
@CordaSerializable
|
||||
data class A(val a: Long)
|
||||
|
||||
val test = 10L
|
||||
val a = A(test)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
|
||||
val amqpObj = obj.obj
|
||||
|
||||
assertEquals(test, amqpObj.a)
|
||||
assertEquals(1, obj.envelope.schema.types.size)
|
||||
require(obj.envelope.schema.types[0] is CompositeType)
|
||||
|
||||
val amqpSchema = obj.envelope.schema.types[0] as CompositeType
|
||||
|
||||
assertEquals(1, amqpSchema.fields.size)
|
||||
assertEquals("a", amqpSchema.fields[0].name)
|
||||
assertEquals("long", amqpSchema.fields[0].type)
|
||||
|
||||
val carpenterSchema = CarpenterMetaSchema.newInstance()
|
||||
amqpSchema.carpenterSchema(
|
||||
classloader = ClassLoader.getSystemClassLoader(),
|
||||
carpenterSchemas = carpenterSchema,
|
||||
force = true)
|
||||
|
||||
val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!!
|
||||
val aBuilder = ClassCarpenterImpl(whitelist = AllWhitelist).build(aSchema)
|
||||
val p = aBuilder.constructors[0].newInstance(test)
|
||||
|
||||
assertEquals(aBuilder.getMethod("getA").invoke(p), amqpObj.a)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun singleShort() {
|
||||
@CordaSerializable
|
||||
data class A(val a: Short)
|
||||
|
||||
val test = 10.toShort()
|
||||
val a = A(test)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
|
||||
val amqpObj = obj.obj
|
||||
|
||||
assertEquals(test, amqpObj.a)
|
||||
assertEquals(1, obj.envelope.schema.types.size)
|
||||
require(obj.envelope.schema.types[0] is CompositeType)
|
||||
|
||||
val amqpSchema = obj.envelope.schema.types[0] as CompositeType
|
||||
|
||||
assertEquals(1, amqpSchema.fields.size)
|
||||
assertEquals("a", amqpSchema.fields[0].name)
|
||||
assertEquals("short", amqpSchema.fields[0].type)
|
||||
|
||||
val carpenterSchema = CarpenterMetaSchema.newInstance()
|
||||
amqpSchema.carpenterSchema(
|
||||
classloader = ClassLoader.getSystemClassLoader(),
|
||||
carpenterSchemas = carpenterSchema,
|
||||
force = true)
|
||||
|
||||
val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!!
|
||||
val aBuilder = ClassCarpenterImpl(whitelist = AllWhitelist).build(aSchema)
|
||||
val p = aBuilder.constructors[0].newInstance(test)
|
||||
|
||||
assertEquals(aBuilder.getMethod("getA").invoke(p), amqpObj.a)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun singleDouble() {
|
||||
@CordaSerializable
|
||||
data class A(val a: Double)
|
||||
|
||||
val test = 10.0
|
||||
val a = A(test)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
|
||||
val amqpObj = obj.obj
|
||||
|
||||
assertEquals(test, amqpObj.a)
|
||||
assertEquals(1, obj.envelope.schema.types.size)
|
||||
require(obj.envelope.schema.types[0] is CompositeType)
|
||||
|
||||
val amqpSchema = obj.envelope.schema.types[0] as CompositeType
|
||||
|
||||
assertEquals(1, amqpSchema.fields.size)
|
||||
assertEquals("a", amqpSchema.fields[0].name)
|
||||
assertEquals("double", amqpSchema.fields[0].type)
|
||||
|
||||
val carpenterSchema = CarpenterMetaSchema.newInstance()
|
||||
amqpSchema.carpenterSchema(
|
||||
classloader = ClassLoader.getSystemClassLoader(),
|
||||
carpenterSchemas = carpenterSchema,
|
||||
force = true)
|
||||
|
||||
val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!!
|
||||
val aBuilder = ClassCarpenterImpl(whitelist = AllWhitelist).build(aSchema)
|
||||
val p = aBuilder.constructors[0].newInstance(test)
|
||||
|
||||
assertEquals(aBuilder.getMethod("getA").invoke(p), amqpObj.a)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun singleFloat() {
|
||||
@CordaSerializable
|
||||
data class A(val a: Float)
|
||||
|
||||
val test = 10.0F
|
||||
val a = A(test)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
|
||||
val amqpObj = obj.obj
|
||||
|
||||
assertEquals(test, amqpObj.a)
|
||||
assertEquals(1, obj.envelope.schema.types.size)
|
||||
require(obj.envelope.schema.types[0] is CompositeType)
|
||||
|
||||
val amqpSchema = obj.envelope.schema.types[0] as CompositeType
|
||||
|
||||
assertEquals(1, amqpSchema.fields.size)
|
||||
assertEquals("a", amqpSchema.fields[0].name)
|
||||
assertEquals("float", amqpSchema.fields[0].type)
|
||||
|
||||
val carpenterSchema = CarpenterMetaSchema.newInstance()
|
||||
amqpSchema.carpenterSchema(
|
||||
classloader = ClassLoader.getSystemClassLoader(),
|
||||
carpenterSchemas = carpenterSchema,
|
||||
force = true)
|
||||
|
||||
val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!!
|
||||
val aBuilder = ClassCarpenterImpl(whitelist = AllWhitelist).build(aSchema)
|
||||
val p = aBuilder.constructors[0].newInstance(test)
|
||||
|
||||
assertEquals(aBuilder.getMethod("getA").invoke(p), amqpObj.a)
|
||||
}
|
||||
}
|
@ -54,7 +54,7 @@ class ClassCarpentingTypeLoaderTests {
|
||||
val person = personType.make("Arthur Putey", 42, address, listOf(previousAddress))
|
||||
val personJson = ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(person)
|
||||
.replace("\r\n", "\n")
|
||||
|
||||
|
||||
assertEquals("""
|
||||
{
|
||||
"name" : "Arthur Putey",
|
||||
|
@ -59,9 +59,9 @@ class LocalTypeModelTests {
|
||||
assertInformation<Nested>("""
|
||||
Nested(collectionHolder: StringKeyedCollectionHolder<Integer>?, intArray: int[], optionalParam: Short?)
|
||||
collectionHolder (optional): StringKeyedCollectionHolder<Integer>(list: List<Integer>, map: Map<String, Integer>, array: List<Integer>[]): CollectionHolder<String, Integer>
|
||||
array: List<Integer>[]
|
||||
list: List<Integer>
|
||||
map: Map<String, Integer>
|
||||
array: List<Integer>[]
|
||||
list: List<Integer>
|
||||
map: Map<String, Integer>
|
||||
intArray: int[]
|
||||
""")
|
||||
|
||||
|
@ -43,6 +43,19 @@ class TypeIdentifierTests {
|
||||
TypeIdentifier.forGenericType(fieldType, HasStringArray::class.java).prettyPrint())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `roundtrip`() {
|
||||
assertRoundtrips(Int::class.javaPrimitiveType!!)
|
||||
assertRoundtrips<Int>()
|
||||
assertRoundtrips<IntArray>()
|
||||
assertRoundtrips(List::class.java)
|
||||
assertRoundtrips<List<String>>()
|
||||
assertRoundtrips<Array<List<String>>>()
|
||||
assertRoundtrips<HasStringArray>()
|
||||
assertRoundtrips(HasArray::class.java)
|
||||
assertRoundtrips<HasArray<String>>()
|
||||
}
|
||||
|
||||
private fun assertIdentified(type: Type, expected: String) =
|
||||
assertEquals(expected, TypeIdentifier.forGenericType(type).prettyPrint())
|
||||
|
||||
@ -50,4 +63,12 @@ class TypeIdentifierTests {
|
||||
assertEquals(expected, TypeIdentifier.forGenericType(typeOf<T>()).prettyPrint())
|
||||
|
||||
private inline fun <reified T> typeOf() = object : TypeToken<T>() {}.type
|
||||
|
||||
private inline fun <reified T> assertRoundtrips() = assertRoundtrips(typeOf<T>())
|
||||
|
||||
private fun assertRoundtrips(original: Type) {
|
||||
val identifier = TypeIdentifier.forGenericType(original)
|
||||
val localType = identifier.getLocalType(classLoader = ClassLoader.getSystemClassLoader())
|
||||
assertIdentified(localType, identifier.prettyPrint())
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user