mirror of
https://github.com/corda/corda.git
synced 2025-06-23 01:19:00 +00:00
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:
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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()));
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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
|
||||
}
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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")}>" })
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
@ -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}")
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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())
|
||||
}
|
||||
}
|
@ -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) }
|
||||
}
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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"))
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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" })
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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)
|
@ -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)
|
||||
}
|
||||
}
|
@ -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" })
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
@ -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)))
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
@ -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"))
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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())
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user