Moved the serialisation logic in nodeapi.internal.serialization into its own module: serialization (#3179)

node-api now depends on this module and upcoming changes will use this as well rather than having to depend on node-api.

EnumEvolveTests.deserializeWithRename and EnumEvolveTests.multiOperations are temporarily ignored since their test resources can't be regenerated due to bugs.
This commit is contained in:
Shams Asari
2018-05-17 16:18:07 +01:00
committed by GitHub
parent bbc80429be
commit 3cdd908714
280 changed files with 759 additions and 709 deletions

View File

@ -0,0 +1,71 @@
package net.corda.serialization.internal;
import com.google.common.collect.Maps;
import net.corda.core.serialization.SerializationContext;
import net.corda.core.serialization.SerializationFactory;
import net.corda.core.serialization.SerializedBytes;
import net.corda.serialization.internal.amqp.SchemaKt;
import net.corda.testing.core.SerializationEnvironmentRule;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import java.io.NotSerializableException;
import java.io.Serializable;
import java.util.EnumSet;
import java.util.concurrent.Callable;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.ThrowableAssert.catchThrowable;
public final class ForbiddenLambdaSerializationTests {
private EnumSet<SerializationContext.UseCase> contexts = EnumSet.complementOf(
EnumSet.of(SerializationContext.UseCase.Checkpoint, SerializationContext.UseCase.Testing));
@Rule
public final SerializationEnvironmentRule testSerialization = new SerializationEnvironmentRule();
private SerializationFactory factory;
@Before
public void setup() {
factory = testSerialization.getSerializationFactory();
}
@Test
public final void serialization_fails_for_serializable_java_lambdas() {
contexts.forEach(ctx -> {
SerializationContext context = new SerializationContextImpl(SchemaKt.getAmqpMagic(),
this.getClass().getClassLoader(), AllWhitelist.INSTANCE, Maps.newHashMap(), true, ctx, null);
String value = "Hey";
Callable<String> target = (Callable<String> & Serializable) () -> value;
Throwable throwable = catchThrowable(() -> serialize(target, context));
assertThat(throwable)
.isNotNull()
.isInstanceOf(NotSerializableException.class)
.hasMessageContaining(getClass().getName());
});
}
@Test
@SuppressWarnings("unchecked")
public final void serialization_fails_for_not_serializable_java_lambdas() {
contexts.forEach(ctx -> {
SerializationContext context = new SerializationContextImpl(SchemaKt.getAmqpMagic(),
this.getClass().getClassLoader(), AllWhitelist.INSTANCE, Maps.newHashMap(), true, ctx, null);
String value = "Hey";
Callable<String> target = () -> value;
Throwable throwable = catchThrowable(() -> serialize(target, context));
assertThat(throwable)
.isNotNull()
.isInstanceOf(NotSerializableException.class)
.hasMessageContaining(getClass().getName());
});
}
private <T> SerializedBytes<T> serialize(final T target, final SerializationContext context) {
return factory.serialize(target, context);
}
}

View File

@ -0,0 +1,64 @@
package net.corda.serialization.internal;
import com.google.common.collect.Maps;
import net.corda.core.serialization.SerializationContext;
import net.corda.core.serialization.SerializationFactory;
import net.corda.core.serialization.SerializedBytes;
import net.corda.testing.core.SerializationEnvironmentRule;
import net.corda.serialization.internal.kryo.CordaClosureSerializer;
import net.corda.serialization.internal.kryo.KryoSerializationSchemeKt;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import java.io.Serializable;
import java.util.concurrent.Callable;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.ThrowableAssert.catchThrowable;
public final class LambdaCheckpointSerializationTest {
@Rule
public final SerializationEnvironmentRule testSerialization = new SerializationEnvironmentRule();
private SerializationFactory factory;
private SerializationContext context;
@Before
public void setup() {
factory = testSerialization.getSerializationFactory();
context = new SerializationContextImpl(KryoSerializationSchemeKt.getKryoMagic(), this.getClass().getClassLoader(), AllWhitelist.INSTANCE, Maps.newHashMap(), true, SerializationContext.UseCase.Checkpoint, null);
}
@Test
@SuppressWarnings("unchecked")
public final void serialization_works_for_serializable_java_lambdas() throws Exception {
String value = "Hey";
Callable<String> target = (Callable<String> & Serializable) () -> value;
SerializedBytes<Callable<String>> serialized = serialize(target);
Callable<String> deserialized = deserialize(serialized, Callable.class);
assertThat(deserialized.call()).isEqualTo(value);
}
@Test
@SuppressWarnings("unchecked")
public final void serialization_fails_for_not_serializable_java_lambdas() throws Exception {
String value = "Hey";
Callable<String> target = () -> value;
Throwable throwable = catchThrowable(() -> serialize(target));
assertThat(throwable).isNotNull();
assertThat(throwable).isInstanceOf(IllegalArgumentException.class);
assertThat(throwable).hasMessage(CordaClosureSerializer.ERROR_MESSAGE);
}
private <T> SerializedBytes<T> serialize(final T target) {
return factory.serialize(target, context);
}
private <T> T deserialize(final SerializedBytes<? extends T> bytes, final Class<T> type) {
return factory.deserialize(bytes, type, context);
}
}

View File

@ -0,0 +1,50 @@
package net.corda.serialization.internal.amqp;
import net.corda.serialization.internal.AllWhitelist;
import net.corda.serialization.internal.amqp.testutils.TestSerializationContext;
import org.assertj.core.api.Assertions;
import org.junit.Ignore;
import org.junit.Test;
import java.io.NotSerializableException;
@Ignore("Current behaviour allows for the serialization of objects with private members, this will be disallowed at some point in the future")
public class ErrorMessageTests {
private String errMsg(String property, String testname) {
return "Property '"
+ property
+ "' or its getter is non public, this renders class 'class "
+ testname
+ "$C' unserializable -> class "
+ testname
+ "$C";
}
static class C {
public Integer a;
public C(Integer a) {
this.a = a;
}
private Integer getA() { return this.a; }
}
@Test
public void testJavaConstructorAnnotations() {
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
FingerPrinter fingerPrinter = new SerializerFingerPrinter();
SerializerFactory factory1 = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter,
fingerPrinter);
SerializationOutput ser = new SerializationOutput(factory1);
Assertions.assertThatThrownBy(() -> ser.serialize(new C(1), TestSerializationContext.testSerializationContext))
.isInstanceOf(NotSerializableException.class)
.hasMessage(errMsg("a", getClass().getName()));
}
}

View File

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

View File

@ -0,0 +1,224 @@
package net.corda.serialization.internal.amqp;
import com.google.common.collect.ImmutableList;
import net.corda.core.contracts.ContractState;
import net.corda.core.identity.AbstractParty;
import net.corda.core.serialization.SerializedBytes;
import net.corda.serialization.internal.AllWhitelist;
import net.corda.serialization.internal.amqp.testutils.TestSerializationContext;
import org.assertj.core.api.Assertions;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import java.io.NotSerializableException;
import java.util.List;
class OuterClass1 {
protected SerializationOutput ser;
DeserializationInput desExisting;
DeserializationInput desRegen;
OuterClass1() {
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
SerializerFactory factory2 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
this.ser = new SerializationOutput(factory1);
this.desExisting = new DeserializationInput(factory1);
this.desRegen = new DeserializationInput(factory2);
}
class DummyState implements ContractState {
@Override @NotNull public List<AbstractParty> getParticipants() {
return ImmutableList.of();
}
}
public void run() throws NotSerializableException {
SerializedBytes b = ser.serialize(new DummyState(), TestSerializationContext.testSerializationContext);
desExisting.deserialize(b, DummyState.class, TestSerializationContext.testSerializationContext);
desRegen.deserialize(b, DummyState.class, TestSerializationContext.testSerializationContext);
}
}
class Inherator1 extends OuterClass1 {
public void iRun() throws NotSerializableException {
SerializedBytes b = ser.serialize(new DummyState(), TestSerializationContext.testSerializationContext);
desExisting.deserialize(b, DummyState.class, TestSerializationContext.testSerializationContext);
desRegen.deserialize(b, DummyState.class, TestSerializationContext.testSerializationContext);
}
}
class OuterClass2 {
protected SerializationOutput ser;
DeserializationInput desExisting;
DeserializationInput desRegen;
OuterClass2() {
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
SerializerFactory factory2 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
this.ser = new SerializationOutput(factory1);
this.desExisting = new DeserializationInput(factory1);
this.desRegen = new DeserializationInput(factory2);
}
protected class DummyState implements ContractState {
private Integer count;
DummyState(Integer count) {
this.count = count;
}
@Override @NotNull public List<AbstractParty> getParticipants() {
return ImmutableList.of();
}
}
public void run() throws NotSerializableException {
SerializedBytes b = ser.serialize(new DummyState(12), TestSerializationContext.testSerializationContext);
desExisting.deserialize(b, DummyState.class, TestSerializationContext.testSerializationContext);
desRegen.deserialize(b, DummyState.class, TestSerializationContext.testSerializationContext);
}
}
class Inherator2 extends OuterClass2 {
public void iRun() throws NotSerializableException {
SerializedBytes b = ser.serialize(new DummyState(12), TestSerializationContext.testSerializationContext);
desExisting.deserialize(b, DummyState.class, TestSerializationContext.testSerializationContext);
desRegen.deserialize(b, DummyState.class, TestSerializationContext.testSerializationContext);
}
}
// Make the base class abstract
abstract class AbstractClass2 {
protected SerializationOutput ser;
AbstractClass2() {
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
this.ser = new SerializationOutput(factory);
}
protected class DummyState implements ContractState {
@Override @NotNull public List<AbstractParty> getParticipants() {
return ImmutableList.of();
}
}
}
class Inherator4 extends AbstractClass2 {
public void run() throws NotSerializableException {
ser.serialize(new DummyState(), TestSerializationContext.testSerializationContext);
}
}
abstract class AbstractClass3 {
protected class DummyState implements ContractState {
@Override @NotNull public List<AbstractParty> getParticipants() {
return ImmutableList.of();
}
}
}
class Inherator5 extends AbstractClass3 {
public void run() throws NotSerializableException {
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
SerializationOutput ser = new SerializationOutput(factory);
ser.serialize(new DummyState(), TestSerializationContext.testSerializationContext);
}
}
class Inherator6 extends AbstractClass3 {
public class Wrapper {
//@Suppress("UnusedDeclaration"])
private ContractState cState;
Wrapper(ContractState cState) {
this.cState = cState;
}
}
public void run() throws NotSerializableException {
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
SerializationOutput ser = new SerializationOutput(factory);
ser.serialize(new Wrapper(new DummyState()), TestSerializationContext.testSerializationContext);
}
}
public class JavaNestedClassesTests {
@Test
public void publicNested() {
Assertions.assertThatThrownBy(() -> new OuterClass1().run()).isInstanceOf(
NotSerializableException.class).hasMessageContaining(
"has synthetic fields and is likely a nested inner class");
}
@Test
public void privateNested() {
Assertions.assertThatThrownBy(() -> new OuterClass2().run()).isInstanceOf(
NotSerializableException.class).hasMessageContaining(
"has synthetic fields and is likely a nested inner class");
}
@Test
public void publicNestedInherited() {
Assertions.assertThatThrownBy(() -> new Inherator1().run()).isInstanceOf(
NotSerializableException.class).hasMessageContaining(
"has synthetic fields and is likely a nested inner class");
Assertions.assertThatThrownBy(() -> new Inherator1().iRun()).isInstanceOf(
NotSerializableException.class).hasMessageContaining(
"has synthetic fields and is likely a nested inner class");
}
@Test
public void protectedNestedInherited() {
Assertions.assertThatThrownBy(() -> new Inherator2().run()).isInstanceOf(
NotSerializableException.class).hasMessageContaining(
"has synthetic fields and is likely a nested inner class");
Assertions.assertThatThrownBy(() -> new Inherator2().iRun()).isInstanceOf(
NotSerializableException.class).hasMessageContaining(
"has synthetic fields and is likely a nested inner class");
}
@Test
public void abstractNested() {
Assertions.assertThatThrownBy(() -> new Inherator4().run()).isInstanceOf(
NotSerializableException.class).hasMessageContaining(
"has synthetic fields and is likely a nested inner class");
}
@Test
public void abstractNestedFactoryOnNested() {
Assertions.assertThatThrownBy(() -> new Inherator5().run()).isInstanceOf(
NotSerializableException.class).hasMessageContaining(
"has synthetic fields and is likely a nested inner class");
}
@Test
public void abstractNestedFactoryOnNestedInWrapper() {
Assertions.assertThatThrownBy(() -> new Inherator6().run()).isInstanceOf(
NotSerializableException.class).hasMessageContaining(
"has synthetic fields and is likely a nested inner class");
}
}

View File

@ -0,0 +1,71 @@
package net.corda.serialization.internal.amqp;
import com.google.common.collect.ImmutableList;
import net.corda.core.contracts.ContractState;
import net.corda.core.identity.AbstractParty;
import net.corda.serialization.internal.AllWhitelist;
import net.corda.serialization.internal.amqp.testutils.TestSerializationContext;
import org.assertj.core.api.Assertions;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import java.io.NotSerializableException;
import java.util.List;
abstract class JavaNestedInheritenceTestsBase {
class DummyState implements ContractState {
@Override @NotNull public List<AbstractParty> getParticipants() {
return ImmutableList.of();
}
}
}
class Wrapper {
private ContractState cs;
Wrapper(ContractState cs) { this.cs = cs; }
}
class TemplateWrapper<T> {
public T obj;
TemplateWrapper(T obj) { this.obj = obj; }
}
public class JavaNestedInheritenceTests extends JavaNestedInheritenceTestsBase {
@Test
public void serializeIt() {
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
SerializationOutput ser = new SerializationOutput(factory);
Assertions.assertThatThrownBy(() -> ser.serialize(new DummyState(), TestSerializationContext.testSerializationContext)).isInstanceOf(
NotSerializableException.class).hasMessageContaining(
"has synthetic fields and is likely a nested inner class");
}
@Test
public void serializeIt2() {
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
SerializationOutput ser = new SerializationOutput(factory);
Assertions.assertThatThrownBy(() -> ser.serialize(new Wrapper (new DummyState()), TestSerializationContext.testSerializationContext)).isInstanceOf(
NotSerializableException.class).hasMessageContaining(
"has synthetic fields and is likely a nested inner class");
}
@Test
public void serializeIt3() throws NotSerializableException {
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
SerializationOutput ser = new SerializationOutput(factory1);
Assertions.assertThatThrownBy(() -> ser.serialize(new TemplateWrapper<ContractState> (new DummyState()), TestSerializationContext.testSerializationContext)).isInstanceOf(
NotSerializableException.class).hasMessageContaining(
"has synthetic fields and is likely a nested inner class");
}
}

View File

@ -0,0 +1,195 @@
package net.corda.serialization.internal.amqp;
import net.corda.serialization.internal.AllWhitelist;
import net.corda.serialization.internal.amqp.testutils.TestSerializationContext;
import org.junit.Test;
import static org.junit.Assert.*;
import java.io.NotSerializableException;
import java.lang.reflect.Field;
import java.util.Map;
public class JavaPrivatePropertyTests {
static class C {
private String a;
C(String a) { this.a = a; }
}
static class C2 {
private String a;
C2(String a) { this.a = a; }
public String getA() { return a; }
}
static class B {
private Boolean b;
B(Boolean b) { this.b = b; }
public Boolean isB() {
return this.b;
}
}
static class B2 {
private Boolean b;
public Boolean isB() {
return this.b;
}
public void setB(Boolean b) {
this.b = b;
}
}
static class B3 {
private Boolean b;
// break the BEAN format explicitly (i.e. it's not isB)
public Boolean isb() {
return this.b;
}
public void setB(Boolean b) {
this.b = b;
}
}
static class C3 {
private Integer a;
public Integer getA() {
return this.a;
}
public Boolean isA() {
return this.a > 0;
}
public void setA(Integer a) {
this.a = a;
}
}
@Test
public void singlePrivateBooleanWithConstructor() throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
SerializationOutput ser = new SerializationOutput(factory);
DeserializationInput des = new DeserializationInput(factory);
B b = new B(true);
B b2 = des.deserialize(ser.serialize(b, TestSerializationContext.testSerializationContext), B.class, TestSerializationContext.testSerializationContext);
assertEquals (b.b, b2.b);
}
@Test
public void singlePrivateBooleanWithNoConstructor() throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
SerializationOutput ser = new SerializationOutput(factory);
DeserializationInput des = new DeserializationInput(factory);
B2 b = new B2();
b.setB(false);
B2 b2 = des.deserialize(ser.serialize(b, TestSerializationContext.testSerializationContext), B2.class, TestSerializationContext.testSerializationContext);
assertEquals (b.b, b2.b);
}
@Test
public void testCapitilsationOfIs() throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
SerializationOutput ser = new SerializationOutput(factory);
DeserializationInput des = new DeserializationInput(factory);
B3 b = new B3();
b.setB(false);
B3 b2 = des.deserialize(ser.serialize(b, TestSerializationContext.testSerializationContext), B3.class, TestSerializationContext.testSerializationContext);
// since we can't find a getter for b (isb != isB) then we won't serialize that parameter
assertNull (b2.b);
}
@Test
public void singlePrivateIntWithBoolean() throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
SerializationOutput ser = new SerializationOutput(factory);
DeserializationInput des = new DeserializationInput(factory);
C3 c = new C3();
c.setA(12345);
C3 c2 = des.deserialize(ser.serialize(c, TestSerializationContext.testSerializationContext), C3.class, TestSerializationContext.testSerializationContext);
assertEquals (c.a, c2.a);
}
@Test
public void singlePrivateWithConstructor() throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
SerializationOutput ser = new SerializationOutput(factory);
DeserializationInput des = new DeserializationInput(factory);
C c = new C("dripping taps");
C c2 = des.deserialize(ser.serialize(c, TestSerializationContext.testSerializationContext), C.class, TestSerializationContext.testSerializationContext);
assertEquals (c.a, c2.a);
//
// Now ensure we actually got a private property serializer
//
Field f = SerializerFactory.class.getDeclaredField("serializersByDescriptor");
f.setAccessible(true);
Map<?, AMQPSerializer<?>> serializersByDescriptor = (Map<?, AMQPSerializer<?>>) f.get(factory);
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]).getGetter().getPropertyReader() instanceof PrivatePropertyReader);
}
@Test
public void singlePrivateWithConstructorAndGetter()
throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
SerializationOutput ser = new SerializationOutput(factory);
DeserializationInput des = new DeserializationInput(factory);
C2 c = new C2("dripping taps");
C2 c2 = des.deserialize(ser.serialize(c, TestSerializationContext.testSerializationContext), C2.class, TestSerializationContext.testSerializationContext);
assertEquals (c.a, c2.a);
//
// Now ensure we actually got a private property serializer
//
Field f = SerializerFactory.class.getDeclaredField("serializersByDescriptor");
f.setAccessible(true);
Map<?, AMQPSerializer<?>> serializersByDescriptor = (Map<?, AMQPSerializer<?>>) f.get(factory);
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]).getGetter().getPropertyReader() instanceof PublicPropertyReader);
}
}

View File

@ -0,0 +1,39 @@
package net.corda.serialization.internal.amqp;
import net.corda.serialization.internal.amqp.testutils.TestSerializationContext;
import org.junit.Test;
import net.corda.serialization.internal.AllWhitelist;
import net.corda.core.serialization.SerializedBytes;
import java.io.NotSerializableException;
public class JavaSerialiseEnumTests {
public enum Bras {
TSHIRT, UNDERWIRE, PUSHUP, BRALETTE, STRAPLESS, SPORTS, BACKLESS, PADDED
}
private static class Bra {
private final Bras bra;
private Bra(Bras bra) {
this.bra = bra;
}
public Bras getBra() {
return this.bra;
}
}
@Test
public void testJavaConstructorAnnotations() throws NotSerializableException {
Bra bra = new Bra(Bras.UNDERWIRE);
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
SerializationOutput ser = new SerializationOutput(factory1);
SerializedBytes<Object> bytes = ser.serialize(bra, TestSerializationContext.testSerializationContext);
}
}

View File

@ -0,0 +1,246 @@
package net.corda.serialization.internal.amqp;
import net.corda.core.serialization.ConstructorForDeserialization;
import net.corda.serialization.internal.AllWhitelist;
import net.corda.core.serialization.SerializedBytes;
import net.corda.serialization.internal.amqp.testutils.TestSerializationContext;
import org.apache.qpid.proton.codec.DecoderImpl;
import org.apache.qpid.proton.codec.EncoderImpl;
import org.junit.Test;
import javax.annotation.Nonnull;
import java.io.NotSerializableException;
import java.nio.ByteBuffer;
import java.util.Objects;
import static org.junit.Assert.assertTrue;
public class JavaSerializationOutputTests {
static class Foo {
private final String bob;
private final int count;
public Foo(String msg, long count) {
this.bob = msg;
this.count = (int) count;
}
@ConstructorForDeserialization
private Foo(String fred, int count) {
this.bob = fred;
this.count = count;
}
@SuppressWarnings("unused")
public String getFred() {
return bob;
}
@SuppressWarnings("unused")
public int getCount() {
return count;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Foo foo = (Foo) o;
if (count != foo.count) return false;
return bob != null ? bob.equals(foo.bob) : foo.bob == null;
}
@Override
public int hashCode() {
int result = bob != null ? bob.hashCode() : 0;
result = 31 * result + count;
return result;
}
}
static class UnAnnotatedFoo {
private final String bob;
private final int count;
private UnAnnotatedFoo(String fred, int count) {
this.bob = fred;
this.count = count;
}
@SuppressWarnings("unused")
public String getFred() {
return bob;
}
@SuppressWarnings("unused")
public int getCount() {
return count;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UnAnnotatedFoo foo = (UnAnnotatedFoo) o;
if (count != foo.count) return false;
return bob != null ? bob.equals(foo.bob) : foo.bob == null;
}
@Override
public int hashCode() {
int result = bob != null ? bob.hashCode() : 0;
result = 31 * result + count;
return result;
}
}
static class BoxedFoo {
private final String fred;
private final Integer count;
private BoxedFoo(String fred, Integer count) {
this.fred = fred;
this.count = count;
}
@SuppressWarnings("unused")
public String getFred() {
return fred;
}
@SuppressWarnings("unused")
public Integer getCount() {
return count;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BoxedFoo boxedFoo = (BoxedFoo) o;
if (fred != null ? !fred.equals(boxedFoo.fred) : boxedFoo.fred != null) return false;
return count != null ? count.equals(boxedFoo.count) : boxedFoo.count == null;
}
@Override
public int hashCode() {
int result = fred != null ? fred.hashCode() : 0;
result = 31 * result + (count != null ? count.hashCode() : 0);
return result;
}
}
static class BoxedFooNotNull {
private final String fred;
private final Integer count;
private BoxedFooNotNull(String fred, Integer count) {
this.fred = fred;
this.count = count;
}
public String getFred() {
return fred;
}
@Nonnull
public Integer getCount() {
return count;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BoxedFooNotNull boxedFoo = (BoxedFooNotNull) o;
if (fred != null ? !fred.equals(boxedFoo.fred) : boxedFoo.fred != null) return false;
return count != null ? count.equals(boxedFoo.count) : boxedFoo.count == null;
}
@Override
public int hashCode() {
int result = fred != null ? fred.hashCode() : 0;
result = 31 * result + (count != null ? count.hashCode() : 0);
return result;
}
}
private Object serdes(Object obj) throws NotSerializableException {
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
FingerPrinter fingerPrinter = new SerializerFingerPrinter();
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter,
fingerPrinter);
SerializerFactory factory2 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter,
fingerPrinter);
SerializationOutput ser = new SerializationOutput(factory1);
SerializedBytes<Object> bytes = ser.serialize(obj, TestSerializationContext.testSerializationContext);
DecoderImpl decoder = new DecoderImpl();
decoder.register(Envelope.Companion.getDESCRIPTOR(), Envelope.Companion);
decoder.register(Schema.Companion.getDESCRIPTOR(), Schema.Companion);
decoder.register(Descriptor.Companion.getDESCRIPTOR(), Descriptor.Companion);
decoder.register(Field.Companion.getDESCRIPTOR(), Field.Companion);
decoder.register(CompositeType.Companion.getDESCRIPTOR(), CompositeType.Companion);
decoder.register(Choice.Companion.getDESCRIPTOR(), Choice.Companion);
decoder.register(RestrictedType.Companion.getDESCRIPTOR(), RestrictedType.Companion);
decoder.register(Transform.Companion.getDESCRIPTOR(), Transform.Companion);
decoder.register(TransformsSchema.Companion.getDESCRIPTOR(), TransformsSchema.Companion);
new EncoderImpl(decoder);
decoder.setByteBuffer(ByteBuffer.wrap(bytes.getBytes(), 8, bytes.getSize() - 8));
Envelope result = (Envelope) decoder.readObject();
assertTrue(result != null);
DeserializationInput des = new DeserializationInput(factory2);
Object desObj = des.deserialize(bytes, Object.class, TestSerializationContext.testSerializationContext);
assertTrue(Objects.deepEquals(obj, desObj));
// Now repeat with a re-used factory
SerializationOutput ser2 = new SerializationOutput(factory1);
DeserializationInput des2 = new DeserializationInput(factory1);
Object desObj2 = des2.deserialize(ser2.serialize(obj, TestSerializationContext.testSerializationContext),
Object.class, TestSerializationContext.testSerializationContext);
assertTrue(Objects.deepEquals(obj, desObj2));
// TODO: check schema is as expected
return desObj2;
}
@Test
public void testJavaConstructorAnnotations() throws NotSerializableException {
Foo obj = new Foo("Hello World!", 123);
serdes(obj);
}
@Test
public void testJavaConstructorWithoutAnnotations() throws NotSerializableException {
UnAnnotatedFoo obj = new UnAnnotatedFoo("Hello World!", 123);
serdes(obj);
}
@Test
public void testBoxedTypes() throws NotSerializableException {
BoxedFoo obj = new BoxedFoo("Hello World!", 123);
serdes(obj);
}
@Test
public void testBoxedTypesNotNull() throws NotSerializableException {
BoxedFooNotNull obj = new BoxedFooNotNull("Hello World!", 123);
serdes(obj);
}
}

View File

@ -0,0 +1,141 @@
package net.corda.serialization.internal.amqp;
import net.corda.core.serialization.CordaSerializable;
import net.corda.core.serialization.SerializedBytes;
import net.corda.serialization.internal.AllWhitelist;
import net.corda.serialization.internal.amqp.testutils.TestSerializationContext;
import org.junit.Assert;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
public class ListsSerializationJavaTest {
@CordaSerializable
interface Parent {
}
public static class Child implements Parent {
private final int value;
Child(int value) {
this.value = value;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Child child = (Child) o;
return value == child.value;
}
@Override
public int hashCode() {
return value;
}
// Needed to show that there is a property called "value"
@SuppressWarnings("unused")
public int getValue() {
return value;
}
}
@CordaSerializable
public static class CovariantContainer<T extends Parent> {
private final List<T> content;
CovariantContainer(List<T> content) {
this.content = content;
}
@Override
@SuppressWarnings("unchecked")
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CovariantContainer<T> that = (CovariantContainer<T>) o;
return content != null ? content.equals(that.content) : that.content == null;
}
@Override
public int hashCode() {
return content != null ? content.hashCode() : 0;
}
// Needed to show that there is a property called "content"
@SuppressWarnings("unused")
public List<T> getContent() {
return content;
}
}
@CordaSerializable
public static class CovariantContainer2 {
private final List<? extends Parent> content;
CovariantContainer2(List<? extends Parent> content) {
this.content = content;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CovariantContainer2 that = (CovariantContainer2) o;
return content != null ? content.equals(that.content) : that.content == null;
}
@Override
public int hashCode() {
return content != null ? content.hashCode() : 0;
}
// Needed to show that there is a property called "content"
@SuppressWarnings("unused")
public List<? extends Parent> getContent() {
return content;
}
}
@Test
public void checkCovariance() throws Exception {
List<Child> payload = new ArrayList<>();
payload.add(new Child(1));
payload.add(new Child(2));
CovariantContainer<Child> container = new CovariantContainer<>(payload);
assertEqualAfterRoundTripSerialization(container, CovariantContainer.class);
}
@Test
public void checkCovariance2() throws Exception {
List<Child> payload = new ArrayList<>();
payload.add(new Child(1));
payload.add(new Child(2));
CovariantContainer2 container = new CovariantContainer2(payload);
assertEqualAfterRoundTripSerialization(container, CovariantContainer2.class);
}
// Have to have own version as Kotlin inline functions cannot be easily called from Java
private static <T> void assertEqualAfterRoundTripSerialization(T container, Class<T> clazz) throws Exception {
EvolutionSerializerGetterBase evolutionSerializerGetter = new EvolutionSerializerGetter();
FingerPrinter fingerPrinter = new SerializerFingerPrinter();
SerializerFactory factory1 = new SerializerFactory(
AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
evolutionSerializerGetter,
fingerPrinter);
SerializationOutput ser = new SerializationOutput(factory1);
SerializedBytes<Object> bytes = ser.serialize(container, TestSerializationContext.testSerializationContext);
DeserializationInput des = new DeserializationInput(factory1);
T deserialized = des.deserialize(bytes, clazz, TestSerializationContext.testSerializationContext);
Assert.assertEquals(container, deserialized);
}
}

View File

@ -0,0 +1,365 @@
package net.corda.serialization.internal.amqp;
import net.corda.core.serialization.SerializedBytes;
import net.corda.serialization.internal.AllWhitelist;
import net.corda.serialization.internal.amqp.testutils.TestSerializationContext;
import org.assertj.core.api.Assertions;
import org.junit.Test;
import static org.junit.Assert.*;
import java.io.NotSerializableException;
import java.util.ArrayList;
import java.util.List;
public class SetterConstructorTests {
static class C {
private int a;
private int b;
private int c;
public int getA() { return a; }
public int getB() { return b; }
public int getC() { return c; }
public void setA(int a) { this.a = a; }
public void setB(int b) { this.b = b; }
public void setC(int c) { this.c = c; }
}
static class C2 {
private int a;
private int b;
private int c;
public int getA() { return a; }
public int getB() { return b; }
public int getC() { return c; }
public void setA(int a) { this.a = a; }
public void setB(int b) { this.b = b; }
}
static class C3 {
private int a;
private int b;
private int c;
public int getA() { return a; }
public int getC() { return c; }
public void setA(int a) { this.a = a; }
public void setB(int b) { this.b = b; }
public void setC(int c) { this.c = c; }
}
static class C4 {
private int a;
private int b;
private int c;
public int getA() { return a; }
protected int getB() { return b; }
public int getC() { return c; }
private void setA(int a) { this.a = a; }
public void setB(int b) { this.b = b; }
public void setC(int c) { this.c = c; }
}
static class CIntList {
private List<Integer> l;
public List getL() { return l; }
public void setL(List<Integer> l) { this.l = l; }
}
static class Inner1 {
private String a;
public Inner1(String a) { this.a = a; }
public String getA() { return this.a; }
}
static class Inner2 {
private Double a;
public Double getA() { return this.a; }
public void setA(Double a) { this.a = a; }
}
static class Outer {
private Inner1 a;
private String b;
private Inner2 c;
public Inner1 getA() { return a; }
public String getB() { return b; }
public Inner2 getC() { return c; }
public void setA(Inner1 a) { this.a = a; }
public void setB(String b) { this.b = b; }
public void setC(Inner2 c) { this.c = c; }
}
static class TypeMismatch {
private Integer a;
public void setA(Integer a) { this.a = a; }
public String getA() { return this.a.toString(); }
}
static class TypeMismatch2 {
private Integer a;
public void setA(String a) { this.a = Integer.parseInt(a); }
public Integer getA() { return this.a; }
}
// despite having no constructor we should still be able to serialise an instance of C
@Test
public void serialiseC() throws NotSerializableException {
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
FingerPrinter fingerPrinter = new SerializerFingerPrinter();
SerializerFactory factory1 = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter,
fingerPrinter);
SerializationOutput ser = new SerializationOutput(factory1);
C c1 = new C();
c1.setA(1);
c1.setB(2);
c1.setC(3);
Schema schemas = ser.serializeAndReturnSchema(c1, TestSerializationContext.testSerializationContext).component2();
assertEquals(1, schemas.component1().size());
assertEquals(this.getClass().getName() + "$C", schemas.component1().get(0).getName());
CompositeType ct = (CompositeType) schemas.component1().get(0);
assertEquals(3, ct.getFields().size());
assertEquals("a", ct.getFields().get(0).getName());
assertEquals("b", ct.getFields().get(1).getName());
assertEquals("c", ct.getFields().get(2).getName());
// No setter for c so should only serialise two properties
C2 c2 = new C2();
c2.setA(1);
c2.setB(2);
schemas = ser.serializeAndReturnSchema(c2, TestSerializationContext.testSerializationContext).component2();
assertEquals(1, schemas.component1().size());
assertEquals(this.getClass().getName() + "$C2", schemas.component1().get(0).getName());
ct = (CompositeType) schemas.component1().get(0);
// With no setter for c we should only serialise a and b into the stream
assertEquals(2, ct.getFields().size());
assertEquals("a", ct.getFields().get(0).getName());
assertEquals("b", ct.getFields().get(1).getName());
// no getter for b so shouldn't serialise it,thus only a and c should apper in the envelope
C3 c3 = new C3();
c3.setA(1);
c3.setB(2);
c3.setC(3);
schemas = ser.serializeAndReturnSchema(c3, TestSerializationContext.testSerializationContext).component2();
assertEquals(1, schemas.component1().size());
assertEquals(this.getClass().getName() + "$C3", schemas.component1().get(0).getName());
ct = (CompositeType) schemas.component1().get(0);
// With no setter for c we should only serialise a and b into the stream
assertEquals(2, ct.getFields().size());
assertEquals("a", ct.getFields().get(0).getName());
assertEquals("c", ct.getFields().get(1).getName());
C4 c4 = new C4();
c4.setA(1);
c4.setB(2);
c4.setC(3);
schemas = ser.serializeAndReturnSchema(c4, TestSerializationContext.testSerializationContext).component2();
assertEquals(1, schemas.component1().size());
assertEquals(this.getClass().getName() + "$C4", schemas.component1().get(0).getName());
ct = (CompositeType) schemas.component1().get(0);
// With non public visibility on a setter and getter for a and b, only c should be serialised
assertEquals(1, ct.getFields().size());
assertEquals("c", ct.getFields().get(0).getName());
}
@Test
public void deserialiseC() throws NotSerializableException {
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
FingerPrinter fingerPrinter = new SerializerFingerPrinter();
SerializerFactory factory1 = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter,
fingerPrinter);
C cPre1 = new C();
int a = 1;
int b = 2;
int c = 3;
cPre1.setA(a);
cPre1.setB(b);
cPre1.setC(c);
SerializedBytes bytes = new SerializationOutput(factory1).serialize(cPre1, TestSerializationContext.testSerializationContext);
C cPost1 = new DeserializationInput(factory1).deserialize(bytes, C.class, TestSerializationContext.testSerializationContext);
assertEquals(a, cPost1.a);
assertEquals(b, cPost1.b);
assertEquals(c, cPost1.c);
C2 cPre2 = new C2();
cPre2.setA(1);
cPre2.setB(2);
C2 cPost2 = new DeserializationInput(factory1).deserialize(new SerializationOutput(factory1).serialize(cPre2, TestSerializationContext.testSerializationContext),
C2.class, TestSerializationContext.testSerializationContext);
assertEquals(a, cPost2.a);
assertEquals(b, cPost2.b);
// no setter for c means nothing will be serialised and thus it will have the default value of zero
// set
assertEquals(0, cPost2.c);
C3 cPre3 = new C3();
cPre3.setA(1);
cPre3.setB(2);
cPre3.setC(3);
C3 cPost3 = new DeserializationInput(factory1).deserialize(
new SerializationOutput(factory1).serialize(cPre3, TestSerializationContext.testSerializationContext),
C3.class, TestSerializationContext.testSerializationContext);
assertEquals(a, cPost3.a);
// no getter for b means, as before, it'll have been not set and will thus be defaulted to 0
assertEquals(0, cPost3.b);
assertEquals(c, cPost3.c);
C4 cPre4 = new C4();
cPre4.setA(1);
cPre4.setB(2);
cPre4.setC(3);
C4 cPost4 = new DeserializationInput(factory1).deserialize(
new SerializationOutput(factory1).serialize(cPre4,
TestSerializationContext.testSerializationContext),
C4.class,
TestSerializationContext.testSerializationContext);
assertEquals(0, cPost4.a);
assertEquals(0, cPost4.b);
assertEquals(c, cPost4.c);
}
@Test
public void serialiseOuterAndInner() throws NotSerializableException {
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
FingerPrinter fingerPrinter = new SerializerFingerPrinter();
SerializerFactory factory1 = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter,
fingerPrinter);
Inner1 i1 = new Inner1("Hello");
Inner2 i2 = new Inner2();
i2.setA(10.5);
Outer o = new Outer();
o.setA(i1);
o.setB("World");
o.setC(i2);
Outer post = new DeserializationInput(factory1).deserialize(
new SerializationOutput(factory1).serialize(
o, TestSerializationContext.testSerializationContext),
Outer.class, TestSerializationContext.testSerializationContext);
assertEquals("Hello", post.a.a);
assertEquals("World", post.b);
assertEquals((Double)10.5, post.c.a);
}
@Test
public void typeMistmatch() {
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
FingerPrinter fingerPrinter = new SerializerFingerPrinter();
SerializerFactory factory1 = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter,
fingerPrinter);
TypeMismatch tm = new TypeMismatch();
tm.setA(10);
assertEquals("10", tm.getA());
Assertions.assertThatThrownBy(() -> new SerializationOutput(factory1).serialize(tm,
TestSerializationContext.testSerializationContext)).isInstanceOf (
NotSerializableException.class);
}
@Test
public void typeMistmatch2() {
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
FingerPrinter fingerPrinter = new SerializerFingerPrinter();
SerializerFactory factory1 = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter,
fingerPrinter);
TypeMismatch2 tm = new TypeMismatch2();
tm.setA("10");
assertEquals((Integer)10, tm.getA());
Assertions.assertThatThrownBy(() -> new SerializationOutput(factory1).serialize(tm,
TestSerializationContext.testSerializationContext)).isInstanceOf(
NotSerializableException.class);
}
// This not blowing up means it's working
@Test
public void intList() throws NotSerializableException {
CIntList cil = new CIntList();
List<Integer> l = new ArrayList<>();
l.add(1);
l.add(2);
l.add(3);
cil.setL(l);
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
FingerPrinter fingerPrinter = new SerializerFingerPrinter();
SerializerFactory factory1 = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter,
fingerPrinter);
// if we've got super / sub types on the setter vs the underlying type the wrong way around this will
// explode. See CORDA-1229 (https://r3-cev.atlassian.net/browse/CORDA-1229)
new DeserializationInput(factory1).deserialize(
new SerializationOutput(factory1).serialize(
cil, TestSerializationContext.testSerializationContext),
CIntList.class,
TestSerializationContext.testSerializationContext);
}
}

