Remove Kryo from core module completely, including gradle dependencies (#1161)

This commit is contained in:
Rick Parker
2017-08-09 14:53:46 +01:00
committed by GitHub
parent f81f374377
commit 28610868c4
84 changed files with 450 additions and 387 deletions

View File

@ -0,0 +1,331 @@
package net.corda.nodeapi
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.node.ServiceHub
import net.corda.core.node.services.AttachmentStorage
import net.corda.core.serialization.*
import net.corda.core.serialization.SerializationDefaults.P2P_CONTEXT
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.nodeapi.internal.serialization.SerializeAsTokenContextImpl
import net.corda.nodeapi.internal.serialization.WireTransactionSerializer
import net.corda.nodeapi.internal.serialization.withTokenContext
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.MEGA_CORP
import net.corda.testing.TestDependencyInjectionBase
import net.corda.testing.node.MockAttachmentStorage
import org.apache.commons.io.IOUtils
import org.junit.Assert
import org.junit.Test
import java.io.ByteArrayInputStream
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.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
interface DummyContractBackdoor {
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder
fun inspectState(state: ContractState): Int
}
val ATTACHMENT_TEST_PROGRAM_ID = AttachmentClassLoaderTests.AttachmentDummyContract()
class AttachmentClassLoaderTests : TestDependencyInjectionBase() {
companion object {
val ISOLATED_CONTRACTS_JAR_PATH: URL = AttachmentClassLoaderTests::class.java.getResource("isolated.jar")
private fun SerializationContext.withAttachmentStorage(attachmentStorage: AttachmentStorage): SerializationContext {
val serviceHub = mock<ServiceHub>()
whenever(serviceHub.attachments).thenReturn(attachmentStorage)
return this.withTokenContext(SerializeAsTokenContextImpl(serviceHub) {}).withProperty(WireTransactionSerializer.attachmentsClassLoaderEnabled, true)
}
}
class AttachmentDummyContract : Contract {
data class State(val magicNumber: Int = 0) : ContractState {
override val contract = ATTACHMENT_TEST_PROGRAM_ID
override val participants: List<AbstractParty>
get() = listOf()
}
interface Commands : CommandData {
class Create : TypeOnlyCommandData(), Commands
}
override fun verify(tx: LedgerTransaction) {
// Always accepts.
}
// The "empty contract"
override val legalContractReference: SecureHash = SecureHash.sha256("")
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder {
val state = State(magicNumber)
return TransactionBuilder(notary).withItems(state, Command(Commands.Create(), owner.party.owningKey))
}
}
fun importJar(storage: AttachmentStorage) = ISOLATED_CONTRACTS_JAR_PATH.openStream().use { storage.importAttachment(it) }
// 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() {
override fun loadClass(name: String, resolve: Boolean): Class<*>? {
if ("AnotherDummyContract" in name) {
return null
} else
return super.loadClass(name, resolve)
}
}
class ClassLoaderForTests : URLClassLoader(arrayOf(ISOLATED_CONTRACTS_JAR_PATH), FilteringClassLoader)
@Test
fun `dynamically load AnotherDummyContract from isolated contracts jar`() {
val child = ClassLoaderForTests()
val contractClass = Class.forName("net.corda.contracts.isolated.AnotherDummyContract", true, child)
val contract = contractClass.newInstance() as Contract
assertEquals(SecureHash.sha256("https://anotherdummy.org"), contract.legalContractReference)
}
fun fakeAttachment(filepath: String, content: String): ByteArray {
val bs = ByteArrayOutputStream()
val js = JarOutputStream(bs)
js.putNextEntry(ZipEntry(filepath))
js.writer().apply { append(content); flush() }
js.closeEntry()
js.close()
return bs.toByteArray()
}
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 = MockAttachmentStorage()
val key = importJar(storage)
val attachment = storage.openAttachment(key)!!
val jar = attachment.openAsJAR()
assertNotNull(jar.nextEntry)
}
@Test
fun `test overlapping file exception`() {
val storage = MockAttachmentStorage()
val att0 = importJar(storage)
val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file.txt", "some data")))
val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file.txt", "some other data")))
assertFailsWith(AttachmentsClassLoader.OverlappingAttachments::class) {
AttachmentsClassLoader(arrayOf(att0, att1, att2).map { storage.openAttachment(it)!! })
}
}
@Test
fun `basic`() {
val storage = MockAttachmentStorage()
val att0 = importJar(storage)
val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file1.txt", "some data")))
val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file2.txt", "some other data")))
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
fun `Check platform independent path handling in attachment jars`() {
val storage = MockAttachmentStorage()
val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("/folder1/foldera/file1.txt", "some data")))
val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("\\folder1\\folderb\\file2.txt", "some other data")))
val data1a = readAttachment(storage.openAttachment(att1)!!, "/folder1/foldera/file1.txt")
Assert.assertArrayEquals("some data".toByteArray(), data1a)
val data1b = readAttachment(storage.openAttachment(att1)!!, "\\folder1\\foldera\\file1.txt")
Assert.assertArrayEquals("some data".toByteArray(), data1b)
val data2a = readAttachment(storage.openAttachment(att2)!!, "\\folder1\\folderb\\file2.txt")
Assert.assertArrayEquals("some other data".toByteArray(), data2a)
val data2b = readAttachment(storage.openAttachment(att2)!!, "/folder1/folderb/file2.txt")
Assert.assertArrayEquals("some other data".toByteArray(), data2b)
}
@Test
fun `loading class AnotherDummyContract`() {
val storage = MockAttachmentStorage()
val att0 = importJar(storage)
val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file1.txt", "some data")))
val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file2.txt", "some other data")))
val cl = AttachmentsClassLoader(arrayOf(att0, att1, att2).map { storage.openAttachment(it)!! }, FilteringClassLoader)
val contractClass = Class.forName("net.corda.contracts.isolated.AnotherDummyContract", true, cl)
val contract = contractClass.newInstance() as Contract
assertEquals(cl, contract.javaClass.classLoader)
assertEquals(SecureHash.sha256("https://anotherdummy.org"), contract.legalContractReference)
}
@Test
fun `verify that contract DummyContract is in classPath`() {
val contractClass = Class.forName("net.corda.nodeapi.AttachmentClassLoaderTests\$AttachmentDummyContract")
val contract = contractClass.newInstance() as Contract
assertNotNull(contract)
}
fun createContract2Cash(): Contract {
val cl = ClassLoaderForTests()
val contractClass = Class.forName("net.corda.contracts.isolated.AnotherDummyContract", true, cl)
return contractClass.newInstance() as Contract
}
@Test
fun `testing Kryo with ClassLoader (with top level class name)`() {
val contract = createContract2Cash()
val bytes = contract.serialize()
val storage = MockAttachmentStorage()
val att0 = importJar(storage)
val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file1.txt", "some data")))
val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file2.txt", "some other data")))
val cl = AttachmentsClassLoader(arrayOf(att0, att1, att2).map { storage.openAttachment(it)!! }, FilteringClassLoader)
val context = P2P_CONTEXT.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
fun `testing Kryo with ClassLoader (without top level class name)`() {
val data = Data(createContract2Cash())
assertNotNull(data.contract)
val context2 = P2P_CONTEXT.withWhitelisted(data.contract.javaClass)
val bytes = data.serialize(context = context2)
val storage = MockAttachmentStorage()
val att0 = importJar(storage)
val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file1.txt", "some data")))
val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file2.txt", "some other data")))
val cl = AttachmentsClassLoader(arrayOf(att0, att1, att2).map { storage.openAttachment(it)!! }, FilteringClassLoader)
val context = P2P_CONTEXT.withClassLoader(cl).withWhitelisted(Class.forName("net.corda.contracts.isolated.AnotherDummyContract", 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 = P2P_CONTEXT.withClassLoader(cl2).withWhitelisted(Class.forName("net.corda.contracts.isolated.AnotherDummyContract", true, cl2))
val state3 = bytes.deserialize(context = context3)
assertEquals(cl2, state3.contract.javaClass.classLoader)
assertNotNull(state3)
}
@Test
fun `test serialization of WireTransaction with statically loaded contract`() {
val tx = ATTACHMENT_TEST_PROGRAM_ID.generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY)
val wireTransaction = tx.toWireTransaction()
val bytes = wireTransaction.serialize()
val copiedWireTransaction = bytes.deserialize()
assertEquals(1, copiedWireTransaction.outputs.size)
assertEquals(42, (copiedWireTransaction.outputs[0].data as AttachmentDummyContract.State).magicNumber)
}
@Test
fun `test serialization of WireTransaction with dynamically loaded contract`() {
val child = ClassLoaderForTests()
val contractClass = Class.forName("net.corda.contracts.isolated.AnotherDummyContract", true, child)
val contract = contractClass.newInstance() as DummyContractBackdoor
val tx = contract.generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY)
val storage = MockAttachmentStorage()
val context = P2P_CONTEXT.withWhitelisted(contract.javaClass)
.withWhitelisted(Class.forName("net.corda.contracts.isolated.AnotherDummyContract\$State", true, child))
.withWhitelisted(Class.forName("net.corda.contracts.isolated.AnotherDummyContract\$Commands\$Create", true, child))
.withAttachmentStorage(storage)
// todo - think about better way to push attachmentStorage down to serializer
val bytes = run {
val attachmentRef = importJar(storage)
tx.addAttachment(storage.openAttachment(attachmentRef)!!.id)
val wireTransaction = tx.toWireTransaction()
wireTransaction.serialize(context = context)
}
val copiedWireTransaction = bytes.deserialize(context = context)
assertEquals(1, copiedWireTransaction.outputs.size)
val contract2 = copiedWireTransaction.getOutput(0).contract as DummyContractBackdoor
assertEquals(42, contract2.inspectState(copiedWireTransaction.outputs[0].data))
}
@Test
fun `test deserialize of WireTransaction where contract cannot be found`() {
val child = ClassLoaderForTests()
val contractClass = Class.forName("net.corda.contracts.isolated.AnotherDummyContract", true, child)
val contract = contractClass.newInstance() as DummyContractBackdoor
val tx = contract.generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY)
val storage = MockAttachmentStorage()
// todo - think about better way to push attachmentStorage down to serializer
val attachmentRef = importJar(storage)
val bytes = run {
tx.addAttachment(storage.openAttachment(attachmentRef)!!.id)
val wireTransaction = tx.toWireTransaction()
wireTransaction.serialize(context = P2P_CONTEXT.withAttachmentStorage(storage))
}
// use empty attachmentStorage
val e = assertFailsWith(MissingAttachmentsException::class) {
bytes.deserialize(context = P2P_CONTEXT.withAttachmentStorage(MockAttachmentStorage()))
}
assertEquals(attachmentRef, e.ids.single())
}
}

