From 358dc67802d89559b19338cbb29d0abf970728da Mon Sep 17 00:00:00 2001 From: Stephen Edwards Date: Wed, 8 Oct 2025 13:14:16 -0400 Subject: [PATCH 1/4] Collect renderings on context specified in WorkflowLayout.take --- .../squareup/workflow1/ui/WorkflowLayout.kt | 19 ++++++++++--------- .../workflow1/ui/WorkflowLayoutTest.kt | 17 ----------------- 2 files changed, 10 insertions(+), 26 deletions(-) diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowLayout.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowLayout.kt index 790796c680..7fe2a50609 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowLayout.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowLayout.kt @@ -2,6 +2,7 @@ package com.squareup.workflow1.ui import android.content.Context import android.os.Build.VERSION +import android.os.Looper import android.os.Parcel import android.os.Parcelable import android.os.Parcelable.Creator @@ -20,6 +21,7 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext @@ -89,8 +91,8 @@ public class WorkflowLayout( * @param [repeatOnLifecycle] the lifecycle state in which renderings should be actively * updated. Defaults to STARTED, which is appropriate for Activity and Fragment. * @param [collectionContext] additional [CoroutineContext] we want for the coroutine that is - * launched to collect the renderings. This should not override the [CoroutineDispatcher][kotlinx.coroutines.CoroutineDispatcher] - * but may include some other instrumentation elements. + * launched to collect the renderings, can include a different dispatcher - but it should be + * a main thread dispatcher! * * @return the [Job] started to collect [renderings], to give callers the option to * [cancel][Job.cancel] collection -- e.g., before calling [take] again with a new @@ -104,16 +106,15 @@ public class WorkflowLayout( repeatOnLifecycle: State = STARTED, collectionContext: CoroutineContext = EmptyCoroutineContext ): Job { - // We remove the dispatcher as we want to use what is provided by the lifecycle.coroutineScope. - val contextWithoutDispatcher = collectionContext.minusKey(CoroutineDispatcher.Key) - val lifecycleDispatcher = lifecycle.coroutineScope.coroutineContext[CoroutineDispatcher.Key] // Just like https://medium.com/androiddevelopers/a-safer-way-to-collect-flows-from-android-uis-23080b1f8bda - return lifecycle.coroutineScope.launch(contextWithoutDispatcher) { + return lifecycle.coroutineScope.launch { lifecycle.repeatOnLifecycle(repeatOnLifecycle) { - require(coroutineContext[CoroutineDispatcher.Key] == lifecycleDispatcher) { - "Collection dispatch should happen on the lifecycle's dispatcher." + withContext(collectionContext) { + require(Looper.myLooper() == Looper.getMainLooper()) { + "Collection dispatch should happen on the main thread!" + } + renderings.collect { show(it) } } - renderings.collect { show(it) } } } } diff --git a/workflow-ui/core-android/src/test/java/com/squareup/workflow1/ui/WorkflowLayoutTest.kt b/workflow-ui/core-android/src/test/java/com/squareup/workflow1/ui/WorkflowLayoutTest.kt index a0310243ad..fd8fb05692 100644 --- a/workflow-ui/core-android/src/test/java/com/squareup/workflow1/ui/WorkflowLayoutTest.kt +++ b/workflow-ui/core-android/src/test/java/com/squareup/workflow1/ui/WorkflowLayoutTest.kt @@ -92,23 +92,6 @@ internal class WorkflowLayoutTest { unoriginal.show(BScreen(), env) } - @Test fun usesLifecycleDispatcher() { - val lifecycleDispatcher = UnconfinedTestDispatcher() - val collectionContext: CoroutineContext = UnconfinedTestDispatcher() - val testLifecycle = TestLifecycleOwner( - Lifecycle.State.RESUMED, - lifecycleDispatcher - ) - - workflowLayout.take( - lifecycle = testLifecycle.lifecycle, - renderings = flowOf(WrappedScreen(), WrappedScreen()), - collectionContext = collectionContext - ) - - // No crash then we safely removed the dispatcher. - } - @Test fun takes() { val lifecycleDispatcher = UnconfinedTestDispatcher() val testLifecycle = TestLifecycleOwner( From e370e97c322d0b28f25642f942109c4e8dac875e Mon Sep 17 00:00:00 2001 From: steve-the-edwards <8658187+steve-the-edwards@users.noreply.github.com> Date: Wed, 8 Oct 2025 17:19:56 +0000 Subject: [PATCH 2/4] Apply changes from ktLintFormat Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../src/main/java/com/squareup/workflow1/ui/WorkflowLayout.kt | 1 - .../test/java/com/squareup/workflow1/ui/WorkflowLayoutTest.kt | 2 -- 2 files changed, 3 deletions(-) diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowLayout.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowLayout.kt index 7fe2a50609..5b95e6d66f 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowLayout.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowLayout.kt @@ -17,7 +17,6 @@ import androidx.lifecycle.coroutineScope import androidx.lifecycle.repeatOnLifecycle import com.squareup.workflow1.ui.androidx.OnBackPressedDispatcherOwnerKey import com.squareup.workflow1.ui.androidx.WorkflowAndroidXSupport.onBackPressedDispatcherOwnerOrNull -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch diff --git a/workflow-ui/core-android/src/test/java/com/squareup/workflow1/ui/WorkflowLayoutTest.kt b/workflow-ui/core-android/src/test/java/com/squareup/workflow1/ui/WorkflowLayoutTest.kt index fd8fb05692..3a3e5f81a5 100644 --- a/workflow-ui/core-android/src/test/java/com/squareup/workflow1/ui/WorkflowLayoutTest.kt +++ b/workflow-ui/core-android/src/test/java/com/squareup/workflow1/ui/WorkflowLayoutTest.kt @@ -17,14 +17,12 @@ import com.squareup.workflow1.ui.androidx.OnBackPressedDispatcherOwnerKey import com.squareup.workflow1.ui.navigation.WrappedScreen import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config -import kotlin.coroutines.CoroutineContext @RunWith(RobolectricTestRunner::class) // SDK 28 required for the four-arg constructor we use in our custom view classes. From 62499054079a85bb72780f1cc4f21bfbebdfa9d4 Mon Sep 17 00:00:00 2001 From: Stephen Edwards Date: Tue, 14 Oct 2025 13:09:10 -0400 Subject: [PATCH 3/4] Increase Snapshot timeout to 90 minutes --- .github/workflows/publish-snapshot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-snapshot.yml b/.github/workflows/publish-snapshot.yml index d9888800d0..6d3647a00f 100644 --- a/.github/workflows/publish-snapshot.yml +++ b/.github/workflows/publish-snapshot.yml @@ -10,7 +10,7 @@ jobs: publish-snapshot: runs-on: macos-latest if: github.repository == 'square/workflow-kotlin' - timeout-minutes: 45 + timeout-minutes: 90 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 From ee8b977b5efe93b5e6737809733689fef49c492c Mon Sep 17 00:00:00 2001 From: Stephen Edwards Date: Tue, 14 Oct 2025 10:46:57 -0400 Subject: [PATCH 4/4] Add Dispatch Tracking --- workflow-runtime/api/workflow-runtime.api | 1 + .../squareup/workflow1/WorkflowInterceptor.kt | 3 ++ .../workflow1/internal/WorkflowNode.kt | 3 ++ .../SimpleLoggingWorkflowInterceptorTest.kt | 2 + .../workflow1/WorkflowInterceptorTest.kt | 1 + .../ChainedWorkflowInterceptorTest.kt | 2 + .../workflow1/internal/WorkflowNodeTest.kt | 2 + .../tracing/papa/WorkflowPapaTracerTest.kt | 5 +- workflow-tracing/api/workflow-tracing.api | 4 +- .../workflow1/tracing/ConfigSnapshot.kt | 10 +++- .../tracing/WorkflowRuntimeMonitor.kt | 3 +- .../tracing/WorkflowRuntimeMonitorTest.kt | 48 +++++++++++++++---- 12 files changed, 69 insertions(+), 15 deletions(-) diff --git a/workflow-runtime/api/workflow-runtime.api b/workflow-runtime/api/workflow-runtime.api index 4498c73848..bb844c0e2f 100644 --- a/workflow-runtime/api/workflow-runtime.api +++ b/workflow-runtime/api/workflow-runtime.api @@ -123,6 +123,7 @@ public abstract interface class com/squareup/workflow1/WorkflowInterceptor$Workf public abstract fun getParent ()Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession; public abstract fun getRenderKey ()Ljava/lang/String; public abstract fun getRuntimeConfig ()Ljava/util/Set; + public abstract fun getRuntimeContext ()Lkotlin/coroutines/CoroutineContext; public abstract fun getSessionId ()J public abstract fun getWorkflowTracer ()Lcom/squareup/workflow1/WorkflowTracer; public abstract fun isRootWorkflow ()Z diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowInterceptor.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowInterceptor.kt index 728fb5aaee..2347b3531a 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowInterceptor.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowInterceptor.kt @@ -223,6 +223,9 @@ public interface WorkflowInterceptor { /** The [RuntimeConfig] of the runtime this session is executing in. */ public val runtimeConfig: RuntimeConfig + /** The [CoroutineContext] of the runtime this session is executing in. */ + public val runtimeContext: CoroutineContext + /** The optional [WorkflowTracer] of the runtime this session is executing in. */ public val workflowTracer: WorkflowTracer? } diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowNode.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowNode.kt index 22b2d5d31c..82d7ebe7ce 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowNode.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowNode.kt @@ -71,6 +71,9 @@ internal class WorkflowNode( */ override val coroutineContext = baseContext + Job(baseContext[Job]) + CoroutineName(id.toString()) + override val runtimeContext: CoroutineContext + get() = coroutineContext + // WorkflowInstance properties override val identifier: WorkflowIdentifier get() = id.identifier override val renderKey: String get() = id.name diff --git a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/SimpleLoggingWorkflowInterceptorTest.kt b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/SimpleLoggingWorkflowInterceptorTest.kt index 477326786a..44eddb03f5 100644 --- a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/SimpleLoggingWorkflowInterceptorTest.kt +++ b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/SimpleLoggingWorkflowInterceptorTest.kt @@ -3,6 +3,7 @@ package com.squareup.workflow1 import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.cancel +import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.reflect.KType import kotlin.reflect.typeOf @@ -90,6 +91,7 @@ internal class SimpleLoggingWorkflowInterceptorTest { override val parent: WorkflowSession? get() = null override val runtimeConfig: RuntimeConfig = RuntimeConfigOptions.DEFAULT_CONFIG override val workflowTracer: WorkflowTracer? = null + override val runtimeContext: CoroutineContext = EmptyCoroutineContext } private object FakeRenderContext : BaseRenderContext { diff --git a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/WorkflowInterceptorTest.kt b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/WorkflowInterceptorTest.kt index f25b97636c..b9c09d349c 100644 --- a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/WorkflowInterceptorTest.kt +++ b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/WorkflowInterceptorTest.kt @@ -183,6 +183,7 @@ internal class WorkflowInterceptorTest { override val parent: WorkflowSession? = null override val runtimeConfig: RuntimeConfig = RuntimeConfigOptions.DEFAULT_CONFIG override val workflowTracer: WorkflowTracer? = null + override val runtimeContext: CoroutineContext = EmptyCoroutineContext } private object TestWorkflow : StatefulWorkflow() { diff --git a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/ChainedWorkflowInterceptorTest.kt b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/ChainedWorkflowInterceptorTest.kt index 0307d72fc7..a8f88aca42 100644 --- a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/ChainedWorkflowInterceptorTest.kt +++ b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/ChainedWorkflowInterceptorTest.kt @@ -24,6 +24,7 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest +import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.reflect.KType import kotlin.test.Test @@ -359,5 +360,6 @@ internal class ChainedWorkflowInterceptorTest { override val parent: WorkflowSession? = null override val runtimeConfig: RuntimeConfig = RuntimeConfigOptions.DEFAULT_CONFIG override val workflowTracer: WorkflowTracer? = null + override val runtimeContext: CoroutineContext = EmptyCoroutineContext } } diff --git a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/WorkflowNodeTest.kt b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/WorkflowNodeTest.kt index 5658d59ee0..bbe7d70ea7 100644 --- a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/WorkflowNodeTest.kt +++ b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/WorkflowNodeTest.kt @@ -42,6 +42,7 @@ import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.test.runTest import kotlinx.coroutines.withTimeout import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext import kotlin.reflect.typeOf import kotlin.test.AfterTest import kotlin.test.Test @@ -1418,5 +1419,6 @@ internal class WorkflowNodeTest { override val parent: WorkflowSession? = null override val runtimeConfig: RuntimeConfig = RuntimeConfigOptions.DEFAULT_CONFIG override val workflowTracer: WorkflowTracer? = null + override val runtimeContext: CoroutineContext = EmptyCoroutineContext } } diff --git a/workflow-tracing-papa/src/test/java/com/squareup/workflow1/tracing/papa/WorkflowPapaTracerTest.kt b/workflow-tracing-papa/src/test/java/com/squareup/workflow1/tracing/papa/WorkflowPapaTracerTest.kt index 8ddfd07809..a9f1065713 100644 --- a/workflow-tracing-papa/src/test/java/com/squareup/workflow1/tracing/papa/WorkflowPapaTracerTest.kt +++ b/workflow-tracing-papa/src/test/java/com/squareup/workflow1/tracing/papa/WorkflowPapaTracerTest.kt @@ -16,6 +16,8 @@ import com.squareup.workflow1.tracing.RuntimeTraceContext import com.squareup.workflow1.tracing.RuntimeUpdateLogLine import com.squareup.workflow1.tracing.WorkflowSessionInfo import kotlinx.coroutines.test.TestScope +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull @@ -246,7 +248,8 @@ internal class WorkflowPapaTracerTest { private val workflow: TestWorkflow, override val sessionId: Long, override val renderKey: String, - override val parent: WorkflowSession? + override val parent: WorkflowSession?, + override val runtimeContext: CoroutineContext = EmptyCoroutineContext ) : WorkflowSession { override val identifier = workflow.identifier override val runtimeConfig = TestRuntimeConfig() diff --git a/workflow-tracing/api/workflow-tracing.api b/workflow-tracing/api/workflow-tracing.api index 3141eae641..73d9a9b6a8 100644 --- a/workflow-tracing/api/workflow-tracing.api +++ b/workflow-tracing/api/workflow-tracing.api @@ -25,8 +25,10 @@ public final class com/squareup/workflow1/tracing/ChainedWorkflowRuntimeTracerKt } public final class com/squareup/workflow1/tracing/ConfigSnapshot { - public fun (Ljava/util/Set;)V + public fun (Ljava/util/Set;Lkotlinx/coroutines/CoroutineDispatcher;)V + public synthetic fun (Ljava/util/Set;Lkotlinx/coroutines/CoroutineDispatcher;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getConfigAsString ()Ljava/lang/String; + public final fun getRuntimeDispatch ()Lkotlinx/coroutines/CoroutineDispatcher; public final fun getShortConfigAsString ()Ljava/lang/String; } diff --git a/workflow-tracing/src/main/java/com/squareup/workflow1/tracing/ConfigSnapshot.kt b/workflow-tracing/src/main/java/com/squareup/workflow1/tracing/ConfigSnapshot.kt index 3d1c0ea710..2e01d22d73 100644 --- a/workflow-tracing/src/main/java/com/squareup/workflow1/tracing/ConfigSnapshot.kt +++ b/workflow-tracing/src/main/java/com/squareup/workflow1/tracing/ConfigSnapshot.kt @@ -8,13 +8,18 @@ import com.squareup.workflow1.RuntimeConfigOptions.RENDER_ONLY_WHEN_STATE_CHANGE import com.squareup.workflow1.RuntimeConfigOptions.STABLE_EVENT_HANDLERS import com.squareup.workflow1.RuntimeConfigOptions.WORK_STEALING_DISPATCHER import com.squareup.workflow1.WorkflowExperimentalRuntime +import kotlinx.coroutines.CoroutineDispatcher /** * Snapshot of the current [RuntimeConfig] */ @OptIn(WorkflowExperimentalRuntime::class) -public class ConfigSnapshot(config: RuntimeConfig) { - public val configAsString: String = config.toString() +public class ConfigSnapshot( + config: RuntimeConfig, + public val runtimeDispatch: CoroutineDispatcher? = null +) { + + public val configAsString: String = "$config, $runtimeDispatch" public val shortConfigAsString: String by lazy { buildString { @@ -40,6 +45,7 @@ public class ConfigSnapshot(config: RuntimeConfig) { if (config.isEmpty()) { append("Base, ") } + append("Dispatch: ${runtimeDispatch?.toString()?.take(6)}") } } } diff --git a/workflow-tracing/src/main/java/com/squareup/workflow1/tracing/WorkflowRuntimeMonitor.kt b/workflow-tracing/src/main/java/com/squareup/workflow1/tracing/WorkflowRuntimeMonitor.kt index c4a8fba196..318659cca3 100644 --- a/workflow-tracing/src/main/java/com/squareup/workflow1/tracing/WorkflowRuntimeMonitor.kt +++ b/workflow-tracing/src/main/java/com/squareup/workflow1/tracing/WorkflowRuntimeMonitor.kt @@ -29,6 +29,7 @@ import com.squareup.workflow1.tracing.RenderCause.RootPropsChanged import com.squareup.workflow1.tracing.RenderCause.WaitingForOutput import com.squareup.workflow1.tracing.WorkflowRuntimeMonitor.ActionType.CascadeAction import com.squareup.workflow1.tracing.WorkflowRuntimeMonitor.ActionType.QueuedAction +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlin.time.Duration.Companion.nanoseconds @@ -105,7 +106,7 @@ public class WorkflowRuntimeMonitor( if (session.isRootWorkflow) { // Cache the config snapshot for this whole runtime. - configSnapshot = ConfigSnapshot(session.runtimeConfig) + configSnapshot = ConfigSnapshot(session.runtimeConfig, session.runtimeContext[CoroutineDispatcher]) check(renderIncomingCauses.isEmpty()) { "Workflow runtime for $runtimeName already has incoming render on creation triggered by " + "${renderIncomingCauses.lastOrNull()}" diff --git a/workflow-tracing/src/test/java/com/squareup/workflow1/tracing/WorkflowRuntimeMonitorTest.kt b/workflow-tracing/src/test/java/com/squareup/workflow1/tracing/WorkflowRuntimeMonitorTest.kt index 1864a643f1..06664d5425 100644 --- a/workflow-tracing/src/test/java/com/squareup/workflow1/tracing/WorkflowRuntimeMonitorTest.kt +++ b/workflow-tracing/src/test/java/com/squareup/workflow1/tracing/WorkflowRuntimeMonitorTest.kt @@ -21,7 +21,11 @@ import com.squareup.workflow1.identifier import com.squareup.workflow1.tracing.RenderCause.RootCreation import com.squareup.workflow1.tracing.RenderCause.RootPropsChanged import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext import kotlin.reflect.KType import kotlin.test.Test import kotlin.test.assertContains @@ -74,6 +78,23 @@ internal class WorkflowRuntimeMonitorTest { assertTrue(monitor.renderIncomingCauses.first() is RootCreation) } + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun `onSessionStarted captures runtime context`() { + val monitor = WorkflowRuntimeMonitor( + runtimeName = runtimeName, + workflowRuntimeTracers = listOf(fakeRuntimeTracer) + ) + val testWorkflow = TestWorkflow() + val testCoroutineDispatcher = UnconfinedTestDispatcher() + val rootSession = testWorkflow.createRootSession(testCoroutineDispatcher) + val testScope = TestScope(testCoroutineDispatcher) + + monitor.onSessionStarted(testScope, rootSession) + + assertEquals(testCoroutineDispatcher, monitor.configSnapshot.runtimeDispatch) + } + @Test fun `onSessionStarted handles child workflow session`() { val monitor = WorkflowRuntimeMonitor( @@ -647,18 +668,24 @@ internal class WorkflowRuntimeMonitorTest { override fun snapshotState(state: String): Snapshot = Snapshot.of(state) - fun createRootSession(): WorkflowSession = TestWorkflowSession( - workflow = this, - sessionId = 1L, - renderKey = "root", - parent = null - ) - - fun createChildSession(parent: WorkflowSession): WorkflowSession = TestWorkflowSession( + fun createRootSession(context: CoroutineContext = EmptyCoroutineContext): WorkflowSession = + TestWorkflowSession( + workflow = this, + sessionId = 1L, + renderKey = "root", + parent = null, + runtimeContext = context + ) + + fun createChildSession( + parent: WorkflowSession, + context: CoroutineContext = EmptyCoroutineContext + ): WorkflowSession = TestWorkflowSession( workflow = this, sessionId = 2L, renderKey = "child", - parent = parent + parent = parent, + runtimeContext = context ) } @@ -666,7 +693,8 @@ internal class WorkflowRuntimeMonitorTest { private val workflow: TestWorkflow, override val sessionId: Long, override val renderKey: String, - override val parent: WorkflowSession? + override val parent: WorkflowSession?, + override val runtimeContext: CoroutineContext = EmptyCoroutineContext ) : WorkflowSession { override val identifier = workflow.identifier override val runtimeConfig = TestRuntimeConfig()