View File

@ -0,0 +1,24 @@
package net.corda.serialization.internal.amqp.testutils;
import net.corda.core.serialization.SerializationContext;
import net.corda.core.utilities.ByteSequence;
import net.corda.serialization.internal.AllWhitelist;
import net.corda.serialization.internal.SerializationContextImpl;
import java.util.HashMap;
import java.util.Map;
public class TestSerializationContext {
private static Map<Object, Object> serializationProperties = new HashMap<>();
public static SerializationContext testSerializationContext = new SerializationContextImpl(
ByteSequence.of(new byte[] { 'c', 'o', 'r', 'd', 'a', (byte)0, (byte)0, (byte)1}),
ClassLoader.getSystemClassLoader(),
AllWhitelist.INSTANCE,
serializationProperties,
false,
SerializationContext.UseCase.Testing,
null);
}

View File

@ -0,0 +1,17 @@
package net.corda.nodeapi
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.PartyAndReference
import net.corda.core.identity.Party
import net.corda.core.transactions.TransactionBuilder
/**
* This interface deliberately mirrors the one in the finance:isolated module.
* We will actually link [AnotherDummyContract] against this interface rather
* than the one inside isolated.jar, which means we won't need to use reflection
* to execute the contract's generateInitial() method.
*/
interface DummyContractBackdoor {
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder
fun inspectState(state: ContractState): Int
}

View File

@ -0,0 +1,380 @@
package net.corda.serialization.internal
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.Attachment
import net.corda.core.contracts.Contract
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.declaredField
import net.corda.core.internal.toWireTransaction
import net.corda.core.node.ServiceHub
import net.corda.core.node.services.AttachmentStorage
import net.corda.core.serialization.*
import net.corda.core.utilities.ByteSequence
import net.corda.core.utilities.OpaqueBytes
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.nodeapi.DummyContractBackdoor
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity
import net.corda.testing.internal.MockCordappConfigProvider
import net.corda.testing.internal.kryoSpecific
import net.corda.testing.internal.rigorousMock
import net.corda.testing.services.MockAttachmentStorage
import org.apache.commons.io.IOUtils
import org.junit.Assert.*
import org.junit.Rule
import org.junit.Test
import java.io.ByteArrayOutputStream
import java.net.URL
import java.net.URLClassLoader
import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry
import kotlin.test.assertFailsWith
class AttachmentsClassLoaderTests {
companion object {
val ISOLATED_CONTRACTS_JAR_PATH: URL = AttachmentsClassLoaderTests::class.java.getResource("isolated.jar")
private const val ISOLATED_CONTRACT_CLASS_NAME = "net.corda.finance.contracts.isolated.AnotherDummyContract"
private val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
private val MEGA_CORP = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")).party
private fun SerializationContext.withAttachmentStorage(attachmentStorage: AttachmentStorage): SerializationContext {
val serviceHub = rigorousMock<ServiceHub>()
doReturn(attachmentStorage).whenever(serviceHub).attachments
return this.withServiceHub(serviceHub)
}
private fun SerializationContext.withServiceHub(serviceHub: ServiceHub): SerializationContext {
return this.withTokenContext(SerializeAsTokenContextImpl(serviceHub) {}).withProperty(attachmentsClassLoaderEnabledPropertyName, true)
}
}
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
private val attachments = MockAttachmentStorage()
private val networkParameters = testNetworkParameters()
private val cordappProvider = CordappProviderImpl(CordappLoader.createDevMode(listOf(ISOLATED_CONTRACTS_JAR_PATH)), MockCordappConfigProvider(), attachments, networkParameters.whitelistedContractImplementations)
private val cordapp get() = cordappProvider.cordapps.first()
private val attachmentId get() = cordappProvider.getCordappAttachmentId(cordapp)!!
private val appContext get() = cordappProvider.getAppContext(cordapp)
private val serviceHub = rigorousMock<ServiceHub>().also {
doReturn(attachments).whenever(it).attachments
doReturn(cordappProvider).whenever(it).cordappProvider
doReturn(networkParameters).whenever(it).networkParameters
}
// These ClassLoaders work together to load 'AnotherDummyContract' in a disposable way, such that even though
// the class may be on the unit test class path (due to default IDE settings, etc), it won't be loaded into the
// regular app classloader but rather than ClassLoaderForTests. This helps keep our environment clean and
// ensures we have precise control over where it's loaded.
object FilteringClassLoader : ClassLoader() {
@Throws(ClassNotFoundException::class)
override fun loadClass(name: String, resolve: Boolean): Class<*> {
if ("AnotherDummyContract" in name) {
throw ClassNotFoundException(name)
}
return super.loadClass(name, resolve)
}
}
class ClassLoaderForTests : URLClassLoader(arrayOf(ISOLATED_CONTRACTS_JAR_PATH), FilteringClassLoader)
@Test
fun `dynamically load AnotherDummyContract from isolated contracts jar`() {
ClassLoaderForTests().use { child ->
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child)
val contract = contractClass.newInstance() as Contract
assertEquals("helloworld", contract.declaredField<Any?>("magicString").value)
}
}
private fun fakeAttachment(filepath: String, content: String): ByteArray {
val bs = ByteArrayOutputStream()
JarOutputStream(bs).use { js ->
js.putNextEntry(ZipEntry(filepath))
js.writer().apply { append(content); flush() }
js.closeEntry()
}
return bs.toByteArray()
}
private fun readAttachment(attachment: Attachment, filepath: String): ByteArray {
ByteArrayOutputStream().use {
attachment.extractFile(filepath, it)
return it.toByteArray()
}
}
@Test
fun `test MockAttachmentStorage open as jar`() {
val storage = attachments
val key = attachmentId
val attachment = storage.openAttachment(key)!!
val jar = attachment.openAsJAR()
assertNotNull(jar.nextEntry)
}
@Test
@Suppress("DEPRECATION")
fun `test overlapping file exception`() {
val storage = attachments
val att0 = attachmentId
val att1 = storage.importAttachment(fakeAttachment("file.txt", "some data").inputStream())
val att2 = storage.importAttachment(fakeAttachment("file.txt", "some other data").inputStream())
assertFailsWith(AttachmentsClassLoader.OverlappingAttachments::class) {
AttachmentsClassLoader(arrayOf(att0, att1, att2).map { storage.openAttachment(it)!! })
}
}
@Test
@Suppress("DEPRECATION")
fun basic() {
val storage = attachments
val att0 = attachmentId
val att1 = storage.importAttachment(fakeAttachment("file1.txt", "some data").inputStream())
val att2 = storage.importAttachment(fakeAttachment("file2.txt", "some other data").inputStream())
val cl = AttachmentsClassLoader(arrayOf(att0, att1, att2).map { storage.openAttachment(it)!! })
val txt = IOUtils.toString(cl.getResourceAsStream("file1.txt"), Charsets.UTF_8.name())
assertEquals("some data", txt)
}
@Test
@Suppress("DEPRECATION")
fun `Check platform independent path handling in attachment jars`() {
val storage = MockAttachmentStorage()
val att1 = storage.importAttachment(fakeAttachment("/folder1/foldera/file1.txt", "some data").inputStream())
val att2 = storage.importAttachment(fakeAttachment("\\folder1\\folderb\\file2.txt", "some other data").inputStream())
val data1a = readAttachment(storage.openAttachment(att1)!!, "/folder1/foldera/file1.txt")
assertArrayEquals("some data".toByteArray(), data1a)
val data1b = readAttachment(storage.openAttachment(att1)!!, "\\folder1\\foldera\\file1.txt")
assertArrayEquals("some data".toByteArray(), data1b)
val data2a = readAttachment(storage.openAttachment(att2)!!, "\\folder1\\folderb\\file2.txt")
assertArrayEquals("some other data".toByteArray(), data2a)
val data2b = readAttachment(storage.openAttachment(att2)!!, "/folder1/folderb/file2.txt")
assertArrayEquals("some other data".toByteArray(), data2b)
}
@Test
@Suppress("DEPRECATION")
fun `loading class AnotherDummyContract`() {
val storage = attachments
val att0 = attachmentId
val att1 = storage.importAttachment(fakeAttachment("file1.txt", "some data").inputStream())
val att2 = storage.importAttachment(fakeAttachment("file2.txt", "some other data").inputStream())
val cl = AttachmentsClassLoader(arrayOf(att0, att1, att2).map { storage.openAttachment(it)!! }, FilteringClassLoader)
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, cl)
val contract = contractClass.newInstance() as Contract
assertEquals(cl, contract.javaClass.classLoader)
assertEquals("helloworld", contract.declaredField<Any?>("magicString").value)
}
private fun createContract2Cash(): Contract {
ClassLoaderForTests().use { cl ->
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, cl)
return contractClass.newInstance() as Contract
}
}
@Test
@Suppress("DEPRECATION")
fun `testing Kryo with ClassLoader (with top level class name)`() {
val contract = createContract2Cash()
val bytes = contract.serialize()
val storage = attachments
val att0 = attachmentId
val att1 = storage.importAttachment(fakeAttachment("file1.txt", "some data").inputStream())
val att2 = storage.importAttachment(fakeAttachment("file2.txt", "some other data").inputStream())
val cl = AttachmentsClassLoader(arrayOf(att0, att1, att2).map { storage.openAttachment(it)!! }, FilteringClassLoader)
val context = SerializationFactory.defaultFactory.defaultContext.withClassLoader(cl).withWhitelisted(contract.javaClass)
val state2 = bytes.deserialize(context = context)
assertTrue(state2.javaClass.classLoader is AttachmentsClassLoader)
assertNotNull(state2)
}
// top level wrapper
@CordaSerializable
class Data(val contract: Contract)
@Test
@Suppress("DEPRECATION")
fun `testing Kryo with ClassLoader (without top level class name)`() {
val data = Data(createContract2Cash())
assertNotNull(data.contract)
val context2 = SerializationFactory.defaultFactory.defaultContext.withWhitelisted(data.contract.javaClass)
val bytes = data.serialize(context = context2)
val storage = attachments
val att0 = attachmentId
val att1 = storage.importAttachment(fakeAttachment("file1.txt", "some data").inputStream())
val att2 = storage.importAttachment(fakeAttachment("file2.txt", "some other data").inputStream())
val cl = AttachmentsClassLoader(arrayOf(att0, att1, att2).map { storage.openAttachment(it)!! }, FilteringClassLoader)
val context = SerializationFactory.defaultFactory.defaultContext.withClassLoader(cl).withWhitelisted(Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, cl))
val state2 = bytes.deserialize(context = context)
assertEquals(cl, state2.contract.javaClass.classLoader)
assertNotNull(state2)
// We should be able to load same class from a different class loader and have them be distinct.
val cl2 = AttachmentsClassLoader(arrayOf(att0, att1, att2).map { storage.openAttachment(it)!! }, FilteringClassLoader)
val context3 = SerializationFactory.defaultFactory.defaultContext.withClassLoader(cl2).withWhitelisted(Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, cl2))
val state3 = bytes.deserialize(context = context3)
assertEquals(cl2, state3.contract.javaClass.classLoader)
assertNotNull(state3)
}
@Test
fun `test serialization of SecureHash`() {
val secureHash = SecureHash.randomSHA256()
val bytes = secureHash.serialize()
val copiedSecuredHash = bytes.deserialize()
assertEquals(secureHash, copiedSecuredHash)
}
@Test
fun `test serialization of OpaqueBytes`() {
val opaqueBytes = OpaqueBytes("0123456789".toByteArray())
val bytes = opaqueBytes.serialize()
val copiedOpaqueBytes = bytes.deserialize()
assertEquals(opaqueBytes, copiedOpaqueBytes)
}
@Test
fun `test serialization of sub-sequence OpaqueBytes`() {
val bytesSequence = ByteSequence.of("0123456789".toByteArray(), 3, 2)
val bytes = bytesSequence.serialize()
val copiedBytesSequence = bytes.deserialize()
assertEquals(bytesSequence, copiedBytesSequence)
}
@Test
fun `test serialization of WireTransaction with dynamically loaded contract`() {
val child = appContext.classLoader
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child)
val contract = contractClass.newInstance() as DummyContractBackdoor
val tx = contract.generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY)
val context = SerializationFactory.defaultFactory.defaultContext
.withWhitelisted(contract.javaClass)
.withWhitelisted(Class.forName("$ISOLATED_CONTRACT_CLASS_NAME\$State", true, child))
.withWhitelisted(Class.forName("$ISOLATED_CONTRACT_CLASS_NAME\$Commands\$Create", true, child))
.withServiceHub(serviceHub)
.withClassLoader(child)
val bytes = run {
val wireTransaction = tx.toWireTransaction(serviceHub, context)
wireTransaction.serialize(context = context)
}
val copiedWireTransaction = bytes.deserialize(context = context)
assertEquals(1, copiedWireTransaction.outputs.size)
// Contracts need to be loaded by the same classloader as the ContractState itself
val contractClassloader = copiedWireTransaction.getOutput(0).javaClass.classLoader
val contract2 = contractClassloader.loadClass(copiedWireTransaction.outputs.first().contract).newInstance() as DummyContractBackdoor
assertEquals(contract2.javaClass.classLoader, copiedWireTransaction.outputs[0].data.javaClass.classLoader)
assertEquals(42, contract2.inspectState(copiedWireTransaction.outputs[0].data))
}
@Test
fun `test deserialize of WireTransaction where contract cannot be found`() {
kryoSpecific("Kryo verifies/loads attachments on deserialization, whereas AMQP currently does not") {
ClassLoaderForTests().use { child ->
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child)
val contract = contractClass.newInstance() as DummyContractBackdoor
val tx = contract.generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY)
val attachmentRef = attachmentId
val bytes = run {
val outboundContext = SerializationFactory.defaultFactory.defaultContext
.withServiceHub(serviceHub)
.withClassLoader(child)
val wireTransaction = tx.toWireTransaction(serviceHub, outboundContext)
wireTransaction.serialize(context = outboundContext)
}
// use empty attachmentStorage
val e = assertFailsWith(MissingAttachmentsException::class) {
val mockAttStorage = MockAttachmentStorage()
val inboundContext = SerializationFactory.defaultFactory.defaultContext
.withAttachmentStorage(mockAttStorage)
.withAttachmentsClassLoader(listOf(attachmentRef))
bytes.deserialize(context = inboundContext)
if (mockAttStorage.openAttachment(attachmentRef) == null) {
throw MissingAttachmentsException(listOf(attachmentRef))
}
}
assertEquals(attachmentRef, e.ids.single())
}
}
}
@Test
fun `test loading a class from attachment during deserialization`() {
ClassLoaderForTests().use { child ->
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child)
val contract = contractClass.newInstance() as DummyContractBackdoor
val outboundContext = SerializationFactory.defaultFactory.defaultContext.withClassLoader(child)
val attachmentRef = attachmentId
// We currently ignore annotations in attachments, so manually whitelist.
val inboundContext = SerializationFactory
.defaultFactory
.defaultContext
.withWhitelisted(contract.javaClass)
.withServiceHub(serviceHub)
.withAttachmentsClassLoader(listOf(attachmentRef))
// Serialize with custom context to avoid populating the default context with the specially loaded class
val serialized = contract.serialize(context = outboundContext)
// Then deserialize with the attachment class loader associated with the attachment
serialized.deserialize(context = inboundContext)
}
}
@Test
fun `test loading a class with attachment missing during deserialization`() {
ClassLoaderForTests().use { child ->
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child)
val contract = contractClass.newInstance() as DummyContractBackdoor
val attachmentRef = SecureHash.randomSHA256()
val outboundContext = SerializationFactory.defaultFactory.defaultContext.withClassLoader(child)
// Serialize with custom context to avoid populating the default context with the specially loaded class
val serialized = contract.serialize(context = outboundContext)
// Then deserialize with the attachment class loader associated with the attachment
val e = assertFailsWith(MissingAttachmentsException::class) {
// We currently ignore annotations in attachments, so manually whitelist.
val inboundContext = SerializationFactory
.defaultFactory
.defaultContext
.withWhitelisted(contract.javaClass)
.withServiceHub(serviceHub)
.withAttachmentsClassLoader(listOf(attachmentRef))
serialized.deserialize(context = inboundContext)
}
assertEquals(attachmentRef, e.ids.single())
}
}
}

View File

@ -0,0 +1,105 @@
package net.corda.serialization.internal
import net.corda.core.contracts.ContractAttachment
import net.corda.core.identity.CordaX500Name
import net.corda.core.serialization.*
import net.corda.testing.contracts.DummyContract
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockServices
import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Assert.assertArrayEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import kotlin.test.assertEquals
class ContractAttachmentSerializerTest {
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
private lateinit var factory: SerializationFactory
private lateinit var context: SerializationContext
private lateinit var contextWithToken: SerializationContext
private val mockServices = MockServices(emptyList(), CordaX500Name("MegaCorp", "London", "GB"), rigorousMock())
@Before
fun setup() {
factory = testSerialization.serializationFactory
context = testSerialization.checkpointContext
contextWithToken = context.withTokenContext(SerializeAsTokenContextImpl(Any(), factory, context, mockServices))
}
@Test
fun `write contract attachment and read it back`() {
val contractAttachment = ContractAttachment(GeneratedAttachment(EMPTY_BYTE_ARRAY), DummyContract.PROGRAM_ID)
// no token context so will serialize the whole attachment
val serialized = contractAttachment.serialize(factory, context)
val deserialized = serialized.deserialize(factory, context)
assertEquals(contractAttachment.id, deserialized.attachment.id)
assertEquals(contractAttachment.contract, deserialized.contract)
assertEquals(contractAttachment.additionalContracts, deserialized.additionalContracts)
assertArrayEquals(contractAttachment.open().readBytes(), deserialized.open().readBytes())
}
@Test
fun `write contract attachment and read it back using token context`() {
val attachment = GeneratedAttachment("test".toByteArray())
mockServices.attachments.importAttachment(attachment.open())
val contractAttachment = ContractAttachment(attachment, DummyContract.PROGRAM_ID)
val serialized = contractAttachment.serialize(factory, contextWithToken)
val deserialized = serialized.deserialize(factory, contextWithToken)
assertEquals(contractAttachment.id, deserialized.attachment.id)
assertEquals(contractAttachment.contract, deserialized.contract)
assertEquals(contractAttachment.additionalContracts, deserialized.additionalContracts)
assertArrayEquals(contractAttachment.open().readBytes(), deserialized.open().readBytes())
}
@Test
fun `check only serialize attachment id and contract class name when using token context`() {
val largeAttachmentSize = 1024 * 1024
val attachment = GeneratedAttachment(ByteArray(largeAttachmentSize))
mockServices.attachments.importAttachment(attachment.open())
val contractAttachment = ContractAttachment(attachment, DummyContract.PROGRAM_ID)
val serialized = contractAttachment.serialize(factory, contextWithToken)
assertThat(serialized.size).isLessThan(largeAttachmentSize)
}
@Test
fun `throws when missing attachment when using token context`() {
val attachment = GeneratedAttachment("test".toByteArray())
// don't importAttachment in mockService
val contractAttachment = ContractAttachment(attachment, DummyContract.PROGRAM_ID)
val serialized = contractAttachment.serialize(factory, contextWithToken)
val deserialized = serialized.deserialize(factory, contextWithToken)
assertThatThrownBy { deserialized.attachment.open() }.isInstanceOf(MissingAttachmentsException::class.java)
}
@Test
fun `check attachment in deserialize is lazy loaded when using token context`() {
val attachment = GeneratedAttachment(EMPTY_BYTE_ARRAY)
// don't importAttachment in mockService
val contractAttachment = ContractAttachment(attachment, DummyContract.PROGRAM_ID)
val serialized = contractAttachment.serialize(factory, contextWithToken)
serialized.deserialize(factory, contextWithToken)
// MissingAttachmentsException thrown if we try to open attachment
}
}

View File

@ -0,0 +1,380 @@
package net.corda.serialization.internal
import com.esotericsoftware.kryo.*
import com.esotericsoftware.kryo.io.Input
import com.esotericsoftware.kryo.io.Output
import com.esotericsoftware.kryo.util.DefaultClassResolver
import com.esotericsoftware.kryo.util.MapReferenceResolver
import com.nhaarman.mockito_kotlin.any
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.verify
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.internal.DEPLOYED_CORDAPP_UPLOADER
import net.corda.core.node.services.AttachmentStorage
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializationContext
import net.corda.serialization.internal.kryo.CordaClassResolver
import net.corda.serialization.internal.kryo.CordaKryo
import net.corda.serialization.internal.kryo.kryoMagic
import net.corda.testing.internal.rigorousMock
import net.corda.testing.services.MockAttachmentStorage
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import java.lang.IllegalStateException
import java.sql.Connection
import java.util.*
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
@CordaSerializable
enum class Foo {
Bar {
override val value = 0
},
Stick {
override val value = 1
};
abstract val value: Int
}
enum class BadFood {
Mud {
override val value = -1
};
abstract val value: Int
}
@CordaSerializable
enum class Simple {
Easy
}
enum class BadSimple {
Nasty
}
@CordaSerializable
open class Element
open class SubElement : Element()
class SubSubElement : SubElement()
abstract class AbstractClass
interface Interface
@CordaSerializable
interface SerializableInterface
interface SerializableSubInterface : SerializableInterface
class NotSerializable
class SerializableViaInterface : SerializableInterface
open class SerializableViaSubInterface : SerializableSubInterface
class SerializableViaSuperSubInterface : SerializableViaSubInterface()
@CordaSerializable
class CustomSerializable : KryoSerializable {
override fun read(kryo: Kryo?, input: Input?) {
}
override fun write(kryo: Kryo?, output: Output?) {
}
}
@CordaSerializable
@DefaultSerializer(DefaultSerializableSerializer::class)
class DefaultSerializable
class DefaultSerializableSerializer : Serializer<DefaultSerializable>() {
override fun write(kryo: Kryo, output: Output, obj: DefaultSerializable) {
}
override fun read(kryo: Kryo, input: Input, type: Class<DefaultSerializable>): DefaultSerializable {
return DefaultSerializable()
}
}
object EmptyWhitelist : ClassWhitelist {
override fun hasListed(type: Class<*>): Boolean = false
}
class CordaClassResolverTests {
private companion object {
val emptyListClass = listOf<Any>().javaClass
val emptySetClass = setOf<Any>().javaClass
val emptyMapClass = mapOf<Any, Any>().javaClass
}
private val emptyWhitelistContext: SerializationContext = SerializationContextImpl(kryoMagic, this.javaClass.classLoader, EmptyWhitelist, emptyMap(), true, SerializationContext.UseCase.P2P, null)
private val allButBlacklistedContext: SerializationContext = SerializationContextImpl(kryoMagic, this.javaClass.classLoader, AllButBlacklisted, emptyMap(), true, SerializationContext.UseCase.P2P, null)
@Test
fun `Annotation on enum works for specialised entries`() {
CordaClassResolver(emptyWhitelistContext).getRegistration(Foo.Bar::class.java)
}
@Test(expected = KryoException::class)
fun `Unannotated specialised enum does not work`() {
CordaClassResolver(emptyWhitelistContext).getRegistration(BadFood.Mud::class.java)
}
@Test
fun `Annotation on simple enum works`() {
CordaClassResolver(emptyWhitelistContext).getRegistration(Simple.Easy::class.java)
}
@Test(expected = KryoException::class)
fun `Unannotated simple enum does not work`() {
CordaClassResolver(emptyWhitelistContext).getRegistration(BadSimple.Nasty::class.java)
}
@Test
fun `Annotation on array element works`() {
val values = arrayOf(Element())
CordaClassResolver(emptyWhitelistContext).getRegistration(values.javaClass)
}
@Test(expected = KryoException::class)
fun `Unannotated array elements do not work`() {
val values = arrayOf(NotSerializable())
CordaClassResolver(emptyWhitelistContext).getRegistration(values.javaClass)
}
@Test
fun `Annotation not needed on abstract class`() {
CordaClassResolver(emptyWhitelistContext).getRegistration(AbstractClass::class.java)
}
@Test
fun `Annotation not needed on interface`() {
CordaClassResolver(emptyWhitelistContext).getRegistration(Interface::class.java)
}
@Test
fun `Calling register method on modified Kryo does not consult the whitelist`() {
val kryo = CordaKryo(CordaClassResolver(emptyWhitelistContext))
kryo.register(NotSerializable::class.java)
}
@Test(expected = KryoException::class)
fun `Calling register method on unmodified Kryo does consult the whitelist`() {
val kryo = Kryo(CordaClassResolver(emptyWhitelistContext), MapReferenceResolver())
kryo.register(NotSerializable::class.java)
}
@Test(expected = KryoException::class)
fun `Annotation is needed without whitelisting`() {
CordaClassResolver(emptyWhitelistContext).getRegistration(NotSerializable::class.java)
}
@Test
fun `Annotation is not needed with whitelisting`() {
val resolver = CordaClassResolver(emptyWhitelistContext.withWhitelisted(NotSerializable::class.java))
resolver.getRegistration(NotSerializable::class.java)
}
@Test
fun `Annotation not needed on Object`() {
CordaClassResolver(emptyWhitelistContext).getRegistration(Object::class.java)
}
@Test
fun `Annotation not needed on primitive`() {
CordaClassResolver(emptyWhitelistContext).getRegistration(Integer.TYPE)
}
@Test(expected = KryoException::class)
fun `Annotation does not work for custom serializable`() {
CordaClassResolver(emptyWhitelistContext).getRegistration(CustomSerializable::class.java)
}
@Test(expected = KryoException::class)
fun `Annotation does not work in conjunction with Kryo annotation`() {
CordaClassResolver(emptyWhitelistContext).getRegistration(DefaultSerializable::class.java)
}
private fun importJar(storage: AttachmentStorage, uploader: String = DEPLOYED_CORDAPP_UPLOADER) = AttachmentsClassLoaderTests.ISOLATED_CONTRACTS_JAR_PATH.openStream().use { storage.importAttachment(it, uploader, "") }
@Test(expected = KryoException::class)
fun `Annotation does not work in conjunction with AttachmentClassLoader annotation`() {
val storage = MockAttachmentStorage()
val attachmentHash = importJar(storage)
val classLoader = AttachmentsClassLoader(arrayOf(attachmentHash).map { storage.openAttachment(it)!! })
val attachedClass = Class.forName("net.corda.finance.contracts.isolated.AnotherDummyContract", true, classLoader)
CordaClassResolver(emptyWhitelistContext).getRegistration(attachedClass)
}
@Test(expected = IllegalArgumentException::class)
fun `Attempt to load contract attachment with the incorrect uploader should fails with IAE`() {
val storage = MockAttachmentStorage()
val attachmentHash = importJar(storage, "some_uploader")
val classLoader = AttachmentsClassLoader(arrayOf(attachmentHash).map { storage.openAttachment(it)!! })
val attachedClass = Class.forName("net.corda.finance.contracts.isolated.AnotherDummyContract", true, classLoader)
CordaClassResolver(emptyWhitelistContext).getRegistration(attachedClass)
}
@Test
fun `Annotation is inherited from interfaces`() {
CordaClassResolver(emptyWhitelistContext).getRegistration(SerializableViaInterface::class.java)
CordaClassResolver(emptyWhitelistContext).getRegistration(SerializableViaSubInterface::class.java)
}
@Test
fun `Annotation is inherited from superclass`() {
CordaClassResolver(emptyWhitelistContext).getRegistration(SubElement::class.java)
CordaClassResolver(emptyWhitelistContext).getRegistration(SubSubElement::class.java)
CordaClassResolver(emptyWhitelistContext).getRegistration(SerializableViaSuperSubInterface::class.java)
}
// Blacklist tests. Note: leave the variable public or else expected messages do not work correctly
@get:Rule
val expectedEx = ExpectedException.none()!!
@Test
fun `Check blacklisted class`() {
expectedEx.expect(IllegalStateException::class.java)
expectedEx.expectMessage("Class java.util.HashSet is blacklisted, so it cannot be used in serialization.")
val resolver = CordaClassResolver(allButBlacklistedContext)
// HashSet is blacklisted.
resolver.getRegistration(HashSet::class.java)
}
@Test
fun `Kotlin EmptyList not registered`() {
val resolver = CordaClassResolver(allButBlacklistedContext)
assertNull(resolver.getRegistration(emptyListClass))
}
@Test
fun `Kotlin EmptyList registers as Java emptyList`() {
val javaEmptyListClass = Collections.emptyList<Any>().javaClass
val kryo = rigorousMock<Kryo>()
val resolver = CordaClassResolver(allButBlacklistedContext).apply { setKryo(kryo) }
doReturn(DefaultSerializableSerializer()).whenever(kryo).getDefaultSerializer(javaEmptyListClass)
doReturn(false).whenever(kryo).references
doReturn(false).whenever(kryo).references = any()
val registration = resolver.registerImplicit(emptyListClass)
assertNotNull(registration)
assertEquals(javaEmptyListClass, registration.type)
assertEquals(DefaultClassResolver.NAME.toInt(), registration.id)
verify(kryo).getDefaultSerializer(javaEmptyListClass)
assertEquals(registration, resolver.getRegistration(emptyListClass))
}
@Test
fun `Kotlin EmptySet not registered`() {
val resolver = CordaClassResolver(allButBlacklistedContext)
assertNull(resolver.getRegistration(emptySetClass))
}
@Test
fun `Kotlin EmptySet registers as Java emptySet`() {
val javaEmptySetClass = Collections.emptySet<Any>().javaClass
val kryo = rigorousMock<Kryo>()
val resolver = CordaClassResolver(allButBlacklistedContext).apply { setKryo(kryo) }
doReturn(DefaultSerializableSerializer()).whenever(kryo).getDefaultSerializer(javaEmptySetClass)
doReturn(false).whenever(kryo).references
doReturn(false).whenever(kryo).references = any()
val registration = resolver.registerImplicit(emptySetClass)
assertNotNull(registration)
assertEquals(javaEmptySetClass, registration.type)
assertEquals(DefaultClassResolver.NAME.toInt(), registration.id)
verify(kryo).getDefaultSerializer(javaEmptySetClass)
assertEquals(registration, resolver.getRegistration(emptySetClass))
}
@Test
fun `Kotlin EmptyMap not registered`() {
val resolver = CordaClassResolver(allButBlacklistedContext)
assertNull(resolver.getRegistration(emptyMapClass))
}
@Test
fun `Kotlin EmptyMap registers as Java emptyMap`() {
val javaEmptyMapClass = Collections.emptyMap<Any, Any>().javaClass
val kryo = rigorousMock<Kryo>()
val resolver = CordaClassResolver(allButBlacklistedContext).apply { setKryo(kryo) }
doReturn(DefaultSerializableSerializer()).whenever(kryo).getDefaultSerializer(javaEmptyMapClass)
doReturn(false).whenever(kryo).references
doReturn(false).whenever(kryo).references = any()
val registration = resolver.registerImplicit(emptyMapClass)
assertNotNull(registration)
assertEquals(javaEmptyMapClass, registration.type)
assertEquals(DefaultClassResolver.NAME.toInt(), registration.id)
verify(kryo).getDefaultSerializer(javaEmptyMapClass)
assertEquals(registration, resolver.getRegistration(emptyMapClass))
}
open class SubHashSet<E> : HashSet<E>()
@Test
fun `Check blacklisted subclass`() {
expectedEx.expect(IllegalStateException::class.java)
expectedEx.expectMessage("The superclass java.util.HashSet of net.corda.serialization.internal.CordaClassResolverTests\$SubHashSet is blacklisted, so it cannot be used in serialization.")
val resolver = CordaClassResolver(allButBlacklistedContext)
// SubHashSet extends the blacklisted HashSet.
resolver.getRegistration(SubHashSet::class.java)
}
class SubSubHashSet<E> : SubHashSet<E>()
@Test
fun `Check blacklisted subsubclass`() {
expectedEx.expect(IllegalStateException::class.java)
expectedEx.expectMessage("The superclass java.util.HashSet of net.corda.serialization.internal.CordaClassResolverTests\$SubSubHashSet is blacklisted, so it cannot be used in serialization.")
val resolver = CordaClassResolver(allButBlacklistedContext)
// SubSubHashSet extends SubHashSet, which extends the blacklisted HashSet.
resolver.getRegistration(SubSubHashSet::class.java)
}
class ConnectionImpl(private val connection: Connection) : Connection by connection
@Test
fun `Check blacklisted interface impl`() {
expectedEx.expect(IllegalStateException::class.java)
expectedEx.expectMessage("The superinterface java.sql.Connection of net.corda.serialization.internal.CordaClassResolverTests\$ConnectionImpl is blacklisted, so it cannot be used in serialization.")
val resolver = CordaClassResolver(allButBlacklistedContext)
// ConnectionImpl implements blacklisted Connection.
resolver.getRegistration(ConnectionImpl::class.java)
}
interface SubConnection : Connection
class SubConnectionImpl(private val subConnection: SubConnection) : SubConnection by subConnection
@Test
fun `Check blacklisted super-interface impl`() {
expectedEx.expect(IllegalStateException::class.java)
expectedEx.expectMessage("The superinterface java.sql.Connection of net.corda.serialization.internal.CordaClassResolverTests\$SubConnectionImpl is blacklisted, so it cannot be used in serialization.")
val resolver = CordaClassResolver(allButBlacklistedContext)
// SubConnectionImpl implements SubConnection, which extends the blacklisted Connection.
resolver.getRegistration(SubConnectionImpl::class.java)
}
@Test
fun `Check forcibly allowed`() {
val resolver = CordaClassResolver(allButBlacklistedContext)
// LinkedHashSet is allowed for serialization.
resolver.getRegistration(LinkedHashSet::class.java)
}
@CordaSerializable
class CordaSerializableHashSet<E> : HashSet<E>()
@Test
fun `Check blacklist precedes CordaSerializable`() {
expectedEx.expect(IllegalStateException::class.java)
expectedEx.expectMessage("The superclass java.util.HashSet of net.corda.serialization.internal.CordaClassResolverTests\$CordaSerializableHashSet is blacklisted, so it cannot be used in serialization.")
val resolver = CordaClassResolver(allButBlacklistedContext)
// CordaSerializableHashSet is @CordaSerializable, but extends the blacklisted HashSet.
resolver.getRegistration(CordaSerializableHashSet::class.java)
}
}

View File

@ -0,0 +1,128 @@
package net.corda.serialization.internal
import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.util.DefaultClassResolver
import net.corda.core.serialization.*
import net.corda.node.services.statemachine.DataSessionMessage
import net.corda.serialization.internal.amqp.DeserializationInput
import net.corda.serialization.internal.amqp.Envelope
import net.corda.serialization.internal.amqp.SerializerFactory
import net.corda.serialization.internal.kryo.kryoMagic
import net.corda.testing.internal.amqpSpecific
import net.corda.testing.internal.kryoSpecific
import net.corda.testing.core.SerializationEnvironmentRule
import org.assertj.core.api.Assertions
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import java.io.ByteArrayOutputStream
import java.io.NotSerializableException
import java.nio.charset.StandardCharsets.US_ASCII
import java.util.*
class ListsSerializationTest {
private companion object {
val javaEmptyListClass = Collections.emptyList<Any>().javaClass
fun <T : Any> verifyEnvelope(serBytes: SerializedBytes<T>, envVerBody: (Envelope) -> Unit) =
amqpSpecific("AMQP specific envelope verification") {
val context = SerializationFactory.defaultFactory.defaultContext
val envelope = DeserializationInput(SerializerFactory(context.whitelist, context.deserializationClassLoader)).getEnvelope(serBytes)
envVerBody(envelope)
}
}
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
@Test
fun `check list can be serialized as root of serialization graph`() {
assertEqualAfterRoundTripSerialization(emptyList<Int>())
assertEqualAfterRoundTripSerialization(listOf(1))
assertEqualAfterRoundTripSerialization(listOf(1, 2))
}
@Test
fun `check list can be serialized as part of SessionData`() {
run {
val sessionData = DataSessionMessage(listOf(1).serialize())
assertEqualAfterRoundTripSerialization(sessionData)
assertEquals(listOf(1), sessionData.payload.deserialize())
}
run {
val sessionData = DataSessionMessage(listOf(1, 2).serialize())
assertEqualAfterRoundTripSerialization(sessionData)
assertEquals(listOf(1, 2), sessionData.payload.deserialize())
}
run {
val sessionData = DataSessionMessage(emptyList<Int>().serialize())
assertEqualAfterRoundTripSerialization(sessionData)
assertEquals(emptyList<Int>(), sessionData.payload.deserialize())
}
}
@Test
fun `check empty list serialises as Java emptyList`() = kryoSpecific("Kryo specific test") {
val nameID = 0
val serializedForm = emptyList<Int>().serialize()
val output = ByteArrayOutputStream().apply {
kryoMagic.writeTo(this)
SectionId.ALT_DATA_AND_STOP.writeTo(this)
write(DefaultClassResolver.NAME + 2)
write(nameID)
write(javaEmptyListClass.name.toAscii())
write(Kryo.NOT_NULL.toInt())
}
assertArrayEquals(output.toByteArray(), serializedForm.bytes)
}
@CordaSerializable
data class WrongPayloadType(val payload: ArrayList<Int>)
@Test
fun `check throws for forbidden declared type`() = amqpSpecific("Such exceptions are not expected in Kryo mode.") {
val payload = ArrayList<Int>()
payload.add(1)
payload.add(2)
val wrongPayloadType = WrongPayloadType(payload)
Assertions.assertThatThrownBy { wrongPayloadType.serialize() }
.isInstanceOf(NotSerializableException::class.java).hasMessageContaining("Cannot derive collection type for declaredType")
}
@CordaSerializable
interface Parent
data class Child(val value: Int) : Parent
@CordaSerializable
data class CovariantContainer<out T : Parent>(val payload: List<T>)
@Test
fun `check covariance`() {
val payload = ArrayList<Child>()
payload.add(Child(1))
payload.add(Child(2))
val container = CovariantContainer(payload)
fun verifyEnvelopeBody(envelope: Envelope) {
envelope.schema.types.single { typeNotation -> typeNotation.name == java.util.List::class.java.name + "<?>" }
}
assertEqualAfterRoundTripSerialization(container, { bytes -> verifyEnvelope(bytes, ::verifyEnvelopeBody) })
}
}
internal inline fun <reified T : Any> assertEqualAfterRoundTripSerialization(obj: T, noinline streamValidation: ((SerializedBytes<T>) -> Unit)? = null) {
val serializedForm: SerializedBytes<T> = obj.serialize()
streamValidation?.invoke(serializedForm)
val deserializedInstance = serializedForm.deserialize()
assertEquals(obj, deserializedInstance)
}
internal fun String.toAscii(): ByteArray = toByteArray(US_ASCII).apply {
this[lastIndex] = (this[lastIndex] + 0x80).toByte()
}

