Skip to content

Commit

Permalink
985: Add Ability to track whether state changed; Add Config for Rende…
Browse files Browse the repository at this point in the history
…r on State Change Only
  • Loading branch information
steve-the-edwards committed Jun 8, 2023
1 parent afd5589 commit 2db3e12
Show file tree
Hide file tree
Showing 26 changed files with 687 additions and 206 deletions.
153 changes: 152 additions & 1 deletion .github/workflows/kotlin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,62 @@ jobs :
with :
report_paths : '**/build/test-results/test/TEST-*.xml'

jvm-stateChange-runtime-test :
name : Render On State Change Only Runtime JVM Tests
runs-on : ubuntu-latest
timeout-minutes : 20
steps :
- uses : actions/checkout@v3
- uses : gradle/wrapper-validation-action@v1
- name : set up JDK 11
uses : actions/setup-java@v3
with :
distribution : 'zulu'
java-version : 11

## Actual task
- uses : gradle/gradle-build-action@v2
name : Check with Gradle
with :
arguments : |
jvmTest --continue -Pworkflow.runtime=baseline-stateChange
cache-read-only : false

# Report as Github Pull Request Check.
- name : Publish Test Report
uses : mikepenz/action-junit-report@v3
if : always() # always run even if the previous step fails
with :
report_paths : '**/build/test-results/test/TEST-*.xml'

jvm-conflate-stateChange-runtime-test :
name : Render On State Change Only Runtime JVM Tests
runs-on : ubuntu-latest
timeout-minutes : 20
steps :
- uses : actions/checkout@v3
- uses : gradle/wrapper-validation-action@v1
- name : set up JDK 11
uses : actions/setup-java@v3
with :
distribution : 'zulu'
java-version : 11

## Actual task
- uses : gradle/gradle-build-action@v2
name : Check with Gradle
with :
arguments : |
jvmTest --continue -Pworkflow.runtime=conflate-stateChange
cache-read-only : false

# Report as Github Pull Request Check.
- name : Publish Test Report
uses : mikepenz/action-junit-report@v3
if : always() # always run even if the previous step fails
with :
report_paths : '**/build/test-results/test/TEST-*.xml'

ios-tests :
name : iOS Tests
runs-on : macos-latest
Expand Down Expand Up @@ -280,7 +336,6 @@ jobs :
profile : Galaxy Nexus
api-level : ${{ matrix.api-level }}
arch : x86_64
# Skip the benchmarks as this is running on emulators
script : ./gradlew :benchmarks:performance-poetry:complex-poetry:connectedCheck --continue

