Push unit tests that needed some contract functionality into core by embedding the minimal contract support needed.

This commit is contained in:
Matthew Nesbit 2016-05-19 14:01:38 +01:00
parent a7c0296f6b
commit 69cba98ddc
7 changed files with 148 additions and 34 deletions

View File

@ -80,5 +80,4 @@ dependencies {
compile project(':core') compile project(':core')
testCompile 'junit:junit:4.12' testCompile 'junit:junit:4.12'
testCompile "commons-fileupload:commons-fileupload:1.3.1"
} }

View File

@ -21,9 +21,15 @@ repositories {
} }
} }
//noinspection GroovyAssignabilityCheck
configurations {
quasar
}
dependencies { dependencies {
testCompile 'junit:junit:4.12' testCompile 'junit:junit:4.12'
testCompile 'org.assertj:assertj-core:3.4.1' 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-stdlib:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
@ -57,4 +63,40 @@ dependencies {
// For JSON // For JSON
compile "com.fasterxml.jackson.core:jackson-databind:2.5.5" 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

View File

@ -1,16 +1,43 @@
package core.contracts package core.contracts
import contracts.Cash import core.crypto.Party
import contracts.testing.`owned by` import core.crypto.SecureHash
import core.node.services.testing.MockStorageService import core.node.services.testing.MockStorageService
import core.testing.* import core.testing.*
import org.junit.Test import org.junit.Test
import java.security.PublicKey
import java.security.SecureRandom
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
import kotlin.test.assertNotEquals import kotlin.test.assertNotEquals
val TEST_PROGRAM_ID = TransactionGroupTests.TestCash()
class TransactionGroupTests { 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 @Test
fun success() { fun success() {
@ -22,13 +49,13 @@ class TransactionGroupTests {
transaction { transaction {
input("£1000") input("£1000")
output("alice's £1000") { A_THOUSAND_POUNDS `owned by` ALICE_PUBKEY } 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 { transaction {
input("alice's £1000") input("alice's £1000")
arg(ALICE_PUBKEY) { Cash.Commands.Move() } arg(ALICE_PUBKEY) { TestCash.Commands.Move() }
arg(MINI_CORP_PUBKEY) { Cash.Commands.Exit(1000.POUNDS) } arg(MINI_CORP_PUBKEY) { TestCash.Commands.Exit(1000.POUNDS) }
} }
verify() verify()
@ -40,7 +67,7 @@ class TransactionGroupTests {
transactionGroup { transactionGroup {
val t = transaction { val t = transaction {
output("cash") { A_THOUSAND_POUNDS } output("cash") { A_THOUSAND_POUNDS }
arg(MINI_CORP_PUBKEY) { Cash.Commands.Issue() } arg(MINI_CORP_PUBKEY) { TestCash.Commands.Issue() }
} }
val conflict1 = transaction { val conflict1 = transaction {
@ -48,7 +75,7 @@ class TransactionGroupTests {
val HALF = A_THOUSAND_POUNDS.copy(amount = 500.POUNDS) `owned by` BOB_PUBKEY val HALF = A_THOUSAND_POUNDS.copy(amount = 500.POUNDS) `owned by` BOB_PUBKEY
output { HALF } output { HALF }
output { HALF } output { HALF }
arg(MINI_CORP_PUBKEY) { Cash.Commands.Move() } arg(MINI_CORP_PUBKEY) { TestCash.Commands.Move() }
} }
verify() verify()
@ -59,7 +86,7 @@ class TransactionGroupTests {
val HALF = A_THOUSAND_POUNDS.copy(amount = 500.POUNDS) `owned by` ALICE_PUBKEY val HALF = A_THOUSAND_POUNDS.copy(amount = 500.POUNDS) `owned by` ALICE_PUBKEY
output { HALF } output { HALF }
output { HALF } output { HALF }
arg(MINI_CORP_PUBKEY) { Cash.Commands.Move() } arg(MINI_CORP_PUBKEY) { TestCash.Commands.Move() }
} }
assertNotEquals(conflict1, conflict2) assertNotEquals(conflict1, conflict2)
@ -78,7 +105,7 @@ class TransactionGroupTests {
val tg = transactionGroup { val tg = transactionGroup {
transaction { transaction {
output("cash") { A_THOUSAND_POUNDS } output("cash") { A_THOUSAND_POUNDS }
arg(MINI_CORP_PUBKEY) { Cash.Commands.Issue() } arg(MINI_CORP_PUBKEY) { TestCash.Commands.Issue() }
} }
transaction { transaction {
@ -93,7 +120,7 @@ class TransactionGroupTests {
tg.txns += TransactionBuilder().apply { tg.txns += TransactionBuilder().apply {
addInputState(input) addInputState(input)
addOutputState(A_THOUSAND_POUNDS) addOutputState(A_THOUSAND_POUNDS)
addCommand(Cash.Commands.Move(), BOB_PUBKEY) addCommand(TestCash.Commands.Move(), BOB_PUBKEY)
}.toWireTransaction() }.toWireTransaction()
val e = assertFailsWith(TransactionResolutionException::class) { val e = assertFailsWith(TransactionResolutionException::class) {
@ -114,7 +141,7 @@ class TransactionGroupTests {
input("£1000") input("£1000")
input("£1000") input("£1000")
output { A_THOUSAND_POUNDS.copy(amount = A_THOUSAND_POUNDS.amount * 2) } 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) { assertFailsWith(TransactionConflictException::class) {
@ -128,19 +155,19 @@ class TransactionGroupTests {
val signedTxns: List<SignedTransaction> = transactionGroup { val signedTxns: List<SignedTransaction> = transactionGroup {
transaction { transaction {
output("£1000") { A_THOUSAND_POUNDS } output("£1000") { A_THOUSAND_POUNDS }
arg(MINI_CORP_PUBKEY) { Cash.Commands.Issue() } arg(MINI_CORP_PUBKEY) { TestCash.Commands.Issue() }
} }
transaction { transaction {
input("£1000") input("£1000")
output("alice's £1000") { A_THOUSAND_POUNDS `owned by` ALICE_PUBKEY } 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 { transaction {
input("alice's £1000") input("alice's £1000")
arg(ALICE_PUBKEY) { Cash.Commands.Move() } arg(ALICE_PUBKEY) { TestCash.Commands.Move() }
arg(MINI_CORP_PUBKEY) { Cash.Commands.Exit(1000.POUNDS) } arg(MINI_CORP_PUBKEY) { TestCash.Commands.Exit(1000.POUNDS) }
} }
}.signAll() }.signAll()

View File

@ -1,14 +1,8 @@
package core.node package core.node
import contracts.DUMMY_PROGRAM_ID import core.contracts.*
import contracts.DummyContract
import core.contracts.Contract
import core.contracts.ContractState
import core.contracts.PartyAndReference
import core.contracts.TransactionBuilder
import core.crypto.Party import core.crypto.Party
import core.crypto.SecureHash import core.crypto.SecureHash
import core.node.AttachmentsClassLoader
import core.node.services.AttachmentStorage import core.node.services.AttachmentStorage
import core.node.services.testing.MockAttachmentStorage import core.node.services.testing.MockAttachmentStorage
import core.serialization.* import core.serialization.*
@ -30,11 +24,36 @@ interface DummyContractBackdoor {
fun inspectState(state: ContractState): Int fun inspectState(state: ContractState): Int
} }
val ATTACHMENT_TEST_PROGRAM_ID = AttachmentClassLoaderTests.AttachmentDummyContract()
class AttachmentClassLoaderTests { class AttachmentClassLoaderTests {
companion object { companion object {
val ISOLATED_CONTRACTS_JAR_PATH = AttachmentClassLoaderTests::class.java.getResource("isolated.jar") 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) } 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 // These ClassLoaders work together to load 'AnotherDummyContract' in a disposable way, such that even though
@ -127,7 +146,7 @@ class AttachmentClassLoaderTests {
@Test @Test
fun `verify that contract DummyContract is in classPath`() { 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 val contract = contractClass.newInstance() as Contract
assertNotNull(contract) assertNotNull(contract)
@ -190,13 +209,13 @@ class AttachmentClassLoaderTests {
@Test @Test
fun `test serialization of WireTransaction with statically loaded contract`() { 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 wireTransaction = tx.toWireTransaction()
val bytes = wireTransaction.serialize() val bytes = wireTransaction.serialize()
val copiedWireTransaction = bytes.deserialize() val copiedWireTransaction = bytes.deserialize()
assertEquals(1, copiedWireTransaction.outputs.size) assertEquals(1, copiedWireTransaction.outputs.size)
assertEquals(42, (copiedWireTransaction.outputs[0] as DummyContract.State).magicNumber) assertEquals(42, (copiedWireTransaction.outputs[0] as AttachmentDummyContract.State).magicNumber)
} }
@Test @Test

View File

@ -74,6 +74,7 @@ class SerializationTokenTest {
@Test(expected = IllegalStateException::class) @Test(expected = IllegalStateException::class)
fun `unannotated throws`() { fun `unannotated throws`() {
@Suppress("UNUSED_VARIABLE")
val tokenizableBefore = UnannotatedSerializeAsSingletonToken() val tokenizableBefore = UnannotatedSerializeAsSingletonToken()
} }
} }

View File

@ -1,22 +1,48 @@
package core.serialization package core.serialization
import contracts.Cash
import core.*
import core.contracts.* import core.contracts.*
import core.crypto.Party
import core.crypto.SecureHash
import core.node.services.testing.MockStorageService import core.node.services.testing.MockStorageService
import core.seconds
import core.testing.* import core.testing.*
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import java.security.PublicKey
import java.security.SecureRandom
import java.security.SignatureException import java.security.SignatureException
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
val TEST_PROGRAM_ID = TransactionSerializationTests.TestCash()
class TransactionSerializationTests { class TransactionSerializationTests {
// Simple TX that takes 1000 pounds from me and sends 600 to someone else (with 400 change). 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. // It refers to a fake TX/state that we don't bother creating here.
val depositRef = MINI_CORP.ref(1) val depositRef = MINI_CORP.ref(1)
val outputState = Cash.State(depositRef, 600.POUNDS, DUMMY_PUBKEY_1, DUMMY_NOTARY) val outputState = TestCash.State(depositRef, 600.POUNDS, DUMMY_PUBKEY_1, DUMMY_NOTARY)
val changeState = Cash.State(depositRef, 400.POUNDS, TestUtils.keypair.public, DUMMY_NOTARY) val changeState = TestCash.State(depositRef, 400.POUNDS, TestUtils.keypair.public, DUMMY_NOTARY)
val fakeStateRef = generateStateRef() val fakeStateRef = generateStateRef()
lateinit var tx: TransactionBuilder lateinit var tx: TransactionBuilder
@ -24,7 +50,7 @@ class TransactionSerializationTests {
@Before @Before
fun setup() { fun setup() {
tx = TransactionBuilder().withItems( 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. // If the signature was replaced in transit, we don't like it.
assertFailsWith(SignatureException::class) { assertFailsWith(SignatureException::class) {
val tx2 = TransactionBuilder().withItems(fakeStateRef, outputState, changeState, 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) tx2.signWith(TestUtils.keypair2)
signedTX.copy(sigs = tx2.toSignedTransaction().sigs).verify() signedTX.copy(sigs = tx2.toSignedTransaction().sigs).verify()