View File

@ -0,0 +1,91 @@
package net.corda.serialization.internal
import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.util.DefaultClassResolver
import net.corda.core.identity.CordaX500Name
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.node.services.statemachine.DataSessionMessage
import net.corda.serialization.internal.kryo.kryoMagic
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.internal.amqpSpecific
import net.corda.testing.internal.kryoSpecific
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Assert.assertArrayEquals
import org.junit.Rule
import org.junit.Test
import java.io.ByteArrayOutputStream
import java.util.*
import kotlin.test.assertEquals
class MapsSerializationTest {
private companion object {
val javaEmptyMapClass = Collections.emptyMap<Any, Any>().javaClass
val smallMap = mapOf("foo" to "bar", "buzz" to "bull")
}
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
@Test
fun `check EmptyMap serialization`() = amqpSpecific("kotlin.collections.EmptyMap is not enabled for Kryo serialization") {
assertEqualAfterRoundTripSerialization(emptyMap<Any, Any>())
}
@Test
fun `check Map can be root of serialization graph`() {
assertEqualAfterRoundTripSerialization(smallMap)
}
@Test
fun `check list can be serialized as part of SessionData`() {
val sessionData = DataSessionMessage(smallMap.serialize())
assertEqualAfterRoundTripSerialization(sessionData)
assertEquals(smallMap, sessionData.payload.deserialize())
}
@CordaSerializable
data class WrongPayloadType(val payload: HashMap<String, String>)
@Test
fun `check throws for forbidden declared type`() = amqpSpecific("Such exceptions are not expected in Kryo mode.") {
val payload = HashMap<String, String>(smallMap)
val wrongPayloadType = WrongPayloadType(payload)
assertThatThrownBy { wrongPayloadType.serialize() }
.isInstanceOf(IllegalArgumentException::class.java).hasMessageContaining(
"Map type class java.util.HashMap is unstable under iteration. Suggested fix: use java.util.LinkedHashMap instead.")
}
@CordaSerializable
data class MyKey(val keyContent: Double)
@CordaSerializable
data class MyValue(val valueContent: CordaX500Name)
@Test
fun `check map serialization works with custom types`() {
val myMap = mapOf(
MyKey(1.0) to MyValue(CordaX500Name("OOO", "LLL", "CC")),
MyKey(10.0) to MyValue(CordaX500Name("OO", "LL", "CC")))
assertEqualAfterRoundTripSerialization(myMap)
}
@Test
fun `check empty map serialises as Java emptyMap`() {
kryoSpecific("Specifically checks Kryo serialization") {
val nameID = 0
val serializedForm = emptyMap<Int, Int>().serialize()
val output = ByteArrayOutputStream().apply {
kryoMagic.writeTo(this)
SectionId.ALT_DATA_AND_STOP.writeTo(this)
write(DefaultClassResolver.NAME + 2)
write(nameID)
write(javaEmptyMapClass.name.toAscii())
write(Kryo.NOT_NULL.toInt())
}
assertArrayEquals(output.toByteArray(), serializedForm.bytes)
}
}
}

View File

@ -0,0 +1,45 @@
package net.corda.serialization.internal
import net.corda.core.crypto.Crypto
import net.corda.core.serialization.SerializationContext.UseCase.*
import net.corda.core.serialization.SerializationDefaults
import net.corda.core.serialization.serialize
import net.corda.testing.core.SerializationEnvironmentRule
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import java.security.PrivateKey
import kotlin.test.assertTrue
@RunWith(Parameterized::class)
class PrivateKeySerializationTest(private val privateKey: PrivateKey, private val testName: String) {
companion object {
@JvmStatic
@Parameterized.Parameters(name = "{1}")
fun data(): Collection<Array<Any>> {
val privateKeys: List<PrivateKey> = Crypto.supportedSignatureSchemes().filterNot { Crypto.COMPOSITE_KEY === it }
.map { Crypto.generateKeyPair(it).private }
return privateKeys.map { arrayOf<Any>(it, PrivateKeySerializationTest::class.java.simpleName + "-" + it.javaClass.simpleName) }
}
}
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
@Test
fun `passed with expected UseCases`() {
assertTrue { privateKey.serialize(context = SerializationDefaults.STORAGE_CONTEXT).bytes.isNotEmpty() }
assertTrue { privateKey.serialize(context = SerializationDefaults.CHECKPOINT_CONTEXT).bytes.isNotEmpty() }
}
@Test
fun `failed with wrong UseCase`() {
assertThatThrownBy { privateKey.serialize(context = SerializationDefaults.P2P_CONTEXT) }
.isInstanceOf(IllegalStateException::class.java)
.hasMessageContaining("UseCase '$P2P' is not within")
}
}

View File

@ -0,0 +1,127 @@
package net.corda.serialization.internal
import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.KryoException
import com.esotericsoftware.kryo.io.Output
import net.corda.core.serialization.*
import net.corda.core.utilities.OpaqueBytes
import net.corda.serialization.internal.kryo.CordaClassResolver
import net.corda.serialization.internal.kryo.CordaKryo
import net.corda.serialization.internal.kryo.DefaultKryoCustomizer
import net.corda.serialization.internal.kryo.kryoMagic
import net.corda.testing.internal.rigorousMock
import net.corda.testing.core.SerializationEnvironmentRule
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.io.ByteArrayOutputStream
class SerializationTokenTest {
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
private lateinit var factory: SerializationFactory
private lateinit var context: SerializationContext
@Before
fun setup() {
factory = testSerialization.serializationFactory
context = testSerialization.checkpointContext.withWhitelisted(SingletonSerializationToken::class.java)
}
// Large tokenizable object so we can tell from the smaller number of serialized bytes it was actually tokenized
private class LargeTokenizable : SingletonSerializeAsToken() {
val bytes = OpaqueBytes(ByteArray(1024))
val numBytes: Int
get() = bytes.size
override fun hashCode() = bytes.size
override fun equals(other: Any?) = other is LargeTokenizable && other.bytes.size == this.bytes.size
}
private fun serializeAsTokenContext(toBeTokenized: Any) = SerializeAsTokenContextImpl(toBeTokenized, factory, context, rigorousMock())
@Test
fun `write token and read tokenizable`() {
val tokenizableBefore = LargeTokenizable()
val context = serializeAsTokenContext(tokenizableBefore)
val testContext = this.context.withTokenContext(context)
val serializedBytes = tokenizableBefore.serialize(factory, testContext)
assertThat(serializedBytes.size).isLessThan(tokenizableBefore.numBytes)
val tokenizableAfter = serializedBytes.deserialize(factory, testContext)
assertThat(tokenizableAfter).isSameAs(tokenizableBefore)
}
private class UnitSerializeAsToken : SingletonSerializeAsToken()
@Test
fun `write and read singleton`() {
val tokenizableBefore = UnitSerializeAsToken()
val context = serializeAsTokenContext(tokenizableBefore)
val testContext = this.context.withTokenContext(context)
val serializedBytes = tokenizableBefore.serialize(factory, testContext)
val tokenizableAfter = serializedBytes.deserialize(factory, testContext)
assertThat(tokenizableAfter).isSameAs(tokenizableBefore)
}
@Test(expected = UnsupportedOperationException::class)
fun `new token encountered after context init`() {
val tokenizableBefore = UnitSerializeAsToken()
val context = serializeAsTokenContext(emptyList<Any>())
val testContext = this.context.withTokenContext(context)
tokenizableBefore.serialize(factory, testContext)
}
@Test(expected = UnsupportedOperationException::class)
fun `deserialize unregistered token`() {
val tokenizableBefore = UnitSerializeAsToken()
val context = serializeAsTokenContext(emptyList<Any>())
val testContext = this.context.withTokenContext(context)
val serializedBytes = tokenizableBefore.toToken(serializeAsTokenContext(emptyList<Any>())).serialize(factory, testContext)
serializedBytes.deserialize(factory, testContext)
}
@Test(expected = KryoException::class)
fun `no context set`() {
val tokenizableBefore = UnitSerializeAsToken()
tokenizableBefore.serialize(factory, context)
}
@Test(expected = KryoException::class)
fun `deserialize non-token`() {
val tokenizableBefore = UnitSerializeAsToken()
val context = serializeAsTokenContext(tokenizableBefore)
val testContext = this.context.withTokenContext(context)
val kryo: Kryo = DefaultKryoCustomizer.customize(CordaKryo(CordaClassResolver(this.context)))
val stream = ByteArrayOutputStream()
Output(stream).use {
kryoMagic.writeTo(it)
SectionId.ALT_DATA_AND_STOP.writeTo(it)
kryo.writeClass(it, SingletonSerializeAsToken::class.java)
kryo.writeObject(it, emptyList<Any>())
}
val serializedBytes = SerializedBytes<Any>(stream.toByteArray())
serializedBytes.deserialize(factory, testContext)
}
private class WrongTypeSerializeAsToken : SerializeAsToken {
object UnitSerializationToken : SerializationToken {
override fun fromToken(context: SerializeAsTokenContext): Any = UnitSerializeAsToken()
}
override fun toToken(context: SerializeAsTokenContext): SerializationToken = UnitSerializationToken
}
@Test(expected = KryoException::class)
fun `token returns unexpected type`() {
val tokenizableBefore = WrongTypeSerializeAsToken()
val context = serializeAsTokenContext(tokenizableBefore)
val testContext = this.context.withTokenContext(context)
val serializedBytes = tokenizableBefore.serialize(factory, testContext)
serializedBytes.deserialize(factory, testContext)
}
}

View File

@ -0,0 +1,67 @@
package net.corda.serialization.internal
import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.util.DefaultClassResolver
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.node.services.statemachine.DataSessionMessage
import net.corda.serialization.internal.kryo.kryoMagic
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.internal.kryoSpecific
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import java.io.ByteArrayOutputStream
import java.util.*
class SetsSerializationTest {
private companion object {
val javaEmptySetClass = Collections.emptySet<Any>().javaClass
}
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
@Test
fun `check set can be serialized as root of serialization graph`() {
assertEqualAfterRoundTripSerialization(emptySet<Int>())
assertEqualAfterRoundTripSerialization(setOf(1))
assertEqualAfterRoundTripSerialization(setOf(1, 2))
}
@Test
fun `check set can be serialized as part of DataSessionMessage`() {
run {
val sessionData = DataSessionMessage(setOf(1).serialize())
assertEqualAfterRoundTripSerialization(sessionData)
assertEquals(setOf(1), sessionData.payload.deserialize())
}
run {
val sessionData = DataSessionMessage(setOf(1, 2).serialize())
assertEqualAfterRoundTripSerialization(sessionData)
assertEquals(setOf(1, 2), sessionData.payload.deserialize())
}
run {
val sessionData = DataSessionMessage(emptySet<Int>().serialize())
assertEqualAfterRoundTripSerialization(sessionData)
assertEquals(emptySet<Int>(), sessionData.payload.deserialize())
}
}
@Test
fun `check empty set serialises as Java emptySet`() = kryoSpecific("Checks Kryo header properties") {
val nameID = 0
val serializedForm = emptySet<Int>().serialize()
val output = ByteArrayOutputStream().apply {
kryoMagic.writeTo(this)
SectionId.ALT_DATA_AND_STOP.writeTo(this)
write(DefaultClassResolver.NAME + 2)
write(nameID)
write(javaEmptySetClass.name.toAscii())
write(Kryo.NOT_NULL.toInt())
}
assertArrayEquals(output.toByteArray(), serializedForm.bytes)
}
}

View File

@ -0,0 +1,154 @@
package net.corda.serialization.internal.amqp
import org.junit.Test
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.SerializationCustomSerializer
import net.corda.serialization.internal.amqp.testutils.deserializeAndReturnEnvelope
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.testDefaultFactory
import org.assertj.core.api.Assertions
import java.io.NotSerializableException
import kotlin.test.assertEquals
class CorDappSerializerTests {
data class NeedsProxy (val a: String)
class NeedsProxyProxySerializer : SerializationCustomSerializer<NeedsProxy, NeedsProxyProxySerializer.Proxy> {
data class Proxy(val proxy_a_: String)
override fun fromProxy(proxy: Proxy) = NeedsProxy(proxy.proxy_a_)
override fun toProxy(obj: NeedsProxy) = Proxy(obj.a)
}
// Standard proxy serializer used internally, here for comparison purposes
class InternalProxySerializer(factory: SerializerFactory) :
CustomSerializer.Proxy<NeedsProxy, InternalProxySerializer.Proxy> (
NeedsProxy::class.java,
InternalProxySerializer.Proxy::class.java,
factory) {
data class Proxy(val proxy_a_: String)
override fun toProxy(obj: NeedsProxy): Proxy {
return Proxy(obj.a)
}
override fun fromProxy(proxy: Proxy): NeedsProxy {
return NeedsProxy(proxy.proxy_a_)
}
}
@Test
fun `type uses proxy`() {
val internalProxyFactory = testDefaultFactory()
val proxyFactory = testDefaultFactory()
val defaultFactory = testDefaultFactory()
val msg = "help"
proxyFactory.registerExternal (CorDappCustomSerializer(NeedsProxyProxySerializer(), proxyFactory))
internalProxyFactory.register (InternalProxySerializer(internalProxyFactory))
val needsProxy = NeedsProxy(msg)
val bAndSProxy = SerializationOutput(proxyFactory).serializeAndReturnSchema (needsProxy)
val bAndSInternal = SerializationOutput(internalProxyFactory).serializeAndReturnSchema (needsProxy)
val bAndSDefault = SerializationOutput(defaultFactory).serializeAndReturnSchema (needsProxy)
val objFromDefault = DeserializationInput(defaultFactory).deserializeAndReturnEnvelope(bAndSDefault.obj)
val objFromInternal = DeserializationInput(internalProxyFactory).deserializeAndReturnEnvelope(bAndSInternal.obj)
val objFromProxy = DeserializationInput(proxyFactory).deserializeAndReturnEnvelope(bAndSProxy.obj)
assertEquals(msg, objFromDefault.obj.a)
assertEquals(msg, objFromInternal.obj.a)
assertEquals(msg, objFromProxy.obj.a)
}
@Test
fun proxiedTypeIsNested() {
data class A (val a: Int, val b: NeedsProxy)
val factory = testDefaultFactory()
factory.registerExternal (CorDappCustomSerializer(NeedsProxyProxySerializer(), factory))
val tv1 = 100
val tv2 = "pants schmants"
val bAndS = SerializationOutput(factory).serializeAndReturnSchema (A(tv1, NeedsProxy(tv2)))
val objFromDefault = DeserializationInput(factory).deserializeAndReturnEnvelope(bAndS.obj)
assertEquals(tv1, objFromDefault.obj.a)
assertEquals(tv2, objFromDefault.obj.b.a)
}
@Test
fun testWithWhitelistNotAllowed() {
data class A (val a: Int, val b: NeedsProxy)
class WL : ClassWhitelist {
private val allowedClasses = emptySet<String>()
override fun hasListed(type: Class<*>): Boolean = type.name in allowedClasses
}
val factory = SerializerFactory(WL(), ClassLoader.getSystemClassLoader())
factory.registerExternal (CorDappCustomSerializer(NeedsProxyProxySerializer(), factory))
val tv1 = 100
val tv2 = "pants schmants"
Assertions.assertThatThrownBy {
SerializationOutput(factory).serialize(A(tv1, NeedsProxy(tv2)))
}.isInstanceOf(NotSerializableException::class.java)
}
@Test
fun testWithWhitelistAllowed() {
data class A (val a: Int, val b: NeedsProxy)
class WL : ClassWhitelist {
private val allowedClasses = setOf<String>(
A::class.java.name,
NeedsProxy::class.java.name)
override fun hasListed(type: Class<*>): Boolean = type.name in allowedClasses
}
val factory = SerializerFactory(WL(), ClassLoader.getSystemClassLoader())
factory.registerExternal (CorDappCustomSerializer(NeedsProxyProxySerializer(), factory))
val tv1 = 100
val tv2 = "pants schmants"
val obj = DeserializationInput(factory).deserialize(
SerializationOutput(factory).serialize(A(tv1, NeedsProxy(tv2))))
assertEquals(tv1, obj.a)
assertEquals(tv2, obj.b.a)
}
// The custom type not being whitelisted won't matter here because the act of adding a
// custom serializer bypasses the whitelist
@Test
fun testWithWhitelistAllowedOuterOnly() {
data class A (val a: Int, val b: NeedsProxy)
class WL : ClassWhitelist {
// explicitly don't add NeedsProxy
private val allowedClasses = setOf<String>(A::class.java.name)
override fun hasListed(type: Class<*>): Boolean = type.name in allowedClasses
}
val factory = SerializerFactory(WL(), ClassLoader.getSystemClassLoader())
factory.registerExternal (CorDappCustomSerializer(NeedsProxyProxySerializer(), factory))
val tv1 = 100
val tv2 = "pants schmants"
val obj = DeserializationInput(factory).deserialize(
SerializationOutput(factory).serialize(A(tv1, NeedsProxy(tv2))))
assertEquals(tv1, obj.a)
assertEquals(tv2, obj.b.a)
}
}

View File

@ -0,0 +1,68 @@
package net.corda.serialization.internal.amqp
import net.corda.core.serialization.CordaSerializable
import net.corda.serialization.internal.amqp.testutils.deserializeAndReturnEnvelope
import net.corda.serialization.internal.amqp.testutils.serialize
import net.corda.serialization.internal.amqp.testutils.testDefaultFactoryNoEvolution
import net.corda.serialization.internal.amqp.testutils.testDefaultFactoryWithWhitelist
import net.corda.serialization.internal.amqp.testutils.testName
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.assertTrue
class DeserializeAndReturnEnvelopeTests {
// the 'this' reference means we can't just move this to the common test utils
@Suppress("NOTHING_TO_INLINE")
private inline fun classTestName(clazz: String) = "${this.javaClass.name}\$${testName()}\$$clazz"
val factory = testDefaultFactoryNoEvolution()
@Test
fun oneType() {
data class A(val a: Int, val b: String)
val a = A(10, "20")
fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
assertTrue(obj.obj is A)
assertEquals(1, obj.envelope.schema.types.size)
assertEquals(classTestName("A"), obj.envelope.schema.types.first().name)
}
@Test
fun twoTypes() {
data class A(val a: Int, val b: String)
data class B(val a: A, val b: Float)
val b = B(A(10, "20"), 30.0F)
fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b))
assertTrue(obj.obj is B)
assertEquals(2, obj.envelope.schema.types.size)
assertNotEquals(null, obj.envelope.schema.types.find { it.name == classTestName("A") })
assertNotEquals(null, obj.envelope.schema.types.find { it.name == classTestName("B") })
}
@Test
fun unannotatedInterfaceIsNotInSchema() {
@CordaSerializable
data class Foo(val bar: Int) : Comparable<Foo> {
override fun compareTo(other: Foo): Int = bar.compareTo(other.bar)
}
val a = Foo(123)
val factory = testDefaultFactoryWithWhitelist()
fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
assertTrue(obj.obj is Foo)
assertEquals(1, obj.envelope.schema.types.size)
assertNotEquals(null, obj.envelope.schema.types.find { it.name == classTestName("Foo") })
assertEquals(null, obj.envelope.schema.types.find { it.name == "java.lang.Comparable<${classTestName("Foo")}>" })
}
}

View File

@ -0,0 +1,138 @@
package net.corda.serialization.internal.amqp
import net.corda.serialization.internal.amqp.testutils.TestSerializationOutput
import net.corda.serialization.internal.amqp.testutils.deserialize
import net.corda.serialization.internal.amqp.testutils.serialize
import net.corda.serialization.internal.amqp.testutils.testDefaultFactoryNoEvolution
import org.assertj.core.api.Assertions
import org.junit.Test
import java.util.*
class DeserializeMapTests {
companion object {
/**
* If you want to see the schema encoded into the envelope after serialisation change this to true
*/
private const val VERBOSE = false
}
private val sf = testDefaultFactoryNoEvolution()
@Test
fun mapTest() {
data class C(val c: Map<String, Int>)
val c = C(mapOf("A" to 1, "B" to 2))
val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(c)
DeserializationInput(sf).deserialize(serialisedBytes)
}
@Test(expected = java.io.NotSerializableException::class)
fun abstractMapFromMapOf() {
data class C(val c: AbstractMap<String, Int>)
val c = C(mapOf("A" to 1, "B" to 2) as AbstractMap)
val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(c)
DeserializationInput(sf).deserialize(serialisedBytes)
}
@Test(expected = java.io.NotSerializableException::class)
fun abstractMapFromTreeMap() {
data class C(val c: AbstractMap<String, Int>)
val c = C(TreeMap(mapOf("A" to 1, "B" to 2)))
val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(c)
DeserializationInput(sf).deserialize(serialisedBytes)
}
@Test
fun sortedMapTest() {
data class C(val c: SortedMap<String, Int>)
val c = C(sortedMapOf("A" to 1, "B" to 2))
val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(c)
DeserializationInput(sf).deserialize(serialisedBytes)
}
@Test
fun navigableMapTest() {
data class C(val c: NavigableMap<String, Int>)
val c = C(TreeMap(mapOf("A" to 1, "B" to 2)).descendingMap())
val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(c)
DeserializationInput(sf).deserialize(serialisedBytes)
}
@Test
fun dictionaryTest() {
data class C(val c: Dictionary<String, Int>)
val v: Hashtable<String, Int> = Hashtable()
v["a"] = 1
v["b"] = 2
val c = C(v)
// expected to throw
Assertions.assertThatThrownBy { TestSerializationOutput(VERBOSE, sf).serialize(c) }
.isInstanceOf(IllegalArgumentException::class.java).hasMessageContaining("Unable to serialise deprecated type class java.util.Dictionary.")
}
@Test
fun hashtableTest() {
data class C(val c: Hashtable<String, Int>)
val v: Hashtable<String, Int> = Hashtable()
v["a"] = 1
v["b"] = 2
val c = C(v)
// expected to throw
Assertions.assertThatThrownBy { TestSerializationOutput(VERBOSE, sf).serialize(c) }
.isInstanceOf(IllegalArgumentException::class.java).hasMessageContaining("Unable to serialise deprecated type class java.util.Hashtable. Suggested fix: prefer java.util.map implementations")
}
@Test
fun hashMapTest() {
data class C(val c: HashMap<String, Int>)
val c = C(HashMap(mapOf("A" to 1, "B" to 2)))
// expect this to throw
Assertions.assertThatThrownBy { TestSerializationOutput(VERBOSE, sf).serialize(c) }
.isInstanceOf(IllegalArgumentException::class.java).hasMessageContaining("Map type class java.util.HashMap is unstable under iteration. Suggested fix: use java.util.LinkedHashMap instead.")
}
@Test
fun weakHashMapTest() {
data class C(val c: WeakHashMap<String, Int>)
val c = C(WeakHashMap(mapOf("A" to 1, "B" to 2)))
Assertions.assertThatThrownBy { TestSerializationOutput(VERBOSE, sf).serialize(c) }
.isInstanceOf(IllegalArgumentException::class.java).hasMessageContaining("Weak references with map types not supported. Suggested fix: use java.util.LinkedHashMap instead.")
}
@Test
fun concreteTreeMapTest() {
data class C(val c: TreeMap<String, Int>)
val c = C(TreeMap(mapOf("A" to 1, "B" to 3)))
val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(c)
DeserializationInput(sf).deserialize(serialisedBytes)
}
@Test
fun concreteLinkedHashMapTest() {
data class C(val c: LinkedHashMap<String, Int>)
val c = C(LinkedHashMap(mapOf("A" to 1, "B" to 2)))
val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(c)
DeserializationInput(sf).deserialize(serialisedBytes)
}
}

View File

@ -0,0 +1,111 @@
package net.corda.serialization.internal.amqp
import net.corda.serialization.internal.AllWhitelist
import net.corda.serialization.internal.amqp.testutils.*
import net.corda.serialization.internal.carpenter.*
import org.junit.Test
import kotlin.test.*
class DeserializeNeedingCarpentryOfEnumsTest : AmqpCarpenterBase(AllWhitelist) {
companion object {
/**
* If you want to see the schema encoded into the envelope after serialisation change this to true
*/
private const val VERBOSE = false
}
@Test
fun singleEnum() {
//
// Setup the test
//
val setupFactory = testDefaultFactoryNoEvolution()
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))
// create the class that has that enum as an element
val testClassType = setupFactory.classCarpenter.build(ClassSchema("test.testClassType",
mapOf("a" to NonNullableField(testEnumType))))
// create an instance of the class we can then serialise
val testInstance = testClassType.constructors[0].newInstance(testEnumType.getMethod(
"valueOf", String::class.java).invoke(null, "BBB"))
// serialise the object
val serialisedBytes = TestSerializationOutput(VERBOSE, setupFactory).serialize(testInstance)
//
// Test setup done, now on with the actual test
//
// need a second factory to ensure a second carpenter is used and thus the class we're attempting
// to de-serialise isn't in the factories class loader
val testFactory = testDefaultFactoryWithWhitelist()
val deserializedObj = DeserializationInput(testFactory).deserialize(serialisedBytes)
assertTrue(deserializedObj::class.java.getMethod("getA").invoke(deserializedObj)::class.java.isEnum)
assertEquals("BBB",
(deserializedObj::class.java.getMethod("getA").invoke(deserializedObj) as Enum<*>).name)
}
@Test
fun compositeIncludingEnums() {
//
// Setup the test
//
val setupFactory = testDefaultFactoryNoEvolution()
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))
// create the class that has that enum as an element
val testClassType = setupFactory.classCarpenter.build(ClassSchema("test.testClassType",
mapOf(
"a" to NonNullableField(testEnumType1),
"b" to NonNullableField(testEnumType2),
"c" to NullableField(testEnumType1),
"d" to NullableField(String::class.java))))
val vOf1 = testEnumType1.getMethod("valueOf", String::class.java)
val vOf2 = testEnumType2.getMethod("valueOf", String::class.java)
val testStr = "so many things [Ø Þ]"
// create an instance of the class we can then serialise
val testInstance = testClassType.constructors[0].newInstance(
vOf1.invoke(null, "CCC"),
vOf2.invoke(null, "EEE"),
null,
testStr)
// serialise the object
val serialisedBytes = TestSerializationOutput(VERBOSE, setupFactory).serialize(testInstance)
//
// Test setup done, now on with the actual test
//
// need a second factory to ensure a second carpenter is used and thus the class we're attempting
// to de-serialise isn't in the factories class loader
val testFactory = testDefaultFactoryWithWhitelist()
val deserializedObj = DeserializationInput(testFactory).deserialize(serialisedBytes)
assertTrue(deserializedObj::class.java.getMethod("getA").invoke(deserializedObj)::class.java.isEnum)
assertEquals("CCC",
(deserializedObj::class.java.getMethod("getA").invoke(deserializedObj) as Enum<*>).name)
assertTrue(deserializedObj::class.java.getMethod("getB").invoke(deserializedObj)::class.java.isEnum)
assertEquals("EEE",
(deserializedObj::class.java.getMethod("getB").invoke(deserializedObj) as Enum<*>).name)
assertNull(deserializedObj::class.java.getMethod("getC").invoke(deserializedObj))
assertEquals(testStr, deserializedObj::class.java.getMethod("getD").invoke(deserializedObj))
}
}

View File

@ -0,0 +1,450 @@
package net.corda.serialization.internal.amqp
import net.corda.serialization.internal.AllWhitelist
import net.corda.serialization.internal.carpenter.*
import net.corda.serialization.internal.amqp.testutils.TestSerializationOutput
import net.corda.serialization.internal.amqp.testutils.deserialize
import net.corda.serialization.internal.amqp.testutils.serialize
import net.corda.serialization.internal.amqp.testutils.testDefaultFactoryNoEvolution
import org.junit.Test
import kotlin.test.*
// These tests work by having the class carpenter build the classes we serialise and then deserialise. Because
// those classes don't exist within the system's Class Loader the deserialiser will be forced to carpent
// versions of them up using its own internal class carpenter (each carpenter houses it's own loader). This
// replicates the situation where a receiver doesn't have some or all elements of a schema present on it's classpath
class DeserializeNeedingCarpentrySimpleTypesTest : AmqpCarpenterBase(AllWhitelist) {
companion object {
/**
* If you want to see the schema encoded into the envelope after serialisation change this to true
*/
private const val VERBOSE = false
}
private val sf = testDefaultFactoryNoEvolution()
private val sf2 = testDefaultFactoryNoEvolution()
@Test
fun singleInt() {
val clazz = ClassCarpenterImpl(whitelist = AllWhitelist).build(ClassSchema(testName(), mapOf(
"int" to NonNullableField(Integer::class.javaPrimitiveType!!)
)))
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(1))
val db = DeserializationInput(sf).deserialize(sb)
val db2 = DeserializationInput(sf2).deserialize(sb)
// despite being carpented, and thus not on the class path, we should've cached clazz
// inside the serialiser object and thus we should have created the same type
assertEquals(db::class.java, clazz)
assertNotEquals(db2::class.java, clazz)
assertNotEquals(db::class.java, db2::class.java)
assertEquals(1, db::class.java.getMethod("getInt").invoke(db))
assertEquals(1, db2::class.java.getMethod("getInt").invoke(db2))
}
@Test
fun singleIntNullable() {
val clazz = ClassCarpenterImpl(whitelist = AllWhitelist).build(ClassSchema(testName(), mapOf(
"int" to NullableField(Integer::class.java)
)))
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(1))
val db1 = DeserializationInput(sf).deserialize(sb)
val db2 = DeserializationInput(sf2).deserialize(sb)
assertEquals(clazz, db1::class.java)
assertNotEquals(clazz, db2::class.java)
assertEquals(1, db1::class.java.getMethod("getInt").invoke(db1))
assertEquals(1, db2::class.java.getMethod("getInt").invoke(db2))
}
@Test
fun singleIntNullableNull() {
val clazz = ClassCarpenterImpl(whitelist = AllWhitelist).build(ClassSchema(testName(), mapOf(
"int" to NullableField(Integer::class.java)
)))
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null))
val db1 = DeserializationInput(sf).deserialize(sb)
val db2 = DeserializationInput(sf2).deserialize(sb)
assertEquals(clazz, db1::class.java)
assertNotEquals(clazz, db2::class.java)
assertEquals(null, db1::class.java.getMethod("getInt").invoke(db1))
assertEquals(null, db2::class.java.getMethod("getInt").invoke(db2))
}
@Test
fun singleChar() {
val clazz = ClassCarpenterImpl(whitelist = AllWhitelist).build(ClassSchema(testName(), mapOf(
"char" to NonNullableField(Character::class.javaPrimitiveType!!)
)))
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance('a'))
val db = DeserializationInput(sf2).deserialize(sb)
assertNotEquals(clazz, db::class.java)
assertEquals('a', db::class.java.getMethod("getChar").invoke(db))
}
@Test
fun singleCharNullable() {
val clazz = ClassCarpenterImpl(whitelist = AllWhitelist).build(ClassSchema(testName(), mapOf(
"char" to NullableField(Character::class.javaObjectType)
)))
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance('a'))
val db = DeserializationInput(sf2).deserialize(sb)
assertNotEquals(clazz, db::class.java)
assertEquals('a', db::class.java.getMethod("getChar").invoke(db))
}
@Test
fun singleCharNullableNull() {
val clazz = ClassCarpenterImpl(whitelist = AllWhitelist).build(ClassSchema(testName(), mapOf(
"char" to NullableField(java.lang.Character::class.java)
)))
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null))
val db = DeserializationInput(sf2).deserialize(sb)
assertNotEquals(clazz, db::class.java)
assertEquals(null, db::class.java.getMethod("getChar").invoke(db))
}
@Test
fun singleLong() {
val clazz = ClassCarpenterImpl(whitelist = AllWhitelist).build(ClassSchema(testName(), mapOf(
"long" to NonNullableField(Long::class.javaPrimitiveType!!)
)))
val l: Long = 1
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(l))
val db = DeserializationInput(sf2).deserialize(sb)
assertNotEquals(clazz, db::class.java)
assertEquals(l, (db::class.java.getMethod("getLong").invoke(db)))
}
@Test
fun singleLongNullable() {
val clazz = ClassCarpenterImpl(whitelist = AllWhitelist).build(ClassSchema(testName(), mapOf(
"long" to NullableField(Long::class.javaObjectType)
)))
val l: Long = 1
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(l))
val db = DeserializationInput(sf2).deserialize(sb)
assertNotEquals(clazz, db::class.java)
assertEquals(l, (db::class.java.getMethod("getLong").invoke(db)))
}
@Test
fun singleLongNullableNull() {
val clazz = ClassCarpenterImpl(whitelist = AllWhitelist).build(ClassSchema(testName(), mapOf(
"long" to NullableField(Long::class.javaObjectType)
)))
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null))
val db = DeserializationInput(sf2).deserialize(sb)
assertNotEquals(clazz, db::class.java)
assertEquals(null, (db::class.java.getMethod("getLong").invoke(db)))
}
@Test
fun singleBoolean() {
val clazz = ClassCarpenterImpl(whitelist = AllWhitelist).build(ClassSchema(testName(), mapOf(
"boolean" to NonNullableField(Boolean::class.javaPrimitiveType!!)
)))
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(true))
val db = DeserializationInput(sf2).deserialize(sb)
assertNotEquals(clazz, db::class.java)
assertEquals(true, db::class.java.getMethod("getBoolean").invoke(db))
}
@Test
fun singleBooleanNullable() {
val clazz = ClassCarpenterImpl(whitelist = AllWhitelist).build(ClassSchema(testName(), mapOf(
"boolean" to NullableField(Boolean::class.javaObjectType)
)))
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(true))
val db = DeserializationInput(sf2).deserialize(sb)
assertNotEquals(clazz, db::class.java)
assertEquals(true, db::class.java.getMethod("getBoolean").invoke(db))
}
@Test
fun singleBooleanNullableNull() {
val clazz = ClassCarpenterImpl(whitelist = AllWhitelist).build(ClassSchema(testName(), mapOf(
"boolean" to NullableField(Boolean::class.javaObjectType)
)))
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null))
val db = DeserializationInput(sf2).deserialize(sb)
assertNotEquals(clazz, db::class.java)
assertEquals(null, db::class.java.getMethod("getBoolean").invoke(db))
}
@Test
fun singleDouble() {
val clazz = ClassCarpenterImpl(whitelist = AllWhitelist).build(ClassSchema(testName(), mapOf(
"double" to NonNullableField(Double::class.javaPrimitiveType!!)
)))
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(10.0))
val db = DeserializationInput(sf2).deserialize(sb)
assertNotEquals(clazz, db::class.java)
assertEquals(10.0, db::class.java.getMethod("getDouble").invoke(db))
}
@Test
fun singleDoubleNullable() {
val clazz = ClassCarpenterImpl(whitelist = AllWhitelist).build(ClassSchema(testName(), mapOf(
"double" to NullableField(Double::class.javaObjectType)
)))
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(10.0))
val db = DeserializationInput(sf2).deserialize(sb)
assertNotEquals(clazz, db::class.java)
assertEquals(10.0, db::class.java.getMethod("getDouble").invoke(db))
}
@Test
fun singleDoubleNullableNull() {
val clazz = ClassCarpenterImpl(whitelist = AllWhitelist).build(ClassSchema(testName(), mapOf(
"double" to NullableField(Double::class.javaObjectType)
)))
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null))
val db = DeserializationInput(sf2).deserialize(sb)
assertNotEquals(clazz, db::class.java)
assertEquals(null, db::class.java.getMethod("getDouble").invoke(db))
}
@Test
fun singleShort() {
val clazz = ClassCarpenterImpl(whitelist = AllWhitelist).build(ClassSchema(testName(), mapOf(
"short" to NonNullableField(Short::class.javaPrimitiveType!!)
)))
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(3.toShort()))
val db = DeserializationInput(sf2).deserialize(sb)
assertNotEquals(clazz, db::class.java)
assertEquals(3.toShort(), db::class.java.getMethod("getShort").invoke(db))
}
@Test
fun singleShortNullable() {
val clazz = ClassCarpenterImpl(whitelist = AllWhitelist).build(ClassSchema(testName(), mapOf(
"short" to NullableField(Short::class.javaObjectType)
)))
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(3.toShort()))
val db = DeserializationInput(sf2).deserialize(sb)
assertNotEquals(clazz, db::class.java)
assertEquals(3.toShort(), db::class.java.getMethod("getShort").invoke(db))
}
@Test
fun singleShortNullableNull() {
val clazz = ClassCarpenterImpl(whitelist = AllWhitelist).build(ClassSchema(testName(), mapOf(
"short" to NullableField(Short::class.javaObjectType)
)))
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null))
val db = DeserializationInput(sf2).deserialize(sb)
assertNotEquals(clazz, db::class.java)
assertEquals(null, db::class.java.getMethod("getShort").invoke(db))
}
@Test
fun singleFloat() {
val clazz = ClassCarpenterImpl(whitelist = AllWhitelist).build(ClassSchema(testName(), mapOf(
"float" to NonNullableField(Float::class.javaPrimitiveType!!)
)))
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(10.0F))
val db = DeserializationInput(sf2).deserialize(sb)
assertNotEquals(clazz, db::class.java)
assertEquals(10.0F, db::class.java.getMethod("getFloat").invoke(db))
}
@Test
fun singleFloatNullable() {
val clazz = ClassCarpenterImpl(whitelist = AllWhitelist).build(ClassSchema(testName(), mapOf(
"float" to NullableField(Float::class.javaObjectType)
)))
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(10.0F))
val db = DeserializationInput(sf2).deserialize(sb)
assertNotEquals(clazz, db::class.java)
assertEquals(10.0F, db::class.java.getMethod("getFloat").invoke(db))
}
@Test
fun singleFloatNullableNull() {
val clazz = ClassCarpenterImpl(whitelist = AllWhitelist).build(ClassSchema(testName(), mapOf(
"float" to NullableField(Float::class.javaObjectType)
)))
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null))
val db = DeserializationInput(sf2).deserialize(sb)
assertNotEquals(clazz, db::class.java)
assertEquals(null, db::class.java.getMethod("getFloat").invoke(db))
}
@Test
fun singleByte() {
val clazz = ClassCarpenterImpl(whitelist = AllWhitelist).build(ClassSchema(testName(), mapOf(
"byte" to NonNullableField(Byte::class.javaPrimitiveType!!)
)))
val b: Byte = 0b0101
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(b))
val db = DeserializationInput(sf2).deserialize(sb)
assertNotEquals(clazz, db::class.java)
assertEquals(b, db::class.java.getMethod("getByte").invoke(db))
assertEquals(0b0101, (db::class.java.getMethod("getByte").invoke(db) as Byte))
}
@Test
fun singleByteNullable() {
val clazz = ClassCarpenterImpl(whitelist = AllWhitelist).build(ClassSchema(testName(), mapOf(
"byte" to NullableField(Byte::class.javaObjectType)
)))
val b: Byte = 0b0101
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(b))
val db = DeserializationInput(sf2).deserialize(sb)
assertNotEquals(clazz, db::class.java)
assertEquals(b, db::class.java.getMethod("getByte").invoke(db))
assertEquals(0b0101, (db::class.java.getMethod("getByte").invoke(db) as Byte))
}
@Test
fun singleByteNullableNull() {
val clazz = ClassCarpenterImpl(whitelist = AllWhitelist).build(ClassSchema(testName(), mapOf(
"byte" to NullableField(Byte::class.javaObjectType)
)))
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null))
val db = DeserializationInput(sf2).deserialize(sb)
assertNotEquals(clazz, db::class.java)
assertEquals(null, db::class.java.getMethod("getByte").invoke(db))
}
@Test
fun simpleTypeKnownInterface() {
val clazz = ClassCarpenterImpl(whitelist = AllWhitelist).build(ClassSchema(
testName(), mapOf("name" to NonNullableField(String::class.java)),
interfaces = listOf(I::class.java)))
val testVal = "Some Person"
val classInstance = clazz.constructors[0].newInstance(testVal)
val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(classInstance)
val deserializedObj = DeserializationInput(sf2).deserialize(serialisedBytes)
assertNotEquals(clazz, deserializedObj::class.java)
assertTrue(deserializedObj is I)
assertEquals(testVal, (deserializedObj as I).getName())
}
@Test
fun manyTypes() {
val manyClass = ClassCarpenterImpl(whitelist = AllWhitelist).build(ClassSchema(testName(), mapOf(
"intA" to NonNullableField(Int::class.java),
"intB" to NullableField(Integer::class.java),
"intC" to NullableField(Integer::class.java),
"strA" to NonNullableField(String::class.java),
"strB" to NullableField(String::class.java),
"strC" to NullableField(String::class.java),
"charA" to NonNullableField(Char::class.java),
"charB" to NullableField(Character::class.javaObjectType),
"charC" to NullableField(Character::class.javaObjectType),
"shortA" to NonNullableField(Short::class.javaPrimitiveType!!),
"shortB" to NullableField(Short::class.javaObjectType),
"shortC" to NullableField(Short::class.javaObjectType),
"longA" to NonNullableField(Long::class.javaPrimitiveType!!),
"longB" to NullableField(Long::class.javaObjectType),
"longC" to NullableField(Long::class.javaObjectType),
"booleanA" to NonNullableField(Boolean::class.javaPrimitiveType!!),
"booleanB" to NullableField(Boolean::class.javaObjectType),
"booleanC" to NullableField(Boolean::class.javaObjectType),
"doubleA" to NonNullableField(Double::class.javaPrimitiveType!!),
"doubleB" to NullableField(Double::class.javaObjectType),
"doubleC" to NullableField(Double::class.javaObjectType),
"floatA" to NonNullableField(Float::class.javaPrimitiveType!!),
"floatB" to NullableField(Float::class.javaObjectType),
"floatC" to NullableField(Float::class.javaObjectType),
"byteA" to NonNullableField(Byte::class.javaPrimitiveType!!),
"byteB" to NullableField(Byte::class.javaObjectType),
"byteC" to NullableField(Byte::class.javaObjectType))))
val serialisedBytes = TestSerializationOutput(VERBOSE, factory).serialize(
manyClass.constructors.first().newInstance(
1, 2, null,
"a", "b", null,
'c', 'd', null,
3.toShort(), 4.toShort(), null,
100.toLong(), 200.toLong(), null,
true, false, null,
10.0, 20.0, null,
10.0F, 20.0F, null,
0b0101.toByte(), 0b1010.toByte(), null))
val deserializedObj = DeserializationInput(sf2).deserialize(serialisedBytes)
assertNotEquals(manyClass, deserializedObj::class.java)
assertEquals(1, deserializedObj::class.java.getMethod("getIntA").invoke(deserializedObj))
assertEquals(2, deserializedObj::class.java.getMethod("getIntB").invoke(deserializedObj))
assertEquals(null, deserializedObj::class.java.getMethod("getIntC").invoke(deserializedObj))
assertEquals("a", deserializedObj::class.java.getMethod("getStrA").invoke(deserializedObj))
assertEquals("b", deserializedObj::class.java.getMethod("getStrB").invoke(deserializedObj))
assertEquals(null, deserializedObj::class.java.getMethod("getStrC").invoke(deserializedObj))
assertEquals('c', deserializedObj::class.java.getMethod("getCharA").invoke(deserializedObj))
assertEquals('d', deserializedObj::class.java.getMethod("getCharB").invoke(deserializedObj))
assertEquals(null, deserializedObj::class.java.getMethod("getCharC").invoke(deserializedObj))
assertEquals(3.toShort(), deserializedObj::class.java.getMethod("getShortA").invoke(deserializedObj))
assertEquals(4.toShort(), deserializedObj::class.java.getMethod("getShortB").invoke(deserializedObj))
assertEquals(null, deserializedObj::class.java.getMethod("getShortC").invoke(deserializedObj))
assertEquals(100.toLong(), deserializedObj::class.java.getMethod("getLongA").invoke(deserializedObj))
assertEquals(200.toLong(), deserializedObj::class.java.getMethod("getLongB").invoke(deserializedObj))
assertEquals(null, deserializedObj::class.java.getMethod("getLongC").invoke(deserializedObj))
assertEquals(true, deserializedObj::class.java.getMethod("getBooleanA").invoke(deserializedObj))
assertEquals(false, deserializedObj::class.java.getMethod("getBooleanB").invoke(deserializedObj))
assertEquals(null, deserializedObj::class.java.getMethod("getBooleanC").invoke(deserializedObj))
assertEquals(10.0, deserializedObj::class.java.getMethod("getDoubleA").invoke(deserializedObj))
assertEquals(20.0, deserializedObj::class.java.getMethod("getDoubleB").invoke(deserializedObj))
assertEquals(null, deserializedObj::class.java.getMethod("getDoubleC").invoke(deserializedObj))
assertEquals(10.0F, deserializedObj::class.java.getMethod("getFloatA").invoke(deserializedObj))
assertEquals(20.0F, deserializedObj::class.java.getMethod("getFloatB").invoke(deserializedObj))
assertEquals(null, deserializedObj::class.java.getMethod("getFloatC").invoke(deserializedObj))
assertEquals(0b0101.toByte(), deserializedObj::class.java.getMethod("getByteA").invoke(deserializedObj))
assertEquals(0b1010.toByte(), deserializedObj::class.java.getMethod("getByteB").invoke(deserializedObj))
assertEquals(null, deserializedObj::class.java.getMethod("getByteC").invoke(deserializedObj))
}
}