- name : Upload results
Expand Down Expand Up @@ -384,6 +439,102 @@ jobs :
name : instrumentation-test-results-${{ matrix.api-level }}
path : ./**/build/reports/androidTests/connected/**

stateChange-runtime-instrumentation-tests :
name : Render on State Change Only Instrumentation tests
runs-on : macos-latest
timeout-minutes : 45
strategy :
# Allow tests to continue on other devices if they fail on one device.
fail-fast : false
matrix :
api-level :
- 29
# Unclear that older versions actually honor command to disable animation.
# Newer versions are reputed to be too slow: https://github.com/ReactiveCircus/android-emulator-runner/issues/222
steps :
- uses : actions/checkout@v3
- name : set up JDK 11
uses : actions/setup-java@v3
with :
distribution : 'zulu'
java-version : 11

## Build before running tests, using cache.
- uses : gradle/gradle-build-action@v2
name : Build instrumented tests
with :
# Unfortunately I don't think we can key this cache based on our project property so
# we clean and rebuild.
arguments : |
clean assembleDebugAndroidTest -Pworkflow.runtime=baseline-stateChange
cache-read-only : false

## Actual task
- name : Instrumentation Tests
uses : reactivecircus/android-emulator-runner@v2
with :
# @ychescale9 suspects Galaxy Nexus is the fastest one
profile : Galaxy Nexus
api-level : ${{ matrix.api-level }}
arch : x86_64
# Skip the benchmarks as this is running on emulators
script : ./gradlew connectedCheck -x :benchmarks:dungeon-benchmark:connectedCheck -x :benchmarks:performance-poetry:complex-benchmark:connectedCheck -x :benchmarks:performance-poetry:complex-poetry:connectedCheck -Pworkflow.runtime=baseline-stateChange

- name : Upload results
if : ${{ always() }}
uses : actions/upload-artifact@v3
with :
name : instrumentation-test-results-${{ matrix.api-level }}
path : ./**/build/reports/androidTests/connected/**

conflate-stateChange-runtime-instrumentation-tests :
name : Render on State Change Only Instrumentation tests
runs-on : macos-latest
timeout-minutes : 45
strategy :
# Allow tests to continue on other devices if they fail on one device.
fail-fast : false
matrix :
api-level :
- 29
# Unclear that older versions actually honor command to disable animation.
# Newer versions are reputed to be too slow: https://github.com/ReactiveCircus/android-emulator-runner/issues/222
steps :
- uses : actions/checkout@v3
- name : set up JDK 11
uses : actions/setup-java@v3
with :
distribution : 'zulu'
java-version : 11

## Build before running tests, using cache.
- uses : gradle/gradle-build-action@v2
name : Build instrumented tests
with :
# Unfortunately I don't think we can key this cache based on our project property so
# we clean and rebuild.
arguments : |
clean assembleDebugAndroidTest -Pworkflow.runtime=conflate-stateChange
cache-read-only : false

## Actual task
- name : Instrumentation Tests
uses : reactivecircus/android-emulator-runner@v2
with :
# @ychescale9 suspects Galaxy Nexus is the fastest one
profile : Galaxy Nexus
api-level : ${{ matrix.api-level }}
arch : x86_64
# Skip the benchmarks as this is running on emulators
script : ./gradlew connectedCheck -x :benchmarks:dungeon-benchmark:connectedCheck -x :benchmarks:performance-poetry:complex-benchmark:connectedCheck -x :benchmarks:performance-poetry:complex-poetry:connectedCheck -Pworkflow.runtime=conflate-stateChange

- name : Upload results
if : ${{ always() }}
uses : actions/upload-artifact@v3
with :
name : instrumentation-test-results-${{ matrix.api-level }}
path : ./**/build/reports/androidTests/connected/**

upload-to-mobiledev :
name : mobile.dev | Build & Upload
runs-on : ubuntu-latest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ class PerformancePoetryActivity : AppCompatActivity() {
installedInterceptor = ActionHandlingTracingInterceptor()
}

val runtimeConfig = RenderPerAction
val runtimeConfig = RenderPerAction()

val component =
PerformancePoetryComponent(installedInterceptor, simulatedPerfConfig, runtimeConfig)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ public class AndroidRuntimeConfigTools {
/**
* Helper for Configuration for the workflow runtime in an application.
* This allows one to specify a project property from the gradle build to choose a runtime.
* e.g. add "-Pworkflow.runtime=timeout" in your gradle build to build the timeout runtime into
* the application.
* e.g. add "-Pworkflow.runtime=conflate" in your gradle build to build the conflate runtime
* into the application.
*
* Note that this must be specified in the application built for any ui/integration tests. Call
* this function, and then pass that to the call to [renderWorkflowIn] as the [RuntimeConfig].
Expand All @@ -22,12 +22,21 @@ public class AndroidRuntimeConfigTools {
* to the UI layer.
* "baseline" : [RenderPerAction] Original Workflow Runtime. Note that this doesn't need to
* be specified as it is the current default and is assumed by this utility.
*
* Then, these can be combined (via '-') with:
* "stateChange" : Only re-render when the state of some WorkflowNode has been changed by an
* action cascade.
*
* E.g., "baseline-stateChange" to turn on the stateChange option with the baseline runtime.
*
*/
@WorkflowExperimentalRuntime
public fun getAppWorkflowRuntimeConfig(): RuntimeConfig {
return when (BuildConfig.WORKFLOW_RUNTIME) {
"conflate" -> ConflateStaleRenderings
else -> RenderPerAction
"conflate" -> ConflateStaleRenderings(renderOnStateChangeOnly = false)
"conflate-stateChange" -> ConflateStaleRenderings(renderOnStateChangeOnly = true)
"baseline-stateChange" -> RenderPerAction(renderOnStateChangeOnly = true)
else -> RuntimeConfig.DEFAULT_CONFIG
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import com.squareup.workflow1.WorkflowExperimentalRuntime
public class JvmTestRuntimeConfigTools {
public companion object {
/**
* Helper for Configuration for the Workflow Runtime while running tests on the JVM.
* Helper for Configuration for the workflow runtime in an application.
* This allows one to specify a project property from the gradle build to choose a runtime.
* e.g. add "-Pworkflow.runtime=timeout" in your gradle build to build the timeout runtime.
* e.g. add "-Pworkflow.runtime=conflate" in your gradle build to build the conflate runtime
* into the application.
*
* The [WorkflowTestRuntime] already calls this utility, but if starting your own runtime, then
* call this function and pass the result to the call to [renderWorkflowIn] as the
Expand All @@ -21,13 +22,21 @@ public class JvmTestRuntimeConfigTools {
* to the UI layer.
* "baseline" : [RenderPerAction] Original Workflow Runtime. Note that this doesn't need to
* be specified as it is the current default and is assumed by this utility.
*
* Then, these can be combined (via '-') with:
* "stateChange" : Only re-render when the state of some WorkflowNode has been changed by an
* action cascade.
*
* E.g., "baseline-stateChange" to turn on the stateChange option with the baseline runtime.
*
*/
@OptIn(WorkflowExperimentalRuntime::class)
public fun getTestRuntimeConfig(): RuntimeConfig {
val runtimeConfig = System.getProperty("workflow.runtime", "baseline")
return when (runtimeConfig) {
"conflate" -> ConflateStaleRenderings
else -> RenderPerAction
return when (System.getProperty("workflow.runtime", "baseline")) {
"conflate" -> ConflateStaleRenderings(renderOnStateChangeOnly = false)
"conflate-stateChange" -> ConflateStaleRenderings(renderOnStateChangeOnly = true)
"baseline-stateChange" -> RenderPerAction(renderOnStateChangeOnly = true)
else -> RuntimeConfig.DEFAULT_CONFIG
}
}
}
Expand Down
16 changes: 15 additions & 1 deletion workflow-core/api/workflow-core.api
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
public final class com/squareup/workflow1/ActionApplied : com/squareup/workflow1/ActionProcessingResult {
public fun <init> (Lcom/squareup/workflow1/WorkflowOutput;Z)V
public synthetic fun <init> (Lcom/squareup/workflow1/WorkflowOutput;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Lcom/squareup/workflow1/WorkflowOutput;
public final fun component2 ()Z
public final fun copy (Lcom/squareup/workflow1/WorkflowOutput;Z)Lcom/squareup/workflow1/ActionApplied;
public static synthetic fun copy$default (Lcom/squareup/workflow1/ActionApplied;Lcom/squareup/workflow1/WorkflowOutput;ZILjava/lang/Object;)Lcom/squareup/workflow1/ActionApplied;
public fun equals (Ljava/lang/Object;)Z
public final fun getOutput ()Lcom/squareup/workflow1/WorkflowOutput;
public final fun getStateChanged ()Z
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public abstract interface class com/squareup/workflow1/ActionProcessingResult {
}

Expand Down Expand Up @@ -271,7 +285,7 @@ public final class com/squareup/workflow1/WorkflowIdentifierType$Unsnapshottable
public fun toString ()Ljava/lang/String;
}

public final class com/squareup/workflow1/WorkflowOutput : com/squareup/workflow1/ActionProcessingResult {
public final class com/squareup/workflow1/WorkflowOutput {
public fun <init> (Ljava/lang/Object;)V
public fun equals (Ljava/lang/Object;)Z
public final fun getValue ()Ljava/lang/Object;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,16 @@ public abstract class WorkflowAction<in PropsT, StateT, out OutputT> {
public val props: @UnsafeVariance PropsT,
public var state: StateT
) {
internal var maybeOutput: WorkflowOutput<@UnsafeVariance OutputT>? = null
internal val startingState = state
internal var outputOrNull: WorkflowOutput<@UnsafeVariance OutputT>? = null
private set

/**
* Sets the value the workflow will emit as output when this action is applied.
* If this method is not called, there will be no output.
*/
public fun setOutput(output: @UnsafeVariance OutputT) {
this.maybeOutput = WorkflowOutput(output)
this.outputOrNull = WorkflowOutput(output)
}
}

Expand Down Expand Up @@ -105,24 +106,24 @@ public fun <PropsT, StateT, OutputT> action(
public fun <PropsT, StateT, OutputT> WorkflowAction<PropsT, StateT, OutputT>.applyTo(
props: PropsT,
state: StateT
): Pair<StateT, WorkflowOutput<OutputT>?> {
): Pair<StateT, ActionApplied<OutputT>> {
val updater = Updater(props, state)
updater.apply()
return Pair(updater.state, updater.maybeOutput)
return Pair(
updater.state,
ActionApplied(
output = updater.outputOrNull,
stateChanged = updater.state != updater.startingState,
)
)
}

/**
* Only [WorkflowOutput] needs the generic OutputT so we do not include it in the root
* interface here.
* Box around a potentially nullable [OutputT]
*/
public sealed interface ActionProcessingResult

public object PropsUpdated : ActionProcessingResult

public object ActionsExhausted : ActionProcessingResult

/** Wrapper around a potentially-nullable [OutputT] value. */
public class WorkflowOutput<out OutputT>(public val value: OutputT) : ActionProcessingResult {
public class WorkflowOutput<out OutputT>(
public val value: OutputT
) {
override fun toString(): String = "WorkflowOutput($value)"

override fun equals(other: Any?): Boolean = when {
Expand All @@ -133,3 +134,28 @@ public class WorkflowOutput<out OutputT>(public val value: OutputT) : ActionProc

override fun hashCode(): Int = value.hashCode()
}

/**
* An [ActionProcessingResult] is any possible outcome after the runtime does a loop of processing.
*
* Only [ActionApplied] needs the generic OutputT so we do not include it in the root
* interface here.
*/
public sealed interface ActionProcessingResult

public object PropsUpdated : ActionProcessingResult

public object ActionsExhausted : ActionProcessingResult

/**
* Result of applying an action.
*
* @param output: the potentially null [WorkflowOutput]. If null, then no output was set by the
* action. Otherwise it is a [WorkflowOutput] around the output value of type [OutputT],
* which could be null.
* @param stateChanged: whether or not the action changed the state.
*/
public data class ActionApplied<out OutputT>(
public val output: WorkflowOutput<OutputT>?,
public val stateChanged: Boolean = false,
) : ActionProcessingResult
Loading

0 comments on commit 2db3e12

Please sign in to comment.