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 + } +}
