mirror of
https://github.com/corda/corda.git
synced 2025-06-18 23:28:21 +00:00
Remove Kryo from core module completely, including gradle dependencies (#1161)
This commit is contained in:
@ -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())
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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") })
|
||||
}
|
||||
}
|
@ -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])
|
||||
}}}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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" })
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
@ -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)
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
BIN
node-api/src/test/resources/net/corda/nodeapi/isolated.jar
Normal file
BIN
node-api/src/test/resources/net/corda/nodeapi/isolated.jar
Normal file
Binary file not shown.
Reference in New Issue
Block a user