View File

@ -0,0 +1,241 @@
package net.corda.nodeapi.internal.serialization
import com.esotericsoftware.kryo.*
import com.esotericsoftware.kryo.io.Input
import com.esotericsoftware.kryo.io.Output
import com.esotericsoftware.kryo.util.MapReferenceResolver
import net.corda.core.node.services.AttachmentStorage
import net.corda.core.serialization.AttachmentsClassLoader
import net.corda.core.serialization.CordaSerializable
import net.corda.nodeapi.AttachmentClassLoaderTests
import net.corda.testing.node.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.*
@CordaSerializable
enum class Foo {
Bar {
override val value = 0
},
Stick {
override val value = 1
};
abstract val value: Int
}
@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()
}
}
class CordaClassResolverTests {
@Test
fun `Annotation on enum works for specialised entries`() {
// TODO: Remove this suppress when we upgrade to kotlin 1.1 or when JetBrain fixes the bug.
@Suppress("UNSUPPORTED_FEATURE")
CordaClassResolver(EmptyWhitelist).getRegistration(Foo.Bar::class.java)
}
@Test
fun `Annotation on array element works`() {
val values = arrayOf(Element())
CordaClassResolver(EmptyWhitelist).getRegistration(values.javaClass)
}
@Test
fun `Annotation not needed on abstract class`() {
CordaClassResolver(EmptyWhitelist).getRegistration(AbstractClass::class.java)
}
@Test
fun `Annotation not needed on interface`() {
CordaClassResolver(EmptyWhitelist).getRegistration(Interface::class.java)
}
@Test
fun `Calling register method on modified Kryo does not consult the whitelist`() {
val kryo = CordaKryo(CordaClassResolver(EmptyWhitelist))
kryo.register(NotSerializable::class.java)
}
@Test(expected = KryoException::class)
fun `Calling register method on unmodified Kryo does consult the whitelist`() {
val kryo = Kryo(CordaClassResolver(EmptyWhitelist), MapReferenceResolver())
kryo.register(NotSerializable::class.java)
}
@Test(expected = KryoException::class)
fun `Annotation is needed without whitelisting`() {
CordaClassResolver(EmptyWhitelist).getRegistration(NotSerializable::class.java)
}
@Test
fun `Annotation is not needed with whitelisting`() {
val resolver = CordaClassResolver(GlobalTransientClassWhiteList(EmptyWhitelist))
(resolver.whitelist as MutableClassWhitelist).add(NotSerializable::class.java)
resolver.getRegistration(NotSerializable::class.java)
}
@Test
fun `Annotation not needed on Object`() {
CordaClassResolver(EmptyWhitelist).getRegistration(Object::class.java)
}
@Test
fun `Annotation not needed on primitive`() {
CordaClassResolver(EmptyWhitelist).getRegistration(Integer.TYPE)
}
@Test(expected = KryoException::class)
fun `Annotation does not work for custom serializable`() {
CordaClassResolver(EmptyWhitelist).getRegistration(CustomSerializable::class.java)
}
@Test(expected = KryoException::class)
fun `Annotation does not work in conjunction with Kryo annotation`() {
CordaClassResolver(EmptyWhitelist).getRegistration(DefaultSerializable::class.java)
}
private fun importJar(storage: AttachmentStorage) = AttachmentClassLoaderTests.ISOLATED_CONTRACTS_JAR_PATH.openStream().use { storage.importAttachment(it) }
@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.contracts.isolated.AnotherDummyContract", true, classLoader)
CordaClassResolver(EmptyWhitelist).getRegistration(attachedClass)
}
@Test
fun `Annotation is inherited from interfaces`() {
CordaClassResolver(EmptyWhitelist).getRegistration(SerializableViaInterface::class.java)
CordaClassResolver(EmptyWhitelist).getRegistration(SerializableViaSubInterface::class.java)
}
@Test
fun `Annotation is inherited from superclass`() {
CordaClassResolver(EmptyWhitelist).getRegistration(SubElement::class.java)
CordaClassResolver(EmptyWhitelist).getRegistration(SubSubElement::class.java)
CordaClassResolver(EmptyWhitelist).getRegistration(SerializableViaSuperSubInterface::class.java)
}
// Blacklist tests.
@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(AllButBlacklisted)
// HashSet is blacklisted.
resolver.getRegistration(HashSet::class.java)
}
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.nodeapi.internal.serialization.CordaClassResolverTests\$SubHashSet is blacklisted, so it cannot be used in serialization.")
val resolver = CordaClassResolver(AllButBlacklisted)
// 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.nodeapi.internal.serialization.CordaClassResolverTests\$SubSubHashSet is blacklisted, so it cannot be used in serialization.")
val resolver = CordaClassResolver(AllButBlacklisted)
// SubSubHashSet extends SubHashSet, which extends the blacklisted HashSet.
resolver.getRegistration(SubSubHashSet::class.java)
}
class ConnectionImpl(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.nodeapi.internal.serialization.CordaClassResolverTests\$ConnectionImpl is blacklisted, so it cannot be used in serialization.")
val resolver = CordaClassResolver(AllButBlacklisted)
// ConnectionImpl implements blacklisted Connection.
resolver.getRegistration(ConnectionImpl::class.java)
}
interface SubConnection : Connection
class SubConnectionImpl(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.nodeapi.internal.serialization.CordaClassResolverTests\$SubConnectionImpl is blacklisted, so it cannot be used in serialization.")
val resolver = CordaClassResolver(AllButBlacklisted)
// SubConnectionImpl implements SubConnection, which extends the blacklisted Connection.
resolver.getRegistration(SubConnectionImpl::class.java)
}
@Test
fun `Check forcibly allowed`() {
val resolver = CordaClassResolver(AllButBlacklisted)
// 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.nodeapi.internal.serialization.CordaClassResolverTests\$CordaSerializableHashSet is blacklisted, so it cannot be used in serialization.")
val resolver = CordaClassResolver(AllButBlacklisted)
// CordaSerializableHashSet is @CordaSerializable, but extends the blacklisted HashSet.
resolver.getRegistration(CordaSerializableHashSet::class.java)
}
}

