Skip to content

Commit

Permalink
Add another test
Browse files Browse the repository at this point in the history
  • Loading branch information
jhowens89 committed May 18, 2024
1 parent 4040e7b commit 15351c1
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,6 @@ internal class FormulaManagerImpl<Input, State, Output>(
// starting new actions.
if (isTerminated()) {
manager.markAsTerminated()
manager.performTerminationSideEffects()
}
manager.setValidationRun(isValidationEnabled)

Expand Down
63 changes: 43 additions & 20 deletions formula/src/test/java/com/instacart/formula/FormulaRuntimeTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.instacart.formula.plugin.Inspector
import com.instacart.formula.plugin.Plugin
import com.instacart.formula.rxjava3.RxAction
import com.instacart.formula.subjects.ChildActionFiresParentEventOnStart
import com.instacart.formula.subjects.ChildErrorAfterToggleFormula
import com.instacart.formula.subjects.ChildMessageNoParentStateChange
import com.instacart.formula.subjects.ChildMessageTriggersEventTransitionInParent
import com.instacart.formula.subjects.ChildMessageWithParentStateChange
Expand Down Expand Up @@ -1378,18 +1379,14 @@ class FormulaRuntimeTest(val runtime: TestableRuntime, val name: String) {

@Test
fun `errored child formula can fail in isolation when evaluation throws`() {
val childFormula = object : StatelessFormula<Int, Int>() {
override fun Snapshot<Int, Unit>.evaluate(): Evaluation<Int> {
if (input == 1) throw RuntimeException()
return Evaluation(output = input)
val childFormula = object : StatelessFormula<HasChildrenFormula.ChildParamsInput<*>, Int>() {
override fun Snapshot<HasChildrenFormula.ChildParamsInput<*>, Unit>.evaluate(): Evaluation<Int> {
if (input.index == 1) throw RuntimeException()
return Evaluation(output = input.index)
}
}
val formula = HasChildrenFormula(
childCount = 3,
child = childFormula,
createChildInput = { it },
)
runtime.test(formula, Unit)
val formula = HasChildrenFormula(childCount = 3, childFormula)
runtime.test(formula, 0)
.output {
assertThat(childOutputs).isEqualTo(listOf(0, 2))
assertThat(errors).hasSize(1)
Expand All @@ -1398,30 +1395,56 @@ class FormulaRuntimeTest(val runtime: TestableRuntime, val name: String) {

@Test
fun `errored child formula can fail in isolation when action throws`() {
val childFormula = object : StatelessFormula<Int, Int>() {
override fun Snapshot<Int, Unit>.evaluate(): Evaluation<Int> {
val childFormula = object : StatelessFormula<HasChildrenFormula.ChildParamsInput<*>, Int>() {
override fun Snapshot<HasChildrenFormula.ChildParamsInput<*>, Unit>.evaluate(): Evaluation<Int> {
return Evaluation(
output = input,
output = input.index,
actions = context.actions {
Action.onInit().onEvent {
if (input == 1) throw RuntimeException()
if (input.index == 1) throw RuntimeException()
transition(Unit) {}
}
}
)
}
}
val formula = HasChildrenFormula(
childCount = 3,
child = childFormula,
createChildInput = { it },
)
runtime.test(formula, Unit)
val formula = HasChildrenFormula(childCount = 3, childFormula)
runtime.test(formula, 0)
.output {
assertThat(childOutputs).isEqualTo(listOf(0, 2))
assertThat(errors).hasSize(1)
}
}
@Test
fun `errored child event listener disabled`() {
val indexToExecutionCount = mutableMapOf<Int, Int>()
val listener = { index: Int ->
indexToExecutionCount[index] = indexToExecutionCount.getOrDefault(index, 0) + 1
}
val formula = HasChildrenFormula(
childCount = 3,
child = ChildErrorAfterToggleFormula(),
createChildInput = { params ->
HasChildrenFormula.ChildParamsInput(
index = params.index,
run = params.index,
value = callback { transition { listener(params.index) }}
)
},
)
runtime.test(formula, 0)
.output {
childOutputs.forEach { it.listener() }
childOutputs[1].errorToggle()
childOutputs.forEach { it.listener() }
}
val expected = mapOf(
0 to 2,
1 to 1,
2 to 2,
)
assertThat(indexToExecutionCount).containsExactlyEntriesIn(expected)
}

@Test
fun `initialize 100 levels nested formula`() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.instacart.formula.subjects

import com.instacart.formula.Evaluation
import com.instacart.formula.Formula
import com.instacart.formula.Snapshot
import com.instacart.formula.subjects.ChildErrorAfterToggleFormula.Output
import com.instacart.formula.subjects.ChildErrorAfterToggleFormula.State

class ChildErrorAfterToggleFormula: Formula<HasChildrenFormula.ChildParamsInput<()-> Unit>, State, Output>() {
data class Output(
val errorToggle: () -> Unit,
val listener: () -> Unit
)

data class State(val throwError: Boolean = false)

override fun initialState(input: HasChildrenFormula.ChildParamsInput<()-> Unit>) = State()

override fun Snapshot<HasChildrenFormula.ChildParamsInput<()-> Unit>, State>.evaluate(): Evaluation<Output> {
if (state.throwError) throw RuntimeException()
return Evaluation(
output = Output(
errorToggle = context.callback() {
transition(state.copy(throwError = !state.throwError))
},
listener = context.callback {
transition { input.value() }
}
),
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,55 @@ import com.instacart.formula.subjects.HasChildrenFormula.Output

class HasChildrenFormula<ChildInput, ChildOutput>(
private val childCount: Int,
private val child: IFormula<ChildInput, ChildOutput>,
private val createChildInput: FormulaContext<*, State>.(Int) -> ChildInput
) : Formula<Unit, State, Output<ChildOutput>>() {
private val child: IFormula<ChildParamsInput<ChildInput>, ChildOutput>,
private val createChildInput: FormulaContext<*, State>.(ChildParams) -> ChildParamsInput<ChildInput>,
) : Formula<Int, State, Output<ChildOutput>>() {

companion object {
operator fun <ChildOutput> invoke(
childCount:Int,
child: IFormula<ChildParamsInput<Unit>, ChildOutput>
): HasChildrenFormula<Unit, ChildOutput> {
return HasChildrenFormula(childCount, child) {
ChildParamsInput(
index = it.index,
run = it.run,
value = Unit,
)
}
}
}
data class ChildParams(
val index: Int,
val run: Int,
)

data class ChildParamsInput<Input>(
val index: Int,
val run: Int,
val value: Input,
)

data class State(
val errors: List<Throwable> = emptyList(),
)

data class Output<ChildOutput>(
val run: Int,
val errors: List<Throwable>,
val childOutputs: List<ChildOutput>,
)

override fun initialState(input: Unit): State = State()
override fun initialState(input: Int): State = State()

override fun Snapshot<Unit, State>.evaluate(): Evaluation<Output<ChildOutput>> {
override fun Snapshot<Int, State>.evaluate(): Evaluation<Output<ChildOutput>> {
val childOutputs = (0 until childCount).mapNotNull{ index ->
context.key(index) {
val childParams = ChildParams(index = index, run = input)
val childInput = createChildInput(context, childParams)
context.child(
formula = child,
input = createChildInput(context, index),
input = childInput,
onError = context.onEvent { error ->
transition(state.copy(errors = state.errors + error))
}
Expand All @@ -39,6 +67,7 @@ class HasChildrenFormula<ChildInput, ChildOutput>(
}
return Evaluation(
output = Output(
run = input,
errors = state.errors,
childOutputs = childOutputs,
)
Expand Down

0 comments on commit 15351c1

Please sign in to comment.