View File

@ -0,0 +1,259 @@
package net.corda.serialization.internal.amqp
import net.corda.core.serialization.CordaSerializable
import net.corda.serialization.internal.carpenter.*
import net.corda.serialization.internal.AllWhitelist
import net.corda.serialization.internal.amqp.testutils.TestSerializationOutput
import net.corda.serialization.internal.amqp.testutils.testDefaultFactoryNoEvolution
import net.corda.serialization.internal.amqp.testutils.testDefaultFactoryWithWhitelist
import net.corda.serialization.internal.amqp.testutils.serialize
import net.corda.serialization.internal.amqp.testutils.deserialize
import org.junit.Test
import kotlin.test.*
@CordaSerializable
interface I {
fun getName(): String
}
// These tests work by having the class carpenter build the classes we serialise and then deserialise them
// within the context of a second serialiser factory. The second factory is required as the first, having
// been used to serialise the class, will have cached a copy of the class and will thus bypass the need
// to pull it out of the class loader.
//
// However, those classes don't exist within the system's Class Loader and thus the deserialiser will be forced
// to carpent versions of them up using its own internal class carpenter (each carpenter houses it's own loader). This
// replicates the situation where a receiver doesn't have some or all elements of a schema present on it's classpath
class DeserializeNeedingCarpentryTests : AmqpCarpenterBase(AllWhitelist) {
companion object {
/**
* If you want to see the schema encoded into the envelope after serialisation change this to true
*/
private const val VERBOSE = false
}
private val sf1 = testDefaultFactoryNoEvolution()
// Deserialize with whitelisting on to check that `CordaSerializable` annotation present.
private val sf2 = testDefaultFactoryWithWhitelist()
@Test
fun verySimpleType() {
val testVal = 10
val clazz = ClassCarpenterImpl(whitelist = AllWhitelist).build(ClassSchema(testName(),
mapOf("a" to NonNullableField(Int::class.java))))
val classInstance = clazz.constructors[0].newInstance(testVal)
val serialisedBytes = TestSerializationOutput(VERBOSE, sf1).serialize(classInstance)
val deserializedObj1 = DeserializationInput(sf1).deserialize(serialisedBytes)
assertEquals(clazz, deserializedObj1::class.java)
assertEquals(testVal, deserializedObj1::class.java.getMethod("getA").invoke(deserializedObj1))
val deserializedObj2 = DeserializationInput(sf1).deserialize(serialisedBytes)
assertEquals(clazz, deserializedObj2::class.java)
assertEquals(deserializedObj1::class.java, deserializedObj2::class.java)
assertEquals(testVal, deserializedObj2::class.java.getMethod("getA").invoke(deserializedObj2))
val deserializedObj3 = DeserializationInput(sf2).deserialize(serialisedBytes)
assertNotEquals(clazz, deserializedObj3::class.java)
assertNotEquals(deserializedObj1::class.java, deserializedObj3::class.java)
assertNotEquals(deserializedObj2::class.java, deserializedObj3::class.java)
assertEquals(testVal, deserializedObj3::class.java.getMethod("getA").invoke(deserializedObj3))
val deserializedObj4 = DeserializationInput(sf2).deserialize(serialisedBytes)
assertNotEquals(clazz, deserializedObj4::class.java)
assertNotEquals(deserializedObj1::class.java, deserializedObj4::class.java)
assertNotEquals(deserializedObj2::class.java, deserializedObj4::class.java)
assertEquals(deserializedObj3::class.java, deserializedObj4::class.java)
assertEquals(testVal, deserializedObj4::class.java.getMethod("getA").invoke(deserializedObj4))
}
@Test
fun repeatedTypesAreRecognised() {
val testValA = 10
val testValB = 20
val testValC = 20
val clazz = ClassCarpenterImpl(whitelist = AllWhitelist).build(ClassSchema("${testName()}_clazz",
mapOf("a" to NonNullableField(Int::class.java))))
val concreteA = clazz.constructors[0].newInstance(testValA)
val concreteB = clazz.constructors[0].newInstance(testValB)
val concreteC = clazz.constructors[0].newInstance(testValC)
val deserialisedA = DeserializationInput(sf2).deserialize(
TestSerializationOutput(VERBOSE, sf1).serialize(concreteA))
assertEquals(testValA, deserialisedA::class.java.getMethod("getA").invoke(deserialisedA))
val deserialisedB = DeserializationInput(sf2).deserialize(
TestSerializationOutput(VERBOSE, sf1).serialize(concreteB))
assertEquals(testValB, deserialisedA::class.java.getMethod("getA").invoke(deserialisedB))
assertEquals(deserialisedA::class.java, deserialisedB::class.java)
// C is deseriliased with a different factory, meaning a different class carpenter, so the type
// won't already exist and it will be carpented a second time showing that when A and B are the
// same underlying class that we didn't create a second instance of the class with the
// second deserialisation
val lfactory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
val deserialisedC = DeserializationInput(lfactory).deserialize(
TestSerializationOutput(VERBOSE, lfactory).serialize(concreteC))
assertEquals(testValC, deserialisedC::class.java.getMethod("getA").invoke(deserialisedC))
assertNotEquals(deserialisedA::class.java, deserialisedC::class.java)
assertNotEquals(deserialisedB::class.java, deserialisedC::class.java)
}
@Test
fun simpleTypeKnownInterface() {
val clazz = ClassCarpenterImpl(whitelist = AllWhitelist).build(ClassSchema(
testName(), mapOf("name" to NonNullableField(String::class.java)),
interfaces = listOf(I::class.java)))
val testVal = "Some Person"
val classInstance = clazz.constructors[0].newInstance(testVal)
val serialisedBytes = TestSerializationOutput(VERBOSE, sf1).serialize(classInstance)
val deserializedObj = DeserializationInput(sf2).deserialize(serialisedBytes)
assertTrue(deserializedObj is I)
assertEquals(testVal, (deserializedObj as I).getName())
}
@Test
fun arrayOfTypes() {
val clazz = ClassCarpenterImpl(whitelist = AllWhitelist).build(ClassSchema(testName(),
mapOf("a" to NonNullableField(Int::class.java))))
@CordaSerializable
data class Outer(val a: Array<Any>)
val outer = Outer(arrayOf(
clazz.constructors[0].newInstance(1),
clazz.constructors[0].newInstance(2),
clazz.constructors[0].newInstance(3)))
val deserializedObj = DeserializationInput(sf2).deserialize(TestSerializationOutput(VERBOSE, sf1).serialize(outer))
assertNotEquals((deserializedObj.a[0])::class.java, (outer.a[0])::class.java)
assertNotEquals((deserializedObj.a[1])::class.java, (outer.a[1])::class.java)
assertNotEquals((deserializedObj.a[2])::class.java, (outer.a[2])::class.java)
assertEquals((deserializedObj.a[0])::class.java, (deserializedObj.a[1])::class.java)
assertEquals((deserializedObj.a[0])::class.java, (deserializedObj.a[2])::class.java)
assertEquals((deserializedObj.a[1])::class.java, (deserializedObj.a[2])::class.java)
assertEquals(
outer.a[0]::class.java.getMethod("getA").invoke(outer.a[0]),
deserializedObj.a[0]::class.java.getMethod("getA").invoke(deserializedObj.a[0]))
assertEquals(
outer.a[1]::class.java.getMethod("getA").invoke(outer.a[1]),
deserializedObj.a[1]::class.java.getMethod("getA").invoke(deserializedObj.a[1]))
assertEquals(
outer.a[2]::class.java.getMethod("getA").invoke(outer.a[2]),
deserializedObj.a[2]::class.java.getMethod("getA").invoke(deserializedObj.a[2]))
}
@Test
fun reusedClasses() {
val cc = ClassCarpenterImpl(whitelist = AllWhitelist)
val innerType = cc.build(ClassSchema("${testName()}.inner", mapOf("a" to NonNullableField(Int::class.java))))
val outerType = cc.build(ClassSchema("${testName()}.outer", mapOf("a" to NonNullableField(innerType))))
val inner = innerType.constructors[0].newInstance(1)
val outer = outerType.constructors[0].newInstance(innerType.constructors[0].newInstance(2))
val serializedI = TestSerializationOutput(VERBOSE, sf1).serialize(inner)
val deserialisedI = DeserializationInput(sf2).deserialize(serializedI)
val serialisedO = TestSerializationOutput(VERBOSE, sf1).serialize(outer)
val deserialisedO = DeserializationInput(sf2).deserialize(serialisedO)
// ensure out carpented version of inner is reused
assertEquals(deserialisedI::class.java,
(deserialisedO::class.java.getMethod("getA").invoke(deserialisedO))::class.java)
}
@Test
fun nestedTypes() {
val cc = ClassCarpenterImpl(whitelist = AllWhitelist)
val nestedClass = cc.build(ClassSchema("nestedType",
mapOf("name" to NonNullableField(String::class.java))))
val outerClass = cc.build(ClassSchema("outerType",
mapOf("inner" to NonNullableField(nestedClass))))
val classInstance = outerClass.constructors.first().newInstance(nestedClass.constructors.first().newInstance("name"))
val serialisedBytes = TestSerializationOutput(VERBOSE, sf1).serialize(classInstance)
val deserializedObj = DeserializationInput(sf2).deserialize(serialisedBytes)
val inner = deserializedObj::class.java.getMethod("getInner").invoke(deserializedObj)
assertEquals("name", inner::class.java.getMethod("getName").invoke(inner))
}
@Test
fun repeatedNestedTypes() {
val cc = ClassCarpenterImpl(whitelist = AllWhitelist)
val nestedClass = cc.build(ClassSchema("nestedType",
mapOf("name" to NonNullableField(String::class.java))))
@CordaSerializable
data class outer(val a: Any, val b: Any)
val classInstance = outer(
nestedClass.constructors.first().newInstance("foo"),
nestedClass.constructors.first().newInstance("bar"))
val serialisedBytes = TestSerializationOutput(VERBOSE, sf1).serialize(classInstance)
val deserializedObj = DeserializationInput(sf2).deserialize(serialisedBytes)
assertEquals("foo", deserializedObj.a::class.java.getMethod("getName").invoke(deserializedObj.a))
assertEquals("bar", deserializedObj.b::class.java.getMethod("getName").invoke(deserializedObj.b))
}
@Test
fun listOfType() {
val unknownClass = ClassCarpenterImpl(whitelist = AllWhitelist).build(ClassSchema(testName(), mapOf(
"v1" to NonNullableField(Int::class.java),
"v2" to NonNullableField(Int::class.java))))
@CordaSerializable
data class outer(val l: List<Any>)
val toSerialise = outer(listOf(
unknownClass.constructors.first().newInstance(1, 2),
unknownClass.constructors.first().newInstance(3, 4),
unknownClass.constructors.first().newInstance(5, 6),
unknownClass.constructors.first().newInstance(7, 8)))
val serialisedBytes = TestSerializationOutput(VERBOSE, sf1).serialize(toSerialise)
val deserializedObj = DeserializationInput(sf2).deserialize(serialisedBytes)
var sentinel = 1
deserializedObj.l.forEach {
assertEquals(sentinel++, it::class.java.getMethod("getV1").invoke(it))
assertEquals(sentinel++, it::class.java.getMethod("getV2").invoke(it))
}
}
@Test
fun unknownInterface() {
val cc = ClassCarpenterImpl(whitelist = AllWhitelist)
val interfaceClass = cc.build(InterfaceSchema(
"gen.Interface",
mapOf("age" to NonNullableField(Int::class.java))))
val concreteClass = cc.build(ClassSchema(testName(), mapOf(
"age" to NonNullableField(Int::class.java),
"name" to NonNullableField(String::class.java)),
interfaces = listOf(I::class.java, interfaceClass)))
val serialisedBytes = TestSerializationOutput(VERBOSE, sf1).serialize(
concreteClass.constructors.first().newInstance(12, "timmy"))
val deserializedObj = DeserializationInput(sf2).deserialize(serialisedBytes)
assertTrue(deserializedObj is I)
assertEquals("timmy", (deserializedObj as I).getName())
assertEquals("timmy", deserializedObj::class.java.getMethod("getName").invoke(deserializedObj))
assertEquals(12, deserializedObj::class.java.getMethod("getAge").invoke(deserializedObj))
}
}

View File

@ -0,0 +1,500 @@
package net.corda.serialization.internal.amqp
import net.corda.serialization.internal.amqp.testutils.TestSerializationOutput
import net.corda.serialization.internal.amqp.testutils.deserialize
import net.corda.serialization.internal.amqp.testutils.serialize
import net.corda.serialization.internal.amqp.testutils.testDefaultFactoryNoEvolution
import org.junit.Test
import kotlin.test.assertEquals
// Prior to certain fixes being made within the [PropertySerializaer] classes these simple
// deserialization operations would've blown up with type mismatch errors where the deserlized
// char property of the class would've been treated as an Integer and given to the constructor
// as such
class DeserializeSimpleTypesTests {
companion object {
/**
* If you want to see the schema encoded into the envelope after serialisation change this to true
*/
private const val VERBOSE = false
}
val sf1 = testDefaultFactoryNoEvolution()
val sf2 = testDefaultFactoryNoEvolution()
@Test
fun testChar() {
data class C(val c: Char)
var deserializedC = DeserializationInput(sf1).deserialize(SerializationOutput(sf1).serialize(C('c')))
assertEquals('c', deserializedC.c)
// CYRILLIC CAPITAL LETTER YU (U+042E)
deserializedC = DeserializationInput(sf1).deserialize(SerializationOutput(sf1).serialize(C('Ю')))
assertEquals('Ю', deserializedC.c)
// ARABIC LETTER FEH WITH DOT BELOW (U+06A3)
deserializedC = DeserializationInput(sf1).deserialize(SerializationOutput(sf1).serialize(C('ڣ')))
assertEquals('ڣ', deserializedC.c)
// ARABIC LETTER DAD WITH DOT BELOW (U+06FB)
deserializedC = DeserializationInput(sf1).deserialize(SerializationOutput(sf1).serialize(C('ۻ')))
assertEquals('ۻ', deserializedC.c)
// BENGALI LETTER AA (U+0986)
deserializedC = DeserializationInput(sf1).deserialize(SerializationOutput(sf1).serialize(C('আ')))
assertEquals('আ', deserializedC.c)
}
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
@Test
fun testCharacter() {
data class C(val c: Character)
val c = C(Character('c'))
val serialisedC = SerializationOutput(sf1).serialize(c)
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
assertEquals(c.c, deserializedC.c)
}
@Test
fun testNullCharacter() {
data class C(val c: Char?)
val c = C(null)
val serialisedC = SerializationOutput(sf1).serialize(c)
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
assertEquals(c.c, deserializedC.c)
}
@Test
fun testArrayOfInt() {
class IA(val ia: Array<Int>)
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[]")
val serialisedIA = TestSerializationOutput(VERBOSE, sf1).serialize(ia)
val deserializedIA = DeserializationInput(sf1).deserialize(serialisedIA)
assertEquals(ia.ia.size, deserializedIA.ia.size)
assertEquals(ia.ia[0], deserializedIA.ia[0])
assertEquals(ia.ia[1], deserializedIA.ia[1])
assertEquals(ia.ia[2], deserializedIA.ia[2])
}
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
@Test
fun testArrayOfInteger() {
class IA(val ia: Array<Integer>)
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[]")
val serialisedIA = TestSerializationOutput(VERBOSE, sf1).serialize(ia)
val deserializedIA = DeserializationInput(sf1).deserialize(serialisedIA)
assertEquals(ia.ia.size, deserializedIA.ia.size)
assertEquals(ia.ia[0], deserializedIA.ia[0])
assertEquals(ia.ia[1], deserializedIA.ia[1])
assertEquals(ia.ia[2], deserializedIA.ia[2])
}
/**
* Test unboxed primitives
*/
@Test
fun testIntArray() {
class IA(val ia: IntArray)
val v = IntArray(3)
v[0] = 1; v[1] = 2; v[2] = 3
val ia = IA(v)
assertEquals("class [I", ia.ia::class.java.toString())
assertEquals(SerializerFactory.nameForType(ia.ia::class.java), "int[p]")
val serialisedIA = TestSerializationOutput(VERBOSE, sf1).serialize(ia)
val deserializedIA = DeserializationInput(sf1).deserialize(serialisedIA)
assertEquals(ia.ia.size, deserializedIA.ia.size)
assertEquals(ia.ia[0], deserializedIA.ia[0])
assertEquals(ia.ia[1], deserializedIA.ia[1])
assertEquals(ia.ia[2], deserializedIA.ia[2])
}
@Test
fun testArrayOfChars() {
class C(val c: Array<Char>)
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[]")
val serialisedC = TestSerializationOutput(VERBOSE, sf1).serialize(c)
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
assertEquals(c.c.size, deserializedC.c.size)
assertEquals(c.c[0], deserializedC.c[0])
assertEquals(c.c[1], deserializedC.c[1])
assertEquals(c.c[2], deserializedC.c[2])
}
@Test
fun testCharArray() {
class C(val c: CharArray)
val v = CharArray(3)
v[0] = 'a'; v[1] = 'b'; v[2] = 'c'
val c = C(v)
assertEquals("class [C", c.c::class.java.toString())
assertEquals(SerializerFactory.nameForType(c.c::class.java), "char[p]")
val serialisedC = TestSerializationOutput(VERBOSE, sf1).serialize(c)
var deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
assertEquals(c.c.size, deserializedC.c.size)
assertEquals(c.c[0], deserializedC.c[0])
assertEquals(c.c[1], deserializedC.c[1])
assertEquals(c.c[2], deserializedC.c[2])
// second test with more interesting characters
v[0] = 'ই'; v[1] = ' '; v[2] = 'ਔ'
val c2 = C(v)
deserializedC = DeserializationInput(sf1).deserialize(TestSerializationOutput(VERBOSE, sf1).serialize(c2))
assertEquals(c2.c.size, deserializedC.c.size)
assertEquals(c2.c[0], deserializedC.c[0])
assertEquals(c2.c[1], deserializedC.c[1])
assertEquals(c2.c[2], deserializedC.c[2])
}
@Test
fun testArrayOfBoolean() {
class C(val c: Array<Boolean>)
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[]")
val serialisedC = TestSerializationOutput(VERBOSE, sf1).serialize(c)
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
assertEquals(c.c.size, deserializedC.c.size)
assertEquals(c.c[0], deserializedC.c[0])
assertEquals(c.c[1], deserializedC.c[1])
assertEquals(c.c[2], deserializedC.c[2])
assertEquals(c.c[3], deserializedC.c[3])
}
@Test
fun testBooleanArray() {
class C(val c: BooleanArray)
val c = C(BooleanArray(4))
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]")
val serialisedC = TestSerializationOutput(VERBOSE, sf1).serialize(c)
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
assertEquals(c.c.size, deserializedC.c.size)
assertEquals(c.c[0], deserializedC.c[0])
assertEquals(c.c[1], deserializedC.c[1])
assertEquals(c.c[2], deserializedC.c[2])
assertEquals(c.c[3], deserializedC.c[3])
}
@Test
fun testArrayOfByte() {
class C(val c: Array<Byte>)
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[]")
val serialisedC = TestSerializationOutput(VERBOSE, sf1).serialize(c)
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
assertEquals(c.c.size, deserializedC.c.size)
assertEquals(c.c[0], deserializedC.c[0])
assertEquals(c.c[1], deserializedC.c[1])
assertEquals(c.c[2], deserializedC.c[2])
}
@Test
fun testByteArray() {
class C(val c: ByteArray)
val c = C(ByteArray(3))
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")
val serialisedC = TestSerializationOutput(VERBOSE, sf1).serialize(c)
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
assertEquals(c.c.size, deserializedC.c.size)
assertEquals(c.c[0], deserializedC.c[0])
assertEquals(c.c[1], deserializedC.c[1])
assertEquals(c.c[2], deserializedC.c[2])
}
@Test
fun testArrayOfShort() {
class C(val c: Array<Short>)
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[]")
val serialisedC = TestSerializationOutput(VERBOSE, sf1).serialize(c)
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
assertEquals(c.c.size, deserializedC.c.size)
assertEquals(c.c[0], deserializedC.c[0])
assertEquals(c.c[1], deserializedC.c[1])
assertEquals(c.c[2], deserializedC.c[2])
}
@Test
fun testShortArray() {
class C(val c: ShortArray)
val c = C(ShortArray(3))
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]")
val serialisedC = TestSerializationOutput(VERBOSE, sf1).serialize(c)
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
assertEquals(c.c.size, deserializedC.c.size)
assertEquals(c.c[0], deserializedC.c[0])
assertEquals(c.c[1], deserializedC.c[1])
assertEquals(c.c[2], deserializedC.c[2])
}
@Test
fun testArrayOfLong() {
class C(val c: Array<Long>)
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[]")
val serialisedC = TestSerializationOutput(VERBOSE, sf1).serialize(c)
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
assertEquals(c.c.size, deserializedC.c.size)
assertEquals(c.c[0], deserializedC.c[0])
assertEquals(c.c[1], deserializedC.c[1])
assertEquals(c.c[2], deserializedC.c[2])
}
@Test
fun testLongArray() {
class C(val c: LongArray)
val c = C(LongArray(3))
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]")
val serialisedC = TestSerializationOutput(VERBOSE, sf1).serialize(c)
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
assertEquals(c.c.size, deserializedC.c.size)
assertEquals(c.c[0], deserializedC.c[0])
assertEquals(c.c[1], deserializedC.c[1])
assertEquals(c.c[2], deserializedC.c[2])
}
@Test
fun testArrayOfFloat() {
class C(val c: Array<Float>)
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[]")
val serialisedC = TestSerializationOutput(VERBOSE, sf1).serialize(c)
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
assertEquals(c.c.size, deserializedC.c.size)
assertEquals(c.c[0], deserializedC.c[0])
assertEquals(c.c[1], deserializedC.c[1])
assertEquals(c.c[2], deserializedC.c[2])
}
@Test
fun testFloatArray() {
class C(val c: FloatArray)
val c = C(FloatArray(3))
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]")
val serialisedC = TestSerializationOutput(VERBOSE, sf1).serialize(c)
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
assertEquals(c.c.size, deserializedC.c.size)
assertEquals(c.c[0], deserializedC.c[0])
assertEquals(c.c[1], deserializedC.c[1])
assertEquals(c.c[2], deserializedC.c[2])
}
@Test
fun testArrayOfDouble() {
class C(val c: Array<Double>)
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[]")
val serialisedC = TestSerializationOutput(VERBOSE, sf1).serialize(c)
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
assertEquals(c.c.size, deserializedC.c.size)
assertEquals(c.c[0], deserializedC.c[0])
assertEquals(c.c[1], deserializedC.c[1])
assertEquals(c.c[2], deserializedC.c[2])
}
@Test
fun testDoubleArray() {
class C(val c: DoubleArray)
val c = C(DoubleArray(3))
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]")
val serialisedC = TestSerializationOutput(VERBOSE, sf1).serialize(c)
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
assertEquals(c.c.size, deserializedC.c.size)
assertEquals(c.c[0], deserializedC.c[0])
assertEquals(c.c[1], deserializedC.c[1])
assertEquals(c.c[2], deserializedC.c[2])
}
@Test
fun arrayOfArrayOfInt() {
class C(val c: Array<Array<Int>>)
val c = C(arrayOf(arrayOf(1, 2, 3), arrayOf(4, 5, 6)))
val serialisedC = TestSerializationOutput(VERBOSE, sf1).serialize(c)
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
assertEquals(c.c.size, deserializedC.c.size)
assertEquals(c.c[0].size, deserializedC.c[0].size)
assertEquals(c.c[0][0], deserializedC.c[0][0])
assertEquals(c.c[0][1], deserializedC.c[0][1])
assertEquals(c.c[0][2], deserializedC.c[0][2])
assertEquals(c.c[1].size, deserializedC.c[1].size)
assertEquals(c.c[1][0], deserializedC.c[1][0])
assertEquals(c.c[1][1], deserializedC.c[1][1])
assertEquals(c.c[1][2], deserializedC.c[1][2])
}
@Test
fun arrayOfIntArray() {
class C(val c: Array<IntArray>)
val c = C(arrayOf(IntArray(3), IntArray(3)))
c.c[0][0] = 1; c.c[0][1] = 2; c.c[0][2] = 3
c.c[1][0] = 4; c.c[1][1] = 5; c.c[1][2] = 6
val serialisedC = TestSerializationOutput(VERBOSE, sf1).serialize(c)
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
assertEquals(c.c.size, deserializedC.c.size)
assertEquals(c.c[0].size, deserializedC.c[0].size)
assertEquals(c.c[0][0], deserializedC.c[0][0])
assertEquals(c.c[0][1], deserializedC.c[0][1])
assertEquals(c.c[0][2], deserializedC.c[0][2])
assertEquals(c.c[1].size, deserializedC.c[1].size)
assertEquals(c.c[1][0], deserializedC.c[1][0])
assertEquals(c.c[1][1], deserializedC.c[1][1])
assertEquals(c.c[1][2], deserializedC.c[1][2])
}
@Test
fun arrayOfArrayOfIntArray() {
class C(val c: Array<Array<IntArray>>)
val c = C(arrayOf(arrayOf(IntArray(3), IntArray(3), IntArray(3)),
arrayOf(IntArray(3), IntArray(3), IntArray(3)),
arrayOf(IntArray(3), IntArray(3), IntArray(3))))
for (i in 0..2) {
for (j in 0..2) {
for (k in 0..2) {
c.c[i][j][k] = i + j + k
}
}
}
val serialisedC = TestSerializationOutput(VERBOSE, sf1).serialize(c)
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
for (i in 0..2) {
for (j in 0..2) {
for (k in 0..2) {
assertEquals(c.c[i][j][k], deserializedC.c[i][j][k])
}
}
}
}
@Test
fun nestedRepeatedTypes() {
class A(val a: A?, val b: Int)
var a = A(A(A(A(A(null, 1), 2), 3), 4), 5)
val sa = TestSerializationOutput(VERBOSE, sf1).serialize(a)
val da1 = DeserializationInput(sf1).deserialize(sa)
val da2 = DeserializationInput(sf2).deserialize(sa)
assertEquals(5, da1.b)
assertEquals(4, da1.a?.b)
assertEquals(3, da1.a?.a?.b)
assertEquals(2, da1.a?.a?.a?.b)
assertEquals(1, da1.a?.a?.a?.a?.b)
assertEquals(5, da2.b)
assertEquals(4, da2.a?.b)
assertEquals(3, da2.a?.a?.b)
assertEquals(2, da2.a?.a?.a?.b)
assertEquals(1, da2.a?.a?.a?.a?.b)
}
}

View File

@ -0,0 +1,105 @@
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