View File

@ -0,0 +1,237 @@
package net.corda.nodeapi.internal.serialization
import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.KryoSerializable
import com.esotericsoftware.kryo.io.Input
import com.esotericsoftware.kryo.io.Output
import com.google.common.primitives.Ints
import net.corda.core.crypto.*
import net.corda.core.serialization.*
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.sequence
import net.corda.node.serialization.KryoServerSerializationScheme
import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.testing.ALICE
import net.corda.testing.ALICE_PUBKEY
import net.corda.testing.BOB
import net.corda.testing.BOB_PUBKEY
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.bouncycastle.cert.X509CertificateHolder
import org.junit.Before
import org.junit.Test
import org.slf4j.LoggerFactory
import java.io.ByteArrayInputStream
import java.io.InputStream
import java.security.cert.CertPath
import java.security.cert.CertificateFactory
import java.time.Instant
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class KryoTests {
private lateinit var factory: SerializationFactory
private lateinit var context: SerializationContext
@Before
fun setup() {
factory = SerializationFactoryImpl().apply { registerScheme(KryoServerSerializationScheme()) }
context = SerializationContextImpl(KryoHeaderV0_1,
javaClass.classLoader,
AllWhitelist,
emptyMap(),
true,
SerializationContext.UseCase.P2P)
}
@Test
fun ok() {
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 nullables() {
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 = Ints.toByteArray(0x01234567).sequence()
val originalList = 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 `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 `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, ByteArrayInputStream(rubbish)).serialize(factory, context).deserialize(factory, context)
for (i in 0..12344) {
assertEquals(rubbish[i], readRubbishStream.read().toByte())
}
assertEquals(-1, readRubbishStream.read())
}
@Test
fun `serialize - deserialize X509CertififcateHolder`() {
val expected: X509CertificateHolder = X509Utilities.createSelfSignedCACertificate(ALICE.name, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME))
val serialized = expected.serialize(factory, context).bytes
val actual: X509CertificateHolder = serialized.deserialize(factory, context)
assertEquals(expected, actual)
}
@Test
fun `serialize - deserialize X509CertPath`() {
val certFactory = CertificateFactory.getInstance("X509")
val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val rootCACert = X509Utilities.createSelfSignedCACertificate(ALICE.name, rootCAKey)
val certificate = X509Utilities.createCertificate(CertificateType.TLS, rootCACert, rootCAKey, BOB.name, BOB_PUBKEY)
val expected = certFactory.generateCertPath(listOf(certificate.cert, rootCACert.cert))
val serialized = expected.serialize(factory, context).bytes
val actual: CertPath = serialized.deserialize(factory, context)
assertEquals(expected, actual)
}
@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)"
}
@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(KryoServerSerializationScheme()) }
val context = SerializationContextImpl(KryoHeaderV0_1,
javaClass.classLoader,
AllWhitelist,
emptyMap(),
true,
SerializationContext.UseCase.P2P)
pt.serialize(factory, context)
}
}

View File

