mirror of
https://github.com/corda/corda.git
synced 2025-04-19 08:36:39 +00:00
Merged in aslemmer-relax-tptp-tests (pull request #344)
Relax tptp tests
This commit is contained in:
commit
600c630f91
@ -298,17 +298,19 @@ class TwoPartyTradeProtocolTests {
|
||||
run {
|
||||
val records = (bobNode.storage.validatedTransactions as RecordingTransactionStorage).records
|
||||
// Check Bobs's database accesses as Bob's cash transactions are downloaded by Alice.
|
||||
assertThat(records).containsExactly(
|
||||
// Buyer Bob is told about Alice's commercial paper, but doesn't know it ..
|
||||
TxRecord.Get(alicesFakePaper[0].id),
|
||||
// He asks and gets the tx, validates it, sees it's a self issue with no dependencies, stores.
|
||||
TxRecord.Add(alicesSignedTxns.values.first()),
|
||||
// Alice gets Bob's proposed transaction and doesn't know his two cash states. She asks, Bob answers.
|
||||
TxRecord.Get(bobsFakeCash[1].id),
|
||||
TxRecord.Get(bobsFakeCash[2].id),
|
||||
// Alice notices that Bob's cash txns depend on a third tx she also doesn't know. She asks, Bob answers.
|
||||
TxRecord.Get(bobsFakeCash[0].id)
|
||||
)
|
||||
records.expectEvents(isStrict = false) {
|
||||
sequence(
|
||||
// Buyer Bob is told about Alice's commercial paper, but doesn't know it ..
|
||||
expect(TxRecord.Get(alicesFakePaper[0].id)),
|
||||
// He asks and gets the tx, validates it, sees it's a self issue with no dependencies, stores.
|
||||
expect(TxRecord.Add(alicesSignedTxns.values.first())),
|
||||
// Alice gets Bob's proposed transaction and doesn't know his two cash states. She asks, Bob answers.
|
||||
expect(TxRecord.Get(bobsFakeCash[1].id)),
|
||||
expect(TxRecord.Get(bobsFakeCash[2].id)),
|
||||
// Alice notices that Bob's cash txns depend on a third tx she also doesn't know. She asks, Bob answers.
|
||||
expect(TxRecord.Get(bobsFakeCash[0].id))
|
||||
)
|
||||
}
|
||||
|
||||
// Bob has downloaded the attachment.
|
||||
bobNode.storage.attachments.openAttachment(attachmentID)!!.openAsJAR().use {
|
||||
@ -321,33 +323,35 @@ class TwoPartyTradeProtocolTests {
|
||||
// And from Alice's perspective ...
|
||||
run {
|
||||
val records = (aliceNode.storage.validatedTransactions as RecordingTransactionStorage).records
|
||||
assertThat(records).containsExactly(
|
||||
// Seller Alice sends her seller info to Bob, who wants to check the asset for sale.
|
||||
// He requests, Alice looks up in her DB to send the tx to Bob
|
||||
TxRecord.Get(alicesFakePaper[0].id),
|
||||
// Seller Alice gets a proposed tx which depends on Bob's two cash txns and her own tx.
|
||||
TxRecord.Get(bobsFakeCash[1].id),
|
||||
TxRecord.Get(bobsFakeCash[2].id),
|
||||
TxRecord.Get(alicesFakePaper[0].id),
|
||||
// Alice notices that Bob's cash txns depend on a third tx she also doesn't know.
|
||||
TxRecord.Get(bobsFakeCash[0].id),
|
||||
// Bob answers with the transactions that are now all verifiable, as Alice bottomed out.
|
||||
// Bob's transactions are valid, so she commits to the database
|
||||
TxRecord.Add(bobsSignedTxns[bobsFakeCash[0].id]!!),
|
||||
TxRecord.Get(bobsFakeCash[0].id), // Verify
|
||||
TxRecord.Add(bobsSignedTxns[bobsFakeCash[2].id]!!),
|
||||
TxRecord.Get(bobsFakeCash[0].id), // Verify
|
||||
TxRecord.Add(bobsSignedTxns[bobsFakeCash[1].id]!!),
|
||||
// Now she verifies the transaction is contract-valid (not signature valid) which means
|
||||
// looking up the states again.
|
||||
TxRecord.Get(bobsFakeCash[1].id),
|
||||
TxRecord.Get(bobsFakeCash[2].id),
|
||||
TxRecord.Get(alicesFakePaper[0].id),
|
||||
// Alice needs to look up the input states to find out which Notary they point to
|
||||
TxRecord.Get(bobsFakeCash[1].id),
|
||||
TxRecord.Get(bobsFakeCash[2].id),
|
||||
TxRecord.Get(alicesFakePaper[0].id)
|
||||
)
|
||||
records.expectEvents(isStrict = false) {
|
||||
sequence(
|
||||
// Seller Alice sends her seller info to Bob, who wants to check the asset for sale.
|
||||
// He requests, Alice looks up in her DB to send the tx to Bob
|
||||
expect(TxRecord.Get(alicesFakePaper[0].id)),
|
||||
// Seller Alice gets a proposed tx which depends on Bob's two cash txns and her own tx.
|
||||
expect(TxRecord.Get(bobsFakeCash[1].id)),
|
||||
expect(TxRecord.Get(bobsFakeCash[2].id)),
|
||||
expect(TxRecord.Get(alicesFakePaper[0].id)),
|
||||
// Alice notices that Bob's cash txns depend on a third tx she also doesn't know.
|
||||
expect(TxRecord.Get(bobsFakeCash[0].id)),
|
||||
// Bob answers with the transactions that are now all verifiable, as Alice bottomed out.
|
||||
// Bob's transactions are valid, so she commits to the database
|
||||
expect(TxRecord.Add(bobsSignedTxns[bobsFakeCash[0].id]!!)),
|
||||
expect(TxRecord.Get(bobsFakeCash[0].id)), // Verify
|
||||
expect(TxRecord.Add(bobsSignedTxns[bobsFakeCash[2].id]!!)),
|
||||
expect(TxRecord.Get(bobsFakeCash[0].id)), // Verify
|
||||
expect(TxRecord.Add(bobsSignedTxns[bobsFakeCash[1].id]!!)),
|
||||
// Now she verifies the transaction is contract-valid (not signature valid) which means
|
||||
// looking up the states again.
|
||||
expect(TxRecord.Get(bobsFakeCash[1].id)),
|
||||
expect(TxRecord.Get(bobsFakeCash[2].id)),
|
||||
expect(TxRecord.Get(alicesFakePaper[0].id)),
|
||||
// Alice needs to look up the input states to find out which Notary they point to
|
||||
expect(TxRecord.Get(bobsFakeCash[1].id)),
|
||||
expect(TxRecord.Get(bobsFakeCash[2].id)),
|
||||
expect(TxRecord.Get(alicesFakePaper[0].id))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,9 +11,10 @@ import rx.Observable
|
||||
* [sequence] is used to impose ordering invariants on the stream, whereas [parallel] allows events to arrive in any order.
|
||||
*
|
||||
* The only restriction on [parallel] is that we should be able to discriminate which branch to take based on the
|
||||
* arrived event's type. If this is ambiguous the first matching piece of DSL will be run.
|
||||
|
||||
* [sequence]s and [parallel]s can be nested arbitrarily.
|
||||
* arrived event's type and optionally custom matching logic. If this is ambiguous the first matching piece of DSL will
|
||||
* be run.
|
||||
*
|
||||
* [sequence]s and [parallel]s can be nested arbitrarily
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
@ -35,12 +36,31 @@ private val log: Logger = LoggerFactory.getLogger("Expect")
|
||||
|
||||
/**
|
||||
* Expect an event of type [T] and run [expectClosure] on it
|
||||
*
|
||||
* @param klass The [Class] to use for checking the incoming event's type
|
||||
* @param match Optional additional matching logic
|
||||
* @param expectClosure The closure to run on the event
|
||||
*/
|
||||
inline fun <E : Any, reified T : E> expect(noinline expectClosure: (T) -> Unit) = expect(T::class.java, expectClosure)
|
||||
fun <E : Any, T : E> expect(klass: Class<T>, expectClosure: (T) -> Unit): ExpectCompose<E> {
|
||||
return ExpectCompose.Single(Expect(klass, expectClosure))
|
||||
fun <E : Any> expect(klass: Class<E>, match: (E) -> Boolean, expectClosure: (E) -> Unit): ExpectCompose<E> {
|
||||
return ExpectCompose.Single(Expect(klass, match, expectClosure))
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience variant of [expect] reifying the [Class] parameter
|
||||
*/
|
||||
inline fun <reified E : Any> expect(
|
||||
noinline match: (E) -> Boolean = { true },
|
||||
noinline expectClosure: (E) -> Unit
|
||||
): ExpectCompose<E> = expect(E::class.java, match, expectClosure)
|
||||
|
||||
/**
|
||||
* Convenience variant of [expect] that only matches events that are strictly equal to [event]
|
||||
*/
|
||||
inline fun <reified E : Any> expect(
|
||||
event: E,
|
||||
noinline expectClosure: (E) -> Unit = {}
|
||||
): ExpectCompose<E> = expect(match = { event == it }, expectClosure = expectClosure)
|
||||
|
||||
/**
|
||||
* Tests that events arrive in the specified order.
|
||||
*
|
||||
@ -61,45 +81,97 @@ fun <E> parallel(vararg expectations: ExpectCompose<E>): ExpectCompose<E> = Expe
|
||||
* @param number The number of events expected.
|
||||
* @param expectation The piece of DSL to run on each event, with the index of the event passed in.
|
||||
*/
|
||||
inline fun <E> replicate(number: Int, expectation: (Int) -> ExpectCompose<E>) = sequence(*Array(number) { expectation(it) })
|
||||
inline fun <E> replicate(number: Int, expectation: (Int) -> ExpectCompose<E>): ExpectCompose<E> =
|
||||
sequence(*Array(number) { expectation(it) })
|
||||
|
||||
/**
|
||||
* Run the specified DSL against the event stream.
|
||||
* Run the specified DSL against the event [Observable].
|
||||
*
|
||||
* @param isStrict If false non-matched events are disregarded (so the DSL will only check a subset of events).
|
||||
* @param isStrict If false non-matched events are disregarded (so the DSL will only check a subsequence of events).
|
||||
* @param expectCompose The DSL we expect to match against the stream of events.
|
||||
*/
|
||||
fun <E : Any> Observable<E>.expectEvents(isStrict: Boolean = true, expectCompose: () -> ExpectCompose<E>) {
|
||||
fun <E : Any> Observable<E>.expectEvents(isStrict: Boolean = true, expectCompose: () -> ExpectCompose<E>) =
|
||||
serialize().genericExpectEvents(
|
||||
isStrict = isStrict,
|
||||
stream = { action: (E) -> Unit ->
|
||||
val lock = object {}
|
||||
subscribe { event ->
|
||||
synchronized(lock) {
|
||||
action(event)
|
||||
}
|
||||
}
|
||||
},
|
||||
expectCompose = expectCompose
|
||||
)
|
||||
|
||||
/**
|
||||
* Run the specified DSL against the event [Iterable].
|
||||
*
|
||||
* @param isStrict If false non-matched events are disregarded (so the DSL will only check a subsequence of events).
|
||||
* @param expectCompose The DSL we expect to match against the stream of events.
|
||||
*/
|
||||
fun <E : Any> Iterable<E>.expectEvents(isStrict: Boolean = true, expectCompose: () -> ExpectCompose<E>) =
|
||||
genericExpectEvents(
|
||||
isStrict = isStrict,
|
||||
stream = { action: (E) -> Unit ->
|
||||
forEach(action)
|
||||
},
|
||||
expectCompose = expectCompose
|
||||
)
|
||||
|
||||
/**
|
||||
* Run the specified DSL against the generic event [S]tream
|
||||
*
|
||||
* @param isStrict If false non-matched events are disregarded (so the DSL will only check a subsequence of events).
|
||||
* @param stream A function that extracts events from the stream.
|
||||
* @param expectCompose The DSL we expect to match against the stream of events.
|
||||
*/
|
||||
fun <S, E : Any> S.genericExpectEvents(
|
||||
isStrict: Boolean = true,
|
||||
stream: S.((E) -> Unit) -> Unit,
|
||||
expectCompose: () -> ExpectCompose<E>
|
||||
) {
|
||||
val finishFuture = SettableFuture<Unit>()
|
||||
val stateLock = object {}
|
||||
/**
|
||||
* Internally we create a "lazy" state automaton. The outgoing edges are state.getExpectedEvents() modulo additional
|
||||
* matching logic. When an event comes we extract the first edge that matches using state.nextState(event), which
|
||||
* returns the next state and the piece of dsl to be run on the event. If nextState() returns null it means the event
|
||||
* didn't match at all, in this case we either fail (if isStrict=true) or carry on with the same state (if isStrict=false)
|
||||
*
|
||||
* TODO Think about pre-compiling the state automaton, possibly introducing regexp constructs. This requires some
|
||||
* thinking, as the [parallel] construct blows up the state space factorially, so we need some clever lazy expansion
|
||||
* of states.
|
||||
*/
|
||||
var state = ExpectComposeState.fromExpectCompose(expectCompose())
|
||||
subscribe { event ->
|
||||
synchronized(stateLock) {
|
||||
if (state is ExpectComposeState.Finished) {
|
||||
stream { event ->
|
||||
if (state is ExpectComposeState.Finished) {
|
||||
if (isStrict) {
|
||||
log.warn("Got event $event, but was expecting no further events")
|
||||
return@subscribe
|
||||
}
|
||||
val next = state.nextState(event)
|
||||
log.info("$event :: ${state.getExpectedEvents()} -> ${next?.second?.getExpectedEvents()}")
|
||||
if (next == null) {
|
||||
val expectedStates = state.getExpectedEvents()
|
||||
val message = "Got $event, expected one of $expectedStates"
|
||||
if (isStrict) {
|
||||
finishFuture.setException(Exception(message))
|
||||
state = ExpectComposeState.Finished()
|
||||
} else {
|
||||
log.warn("$message, discarding event as isStrict=false")
|
||||
}
|
||||
return@stream
|
||||
}
|
||||
val next = state.nextState(event)
|
||||
val expectedStates = state.getExpectedEvents()
|
||||
log.info("$event :: ${expectedStates.map { it.simpleName }} -> ${next?.second?.getExpectedEvents()?.map { it.simpleName }}")
|
||||
if (next == null) {
|
||||
val message = "Got $event, did not match any expectations of type ${expectedStates.map { it.simpleName }}"
|
||||
if (isStrict) {
|
||||
finishFuture.setException(Exception(message))
|
||||
state = ExpectComposeState.Finished()
|
||||
} else {
|
||||
state = next.second
|
||||
try {
|
||||
next.first()
|
||||
} catch (exception: Exception) {
|
||||
finishFuture.setException(exception)
|
||||
}
|
||||
if (state is ExpectComposeState.Finished) {
|
||||
finishFuture.set(Unit)
|
||||
}
|
||||
log.info("$message, discarding event as isStrict=false")
|
||||
}
|
||||
} else {
|
||||
state = next.second
|
||||
val expectClosure = next.first
|
||||
// Now run the matching piece of dsl
|
||||
try {
|
||||
expectClosure()
|
||||
} catch (exception: Exception) {
|
||||
finishFuture.setException(exception)
|
||||
}
|
||||
if (state is ExpectComposeState.Finished) {
|
||||
finishFuture.set(Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -107,13 +179,14 @@ fun <E : Any> Observable<E>.expectEvents(isStrict: Boolean = true, expectCompose
|
||||
}
|
||||
|
||||
sealed class ExpectCompose<out E> {
|
||||
internal class Single<E>(val expect: Expect<E, E>) : ExpectCompose<E>()
|
||||
internal class Sequential<E>(val sequence: List<ExpectCompose<E>>) : ExpectCompose<E>()
|
||||
internal class Parallel<E>(val parallel: List<ExpectCompose<E>>) : ExpectCompose<E>()
|
||||
internal class Single<out E, T : E>(val expect: Expect<E, T>) : ExpectCompose<E>()
|
||||
internal class Sequential<out E>(val sequence: List<ExpectCompose<E>>) : ExpectCompose<E>()
|
||||
internal class Parallel<out E>(val parallel: List<ExpectCompose<E>>) : ExpectCompose<E>()
|
||||
}
|
||||
|
||||
internal data class Expect<E, T : E>(
|
||||
internal data class Expect<out E, T : E>(
|
||||
val clazz: Class<T>,
|
||||
val match: (T) -> Boolean,
|
||||
val expectClosure: (T) -> Unit
|
||||
)
|
||||
|
||||
@ -124,13 +197,18 @@ private sealed class ExpectComposeState<E : Any> {
|
||||
|
||||
class Finished<E : Any> : ExpectComposeState<E>() {
|
||||
override fun nextState(event: E) = null
|
||||
override fun getExpectedEvents(): List<Class<out E>> = listOf()
|
||||
override fun getExpectedEvents(): List<Class<E>> = listOf()
|
||||
}
|
||||
class Single<E : Any>(val single: ExpectCompose.Single<E>) : ExpectComposeState<E>() {
|
||||
class Single<E : Any, T : E>(val single: ExpectCompose.Single<E, T>) : ExpectComposeState<E>() {
|
||||
override fun nextState(event: E): Pair<() -> Unit, ExpectComposeState<E>>? =
|
||||
if (single.expect.clazz.isAssignableFrom(event.javaClass)) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
Pair({ single.expect.expectClosure(event) }, Finished())
|
||||
val coercedEvent = event as T
|
||||
if (single.expect.match(event)) {
|
||||
Pair({ single.expect.expectClosure(coercedEvent) }, Finished())
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
@ -191,7 +269,12 @@ private sealed class ExpectComposeState<E : Any> {
|
||||
companion object {
|
||||
fun <E : Any> fromExpectCompose(expectCompose: ExpectCompose<E>): ExpectComposeState<E> {
|
||||
return when (expectCompose) {
|
||||
is ExpectCompose.Single -> Single(expectCompose)
|
||||
is ExpectCompose.Single<E, *> -> {
|
||||
// This coercion should not be needed but kotlin can't reason about existential type variables(T)
|
||||
// so here we're coercing T into E (even though T is invariant).
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
Single(expectCompose as ExpectCompose.Single<E, E>)
|
||||
}
|
||||
is ExpectCompose.Sequential -> {
|
||||
if (expectCompose.sequence.size > 0) {
|
||||
Sequential(expectCompose, 0, fromExpectCompose(expectCompose.sequence[0]))
|
||||
|
Loading…
x
Reference in New Issue
Block a user