@ -0,0 +1,538 @@
package net.corda.serialization.internal.amqp
import net.corda.core.serialization.*
import net.corda.serialization.internal.amqp.testutils.*
import net.corda.testing.common.internal.ProjectStructure.projectRootDir
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Test
import java.io.NotSerializableException
import java.net.URI
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class EnumEvolvabilityTests {
@Suppress("UNUSED")
val localPath: URI = projectRootDir.toUri().resolve(
"serialization/src/test/resources/net/corda/serialization/internal/amqp")
companion object {
const val VERBOSE = false
}
enum class NotAnnotated {
A, B, C, D
}
@CordaSerializationTransformRenames()
enum class MissingRenames {
A, B, C, D
}
@CordaSerializationTransformEnumDefault("D", "A")
enum class AnnotatedEnumOnce {
A, B, C, D
}
@CordaSerializationTransformEnumDefaults(
CordaSerializationTransformEnumDefault("E", "D"),
CordaSerializationTransformEnumDefault("D", "A"))
enum class AnnotatedEnumTwice {
A, B, C, D, E
}
@CordaSerializationTransformRename("E", "D")
enum class RenameEnumOnce {
A, B, C, E
}
@Test
fun noAnnotation() {
data class C(val n: NotAnnotated)
val sf = testDefaultFactory()
val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(NotAnnotated.A))
assertEquals(2, bAndS.schema.types.size)
assertEquals(0, bAndS.transformsSchema.types.size)
}
@CordaSerializationTransformEnumDefaults()
enum class MissingDefaults {
A, B, C, D
}
@Test
fun missingDefaults() {
data class C(val m: MissingDefaults)
val sf = testDefaultFactory()
val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(MissingDefaults.A))
assertEquals(2, bAndS.schema.types.size)
assertEquals(0, bAndS.transformsSchema.types.size)
}
@Test
fun missingRenames() {
data class C(val m: MissingRenames)
val sf = testDefaultFactory()
val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(MissingRenames.A))
assertEquals(2, bAndS.schema.types.size)
assertEquals(0, bAndS.transformsSchema.types.size)
}
@Test
fun defaultAnnotationIsAddedToEnvelope() {
data class C(val annotatedEnum: AnnotatedEnumOnce)
val sf = testDefaultFactory()
val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(AnnotatedEnumOnce.D))
// only the enum is decorated so schema sizes should be different (2 objects, only one evolved)
assertEquals(2, bAndS.schema.types.size)
assertEquals(1, bAndS.transformsSchema.types.size)
assertEquals(AnnotatedEnumOnce::class.java.name, bAndS.transformsSchema.types.keys.first())
val schema = bAndS.transformsSchema.types.values.first()
assertEquals(1, schema.size)
assertTrue(schema.keys.contains(TransformTypes.EnumDefault))
assertEquals(1, schema[TransformTypes.EnumDefault]!!.size)
assertTrue(schema[TransformTypes.EnumDefault]!![0] is EnumDefaultSchemaTransform)
assertEquals("D", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemaTransform).new)
assertEquals("A", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemaTransform).old)
}
@Test
fun doubleDefaultAnnotationIsAddedToEnvelope() {
data class C(val annotatedEnum: AnnotatedEnumTwice)
val sf = testDefaultFactory()
val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(AnnotatedEnumTwice.E))
assertEquals(2, bAndS.schema.types.size)
assertEquals(1, bAndS.transformsSchema.types.size)
assertEquals(AnnotatedEnumTwice::class.java.name, bAndS.transformsSchema.types.keys.first())
val schema = bAndS.transformsSchema.types.values.first()
assertEquals(1, schema.size)
assertTrue(schema.keys.contains(TransformTypes.EnumDefault))
assertEquals(2, schema[TransformTypes.EnumDefault]!!.size)
assertTrue(schema[TransformTypes.EnumDefault]!![0] is EnumDefaultSchemaTransform)
assertEquals("E", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemaTransform).new)
assertEquals("D", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemaTransform).old)
assertTrue(schema[TransformTypes.EnumDefault]!![1] is EnumDefaultSchemaTransform)
assertEquals("D", (schema[TransformTypes.EnumDefault]!![1] as EnumDefaultSchemaTransform).new)
assertEquals("A", (schema[TransformTypes.EnumDefault]!![1] as EnumDefaultSchemaTransform).old)
}
@Test
fun defaultAnnotationIsAddedToEnvelopeAndDeserialised() {
data class C(val annotatedEnum: AnnotatedEnumOnce)
val sf = testDefaultFactory()
val sb = TestSerializationOutput(VERBOSE, sf).serialize(C(AnnotatedEnumOnce.D))
val db = DeserializationInput(sf).deserializeAndReturnEnvelope(sb)
// as with the serialisation stage, de-serialising the object we should see two
// types described in the header with one of those having transforms
assertEquals(2, db.envelope.schema.types.size)
assertEquals(1, db.envelope.transformsSchema.types.size)
val eName = AnnotatedEnumOnce::class.java.name
val types = db.envelope.schema.types
val transforms = db.envelope.transformsSchema.types
assertEquals(1, types.filter { it.name == eName }.size)
assertTrue(eName in transforms)
val schema = transforms[eName]
assertTrue(schema!!.keys.contains(TransformTypes.EnumDefault))
assertEquals(1, schema[TransformTypes.EnumDefault]!!.size)
assertTrue(schema[TransformTypes.EnumDefault]!![0] is EnumDefaultSchemaTransform)
assertEquals("D", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemaTransform).new)
assertEquals("A", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemaTransform).old)
}
@Test
fun doubleDefaultAnnotationIsAddedToEnvelopeAndDeserialised() {
data class C(val annotatedEnum: AnnotatedEnumTwice)
val sf = testDefaultFactory()
val sb = TestSerializationOutput(VERBOSE, sf).serialize(C(AnnotatedEnumTwice.E))
val db = DeserializationInput(sf).deserializeAndReturnEnvelope(sb)
// as with the serialisation stage, de-serialising the object we should see two
// types described in the header with one of those having transforms
assertEquals(2, db.envelope.schema.types.size)
assertEquals(1, db.envelope.transformsSchema.types.size)
val transforms = db.envelope.transformsSchema.types
assertTrue(transforms.contains(AnnotatedEnumTwice::class.java.name))
assertTrue(transforms[AnnotatedEnumTwice::class.java.name]!!.contains(TransformTypes.EnumDefault))
assertEquals(2, transforms[AnnotatedEnumTwice::class.java.name]!![TransformTypes.EnumDefault]!!.size)
val enumDefaults = transforms[AnnotatedEnumTwice::class.java.name]!![TransformTypes.EnumDefault]!!
assertEquals("E", (enumDefaults[0] as EnumDefaultSchemaTransform).new)
assertEquals("D", (enumDefaults[0] as EnumDefaultSchemaTransform).old)
assertEquals("D", (enumDefaults[1] as EnumDefaultSchemaTransform).new)
assertEquals("A", (enumDefaults[1] as EnumDefaultSchemaTransform).old)
}
@Test
fun renameAnnotationIsAdded() {
data class C(val annotatedEnum: RenameEnumOnce)
val sf = testDefaultFactory()
// Serialise the object
val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(RenameEnumOnce.E))
assertEquals(2, bAndS.schema.types.size)
assertEquals(1, bAndS.transformsSchema.types.size)
assertEquals(RenameEnumOnce::class.java.name, bAndS.transformsSchema.types.keys.first())
val serialisedSchema = bAndS.transformsSchema.types[RenameEnumOnce::class.java.name]!!
assertEquals(1, serialisedSchema.size)
assertTrue(serialisedSchema.containsKey(TransformTypes.Rename))
assertEquals(1, serialisedSchema[TransformTypes.Rename]!!.size)
assertEquals("D", (serialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).from)
assertEquals("E", (serialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).to)
// Now de-serialise the blob
val cAndS = DeserializationInput(sf).deserializeAndReturnEnvelope(bAndS.obj)
assertEquals(2, cAndS.envelope.schema.types.size)
assertEquals(1, cAndS.envelope.transformsSchema.types.size)
assertEquals(RenameEnumOnce::class.java.name, cAndS.envelope.transformsSchema.types.keys.first())
val deserialisedSchema = cAndS.envelope.transformsSchema.types[RenameEnumOnce::class.java.name]!!
assertEquals(1, deserialisedSchema.size)
assertTrue(deserialisedSchema.containsKey(TransformTypes.Rename))
assertEquals(1, deserialisedSchema[TransformTypes.Rename]!!.size)
assertEquals("D", (deserialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).from)
assertEquals("E", (deserialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).to)
}
@CordaSerializationTransformRenames(
CordaSerializationTransformRename("E", "C"),
CordaSerializationTransformRename("F", "D"))
enum class RenameEnumTwice {
A, B, E, F
}
@Test
fun doubleRenameAnnotationIsAdded() {
data class C(val annotatedEnum: RenameEnumTwice)
val sf = testDefaultFactory()
// Serialise the object
val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(RenameEnumTwice.F))
assertEquals(2, bAndS.schema.types.size)
assertEquals(1, bAndS.transformsSchema.types.size)
assertEquals(RenameEnumTwice::class.java.name, bAndS.transformsSchema.types.keys.first())
val serialisedSchema = bAndS.transformsSchema.types[RenameEnumTwice::class.java.name]!!
assertEquals(1, serialisedSchema.size)
assertTrue(serialisedSchema.containsKey(TransformTypes.Rename))
assertEquals(2, serialisedSchema[TransformTypes.Rename]!!.size)
assertEquals("C", (serialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).from)
assertEquals("E", (serialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).to)
assertEquals("D", (serialisedSchema[TransformTypes.Rename]!![1] as RenameSchemaTransform).from)
assertEquals("F", (serialisedSchema[TransformTypes.Rename]!![1] as RenameSchemaTransform).to)
// Now de-serialise the blob
val cAndS = DeserializationInput(sf).deserializeAndReturnEnvelope(bAndS.obj)
assertEquals(2, cAndS.envelope.schema.types.size)
assertEquals(1, cAndS.envelope.transformsSchema.types.size)
assertEquals(RenameEnumTwice::class.java.name, cAndS.envelope.transformsSchema.types.keys.first())
val deserialisedSchema = cAndS.envelope.transformsSchema.types[RenameEnumTwice::class.java.name]!!
assertEquals(1, deserialisedSchema.size)
assertTrue(deserialisedSchema.containsKey(TransformTypes.Rename))
assertEquals(2, deserialisedSchema[TransformTypes.Rename]!!.size)
assertEquals("C", (deserialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).from)
assertEquals("E", (deserialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).to)
assertEquals("D", (deserialisedSchema[TransformTypes.Rename]!![1] as RenameSchemaTransform).from)
assertEquals("F", (deserialisedSchema[TransformTypes.Rename]!![1] as RenameSchemaTransform).to)
}
@CordaSerializationTransformRename(from = "A", to = "X")
@CordaSerializationTransformEnumDefault(old = "X", new = "E")
enum class RenameAndExtendEnum {
X, B, C, D, E
}
@Test
fun bothAnnotationTypes() {
data class C(val annotatedEnum: RenameAndExtendEnum)
val sf = testDefaultFactory()
// Serialise the object
val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(RenameAndExtendEnum.X))
assertEquals(2, bAndS.schema.types.size)
assertEquals(1, bAndS.transformsSchema.types.size)
assertEquals(RenameAndExtendEnum::class.java.name, bAndS.transformsSchema.types.keys.first())
val serialisedSchema = bAndS.transformsSchema.types[RenameAndExtendEnum::class.java.name]!!
// This time there should be two distinct transform types (all previous tests have had only
// a single type
assertEquals(2, serialisedSchema.size)
assertTrue(serialisedSchema.containsKey(TransformTypes.Rename))
assertTrue(serialisedSchema.containsKey(TransformTypes.EnumDefault))
assertEquals(1, serialisedSchema[TransformTypes.Rename]!!.size)
assertEquals("A", (serialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).from)
assertEquals("X", (serialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).to)
assertEquals(1, serialisedSchema[TransformTypes.EnumDefault]!!.size)
assertEquals("E", (serialisedSchema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemaTransform).new)
assertEquals("X", (serialisedSchema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemaTransform).old)
}
@CordaSerializationTransformEnumDefaults(
CordaSerializationTransformEnumDefault("D", "A"),
CordaSerializationTransformEnumDefault("D", "A"))
enum class RepeatedAnnotation {
A, B, C, D, E
}
@Test
fun repeatedAnnotation() {
data class C(val a: RepeatedAnnotation)
val sf = testDefaultFactory()
assertThatThrownBy {
TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(RepeatedAnnotation.A))
}.isInstanceOf(NotSerializableException::class.java)
}
@CordaSerializationTransformEnumDefault("D", "A")
enum class E1 {
A, B, C, D
}
@CordaSerializationTransformEnumDefaults(
CordaSerializationTransformEnumDefault("D", "A"),
CordaSerializationTransformEnumDefault("E", "A"))
enum class E2 {
A, B, C, D, E
}
@CordaSerializationTransformEnumDefaults(CordaSerializationTransformEnumDefault("D", "A"))
enum class E3 {
A, B, C, D
}
@Test
fun multiEnums() {
data class A(val a: E1, val b: E2)
data class B(val a: E3, val b: A, val c: E1)
data class C(val a: B, val b: E2, val c: E3)
val c = C(B(E3.A, A(E1.A, E2.B), E1.C), E2.B, E3.A)
val sf = testDefaultFactory()
// Serialise the object
val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(c)
println(bAndS.transformsSchema)
// we have six types and three of those, the enums, should have transforms
assertEquals(6, bAndS.schema.types.size)
assertEquals(3, bAndS.transformsSchema.types.size)
assertTrue(E1::class.java.name in bAndS.transformsSchema.types)
assertTrue(E2::class.java.name in bAndS.transformsSchema.types)
assertTrue(E3::class.java.name in bAndS.transformsSchema.types)
val e1S = bAndS.transformsSchema.types[E1::class.java.name]!!
val e2S = bAndS.transformsSchema.types[E2::class.java.name]!!
val e3S = bAndS.transformsSchema.types[E3::class.java.name]!!
assertEquals(1, e1S.size)
assertEquals(1, e2S.size)
assertEquals(1, e3S.size)
assertTrue(TransformTypes.EnumDefault in e1S)
assertTrue(TransformTypes.EnumDefault in e2S)
assertTrue(TransformTypes.EnumDefault in e3S)
assertEquals(1, e1S[TransformTypes.EnumDefault]!!.size)
assertEquals(2, e2S[TransformTypes.EnumDefault]!!.size)
assertEquals(1, e3S[TransformTypes.EnumDefault]!!.size)
}
@Test
fun testCache() {
data class C2(val annotatedEnum: AnnotatedEnumOnce)
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])
}
//@UnknownTransformAnnotation(10, 20, 30)
enum class WithUnknownTest {
A, B, C, D
}
data class WrapsUnknown(val unknown: WithUnknownTest)
// To regenerate the types for this test uncomment UnknownTransformAnnotation from SupportedTransforms.kt and it's
// entry in the supportedTransforms list and the UnknownTest enum value in TransformTypes.kt
// ALSO: remember to re-annotate the enum WithUnkownTest above
@Test
fun testUnknownTransform() {
val resource = "EnumEvolvabilityTests.testUnknownTransform"
val sf = testDefaultFactory()
//File(URI("$localPath/$resource")).writeBytes(
// SerializationOutput(sf).serialize(WrapsUnknown(WithUnknownTest.D)).bytes)
val sb1 = EvolvabilityTests::class.java.getResource(resource).readBytes()
val envelope = DeserializationInput(sf).deserializeAndReturnEnvelope(SerializedBytes<WrapsUnknown>(sb1)).envelope
assertTrue(envelope.transformsSchema.types.containsKey(WithUnknownTest::class.java.name))
assertTrue(envelope.transformsSchema.types[WithUnknownTest::class.java.name]!!.containsKey(TransformTypes.Unknown))
}
//
// In this example we will have attempted to rename D back to C
//
// The life cycle of the class would've looked like this
//
// 1. enum class RejectCyclicRename { A, B, C }
// 2. enum class RejectCyclicRename { A, B, D }
// 3. enum class RejectCyclicRename { A, B, C }
//
// And we're not at 3. However, we ban this rename
//
@CordaSerializationTransformRenames(
CordaSerializationTransformRename("D", "C"),
CordaSerializationTransformRename("C", "D")
)
enum class RejectCyclicRename { A, B, C }
@Test
fun rejectCyclicRename() {
data class C(val e: RejectCyclicRename)
val sf = testDefaultFactory()
assertThatThrownBy {
SerializationOutput(sf).serialize(C(RejectCyclicRename.A))
}.isInstanceOf(NotSerializableException::class.java)
}
//
// In this test, like the above, we're looking to ensure repeated renames are rejected as
// unserailzble. However, in this case, it isn't a struct cycle, rather one element
// is renamed to match what a different element used to be called
//
@CordaSerializationTransformRenames(
CordaSerializationTransformRename(from = "B", to = "C"),
CordaSerializationTransformRename(from = "C", to = "D")
)
enum class RejectCyclicRenameAlt { A, C, D }
@Test
fun rejectCyclicRenameAlt() {
data class C(val e: RejectCyclicRenameAlt)
val sf = testDefaultFactory()
assertThatThrownBy {
SerializationOutput(sf).serialize(C(RejectCyclicRenameAlt.A))
}.isInstanceOf(NotSerializableException::class.java)
}
@CordaSerializationTransformRenames(
CordaSerializationTransformRename("G", "C"),
CordaSerializationTransformRename("F", "G"),
CordaSerializationTransformRename("E", "F"),
CordaSerializationTransformRename("D", "E"),
CordaSerializationTransformRename("C", "D")
)
enum class RejectCyclicRenameRedux { A, B, C }
@Test
fun rejectCyclicRenameRedux() {
data class C(val e: RejectCyclicRenameRedux)
val sf = testDefaultFactory()
assertThatThrownBy {
SerializationOutput(sf).serialize(C(RejectCyclicRenameRedux.A))
}.isInstanceOf(NotSerializableException::class.java)
}
@CordaSerializationTransformEnumDefault(new = "D", old = "X")
enum class RejectBadDefault { A, B, C, D }
@Test
fun rejectBadDefault() {
data class C(val e: RejectBadDefault)
val sf = testDefaultFactory()
assertThatThrownBy {
SerializationOutput(sf).serialize(C(RejectBadDefault.D))
}.isInstanceOf(NotSerializableException::class.java)
}
@CordaSerializationTransformEnumDefault(new = "D", old = "D")
enum class RejectBadDefaultToSelf { A, B, C, D }
@Test
fun rejectBadDefaultToSelf() {
data class C(val e: RejectBadDefaultToSelf)
val sf = testDefaultFactory()
assertThatThrownBy {
SerializationOutput(sf).serialize(C(RejectBadDefaultToSelf.D))
}.isInstanceOf(NotSerializableException::class.java)
}
}

View File

@ -0,0 +1,428 @@
package net.corda.serialization.internal.amqp
import net.corda.core.internal.toPath
import net.corda.core.serialization.CordaSerializationTransformEnumDefault
import net.corda.core.serialization.CordaSerializationTransformEnumDefaults
import net.corda.core.serialization.SerializedBytes
import net.corda.serialization.internal.amqp.testutils.deserialize
import net.corda.serialization.internal.amqp.testutils.serialize
import net.corda.serialization.internal.amqp.testutils.testDefaultFactory
import net.corda.serialization.internal.amqp.testutils.testName
import net.corda.testing.common.internal.ProjectStructure.projectRootDir
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Ignore
import org.junit.Test
import java.io.NotSerializableException
import java.net.URI
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
// NOTE: To recreate the test files used by these tests uncomment the original test classes and comment
// the new ones out, then change each test to write out the serialized bytes rather than read
// the file.
class EnumEvolveTests {
@Suppress("UNUSED")
var localPath: URI = projectRootDir.toUri().resolve(
"serialization/src/test/resources/net/corda/serialization/internal/amqp")
// Version of the class as it was serialised
//
// @CordaSerializationTransformEnumDefault("D", "C")
// enum class DeserializeNewerSetToUnknown { A, B, C, D }
//
// Version of the class as it's used in the test
enum class DeserializeNewerSetToUnknown { A, B, C }
@Test
fun deserialiseNewerSetToUnknown() {
val resource = "${javaClass.simpleName}.${testName()}"
val sf = testDefaultFactory()
data class C(val e: DeserializeNewerSetToUnknown)
// Uncomment to re-generate test files
// File(URI("$localPath/$resource")).writeBytes(
// SerializationOutput(sf).serialize(C(DeserializeNewerSetToUnknown.D)).bytes)
val url = javaClass.getResource(resource)
val obj = DeserializationInput(sf).deserialize(SerializedBytes<C>(url.readBytes()))
assertEquals(DeserializeNewerSetToUnknown.C, obj.e)
}
// Version of the class as it was serialised
//
// @CordaSerializationTransformEnumDefaults (
// CordaSerializationTransformEnumDefault("D", "C"),
// CordaSerializationTransformEnumDefault("E", "D"))
// enum class DeserializeNewerSetToUnknown2 { A, B, C, D, E }
//
// Version of the class as it's used in the test
enum class DeserializeNewerSetToUnknown2 { A, B, C }
@Test
fun deserialiseNewerSetToUnknown2() {
val resource = "${javaClass.simpleName}.${testName()}"
val sf = testDefaultFactory()
data class C(val e: DeserializeNewerSetToUnknown2)
// Uncomment to re-generate test files
// val so = SerializationOutput(sf)
// File(URI("$localPath/$resource.C")).writeBytes(so.serialize(C(DeserializeNewerSetToUnknown2.C)).bytes)
// File(URI("$localPath/$resource.D")).writeBytes(so.serialize(C(DeserializeNewerSetToUnknown2.D)).bytes)
// File(URI("$localPath/$resource.E")).writeBytes(so.serialize(C(DeserializeNewerSetToUnknown2.E)).bytes)
val url1 = javaClass.getResource("$resource.C")
val url2 = javaClass.getResource("$resource.D")
val url3 = javaClass.getResource("$resource.E")
// C will just work
val obj1 = DeserializationInput(sf).deserialize(SerializedBytes<C>(url1.readBytes()))
// D will transform directly to C
val obj2 = DeserializationInput(sf).deserialize(SerializedBytes<C>(url2.readBytes()))
// E will have to transform from E -> D -> C to work, so this should exercise that part
// of the evolution code
val obj3 = DeserializationInput(sf).deserialize(SerializedBytes<C>(url3.readBytes()))
assertEquals(DeserializeNewerSetToUnknown2.C, obj1.e)
assertEquals(DeserializeNewerSetToUnknown2.C, obj2.e)
assertEquals(DeserializeNewerSetToUnknown2.C, obj3.e)
}
// Version of the class as it was serialised, evolve rule purposfuly not included to
// test failure conditions
//
// enum class DeserializeNewerWithNoRule { A, B, C, D }
//
// Class as it exists for the test
enum class DeserializeNewerWithNoRule { A, B, C }
// Lets test to see if they forgot to provide an upgrade rule
@Test
fun deserialiseNewerWithNoRule() {
val resource = "${javaClass.simpleName}.${testName()}"
val sf = testDefaultFactory()
data class C(val e: DeserializeNewerWithNoRule)
// Uncomment to re-generate test files
// val so = SerializationOutput(sf)
// File(URI("$localPath/$resource")).writeBytes(so.serialize(C(DeserializeNewerWithNoRule.D)).bytes)
val url = EvolvabilityTests::class.java.getResource(resource)
assertThatThrownBy {
DeserializationInput(sf).deserialize(SerializedBytes<C>(url.readBytes()))
}.isInstanceOf(NotSerializableException::class.java)
}
// Version of class as it was serialized, at some point in the "future" several
// values have been renamed
//
// First Change
// A -> AA
// @CordaSerializationTransformRenames (
// CordaSerializationTransformRename(from ="A", to = "AA")
// )
// enum class DeserializeWithRename { AA, B, C }
//
// Second Change
// B -> BB
// @CordaSerializationTransformRenames (
// CordaSerializationTransformRename(from = "B", to = "BB"),
// CordaSerializationTransformRename(from = "A", to = "AA")
// )
// enum class DeserializeWithRename { AA, BB, C }
//
// Third Change
// BB -> XX
// @CordaSerializationTransformRenames (
// CordaSerializationTransformRename(from = "B", to = "BB"),
// CordaSerializationTransformRename(from = "BB", to = "XX"),
// CordaSerializationTransformRename(from = "A", to = "AA")
// )
// enum class DeserializeWithRename { AA, XX, C }
//
// Finally, the version we're using to test with
enum class DeserializeWithRename { A, B, C }
@Ignore("https://r3-cev.atlassian.net/browse/CORDA-1498")
@Test
fun deserializeWithRename() {
val resource = "${javaClass.simpleName}.${testName()}"
val sf = testDefaultFactory()
data class C(val e: DeserializeWithRename)
// Uncomment to re-generate test files, needs to be done in three stages
// val so = SerializationOutput(sf)
// First change
// File(URI("$localPath/$resource.1.AA")).writeBytes(so.serialize(C(DeserializeWithRename.AA)).bytes)
// File(URI("$localPath/$resource.1.B")).writeBytes(so.serialize(C(DeserializeWithRename.B)).bytes)
// File(URI("$localPath/$resource.1.C")).writeBytes(so.serialize(C(DeserializeWithRename.C)).bytes)
// Second change
// File(URI("$localPath/$resource.2.AA")).writeBytes(so.serialize(C(DeserializeWithRename.AA)).bytes)
// File(URI("$localPath/$resource.2.BB")).writeBytes(so.serialize(C(DeserializeWithRename.BB)).bytes)
// File(URI("$localPath/$resource.2.C")).writeBytes(so.serialize(C(DeserializeWithRename.C)).bytes)
// Third change
// File(URI("$localPath/$resource.3.AA")).writeBytes(so.serialize(C(DeserializeWithRename.AA)).bytes)
// File(URI("$localPath/$resource.3.XX")).writeBytes(so.serialize(C(DeserializeWithRename.XX)).bytes)
// File(URI("$localPath/$resource.3.C")).writeBytes(so.serialize(C(DeserializeWithRename.C)).bytes)
//
// Test we can deserialize instances of the class after its first transformation
//
val path1_AA = EvolvabilityTests::class.java.getResource("$resource.1.AA")
val path1_B = EvolvabilityTests::class.java.getResource("$resource.1.B")
val path1_C = EvolvabilityTests::class.java.getResource("$resource.1.C")
val obj1_AA = DeserializationInput(sf).deserialize(SerializedBytes<C>(path1_AA.readBytes()))
val obj1_B = DeserializationInput(sf).deserialize(SerializedBytes<C>(path1_B.readBytes()))
val obj1_C = DeserializationInput(sf).deserialize(SerializedBytes<C>(path1_C.readBytes()))
assertEquals(DeserializeWithRename.A, obj1_AA.e)
assertEquals(DeserializeWithRename.B, obj1_B.e)
assertEquals(DeserializeWithRename.C, obj1_C.e)
//
// Test we can deserialize instances of the class after its second transformation
//
val path2_AA = EvolvabilityTests::class.java.getResource("$resource.2.AA")
val path2_BB = EvolvabilityTests::class.java.getResource("$resource.2.BB")
val path2_C = EvolvabilityTests::class.java.getResource("$resource.2.C")
val obj2_AA = DeserializationInput(sf).deserialize(SerializedBytes<C>(path2_AA.readBytes()))
val obj2_BB = DeserializationInput(sf).deserialize(SerializedBytes<C>(path2_BB.readBytes()))
val obj2_C = DeserializationInput(sf).deserialize(SerializedBytes<C>(path2_C.readBytes()))
assertEquals(DeserializeWithRename.A, obj2_AA.e)
assertEquals(DeserializeWithRename.B, obj2_BB.e)
assertEquals(DeserializeWithRename.C, obj2_C.e)
//
// Test we can deserialize instances of the class after its third transformation
//
val path3_AA = EvolvabilityTests::class.java.getResource("$resource.3.AA")
val path3_XX = EvolvabilityTests::class.java.getResource("$resource.3.XX")
val path3_C = EvolvabilityTests::class.java.getResource("$resource.3.C")
val obj3_AA = DeserializationInput(sf).deserialize(SerializedBytes<C>(path3_AA.readBytes()))
val obj3_XX = DeserializationInput(sf).deserialize(SerializedBytes<C>(path3_XX.readBytes()))
val obj3_C = DeserializationInput(sf).deserialize(SerializedBytes<C>(path3_C.readBytes()))
assertEquals(DeserializeWithRename.A, obj3_AA.e)
assertEquals(DeserializeWithRename.B, obj3_XX.e)
assertEquals(DeserializeWithRename.C, obj3_C.e)
}
// The origional version of the enum, what we'll be eventually deserialising into
// enum class MultiOperations { A, B, C }
//
// First alteration, add D
// @CordaSerializationTransformEnumDefault(old = "C", new = "D")
// enum class MultiOperations { A, B, C, D }
//
// Second, add E
// @CordaSerializationTransformEnumDefaults(
// CordaSerializationTransformEnumDefault(old = "C", new = "D"),
// CordaSerializationTransformEnumDefault(old = "D", new = "E")
// )
// enum class MultiOperations { A, B, C, D, E }
//
// Third, Rename E to BOB
// @CordaSerializationTransformEnumDefaults(
// CordaSerializationTransformEnumDefault(old = "C", new = "D"),
// CordaSerializationTransformEnumDefault(old = "D", new = "E")
// )
// @CordaSerializationTransformRename(to = "BOB", from = "E")
// enum class MultiOperations { A, B, C, D, BOB }
//
// Fourth, Rename C to CAT, ADD F and G
// @CordaSerializationTransformEnumDefaults(
// CordaSerializationTransformEnumDefault(old = "F", new = "G"),
// CordaSerializationTransformEnumDefault(old = "BOB", new = "F"),
// CordaSerializationTransformEnumDefault(old = "D", new = "E"),
// CordaSerializationTransformEnumDefault(old = "C", new = "D")
// )
// @CordaSerializationTransformRenames (
// CordaSerializationTransformRename(to = "CAT", from = "C"),
// CordaSerializationTransformRename(to = "BOB", from = "E")
// )
// enum class MultiOperations { A, B, CAT, D, BOB, F, G}
//
// Fifth, Rename F to FLUMP, Rename BOB to BBB, Rename A to APPLE
// @CordaSerializationTransformEnumDefaults(
// CordaSerializationTransformEnumDefault(old = "F", new = "G"),
// CordaSerializationTransformEnumDefault(old = "BOB", new = "F"),
// CordaSerializationTransformEnumDefault(old = "D", new = "E"),
// CordaSerializationTransformEnumDefault(old = "C", new = "D")
// )
// @CordaSerializationTransformRenames (
// CordaSerializationTransformRename(to = "APPLE", from = "A"),
// CordaSerializationTransformRename(to = "BBB", from = "BOB"),
// CordaSerializationTransformRename(to = "FLUMP", from = "F"),
// CordaSerializationTransformRename(to = "CAT", from = "C"),
// CordaSerializationTransformRename(to = "BOB", from = "E")
// )
// enum class MultiOperations { APPLE, B, CAT, D, BBB, FLUMP, G}
//
// Finally, the original version of teh class that we're going to be testing with
enum class MultiOperations { A, B, C }
@Ignore("https://r3-cev.atlassian.net/browse/CORDA-1497")
@Test
fun multiOperations() {
val resource = "${javaClass.simpleName}.${testName()}"
val sf = testDefaultFactory()
data class C(val e: MultiOperations)
// Uncomment to re-generate test files, needs to be done in three stages
// val so = SerializationOutput(sf)
// First change
// File(URI("$localPath/$resource.1.A")).writeBytes(so.serialize(C(MultiOperations.A)).bytes)
// File(URI("$localPath/$resource.1.B")).writeBytes(so.serialize(C(MultiOperations.B)).bytes)
// File(URI("$localPath/$resource.1.C")).writeBytes(so.serialize(C(MultiOperations.C)).bytes)
// File(URI("$localPath/$resource.1.D")).writeBytes(so.serialize(C(MultiOperations.D)).bytes)
// Second change
// File(URI("$localPath/$resource.2.A")).writeBytes(so.serialize(C(MultiOperations.A)).bytes)
// File(URI("$localPath/$resource.2.B")).writeBytes(so.serialize(C(MultiOperations.B)).bytes)
// File(URI("$localPath/$resource.2.C")).writeBytes(so.serialize(C(MultiOperations.C)).bytes)
// File(URI("$localPath/$resource.2.D")).writeBytes(so.serialize(C(MultiOperations.D)).bytes)
// File(URI("$localPath/$resource.2.E")).writeBytes(so.serialize(C(MultiOperations.E)).bytes)
// Third change
// File(URI("$localPath/$resource.3.A")).writeBytes(so.serialize(C(MultiOperations.A)).bytes)
// File(URI("$localPath/$resource.3.B")).writeBytes(so.serialize(C(MultiOperations.B)).bytes)
// File(URI("$localPath/$resource.3.C")).writeBytes(so.serialize(C(MultiOperations.C)).bytes)
// File(URI("$localPath/$resource.3.D")).writeBytes(so.serialize(C(MultiOperations.D)).bytes)
// File(URI("$localPath/$resource.3.BOB")).writeBytes(so.serialize(C(MultiOperations.BOB)).bytes)
// Fourth change
// File(URI("$localPath/$resource.4.A")).writeBytes(so.serialize(C(MultiOperations.A)).bytes)
// File(URI("$localPath/$resource.4.B")).writeBytes(so.serialize(C(MultiOperations.B)).bytes)
// File(URI("$localPath/$resource.4.CAT")).writeBytes(so.serialize(C(MultiOperations.CAT)).bytes)
// File(URI("$localPath/$resource.4.D")).writeBytes(so.serialize(C(MultiOperations.D)).bytes)
// File(URI("$localPath/$resource.4.BOB")).writeBytes(so.serialize(C(MultiOperations.BOB)).bytes)
// File(URI("$localPath/$resource.4.F")).writeBytes(so.serialize(C(MultiOperations.F)).bytes)
// File(URI("$localPath/$resource.4.G")).writeBytes(so.serialize(C(MultiOperations.G)).bytes)
// Fifth change - { APPLE, B, CAT, D, BBB, FLUMP, G}
// File(URI("$localPath/$resource.5.APPLE")).writeBytes(so.serialize(C(MultiOperations.APPLE)).bytes)
// File(URI("$localPath/$resource.5.B")).writeBytes(so.serialize(C(MultiOperations.B)).bytes)
// File(URI("$localPath/$resource.5.CAT")).writeBytes(so.serialize(C(MultiOperations.CAT)).bytes)
// File(URI("$localPath/$resource.5.D")).writeBytes(so.serialize(C(MultiOperations.D)).bytes)
// File(URI("$localPath/$resource.5.BBB")).writeBytes(so.serialize(C(MultiOperations.BBB)).bytes)
// File(URI("$localPath/$resource.5.FLUMP")).writeBytes(so.serialize(C(MultiOperations.FLUMP)).bytes)
// File(URI("$localPath/$resource.5.G")).writeBytes(so.serialize(C(MultiOperations.G)).bytes)
val stage1Resources = listOf(
Pair("$resource.1.A", MultiOperations.A),
Pair("$resource.1.B", MultiOperations.B),
Pair("$resource.1.C", MultiOperations.C),
Pair("$resource.1.D", MultiOperations.C))
val stage2Resources = listOf(
Pair("$resource.2.A", MultiOperations.A),
Pair("$resource.2.B", MultiOperations.B),
Pair("$resource.2.C", MultiOperations.C),
Pair("$resource.2.D", MultiOperations.C),
Pair("$resource.2.E", MultiOperations.C))
val stage3Resources = listOf(
Pair("$resource.3.A", MultiOperations.A),
Pair("$resource.3.B", MultiOperations.B),
Pair("$resource.3.C", MultiOperations.C),
Pair("$resource.3.D", MultiOperations.C),
Pair("$resource.3.BOB", MultiOperations.C))
val stage4Resources = listOf(
Pair("$resource.4.A", MultiOperations.A),
Pair("$resource.4.B", MultiOperations.B),
Pair("$resource.4.CAT", MultiOperations.C),
Pair("$resource.4.D", MultiOperations.C),
Pair("$resource.4.BOB", MultiOperations.C),
Pair("$resource.4.F", MultiOperations.C),
Pair("$resource.4.G", MultiOperations.C))
val stage5Resources = listOf(
Pair("$resource.5.APPLE", MultiOperations.A),
Pair("$resource.5.B", MultiOperations.B),
Pair("$resource.5.CAT", MultiOperations.C),
Pair("$resource.5.D", MultiOperations.C),
Pair("$resource.5.BBB", MultiOperations.C),
Pair("$resource.5.FLUMP", MultiOperations.C),
Pair("$resource.5.G", MultiOperations.C))
fun load(l: List<Pair<String, MultiOperations>>) = l.map {
assertNotNull(EvolvabilityTests::class.java.getResource(it.first))
assertThat(EvolvabilityTests::class.java.getResource(it.first).toPath()).exists()
Pair(DeserializationInput(sf).deserialize(SerializedBytes<C>(
EvolvabilityTests::class.java.getResource(it.first).readBytes())), it.second)
}
load(stage1Resources).forEach { assertEquals(it.second, it.first.e) }
load(stage2Resources).forEach { assertEquals(it.second, it.first.e) }
load(stage3Resources).forEach { assertEquals(it.second, it.first.e) }
load(stage4Resources).forEach { assertEquals(it.second, it.first.e) }
load(stage5Resources).forEach { assertEquals(it.second, it.first.e) }
}
@CordaSerializationTransformEnumDefault(old = "A", new = "F")
enum class BadNewValue { A, B, C, D }
@Test
fun badNewValue() {
val sf = testDefaultFactory()
data class C(val e: BadNewValue)
assertThatThrownBy {
SerializationOutput(sf).serialize(C(BadNewValue.A))
}.isInstanceOf(NotSerializableException::class.java)
}
@CordaSerializationTransformEnumDefaults(
CordaSerializationTransformEnumDefault(new = "D", old = "E"),
CordaSerializationTransformEnumDefault(new = "E", old = "A")
)
enum class OutOfOrder { A, B, C, D, E }
@Test
fun outOfOrder() {
val sf = testDefaultFactory()
data class C(val e: OutOfOrder)
assertThatThrownBy {
SerializationOutput(sf).serialize(C(OutOfOrder.A))
}.isInstanceOf(NotSerializableException::class.java)
}
// class as it existed as it was serialized
//
// enum class ChangedOrdinality { A, B, C }
//
// class as it exists for the tests
@CordaSerializationTransformEnumDefault("D", "A")
enum class ChangedOrdinality { A, B, D, C }
@Test
fun changedOrdinality() {
val resource = "${javaClass.simpleName}.${testName()}"
val sf = testDefaultFactory()
data class C(val e: ChangedOrdinality)
// Uncomment to re-generate test files, needs to be done in three stages
// File(URI("$localPath/$resource")).writeBytes(
// SerializationOutput(sf).serialize(C(ChangedOrdinality.A)).bytes)
assertThatThrownBy {
DeserializationInput(sf).deserialize(SerializedBytes<C>(
EvolvabilityTests::class.java.getResource(resource).readBytes()))
}.isInstanceOf(NotSerializableException::class.java)
}
}

View File