@ -0,0 +1,125 @@
package net.corda.nodeapi.internal.serialization
import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.KryoException
import com.esotericsoftware.kryo.io.Output
import com.nhaarman.mockito_kotlin.mock
import net.corda.core.node.ServiceHub
import net.corda.core.serialization.*
import net.corda.core.utilities.OpaqueBytes
import net.corda.node.serialization.KryoServerSerializationScheme
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Test
import java.io.ByteArrayOutputStream
class SerializationTokenTest {
lateinit var factory: SerializationFactory
lateinit var context: SerializationContext
@Before
fun setup() {
factory = SerializationFactoryImpl().apply { registerScheme(KryoServerSerializationScheme()) }
context = SerializationContextImpl(KryoHeaderV0_1,
javaClass.classLoader,
AllWhitelist,
emptyMap(),
true,
SerializationContext.UseCase.P2P)
}
// 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, mock<ServiceHub>())
@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(makeNoWhitelistClassResolver()))
val stream = ByteArrayOutputStream()
Output(stream).use {
it.write(KryoHeaderV0_1.bytes)
kryo.writeClass(it, SingletonSerializeAsToken::class.java)
kryo.writeObject(it, emptyList<Any>())
}
val serializedBytes = SerializedBytes<Any>(stream.toByteArray())
serializedBytes.deserialize(factory, testContext)
}
private class WrongTypeSerializeAsToken : SerializeAsToken {
object UnitSerializationToken : SerializationToken {
override fun fromToken(context: SerializeAsTokenContext): Any = UnitSerializeAsToken()
}
override fun toToken(context: SerializeAsTokenContext): SerializationToken = UnitSerializationToken
}
@Test(expected = KryoException::class)
fun `token returns unexpected type`() {
val tokenizableBefore = WrongTypeSerializeAsToken()
val context = serializeAsTokenContext(tokenizableBefore)
val testContext = this.context.withTokenContext(context)
val serializedBytes = tokenizableBefore.serialize(factory, testContext)
serializedBytes.deserialize(factory, testContext)
}
}

View File

@ -0,0 +1,45 @@
package net.corda.nodeapi.internal.serialization.amqp
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.assertTrue
class DeserializeAndReturnEnvelopeTests {
fun testName(): String = Thread.currentThread().stackTrace[2].methodName
@Suppress("NOTHING_TO_INLINE")
inline fun classTestName(clazz: String) = "${this.javaClass.name}\$${testName()}\$$clazz"
@Test
fun oneType() {
data class A(val a: Int, val b: String)
val a = A(10, "20")
val factory = SerializerFactory()
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)
val factory = SerializerFactory()
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") })
}
}

View File

@ -0,0 +1,469 @@
package net.corda.nodeapi.internal.serialization.amqp
import org.apache.qpid.proton.codec.Data
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 {
class TestSerializationOutput(
private val verbose: Boolean,
serializerFactory: SerializerFactory = SerializerFactory()) : SerializationOutput(serializerFactory) {
override fun writeSchema(schema: Schema, data: Data) {
if (verbose) println(schema)
super.writeSchema(schema, data)
}
}
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 sf = SerializerFactory()
@Test
fun testChar() {
data class C(val c: Char)
var deserializedC = DeserializationInput().deserialize(SerializationOutput().serialize(C('c')))
assertEquals('c', deserializedC.c)
// CYRILLIC CAPITAL LETTER YU (U+042E)
deserializedC = DeserializationInput().deserialize(SerializationOutput().serialize(C('Ю')))
assertEquals('Ю', deserializedC.c)
// ARABIC LETTER FEH WITH DOT BELOW (U+06A3)
deserializedC = DeserializationInput().deserialize(SerializationOutput().serialize(C('ڣ')))
assertEquals('ڣ', deserializedC.c)
// ARABIC LETTER DAD WITH DOT BELOW (U+06FB)
deserializedC = DeserializationInput().deserialize(SerializationOutput().serialize(C('ۻ')))
assertEquals('ۻ', deserializedC.c)
// BENGALI LETTER AA (U+0986)
deserializedC = DeserializationInput().deserialize(SerializationOutput().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().serialize(c)
val deserializedC = DeserializationInput().deserialize(serialisedC)
assertEquals(c.c, deserializedC.c)
}
@Test
fun testNullCharacter() {
data class C(val c: Char?)
val c = C(null)
val serialisedC = SerializationOutput().serialize(c)
val deserializedC = DeserializationInput().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, sf).serialize(ia)
val deserializedIA = DeserializationInput(sf).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, sf).serialize(ia)
val deserializedIA = DeserializationInput(sf).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, sf).serialize(ia)
val deserializedIA = DeserializationInput(sf).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, sf).serialize(c)
val deserializedC = DeserializationInput(sf).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, sf).serialize(c)
var deserializedC = DeserializationInput(sf).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(sf).deserialize(TestSerializationOutput(VERBOSE, sf).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, sf).serialize(c)
val deserializedC = DeserializationInput(sf).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, sf).serialize(c)
val deserializedC = DeserializationInput(sf).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, sf).serialize(c)
val deserializedC = DeserializationInput(sf).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, sf).serialize(c)
val deserializedC = DeserializationInput(sf).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, sf).serialize(c)
val deserializedC = DeserializationInput(sf).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, sf).serialize(c)
val deserializedC = DeserializationInput(sf).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, sf).serialize(c)
val deserializedC = DeserializationInput(sf).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, sf).serialize(c)
val deserializedC = DeserializationInput(sf).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, sf).serialize(c)
val deserializedC = DeserializationInput(sf).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, sf).serialize(c)
val deserializedC = DeserializationInput(sf).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, sf).serialize(c)
val deserializedC = DeserializationInput(sf).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, sf).serialize(c)
val deserializedC = DeserializationInput(sf).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, sf).serialize(c)
val deserializedC = DeserializationInput(sf).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, sf).serialize(c)
val deserializedC = DeserializationInput(sf).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, sf).serialize(c)
val deserializedC = DeserializationInput(sf).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])
}}}
}
}

View File

