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

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

commit a4848aff5e5da4ed8935dc06150f0df2d2ff9134
Author: Xinyuan Lin <[email protected]>
AuthorDate: Sat Jun 20 20:58:45 2026 -0700

    test(workflow-operator): add unit test coverage for operator metadata types 
(OperatorInfo, PortDescription, PortDescriptor) (#5832)
    
    ### What changes were proposed in this PR?
    
    Pin behavior of three core operator-metadata types in
    `common/workflow-operator/` — the data/trait types every `LogicalOp`
    relies on for port wiring and metadata. No production-code changes.
    
    | Spec | Source class | Tests |
    | --- | --- | --- |
    | `OperatorInfoSpec` | `OperatorInfo` (case class) | 4 |
    | `PortDescriptionSpec` | `PortDescription` (case class) | 5 |
    | `PortDescriptorSpec` | `PortDescriptor` (trait) | 2 |
    
    **Behavior pinned**
    
    | Surface | Contract |
    | --- | --- |
    | `OperatorInfo` fields | constructor field exposure; the four boolean
    flags default to `false` and round-trip `true` when set; value equality
    |
    | `OperatorInfo.forVisualization` | `inputPorts ==
    List(InputPort(disallowMultiLinks = true))`, `outputPorts ==
    List(OutputPort(mode = SINGLE_SNAPSHOT))` |
    | `PortDescription` | field exposure; `dependencies` defaults
    `List.empty` + accepts explicit deps; value equality + `copy`;
    `@JsonIgnoreProperties("allowMultiInputs")` backward-compat marker
    (verified via reflection) |
    | `PortDescriptor` (trait) | `inputPorts`/`outputPorts` default to
    `null` (not empty) and are reassignable |
    
    **Note for reviewers:** scoped to the pure case-class/trait types only.
    The `OperatorMetadataGenerator` object in the same file is deliberately
    **not** tested — it eagerly reflects over the entire `LogicalOp`
    registry at init (instantiating every descriptor), which is not
    pure-unit-testable.
    
    ### Any related issues, documentation, discussions?
    
    Closes #5829.
    
    ### How was this PR tested?
    
    - `sbt "WorkflowOperator/testOnly
    org.apache.texera.amber.operator.metadata.OperatorInfoSpec
    org.apache.texera.amber.operator.PortDescriptionSpec
    org.apache.texera.amber.operator.PortDescriptorSpec"` — 11 tests, all
    green
    - `sbt "WorkflowOperator/Test/scalafmtCheck"` and `sbt
    "WorkflowOperator/Test/scalafix --check"` — clean
    - CI to confirm
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (Opus 4.8 [1M context])
---
 .../amber/operator/PortDescriptionSpec.scala       | 72 ++++++++++++++++++++
 .../texera/amber/operator/PortDescriptorSpec.scala | 52 +++++++++++++++
 .../amber/operator/metadata/OperatorInfoSpec.scala | 77 ++++++++++++++++++++++
 3 files changed, 201 insertions(+)

diff --git 
a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/PortDescriptionSpec.scala
 
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/PortDescriptionSpec.scala
new file mode 100644
index 0000000000..3fc1358d97
--- /dev/null
+++ 
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/PortDescriptionSpec.scala
@@ -0,0 +1,72 @@
+/*
+ * 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.operator
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties
+import org.apache.texera.amber.core.workflow.UnknownPartition
+import org.scalatest.flatspec.AnyFlatSpec
+import org.scalatest.matchers.should.Matchers
+
+class PortDescriptionSpec extends AnyFlatSpec with Matchers {
+
+  private def desc(deps: List[Int] = List.empty): PortDescription =
+    PortDescription(
+      "p0",
+      "input-0",
+      disallowMultiInputs = true,
+      isDynamicPort = false,
+      UnknownPartition(),
+      deps
+    )
+
+  "PortDescription" should "expose all constructor fields" in {
+    val d = PortDescription(
+      "p1",
+      "out",
+      disallowMultiInputs = false,
+      isDynamicPort = true,
+      UnknownPartition()
+    )
+    d.portID shouldBe "p1"
+    d.displayName shouldBe "out"
+    d.disallowMultiInputs shouldBe false
+    d.isDynamicPort shouldBe true
+    d.partitionRequirement shouldBe UnknownPartition()
+  }
+
+  it should "default dependencies to an empty list" in {
+    desc().dependencies shouldBe empty
+  }
+
+  it should "accept explicit dependencies" in {
+    desc(List(1, 2, 3)).dependencies shouldBe List(1, 2, 3)
+  }
+
+  it should "be value-equal and support copy" in {
+    desc() shouldBe desc()
+    desc().copy(displayName = "changed") should not be desc()
+  }
+
+  it should "ignore the legacy 'allowMultiInputs' JSON key for backward 
compat" in {
+    val ann = 
classOf[PortDescription].getAnnotation(classOf[JsonIgnoreProperties])
+    ann should not be null
+    ann.value should contain("allowMultiInputs")
+  }
+}
diff --git 
a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/PortDescriptorSpec.scala
 
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/PortDescriptorSpec.scala
new file mode 100644
index 0000000000..624abe4c44
--- /dev/null
+++ 
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/PortDescriptorSpec.scala
@@ -0,0 +1,52 @@
+/*
+ * 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.operator
+
+import org.apache.texera.amber.core.workflow.UnknownPartition
+import org.scalatest.flatspec.AnyFlatSpec
+import org.scalatest.matchers.should.Matchers
+
+class PortDescriptorSpec extends AnyFlatSpec with Matchers {
+
+  private def portDesc(id: String, name: String): PortDescription =
+    PortDescription(
+      id,
+      name,
+      disallowMultiInputs = false,
+      isDynamicPort = false,
+      UnknownPartition()
+    )
+
+  "PortDescriptor" should "default inputPorts and outputPorts to null (not an 
empty list)" in {
+    val pd = new PortDescriptor {}
+    pd.inputPorts shouldBe null
+    pd.outputPorts shouldBe null
+  }
+
+  it should "allow inputPorts and outputPorts to be reassigned" in {
+    val pd = new PortDescriptor {}
+    val in = List(portDesc("i", "in"))
+    val out = List(portDesc("o", "out"))
+    pd.inputPorts = in
+    pd.outputPorts = out
+    pd.inputPorts shouldBe in
+    pd.outputPorts shouldBe out
+  }
+}
diff --git 
a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/metadata/OperatorInfoSpec.scala
 
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/metadata/OperatorInfoSpec.scala
new file mode 100644
index 0000000000..6eba92e8a2
--- /dev/null
+++ 
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/metadata/OperatorInfoSpec.scala
@@ -0,0 +1,77 @@
+/*
+ * 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.operator.metadata
+
+import org.apache.texera.amber.core.workflow.OutputPort.OutputMode
+import org.apache.texera.amber.core.workflow.{InputPort, OutputPort}
+import org.scalatest.flatspec.AnyFlatSpec
+import org.scalatest.matchers.should.Matchers
+
+class OperatorInfoSpec extends AnyFlatSpec with Matchers {
+
+  "OperatorInfo" should
+    "expose its constructor fields and default the four boolean flags to 
false" in {
+    val info = OperatorInfo("Op", "desc", "Group", List(InputPort()), 
List(OutputPort()))
+    info.userFriendlyName shouldBe "Op"
+    info.operatorDescription shouldBe "desc"
+    info.operatorGroupName shouldBe "Group"
+    info.inputPorts should have length 1
+    info.outputPorts should have length 1
+    info.dynamicInputPorts shouldBe false
+    info.dynamicOutputPorts shouldBe false
+    info.supportReconfiguration shouldBe false
+    info.allowPortCustomization shouldBe false
+  }
+
+  it should "carry the boolean flags when explicitly set" in {
+    val info = OperatorInfo(
+      "Op",
+      "d",
+      "G",
+      List(InputPort()),
+      List(OutputPort()),
+      dynamicInputPorts = true,
+      dynamicOutputPorts = true,
+      supportReconfiguration = true,
+      allowPortCustomization = true
+    )
+    info.dynamicInputPorts shouldBe true
+    info.dynamicOutputPorts shouldBe true
+    info.supportReconfiguration shouldBe true
+    info.allowPortCustomization shouldBe true
+  }
+
+  "OperatorInfo.forVisualization" should
+    "build a single disallow-multi-links input and a single SINGLE_SNAPSHOT 
output" in {
+    val info = OperatorInfo.forVisualization("Viz", "render", "VizGroup")
+    info.userFriendlyName shouldBe "Viz"
+    info.operatorDescription shouldBe "render"
+    info.operatorGroupName shouldBe "VizGroup"
+    info.inputPorts shouldBe List(InputPort(disallowMultiLinks = true))
+    info.outputPorts shouldBe List(OutputPort(mode = 
OutputMode.SINGLE_SNAPSHOT))
+  }
+
+  "OperatorInfo equality" should "be value-based (case class)" in {
+    val a = OperatorInfo("Op", "d", "G", List(InputPort()), List(OutputPort()))
+    val b = OperatorInfo("Op", "d", "G", List(InputPort()), List(OutputPort()))
+    a shouldBe b
+    a.copy(supportReconfiguration = true) should not be b
+  }
+}

Reply via email to