@ -0,0 +1,268 @@
package net.corda.serialization.internal.amqp
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializedBytes
import net.corda.serialization.internal.amqp.testutils.TestSerializationOutput
import net.corda.serialization.internal.amqp.testutils.deserialize
import net.corda.serialization.internal.amqp.testutils.deserializeAndReturnEnvelope
import net.corda.serialization.internal.amqp.testutils.serialize
import net.corda.serialization.internal.amqp.testutils.serializeAndReturnSchema
import net.corda.serialization.internal.amqp.testutils.testDefaultFactoryNoEvolution
import net.corda.serialization.internal.amqp.testutils.testName
import org.assertj.core.api.Assertions
import org.junit.Test
import java.io.NotSerializableException
import java.time.DayOfWeek
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
class EnumTests {
enum class Bras {
TSHIRT, UNDERWIRE, PUSHUP, BRALETTE, STRAPLESS, SPORTS, BACKLESS, PADDED
}
@CordaSerializable
enum class AnnotatedBras {
TSHIRT, UNDERWIRE, PUSHUP, BRALETTE, STRAPLESS, SPORTS, BACKLESS, PADDED
}
// The state of the OldBras enum when the tests in changedEnum1 were serialised
// - use if the test file needs regenerating
//enum class OldBras {
// TSHIRT, UNDERWIRE, PUSHUP, BRALETTE
//}
// the new state, SPACER has been added to change the ordinality
enum class OldBras {
SPACER, TSHIRT, UNDERWIRE, PUSHUP, BRALETTE
}
// The state of the OldBras2 enum when the tests in changedEnum2 were serialised
// - use if the test file needs regenerating
//enum class OldBras2 {
// TSHIRT, UNDERWIRE, PUSHUP, BRALETTE
//}
// the new state, note in the test we serialised with value UNDERWIRE so the spacer
// occurring after this won't have changed the ordinality of our serialised value
// and thus should still be deserializable
enum class OldBras2 {
TSHIRT, UNDERWIRE, PUSHUP, SPACER, BRALETTE, SPACER2
}
enum class BrasWithInit(val someList: List<Int>) {
TSHIRT(emptyList()),
UNDERWIRE(listOf(1, 2, 3)),
PUSHUP(listOf(100, 200)),
BRALETTE(emptyList())
}
private val brasTestName = "${this.javaClass.name}\$Bras"
companion object {
/**
* If you want to see the schema encoded into the envelope after serialisation change this to true
*/
private const val VERBOSE = false
}
@Suppress("NOTHING_TO_INLINE")
private inline fun classTestName(clazz: String) = "${this.javaClass.name}\$${testName()}\$$clazz"
private val sf1 = testDefaultFactoryNoEvolution()
@Test
fun serialiseSimpleTest() {
data class C(val c: Bras)
val schema = TestSerializationOutput(VERBOSE, sf1).serializeAndReturnSchema(C(Bras.UNDERWIRE)).schema
assertEquals(2, schema.types.size)
val schema_c = schema.types.find { it.name == classTestName("C") } as CompositeType
val schema_bras = schema.types.find { it.name == brasTestName } as RestrictedType
assertNotNull(schema_c)
assertNotNull(schema_bras)
assertEquals(1, schema_c.fields.size)
assertEquals("c", schema_c.fields.first().name)
assertEquals(brasTestName, schema_c.fields.first().type)
assertEquals(8, schema_bras.choices.size)
Bras.values().forEach {
val bra = it
assertNotNull(schema_bras.choices.find { it.name == bra.name })
}
}
@Test
fun deserialiseSimpleTest() {
data class C(val c: Bras)
val objAndEnvelope = DeserializationInput(sf1).deserializeAndReturnEnvelope(
TestSerializationOutput(VERBOSE, sf1).serialize(C(Bras.UNDERWIRE)))
val obj = objAndEnvelope.obj
val schema = objAndEnvelope.envelope.schema
assertEquals(2, schema.types.size)
val schema_c = schema.types.find { it.name == classTestName("C") } as CompositeType
val schema_bras = schema.types.find { it.name == brasTestName } as RestrictedType
assertEquals(1, schema_c.fields.size)
assertEquals("c", schema_c.fields.first().name)
assertEquals(brasTestName, schema_c.fields.first().type)
assertEquals(8, schema_bras.choices.size)
Bras.values().forEach {
val bra = it
assertNotNull(schema_bras.choices.find { it.name == bra.name })
}
// Test the actual deserialised object
assertEquals(obj.c, Bras.UNDERWIRE)
}
@Test
fun multiEnum() {
data class Support(val top: Bras, val day: DayOfWeek)
data class WeeklySupport(val tops: List<Support>)
val week = WeeklySupport(listOf(
Support(Bras.PUSHUP, DayOfWeek.MONDAY),
Support(Bras.UNDERWIRE, DayOfWeek.WEDNESDAY),
Support(Bras.PADDED, DayOfWeek.SUNDAY)))
val obj = DeserializationInput(sf1).deserialize(TestSerializationOutput(VERBOSE, sf1).serialize(week))
assertEquals(week.tops[0].top, obj.tops[0].top)
assertEquals(week.tops[0].day, obj.tops[0].day)
assertEquals(week.tops[1].top, obj.tops[1].top)
assertEquals(week.tops[1].day, obj.tops[1].day)
assertEquals(week.tops[2].top, obj.tops[2].top)
assertEquals(week.tops[2].day, obj.tops[2].day)
}
@Test
fun enumWithInit() {
data class C(val c: BrasWithInit)
val c = C(BrasWithInit.PUSHUP)
val obj = DeserializationInput(sf1).deserialize(TestSerializationOutput(VERBOSE, sf1).serialize(c))
assertEquals(c.c, obj.c)
}
@Test(expected = NotSerializableException::class)
fun changedEnum1() {
val url = EnumTests::class.java.getResource("EnumTests.changedEnum1")
data class C(val a: OldBras)
// Original version of the class for the serialised version of this class
//
// val a = OldBras.TSHIRT
// val sc = SerializationOutput(sf1).serialize(C(a))
// f.writeBytes(sc.bytes)
// println(path)
val sc2 = url.readBytes()
// we expect this to throw
DeserializationInput(sf1).deserialize(SerializedBytes<C>(sc2))
}
@Test(expected = NotSerializableException::class)
fun changedEnum2() {
val url = EnumTests::class.java.getResource("EnumTests.changedEnum2")
data class C(val a: OldBras2)
// DO NOT CHANGE THIS, it's important we serialise with a value that doesn't
// change position in the upated enum class
// Original version of the class for the serialised version of this class
//
// val a = OldBras2.UNDERWIRE
// val sc = SerializationOutput(sf1).serialize(C(a))
// f.writeBytes(sc.bytes)
// println(path)
val sc2 = url.readBytes()
// we expect this to throw
DeserializationInput(sf1).deserialize(SerializedBytes<C>(sc2))
}
@Test
fun enumNotWhitelistedFails() {
data class C(val c: Bras)
class WL(val allowed: String) : ClassWhitelist {
override fun hasListed(type: Class<*>): Boolean {
return type.name == allowed
}
}
val factory = SerializerFactory(WL(classTestName("C")), ClassLoader.getSystemClassLoader())
Assertions.assertThatThrownBy {
TestSerializationOutput(VERBOSE, factory).serialize(C(Bras.UNDERWIRE))
}.isInstanceOf(NotSerializableException::class.java)
}
@Test
fun enumWhitelisted() {
data class C(val c: Bras)
class WL : ClassWhitelist {
override fun hasListed(type: Class<*>): Boolean {
return type.name == "net.corda.serialization.internal.amqp.EnumTests\$enumWhitelisted\$C" ||
type.name == "net.corda.serialization.internal.amqp.EnumTests\$Bras"
}
}
val factory = SerializerFactory(WL(), ClassLoader.getSystemClassLoader())
// if it all works, this won't explode
TestSerializationOutput(VERBOSE, factory).serialize(C(Bras.UNDERWIRE))
}
@Test
fun enumAnnotated() {
@CordaSerializable data class C(val c: AnnotatedBras)
class WL : ClassWhitelist {
override fun hasListed(type: Class<*>) = false
}
val factory = SerializerFactory(WL(), ClassLoader.getSystemClassLoader())
// if it all works, this won't explode
TestSerializationOutput(VERBOSE, factory).serialize(C(AnnotatedBras.UNDERWIRE))
}
@Test
fun deserializeNonWhitlistedEnum() {
data class C(val c: Bras)
class WL(val allowed: List<String>) : ClassWhitelist {
override fun hasListed(type: Class<*>) = type.name in allowed
}
// first serialise the class using a context in which Bras are whitelisted
val factory = SerializerFactory(WL(listOf(classTestName("C"),
"net.corda.serialization.internal.amqp.EnumTests\$Bras")),
ClassLoader.getSystemClassLoader())
val bytes = TestSerializationOutput(VERBOSE, factory).serialize(C(Bras.UNDERWIRE))
// then take that serialised object and attempt to deserialize it in a context that
// disallows the Bras enum
val factory2 = SerializerFactory(WL(listOf(classTestName("C"))), ClassLoader.getSystemClassLoader())
Assertions.assertThatThrownBy {
DeserializationInput(factory2).deserialize(bytes)
}.isInstanceOf(NotSerializableException::class.java)
}
}

View File

@ -0,0 +1,83 @@
package net.corda.serialization.internal.amqp
import net.corda.serialization.internal.amqp.testutils.TestSerializationOutput
import net.corda.serialization.internal.amqp.testutils.deserialize
import net.corda.serialization.internal.amqp.testutils.serialize
import net.corda.serialization.internal.amqp.testutils.testDefaultFactory
import net.corda.serialization.internal.amqp.testutils.testName
import org.assertj.core.api.Assertions
import org.junit.Ignore
import org.junit.Test
import java.io.NotSerializableException
class ErrorMessagesTests {
companion object {
val VERBOSE get() = false
}
private fun errMsg(property: String, testname: String) =
"Property '$property' or its getter is non public, this renders class 'class $testname\$C' unserializable -> class $testname\$C"
// Java allows this to be set at the class level yet Kotlin doesn't for some reason
@Ignore("Current behaviour allows for the serialization of objects with private members, this will be disallowed at some point in the future")
@Test
fun privateProperty() {
data class C(private val a: Int)
val sf = testDefaultFactory()
val testname = "${javaClass.name}\$${testName()}"
Assertions.assertThatThrownBy {
TestSerializationOutput(VERBOSE, sf).serialize(C(1))
}.isInstanceOf(NotSerializableException::class.java).hasMessage(errMsg("a", testname))
}
// Java allows this to be set at the class level yet Kotlin doesn't for some reason
@Ignore("Current behaviour allows for the serialization of objects with private members, this will be disallowed at some point in the future")
@Test
fun privateProperty2() {
data class C(val a: Int, private val b: Int)
val sf = testDefaultFactory()
val testname = "${javaClass.name}\$${testName()}"
Assertions.assertThatThrownBy {
TestSerializationOutput(VERBOSE, sf).serialize(C(1, 2))
}.isInstanceOf(NotSerializableException::class.java).hasMessage(errMsg("b", testname))
}
// Java allows this to be set at the class level yet Kotlin doesn't for some reason
@Ignore("Current behaviour allows for the serialization of objects with private members, this will be disallowed at some point in the future")
@Test
fun privateProperty3() {
// despite b being private, the getter we've added is public and thus allows for the serialisation
// of the object
data class C(val a: Int, private val b: Int) {
public fun getB() = b
}
val sf = testDefaultFactory()
val testname = "${javaClass.name}\$${testName()}"
val bytes = TestSerializationOutput(VERBOSE, sf).serialize(C(1, 2))
val c = DeserializationInput(sf).deserialize(bytes)
}
// Java allows this to be set at the class level yet Kotlin doesn't for some reason
@Ignore("Current behaviour allows for the serialization of objects with private members, this will be disallowed at some point in the future")
@Test
fun protectedProperty() {
data class C(protected val a: Int)
val sf = testDefaultFactory()
val testname = "${javaClass.name}\$${testName()}"
Assertions.assertThatThrownBy {
TestSerializationOutput(VERBOSE, sf).serialize(C(1))
}.isInstanceOf(NotSerializableException::class.java).hasMessage(errMsg("a", testname))
}
}

View File

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

View File

@ -0,0 +1,574 @@
package net.corda.serialization.internal.amqp
import net.corda.core.crypto.Crypto.generateKeyPair
import net.corda.core.crypto.SignedData
import net.corda.core.crypto.sign
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NotaryInfo
import net.corda.core.serialization.ConstructorForDeserialization
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.core.serialization.SerializedBytes
import net.corda.serialization.internal.amqp.testutils.TestSerializationOutput
import net.corda.serialization.internal.amqp.testutils.deserialize
import net.corda.serialization.internal.amqp.testutils.serialize
import net.corda.serialization.internal.amqp.testutils.testDefaultFactory
import net.corda.testing.common.internal.ProjectStructure.projectRootDir
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.TestIdentity
import org.junit.Ignore
import org.junit.Test
import java.io.File
import java.io.NotSerializableException
import java.net.URI
import java.time.Instant
import kotlin.test.assertEquals
// To regenerate any of the binary test files do the following
//
// 0. set localPath accordingly
// 1. Uncomment the code where the original form of the class is defined in the test
// 2. Comment out the rest of the test
// 3. Run the test
// 4. Using the printed path copy that file to the resources directory
// 5. Comment back out the generation code and uncomment the actual test
class EvolvabilityTests {
// When regenerating the test files this needs to be set to the file system location of the resource files
@Suppress("UNUSED")
var localPath: URI = projectRootDir.toUri().resolve(
"serialization/src/test/resources/net/corda/serialization/internal/amqp")
@Test
fun simpleOrderSwapSameType() {
val sf = testDefaultFactory()
val resource = "EvolvabilityTests.simpleOrderSwapSameType"
val A = 1
val B = 2
// Original version of the class for the serialised version of this class
// data class C (val a: Int, val b: Int)
// File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(C(A, B)).bytes)
// new version of the class, in this case the order of the parameters has been swapped
data class C(val b: Int, val a: Int)
val url = EvolvabilityTests::class.java.getResource(resource)
val sc2 = url.readBytes()
val deserializedC = DeserializationInput(sf).deserialize(SerializedBytes<C>(sc2))
assertEquals(A, deserializedC.a)
assertEquals(B, deserializedC.b)
}
@Test
fun simpleOrderSwapDifferentType() {
val sf = testDefaultFactory()
val A = 1
val B = "two"
val resource = "EvolvabilityTests.simpleOrderSwapDifferentType"
// Original version of the class as it was serialised
// data class C (val a: Int, val b: String)
// File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(C(A, B)).bytes)
// new version of the class, in this case the order of the parameters has been swapped
data class C(val b: String, val a: Int)
val url = EvolvabilityTests::class.java.getResource(resource)
val sc2 = url.readBytes()
val deserializedC = DeserializationInput(sf).deserialize(SerializedBytes<C>(sc2))
assertEquals(A, deserializedC.a)
assertEquals(B, deserializedC.b)
}
@Test
fun addAdditionalParamNotMandatory() {
val sf = testDefaultFactory()
val A = 1
val resource = "EvolvabilityTests.addAdditionalParamNotMandatory"
// Original version of the class as it was serialised
// data class C(val a: Int)
// File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(C(A)).bytes)
data class C(val a: Int, val b: Int?)
val url = EvolvabilityTests::class.java.getResource(resource)
val sc2 = url.readBytes()
val deserializedC = DeserializationInput(sf).deserialize(SerializedBytes<C>(sc2))
assertEquals(A, deserializedC.a)
assertEquals(null, deserializedC.b)
}
@Test(expected = NotSerializableException::class)
fun addAdditionalParam() {
val sf = testDefaultFactory()
val url = EvolvabilityTests::class.java.getResource("EvolvabilityTests.addAdditionalParam")
@Suppress("UNUSED_VARIABLE")
val A = 1
// Original version of the class as it was serialised
//
// data class C(val a: Int)
// val sc = SerializationOutput(sf).serialize(C(A))
// f.writeBytes(sc.bytes)
// println ("Path = $path")
// new version of the class, in this case a new parameter has been added (b)
data class C(val a: Int, val b: Int)
val sc2 = url.readBytes()
// Expected to throw as we can't construct the new type as it contains a newly
// added parameter that isn't optional, i.e. not nullable and there isn't
// a constructor that takes the old parameters
DeserializationInput(sf).deserialize(SerializedBytes<C>(sc2))
}
@Suppress("UNUSED_VARIABLE")
@Test
fun removeParameters() {
val sf = testDefaultFactory()
val resource = "EvolvabilityTests.removeParameters"
val A = 1
val B = "two"
val C = "three"
val D = 4
// Original version of the class as it was serialised
// data class CC(val a: Int, val b: String, val c: String, val d: Int)
// File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(CC(A, B, C, D)).bytes)
data class CC(val b: String, val d: Int)
val url = EvolvabilityTests::class.java.getResource("EvolvabilityTests.removeParameters")
val sc2 = url.readBytes()
val deserializedCC = DeserializationInput(sf).deserialize(SerializedBytes<CC>(sc2))
assertEquals(B, deserializedCC.b)
assertEquals(D, deserializedCC.d)
}
@Suppress("UNUSED_VARIABLE")
@Test
fun addAndRemoveParameters() {
val sf = testDefaultFactory()
val A = 1
val B = "two"
val C = "three"
val D = 4
val E = null
val resource = "EvolvabilityTests.addAndRemoveParameters"
// Original version of the class as it was serialised
// data class CC(val a: Int, val b: String, val c: String, val d: Int)
// File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(CC(A, B, C, D)).bytes)
data class CC(val a: Int, val e: Boolean?, val d: Int)
val url = EvolvabilityTests::class.java.getResource(resource)
val sc2 = url.readBytes()
val deserializedCC = DeserializationInput(sf).deserialize(SerializedBytes<CC>(sc2))
assertEquals(A, deserializedCC.a)
assertEquals(E, deserializedCC.e)
assertEquals(D, deserializedCC.d)
}
@Test
fun addMandatoryFieldWithAltConstructor() {
val sf = testDefaultFactory()
val A = 1
val resource = "EvolvabilityTests.addMandatoryFieldWithAltConstructor"
// Original version of the class as it was serialised
// data class CC(val a: Int)
// File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(CC(A)).bytes)
@Suppress("UNUSED")
data class CC(val a: Int, val b: String) {
@DeprecatedConstructorForDeserialization(1)
constructor (a: Int) : this(a, "hello")
}
val url = EvolvabilityTests::class.java.getResource(resource)
val sc2 = url.readBytes()
val deserializedCC = DeserializationInput(sf).deserialize(SerializedBytes<CC>(sc2))
assertEquals(A, deserializedCC.a)
assertEquals("hello", deserializedCC.b)
}
@Test(expected = NotSerializableException::class)
@Suppress("UNUSED")
fun addMandatoryFieldWithAltConstructorUnAnnotated() {
val sf = testDefaultFactory()
val url = EvolvabilityTests::class.java.getResource(
"EvolvabilityTests.addMandatoryFieldWithAltConstructorUnAnnotated")
@Suppress("UNUSED_VARIABLE")
val A = 1
// Original version of the class as it was serialised
//
// data class CC(val a: Int)
// val scc = SerializationOutput(sf).serialize(CC(A))
// f.writeBytes(scc.bytes)
// println ("Path = $path")
data class CC(val a: Int, val b: String) {
// constructor annotation purposefully omitted
constructor (a: Int) : this(a, "hello")
}
// we expect this to throw as we should not find any constructors
// capable of dealing with this
DeserializationInput(sf).deserialize(SerializedBytes<CC>(url.readBytes()))
}
@Test
fun addMandatoryFieldWithAltReorderedConstructor() {
val sf = testDefaultFactory()
val resource = "EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructor"
val A = 1
val B = 100
val C = "This is not a banana"
// Original version of the class as it was serialised
// data class CC(val a: Int, val b: Int, val c: String)
// File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(CC(A, B, C)).bytes)
@Suppress("UNUSED")
data class CC(val a: Int, val b: Int, val c: String, val d: String) {
// ensure none of the original parameters align with the initial
// construction order
@DeprecatedConstructorForDeserialization(1)
constructor (c: String, a: Int, b: Int) : this(a, b, c, "wibble")
}
val url = EvolvabilityTests::class.java.getResource(resource)
val sc2 = url.readBytes()
val deserializedCC = DeserializationInput(sf).deserialize(SerializedBytes<CC>(sc2))
assertEquals(A, deserializedCC.a)
assertEquals(B, deserializedCC.b)
assertEquals(C, deserializedCC.c)
assertEquals("wibble", deserializedCC.d)
}
@Test
fun addMandatoryFieldWithAltReorderedConstructorAndRemoval() {
val sf = testDefaultFactory()
val resource = "EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructorAndRemoval"
val A = 1
@Suppress("UNUSED_VARIABLE")
val B = 100
val C = "This is not a banana"
// Original version of the class as it was serialised
// data class CC(val a: Int, val b: Int, val c: String)
// File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(CC(A, B, C)).bytes)
// b is removed, d is added
data class CC(val a: Int, val c: String, val d: String) {
// ensure none of the original parameters align with the initial
// construction order
@Suppress("UNUSED")
@DeprecatedConstructorForDeserialization(1)
constructor (c: String, a: Int) : this(a, c, "wibble")
}
val url = EvolvabilityTests::class.java.getResource(resource)
val sc2 = url.readBytes()
val deserializedCC = DeserializationInput(sf).deserialize(SerializedBytes<CC>(sc2))
assertEquals(A, deserializedCC.a)
assertEquals(C, deserializedCC.c)
assertEquals("wibble", deserializedCC.d)
}
@Test
fun multiVersion() {
val sf = testDefaultFactory()
val resource1 = "EvolvabilityTests.multiVersion.1"
val resource2 = "EvolvabilityTests.multiVersion.2"
val resource3 = "EvolvabilityTests.multiVersion.3"
val a = 100
val b = 200
val c = 300
val d = 400
// Original version of the class as it was serialised
//
// Version 1:
// data class C (val a: Int, val b: Int)
// File(URI("$localPath/$resource1")).writeBytes(SerializationOutput(sf).serialize(C(a, b)).bytes)
//
// Version 2 - add param c
// data class C (val c: Int, val b: Int, val a: Int)
// File(URI("$localPath/$resource2")).writeBytes(SerializationOutput(sf).serialize(C(c, b, a)).bytes)
//
// Version 3 - add param d
// data class C (val b: Int, val c: Int, val d: Int, val a: Int)
// File(URI("$localPath/$resource3")).writeBytes(SerializationOutput(sf).serialize(C(b, c, d, a)).bytes)
@Suppress("UNUSED")
data class C(val e: Int, val c: Int, val b: Int, val a: Int, val d: Int) {
@DeprecatedConstructorForDeserialization(1)
constructor (b: Int, a: Int) : this(-1, -1, b, a, -1)
@DeprecatedConstructorForDeserialization(2)
constructor (a: Int, c: Int, b: Int) : this(-1, c, b, a, -1)
@DeprecatedConstructorForDeserialization(3)
constructor (a: Int, b: Int, c: Int, d: Int) : this(-1, c, b, a, d)
}
val url1 = EvolvabilityTests::class.java.getResource(resource1)
val url2 = EvolvabilityTests::class.java.getResource(resource2)
val url3 = EvolvabilityTests::class.java.getResource(resource3)
val sb1 = url1.readBytes()
val db1 = DeserializationInput(sf).deserialize(SerializedBytes<C>(sb1))
assertEquals(a, db1.a)
assertEquals(b, db1.b)
assertEquals(-1, db1.c)
assertEquals(-1, db1.d)
assertEquals(-1, db1.e)
val sb2 = url2.readBytes()
val db2 = DeserializationInput(sf).deserialize(SerializedBytes<C>(sb2))
assertEquals(a, db2.a)
assertEquals(b, db2.b)
assertEquals(c, db2.c)
assertEquals(-1, db2.d)
assertEquals(-1, db2.e)
val sb3 = url3.readBytes()
val db3 = DeserializationInput(sf).deserialize(SerializedBytes<C>(sb3))
assertEquals(a, db3.a)
assertEquals(b, db3.b)
assertEquals(c, db3.c)
assertEquals(d, db3.d)
assertEquals(-1, db3.e)
}
@Test
fun changeSubType() {
val sf = testDefaultFactory()
val resource = "EvolvabilityTests.changeSubType"
val oa = 100
val ia = 200
// Original version of the class as it was serialised
// data class Inner (val a: Int)
// data class Outer (val a: Int, val b: Inner)
// File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(Outer(oa, Inner (ia))).bytes)
// Add a parameter to inner but keep outer unchanged
data class Inner(val a: Int, val b: String?)
data class Outer(val a: Int, val b: Inner)
val url = EvolvabilityTests::class.java.getResource(resource)
val sc2 = url.readBytes()
val outer = DeserializationInput(sf).deserialize(SerializedBytes<Outer>(sc2))
assertEquals(oa, outer.a)
assertEquals(ia, outer.b.a)
assertEquals(null, outer.b.b)
}
@Test
fun multiVersionWithRemoval() {
val sf = testDefaultFactory()
val resource1 = "EvolvabilityTests.multiVersionWithRemoval.1"
val resource2 = "EvolvabilityTests.multiVersionWithRemoval.2"
val resource3 = "EvolvabilityTests.multiVersionWithRemoval.3"
@Suppress("UNUSED_VARIABLE")
val a = 100
val b = 200
val c = 300
val d = 400
val e = 500
val f = 600
// Original version of the class as it was serialised
//
// Version 1:
// data class C (val a: Int, val b: Int, val c: Int)
// File(URI("$localPath/$resource1")).writeBytes(SerializationOutput(sf).serialize(C(a, b, c)).bytes)
//
// Version 2 - remove property a, add property e
// data class C (val b: Int, val c: Int, val d: Int, val e: Int)
// File(URI("$localPath/$resource2")).writeBytes(SerializationOutput(sf).serialize(C(b, c, d, e)).bytes)
//
// Version 3 - add param d
// data class C (val b: Int, val c: Int, val d: Int, val e: Int, val f: Int)
// File(URI("$localPath/$resource3")).writeBytes(SerializationOutput(sf).serialize(C(b, c, d, e, f)).bytes)
@Suppress("UNUSED")
data class C(val b: Int, val c: Int, val d: Int, val e: Int, val f: Int, val g: Int) {
@DeprecatedConstructorForDeserialization(1)
constructor (b: Int, c: Int) : this(b, c, -1, -1, -1, -1)
@DeprecatedConstructorForDeserialization(2)
constructor (b: Int, c: Int, d: Int) : this(b, c, d, -1, -1, -1)
@DeprecatedConstructorForDeserialization(3)
constructor (b: Int, c: Int, d: Int, e: Int) : this(b, c, d, e, -1, -1)
@DeprecatedConstructorForDeserialization(4)
constructor (b: Int, c: Int, d: Int, e: Int, f: Int) : this(b, c, d, e, f, -1)
}
val url1 = EvolvabilityTests::class.java.getResource(resource1)
val url2 = EvolvabilityTests::class.java.getResource(resource2)
val url3 = EvolvabilityTests::class.java.getResource(resource3)
val sb1 = url1.readBytes()
val db1 = DeserializationInput(sf).deserialize(SerializedBytes<C>(sb1))
assertEquals(b, db1.b)
assertEquals(c, db1.c)
assertEquals(-1, db1.d) // must not be set by calling constructor 2 by mistake
assertEquals(-1, db1.e)
assertEquals(-1, db1.f)
assertEquals(-1, db1.g)
val sb2 = url2.readBytes()
val db2 = DeserializationInput(sf).deserialize(SerializedBytes<C>(sb2))
assertEquals(b, db2.b)
assertEquals(c, db2.c)
assertEquals(d, db2.d)
assertEquals(e, db2.e)
assertEquals(-1, db2.f)
assertEquals(-1, db1.g)
val sb3 = url3.readBytes()
val db3 = DeserializationInput(sf).deserialize(SerializedBytes<C>(sb3))
assertEquals(b, db3.b)
assertEquals(c, db3.c)
assertEquals(d, db3.d)
assertEquals(e, db3.e)
assertEquals(f, db3.f)
assertEquals(-1, db3.g)
}
//
// This test uses a NetworkParameters signed set of bytes generated by R3 Corda and
// is here to ensure we can still read them. This test exists because of the break in
// being able to deserialize an object serialized prior to some fixes to the fingerprinter.
//
// The file itself was generated from R3 Corda at commit
// 6a6b6f256 Skip cache invalidation during init() - caches are still null.
//
// To regenerate the file un-ignore the test below this one (regenerate broken network parameters),
// to regenerate at a specific version add that test to a checkout at the desired sha then take
// the resulting file and add to the repo, changing the filename as appropriate
//
@Test
@Ignore("Test fails after moving NetworkParameters and NotaryInfo into core from node-api")
fun readBrokenNetworkParameters(){
val sf = testDefaultFactory()
sf.register(net.corda.serialization.internal.amqp.custom.InstantSerializer(sf))
sf.register(net.corda.serialization.internal.amqp.custom.PublicKeySerializer)
//
// filename breakdown
// networkParams - because this is a serialised set of network parameters
// r3corda - generated by R3 Corda instead of Corda
// 6a6b6f256 - Commit sha of the build that generated the file we're testing against
//
val resource = "networkParams.r3corda.6a6b6f256"
val url = EvolvabilityTests::class.java.getResource(resource)
val sc2 = url.readBytes()
val deserializedC = DeserializationInput(sf).deserialize(SerializedBytes<SignedData<NetworkParameters>>(sc2))
val networkParams = DeserializationInput(sf).deserialize(deserializedC.raw)
assertEquals(1000, networkParams.maxMessageSize)
assertEquals(1000, networkParams.maxTransactionSize)
assertEquals(3, networkParams.minimumPlatformVersion)
assertEquals(1, networkParams.notaries.size)
assertEquals(TestIdentity(DUMMY_NOTARY_NAME, 20).party, networkParams.notaries.firstOrNull()?.identity)
}
//
// This test created a serialized and signed set of Network Parameters to test whether we
// can still deserialize them
//
@Test
@Ignore("This test simply regenerates the test file used for readBrokenNetworkParameters")
fun `regenerate broken network parameters`() {
// note: 6a6b6f256 is the sha that generates the file
val resource = "networkParams.<corda version>.<commit sha>"
val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
val networkParameters = NetworkParameters(
3, listOf(NotaryInfo(DUMMY_NOTARY, false)),1000, 1000, Instant.EPOCH, 1, emptyMap())
val sf = testDefaultFactory()
sf.register(net.corda.serialization.internal.amqp.custom.InstantSerializer(sf))
sf.register(net.corda.serialization.internal.amqp.custom.PublicKeySerializer)
val testOutput = TestSerializationOutput(true, sf)
val serialized = testOutput.serialize(networkParameters)
val keyPair = generateKeyPair()
val sig = keyPair.private.sign(serialized.bytes, keyPair.public)
val signed = SignedData(serialized, sig)
val signedAndSerialized = testOutput.serialize(signed)
File(URI("$localPath/$resource")).writeBytes(signedAndSerialized.bytes)
}
@Suppress("UNCHECKED_CAST")
@Test
fun getterSetterEvolver1() {
val resource = "EvolvabilityTests.getterSetterEvolver1"
val sf = testDefaultFactory()
//
// Class as it was serialised
//
// data class C(var c: Int, var d: Int, var b: Int, var e: Int, var a: Int) {
// // This will force the serialization engine to use getter / setter
// // instantiation for the object rather than construction
// @ConstructorForDeserialization
// @Suppress("UNUSED")
// constructor() : this(0, 0, 0, 0, 0)
// }
//
// File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(C(3,4,2,5,1)).bytes)
//
// Class as it exists now, c has been removed
//
data class C(var d: Int, var b: Int, var e: Int, var a: Int) {
// This will force the serialization engine to use getter / setter
// instantiation for the object rather than construction
@ConstructorForDeserialization
@Suppress("UNUSED")
constructor() : this(0, 0, 0, 0)
}
val url = EvolvabilityTests::class.java.getResource(resource)
val sc2 = url.readBytes()
val deserializedC = DeserializationInput(sf).deserialize(SerializedBytes<C>(sc2))
assertEquals(1, deserializedC.a)
assertEquals(2, deserializedC.b)
assertEquals(4, deserializedC.d)
assertEquals(5, deserializedC.e)
}
}

View File

@ -0,0 +1,57 @@
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
class FingerPrinterTesting : FingerPrinter {
private var index = 0
private val cache = mutableMapOf<Type, String>()
override fun fingerprint(type: Type): String {
return cache.computeIfAbsent(type) { index++.toString() }
}
override fun setOwner(factory: SerializerFactory) {
return
}
@Suppress("UNUSED")
fun changeFingerprint(type: Type) {
cache.computeIfAbsent(type) { "" }.apply { index++.toString() }
}
}
class FingerPrinterTestingTests {
companion object {
const val VERBOSE = true
}
@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))
}
@Test
fun worksAsReplacement() {
data class C(val a: Int, val b: Long)
val factory = SerializerFactory(
AllWhitelist,
ClassLoader.getSystemClassLoader(),
EvolutionSerializerGetterTesting(),
FingerPrinterTesting())
val blob = TestSerializationOutput(VERBOSE, factory).serializeAndReturnSchema(C(1, 2L))
assertEquals(1, blob.schema.types.size)
assertEquals("<descriptor name=\"net.corda:0\"/>", blob.schema.types[0].descriptor.toString())
}
}

View File

@ -0,0 +1,526 @@
package net.corda.serialization.internal.amqp
import net.corda.core.contracts.Attachment
import net.corda.core.contracts.AttachmentConstraint
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.TransactionState
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.serialization.SerializedBytes
import net.corda.serialization.internal.amqp.testutils.*
import net.corda.serialization.internal.AllWhitelist
import net.corda.testing.common.internal.ProjectStructure.projectRootDir
import net.corda.testing.core.TestIdentity
import org.junit.Test
import java.util.*
import kotlin.test.assertEquals
data class TestContractState(
override val participants: List<AbstractParty>
) : ContractState
class TestAttachmentConstraint : AttachmentConstraint {
override fun isSatisfiedBy(attachment: Attachment) = true
}
class GenericsTests {
companion object {
const val VERBOSE = true
@Suppress("UNUSED")
var localPath = projectRootDir.toUri().resolve(
"serialization/src/test/resources/net/corda/serialization/internal/amqp")
val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
}
private fun printSeparator() = if (VERBOSE) println("\n\n-------------------------------------------\n\n") else Unit
private fun <T : Any> BytesAndSchemas<T>.printSchema() = if (VERBOSE) println("${this.schema}\n") else Unit
private fun 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)
val factory = testDefaultFactoryNoEvolution()
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) }
}
}
@Test
fun doWeIgnoreMultipleParams() {
data class G1<out T>(val a: T)
data class G2<out T>(val a: T)
data class Wrapper<out T>(val a: Int, val b: G1<T>, val c: G2<T>)
val factory = testDefaultFactoryNoEvolution()
val factory2 = testDefaultFactoryNoEvolution()
val bytes = SerializationOutput(factory).serializeAndReturnSchema(Wrapper(1, G1("hi"), G2("poop"))).apply { printSchema() }
printSeparator()
DeserializationInput(factory2).deserialize(bytes.obj)
}
@Test
fun nestedSerializationOfGenerics() {
data class G<out T>(val a: T)
data class Wrapper<out T>(val a: Int, val b: G<T>)
val factory = testDefaultFactoryNoEvolution()
val altContextFactory = testDefaultFactoryNoEvolution()
val ser = SerializationOutput(factory)
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 {
assertEquals(1, a)
assertEquals("hi", b.a)
}
DeserializationInput(altContextFactory).deserialize(bytes2.obj).apply {
assertEquals(1, a)
assertEquals("hi", b.a)
}
}
@Test
fun nestedGenericsReferencesByteArrayViaSerializedBytes() {
data class G(val a: Int)
data class Wrapper<T : Any>(val a: Int, val b: SerializedBytes<T>)
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
val ser = SerializationOutput(factory)
val gBytes = ser.serialize(G(1))
val bytes2 = ser.serializeAndReturnSchema(Wrapper<G>(1, gBytes))
DeserializationInput(factory).deserialize(bytes2.obj).apply {
assertEquals(1, a)
assertEquals(1, DeserializationInput(factory).deserialize(b).a)
}
DeserializationInput(factory2).deserialize(bytes2.obj).apply {
assertEquals(1, a)
assertEquals(1, DeserializationInput(factory).deserialize(b).a)
}
}
@Test
fun nestedSerializationInMultipleContextsDoesntColideGenericTypes() {
data class InnerA(val a_a: Int)
data class InnerB(val a_b: Int)
data class InnerC(val a_c: String)
data class Container<T>(val b: T)
data class Wrapper<T : Any>(val c: Container<T>)
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
val factories = listOf(factory, SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()))
val ser = SerializationOutput(factory)
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()
}
}
}
@Test
fun nestedSerializationWhereGenericDoesntImpactFingerprint() {
data class Inner(val a: Int)
data class Container<T : Any>(val b: Inner)
data class Wrapper<T : Any>(val c: Container<T>)
val factorys = listOf(
SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()),
SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()))
val ser = SerializationOutput(factorys[0])
ser.serialize(Wrapper<Int>(Container(Inner(1)))).apply {
factorys.forEach {
assertEquals(1, DeserializationInput(it).deserialize(this).c.b.a)
}
}
ser.serialize(Wrapper<String>(Container(Inner(1)))).apply {
factorys.forEach {
assertEquals(1, DeserializationInput(it).deserialize(this).c.b.a)
}
}
}
data class ForceWildcard<out T>(val t: T)
private fun forceWildcardSerialize(
a: ForceWildcard<*>,
factory: SerializerFactory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())): SerializedBytes<*> {
val bytes = SerializationOutput(factory).serializeAndReturnSchema(a)
factory.serializersByDescriptor.printKeyToType()
bytes.printSchema()
return bytes.obj
}
@Suppress("UNCHECKED_CAST")
private fun forceWildcardDeserializeString(
bytes: SerializedBytes<*>,
factory: SerializerFactory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())) {
DeserializationInput(factory).deserialize(bytes as SerializedBytes<ForceWildcard<String>>)
}
@Suppress("UNCHECKED_CAST")
private fun forceWildcardDeserializeDouble(
bytes: SerializedBytes<*>,
factory: SerializerFactory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())) {
DeserializationInput(factory).deserialize(bytes as SerializedBytes<ForceWildcard<Double>>)
}
@Suppress("UNCHECKED_CAST")
private fun forceWildcardDeserialize(
bytes: SerializedBytes<*>,
factory: SerializerFactory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())) {
DeserializationInput(factory).deserialize(bytes as SerializedBytes<ForceWildcard<*>>)
}
@Test
fun forceWildcard() {
forceWildcardDeserializeString(forceWildcardSerialize(ForceWildcard("hello")))
forceWildcardDeserializeDouble(forceWildcardSerialize(ForceWildcard(3.0)))
}
@Test
fun forceWildcardSharedFactory() {
val f = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
forceWildcardDeserializeString(forceWildcardSerialize(ForceWildcard("hello"), f), f)
forceWildcardDeserializeDouble(forceWildcardSerialize(ForceWildcard(3.0), f), f)
}
@Test
fun forceWildcardDeserialize() {
forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard("hello")))
forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard(10)))
forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard(20.0)))
}
@Test
fun forceWildcardDeserializeSharedFactory() {
val f = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard("hello"), f), f)
forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard(10), f), f)
forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard(20.0), f), f)
}
@Test
fun loadGenericFromFile() {
val resource = "${javaClass.simpleName}.${testName()}"
val sf = testDefaultFactory()
// Uncomment to re-generate test files
// File(URI("$localPath/$resource")).writeBytes(forceWildcardSerialize(ForceWildcard("wibble")).bytes)
assertEquals("wibble",
DeserializationInput(sf).deserialize(SerializedBytes<ForceWildcard<*>>(
GenericsTests::class.java.getResource(resource).readBytes())).t)
}
data class StateAndString(val state: TransactionState<*>, val ref: String)
data class GenericStateAndString<out T: ContractState>(val state: TransactionState<T>, val ref: String)
//
// If this doesn't blow up all is fine
private fun fingerprintingDiffersStrip(state: Any) {
class cl : ClassLoader()
val m = ClassLoader::class.java.getDeclaredMethod("findLoadedClass", *arrayOf<Class<*>>(String::class.java))
m.isAccessible = true
val factory1 = testDefaultFactory()
factory1.register(net.corda.serialization.internal.amqp.custom.PublicKeySerializer)
val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(state)
// attempt at having a class loader without some of the derived non core types loaded and thus
// possibly altering how we serialise things
val altClassLoader = cl()
val factory2 = SerializerFactory(AllWhitelist, altClassLoader)
factory2.register(net.corda.serialization.internal.amqp.custom.PublicKeySerializer)
val ser2 = TestSerializationOutput(VERBOSE, factory2).serializeAndReturnSchema(state)
// now deserialise those objects
val factory3 = testDefaultFactory()
factory3.register(net.corda.serialization.internal.amqp.custom.PublicKeySerializer)
val des1 = DeserializationInput(factory3).deserializeAndReturnEnvelope(ser1.obj)
val factory4 = SerializerFactory(AllWhitelist, cl())
factory4.register(net.corda.serialization.internal.amqp.custom.PublicKeySerializer)
val des2 = DeserializationInput(factory4).deserializeAndReturnEnvelope(ser2.obj)
}
@Test
fun fingerprintingDiffers() {
val state = TransactionState<TestContractState> (
TestContractState(listOf(miniCorp.party)),
"wibble", miniCorp.party,
encumbrance = null,
constraint = TestAttachmentConstraint())
val sas = StateAndString(state, "wibble")
fingerprintingDiffersStrip(sas)
}
@Test
fun fingerprintingDiffersList() {
val state = TransactionState<TestContractState> (
TestContractState(listOf(miniCorp.party)),
"wibble", miniCorp.party,
encumbrance = null,
constraint = TestAttachmentConstraint())
val sas = StateAndString(state, "wibble")
fingerprintingDiffersStrip(Collections.singletonList(sas))
}
//
// Force object to be serialised as Example<T> and deserialized as Example<?>
//
@Test
fun fingerprintingDiffersListLoaded() {
//
// using this wrapper class we force the object to be serialised as
// net.corda.core.contracts.TransactionState<T>
//
data class TransactionStateWrapper<out T : ContractState> (val o: List<GenericStateAndString<T>>)
val state = TransactionState<TestContractState> (
TestContractState(listOf(miniCorp.party)),
"wibble", miniCorp.party,
encumbrance = null,
constraint = TestAttachmentConstraint())
val sas = GenericStateAndString(state, "wibble")
val factory1 = testDefaultFactoryNoEvolution()
val factory2 = testDefaultFactory()
factory1.register(net.corda.serialization.internal.amqp.custom.PublicKeySerializer)
factory2.register(net.corda.serialization.internal.amqp.custom.PublicKeySerializer)
val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(
TransactionStateWrapper(Collections.singletonList(sas)))
val des1 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser1.obj)
assertEquals(sas.ref, des1.obj.o.firstOrNull()?.ref ?: "WILL NOT MATCH")
}
@Test
fun nestedGenericsWithBound() {
open class BaseState(val a : Int)
class DState(a: Int) : BaseState(a)
data class LTransactionState<out T : BaseState> constructor(val data: T)
data class StateWrapper<out T : BaseState>(val state: LTransactionState<T>)
val factory1 = testDefaultFactoryNoEvolution()
val state = LTransactionState(DState(1020304))
val stateAndString = StateWrapper(state)
val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(stateAndString)
//val factory2 = testDefaultFactoryNoEvolution()
val factory2 = testDefaultFactory()
val des1 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser1.obj)
assertEquals(state.data.a, des1.obj.state.data.a)
}
@Test
fun nestedMultiGenericsWithBound() {
open class BaseState(val a : Int)
class DState(a: Int) : BaseState(a)
class EState(a: Int, val msg: String) : BaseState(a)
data class LTransactionState<out T1 : BaseState, out T2: BaseState> (val data: T1, val context: T2)
data class StateWrapper<out T1 : BaseState, out T2: BaseState>(val state: LTransactionState<T1, T2>)
val factory1 = testDefaultFactoryNoEvolution()
val state = LTransactionState(DState(1020304), EState(5060708, msg = "thigns"))
val stateAndString = StateWrapper(state)
val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(stateAndString)
//val factory2 = testDefaultFactoryNoEvolution()
val factory2 = testDefaultFactory()
val des1 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser1.obj)
assertEquals(state.data.a, des1.obj.state.data.a)
assertEquals(state.context.a, des1.obj.state.context.a)
}
@Test
fun nestedMultiGenericsNoBound() {
open class BaseState(val a : Int)
class DState(a: Int) : BaseState(a)
class EState(a: Int, val msg: String) : BaseState(a)
data class LTransactionState<out T1, out T2> (val data: T1, val context: T2)
data class StateWrapper<out T1, out T2>(val state: LTransactionState<T1, T2>)
val factory1 = testDefaultFactoryNoEvolution()
val state = LTransactionState(DState(1020304), EState(5060708, msg = "things"))
val stateAndString = StateWrapper(state)
val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(stateAndString)
//val factory2 = testDefaultFactoryNoEvolution()
val factory2 = testDefaultFactory()
val des1 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser1.obj)
assertEquals(state.data.a, des1.obj.state.data.a)
assertEquals(state.context.a, des1.obj.state.context.a)
assertEquals(state.context.msg, des1.obj.state.context.msg)
}
@Test
fun baseClassInheritedButNotOverriden() {
val factory1 = testDefaultFactoryNoEvolution()
val factory2 = testDefaultFactory()
open class BaseState<T1, T2>(open val a : T1, open val b: T2)
class DState<T1, T2>(a: T1, b: T2) : BaseState<T1, T2>(a, b)
val state = DState(100, "hello")
val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(state)
val des1 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser1.obj)
assertEquals(state.a, des1.obj.a)
assertEquals(state.b, des1.obj.b)
class DState2<T1, T2, T3>(a: T1, b: T2, val c: T3) : BaseState<T1, T2>(a, b)
val state2 = DState2(100, "hello", 100L)
val ser2 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(state2)
val des2 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser2.obj)
assertEquals(state2.a, des2.obj.a)
assertEquals(state2.b, des2.obj.b)
assertEquals(state2.c, des2.obj.c)
}
@Test
fun baseClassInheritedButNotOverridenBounded() {
val factory1 = testDefaultFactoryNoEvolution()
val factory2 = testDefaultFactory()
open class Bound(val a: Int)
open class BaseState<out T1 : Bound>(open val a: T1)
class DState<out T1: Bound>(a: T1) : BaseState<T1>(a)
val state = DState(Bound(100))
val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(state)
val des1 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser1.obj)
assertEquals(state.a.a, des1.obj.a.a)
}
@Test
fun nestedMultiGenericsAtBottomWithBound() {
open class BaseState<T1, T2>(val a : T1, val b: T2)
class DState<T1, T2>(a: T1, b: T2) : BaseState<T1, T2>(a, b)
class EState<T1, T2>(a: T1, b: T2, val c: Long) : BaseState<T1, T2>(a, b)
data class LTransactionState<T1, T2, T3 : BaseState<T1, T2>, out T4: BaseState<T1, T2>> (val data: T3, val context: T4)
data class StateWrapper<T1, T2, T3 : BaseState<T1, T2>, out T4: BaseState<T1, T2>>(val state: LTransactionState<T1, T2, T3, T4>)
val factory1 = testDefaultFactoryNoEvolution()
val state = LTransactionState(DState(1020304, "Hello"), EState(5060708, "thins", 100L))
val stateAndString = StateWrapper(state)
val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(stateAndString)
//val factory2 = testDefaultFactoryNoEvolution()
val factory2 = testDefaultFactory()
val des1 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser1.obj)
assertEquals(state.data.a, des1.obj.state.data.a)
assertEquals(state.context.a, des1.obj.state.context.a)
}
fun implemntsGeneric() {
open class B<out T>(open val a: T)
class D(override val a: String) : B<String>(a)
val factory = testDefaultFactoryNoEvolution()
val bytes = SerializationOutput(factory).serialize(D("Test"))
DeserializationInput(factory).deserialize(bytes).apply { assertEquals("Test", this.a) }
}
interface implementsGenericInterfaceI<out T> {
val a: T
}
@Test
fun implemntsGenericInterface() {
class D(override val a: String) : implementsGenericInterfaceI<String>
val factory = testDefaultFactoryNoEvolution()
val bytes = SerializationOutput(factory).serialize(D("Test"))
DeserializationInput(factory).deserialize(bytes).apply { assertEquals("Test", this.a) }
}
}