@ -0,0 +1,100 @@
package net.corda.nodeapi.internal.serialization.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(expected = NotSerializableException::class)
fun `test trailing text`() {
verify("java.util.Map<java.lang.String, java.lang.Integer>foo")
}
@Test(expected = NotSerializableException::class)
fun `test trailing comma`() {
verify("java.util.Map<java.lang.String, java.lang.Integer,>")
}
@Test(expected = NotSerializableException::class)
fun `test leading comma`() {
verify("java.util.Map<,java.lang.String, java.lang.Integer>")
}
@Test(expected = NotSerializableException::class)
fun `test middle comma`() {
verify("java.util.Map<,java.lang.String,, java.lang.Integer>")
}
@Test(expected = NotSerializableException::class)
fun `test trailing close`() {
verify("java.util.Map<java.lang.String, java.lang.Integer>>")
}
@Test(expected = NotSerializableException::class)
fun `test empty params`() {
verify("java.util.Map<>")
}
@Test(expected = NotSerializableException::class)
fun `test mid whitespace`() {
verify("java.u til.List<java.lang.String>")
}
@Test(expected = NotSerializableException::class)
fun `test mid whitespace2`() {
verify("java.util.List<java.l ng.String>")
}
@Test(expected = NotSerializableException::class)
fun `test wrong number of parameters`() {
verify("java.util.List<java.lang.String, java.lang.Integer>")
}
@Test
fun `test no parameters`() {
verify("java.lang.String")
}
@Test(expected = NotSerializableException::class)
fun `test parameters on non-generic type`() {
verify("java.lang.String<java.lang.Integer>")
}
@Test(expected = NotSerializableException::class)
fun `test excessive nesting`() {
var nested = "java.lang.Integer"
for (i in 1..DeserializedParameterizedType.MAX_DEPTH) {
nested = "java.util.List<$nested>"
}
verify(nested)
}
}

View File

@ -0,0 +1,590 @@
package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.CordaRuntimeException
import net.corda.core.contracts.Contract
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TransactionState
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowException
import net.corda.core.identity.AbstractParty
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.LedgerTransaction
import net.corda.nodeapi.RPCException
import net.corda.nodeapi.internal.serialization.EmptyWhitelist
import net.corda.nodeapi.internal.serialization.KryoAMQPSerializer
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory.Companion.isPrimitive
import net.corda.nodeapi.internal.serialization.amqp.custom.*
import net.corda.testing.MEGA_CORP
import net.corda.testing.MEGA_CORP_PUBKEY
import org.apache.qpid.proton.amqp.*
import org.apache.qpid.proton.codec.DecoderImpl
import org.apache.qpid.proton.codec.EncoderImpl
import org.junit.Test
import java.io.IOException
import java.io.NotSerializableException
import java.math.BigDecimal
import java.nio.ByteBuffer
import java.time.Instant
import java.util.*
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
class SerializationOutputTests {
data class Foo(val bar: String, val pub: Int)
data class testFloat(val f: Float)
data class testDouble(val d: Double)
data class testShort(val s: Short)
data class testBoolean(val b : Boolean)
interface FooInterface {
val pub: Int
}
data class FooImplements(val bar: String, override val pub: Int) : FooInterface
data class FooImplementsAndList(val bar: String, override val pub: Int, val names: List<String>) : FooInterface
data class WrapHashMap(val map: Map<String, String>)
data class WrapFooListArray(val listArray: Array<List<Foo>>) {
override fun equals(other: Any?): Boolean {
return other is WrapFooListArray && Objects.deepEquals(listArray, other.listArray)
}
override fun hashCode(): Int {
return 1 // This isn't used, but without overriding we get a warning.
}
}
data class Woo(val fred: Int) {
@Suppress("unused")
val bob = "Bob"
}
data class Woo2(val fred: Int, val bob: String = "Bob") {
@ConstructorForDeserialization constructor(fred: Int) : this(fred, "Ginger")
}
@CordaSerializable
data class AnnotatedWoo(val fred: Int) {
@Suppress("unused")
val bob = "Bob"
}
class FooList : ArrayList<Foo>()
@Suppress("AddVarianceModifier")
data class GenericFoo<T>(val bar: String, val pub: T)
data class ContainsGenericFoo(val contain: GenericFoo<String>)
data class NestedGenericFoo<T>(val contain: GenericFoo<T>)
data class ContainsNestedGenericFoo(val contain: NestedGenericFoo<String>)
data class TreeMapWrapper(val tree: TreeMap<Int, Foo>)
data class NavigableMapWrapper(val tree: NavigableMap<Int, Foo>)
data class SortedSetWrapper(val set: SortedSet<Int>)
open class InheritedGeneric<X>(val foo: X)
data class ExtendsGeneric(val bar: Int, val pub: String) : InheritedGeneric<String>(pub)
interface GenericInterface<X> {
val pub: X
}
data class ImplementsGenericString(val bar: Int, override val pub: String) : GenericInterface<String>
data class ImplementsGenericX<Y>(val bar: Int, override val pub: Y) : GenericInterface<Y>
abstract class AbstractGenericX<Z> : GenericInterface<Z>
data class InheritGenericX<A>(val duke: Double, override val pub: A) : AbstractGenericX<A>()
data class CapturesGenericX(val foo: GenericInterface<String>)
object KotlinObject
class Mismatch(fred: Int) {
val ginger: Int = fred
override fun equals(other: Any?): Boolean = (other as? Mismatch)?.ginger == ginger
override fun hashCode(): Int = ginger
}
class MismatchType(fred: Long) {
val ginger: Int = fred.toInt()
override fun equals(other: Any?): Boolean = (other as? MismatchType)?.ginger == ginger
override fun hashCode(): Int = ginger
}
@CordaSerializable
interface AnnotatedInterface
data class InheritAnnotation(val foo: String) : AnnotatedInterface
data class PolymorphicProperty(val foo: FooInterface?)
private fun serdes(obj: Any,
factory: SerializerFactory = SerializerFactory(),
freshDeserializationFactory: SerializerFactory = SerializerFactory(),
expectedEqual: Boolean = true,
expectDeserializedEqual: Boolean = true): Any {
val ser = SerializationOutput(factory)
val bytes = ser.serialize(obj)
val decoder = DecoderImpl().apply {
this.register(Envelope.DESCRIPTOR, Envelope.Companion)
this.register(Schema.DESCRIPTOR, Schema.Companion)
this.register(Descriptor.DESCRIPTOR, Descriptor.Companion)
this.register(Field.DESCRIPTOR, Field.Companion)
this.register(CompositeType.DESCRIPTOR, CompositeType.Companion)
this.register(Choice.DESCRIPTOR, Choice.Companion)
this.register(RestrictedType.DESCRIPTOR, RestrictedType.Companion)
}
EncoderImpl(decoder)
decoder.setByteBuffer(ByteBuffer.wrap(bytes.bytes, 8, bytes.size - 8))
// Check that a vanilla AMQP decoder can deserialize without schema.
val result = decoder.readObject() as Envelope
assertNotNull(result)
val des = DeserializationInput(freshDeserializationFactory)
val desObj = des.deserialize(bytes)
assertTrue(Objects.deepEquals(obj, desObj) == expectedEqual)
// Now repeat with a re-used factory
val ser2 = SerializationOutput(factory)
val des2 = DeserializationInput(factory)
val desObj2 = des2.deserialize(ser2.serialize(obj))
assertTrue(Objects.deepEquals(obj, desObj2) == expectedEqual)
assertTrue(Objects.deepEquals(desObj, desObj2) == expectDeserializedEqual)
// TODO: add some schema assertions to check correctly formed.
return desObj2
}
@Test
fun isPrimitive() {
assertTrue(isPrimitive(Character::class.java))
assertTrue(isPrimitive(Boolean::class.java))
assertTrue(isPrimitive(Byte::class.java))
assertTrue(isPrimitive(UnsignedByte::class.java))
assertTrue(isPrimitive(Short::class.java))
assertTrue(isPrimitive(UnsignedShort::class.java))
assertTrue(isPrimitive(Int::class.java))
assertTrue(isPrimitive(UnsignedInteger::class.java))
assertTrue(isPrimitive(Long::class.java))
assertTrue(isPrimitive(UnsignedLong::class.java))
assertTrue(isPrimitive(Float::class.java))
assertTrue(isPrimitive(Double::class.java))
assertTrue(isPrimitive(Decimal32::class.java))
assertTrue(isPrimitive(Decimal64::class.java))
assertTrue(isPrimitive(Decimal128::class.java))
assertTrue(isPrimitive(Char::class.java))
assertTrue(isPrimitive(Date::class.java))
assertTrue(isPrimitive(UUID::class.java))
assertTrue(isPrimitive(ByteArray::class.java))
assertTrue(isPrimitive(String::class.java))
assertTrue(isPrimitive(Symbol::class.java))
}
@Test
fun `test foo`() {
val obj = Foo("Hello World!", 123)
serdes(obj)
}
@Test
fun `test float`() {
val obj = testFloat(10.0F)
serdes(obj)
}
@Test
fun `test double`() {
val obj = testDouble(10.0)
serdes(obj)
}
@Test
fun `test short`() {
val obj = testShort(1)
serdes(obj)
}
@Test
fun `test bool`() {
val obj = testBoolean(true)
serdes(obj)
}
@Test
fun `test foo implements`() {
val obj = FooImplements("Hello World!", 123)
serdes(obj)
}
@Test
fun `test foo implements and list`() {
val obj = FooImplementsAndList("Hello World!", 123, listOf("Fred", "Ginger"))
serdes(obj)
}
@Test(expected = IllegalArgumentException::class)
fun `test dislike of HashMap`() {
val obj = WrapHashMap(HashMap<String, String>())
serdes(obj)
}
@Test
fun `test string array`() {
val obj = arrayOf("Fred", "Ginger")
serdes(obj)
}
@Test
fun `test foo array`() {
val obj = arrayOf(Foo("Fred", 1), Foo("Ginger", 2))
serdes(obj)
}
@Test(expected = NotSerializableException::class)
fun `test top level list array`() {
val obj = arrayOf(listOf("Fred", "Ginger"), listOf("Rogers", "Hammerstein"))
serdes(obj)
}
@Test
fun `test foo list array`() {
val obj = WrapFooListArray(arrayOf(listOf(Foo("Fred", 1), Foo("Ginger", 2)), listOf(Foo("Rogers", 3), Foo("Hammerstein", 4))))
serdes(obj)
}
@Test
fun `test not all properties in constructor`() {
val obj = Woo(2)
serdes(obj)
}
@Test
fun `test annotated constructor`() {
val obj = Woo2(3)
serdes(obj)
}
@Test(expected = NotSerializableException::class)
fun `test whitelist`() {
val obj = Woo2(4)
serdes(obj, SerializerFactory(EmptyWhitelist))
}
@Test
fun `test annotation whitelisting`() {
val obj = AnnotatedWoo(5)
serdes(obj, SerializerFactory(EmptyWhitelist))
}
@Test(expected = NotSerializableException::class)
fun `test generic list subclass is not supported`() {
val obj = FooList()
serdes(obj)
}
@Test
fun `test generic foo`() {
val obj = GenericFoo("Fred", "Ginger")
serdes(obj)
}
@Test
fun `test generic foo as property`() {
val obj = ContainsGenericFoo(GenericFoo("Fred", "Ginger"))
serdes(obj)
}
@Test
fun `test nested generic foo as property`() {
val obj = ContainsNestedGenericFoo(NestedGenericFoo(GenericFoo("Fred", "Ginger")))
serdes(obj)
}
// TODO: Generic interfaces / superclasses
@Test
fun `test extends generic`() {
val obj = ExtendsGeneric(1, "Ginger")
serdes(obj)
}
@Test
fun `test implements generic`() {
val obj = ImplementsGenericString(1, "Ginger")
serdes(obj)
}
@Test
fun `test implements generic captured`() {
val obj = CapturesGenericX(ImplementsGenericX(1, "Ginger"))
serdes(obj)
}
@Test
fun `test inherits generic captured`() {
val obj = CapturesGenericX(InheritGenericX(1.0, "Ginger"))
serdes(obj)
}
@Test(expected = NotSerializableException::class)
fun `test TreeMap`() {
val obj = TreeMap<Int, Foo>()
obj[456] = Foo("Fred", 123)
serdes(obj)
}
@Test(expected = NotSerializableException::class)
fun `test TreeMap property`() {
val obj = TreeMapWrapper(TreeMap<Int, Foo>())
obj.tree[456] = Foo("Fred", 123)
serdes(obj)
}
@Test
fun `test NavigableMap property`() {
val obj = NavigableMapWrapper(TreeMap<Int, Foo>())
obj.tree[456] = Foo("Fred", 123)
serdes(obj)
}
@Test
fun `test SortedSet property`() {
val obj = SortedSetWrapper(TreeSet<Int>())
obj.set += 456
serdes(obj)
}
@Test(expected = NotSerializableException::class)
fun `test mismatched property and constructor naming`() {
val obj = Mismatch(456)
serdes(obj)
}
@Test(expected = NotSerializableException::class)
fun `test mismatched property and constructor type`() {
val obj = MismatchType(456)
serdes(obj)
}
@Test
fun `test custom serializers on public key`() {
val factory = SerializerFactory()
factory.register(PublicKeySerializer)
val factory2 = SerializerFactory()
factory2.register(PublicKeySerializer)
val obj = MEGA_CORP_PUBKEY
serdes(obj, factory, factory2)
}
@Test
fun `test annotation is inherited`() {
val obj = InheritAnnotation("blah")
serdes(obj, SerializerFactory(EmptyWhitelist))
}
@Test
fun `test throwables serialize`() {
val factory = SerializerFactory()
factory.register(ThrowableSerializer(factory))
val factory2 = SerializerFactory()
factory2.register(ThrowableSerializer(factory2))
val t = IllegalAccessException("message").fillInStackTrace()
val desThrowable = serdes(t, factory, factory2, false) as Throwable
assertSerializedThrowableEquivalent(t, desThrowable)
}
@Test
fun `test complex throwables serialize`() {
val factory = SerializerFactory()
factory.register(ThrowableSerializer(factory))
val factory2 = SerializerFactory()
factory2.register(ThrowableSerializer(factory2))
try {
try {
throw IOException("Layer 1")
} catch(t: Throwable) {
throw IllegalStateException("Layer 2", t)
}
} catch(t: Throwable) {
val desThrowable = serdes(t, factory, factory2, false) as Throwable
assertSerializedThrowableEquivalent(t, desThrowable)
}
}
fun assertSerializedThrowableEquivalent(t: Throwable, desThrowable: Throwable) {
assertTrue(desThrowable is CordaRuntimeException) // Since we don't handle the other case(s) yet
if (desThrowable is CordaRuntimeException) {
assertEquals("${t.javaClass.name}: ${t.message}", desThrowable.message)
assertTrue(desThrowable is CordaRuntimeException)
assertTrue(Objects.deepEquals(t.stackTrace, desThrowable.stackTrace))
assertEquals(t.suppressed.size, desThrowable.suppressed.size)
t.suppressed.zip(desThrowable.suppressed).forEach { (before, after) -> assertSerializedThrowableEquivalent(before, after) }
}
}
@Test
fun `test suppressed throwables serialize`() {
val factory = SerializerFactory()
factory.register(ThrowableSerializer(factory))
val factory2 = SerializerFactory()
factory2.register(ThrowableSerializer(factory2))
try {
try {
throw IOException("Layer 1")
} catch(t: Throwable) {
val e = IllegalStateException("Layer 2")
e.addSuppressed(t)
throw e
}
} catch(t: Throwable) {
val desThrowable = serdes(t, factory, factory2, false) as Throwable
assertSerializedThrowableEquivalent(t, desThrowable)
}
}
@Test
fun `test flow corda exception subclasses serialize`() {
val factory = SerializerFactory()
factory.register(ThrowableSerializer(factory))
val factory2 = SerializerFactory()
factory2.register(ThrowableSerializer(factory2))
val obj = FlowException("message").fillInStackTrace()
serdes(obj, factory, factory2)
}
@Test
fun `test RPC corda exception subclasses serialize`() {
val factory = SerializerFactory()
factory.register(ThrowableSerializer(factory))
val factory2 = SerializerFactory()
factory2.register(ThrowableSerializer(factory2))
val obj = RPCException("message").fillInStackTrace()
serdes(obj, factory, factory2)
}
@Test
fun `test polymorphic property`() {
val obj = PolymorphicProperty(FooImplements("Ginger", 12))
serdes(obj)
}
@Test
fun `test null polymorphic property`() {
val obj = PolymorphicProperty(null)
serdes(obj)
}
@Test
fun `test kotlin object`() {
serdes(KotlinObject)
}
object FooContract : Contract {
override fun verify(tx: LedgerTransaction) {
}
override val legalContractReference: SecureHash = SecureHash.Companion.sha256("FooContractLegal")
}
class FooState : ContractState {
override val contract: Contract
get() = FooContract
override val participants: List<AbstractParty>
get() = emptyList()
}
@Test
fun `test transaction state`() {
val state = TransactionState<FooState>(FooState(), MEGA_CORP)
val factory = SerializerFactory()
KryoAMQPSerializer.registerCustomSerializers(factory)
val factory2 = SerializerFactory()
KryoAMQPSerializer.registerCustomSerializers(factory2)
val desState = serdes(state, factory, factory2, expectedEqual = false, expectDeserializedEqual = false)
assertTrue(desState is TransactionState<*>)
assertTrue((desState as TransactionState<*>).data is FooState)
assertTrue(desState.notary == state.notary)
assertTrue(desState.encumbrance == state.encumbrance)
}
@Test
fun `test currencies serialize`() {
val factory = SerializerFactory()
factory.register(CurrencySerializer)
val factory2 = SerializerFactory()
factory2.register(CurrencySerializer)
val obj = Currency.getInstance("USD")
serdes(obj, factory, factory2)
}
@Test
fun `test big decimals serialize`() {
val factory = SerializerFactory()
factory.register(BigDecimalSerializer)
val factory2 = SerializerFactory()
factory2.register(BigDecimalSerializer)
val obj = BigDecimal("100000000000000000000000000000.00")
serdes(obj, factory, factory2)
}
@Test
fun `test instants serialize`() {
val factory = SerializerFactory()
factory.register(InstantSerializer(factory))
val factory2 = SerializerFactory()
factory2.register(InstantSerializer(factory2))
val obj = Instant.now()
serdes(obj, factory, factory2)
}
@Test
fun `test StateRef serialize`() {
val factory = SerializerFactory()
factory.register(InstantSerializer(factory))
val factory2 = SerializerFactory()
factory2.register(InstantSerializer(factory2))
val obj = StateRef(SecureHash.randomSHA256(), 0)
serdes(obj, factory, factory2)
}
}

