Skip to content

1311: Dispatcher Tests for RenderWorkflowIn on Android #1350

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

steve-the-edwards
Copy link
Contributor

@steve-the-edwards steve-the-edwards commented Jun 19, 2025

A set of tests for using renderWorkflowIn with particular dispatchers on Android. These are used in two ways:

  1. To ensure we are satisfying the guarantee we want to keep: Updates from the runtime in response to an event are processed before the next Choreographer frame on Android.
  2. We are effectively conflating renderings on Android when using a particular dispatcher (e.g., in this case the Main.immediate dispatcher). Now, this 2nd category is currently failing! That's part of the fix that needs to happen for On Android, process all updates from actions from an event before the next frame (Choreographer.doFrame) #1311 . So we merge these red tests first.

@steve-the-edwards steve-the-edwards force-pushed the sedwards/move-render-android branch 2 times, most recently from b116d56 to 2deda9c Compare June 19, 2025 18:18
@steve-the-edwards steve-the-edwards force-pushed the sedwards/move-render-android branch from e00bd02 to 21e5929 Compare June 19, 2025 18:25
@steve-the-edwards steve-the-edwards marked this pull request as ready for review June 19, 2025 18:30
@OptIn(WorkflowExperimentalRuntime::class)
@Burst
class AndroidDispatchersRenderWorkflowInTest(
private val runtime: RuntimeOptions = RuntimeOptions.DEFAULT
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that burst will create a separate test class for each runtime config combination and run them separately. (i'll need to migrate my hand rolled parameterization in the other tests for runtime config to this soon).

Choreographers.postOnFrameRendered {
// We are expecting this to happen last, after we get the rendering!
expectInOrder(2)
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right before the event that triggers the change I add a callback for the end of the frame (through papa) that expects to be done at order position 2 (the last position for this test, critically after we've received the updated rendering we want from the change).

@pyricau - does this look like a reasonable way to test this guarantee?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add an expectInOrder to the end of the test, maybe after the runTest block, to ensure that this callback is also invoked?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean as a verification that we actually have a choreographer on this thread we are running on? Ya that's probably good.

Choreographers.postOnFrameRendered {
// This should be happening last!
expectInOrder(2)
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note same thing here, right before the callback event we add the listener to the end of the next frame.


testScheduler.advanceUntilIdle()

renderedMutex.lock()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Could we use Turbine to write the assertions on the renderings flow?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried and this version was more readable/intuitive I thought.

Comment on lines +263 to +290
private class SimpleScreen(
val name: String = "Empty",
val callback: () -> Unit,
)

private val orderIndex = AtomicInteger(0)

private fun resetOrderCounter() {
orderIndex.set(0)
}

@Before
fun setup() {
resetOrderCounter()
}

private fun expectInOrder(
expected: Int,
prefix: String = ""
) {
val localActual = orderIndex.getAndIncrement()
assertEquals(
expected,
localActual,
"$prefix: This should have happened" +
" in a different order position:"
)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Shouldn't these be at the top or bottom of the class, not in the middle of test methods?

workflow = workflow,
scope = backgroundScope +
Dispatchers.Main.immediate,
props = MutableStateFlow(Unit).asStateFlow(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oof, I can't believe there's no stateFlowOf().

Choreographers.postOnFrameRendered {
// We are expecting this to happen last, after we get the rendering!
expectInOrder(2)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add an expectInOrder to the end of the test, maybe after the runTest block, to ensure that this callback is also invoked?

Comment on lines +392 to +414
DEFAULT(RENDER_PER_ACTION),
RENDER_ONLY(setOf(RENDER_ONLY_WHEN_STATE_CHANGES)),
CONFLATE(setOf(CONFLATE_STALE_RENDERINGS)),
STABLE(setOf(STABLE_EVENT_HANDLERS)),
RENDER_ONLY_CONFLATE(setOf(CONFLATE_STALE_RENDERINGS, RENDER_ONLY_WHEN_STATE_CHANGES)),
RENDER_ONLY_PARTIAL(setOf(RENDER_ONLY_WHEN_STATE_CHANGES, PARTIAL_TREE_RENDERING)),
RENDER_ONLY_CONFLATE_STABLE(
setOf(CONFLATE_STALE_RENDERINGS, RENDER_ONLY_WHEN_STATE_CHANGES, STABLE_EVENT_HANDLERS)
),
RENDER_ONLY_PARTIAL_STABLE(
setOf(RENDER_ONLY_WHEN_STATE_CHANGES, PARTIAL_TREE_RENDERING, STABLE_EVENT_HANDLERS)
),
RENDER_ONLY_CONFLATE_PARTIAL(
setOf(CONFLATE_STALE_RENDERINGS, RENDER_ONLY_WHEN_STATE_CHANGES, PARTIAL_TREE_RENDERING)
),
RENDER_ONLY_CONFLATE_PARTIAL_STABLE(
setOf(
CONFLATE_STALE_RENDERINGS,
RENDER_ONLY_WHEN_STATE_CHANGES,
PARTIAL_TREE_RENDERING,
STABLE_EVENT_HANDLERS
)
)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Holy crap, do we really need to test all these combinations? Are we running (or planning to) all of these in production??

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, but I wanted not to have restrictions (or unknown bugs) on what combinations could be used.

val renderings = renderWorkflowIn(
workflow = workflow,
scope = backgroundScope +
Dispatchers.Main.immediate,
Copy link
Collaborator

@zach-klippenstein zach-klippenstein Jun 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider pulling this out into a file constant or something so that when you change it later it's easier to ensure that all usages are changed. And could probably pull some other common values out of the tests too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants