mirror of
https://github.com/corda/corda.git
synced 2024-12-19 04:57:58 +00:00
core: Add LastLineShouldBeVerifiesOrFails, fix attachment primitive, Java interop
This commit is contained in:
parent
bdda3d239a
commit
a27f195b4f
@ -2,19 +2,24 @@ package com.r3corda.core.testing
|
||||
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.crypto.SecureHash
|
||||
import java.io.InputStream
|
||||
|
||||
interface OutputStateLookup {
|
||||
fun <State: ContractState> retrieveOutputStateAndRef(clazz: Class<State>, label: String): StateAndRef<State>
|
||||
}
|
||||
|
||||
|
||||
|
||||
interface LedgerDslInterpreter<out TransactionInterpreter: TransactionDslInterpreter> :
|
||||
interface LedgerDslInterpreter<Return, out TransactionInterpreter: TransactionDslInterpreter<Return>> :
|
||||
OutputStateLookup {
|
||||
fun transaction(transactionLabel: String?, dsl: TransactionDsl<TransactionInterpreter>.() -> Unit): WireTransaction
|
||||
fun nonVerifiedTransaction(transactionLabel: String?, dsl: TransactionDsl<TransactionInterpreter>.() -> Unit): WireTransaction
|
||||
fun tweak(dsl: LedgerDsl<TransactionInterpreter, LedgerDslInterpreter<TransactionInterpreter>>.() -> Unit)
|
||||
fun attachment(attachment: Attachment): SecureHash
|
||||
fun transaction(
|
||||
transactionLabel: String?,
|
||||
dsl: TransactionDsl<Return, TransactionInterpreter>.() -> Return
|
||||
): WireTransaction
|
||||
fun nonVerifiedTransaction(
|
||||
transactionLabel: String?,
|
||||
dsl: TransactionDsl<Return, TransactionInterpreter>.() -> Unit
|
||||
): WireTransaction
|
||||
fun tweak(dsl: LedgerDsl<Return, TransactionInterpreter, LedgerDslInterpreter<Return, TransactionInterpreter>>.() -> Unit)
|
||||
fun attachment(attachment: InputStream): SecureHash
|
||||
fun verifies()
|
||||
}
|
||||
|
||||
@ -24,13 +29,15 @@ interface LedgerDslInterpreter<out TransactionInterpreter: TransactionDslInterpr
|
||||
* covariance of the TransactionInterpreter parameter
|
||||
*/
|
||||
class LedgerDsl<
|
||||
out TransactionInterpreter: TransactionDslInterpreter,
|
||||
out LedgerInterpreter: LedgerDslInterpreter<TransactionInterpreter>
|
||||
> (val interpreter: LedgerInterpreter)
|
||||
: LedgerDslInterpreter<TransactionDslInterpreter> by interpreter {
|
||||
Return,
|
||||
out TransactionInterpreter: TransactionDslInterpreter<Return>,
|
||||
out LedgerInterpreter: LedgerDslInterpreter<Return, TransactionInterpreter>
|
||||
> (val interpreter: LedgerInterpreter
|
||||
) : LedgerDslInterpreter<Return, TransactionDslInterpreter<Return>> by interpreter {
|
||||
|
||||
fun transaction(dsl: TransactionDsl<TransactionDslInterpreter>.() -> Unit) = transaction(null, dsl)
|
||||
fun nonVerifiedTransaction(dsl: TransactionDsl<TransactionDslInterpreter>.() -> Unit) =
|
||||
fun transaction(dsl: TransactionDsl<Return, TransactionDslInterpreter<Return>>.() -> Return) =
|
||||
transaction(null, dsl)
|
||||
fun nonVerifiedTransaction(dsl: TransactionDsl<Return, TransactionDslInterpreter<Return>>.() -> Unit) =
|
||||
nonVerifiedTransaction(null, dsl)
|
||||
|
||||
inline fun <reified State: ContractState> String.outputStateAndRef(): StateAndRef<State> =
|
||||
@ -38,12 +45,4 @@ class LedgerDsl<
|
||||
inline fun <reified State: ContractState> String.output(): TransactionState<State> =
|
||||
outputStateAndRef<State>().state
|
||||
fun String.outputRef(): StateRef = outputStateAndRef<ContractState>().ref
|
||||
|
||||
fun TransactionDslInterpreter.input(state: ContractState) {
|
||||
val transaction = nonVerifiedTransaction {
|
||||
output { state }
|
||||
}
|
||||
input(transaction.outRef<ContractState>(0).ref)
|
||||
}
|
||||
fun TransactionDslInterpreter.input(stateClosure: () -> ContractState) = input(stateClosure())
|
||||
}
|
||||
|
@ -9,26 +9,23 @@ import com.r3corda.core.node.services.IdentityService
|
||||
import com.r3corda.core.node.services.StorageService
|
||||
import com.r3corda.core.node.services.testing.MockStorageService
|
||||
import com.r3corda.core.serialization.serialize
|
||||
import java.io.InputStream
|
||||
import java.security.KeyPair
|
||||
import java.security.PublicKey
|
||||
import java.util.*
|
||||
|
||||
inline fun ledger(
|
||||
fun ledger(
|
||||
identityService: IdentityService = MOCK_IDENTITY_SERVICE,
|
||||
storageService: StorageService = MockStorageService(),
|
||||
dsl: LedgerDsl<TestTransactionDslInterpreter, TestLedgerDslInterpreter>.() -> Unit
|
||||
): LedgerDsl<TestTransactionDslInterpreter, TestLedgerDslInterpreter> {
|
||||
val ledgerDsl = LedgerDsl(TestLedgerDslInterpreter(identityService, storageService))
|
||||
dsl(ledgerDsl)
|
||||
return ledgerDsl
|
||||
}
|
||||
dsl: LedgerDsl<LastLineShouldTestForVerifiesOrFails, TestTransactionDslInterpreter, TestLedgerDslInterpreter>.() -> Unit
|
||||
) = JavaTestHelpers.ledger(identityService, storageService, dsl)
|
||||
|
||||
@Deprecated(
|
||||
message = "ledger doesn't nest, use tweak",
|
||||
replaceWith = ReplaceWith("tweak"),
|
||||
level = DeprecationLevel.ERROR)
|
||||
fun TransactionDslInterpreter.ledger(
|
||||
dsl: LedgerDsl<TestTransactionDslInterpreter, TestLedgerDslInterpreter>.() -> Unit) {
|
||||
fun TransactionDslInterpreter<LastLineShouldTestForVerifiesOrFails>.ledger(
|
||||
dsl: LedgerDsl<LastLineShouldTestForVerifiesOrFails, TestTransactionDslInterpreter, TestLedgerDslInterpreter>.() -> Unit) {
|
||||
this.toString()
|
||||
dsl.toString()
|
||||
}
|
||||
@ -37,25 +34,34 @@ fun TransactionDslInterpreter.ledger(
|
||||
message = "ledger doesn't nest, use tweak",
|
||||
replaceWith = ReplaceWith("tweak"),
|
||||
level = DeprecationLevel.ERROR)
|
||||
fun LedgerDslInterpreter<TransactionDslInterpreter>.ledger(
|
||||
dsl: LedgerDsl<TestTransactionDslInterpreter, TestLedgerDslInterpreter>.() -> Unit) {
|
||||
fun LedgerDslInterpreter<LastLineShouldTestForVerifiesOrFails, TransactionDslInterpreter<LastLineShouldTestForVerifiesOrFails>>.ledger(
|
||||
dsl: LedgerDsl<LastLineShouldTestForVerifiesOrFails, TestTransactionDslInterpreter, TestLedgerDslInterpreter>.() -> Unit) {
|
||||
this.toString()
|
||||
dsl.toString()
|
||||
}
|
||||
|
||||
/** If you jumped here from a compiler error make sure the last line of your test tests for a transaction verify or fail
|
||||
* This is a dummy type that can only be instantiated by functions in this module. This way we can ensure that all tests
|
||||
* will have as the last line either an accept or a failure test. The name is deliberately long to help make sense of
|
||||
* the triggered diagnostic
|
||||
*/
|
||||
sealed class LastLineShouldTestForVerifiesOrFails {
|
||||
internal object Token: LastLineShouldTestForVerifiesOrFails()
|
||||
}
|
||||
|
||||
/**
|
||||
* This interpreter builds a transaction, and [TransactionDsl.verifies] that the resolved transaction is correct. Note
|
||||
* that transactions corresponding to input states are not verified. Use [LedgerDsl.verifies] for that.
|
||||
*/
|
||||
data class TestTransactionDslInterpreter(
|
||||
private val ledgerInterpreter: TestLedgerDslInterpreter,
|
||||
override val ledgerInterpreter: TestLedgerDslInterpreter,
|
||||
private val inputStateRefs: ArrayList<StateRef> = arrayListOf(),
|
||||
internal val outputStates: ArrayList<LabeledOutput> = arrayListOf(),
|
||||
private val attachments: ArrayList<SecureHash> = arrayListOf(),
|
||||
private val commands: ArrayList<Command> = arrayListOf(),
|
||||
private val signers: LinkedHashSet<PublicKey> = LinkedHashSet(),
|
||||
private val transactionType: TransactionType = TransactionType.General()
|
||||
) : TransactionDslInterpreter, OutputStateLookup {
|
||||
) : TransactionDslInterpreter<LastLineShouldTestForVerifiesOrFails>, OutputStateLookup by ledgerInterpreter {
|
||||
private fun copy(): TestTransactionDslInterpreter =
|
||||
TestTransactionDslInterpreter(
|
||||
ledgerInterpreter = ledgerInterpreter,
|
||||
@ -83,7 +89,7 @@ data class TestTransactionDslInterpreter(
|
||||
inputStateRefs.add(stateRef)
|
||||
}
|
||||
|
||||
override fun output(label: String?, notary: Party, contractState: ContractState) {
|
||||
override fun _output(label: String?, notary: Party, contractState: ContractState) {
|
||||
outputStates.add(LabeledOutput(label, TransactionState(contractState, notary)))
|
||||
}
|
||||
|
||||
@ -96,12 +102,13 @@ data class TestTransactionDslInterpreter(
|
||||
commands.add(Command(commandData, signers))
|
||||
}
|
||||
|
||||
override fun verifies() {
|
||||
override fun verifies(): LastLineShouldTestForVerifiesOrFails {
|
||||
val resolvedTransaction = ledgerInterpreter.resolveWireTransaction(toWireTransaction())
|
||||
resolvedTransaction.verify()
|
||||
return LastLineShouldTestForVerifiesOrFails.Token
|
||||
}
|
||||
|
||||
override fun failsWith(expectedMessage: String?) {
|
||||
override fun failsWith(expectedMessage: String?): LastLineShouldTestForVerifiesOrFails {
|
||||
val exceptionThrown = try {
|
||||
this.verifies()
|
||||
false
|
||||
@ -124,12 +131,16 @@ data class TestTransactionDslInterpreter(
|
||||
if (!exceptionThrown) {
|
||||
throw AssertionError("Expected exception but didn't get one")
|
||||
}
|
||||
|
||||
return LastLineShouldTestForVerifiesOrFails.Token
|
||||
}
|
||||
|
||||
override fun tweak(dsl: TransactionDsl<TransactionDslInterpreter>.() -> Unit) =
|
||||
dsl(TransactionDsl(copy()))
|
||||
|
||||
override fun <State: ContractState> retrieveOutputStateAndRef(clazz: Class<State>, label: String) = ledgerInterpreter.retrieveOutputStateAndRef(clazz, label)
|
||||
override fun tweak(
|
||||
dsl: TransactionDsl<
|
||||
LastLineShouldTestForVerifiesOrFails,
|
||||
TransactionDslInterpreter<LastLineShouldTestForVerifiesOrFails>
|
||||
>.() -> LastLineShouldTestForVerifiesOrFails
|
||||
) = dsl(TransactionDsl(copy()))
|
||||
}
|
||||
|
||||
class AttachmentResolutionException(attachmentId: SecureHash) :
|
||||
@ -141,7 +152,7 @@ data class TestLedgerDslInterpreter private constructor (
|
||||
internal val labelToOutputStateAndRefs: HashMap<String, StateAndRef<ContractState>> = HashMap(),
|
||||
private val transactionWithLocations: HashMap<SecureHash, WireTransactionWithLocation> = HashMap(),
|
||||
private val nonVerifiedTransactionWithLocations: HashMap<SecureHash, WireTransactionWithLocation> = HashMap()
|
||||
) : LedgerDslInterpreter<TestTransactionDslInterpreter> {
|
||||
) : LedgerDslInterpreter<LastLineShouldTestForVerifiesOrFails, TestTransactionDslInterpreter> {
|
||||
|
||||
val wireTransactions: List<WireTransaction> get() = transactionWithLocations.values.map { it.transaction }
|
||||
|
||||
@ -212,8 +223,9 @@ data class TestLedgerDslInterpreter private constructor (
|
||||
internal fun resolveAttachment(attachmentId: SecureHash): Attachment =
|
||||
storageService.attachments.openAttachment(attachmentId) ?: throw AttachmentResolutionException(attachmentId)
|
||||
|
||||
private fun interpretTransactionDsl(dsl: TransactionDsl<TestTransactionDslInterpreter>.() -> Unit):
|
||||
TestTransactionDslInterpreter {
|
||||
private fun <Return> interpretTransactionDsl(
|
||||
dsl: TransactionDsl<LastLineShouldTestForVerifiesOrFails, TestTransactionDslInterpreter>.() -> Return
|
||||
): TestTransactionDslInterpreter {
|
||||
val transactionInterpreter = TestTransactionDslInterpreter(this)
|
||||
dsl(TransactionDsl(transactionInterpreter))
|
||||
return transactionInterpreter
|
||||
@ -241,9 +253,9 @@ data class TestLedgerDslInterpreter private constructor (
|
||||
fun outputToLabel(state: ContractState): String? =
|
||||
labelToOutputStateAndRefs.filter { it.value.state.data == state }.keys.firstOrNull()
|
||||
|
||||
private fun recordTransactionWithTransactionMap(
|
||||
private fun <Return> recordTransactionWithTransactionMap(
|
||||
transactionLabel: String?,
|
||||
dsl: TransactionDsl<TestTransactionDslInterpreter>.() -> Unit,
|
||||
dsl: TransactionDsl<LastLineShouldTestForVerifiesOrFails, TestTransactionDslInterpreter>.() -> Return,
|
||||
transactionMap: HashMap<SecureHash, WireTransactionWithLocation> = HashMap()
|
||||
): WireTransaction {
|
||||
val transactionLocation = getCallerLocation(3)
|
||||
@ -263,20 +275,24 @@ data class TestLedgerDslInterpreter private constructor (
|
||||
return wireTransaction
|
||||
}
|
||||
|
||||
override fun transaction(transactionLabel: String?, dsl: TransactionDsl<TestTransactionDslInterpreter>.() -> Unit) =
|
||||
recordTransactionWithTransactionMap(transactionLabel, dsl, transactionWithLocations)
|
||||
override fun transaction(
|
||||
transactionLabel: String?,
|
||||
dsl: TransactionDsl<LastLineShouldTestForVerifiesOrFails, TestTransactionDslInterpreter>.() -> LastLineShouldTestForVerifiesOrFails
|
||||
) = recordTransactionWithTransactionMap(transactionLabel, dsl, transactionWithLocations)
|
||||
|
||||
override fun nonVerifiedTransaction(transactionLabel: String?, dsl: TransactionDsl<TestTransactionDslInterpreter>.() -> Unit) =
|
||||
override fun nonVerifiedTransaction(
|
||||
transactionLabel: String?,
|
||||
dsl: TransactionDsl<LastLineShouldTestForVerifiesOrFails, TestTransactionDslInterpreter>.() -> Unit
|
||||
) =
|
||||
recordTransactionWithTransactionMap(transactionLabel, dsl, nonVerifiedTransactionWithLocations)
|
||||
|
||||
override fun tweak(
|
||||
dsl: LedgerDsl<TestTransactionDslInterpreter,
|
||||
LedgerDslInterpreter<TestTransactionDslInterpreter>>.() -> Unit) =
|
||||
dsl: LedgerDsl<LastLineShouldTestForVerifiesOrFails, TestTransactionDslInterpreter,
|
||||
LedgerDslInterpreter<LastLineShouldTestForVerifiesOrFails, TestTransactionDslInterpreter>>.() -> Unit) =
|
||||
dsl(LedgerDsl(copy()))
|
||||
|
||||
override fun attachment(attachment: Attachment): SecureHash {
|
||||
storageService.attachments.importAttachment(attachment.open())
|
||||
return attachment.id
|
||||
override fun attachment(attachment: InputStream): SecureHash {
|
||||
return storageService.attachments.importAttachment(attachment)
|
||||
}
|
||||
|
||||
override fun verifies() {
|
||||
|
@ -6,6 +6,8 @@ import com.google.common.base.Throwables
|
||||
import com.google.common.net.HostAndPort
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.crypto.*
|
||||
import com.r3corda.core.node.services.IdentityService
|
||||
import com.r3corda.core.node.services.StorageService
|
||||
import com.r3corda.core.node.services.testing.MockIdentityService
|
||||
import com.r3corda.core.node.services.testing.MockStorageService
|
||||
import com.r3corda.core.seconds
|
||||
@ -94,9 +96,16 @@ object JavaTestHelpers {
|
||||
|
||||
@JvmStatic fun generateStateRef() = StateRef(SecureHash.randomSHA256(), 0)
|
||||
|
||||
@JvmStatic fun transaction(body: TransactionForTest.() -> LastLineShouldTestForAcceptOrFailure): LastLineShouldTestForAcceptOrFailure {
|
||||
return body(TransactionForTest())
|
||||
@JvmStatic @JvmOverloads fun ledger(
|
||||
identityService: IdentityService = MOCK_IDENTITY_SERVICE,
|
||||
storageService: StorageService = MockStorageService(),
|
||||
dsl: LedgerDsl<LastLineShouldTestForVerifiesOrFails, TestTransactionDslInterpreter, TestLedgerDslInterpreter>.() -> Unit
|
||||
): LedgerDsl<LastLineShouldTestForVerifiesOrFails, TestTransactionDslInterpreter, TestLedgerDslInterpreter> {
|
||||
val ledgerDsl = LedgerDsl(TestLedgerDslInterpreter(identityService, storageService))
|
||||
dsl(ledgerDsl)
|
||||
return ledgerDsl
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val TEST_TX_TIME = JavaTestHelpers.TEST_TX_TIME
|
||||
|
@ -3,41 +3,78 @@ package com.r3corda.core.testing
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.crypto.SecureHash
|
||||
import com.r3corda.core.node.services.IdentityService
|
||||
import com.r3corda.core.seconds
|
||||
import java.security.PublicKey
|
||||
import java.time.Instant
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Defines a simple DSL for building pseudo-transactions (not the same as the wire protocol) for testing purposes.
|
||||
//
|
||||
// Define a transaction like this:
|
||||
//
|
||||
// ledger {
|
||||
// transaction {
|
||||
// input { someExpression }
|
||||
// output { someExpression }
|
||||
// command { someExpression }
|
||||
//
|
||||
// tweak {
|
||||
// ... same thing but works with a copy of the parent, can add inputs/outputs/commands just within this scope.
|
||||
// }
|
||||
//
|
||||
// contract.verifies() -> verify() should pass
|
||||
// contract `fails with` "some substring of the error message"
|
||||
// }
|
||||
// }
|
||||
//
|
||||
|
||||
/**
|
||||
* [State] is bound at the top level. This allows the definition of e.g. [String.output], however it also means that we
|
||||
* cannot mix different types of states in the same transaction.
|
||||
* TODO: Move the [State] binding to the primitives' level to allow different State types, use reflection to check types
|
||||
* dynamically, come up with a substitute for primitives relying on early bind
|
||||
* The [TransactionDslInterpreter] defines the interface DSL interpreters should satisfy. No
|
||||
* overloading/default valuing should be done here, only the basic functions that are required to implement everything.
|
||||
* Same goes for functions requiring reflection e.g. [OutputStateLookup.retrieveOutputStateAndRef]
|
||||
* Put convenience functions in [TransactionDsl] instead. There are some cases where the overloads would clash with the
|
||||
* Interpreter interface, in these cases define a "backing" function in the interface instead (e.g. [_command]).
|
||||
*
|
||||
* This way the responsibility of providing a nice frontend DSL and the implementation(s) are separated
|
||||
*/
|
||||
interface TransactionDslInterpreter : OutputStateLookup {
|
||||
interface TransactionDslInterpreter<Return> : OutputStateLookup {
|
||||
val ledgerInterpreter: LedgerDslInterpreter<Return, TransactionDslInterpreter<Return>>
|
||||
fun input(stateRef: StateRef)
|
||||
fun output(label: String?, notary: Party, contractState: ContractState)
|
||||
fun _output(label: String?, notary: Party, contractState: ContractState)
|
||||
fun attachment(attachmentId: SecureHash)
|
||||
fun _command(signers: List<PublicKey>, commandData: CommandData)
|
||||
fun verifies()
|
||||
fun failsWith(expectedMessage: String?)
|
||||
fun tweak(dsl: TransactionDsl<TransactionDslInterpreter>.() -> Unit)
|
||||
fun verifies(): Return
|
||||
fun failsWith(expectedMessage: String?): Return
|
||||
fun tweak(
|
||||
dsl: TransactionDsl<Return, TransactionDslInterpreter<Return>>.() -> Return
|
||||
): Return
|
||||
}
|
||||
|
||||
|
||||
class TransactionDsl<
|
||||
out TransactionInterpreter: TransactionDslInterpreter
|
||||
Return,
|
||||
out TransactionInterpreter: TransactionDslInterpreter<Return>
|
||||
> (val interpreter: TransactionInterpreter)
|
||||
: TransactionDslInterpreter by interpreter {
|
||||
: TransactionDslInterpreter<Return> by interpreter {
|
||||
|
||||
fun input(stateLabel: String) = input(retrieveOutputStateAndRef(ContractState::class.java, stateLabel).ref)
|
||||
/**
|
||||
* Adds the passed in state as a non-verified transaction output to the ledger and adds that as an input
|
||||
*/
|
||||
fun input(state: ContractState) {
|
||||
val transaction = ledgerInterpreter.nonVerifiedTransaction(null) {
|
||||
output { state }
|
||||
}
|
||||
input(transaction.outRef<ContractState>(0).ref)
|
||||
}
|
||||
fun input(stateClosure: () -> ContractState) = input(stateClosure())
|
||||
|
||||
// Convenience functions
|
||||
fun output(label: String? = null, notary: Party = DUMMY_NOTARY, contractStateClosure: () -> ContractState) =
|
||||
output(label, notary, contractStateClosure())
|
||||
@JvmOverloads
|
||||
fun output(label: String? = null, contractState: ContractState) = output(label, DUMMY_NOTARY, contractState)
|
||||
fun output(label: String? = null, notary: Party = DUMMY_NOTARY, contractStateClosure: () -> ContractState) =
|
||||
_output(label, notary, contractStateClosure())
|
||||
@JvmOverloads
|
||||
fun output(label: String? = null, contractState: ContractState) =
|
||||
_output(label, DUMMY_NOTARY, contractState)
|
||||
|
||||
fun command(vararg signers: PublicKey, commandDataClosure: () -> CommandData) =
|
||||
_command(listOf(*signers), commandDataClosure())
|
||||
|
Loading…
Reference in New Issue
Block a user