View File

@ -0,0 +1,498 @@
package net.corda.nodeapi.internal.serialization.carpenter
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
}
val cc = ClassCarpenter()
// 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.
val Class<*>.nonSyntheticFields: List<Field> get() = declaredFields.filterNot { it.isSynthetic }
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
@Suppress("UNCHECKED_CAST")
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
@Suppress("UNCHECKED_CAST")
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 = clazz.getMethod("getA").invoke(i)
assertEquals(1, (arr as Array<Int>)[0])
assertEquals(2, arr[1])
assertEquals(3, arr[2])
assertEquals("$className{a=[1, 2, 3]}", i.toString())
}
@Test
@Suppress("UNCHECKED_CAST")
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
@Suppress("UNCHECKED_CAST")
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
@Suppress("UNCHECKED_CAST")
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 = clazz.getMethod("getA").invoke(i) as Array<String>
assertEquals("toast", arr[0])
assertEquals("butter", arr[1])
assertEquals("jam", arr[2])
}
@Test
@Suppress("UNCHECKED_CAST")
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 = clazz.getMethod("getA").invoke(i) as Array<String>
val arr2 = clazz.getMethod("getC").invoke(i) as Array<String>
assertEquals("bread", arr1[0])
assertEquals("spread", arr1[1])
assertEquals("cheese", arr1[2])
assertEquals("and on the side", clazz.getMethod("getB").invoke(i))
assertEquals("some pickles", arr2[0])
assertEquals("some fries", arr2[1])
}
@Test
fun `nullable sets annotations`() {
val className = "iEnjoyJam"
val schema = ClassSchema(
"gen.$className",
mapOf("a" to NullableField(String::class.java),
"b" to NonNullableField(String::class.java)))
val clazz = cc.build(schema)
assertEquals (2, clazz.declaredFields.size)
assertEquals (1, clazz.getDeclaredField("a").annotations.size)
assertEquals(Nullable::class.java, clazz.getDeclaredField("a").annotations[0].annotationClass.java)
assertEquals (1, clazz.getDeclaredField("b").annotations.size)
assertEquals(Nonnull::class.java, clazz.getDeclaredField("b").annotations[0].annotationClass.java)
assertEquals (1, clazz.getMethod("getA").annotations.size)
assertEquals(Nullable::class.java, clazz.getMethod("getA").annotations[0].annotationClass.java)
assertEquals (1, clazz.getMethod("getB").annotations.size)
assertEquals(Nonnull::class.java, clazz.getMethod("getB").annotations[0].annotationClass.java)
}
@Test
fun beanTest() {
val schema = ClassSchema(
"pantsPantsPants",
mapOf("a" to NonNullableField(Integer::class.java)))
val clazz = cc.build(schema)
val descriptors = Introspector.getBeanInfo(clazz).propertyDescriptors
assertEquals(2, descriptors.size)
assertNotEquals(null, descriptors.find { it.name == "a" })
assertNotEquals(null, descriptors.find { it.name == "class" })
}
}

