Skip to content

Commit

Permalink
[Bugfix] Fix old listener crash. (#310)
Browse files Browse the repository at this point in the history
* Adding tests to replicate a crash.

* [Bugfix] Fix old listener crash.
  • Loading branch information
Laimiux authored Jul 10, 2023
1 parent 81ebe09 commit be776ae
Show file tree
Hide file tree
Showing 5 changed files with 260 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,10 @@ internal class FormulaManagerImpl<Input, State, Output>(
* @return True if we need to re-evaluate.
*/
private fun postEvaluation(evaluationId: Long): Boolean {
if (isEvaluationNeeded(evaluationId)) {
return true
}

if (handleTransitionQueue(evaluationId)) {
return true
}
Expand Down
32 changes: 32 additions & 0 deletions formula/src/test/java/com/instacart/formula/FormulaRuntimeTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@ import com.instacart.formula.subjects.EventFormula
import com.instacart.formula.subjects.ExtremelyNestedFormula
import com.instacart.formula.subjects.FromObservableWithInputFormula
import com.instacart.formula.subjects.HasChildFormula
import com.instacart.formula.subjects.MultiChildIndirectStateChangeRobot
import com.instacart.formula.subjects.InputChangeWhileFormulaRunningRobot
import com.instacart.formula.subjects.KeyUsingListFormula
import com.instacart.formula.subjects.MessageFormula
import com.instacart.formula.subjects.MixingCallbackUseWithKeyUse
import com.instacart.formula.subjects.MultipleChildEvents
import com.instacart.formula.subjects.NestedCallbackCallRobot
import com.instacart.formula.subjects.NestedChildTransitionAfterNoEvaluationPass
import com.instacart.formula.subjects.NestedKeyFormula
import com.instacart.formula.subjects.NestedTerminationWithInputChanged
Expand All @@ -41,6 +43,7 @@ import com.instacart.formula.subjects.OptionalChildFormula
import com.instacart.formula.subjects.OptionalEventCallbackFormula
import com.instacart.formula.subjects.ParallelChildFormulaFiresEventOnStart
import com.instacart.formula.subjects.ParentTransitionOnChildActionStart
import com.instacart.formula.subjects.ParentUpdateChildAndSelfOnEventRobot
import com.instacart.formula.subjects.PendingActionFormulaTerminatedOnActionInit
import com.instacart.formula.subjects.RemovingTerminateStreamSendsNoMessagesFormula
import com.instacart.formula.subjects.RootFormulaKeyTestSubject
Expand Down Expand Up @@ -673,6 +676,35 @@ class FormulaRuntimeTest(val runtime: TestableRuntime, val name: String) {
.assertCurrentValue(2)
}

@Test fun `parent updates a child and self in a single action`() {
val robot = ParentUpdateChildAndSelfOnEventRobot(runtime)
robot.start()
robot.subject.output { onAction() }
robot.subject.output {
assertThat(childValue).isEqualTo(1)
assertThat(parentValue).isEqualTo(3)
}
}

@Test
fun `formula calls an event listener from a transition`() {
val robot = NestedCallbackCallRobot(runtime)
robot.start()
robot.subject.output { onAction() }
robot.subject.output { assertThat(value).isEqualTo(1) }
}

@Test
fun `formula calls own event listener which starts multiple transitions`() {
val robot = MultiChildIndirectStateChangeRobot(runtime)
robot.start()
robot.subject.output { onAction() }
robot.subject.output {
assertThat(childValue).isEqualTo(2)
assertThat(parentValue).isEqualTo(3)
}
}

@Test
fun `action runAgain`() {
val inspector = CountingInspector()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package com.instacart.formula.subjects

import com.instacart.formula.Evaluation
import com.instacart.formula.Formula
import com.instacart.formula.Snapshot
import com.instacart.formula.rxjava3.RxAction
import com.instacart.formula.test.TestableRuntime
import io.reactivex.rxjava3.core.Observable

class MultiChildIndirectStateChangeRobot(runtime: TestableRuntime) {
val subject = runtime.test(Parent())

fun start() = apply {
subject.input(Unit)
}

class Child : Formula<Child.Input, Child.State, Child.Output>() {
data class Input(
val preAction: () -> Unit = {},
)

data class State(
val actionId: Int = 0,
val value: Int = 0,
)

data class Output(
val value: Int,
val onChildAction: () -> Unit,
)

override fun initialState(input: Input): State = State()

override fun Snapshot<Input, State>.evaluate(): Evaluation<Output> {
return Evaluation(
output = Output(
value = state.value,
onChildAction = context.callback {
val newState = state.copy(actionId = state.actionId + 1)
transition(newState)
}
),
actions = context.actions {
if (state.actionId > 0) {
RxAction.fromObservable(state.actionId) {
input.preAction()

// Increment two times
Observable.just(1, 1)
}.onEvent {
val newState = state.copy(value = state.value + it)
transition(newState)
}
}
}
)
}
}

class Parent : Formula<Unit, Parent.State, Parent.Output>() {
data class State(
val actionId: Int = 0,
val value: Int = 0,
)

data class Output(
val parentValue: Int,
val childValue: Int,
val onAction: () -> Unit
)

private val incrementFormula = Child()

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

override fun Snapshot<Unit, State>.evaluate(): Evaluation<Output> {
val next = context.callback {
val newState = state.copy(actionId = state.actionId + 1)
transition(newState)
}

val firstChild = context.key("first") {
context.child(incrementFormula, Child.Input())
}

val secondChild = context.key("second") {
context.child(
formula = incrementFormula,
input = Child.Input(firstChild.onChildAction),
)
}

return Evaluation(
output = Output(
parentValue = state.value,
childValue = secondChild.value,
onAction = context.callback {
transition {
next()
}
}
),
actions = context.actions {
if (state.actionId > 0) {
RxAction.fromObservable(state.actionId) {
// Call child
secondChild.onChildAction()

// Emit events
Observable.just(1, 1, 1)
}.onEvent {
val newState = state.copy(value = state.value + it)
transition(newState)
}
}
}
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.instacart.formula.subjects

import com.instacart.formula.Evaluation
import com.instacart.formula.Formula
import com.instacart.formula.Snapshot
import com.instacart.formula.test.TestableRuntime

class NestedCallbackCallRobot(runtime: TestableRuntime) {
val subject = runtime.test(Parent())

fun start() = apply {
subject.input(Unit)
}

class Parent : Formula<Unit, Int, Parent.Output>() {
data class Output(
val value: Int,
val onAction: () -> Unit,
)

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

override fun Snapshot<Unit, Int>.evaluate(): Evaluation<Output> {
val next = context.callback {
transition(state + 1)
}
return Evaluation(
output = Output(
value = state,
onAction = context.callback {
transition {
next()
}
}
)
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.instacart.formula.subjects

import com.instacart.formula.Evaluation
import com.instacart.formula.Formula
import com.instacart.formula.Snapshot
import com.instacart.formula.rxjava3.RxAction
import com.instacart.formula.test.TestableRuntime
import com.instacart.formula.types.IncrementFormula
import io.reactivex.rxjava3.core.Observable

class ParentUpdateChildAndSelfOnEventRobot(
runtime: TestableRuntime,
) {

val subject = runtime.test(Parent())

fun start() = apply {
subject.input(Unit)
}

class Parent : Formula<Unit, Parent.State, Parent.Output>() {
data class State(
val actionId: Int = 0,
val value: Int = 0,
)

data class Output(
val parentValue: Int,
val childValue: Int,
val onAction: () -> Unit
)

private val incrementFormula = IncrementFormula()

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

override fun Snapshot<Unit, State>.evaluate(): Evaluation<Output> {
val incrementOutput = context.child(incrementFormula)
return Evaluation(
output = Output(
parentValue = state.value,
childValue = incrementOutput.value,
onAction = context.callback {
val newState = state.copy(actionId = state.actionId + 1)
transition(newState)
}
),
actions = context.actions {
if (state.actionId > 0) {
RxAction.fromObservable(state.actionId) {
// Call child
incrementOutput.onIncrement()

// Emit events
Observable.just(1, 1, 1)
}.onEvent {
val newState = state.copy(value = state.value + it)
transition(newState)
}
}
}
)
}
}
}

0 comments on commit be776ae

Please sign in to comment.