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:
Dominic Fox
2018-11-22 11:44:40 +00:00
committed by GitHub
parent 98a495fa84
commit 2c6bce3e5d
84 changed files with 2381 additions and 3974 deletions

View File

@ -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());
}
}

View File

@ -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());
}
}

View File

@ -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) })

View File

@ -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()))

View File

@ -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> {

View File

@ -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),

View File

@ -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))
}
}
}

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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])
}

View File

@ -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)
}
}
}

View File

@ -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}")
}
}

View File

@ -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

View File

@ -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))

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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,

View File

@ -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)
}
}

View File

@ -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

View File

@ -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)
}

View File

@ -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)
}
*/
}

View File

@ -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>()
)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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",

View File

@ -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[]
""")

View File

@ -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())
}
}