mirror of
https://github.com/corda/corda.git
synced 2025-02-18 16:40:55 +00:00
Push unit tests that needed some contract functionality into core by embedding the minimal contract support needed.
This commit is contained in:
parent
a7c0296f6b
commit
69cba98ddc
@ -80,5 +80,4 @@ dependencies {
|
||||
compile project(':core')
|
||||
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile "commons-fileupload:commons-fileupload:1.3.1"
|
||||
}
|
@ -21,9 +21,15 @@ repositories {
|
||||
}
|
||||
}
|
||||
|
||||
//noinspection GroovyAssignabilityCheck
|
||||
configurations {
|
||||
quasar
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile 'org.assertj:assertj-core:3.4.1'
|
||||
testCompile "commons-fileupload:commons-fileupload:1.3.1"
|
||||
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
@ -57,4 +63,40 @@ dependencies {
|
||||
|
||||
// For JSON
|
||||
compile "com.fasterxml.jackson.core:jackson-databind:2.5.5"
|
||||
|
||||
// Quasar: for the bytecode rewriting for state machines.
|
||||
quasar "co.paralleluniverse:quasar-core:${quasar_version}:jdk8@jar"
|
||||
}
|
||||
|
||||
|
||||
tasks.withType(Test) {
|
||||
jvmArgs "-javaagent:${configurations.quasar.singleFile}"
|
||||
jvmArgs "-Dco.paralleluniverse.fibers.verifyInstrumentation"
|
||||
}
|
||||
tasks.withType(JavaExec) {
|
||||
jvmArgs "-javaagent:${configurations.quasar.singleFile}"
|
||||
jvmArgs "-Dco.paralleluniverse.fibers.verifyInstrumentation"
|
||||
}
|
||||
|
||||
|
||||
// These lines tell gradle to run the Quasar suspendables scanner to look for unannotated super methods
|
||||
// that have @Suspendable sub implementations. These tend to cause NPEs and are not caught by the verifier
|
||||
// NOTE: need to make sure the output isn't on the classpath or every other run it generates empty results, so
|
||||
// we explicitly delete to avoid that happening. We also need to turn off what seems to be a spurious warning in the IDE
|
||||
//
|
||||
// TODO: Make this task incremental, as it can be quite slow.
|
||||
|
||||
//noinspection GroovyAssignabilityCheck
|
||||
task quasarScan(dependsOn: ['classes']) << {
|
||||
ant.taskdef(name:'scanSuspendables', classname:'co.paralleluniverse.fibers.instrument.SuspendablesScanner',
|
||||
classpath: "${sourceSets.main.output.classesDir}:${sourceSets.main.output.resourcesDir}:${configurations.runtime.asPath}")
|
||||
delete "$sourceSets.main.output.resourcesDir/META-INF/suspendables", "$sourceSets.main.output.resourcesDir/META-INF/suspendable-supers"
|
||||
ant.scanSuspendables(
|
||||
auto:false,
|
||||
suspendablesFile: "$sourceSets.main.output.resourcesDir/META-INF/suspendables",
|
||||
supersFile: "$sourceSets.main.output.resourcesDir/META-INF/suspendable-supers") {
|
||||
fileset(dir: sourceSets.main.output.classesDir)
|
||||
}
|
||||
}
|
||||
|
||||
jar.dependsOn quasarScan
|
@ -1,16 +1,43 @@
|
||||
package core.contracts
|
||||
|
||||
import contracts.Cash
|
||||
import contracts.testing.`owned by`
|
||||
import core.crypto.Party
|
||||
import core.crypto.SecureHash
|
||||
import core.node.services.testing.MockStorageService
|
||||
import core.testing.*
|
||||
import org.junit.Test
|
||||
import java.security.PublicKey
|
||||
import java.security.SecureRandom
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertNotEquals
|
||||
|
||||
val TEST_PROGRAM_ID = TransactionGroupTests.TestCash()
|
||||
|
||||
class TransactionGroupTests {
|
||||
val A_THOUSAND_POUNDS = Cash.State(MINI_CORP.ref(1, 2, 3), 1000.POUNDS, MINI_CORP_PUBKEY, DUMMY_NOTARY)
|
||||
val A_THOUSAND_POUNDS = TestCash.State(MINI_CORP.ref(1, 2, 3), 1000.POUNDS, MINI_CORP_PUBKEY, DUMMY_NOTARY)
|
||||
|
||||
class TestCash : Contract {
|
||||
override val legalContractReference = SecureHash.sha256("TestCash")
|
||||
|
||||
override fun verify(tx: TransactionForVerification) {
|
||||
}
|
||||
|
||||
data class State(
|
||||
val deposit: PartyAndReference,
|
||||
val amount: Amount,
|
||||
override val owner: PublicKey,
|
||||
override val notary: Party) : OwnableState {
|
||||
override val contract: Contract = TEST_PROGRAM_ID
|
||||
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
|
||||
}
|
||||
interface Commands : CommandData {
|
||||
class Move() : TypeOnlyCommandData(), Commands
|
||||
data class Issue(val nonce: Long = SecureRandom.getInstanceStrong().nextLong()) : Commands
|
||||
data class Exit(val amount: Amount) : Commands
|
||||
}
|
||||
}
|
||||
|
||||
infix fun TestCash.State.`owned by`(owner: PublicKey) = copy(owner = owner)
|
||||
|
||||
@Test
|
||||
fun success() {
|
||||
@ -22,13 +49,13 @@ class TransactionGroupTests {
|
||||
transaction {
|
||||
input("£1000")
|
||||
output("alice's £1000") { A_THOUSAND_POUNDS `owned by` ALICE_PUBKEY }
|
||||
arg(MINI_CORP_PUBKEY) { Cash.Commands.Move() }
|
||||
arg(MINI_CORP_PUBKEY) { TestCash.Commands.Move() }
|
||||
}
|
||||
|
||||
transaction {
|
||||
input("alice's £1000")
|
||||
arg(ALICE_PUBKEY) { Cash.Commands.Move() }
|
||||
arg(MINI_CORP_PUBKEY) { Cash.Commands.Exit(1000.POUNDS) }
|
||||
arg(ALICE_PUBKEY) { TestCash.Commands.Move() }
|
||||
arg(MINI_CORP_PUBKEY) { TestCash.Commands.Exit(1000.POUNDS) }
|
||||
}
|
||||
|
||||
verify()
|
||||
@ -40,7 +67,7 @@ class TransactionGroupTests {
|
||||
transactionGroup {
|
||||
val t = transaction {
|
||||
output("cash") { A_THOUSAND_POUNDS }
|
||||
arg(MINI_CORP_PUBKEY) { Cash.Commands.Issue() }
|
||||
arg(MINI_CORP_PUBKEY) { TestCash.Commands.Issue() }
|
||||
}
|
||||
|
||||
val conflict1 = transaction {
|
||||
@ -48,7 +75,7 @@ class TransactionGroupTests {
|
||||
val HALF = A_THOUSAND_POUNDS.copy(amount = 500.POUNDS) `owned by` BOB_PUBKEY
|
||||
output { HALF }
|
||||
output { HALF }
|
||||
arg(MINI_CORP_PUBKEY) { Cash.Commands.Move() }
|
||||
arg(MINI_CORP_PUBKEY) { TestCash.Commands.Move() }
|
||||
}
|
||||
|
||||
verify()
|
||||
@ -59,7 +86,7 @@ class TransactionGroupTests {
|
||||
val HALF = A_THOUSAND_POUNDS.copy(amount = 500.POUNDS) `owned by` ALICE_PUBKEY
|
||||
output { HALF }
|
||||
output { HALF }
|
||||
arg(MINI_CORP_PUBKEY) { Cash.Commands.Move() }
|
||||
arg(MINI_CORP_PUBKEY) { TestCash.Commands.Move() }
|
||||
}
|
||||
|
||||
assertNotEquals(conflict1, conflict2)
|
||||
@ -78,7 +105,7 @@ class TransactionGroupTests {
|
||||
val tg = transactionGroup {
|
||||
transaction {
|
||||
output("cash") { A_THOUSAND_POUNDS }
|
||||
arg(MINI_CORP_PUBKEY) { Cash.Commands.Issue() }
|
||||
arg(MINI_CORP_PUBKEY) { TestCash.Commands.Issue() }
|
||||
}
|
||||
|
||||
transaction {
|
||||
@ -93,7 +120,7 @@ class TransactionGroupTests {
|
||||
tg.txns += TransactionBuilder().apply {
|
||||
addInputState(input)
|
||||
addOutputState(A_THOUSAND_POUNDS)
|
||||
addCommand(Cash.Commands.Move(), BOB_PUBKEY)
|
||||
addCommand(TestCash.Commands.Move(), BOB_PUBKEY)
|
||||
}.toWireTransaction()
|
||||
|
||||
val e = assertFailsWith(TransactionResolutionException::class) {
|
||||
@ -114,7 +141,7 @@ class TransactionGroupTests {
|
||||
input("£1000")
|
||||
input("£1000")
|
||||
output { A_THOUSAND_POUNDS.copy(amount = A_THOUSAND_POUNDS.amount * 2) }
|
||||
arg(MINI_CORP_PUBKEY) { Cash.Commands.Move() }
|
||||
arg(MINI_CORP_PUBKEY) { TestCash.Commands.Move() }
|
||||
}
|
||||
|
||||
assertFailsWith(TransactionConflictException::class) {
|
||||
@ -128,19 +155,19 @@ class TransactionGroupTests {
|
||||
val signedTxns: List<SignedTransaction> = transactionGroup {
|
||||
transaction {
|
||||
output("£1000") { A_THOUSAND_POUNDS }
|
||||
arg(MINI_CORP_PUBKEY) { Cash.Commands.Issue() }
|
||||
arg(MINI_CORP_PUBKEY) { TestCash.Commands.Issue() }
|
||||
}
|
||||
|
||||
transaction {
|
||||
input("£1000")
|
||||
output("alice's £1000") { A_THOUSAND_POUNDS `owned by` ALICE_PUBKEY }
|
||||
arg(MINI_CORP_PUBKEY) { Cash.Commands.Move() }
|
||||
arg(MINI_CORP_PUBKEY) { TestCash.Commands.Move() }
|
||||
}
|
||||
|
||||
transaction {
|
||||
input("alice's £1000")
|
||||
arg(ALICE_PUBKEY) { Cash.Commands.Move() }
|
||||
arg(MINI_CORP_PUBKEY) { Cash.Commands.Exit(1000.POUNDS) }
|
||||
arg(ALICE_PUBKEY) { TestCash.Commands.Move() }
|
||||
arg(MINI_CORP_PUBKEY) { TestCash.Commands.Exit(1000.POUNDS) }
|
||||
}
|
||||
}.signAll()
|
||||
|
@ -1,14 +1,8 @@
|
||||
package core.node
|
||||
|
||||
import contracts.DUMMY_PROGRAM_ID
|
||||
import contracts.DummyContract
|
||||
import core.contracts.Contract
|
||||
import core.contracts.ContractState
|
||||
import core.contracts.PartyAndReference
|
||||
import core.contracts.TransactionBuilder
|
||||
import core.contracts.*
|
||||
import core.crypto.Party
|
||||
import core.crypto.SecureHash
|
||||
import core.node.AttachmentsClassLoader
|
||||
import core.node.services.AttachmentStorage
|
||||
import core.node.services.testing.MockAttachmentStorage
|
||||
import core.serialization.*
|
||||
@ -30,11 +24,36 @@ interface DummyContractBackdoor {
|
||||
fun inspectState(state: ContractState): Int
|
||||
}
|
||||
|
||||
val ATTACHMENT_TEST_PROGRAM_ID = AttachmentClassLoaderTests.AttachmentDummyContract()
|
||||
|
||||
class AttachmentClassLoaderTests {
|
||||
companion object {
|
||||
val ISOLATED_CONTRACTS_JAR_PATH = AttachmentClassLoaderTests::class.java.getResource("isolated.jar")
|
||||
}
|
||||
|
||||
class AttachmentDummyContract : Contract {
|
||||
class State(val magicNumber: Int = 0,
|
||||
override val notary: Party) : ContractState {
|
||||
override val contract = ATTACHMENT_TEST_PROGRAM_ID
|
||||
}
|
||||
|
||||
interface Commands : CommandData {
|
||||
class Create : TypeOnlyCommandData(), Commands
|
||||
}
|
||||
|
||||
override fun verify(tx: TransactionForVerification) {
|
||||
// Always accepts.
|
||||
}
|
||||
|
||||
// The "empty contract"
|
||||
override val legalContractReference: SecureHash = SecureHash.sha256("")
|
||||
|
||||
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder {
|
||||
val state = State(magicNumber, notary)
|
||||
return TransactionBuilder().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
|
||||
@ -127,7 +146,7 @@ class AttachmentClassLoaderTests {
|
||||
|
||||
@Test
|
||||
fun `verify that contract DummyContract is in classPath`() {
|
||||
val contractClass = Class.forName("contracts.DummyContract")
|
||||
val contractClass = Class.forName("core.node.AttachmentClassLoaderTests\$AttachmentDummyContract")
|
||||
val contract = contractClass.newInstance() as Contract
|
||||
|
||||
assertNotNull(contract)
|
||||
@ -190,13 +209,13 @@ class AttachmentClassLoaderTests {
|
||||
|
||||
@Test
|
||||
fun `test serialization of WireTransaction with statically loaded contract`() {
|
||||
val tx = DUMMY_PROGRAM_ID.generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY)
|
||||
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] as DummyContract.State).magicNumber)
|
||||
assertEquals(42, (copiedWireTransaction.outputs[0] as AttachmentDummyContract.State).magicNumber)
|
||||
}
|
||||
|
||||
@Test
|
@ -74,6 +74,7 @@ class SerializationTokenTest {
|
||||
|
||||
@Test(expected = IllegalStateException::class)
|
||||
fun `unannotated throws`() {
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
val tokenizableBefore = UnannotatedSerializeAsSingletonToken()
|
||||
}
|
||||
}
|
@ -1,22 +1,48 @@
|
||||
package core.serialization
|
||||
|
||||
import contracts.Cash
|
||||
import core.*
|
||||
import core.contracts.*
|
||||
import core.crypto.Party
|
||||
import core.crypto.SecureHash
|
||||
import core.node.services.testing.MockStorageService
|
||||
import core.seconds
|
||||
import core.testing.*
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.security.PublicKey
|
||||
import java.security.SecureRandom
|
||||
import java.security.SignatureException
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
val TEST_PROGRAM_ID = TransactionSerializationTests.TestCash()
|
||||
|
||||
class TransactionSerializationTests {
|
||||
class TestCash : Contract {
|
||||
override val legalContractReference = SecureHash.sha256("TestCash")
|
||||
|
||||
override fun verify(tx: TransactionForVerification) {
|
||||
}
|
||||
|
||||
data class State(
|
||||
val deposit: PartyAndReference,
|
||||
val amount: Amount,
|
||||
override val owner: PublicKey,
|
||||
override val notary: Party) : OwnableState {
|
||||
override val contract: Contract = TEST_PROGRAM_ID
|
||||
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
|
||||
}
|
||||
interface Commands : CommandData {
|
||||
class Move() : TypeOnlyCommandData(), Commands
|
||||
data class Issue(val nonce: Long = SecureRandom.getInstanceStrong().nextLong()) : Commands
|
||||
data class Exit(val amount: Amount) : Commands
|
||||
}
|
||||
}
|
||||
|
||||
// Simple TX that takes 1000 pounds from me and sends 600 to someone else (with 400 change).
|
||||
// It refers to a fake TX/state that we don't bother creating here.
|
||||
val depositRef = MINI_CORP.ref(1)
|
||||
val outputState = Cash.State(depositRef, 600.POUNDS, DUMMY_PUBKEY_1, DUMMY_NOTARY)
|
||||
val changeState = Cash.State(depositRef, 400.POUNDS, TestUtils.keypair.public, DUMMY_NOTARY)
|
||||
val outputState = TestCash.State(depositRef, 600.POUNDS, DUMMY_PUBKEY_1, DUMMY_NOTARY)
|
||||
val changeState = TestCash.State(depositRef, 400.POUNDS, TestUtils.keypair.public, DUMMY_NOTARY)
|
||||
|
||||
val fakeStateRef = generateStateRef()
|
||||
lateinit var tx: TransactionBuilder
|
||||
@ -24,7 +50,7 @@ class TransactionSerializationTests {
|
||||
@Before
|
||||
fun setup() {
|
||||
tx = TransactionBuilder().withItems(
|
||||
fakeStateRef, outputState, changeState, Command(Cash.Commands.Move(), arrayListOf(TestUtils.keypair.public))
|
||||
fakeStateRef, outputState, changeState, Command(TestCash.Commands.Move(), arrayListOf(TestUtils.keypair.public))
|
||||
)
|
||||
}
|
||||
|
||||
@ -61,7 +87,7 @@ class TransactionSerializationTests {
|
||||
// If the signature was replaced in transit, we don't like it.
|
||||
assertFailsWith(SignatureException::class) {
|
||||
val tx2 = TransactionBuilder().withItems(fakeStateRef, outputState, changeState,
|
||||
Command(Cash.Commands.Move(), TestUtils.keypair2.public))
|
||||
Command(TestCash.Commands.Move(), TestUtils.keypair2.public))
|
||||
tx2.signWith(TestUtils.keypair2)
|
||||
|
||||
signedTX.copy(sigs = tx2.toSignedTransaction().sigs).verify()
|
Loading…
x
Reference in New Issue
Block a user