mirror of
https://github.com/corda/corda.git
synced 2024-12-22 06:17:55 +00:00
test-utils: Address PR comments on Expect, fix variance, add some comments
This commit is contained in:
parent
d19e8b6a7b
commit
02a1b93708
@ -91,13 +91,13 @@ inline fun <E> replicate(number: Int, expectation: (Int) -> ExpectCompose<E>): E
|
|||||||
* @param expectCompose The DSL we expect to match against the stream 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>) =
|
||||||
genericExpectEvents(
|
serialize().genericExpectEvents(
|
||||||
isStrict = isStrict,
|
isStrict = isStrict,
|
||||||
stream = { function: (E) -> Unit ->
|
stream = { action: (E) -> Unit ->
|
||||||
val lock = object {}
|
val lock = object {}
|
||||||
subscribe { event ->
|
subscribe { event ->
|
||||||
synchronized(lock) {
|
synchronized(lock) {
|
||||||
function(event)
|
action(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -113,8 +113,8 @@ fun <E : Any> Observable<E>.expectEvents(isStrict: Boolean = true, expectCompose
|
|||||||
fun <E : Any> Iterable<E>.expectEvents(isStrict: Boolean = true, expectCompose: () -> ExpectCompose<E>) =
|
fun <E : Any> Iterable<E>.expectEvents(isStrict: Boolean = true, expectCompose: () -> ExpectCompose<E>) =
|
||||||
genericExpectEvents(
|
genericExpectEvents(
|
||||||
isStrict = isStrict,
|
isStrict = isStrict,
|
||||||
stream = { function: (E) -> Unit ->
|
stream = { action: (E) -> Unit ->
|
||||||
forEach(function)
|
forEach(action)
|
||||||
},
|
},
|
||||||
expectCompose = expectCompose
|
expectCompose = expectCompose
|
||||||
)
|
)
|
||||||
@ -132,6 +132,16 @@ fun <S, E : Any> S.genericExpectEvents(
|
|||||||
expectCompose: () -> ExpectCompose<E>
|
expectCompose: () -> ExpectCompose<E>
|
||||||
) {
|
) {
|
||||||
val finishFuture = SettableFuture<Unit>()
|
val finishFuture = SettableFuture<Unit>()
|
||||||
|
/**
|
||||||
|
* 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())
|
var state = ExpectComposeState.fromExpectCompose(expectCompose())
|
||||||
stream { event ->
|
stream { event ->
|
||||||
if (state is ExpectComposeState.Finished) {
|
if (state is ExpectComposeState.Finished) {
|
||||||
@ -153,8 +163,10 @@ fun <S, E : Any> S.genericExpectEvents(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
state = next.second
|
state = next.second
|
||||||
|
val expectClosure = next.first
|
||||||
|
// Now run the matching piece of dsl
|
||||||
try {
|
try {
|
||||||
next.first()
|
expectClosure()
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
finishFuture.setException(exception)
|
finishFuture.setException(exception)
|
||||||
}
|
}
|
||||||
@ -167,31 +179,36 @@ fun <S, E : Any> S.genericExpectEvents(
|
|||||||
}
|
}
|
||||||
|
|
||||||
sealed class ExpectCompose<out E> {
|
sealed class ExpectCompose<out E> {
|
||||||
internal class Single<E>(val expect: Expect<E>) : ExpectCompose<E>()
|
internal class Single<out E, T : E>(val expect: Expect<E, T>) : ExpectCompose<E>()
|
||||||
internal class Sequential<E>(val sequence: List<ExpectCompose<E>>) : ExpectCompose<E>()
|
internal class Sequential<out E>(val sequence: List<ExpectCompose<E>>) : ExpectCompose<E>()
|
||||||
internal class Parallel<E>(val parallel: List<ExpectCompose<E>>) : ExpectCompose<E>()
|
internal class Parallel<out E>(val parallel: List<ExpectCompose<E>>) : ExpectCompose<E>()
|
||||||
}
|
}
|
||||||
|
|
||||||
internal data class Expect<in E>(
|
internal data class Expect<out E, T : E>(
|
||||||
val clazz: Class<in E>,
|
val clazz: Class<T>,
|
||||||
val match: (E) -> Boolean,
|
val match: (T) -> Boolean,
|
||||||
val expectClosure: (E) -> Unit
|
val expectClosure: (T) -> Unit
|
||||||
)
|
)
|
||||||
|
|
||||||
private sealed class ExpectComposeState<E : Any> {
|
private sealed class ExpectComposeState<E : Any> {
|
||||||
|
|
||||||
abstract fun nextState(event: E): Pair<() -> Unit, ExpectComposeState<E>>?
|
abstract fun nextState(event: E): Pair<() -> Unit, ExpectComposeState<E>>?
|
||||||
abstract fun getExpectedEvents(): List<Class<in E>>
|
abstract fun getExpectedEvents(): List<Class<out E>>
|
||||||
|
|
||||||
class Finished<E : Any> : ExpectComposeState<E>() {
|
class Finished<E : Any> : ExpectComposeState<E>() {
|
||||||
override fun nextState(event: E) = null
|
override fun nextState(event: E) = null
|
||||||
override fun getExpectedEvents(): List<Class<in 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>>? =
|
override fun nextState(event: E): Pair<() -> Unit, ExpectComposeState<E>>? =
|
||||||
if (single.expect.clazz.isAssignableFrom(event.javaClass) && single.expect.match(event)) {
|
if (single.expect.clazz.isAssignableFrom(event.javaClass)) {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@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 {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
@ -252,7 +269,12 @@ private sealed class ExpectComposeState<E : Any> {
|
|||||||
companion object {
|
companion object {
|
||||||
fun <E : Any> fromExpectCompose(expectCompose: ExpectCompose<E>): ExpectComposeState<E> {
|
fun <E : Any> fromExpectCompose(expectCompose: ExpectCompose<E>): ExpectComposeState<E> {
|
||||||
return when (expectCompose) {
|
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 -> {
|
is ExpectCompose.Sequential -> {
|
||||||
if (expectCompose.sequence.size > 0) {
|
if (expectCompose.sequence.size > 0) {
|
||||||
Sequential(expectCompose, 0, fromExpectCompose(expectCompose.sequence[0]))
|
Sequential(expectCompose, 0, fromExpectCompose(expectCompose.sequence[0]))
|
||||||
|
Loading…
Reference in New Issue
Block a user