View File

@ -0,0 +1,68 @@
package net.corda.serialization.internal.amqp
import net.corda.core.serialization.SerializationContext
import net.corda.serialization.internal.CordaSerializationMagic
import net.corda.serialization.internal.AMQP_P2P_CONTEXT
import org.apache.qpid.proton.codec.Data
import org.assertj.core.api.Assertions
import org.junit.Test
import java.lang.reflect.Type
import java.security.PublicKey
class OverridePKSerializerTest {
class SerializerTestException(message: String) : Exception(message)
class TestPublicKeySerializer : CustomSerializer.Implements<PublicKey>(PublicKey::class.java) {
override fun writeDescribedObject(obj: PublicKey, data: Data, type: Type, output: SerializationOutput,
context: SerializationContext
) {
throw SerializerTestException("Custom write call")
}
override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput,
context: SerializationContext
): PublicKey {
throw SerializerTestException("Custom read call")
}
override val schemaForDocumentation: Schema
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
}
class AMQPTestSerializationScheme : AbstractAMQPSerializationScheme(emptyList()) {
override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase) = true
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override val publicKeySerializer = TestPublicKeySerializer()
}
class TestPublicKey : PublicKey {
override fun getAlgorithm(): String {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getEncoded(): ByteArray {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getFormat(): String {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
@Test
fun `test publicKeySerializer is overridden`() {
val scheme = AMQPTestSerializationScheme()
val key = TestPublicKey()
Assertions
.assertThatThrownBy { scheme.serialize(key, AMQP_P2P_CONTEXT) }
.hasMessageMatching("Custom write call")
}
}

View File

@ -0,0 +1,232 @@
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 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()
companion object {
val fields : Map<String, java.lang.reflect.Field> = mapOf (
"serializersByDesc" to SerializerFactory::class.java.getDeclaredField("serializersByDescriptor")).apply {
this.values.forEach {
it.isAccessible = true
}
}
}
@Test
fun testWithOnePrivateProperty() {
data class C(private val b: String)
val c1 = C("Pants are comfortable sometimes")
val c2 = DeserializationInput(factory).deserialize(SerializationOutput(factory).serialize(c1))
assertEquals(c1, c2)
}
@Test
fun testWithOnePrivatePropertyBoolean() {
data class C(private val b: Boolean)
C(false).apply {
assertEquals(this, DeserializationInput(factory).deserialize(SerializationOutput(factory).serialize(this)))
}
}
@Test
fun testWithOnePrivatePropertyNullableNotNull() {
data class C(private val b: String?)
val c1 = C("Pants are comfortable sometimes")
val c2 = DeserializationInput(factory).deserialize(SerializationOutput(factory).serialize(c1))
assertEquals(c1, c2)
}
@Test
fun testWithOnePrivatePropertyNullableNull() {
data class C(private val b: String?)
val c1 = C(null)
val c2 = DeserializationInput(factory).deserialize(SerializationOutput(factory).serialize(c1))
assertEquals(c1, c2)
}
@Test
fun testWithOnePublicOnePrivateProperty() {
data class C(val a: Int, private val b: Int)
val c1 = C(1, 2)
val c2 = DeserializationInput(factory).deserialize(SerializationOutput(factory).serialize(c1))
assertEquals(c1, c2)
}
@Test
fun testWithInheritance() {
open class B(val a: String, protected val b: String)
class D (a: String, b: String) : B (a, b) {
override fun equals(other: Any?): Boolean = when (other) {
is D -> other.a == a && other.b == b
else -> false
}
override fun hashCode(): Int = Objects.hash(a, b)
}
val d1 = D("clump", "lump")
val d2 = DeserializationInput(factory).deserialize(SerializationOutput(factory).serialize(d1))
assertEquals(d1, d2)
}
@Test
fun testMultiArgSetter() {
@Suppress("UNUSED")
data class C(private var a: Int, var b: Int) {
// This will force the serialization engine to use getter / setter
// instantiation for the object rather than construction
@ConstructorForDeserialization
constructor() : this(0, 0)
fun setA(a: Int, b: Int) { this.a = a }
fun getA() = a
}
val c1 = C(33, 44)
val c2 = DeserializationInput(factory).deserialize(SerializationOutput(factory).serialize(c1))
assertEquals(0, c2.getA())
assertEquals(44, c2.b)
}
@Test
fun testBadTypeArgSetter() {
@Suppress("UNUSED")
data class C(private var a: Int, val b: Int) {
@ConstructorForDeserialization
constructor() : this(0, 0)
fun setA(a: String) { this.a = a.toInt() }
fun getA() = a
}
val c1 = C(33, 44)
Assertions.assertThatThrownBy {
SerializationOutput(factory).serialize(c1)
}.isInstanceOf(NotSerializableException::class.java).hasMessageContaining(
"Defined setter for parameter a takes parameter of type class java.lang.String " +
"yet underlying type is int ")
}
@Test
fun testWithOnePublicOnePrivateProperty2() {
data class C(val a: Int, private val b: Int)
val c1 = C(1, 2)
val schemaAndBlob = SerializationOutput(factory).serializeAndReturnSchema(c1)
assertEquals(1, schemaAndBlob.schema.types.size)
@Suppress("UNCHECKED_CAST")
val serializersByDescriptor = fields["serializersByDesc"]?.get(factory) as ConcurrentHashMap<Any, AMQPSerializer<Any>>
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.getter }
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)
}
}
@Test
fun testGetterMakesAPublicReader() {
data class C(val a: Int, private val b: Int) {
@Suppress("UNUSED")
fun getB() = b
}
val c1 = C(1, 2)
val schemaAndBlob = SerializationOutput(factory).serializeAndReturnSchema(c1)
assertEquals(1, schemaAndBlob.schema.types.size)
@Suppress("UNCHECKED_CAST")
val serializersByDescriptor = fields["serializersByDesc"]?.get(factory) as ConcurrentHashMap<Any, AMQPSerializer<Any>>
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.getter }
assertEquals(2, propertySerializers.size)
// 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)
}
}
@Suppress("UNCHECKED_CAST")
@Test
fun testNested() {
data class Inner(private val a: Int)
data class Outer(private val i: Inner)
val c1 = Outer(Inner(1010101))
val output = SerializationOutput(factory).serializeAndReturnSchema(c1)
println (output.schema)
val serializersByDescriptor = fields["serializersByDesc"]!!.get(factory) as ConcurrentHashMap<Any, AMQPSerializer<Any>>
// Inner and Outer
assertEquals(2, serializersByDescriptor.size)
val c2 = DeserializationInput(factory).deserialize(output.obj)
assertEquals(c1, c2)
}
//
// Reproduces CORDA-1134
//
@Suppress("UNCHECKED_CAST")
@Test
fun allCapsProprtyNotPrivate() {
data class C (val CCC: String)
val output = SerializationOutput(factory).serializeAndReturnSchema(C("this is nice"))
val serializersByDescriptor = fields["serializersByDesc"]!!.get(factory) as ConcurrentHashMap<Any, AMQPSerializer<Any>>
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.getter }
// 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)
}
}
}

View File

@ -0,0 +1,66 @@
package net.corda.serialization.internal.amqp
import net.corda.core.serialization.ConstructorForDeserialization
import net.corda.serialization.internal.amqp.testutils.deserialize
import net.corda.serialization.internal.amqp.testutils.serialize
import net.corda.serialization.internal.amqp.testutils.testDefaultFactoryNoEvolution
import org.assertj.core.api.Assertions
import org.junit.Test
class RoundTripTests {
@Test
fun mutableBecomesImmutable() {
data class C(val l: MutableList<String>)
val factory = testDefaultFactoryNoEvolution()
val bytes = SerializationOutput(factory).serialize(C(mutableListOf("a", "b", "c")))
val newC = DeserializationInput(factory).deserialize(bytes)
Assertions.assertThatThrownBy {
newC.l.add("d")
}.isInstanceOf(UnsupportedOperationException::class.java)
}
@Test
fun mutableStillMutable() {
class C {
val l: MutableList<String>
@Suppress("Unused")
constructor (l: MutableList<String>) {
this.l = l.toMutableList()
}
}
val factory = testDefaultFactoryNoEvolution()
val bytes = SerializationOutput(factory).serialize(C(mutableListOf("a", "b", "c")))
val newC = DeserializationInput(factory).deserialize(bytes)
newC.l.add("d")
}
@Test
fun mutableStillMutable2() {
data class C(val l: MutableList<String>) {
@ConstructorForDeserialization
@Suppress("Unused")
constructor (l: Collection<String>) : this(l.toMutableList())
}
val factory = testDefaultFactoryNoEvolution()
val bytes = SerializationOutput(factory).serialize(C(mutableListOf("a", "b", "c")))
val newC = DeserializationInput(factory).deserialize(bytes)
newC.l.add("d")
}
@Test
fun mutableBecomesImmutable4() {
data class C(val l: List<String>)
val factory = testDefaultFactoryNoEvolution()
val bytes = SerializationOutput(factory).serialize(C(listOf("a", "b", "c")))
val newC = DeserializationInput(factory).deserialize(bytes)
val newC2 = newC.copy(l = (newC.l + "d"))
}
}

View File

@ -0,0 +1,130 @@
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 org.junit.Test
import java.util.concurrent.ConcurrentHashMap
import kotlin.test.assertEquals
import org.apache.qpid.proton.amqp.Symbol
import java.lang.reflect.Method
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
class SerializationPropertyOrdering {
companion object {
val VERBOSE get() = false
val sf = testDefaultFactoryNoEvolution()
}
// Force object references to be ued to ensure we go through that code path
// this test shows (not now it's fixed) a bug whereby deserializing objects
// would break where refferenced objects were accessed before they'd been
// processed thanks to the way the blob was deserialized
@Test
fun refferenceOrdering() {
data class Reffed(val c: String, val b: String, val a: String)
data class User(val b: List<Reffed>, val a: List<Reffed>)
val r1 = Reffed("do not", "or", "do")
val r2 = Reffed("do not", "or", "do")
val l = listOf(r1, r2, r1, r2, r1, r2)
val u = User(l,l)
val output = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(u)
val input = DeserializationInput(sf).deserialize(output.obj)
}
@Test
fun randomOrder() {
data class C(val c: Int, val d: Int, val b: Int, val e: Int, val a: Int)
val c = C(3,4,2,5,1)
val output = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(c)
// the schema should reflect the serialized order of properties, not the
// construction order
assertEquals(1, output.schema.types.size)
output.schema.types.firstOrNull()?.apply {
assertEquals(5, (this as CompositeType).fields.size)
assertEquals("a", this.fields[0].name)
assertEquals("b", this.fields[1].name)
assertEquals("c", this.fields[2].name)
assertEquals("d", this.fields[3].name)
assertEquals("e", this.fields[4].name)
}
// and deserializing it should construct the object as it was and not in the order prescribed
// by the serialized form
val input = DeserializationInput(sf).deserialize(output.obj)
assertEquals(1, input.a)
assertEquals(2, input.b)
assertEquals(3, input.c)
assertEquals(4, input.d)
assertEquals(5, input.e)
}
@Suppress("UNCHECKED_CAST")
@Test
fun randomOrderSetter() {
data class C(var c: Int, var d: Int, var b: Int, var e: Int, var a: Int) {
// This will force the serialization engine to use getter / setter
// instantiation for the object rather than construction
@ConstructorForDeserialization
@Suppress("UNUSED")
constructor() : this(0, 0, 0, 0, 0)
}
val c = C()
c.a = 100
c.b = 200
c.c = 300
c.d = 400
c.e = 500
val output = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(c)
// the schema should reflect the serialized order of properties, not the
// construction order
assertEquals(1, output.schema.types.size)
output.schema.types.firstOrNull()?.apply {
assertEquals(5, (this as CompositeType).fields.size)
assertEquals("a", this.fields[0].name)
assertEquals("b", this.fields[1].name)
assertEquals("c", this.fields[2].name)
assertEquals("d", this.fields[3].name)
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 (
"serializersByDesc" to SerializerFactory::class.java.getDeclaredField("serializersByDescriptor"),
"setter" to PropertyAccessorGetterSetter::class.java.getDeclaredField("setter")).apply {
this.values.forEach {
it.isAccessible = true
}
}
val serializersByDescriptor = fields["serializersByDesc"]!!.get(sf) as ConcurrentHashMap<Any, AMQPSerializer<Any>>
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)
assertEquals(300, input.c)
assertEquals(400, input.d)
assertEquals(500, input.e)
}
}

View File

@ -0,0 +1,102 @@
package net.corda.serialization.internal.amqp
import net.corda.core.serialization.*
import net.corda.core.utilities.ByteSequence
import net.corda.serialization.internal.*
import net.corda.serialization.internal.kryo.BuiltInExceptionsWhitelist
import net.corda.serialization.internal.kryo.GlobalTransientClassWhiteList
import org.junit.Test
import java.util.concurrent.ConcurrentHashMap
import kotlin.test.assertEquals
// Make sure all serialization calls in this test don't get stomped on by anything else
val TESTING_CONTEXT = SerializationContextImpl(amqpMagic,
SerializationDefaults.javaClass.classLoader,
GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()),
emptyMap(),
true,
SerializationContext.UseCase.Testing,
null)
// Test factory that lets us count the number of serializer registration attempts
class TestSerializerFactory(
wl: ClassWhitelist,
cl: ClassLoader
) : SerializerFactory(wl, cl) {
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(), ConcurrentHashMap(), 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 = 40
assertEquals(0, testFactory.registerCount)
c.serialize(testSerializationFactory, TESTING_CONTEXT)
assertEquals(expectedCustomSerializerCount, testFactory.registerCount)
c.serialize(testSerializationFactory, TESTING_CONTEXT)
assertEquals(expectedCustomSerializerCount, testFactory.registerCount)
}
}

View File

@ -0,0 +1,38 @@
package net.corda.serialization.internal.amqp
import net.corda.serialization.internal.amqp.testutils.serializeAndReturnSchema
import net.corda.serialization.internal.amqp.testutils.testDefaultFactoryNoEvolution
import net.corda.serialization.internal.amqp.testutils.testName
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
class SerializeAndReturnSchemaTest {
// the 'this' reference means we can't just move this to the common test utils
@Suppress("NOTHING_TO_INLINE")
private inline fun classTestName(clazz: String) = "${this.javaClass.name}\$${testName()}\$$clazz"
val factory = testDefaultFactoryNoEvolution()
// just a simple test to verify the internal test extension for serialize does
// indeed give us the correct schema back. This is more useful in support of other
// tests rather than by itself but for those to be reliable this also needs
// testing
@Test
fun getSchema() {
data class C(val a: Int, val b: Int)
val a = 1
val b = 2
val sc = SerializationOutput(factory).serializeAndReturnSchema(C(a, b))
assertEquals(1, sc.schema.types.size)
assertEquals(classTestName("C"), sc.schema.types.first().name)
assertTrue(sc.schema.types.first() is CompositeType)
assertEquals(2, (sc.schema.types.first() as CompositeType).fields.size)
assertNotNull((sc.schema.types.first() as CompositeType).fields.find { it.name == "a" })
assertNotNull((sc.schema.types.first() as CompositeType).fields.find { it.name == "b" })
}
}

View File

@ -0,0 +1,139 @@
package net.corda.serialization.internal.amqp
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.SerializedBytes
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.Test
import java.io.NotSerializableException
import java.lang.reflect.Type
import java.util.concurrent.ConcurrentHashMap
import kotlin.test.assertEquals
class InStatic : Exception("Help!, help!, I'm being repressed")
class C {
companion object {
init {
throw InStatic()
}
}
}
// To re-setup the resource file for the tests
// * deserializeTest
// * deserializeTest2
// comment out the companion object from here, comment out the test code and uncomment
// the generation code, then re-run the test and copy the file shown in the output print
// to the resource directory
class C2(var b: Int) {
/*
companion object {
init {
throw InStatic()
}
}
*/
}
class StaticInitialisationOfSerializedObjectTest {
@Test(expected = java.lang.ExceptionInInitializerError::class)
fun itBlowsUp() {
C()
}
@Test
fun kotlinObjectWithCompanionObject() {
data class D(val c: C)
val sf = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
val typeMap = sf::class.java.getDeclaredField("serializersByType")
typeMap.isAccessible = true
@Suppress("UNCHECKED_CAST")
val serialisersByType = typeMap.get(sf) as ConcurrentHashMap<Type, AMQPSerializer<Any>>
// pre building a serializer, we shouldn't have anything registered
assertEquals(0, serialisersByType.size)
// 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)
// post creation of the serializer we should have one element in the map, this
// proves we didn't statically construct an instance of C when building the serializer
assertEquals(1, serialisersByType.size)
}
@Test
fun deserializeTest() {
data class D(val c: C2)
val url = EvolvabilityTests::class.java.getResource("StaticInitialisationOfSerializedObjectTest.deserializeTest")
// Original version of the class for the serialised version of this class
//
//val sf1 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
//val sc = SerializationOutput(sf1).serialize(D(C2(20)))
//f.writeBytes(sc.bytes)
//println (path)
class WL : ClassWhitelist {
override fun hasListed(type: Class<*>) =
type.name == "net.corda.serialization.internal.amqp" +
".StaticInitialisationOfSerializedObjectTest\$deserializeTest\$D"
}
val sf2 = SerializerFactory(WL(), ClassLoader.getSystemClassLoader())
val bytes = url.readBytes()
assertThatThrownBy {
DeserializationInput(sf2).deserialize(SerializedBytes<D>(bytes))
}.isInstanceOf(NotSerializableException::class.java)
}
// Version of a serializer factory that will allow the class carpenter living on the
// factory to have a different whitelist applied to it than the factory
class TestSerializerFactory(wl1: ClassWhitelist, wl2: ClassWhitelist) :
SerializerFactory(wl1, ClassCarpenterImpl(ClassLoader.getSystemClassLoader(), wl2))
// This time have the serialization factory and the carpenter use different whitelists
@Test
fun deserializeTest2() {
data class D(val c: C2)
val url = EvolvabilityTests::class.java.getResource("StaticInitialisationOfSerializedObjectTest.deserializeTest2")
// Original version of the class for the serialised version of this class
//
//val sf1 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
//val sc = SerializationOutput(sf1).serialize(D(C2(20)))
//f.writeBytes(sc.bytes)
//println (path)
// whitelist to be used by the serialisation factory
class WL1 : ClassWhitelist {
override fun hasListed(type: Class<*>) =
type.name == "net.corda.serialization.internal.amqp" +
".StaticInitialisationOfSerializedObjectTest\$deserializeTest\$D"
}
// whitelist to be used by the carpenter
class WL2 : ClassWhitelist {
override fun hasListed(type: Class<*>) = true
}
val sf2 = TestSerializerFactory(WL1(), WL2())
val bytes = url.readBytes()
// Deserializing should throw because C is not on the whitelist NOT because
// we ever went anywhere near statically constructing it prior to not actually
// creating an instance of it
assertThatThrownBy {
DeserializationInput(sf2).deserialize(SerializedBytes<D>(bytes))
}.isInstanceOf(NotSerializableException::class.java)
}
}

View File

@ -0,0 +1,67 @@
package net.corda.serialization.internal.amqp.testutils
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializedBytes
import org.apache.qpid.proton.codec.Data
import net.corda.serialization.internal.AllWhitelist
import net.corda.serialization.internal.EmptyWhitelist
import net.corda.serialization.internal.amqp.*
import java.io.NotSerializableException
fun testDefaultFactory() = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
fun testDefaultFactoryNoEvolution() = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader(),
EvolutionSerializerGetterTesting())
fun testDefaultFactoryWithWhitelist() = SerializerFactory(EmptyWhitelist, ClassLoader.getSystemClassLoader())
class TestSerializationOutput(
private val verbose: Boolean,
serializerFactory: SerializerFactory = testDefaultFactory())
: SerializationOutput(serializerFactory) {
override fun writeSchema(schema: Schema, data: Data) {
if (verbose) println(schema)
super.writeSchema(schema, data)
}
override fun writeTransformSchema(transformsSchema: TransformsSchema, data: Data) {
if(verbose) {
println ("Writing Transform Schema")
println (transformsSchema)
}
super.writeTransformSchema(transformsSchema, data)
}
}
fun testName(): String = Thread.currentThread().stackTrace[2].methodName
@Throws(NotSerializableException::class)
inline fun <reified T : Any> DeserializationInput.deserializeAndReturnEnvelope(
bytes: SerializedBytes<T>,
context: SerializationContext? = null
) : ObjectAndEnvelope<T> {
return deserializeAndReturnEnvelope(bytes, T::class.java,
context ?: testSerializationContext)
}
@Throws(NotSerializableException::class)
inline fun <reified T : Any> DeserializationInput.deserialize(
bytes: SerializedBytes<T>,
context: SerializationContext? = null
) : T = deserialize(bytes, T::class.java, context ?: testSerializationContext)
@Throws(NotSerializableException::class)
fun <T : Any> SerializationOutput.serializeAndReturnSchema(
obj: T, context: SerializationContext? = null
): BytesAndSchemas<T> = serializeAndReturnSchema(obj, context ?: testSerializationContext)
@Throws(NotSerializableException::class)
fun <T : Any> SerializationOutput.serialize(obj: T): SerializedBytes<T> {
try {
return _serialize(obj, testSerializationContext)
} finally {
andFinally()
}
}

View File

@ -0,0 +1,17 @@
package net.corda.serialization.internal.amqp.testutils
import net.corda.core.serialization.SerializationContext
import net.corda.serialization.internal.AllWhitelist
import net.corda.serialization.internal.SerializationContextImpl
import net.corda.serialization.internal.amqp.amqpMagic
val serializationProperties: MutableMap<Any, Any> = mutableMapOf()
val testSerializationContext = SerializationContextImpl(
preferredSerializationVersion = amqpMagic,
deserializationClassLoader = ClassLoader.getSystemClassLoader(),
whitelist = AllWhitelist,
properties = serializationProperties,
objectReferencesEnabled = false,
useCase = SerializationContext.UseCase.Testing,
encoding = null)

View File

@ -0,0 +1,100 @@
package net.corda.serialization.internal.carpenter
import com.google.common.reflect.TypeToken
import net.corda.serialization.internal.AllWhitelist
import net.corda.serialization.internal.amqp.*
import net.corda.serialization.internal.amqp.testutils.TestSerializationOutput
import net.corda.serialization.internal.amqp.testutils.deserialize
import net.corda.serialization.internal.amqp.testutils.serialize
import net.corda.serialization.internal.amqp.testutils.testDefaultFactory
import org.assertj.core.api.Assertions
import org.junit.Test
import java.io.NotSerializableException
import java.lang.reflect.Type
import kotlin.reflect.jvm.jvmName
import kotlin.test.*
// Simple way to ensure we end up trying to carpent a class, "remove" it from the class loader (if only
// actually doing that was simple)
class TestClassLoader(private var exclude: List<String>) : ClassLoader() {
override fun loadClass(name: String, resolve: Boolean): Class<*> {
if (name in exclude) {
throw ClassNotFoundException("Pretending we can't find class $name")
}
return super.loadClass(name, resolve)
}
}
interface TestInterface {
fun runThing(): Int
}
// Create a custom serialization factory where we need to be able to both specify a carpenter
// but also have the class loader used by the carpenter be substantially different from the
// one used by the factory so as to ensure we can control their behaviour independently.
class TestFactory(classCarpenter: ClassCarpenter)
: SerializerFactory(classCarpenter.whitelist, classCarpenter)
class CarpenterExceptionTests {
companion object {
val VERBOSE: Boolean get() = false
}
@Test
fun checkClassComparison() {
class CLA : ClassLoader() {
override fun loadClass(name: String, resolve: Boolean): Class<*> {
println("CLA::loadClass $name")
return super.loadClass(name, resolve)
}
}
class CLB : ClassLoader() {
override fun loadClass(name: String, resolve: Boolean): Class<*> {
println("CLB::loadClass $name")
return super.loadClass(name, resolve)
}
}
data class A(val a: Int, val b: Int)
val a3 = ClassLoader.getSystemClassLoader().loadClass(A::class.java.name)
val a1 = CLA().loadClass(A::class.java.name)
val a2 = CLB().loadClass(A::class.java.name)
assertTrue(TypeToken.of(a1).isSubtypeOf(a2))
assertTrue(a1 is Type)
assertTrue(a2 is Type)
assertTrue(a3 is Type)
assertEquals(a1, a2)
assertEquals(a1, a3)
assertEquals(a2, a3)
}
@Test
fun carpenterExceptionRethrownAsNotSerializableException() {
data class C2(val i: Int) : TestInterface {
override fun runThing() = 1
}
data class C1(val i: Int, val c: C2)
// We need two factories to ensure when we deserialize the blob we don't just use the serializer
// we built to serialise things
val ser = TestSerializationOutput(VERBOSE, testDefaultFactory()).serialize(C1(1, C2(2)))
// Our second factory is "special"
// The class loader given to the factory rejects the outer class, this will trigger an attempt to
// carpent that class up. However, when looking at the fields specified as properties of that class
// we set the class loader of the ClassCarpenter to reject one of them, resulting in a CarpentryError
// which we then want the code to wrap in a NotSerializeableException
val cc = ClassCarpenterImpl(TestClassLoader(listOf(C2::class.jvmName)), AllWhitelist)
val factory = TestFactory(cc)
Assertions.assertThatThrownBy {
DeserializationInput(factory).deserialize(ser)
}.isInstanceOf(NotSerializableException::class.java)
.hasMessageContaining(C2::class.java.name)
}
}

View File

