You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@texera.apache.org by gi...@apache.org on 2026/07/10 23:25:48 UTC

(texera) 01/01: test(amber): cover RetrieveWorkflowStateHandler state-propagation dispatch (#6310)

This is an automated email from the ASF dual-hosted git repository.

github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-6310-ecdb1a6a3324f871db4b9a2c606a4d6ebf1e0b01
in repository https://gitbox.apache.org/repos/asf/texera.git

commit 9f9f85b88687ca398c48f7426cecbdc187f935b8
Author: Xinyuan Lin <xi...@uci.edu>
AuthorDate: Fri Jul 10 16:25:27 2026 -0700

    test(amber): cover RetrieveWorkflowStateHandler state-propagation dispatch (#6310)
    
    ### What changes were proposed in this PR?
    
    Adds a unit test for
    `RetrieveWorkflowStateHandler.retrieveWorkflowState`. Using a real
    `CoordinatorProcessor` with a capturing output handler and a
    `CoordinatorAsyncRPCHandlerInitializer`, it invokes the handler and
    asserts the dispatched `PropagateEmbeddedControlMessage` request —
    covering target-operator collection, request construction, dispatch, and
    response-transform registration, for both an empty and a populated
    physical plan.
    
    ### Any related issues, documentation, discussions?
    
    Closes #6307.
    
    ### How was this PR tested?
    
    Added a new `RetrieveWorkflowStateHandlerSpec` (2 cases), a plain-JVM
    `AnyFlatSpec` — no ActorSystem, DB, Python, or storage. Verified locally
    on Java 17 (`WorkflowExecutionService/testOnly`, plus `scalafixAll
    --check` clean). Test-only change — no production code is modified.
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (Opus 4.8 [1M context])
    
    ---------
    
    Co-authored-by: probe <pr...@x>
---
 .../RetrieveWorkflowStateHandlerSpec.scala         | 139 +++++++++++++++++++++
 1 file changed, 139 insertions(+)

diff --git a/amber/src/test/scala/org/apache/texera/amber/engine/architecture/coordinator/promisehandlers/RetrieveWorkflowStateHandlerSpec.scala b/amber/src/test/scala/org/apache/texera/amber/engine/architecture/coordinator/promisehandlers/RetrieveWorkflowStateHandlerSpec.scala
new file mode 100644
index 0000000000..cb5a8d487d
--- /dev/null
+++ b/amber/src/test/scala/org/apache/texera/amber/engine/architecture/coordinator/promisehandlers/RetrieveWorkflowStateHandlerSpec.scala
@@ -0,0 +1,139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.texera.amber.engine.architecture.coordinator.promisehandlers
+
+import org.apache.texera.amber.core.workflow.{PhysicalPlan, WorkflowContext}
+import org.apache.texera.amber.engine.architecture.coordinator.{
+  CoordinatorAsyncRPCHandlerInitializer,
+  CoordinatorConfig,
+  CoordinatorProcessor
+}
+import org.apache.texera.amber.engine.architecture.rpc.controlcommands.EmbeddedControlMessageType.NO_ALIGNMENT
+import org.apache.texera.amber.engine.architecture.rpc.controlcommands.{
+  AsyncRPCContext,
+  ControlInvocation,
+  EmptyRequest,
+  PropagateEmbeddedControlMessageRequest
+}
+import org.apache.texera.amber.engine.architecture.rpc.workerservice.WorkerServiceGrpc.METHOD_RETRIEVE_STATE
+import org.apache.texera.amber.engine.architecture.worker.WorkflowWorker.MainThreadDelegateMessage
+import org.apache.texera.amber.engine.common.ambermessage.WorkflowFIFOMessage
+import org.apache.texera.amber.engine.common.virtualidentity.util.COORDINATOR
+import org.apache.texera.amber.engine.e2e.TestUtils.buildWorkflow
+import org.apache.texera.amber.operator.TestOperators
+import org.scalatest.flatspec.AnyFlatSpec
+
+import scala.collection.mutable.ArrayBuffer
+
+/**
+  * `retrieveWorkflowState` is the coordinator-side entry point that fans a "collect your state" request
+  * out to every operator in the running workflow.
+  *
+  * It does so by wrapping the workflow's physical operators into a single `PropagateEmbeddedControlMessageRequest`
+  * (targeting the `retrieveState` worker method) and dispatching it to itself via `coordinatorInterface`. The
+  * returned `Future` only completes once every worker replies, which never happens in a plain-JVM spec with no
+  * live workers -- so these tests do not await it. Instead they capture the one control message the handler
+  * dispatches synchronously through the coordinator's output handler and assert on its shape.
+  *
+  * The harness mirrors the worker-side `EndHandlerSpec`: build a real `CoordinatorProcessor` plus a
+  * `CoordinatorAsyncRPCHandlerInitializer`, then invoke the handler method directly (no ActorSystem).
+  */
+class RetrieveWorkflowStateHandlerSpec extends AnyFlatSpec {
+
+  private val rpcContext = AsyncRPCContext(COORDINATOR, COORDINATOR)
+
+  /**
+    * Build a coordinator handler initializer whose dispatched output messages are appended to the returned
+    * buffer. `workflowScheduler.physicalPlan` defaults to null, so callers must assign it before invoking the
+    * handler (which dereferences it).
+    */
+  private def newFixture()
+      : (CoordinatorAsyncRPCHandlerInitializer, ArrayBuffer[WorkflowFIFOMessage]) = {
+    val sent = ArrayBuffer[WorkflowFIFOMessage]()
+    val outputHandler: Either[MainThreadDelegateMessage, WorkflowFIFOMessage] => Unit = {
+      case Right(m) => sent += m
+      case _        => ()
+    }
+    val cp = new CoordinatorProcessor(
+      new WorkflowContext(),
+      CoordinatorConfig(None, None, None, None),
+      COORDINATOR,
+      outputHandler
+    )
+    (new CoordinatorAsyncRPCHandlerInitializer(cp), sent)
+  }
+
+  /**
+    * Assert that exactly one control message was dispatched and it carries a propagate-ECM invocation, then
+    * return the unwrapped request for further inspection.
+    */
+  private def dispatchedRequest(
+      sent: ArrayBuffer[WorkflowFIFOMessage]
+  ): PropagateEmbeddedControlMessageRequest = {
+    assert(sent.size == 1)
+    sent.head.payload match {
+      case ci: ControlInvocation =>
+        assert(ci.methodName == "propagateEmbeddedControlMessage")
+        ci.command match {
+          case req: PropagateEmbeddedControlMessageRequest => req
+          case other                                       => fail(s"unexpected control command: $other")
+        }
+      case other => fail(s"unexpected dispatched payload: $other")
+    }
+  }
+
+  behavior of "RetrieveWorkflowStateHandler"
+
+  it should "dispatch a single propagate-ECM control message with no targets for an empty plan" in {
+    val (init, sent) = newFixture()
+    init.cp.workflowScheduler.physicalPlan = PhysicalPlan(Set.empty, Set.empty)
+
+    init.retrieveWorkflowState(EmptyRequest(), rpcContext)
+
+    val req = dispatchedRequest(sent)
+    assert(req.targetOps.isEmpty)
+    assert(req.scope.isEmpty)
+    // No running region executions on a fresh WorkflowExecution, so nothing to start propagation from.
+    assert(req.sourceOpToStartProp.isEmpty)
+    assert(req.ecmType == NO_ALIGNMENT)
+    assert(req.command == EmptyRequest())
+    assert(req.methodName == METHOD_RETRIEVE_STATE.getBareMethodName)
+    assert(req.id.id.startsWith("RetrieveWorkflowState_"))
+  }
+
+  it should "target every operator of a populated physical plan" in {
+    val physicalPlan = buildWorkflow(
+      List(TestOperators.headerlessSmallCsvScanOpDesc()),
+      List(),
+      new WorkflowContext()
+    ).physicalPlan
+    val (init, sent) = newFixture()
+    init.cp.workflowScheduler.physicalPlan = physicalPlan
+
+    init.retrieveWorkflowState(EmptyRequest(), rpcContext)
+
+    val expectedOps = physicalPlan.operators.map(_.id)
+    val req = dispatchedRequest(sent)
+    assert(expectedOps.nonEmpty)
+    // `operators` is a Set, so compare set-wise rather than assuming a stable Seq order.
+    assert(req.targetOps.toSet == expectedOps)
+    assert(req.scope.toSet == expectedOps)
+  }
+}