View File

@ -0,0 +1,43 @@
package net.corda.nodeapi.internal.serialization.carpenter
import net.corda.nodeapi.internal.serialization.amqp.*
import net.corda.nodeapi.internal.serialization.amqp.Schema
import net.corda.nodeapi.internal.serialization.amqp.Field
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)
}
open class AmqpCarpenterBase {
var factory = SerializerFactory()
fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz)
fun testName(): String = Thread.currentThread().stackTrace[2].methodName
@Suppress("NOTHING_TO_INLINE")
inline fun classTestName(clazz: String) = "${this.javaClass.name}\$${testName()}\$$clazz"
}

View File

@ -0,0 +1,285 @@
package net.corda.nodeapi.internal.serialization.carpenter
import net.corda.core.serialization.CordaSerializable
import net.corda.nodeapi.internal.serialization.amqp.CompositeType
import net.corda.nodeapi.internal.serialization.amqp.DeserializationInput
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() {
@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()
// 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()
}
@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()
assertEquals(1, carpenterSchema.size)
val metaCarpenter = MetaCarpenter(carpenterSchema)
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()
// 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)
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()
}
@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()
}
@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())
}
/*
* TODO serializer doesn't support inheritnace at the moment, when it does this should work
@Test
fun `inheritance`() {
val testA = 10
val testB = 20
@CordaSerializable
open class A(open val a: Int)
@CordaSerializable
class B(override val a: Int, val b: Int) : A (a)
val b = B(testA, testB)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b))
assert(obj.obj is B)
val carpenterSchema = obj.envelope.schema.mangleNames(listOf(classTestName("A"), classTestName("B")))
val metaCarpenter = TestMetaCarpenter(carpenterSchema.carpenterSchema())
assertEquals(1, metaCarpenter.schemas.carpenterSchemas.size)
assertEquals(mangleNames(classTestName("B")), metaCarpenter.schemas.carpenterSchemas.first().name)
assertEquals(1, metaCarpenter.schemas.dependencies.size)
assertTrue(mangleNames(classTestName("A")) in metaCarpenter.schemas.dependencies)
}
*/
}

View File

@ -0,0 +1,459 @@
package net.corda.nodeapi.internal.serialization.carpenter
import net.corda.core.serialization.CordaSerializable
import net.corda.nodeapi.internal.serialization.amqp.DeserializationInput
import org.junit.Test
import kotlin.test.*
@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() {
@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()
// 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()
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 = ClassCarpenter().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()
assertEquals(0, l1.size)
val mangleSchema = serSchema.mangleNames(listOf(classTestName("A")))
val aName = mangleName(classTestName("A"))
val l2 = mangleSchema.carpenterSchema()
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 = ClassCarpenter().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()
// 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()
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 = ClassCarpenter().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()
// 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()
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 = ClassCarpenter().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()
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 = ClassCarpenter()
val cc2 = ClassCarpenter()
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()
}
@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()
// 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)
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()
// 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)
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()
// 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)
mc.build()
assertEquals(0, mc.schemas.carpenterSchemas.size)
assertEquals(0, mc.schemas.dependencies.size)
assertEquals(0, mc.schemas.dependsOn.size)
assertEquals(3, mc.objects.size)
assertTrue(aName in mc.objects)
assertTrue(iName in mc.objects)
assertTrue(iiiName in mc.objects)
}
}

View File

@ -0,0 +1,95 @@
package net.corda.nodeapi.internal.serialization.carpenter
import net.corda.core.serialization.CordaSerializable
import net.corda.nodeapi.internal.serialization.amqp.CompositeType
import net.corda.nodeapi.internal.serialization.amqp.DeserializationInput
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
class MultiMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() {
@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 = CarpenterSchemas.newInstance()
amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true)
assertEquals(1, carpenterSchema.size)
val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }
assertNotEquals(null, aSchema)
val pinochio = ClassCarpenter().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 = CarpenterSchemas.newInstance()
amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true)
assertEquals(1, carpenterSchema.size)
val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }
assertNotEquals(null, aSchema)
val pinochio = ClassCarpenter().build(aSchema!!)
val p = pinochio.constructors[0].newInstance(testA, testB)
assertEquals(pinochio.getMethod("getA").invoke(p), amqpObj.a)
assertEquals(pinochio.getMethod("getB").invoke(p), amqpObj.b)
}
}

View File

@ -0,0 +1,197 @@
package net.corda.nodeapi.internal.serialization.carpenter
import net.corda.core.serialization.CordaSerializable
import net.corda.nodeapi.internal.serialization.amqp.CompositeType
import net.corda.nodeapi.internal.serialization.amqp.DeserializationInput
import org.junit.Test
import kotlin.test.assertEquals
class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() {
@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 = CarpenterSchemas.newInstance()
amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true)
val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!!
val aBuilder = ClassCarpenter().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 = CarpenterSchemas.newInstance()
amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true)
val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!!
val aBuilder = ClassCarpenter().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 = CarpenterSchemas.newInstance()
amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true)
val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!!
val aBuilder = ClassCarpenter().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 = CarpenterSchemas.newInstance()
amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true)
val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!!
val aBuilder = ClassCarpenter().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 = CarpenterSchemas.newInstance()
amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true)
val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!!
val aBuilder = ClassCarpenter().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: Float = 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 = CarpenterSchemas.newInstance()
amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true)
val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!!
val aBuilder = ClassCarpenter().build(aSchema)
val p = aBuilder.constructors[0].newInstance(test)
assertEquals(aBuilder.getMethod("getA").invoke(p), amqpObj.a)
}
}