@ -0,0 +1,494 @@
package net.corda.serialization.internal.carpenter
import net.corda.core.internal.uncheckedCast
import net.corda.serialization.internal.AllWhitelist
import org.junit.Test
import java.beans.Introspector
import java.lang.reflect.Field
import java.lang.reflect.Method
import javax.annotation.Nonnull
import javax.annotation.Nullable
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
class ClassCarpenterTest {
interface DummyInterface {
val a: String
val b: Int
}
private val cc = ClassCarpenterImpl(whitelist = AllWhitelist)
// We have to ignore synthetic fields even though ClassCarpenter doesn't create any because the JaCoCo
// coverage framework auto-magically injects one method and one field into every class loaded into the JVM.
private val Class<*>.nonSyntheticFields: List<Field> get() = declaredFields.filterNot { it.isSynthetic }
private val Class<*>.nonSyntheticMethods: List<Method> get() = declaredMethods.filterNot { it.isSynthetic }
@Test
fun empty() {
val clazz = cc.build(ClassSchema("gen.EmptyClass", emptyMap(), null))
assertEquals(0, clazz.nonSyntheticFields.size)
assertEquals(2, clazz.nonSyntheticMethods.size) // get, toString
assertEquals(0, clazz.declaredConstructors[0].parameterCount)
clazz.newInstance() // just test there's no exception.
}
@Test
fun prims() {
val clazz = cc.build(ClassSchema(
"gen.Prims",
mapOf(
"anIntField" to Int::class.javaPrimitiveType!!,
"aLongField" to Long::class.javaPrimitiveType!!,
"someCharField" to Char::class.javaPrimitiveType!!,
"aShortField" to Short::class.javaPrimitiveType!!,
"doubleTrouble" to Double::class.javaPrimitiveType!!,
"floatMyBoat" to Float::class.javaPrimitiveType!!,
"byteMe" to Byte::class.javaPrimitiveType!!,
"booleanField" to Boolean::class.javaPrimitiveType!!).mapValues {
NonNullableField(it.value)
}))
assertEquals(8, clazz.nonSyntheticFields.size)
assertEquals(10, clazz.nonSyntheticMethods.size)
assertEquals(8, clazz.declaredConstructors[0].parameterCount)
val i = clazz.constructors[0].newInstance(1, 2L, 'c', 4.toShort(), 1.23, 4.56F, 127.toByte(), true)
assertEquals(1, clazz.getMethod("getAnIntField").invoke(i))
assertEquals(2L, clazz.getMethod("getALongField").invoke(i))
assertEquals('c', clazz.getMethod("getSomeCharField").invoke(i))
assertEquals(4.toShort(), clazz.getMethod("getAShortField").invoke(i))
assertEquals(1.23, clazz.getMethod("getDoubleTrouble").invoke(i))
assertEquals(4.56F, clazz.getMethod("getFloatMyBoat").invoke(i))
assertEquals(127.toByte(), clazz.getMethod("getByteMe").invoke(i))
assertEquals(true, clazz.getMethod("getBooleanField").invoke(i))
val sfa = i as SimpleFieldAccess
assertEquals(1, sfa["anIntField"])
assertEquals(2L, sfa["aLongField"])
assertEquals('c', sfa["someCharField"])
assertEquals(4.toShort(), sfa["aShortField"])
assertEquals(1.23, sfa["doubleTrouble"])
assertEquals(4.56F, sfa["floatMyBoat"])
assertEquals(127.toByte(), sfa["byteMe"])
assertEquals(true, sfa["booleanField"])
}
private fun genPerson(): Pair<Class<*>, Any> {
val clazz = cc.build(ClassSchema("gen.Person", mapOf(
"age" to Int::class.javaPrimitiveType!!,
"name" to String::class.java
).mapValues { NonNullableField(it.value) }))
val i = clazz.constructors[0].newInstance(32, "Mike")
return Pair(clazz, i)
}
@Test
fun objs() {
val (clazz, i) = genPerson()
assertEquals("Mike", clazz.getMethod("getName").invoke(i))
assertEquals("Mike", (i as SimpleFieldAccess)["name"])
}
@Test
fun `generated toString`() {
val (_, i) = genPerson()
assertEquals("Person{age=32, name=Mike}", i.toString())
}
@Test(expected = DuplicateNameException::class)
fun duplicates() {
cc.build(ClassSchema("gen.EmptyClass", emptyMap(), null))
cc.build(ClassSchema("gen.EmptyClass", emptyMap(), null))
}
@Test
fun `can refer to each other`() {
val (clazz1, i) = genPerson()
val clazz2 = cc.build(ClassSchema("gen.Referee", mapOf(
"ref" to NonNullableField(clazz1)
)))
val i2 = clazz2.constructors[0].newInstance(i)
assertEquals(i, (i2 as SimpleFieldAccess)["ref"])
}
@Test
fun superclasses() {
val schema1 = ClassSchema(
"gen.A",
mapOf("a" to NonNullableField(String::class.java)))
val schema2 = ClassSchema(
"gen.B",
mapOf("b" to NonNullableField(String::class.java)),
schema1)
val clazz = cc.build(schema2)
val i = clazz.constructors[0].newInstance("xa", "xb") as SimpleFieldAccess
assertEquals("xa", i["a"])
assertEquals("xb", i["b"])
assertEquals("B{a=xa, b=xb}", i.toString())
}
@Test
fun interfaces() {
val schema1 = ClassSchema(
"gen.A",
mapOf("a" to NonNullableField(String::class.java)))
val schema2 = ClassSchema("gen.B",
mapOf("b" to NonNullableField(Int::class.java)),
schema1,
interfaces = listOf(DummyInterface::class.java))
val clazz = cc.build(schema2)
val i = clazz.constructors[0].newInstance("xa", 1) as DummyInterface
assertEquals("xa", i.a)
assertEquals(1, i.b)
}
@Test(expected = InterfaceMismatchException::class)
fun `mismatched interface`() {
val schema1 = ClassSchema(
"gen.A",
mapOf("a" to NonNullableField(String::class.java)))
val schema2 = ClassSchema(
"gen.B",
mapOf("c" to NonNullableField(Int::class.java)),
schema1,
interfaces = listOf(DummyInterface::class.java))
val clazz = cc.build(schema2)
val i = clazz.constructors[0].newInstance("xa", 1) as DummyInterface
assertEquals(1, i.b)
}
@Test
fun `generate interface`() {
val schema1 = InterfaceSchema(
"gen.Interface",
mapOf("a" to NonNullableField(Int::class.java)))
val iface = cc.build(schema1)
assert(iface.isInterface)
assert(iface.constructors.isEmpty())
assertEquals(iface.declaredMethods.size, 1)
assertEquals(iface.declaredMethods[0].name, "getA")
val schema2 = ClassSchema(
"gen.Derived",
mapOf("a" to NonNullableField(Int::class.java)),
interfaces = listOf(iface))
val clazz = cc.build(schema2)
val testA = 42
val i = clazz.constructors[0].newInstance(testA) as SimpleFieldAccess
assertEquals(testA, i["a"])
}
@Test
fun `generate multiple interfaces`() {
val iFace1 = InterfaceSchema(
"gen.Interface1",
mapOf(
"a" to NonNullableField(Int::class.java),
"b" to NonNullableField(String::class.java)))
val iFace2 = InterfaceSchema(
"gen.Interface2",
mapOf(
"c" to NonNullableField(Int::class.java),
"d" to NonNullableField(String::class.java)))
val class1 = ClassSchema(
"gen.Derived",
mapOf(
"a" to NonNullableField(Int::class.java),
"b" to NonNullableField(String::class.java),
"c" to NonNullableField(Int::class.java),
"d" to NonNullableField(String::class.java)),
interfaces = listOf(cc.build(iFace1), cc.build(iFace2)))
val clazz = cc.build(class1)
val testA = 42
val testB = "don't touch me, I'm scared"
val testC = 0xDEAD
val testD = "wibble"
val i = clazz.constructors[0].newInstance(testA, testB, testC, testD) as SimpleFieldAccess
assertEquals(testA, i["a"])
assertEquals(testB, i["b"])
assertEquals(testC, i["c"])
assertEquals(testD, i["d"])
}
@Test
fun `interface implementing interface`() {
val iFace1 = InterfaceSchema(
"gen.Interface1",
mapOf(
"a" to NonNullableField(Int::class.java),
"b" to NonNullableField(String::class.java)))
val iFace2 = InterfaceSchema(
"gen.Interface2",
mapOf(
"c" to NonNullableField(Int::class.java),
"d" to NonNullableField(String::class.java)),
interfaces = listOf(cc.build(iFace1)))
val class1 = ClassSchema(
"gen.Derived",
mapOf(
"a" to NonNullableField(Int::class.java),
"b" to NonNullableField(String::class.java),
"c" to NonNullableField(Int::class.java),
"d" to NonNullableField(String::class.java)),
interfaces = listOf(cc.build(iFace2)))
val clazz = cc.build(class1)
val testA = 99
val testB = "green is not a creative colour"
val testC = 7
val testD = "I like jam"
val i = clazz.constructors[0].newInstance(testA, testB, testC, testD) as SimpleFieldAccess
assertEquals(testA, i["a"])
assertEquals(testB, i["b"])
assertEquals(testC, i["c"])
assertEquals(testD, i["d"])
}
@Test(expected = java.lang.IllegalArgumentException::class)
fun `null parameter small int`() {
val className = "iEnjoySwede"
val schema = ClassSchema(
"gen.$className",
mapOf("a" to NonNullableField(Int::class.java)))
val clazz = cc.build(schema)
val a: Int? = null
clazz.constructors[0].newInstance(a)
}
@Test(expected = NullablePrimitiveException::class)
fun `nullable parameter small int`() {
val className = "iEnjoySwede"
val schema = ClassSchema(
"gen.$className",
mapOf("a" to NullableField(Int::class.java)))
cc.build(schema)
}
@Test
fun `nullable parameter integer`() {
val className = "iEnjoyWibble"
val schema = ClassSchema(
"gen.$className",
mapOf("a" to NullableField(Integer::class.java)))
val clazz = cc.build(schema)
val a1: Int? = null
clazz.constructors[0].newInstance(a1)
val a2: Int? = 10
clazz.constructors[0].newInstance(a2)
}
@Test
fun `non nullable parameter integer with non null`() {
val className = "iEnjoyWibble"
val schema = ClassSchema(
"gen.$className",
mapOf("a" to NonNullableField(Integer::class.java)))
val clazz = cc.build(schema)
val a: Int? = 10
clazz.constructors[0].newInstance(a)
}
@Test(expected = java.lang.reflect.InvocationTargetException::class)
fun `non nullable parameter integer with null`() {
val className = "iEnjoyWibble"
val schema = ClassSchema(
"gen.$className",
mapOf("a" to NonNullableField(Integer::class.java)))
val clazz = cc.build(schema)
val a: Int? = null
clazz.constructors[0].newInstance(a)
}
@Test
fun `int array`() {
val className = "iEnjoyPotato"
val schema = ClassSchema(
"gen.$className",
mapOf("a" to NonNullableField(IntArray::class.java)))
val clazz = cc.build(schema)
val i = clazz.constructors[0].newInstance(intArrayOf(1, 2, 3)) as SimpleFieldAccess
val arr = clazz.getMethod("getA").invoke(i)
assertEquals(1, (arr as IntArray)[0])
assertEquals(2, arr[1])
assertEquals(3, arr[2])
assertEquals("$className{a=[1, 2, 3]}", i.toString())
}
@Test(expected = java.lang.reflect.InvocationTargetException::class)
fun `nullable int array throws`() {
val className = "iEnjoySwede"
val schema = ClassSchema(
"gen.$className",
mapOf("a" to NonNullableField(IntArray::class.java)))
val clazz = cc.build(schema)
val a: IntArray? = null
clazz.constructors[0].newInstance(a)
}
@Test
fun `integer array`() {
val className = "iEnjoyFlan"
val schema = ClassSchema(
"gen.$className",
mapOf("a" to NonNullableField(Array<Int>::class.java)))
val clazz = cc.build(schema)
val i = clazz.constructors[0].newInstance(arrayOf(1, 2, 3)) as SimpleFieldAccess
val arr: Array<Int> = uncheckedCast(clazz.getMethod("getA").invoke(i))
assertEquals(1, arr[0])
assertEquals(2, arr[1])
assertEquals(3, arr[2])
assertEquals("$className{a=[1, 2, 3]}", i.toString())
}
@Test
fun `int array with ints`() {
val className = "iEnjoyCrumble"
val schema = ClassSchema(
"gen.$className", mapOf(
"a" to Int::class.java,
"b" to IntArray::class.java,
"c" to Int::class.java).mapValues { NonNullableField(it.value) })
val clazz = cc.build(schema)
val i = clazz.constructors[0].newInstance(2, intArrayOf(4, 8), 16) as SimpleFieldAccess
assertEquals(2, clazz.getMethod("getA").invoke(i))
assertEquals(4, (clazz.getMethod("getB").invoke(i) as IntArray)[0])
assertEquals(8, (clazz.getMethod("getB").invoke(i) as IntArray)[1])
assertEquals(16, clazz.getMethod("getC").invoke(i))
assertEquals("$className{a=2, b=[4, 8], c=16}", i.toString())
}
@Test
fun `multiple int arrays`() {
val className = "iEnjoyJam"
val schema = ClassSchema(
"gen.$className", mapOf(
"a" to IntArray::class.java,
"b" to Int::class.java,
"c" to IntArray::class.java).mapValues { NonNullableField(it.value) })
val clazz = cc.build(schema)
val i = clazz.constructors[0].newInstance(intArrayOf(1, 2), 3, intArrayOf(4, 5, 6))
assertEquals(1, (clazz.getMethod("getA").invoke(i) as IntArray)[0])
assertEquals(2, (clazz.getMethod("getA").invoke(i) as IntArray)[1])
assertEquals(3, clazz.getMethod("getB").invoke(i))
assertEquals(4, (clazz.getMethod("getC").invoke(i) as IntArray)[0])
assertEquals(5, (clazz.getMethod("getC").invoke(i) as IntArray)[1])
assertEquals(6, (clazz.getMethod("getC").invoke(i) as IntArray)[2])
assertEquals("$className{a=[1, 2], b=3, c=[4, 5, 6]}", i.toString())
}
@Test
fun `string array`() {
val className = "iEnjoyToast"
val schema = ClassSchema(
"gen.$className",
mapOf("a" to NullableField(Array<String>::class.java)))
val clazz = cc.build(schema)
val i = clazz.constructors[0].newInstance(arrayOf("toast", "butter", "jam"))
val arr: Array<String> = uncheckedCast(clazz.getMethod("getA").invoke(i))
assertEquals("toast", arr[0])
assertEquals("butter", arr[1])
assertEquals("jam", arr[2])
}
@Test
fun `string arrays`() {
val className = "iEnjoyToast"
val schema = ClassSchema(
"gen.$className",
mapOf(
"a" to Array<String>::class.java,
"b" to String::class.java,
"c" to Array<String>::class.java).mapValues { NullableField(it.value) })
val clazz = cc.build(schema)
val i = clazz.constructors[0].newInstance(
arrayOf("bread", "spread", "cheese"),
"and on the side",
arrayOf("some pickles", "some fries"))
val arr1: Array<String> = uncheckedCast(clazz.getMethod("getA").invoke(i))
val arr2: Array<String> = uncheckedCast(clazz.getMethod("getC").invoke(i))
assertEquals("bread", arr1[0])
assertEquals("spread", arr1[1])
assertEquals("cheese", arr1[2])
assertEquals("and on the side", clazz.getMethod("getB").invoke(i))
assertEquals("some pickles", arr2[0])
assertEquals("some fries", arr2[1])
}
@Test
fun `nullable sets annotations`() {
val className = "iEnjoyJam"
val schema = ClassSchema(
"gen.$className",
mapOf("a" to NullableField(String::class.java),
"b" to NonNullableField(String::class.java)))
val clazz = cc.build(schema)
assertEquals(2, clazz.declaredFields.size)
assertEquals(1, clazz.getDeclaredField("a").annotations.size)
assertEquals(Nullable::class.java, clazz.getDeclaredField("a").annotations[0].annotationClass.java)
assertEquals(1, clazz.getDeclaredField("b").annotations.size)
assertEquals(Nonnull::class.java, clazz.getDeclaredField("b").annotations[0].annotationClass.java)
assertEquals(1, clazz.getMethod("getA").annotations.size)
assertEquals(Nullable::class.java, clazz.getMethod("getA").annotations[0].annotationClass.java)
assertEquals(1, clazz.getMethod("getB").annotations.size)
assertEquals(Nonnull::class.java, clazz.getMethod("getB").annotations[0].annotationClass.java)
}
@Test
fun beanTest() {
val schema = ClassSchema(
"pantsPantsPants",
mapOf("a" to NonNullableField(Integer::class.java)))
val clazz = cc.build(schema)
val descriptors = Introspector.getBeanInfo(clazz).propertyDescriptors
assertEquals(2, descriptors.size)
assertNotEquals(null, descriptors.find { it.name == "a" })
assertNotEquals(null, descriptors.find { it.name == "class" })
}
}

View File

@ -0,0 +1,53 @@
package net.corda.serialization.internal.carpenter
import net.corda.core.serialization.ClassWhitelist
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.serialize
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)
}
/**
* Custom implementation of a [SerializerFactory] where we need to give it a class carpenter
* rather than have it create its own
*/
class SerializerFactoryExternalCarpenter(classCarpenter: ClassCarpenter)
: SerializerFactory(classCarpenter.whitelist, classCarpenter)
open class AmqpCarpenterBase(whitelist: ClassWhitelist) {
var cc = ClassCarpenterImpl(whitelist = whitelist)
var factory = SerializerFactoryExternalCarpenter(cc)
fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz)
fun testName(): String = Thread.currentThread().stackTrace[2].methodName
@Suppress("NOTHING_TO_INLINE")
inline fun classTestName(clazz: String) = "${this.javaClass.name}\$${testName()}\$$clazz"
}

View File

@ -0,0 +1,104 @@
package net.corda.serialization.internal.carpenter
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.CordaSerializable
import org.assertj.core.api.Assertions
import org.junit.Ignore
import org.junit.Test
import java.io.NotSerializableException
class ClassCarpenterWhitelistTest {
// whitelisting a class on the class path will mean we will carpente up a class that
// contains it as a member
@Test
fun whitelisted() {
data class A(val a: Int)
class WL : ClassWhitelist {
private val allowedClasses = setOf<String>(
A::class.java.name
)
override fun hasListed(type: Class<*>): Boolean = type.name in allowedClasses
}
val cc = ClassCarpenterImpl(whitelist = WL())
// if this works, the test works, if it throws then we're in a world of pain, we could
// go further but there are a lot of other tests that test weather we can build
// carpented objects
cc.build(ClassSchema("thing", mapOf("a" to NonNullableField(A::class.java))))
}
@Test
@Ignore("Currently the carpenter doesn't inspect it's whitelist so will carpent anything" +
"it's asked relying on the serializer factory to not ask for anything")
fun notWhitelisted() {
data class A(val a: Int)
class WL : ClassWhitelist {
override fun hasListed(type: Class<*>) = false
}
val cc = ClassCarpenterImpl(whitelist = WL())
// Class A isn't on the whitelist, so we should fail to carpent it
Assertions.assertThatThrownBy {
cc.build(ClassSchema("thing", mapOf("a" to NonNullableField(A::class.java))))
}.isInstanceOf(NotSerializableException::class.java)
}
// despite now being whitelisted and on the class path, we will carpent this because
// it's marked as CordaSerializable
@Test
fun notWhitelistedButAnnotated() {
@CordaSerializable
data class A(val a: Int)
class WL : ClassWhitelist {
override fun hasListed(type: Class<*>) = false
}
val cc = ClassCarpenterImpl(whitelist = WL())
// again, simply not throwing here is enough to show the test worked and the carpenter
// didn't reject the type even though it wasn't on the whitelist because it was
// annotated properly
cc.build(ClassSchema("thing", mapOf("a" to NonNullableField(A::class.java))))
}
@Test
@Ignore("Currently the carpenter doesn't inspect it's whitelist so will carpent anything" +
"it's asked relying on the serializer factory to not ask for anything")
fun notWhitelistedButCarpented() {
// just have the white list reject *Everything* except ints
class WL : ClassWhitelist {
override fun hasListed(type: Class<*>) = type.name == "int"
}
val cc = ClassCarpenterImpl(whitelist = WL())
val schema1a = ClassSchema("thing1a", mapOf("a" to NonNullableField(Int::class.java)))
// thing 1 won't be set as corda serializable, meaning we won't build schema 2
schema1a.unsetCordaSerializable()
val clazz1a = cc.build(schema1a)
val schema2 = ClassSchema("thing2", mapOf("a" to NonNullableField(clazz1a)))
// thing 2 references thing 1 which wasn't carpented as corda s erializable and thus
// this will fail
Assertions.assertThatThrownBy {
cc.build(schema2)
}.isInstanceOf(NotSerializableException::class.java)
// create a second type of schema1, this time leave it as corda serialzable
val schema1b = ClassSchema("thing1b", mapOf("a" to NonNullableField(Int::class.java)))
val clazz1b = cc.build(schema1b)
// since schema 1b was created as CordaSerializable this will work
ClassSchema("thing2", mapOf("a" to NonNullableField(clazz1b)))
}
}

View File

@ -0,0 +1,288 @@
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 kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@CordaSerializable
interface I_ {
val a: Int
}
class CompositeMembers : AmqpCarpenterBase(AllWhitelist) {
@Test
fun bothKnown() {
val testA = 10
val testB = 20
@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))
assert(obj.obj is B)
val amqpObj = obj.obj as B
assertEquals(testB, amqpObj.b)
assertEquals(testA, amqpObj.a.a)
assertEquals(2, obj.envelope.schema.types.size)
assert(obj.envelope.schema.types[0] is CompositeType)
assert(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
}
}
assert(amqpSchemaA != null)
assert(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
assert(metaSchema.carpenterSchemas.isEmpty())
assert(metaSchema.dependsOn.isEmpty())
assert(metaSchema.dependencies.isEmpty())
}
// 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
@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 amqpSchema = obj.envelope.schema.mangleNames(listOf(classTestName("A")))
assert(obj.obj is B)
amqpSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
}
@Test
fun ParentIsUnknown() {
val testA = 10
val testB = 20
@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))
assert(obj.obj is B)
val amqpSchema = obj.envelope.schema.mangleNames(listOf(classTestName("B")))
val carpenterSchema = amqpSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
assertEquals(1, carpenterSchema.size)
val metaCarpenter = MetaCarpenter(carpenterSchema, ClassCarpenterImpl(whitelist = AllWhitelist))
metaCarpenter.build()
assert(mangleName(classTestName("B")) in metaCarpenter.objects)
}
@Test
fun BothUnknown() {
val testA = 10
val testB = 20
@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))
assert(obj.obj is 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)
assert(mangleName(classTestName("B")) in carpenterSchema.dependencies)
assertEquals(1, carpenterSchema.dependsOn.size)
assert(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()
assert(mangleName(classTestName("A")) in metaCarpenter.objects)
assert(mangleName(classTestName("B")) in metaCarpenter.objects)
// and we must be finished
assertTrue(carpenterSchema.carpenterSchemas.isEmpty())
}
@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))
assert(obj.obj is 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))
assert(obj.obj is 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))
assert(obj.obj is 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
@CordaSerializable
open class A(open val a: Int)
@CordaSerializable
class B(override val a: Int, val b: Int) : A (a)
val b = B(testA, testB)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b))
assert(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)
}
*/
}

View File

@ -0,0 +1,106 @@
package net.corda.serialization.internal.carpenter
import net.corda.serialization.internal.AllWhitelist
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class EnumClassTests : AmqpCarpenterBase(AllWhitelist) {
@Test
fun oneValue() {
val enumConstants = mapOf("A" to EnumField())
val schema = EnumSchema("gen.enum", enumConstants)
assertTrue(cc.build(schema).isEnum)
}
@Test
fun oneValueInstantiate() {
val enumConstants = mapOf("A" to EnumField())
val schema = EnumSchema("gen.enum", enumConstants)
val clazz = cc.build(schema)
assertTrue(clazz.isEnum)
assertEquals(enumConstants.size, clazz.enumConstants.size)
assertEquals("A", clazz.enumConstants.first().toString())
assertEquals(0, (clazz.enumConstants.first() as Enum<*>).ordinal)
assertEquals("A", (clazz.enumConstants.first() as Enum<*>).name)
}
@Test
fun twoValuesInstantiate() {
val enumConstants = mapOf("left" to EnumField(), "right" to EnumField())
val schema = EnumSchema("gen.enum", enumConstants)
val clazz = cc.build(schema)
assertTrue(clazz.isEnum)
assertEquals(enumConstants.size, clazz.enumConstants.size)
val left = clazz.enumConstants[0] as Enum<*>
val right = clazz.enumConstants[1] as Enum<*>
assertEquals(0, left.ordinal)
assertEquals("left", left.name)
assertEquals(1, right.ordinal)
assertEquals("right", right.name)
}
@Test
fun manyValues() {
val enumConstants = listOf("AAA", "BBB", "CCC", "DDD", "EEE", "FFF",
"GGG", "HHH", "III", "JJJ").associateBy({ it }, { EnumField() })
val schema = EnumSchema("gen.enum", enumConstants)
val clazz = cc.build(schema)
assertTrue(clazz.isEnum)
assertEquals(enumConstants.size, clazz.enumConstants.size)
var idx = 0
enumConstants.forEach {
val constant = clazz.enumConstants[idx] as Enum<*>
assertEquals(idx++, constant.ordinal)
assertEquals(it.key, constant.name)
}
}
@Test
fun assignment() {
val enumConstants = listOf("AAA", "BBB", "CCC", "DDD", "EEE", "FFF").associateBy({ it }, { EnumField() })
val schema = EnumSchema("gen.enum", enumConstants)
val clazz = cc.build(schema)
assertEquals("CCC", clazz.getMethod("valueOf", String::class.java).invoke(null, "CCC").toString())
assertEquals("CCC", (clazz.getMethod("valueOf", String::class.java).invoke(null, "CCC") as Enum<*>).name)
val ddd = clazz.getMethod("valueOf", String::class.java).invoke(null, "DDD") as Enum<*>
assertTrue(ddd::class.java.isEnum)
assertEquals("DDD", ddd.name)
assertEquals(3, ddd.ordinal)
}
// if anything goes wrong with this test it's going to end up throwing *some*
// exception, hence the lack of asserts
@Test
fun assignAndTest() {
val cc2 = ClassCarpenterImpl(whitelist = AllWhitelist)
val schema1 = EnumSchema("gen.enum",
listOf("AAA", "BBB", "CCC", "DDD", "EEE", "FFF").associateBy({ it }, { EnumField() }))
val enumClazz = cc2.build(schema1)
val schema2 = ClassSchema("gen.class",
mapOf(
"a" to NonNullableField(Int::class.java),
"b" to NonNullableField(enumClazz)))
val classClazz = cc2.build(schema2)
// make sure we can construct a class that has an enum we've constructed as a member
classClazz.constructors[0].newInstance(1, enumClazz.getMethod(
"valueOf", String::class.java).invoke(null, "BBB"))
}
}

View File

@ -0,0 +1,461 @@
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
@CordaSerializable
interface J {
val j: Int
}
@CordaSerializable
interface I {
val i: Int
}
@CordaSerializable
interface II {
val ii: Int
}
@CordaSerializable
interface III : I {
val iii: Int
override val i: Int
}
@CordaSerializable
interface IIII {
val iiii: Int
val i: I
}
class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhitelist) {
@Test
fun interfaceParent1() {
class A(override val j: Int) : J
val testJ = 20
val a = A(testJ)
assertEquals(testJ, a.j)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
assertTrue(obj.obj is A)
val serSchema = obj.envelope.schema
assertEquals(2, serSchema.types.size)
val l1 = serSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
// 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)
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)
}
@Test
fun interfaceParent2() {
class A(override val j: Int, val jj: Int) : J
val testJ = 20
val testJJ = 40
val a = A(testJ, testJJ)
assertEquals(testJ, a.j)
assertEquals(testJJ, a.jj)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
assertTrue(obj.obj is 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)
}
@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))
assertTrue(obj.obj is A)
val serSchema = obj.envelope.schema
assertEquals(3, serSchema.types.size)
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)
}
@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))
assertTrue(obj.obj is A)
val serSchema = obj.envelope.schema
assertEquals(3, serSchema.types.size)
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)
}
@Test
fun memberInterface() {
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))
assertTrue(obj.obj is B)
val serSchema = obj.envelope.schema
// 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)
}
// 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)
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))
assertTrue(obj.obj is B)
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())
}
@Test
fun interfaceAndImplementation() {
class A(override val i: Int) : I
val testI = 25
val a = A(testI)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
assertTrue(obj.obj is A)
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)
}
@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)
}
@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)
}
}

View File

@ -0,0 +1,102 @@
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 kotlin.test.assertNotEquals
import net.corda.serialization.internal.amqp.testutils.deserializeAndReturnEnvelope
class MultiMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhitelist) {
@Test
fun twoInts() {
@CordaSerializable
data class A(val a: Int, val b: Int)
val testA = 10
val testB = 20
val a = A(testA, testB)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
assert(obj.obj is A)
val amqpObj = obj.obj as A
assertEquals(testA, amqpObj.a)
assertEquals(testB, amqpObj.b)
assertEquals(1, obj.envelope.schema.types.size)
assert(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)
}
@Test
fun intAndStr() {
@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))
assert(obj.obj is A)
val amqpObj = obj.obj as A
assertEquals(testA, amqpObj.a)
assertEquals(testB, amqpObj.b)
assertEquals(1, obj.envelope.schema.types.size)
assert(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("string", 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)
}
}

View File

@ -0,0 +1,217 @@
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))
assert(obj.obj is A)
val amqpObj = obj.obj as A
assertEquals(test, amqpObj.a)
assertEquals(1, obj.envelope.schema.types.size)
assert(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))
assert(obj.obj is A)
val amqpObj = obj.obj as A
assertEquals(test, amqpObj.a)
assertEquals(1, obj.envelope.schema.types.size)
assert(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))
assert(obj.obj is A)
val amqpObj = obj.obj as A
assertEquals(test, amqpObj.a)
assertEquals(1, obj.envelope.schema.types.size)
assert(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))
assert(obj.obj is A)
val amqpObj = obj.obj as A
assertEquals(test, amqpObj.a)
assertEquals(1, obj.envelope.schema.types.size)
assert(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))
assert(obj.obj is A)
val amqpObj = obj.obj as A
assertEquals(test, amqpObj.a)
assertEquals(1, obj.envelope.schema.types.size)
assert(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))
assert(obj.obj is A)
val amqpObj = obj.obj as A
assertEquals(test, amqpObj.a)
assertEquals(1, obj.envelope.schema.types.size)
assert(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

@ -0,0 +1,98 @@
package net.corda.serialization.internal.kryo
import net.corda.core.internal.declaredField
import net.corda.serialization.internal.ByteBufferOutputStream
import org.assertj.core.api.Assertions.catchThrowable
import org.junit.Assert.assertArrayEquals
import org.junit.Test
import java.io.*
import java.nio.BufferOverflowException
import java.util.*
import java.util.zip.DeflaterOutputStream
import java.util.zip.InflaterInputStream
import kotlin.test.assertEquals
import kotlin.test.assertSame
class KryoStreamsTest {
class NegOutputStream(private val stream: OutputStream) : OutputStream() {
override fun write(b: Int) = stream.write(-b)
}
class NegInputStream(private val stream: InputStream) : InputStream() {
override fun read() = stream.read().let {
if (it != -1) 0xff and -it else -1
}
}
@Test
fun `substitute output works`() {
assertArrayEquals(byteArrayOf(100, -101), kryoOutput {
write(100)
substitute(::NegOutputStream)
write(101)
})
}
@Test
fun `substitute input works`() {
kryoInput(byteArrayOf(100, 101).inputStream()) {
assertEquals(100, read())
substitute(::NegInputStream)
assertEquals(-101, read().toByte())
assertEquals(-1, read())
}
}
@Test
fun `zip round-trip`() {
val data = ByteArray(12345).also { Random(0).nextBytes(it) }
val encoded = kryoOutput {
write(data)
substitute(::DeflaterOutputStream)
write(data)
substitute(::DeflaterOutputStream) // Potentially useful if a different codec.
write(data)
}
kryoInput(encoded.inputStream()) {
assertArrayEquals(data, readBytes(data.size))
substitute(::InflaterInputStream)
assertArrayEquals(data, readBytes(data.size))
substitute(::InflaterInputStream)
assertArrayEquals(data, readBytes(data.size))
assertEquals(-1, read())
}
}
@Test
fun `ByteBufferOutputStream works`() {
val stream = ByteBufferOutputStream(3)
stream.write("abc".toByteArray())
val getBuf = stream.declaredField<ByteArray>(ByteArrayOutputStream::class, "buf")::value
assertEquals(3, getBuf().size)
repeat(2) {
assertSame<Any>(BufferOverflowException::class.java, catchThrowable {
stream.alsoAsByteBuffer(9) {
it.put("0123456789".toByteArray())
}
}.javaClass)
assertEquals(3 + 9, getBuf().size)
}
// This time make too much space:
stream.alsoAsByteBuffer(11) {
it.put("0123456789".toByteArray())
}
stream.write("def".toByteArray())
assertArrayEquals("abc0123456789def".toByteArray(), stream.toByteArray())
}
@Test
fun `ByteBufferOutputStream discards data after final position`() {
val stream = ByteBufferOutputStream(0)
stream.alsoAsByteBuffer(10) {
it.put("0123456789".toByteArray())
it.position(5)
}
stream.write("def".toByteArray())
assertArrayEquals("01234def".toByteArray(), stream.toByteArray())
}
}

View File

@ -0,0 +1,352 @@
package net.corda.serialization.internal.kryo
import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.KryoException
import com.esotericsoftware.kryo.KryoSerializable
import com.esotericsoftware.kryo.io.Input
import com.esotericsoftware.kryo.io.Output
import com.esotericsoftware.kryo.pool.KryoPool
import com.google.common.primitives.Ints
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.PrivacySalt
import net.corda.core.crypto.*
import net.corda.core.internal.FetchDataFlow
import net.corda.core.serialization.*
import net.corda.core.utilities.ByteSequence
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.sequence
import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.serialization.internal.*
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.TestIdentity
import net.corda.testing.internal.rigorousMock
import org.assertj.core.api.Assertions.*
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.junit.runners.Parameterized.Parameters
import org.slf4j.LoggerFactory
import java.io.InputStream
import java.time.Instant
import java.util.*
import kotlin.test.*
class TestScheme : AbstractKryoSerializationScheme() {
override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean {
return magic == kryoMagic && target != SerializationContext.UseCase.RPCClient
}
override fun rpcClientKryoPool(context: SerializationContext): KryoPool = throw UnsupportedOperationException()
override fun rpcServerKryoPool(context: SerializationContext): KryoPool = throw UnsupportedOperationException()
}
@RunWith(Parameterized::class)
class KryoTests(private val compression: CordaSerializationEncoding?) {
companion object {
private val ALICE_PUBKEY = TestIdentity(ALICE_NAME, 70).publicKey
@Parameters(name = "{0}")
@JvmStatic
fun compression() = arrayOf<CordaSerializationEncoding?>(null) + CordaSerializationEncoding.values()
}
private lateinit var factory: SerializationFactory
private lateinit var context: SerializationContext
@Before
fun setup() {
factory = SerializationFactoryImpl().apply { registerScheme(TestScheme()) }
context = SerializationContextImpl(kryoMagic,
javaClass.classLoader,
AllWhitelist,
emptyMap(),
true,
SerializationContext.UseCase.Storage,
compression,
rigorousMock<EncodingWhitelist>().also {
if (compression != null) doReturn(true).whenever(it).acceptEncoding(compression)
})
}
@Test
fun `simple data class`() {
val birthday = Instant.parse("1984-04-17T00:30:00.00Z")
val mike = Person("mike", birthday)
val bits = mike.serialize(factory, context)
assertThat(bits.deserialize(factory, context)).isEqualTo(Person("mike", birthday))
}
@Test
fun `null values`() {
val bob = Person("bob", null)
val bits = bob.serialize(factory, context)
assertThat(bits.deserialize(factory, context)).isEqualTo(Person("bob", null))
}
@Test
fun `serialised form is stable when the same object instance is added to the deserialised object graph`() {
val noReferencesContext = context.withoutReferences()
val obj : ByteSequence = Ints.toByteArray(0x01234567).sequence()
val originalList : ArrayList<ByteSequence> = arrayListOf(obj)
val deserialisedList = originalList.serialize(factory, noReferencesContext).deserialize(factory, noReferencesContext)
originalList += obj
deserialisedList += obj
assertThat(deserialisedList.serialize(factory, noReferencesContext)).isEqualTo(originalList.serialize(factory, noReferencesContext))
}
@Test
fun `serialised form is stable when the same object instance occurs more than once, and using java serialisation`() {
val noReferencesContext = context.withoutReferences()
val instant = Instant.ofEpochMilli(123)
val instantCopy = Instant.ofEpochMilli(123)
assertThat(instant).isNotSameAs(instantCopy)
val listWithCopies = arrayListOf(instant, instantCopy)
val listWithSameInstances = arrayListOf(instant, instant)
assertThat(listWithSameInstances.serialize(factory, noReferencesContext)).isEqualTo(listWithCopies.serialize(factory, noReferencesContext))
}
@Test
fun `cyclic object graph`() {
val cyclic = Cyclic(3)
val bits = cyclic.serialize(factory, context)
assertThat(bits.deserialize(factory, context)).isEqualTo(cyclic)
}
@Test
fun `deserialised key pair functions the same as serialised one`() {
val keyPair = generateKeyPair()
val bitsToSign: ByteArray = Ints.toByteArray(0x01234567)
val wrongBits: ByteArray = Ints.toByteArray(0x76543210)
val signature = keyPair.sign(bitsToSign)
signature.verify(bitsToSign)
assertThatThrownBy { signature.verify(wrongBits) }
val deserialisedKeyPair = keyPair.serialize(factory, context).deserialize(factory, context)
val deserialisedSignature = deserialisedKeyPair.sign(bitsToSign)
deserialisedSignature.verify(bitsToSign)
assertThatThrownBy { deserialisedSignature.verify(wrongBits) }
}
@Test
fun `write and read Kotlin object singleton`() {
val serialised = TestSingleton.serialize(factory, context)
val deserialised = serialised.deserialize(factory, context)
assertThat(deserialised).isSameAs(TestSingleton)
}
@Test
fun `check Kotlin EmptyList can be serialised`() {
val deserialisedList: List<Int> = emptyList<Int>().serialize(factory, context).deserialize(factory, context)
assertEquals(0, deserialisedList.size)
assertEquals<Any>(Collections.emptyList<Int>().javaClass, deserialisedList.javaClass)
}
@Test
fun `check Kotlin EmptySet can be serialised`() {
val deserialisedSet: Set<Int> = emptySet<Int>().serialize(factory, context).deserialize(factory, context)
assertEquals(0, deserialisedSet.size)
assertEquals<Any>(Collections.emptySet<Int>().javaClass, deserialisedSet.javaClass)
}
@Test
fun `check Kotlin EmptyMap can be serialised`() {
val deserialisedMap: Map<Int, Int> = emptyMap<Int, Int>().serialize(factory, context).deserialize(factory, context)
assertEquals(0, deserialisedMap.size)
assertEquals<Any>(Collections.emptyMap<Int, Int>().javaClass, deserialisedMap.javaClass)
}
@Test
fun `InputStream serialisation`() {
val rubbish = ByteArray(12345, { (it * it * 0.12345).toByte() })
val readRubbishStream: InputStream = rubbish.inputStream().serialize(factory, context).deserialize(factory, context)
for (i in 0..12344) {
assertEquals(rubbish[i], readRubbishStream.read().toByte())
}
assertEquals(-1, readRubbishStream.read())
}
@Test
fun `InputStream serialisation does not write trailing garbage`() {
val byteArrays = listOf("123", "456").map { it.toByteArray() }
val streams = byteArrays.map { it.inputStream() }.serialize(factory, context).deserialize(factory, context).iterator()
byteArrays.forEach { assertArrayEquals(it, streams.next().readBytes()) }
assertFalse(streams.hasNext())
}
@Test
fun `serialize - deserialize SignableData`() {
val testString = "Hello World"
val testBytes = testString.toByteArray()
val meta = SignableData(testBytes.sha256(), SignatureMetadata(1, Crypto.findSignatureScheme(ALICE_PUBKEY).schemeNumberID))
val serializedMetaData = meta.serialize(factory, context).bytes
val meta2 = serializedMetaData.deserialize<SignableData>(factory, context)
assertEquals(meta2, meta)
}
@Test
fun `serialize - deserialize Logger`() {
val storageContext: SerializationContext = context // TODO: make it storage context
val logger = LoggerFactory.getLogger("aName")
val logger2 = logger.serialize(factory, storageContext).deserialize(factory, storageContext)
assertEquals(logger.name, logger2.name)
assertTrue(logger === logger2)
}
@Test
fun `HashCheckingStream (de)serialize`() {
val rubbish = ByteArray(12345, { (it * it * 0.12345).toByte() })
val readRubbishStream: InputStream = NodeAttachmentService.HashCheckingStream(
SecureHash.sha256(rubbish),
rubbish.size,
rubbish.inputStream()
).serialize(factory, context).deserialize(factory, context)
for (i in 0..12344) {
assertEquals(rubbish[i], readRubbishStream.read().toByte())
}
assertEquals(-1, readRubbishStream.read())
}
@CordaSerializable
private data class Person(val name: String, val birthday: Instant?)
@Suppress("unused")
@CordaSerializable
private class Cyclic(val value: Int) {
val thisInstance = this
override fun equals(other: Any?): Boolean = (this === other) || (other is Cyclic && this.value == other.value)
override fun hashCode(): Int = value.hashCode()
override fun toString(): String = "Cyclic($value)"
}
@Test
fun `serialize - deserialize PrivacySalt`() {
val expected = PrivacySalt(byteArrayOf(
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
31, 32
))
val serializedBytes = expected.serialize(factory, context)
val actual = serializedBytes.deserialize(factory, context)
assertEquals(expected, actual)
}
@CordaSerializable
private object TestSingleton
object SimpleSteps {
object ONE : ProgressTracker.Step("one")
object TWO : ProgressTracker.Step("two")
object THREE : ProgressTracker.Step("three")
object FOUR : ProgressTracker.Step("four")
fun tracker() = ProgressTracker(ONE, TWO, THREE, FOUR)
}
object ChildSteps {
object AYY : ProgressTracker.Step("ayy")
object BEE : ProgressTracker.Step("bee")
object SEA : ProgressTracker.Step("sea")
fun tracker() = ProgressTracker(AYY, BEE, SEA)
}
@Test
fun rxSubscriptionsAreNotSerialized() {
val pt: ProgressTracker = SimpleSteps.tracker()
val pt2: ProgressTracker = ChildSteps.tracker()
class Unserializable : KryoSerializable {
override fun write(kryo: Kryo?, output: Output?) = throw AssertionError("not called")
override fun read(kryo: Kryo?, input: Input?) = throw AssertionError("not called")
fun foo() {
println("bar")
}
}
pt.setChildProgressTracker(SimpleSteps.TWO, pt2)
class Tmp {
val unserializable = Unserializable()
init {
pt2.changes.subscribe { unserializable.foo() }
}
}
Tmp()
val factory = SerializationFactoryImpl().apply { registerScheme(TestScheme()) }
val context = SerializationContextImpl(kryoMagic,
javaClass.classLoader,
AllWhitelist,
emptyMap(),
true,
SerializationContext.UseCase.P2P,
null)
pt.serialize(factory, context)
}
@Test
fun `serialize - deserialize Exception with suppressed`() {
val exception = IllegalArgumentException("fooBar")
val toBeSuppressedOnSenderSide = IllegalStateException("bazz1")
exception.addSuppressed(toBeSuppressedOnSenderSide)
val exception2 = exception.serialize(factory, context).deserialize(factory, context)
assertEquals(exception.message, exception2.message)
assertEquals(1, exception2.suppressed.size)
assertNotNull({ exception2.suppressed.find { it.message == toBeSuppressedOnSenderSide.message } })
val toBeSuppressedOnReceiverSide = IllegalStateException("bazz2")
exception2.addSuppressed(toBeSuppressedOnReceiverSide)
assertTrue { exception2.suppressed.contains(toBeSuppressedOnReceiverSide) }
assertEquals(2, exception2.suppressed.size)
}
@Test
fun `serialize - deserialize Exception no suppressed`() {
val exception = IllegalArgumentException("fooBar")
val exception2 = exception.serialize(factory, context).deserialize(factory, context)
assertEquals(exception.message, exception2.message)
assertEquals(0, exception2.suppressed.size)
val toBeSuppressedOnReceiverSide = IllegalStateException("bazz2")
exception2.addSuppressed(toBeSuppressedOnReceiverSide)
assertEquals(1, exception2.suppressed.size)
assertTrue { exception2.suppressed.contains(toBeSuppressedOnReceiverSide) }
}
@Test
fun `serialize - deserialize HashNotFound`() {
val randomHash = SecureHash.randomSHA256()
val exception = FetchDataFlow.HashNotFound(randomHash)
val exception2 = exception.serialize(factory, context).deserialize(factory, context)
assertEquals(randomHash, exception2.requested)
}
@Test
fun `compression has the desired effect`() {
compression ?: return
val data = ByteArray(12345).also { Random(0).nextBytes(it) }.let { it + it }
val compressed = data.serialize(factory, context)
assertEquals(.5, compressed.size.toDouble() / data.size, .03)
assertArrayEquals(data, compressed.deserialize(factory, context))
}
@Test
fun `a particular encoding can be banned for deserialization`() {
compression ?: return
doReturn(false).whenever(context.encodingWhitelist).acceptEncoding(compression)
val compressed = "whatever".serialize(factory, context)
catchThrowable { compressed.deserialize(factory, context) }.run {
assertSame<Any>(KryoException::class.java, javaClass)
assertEquals(encodingNotPermittedFormat.format(compression), message)
}
}
}

Some files were not shown because too many files have changed in this diff Show More