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/05 01:32:28 UTC

(texera) 01/01: test(workflow-operator): add unit test coverage for interval-join, and sleep (#6098)

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-6098-3084881cd96a6896664ab1bed0e97aa11a85039d
in repository https://gitbox.apache.org/repos/asf/texera.git

commit 3a12e6214c5f45477042374ae3a4a569835d1fda
Author: Xinyuan Lin <xi...@uci.edu>
AuthorDate: Sat Jul 4 18:32:04 2026 -0700

    test(workflow-operator): add unit test coverage for interval-join, and sleep (#6098)
    
    ### What changes were proposed in this PR?
    
    Add unit test coverage for two `workflow-operator` executors, selected
    from the Codecov report. No production-code changes.
    
    | File | Codecov before | New coverage |
    | --- | --- | --- |
    | `IntervalJoinOpExec.scala` | 81% | extends the existing spec to cover
    the YEAR/MONTH/HOUR/MINUTE/SECOND timestamp interval-unit arms and the
    unsupported-key-type `WorkflowRuntimeException` |
    | `SleepOpExec.scala` | 0% | new spec: construction from a serialized
    desc + the pass-through `processTuple` (sleepTime 0) |
    
    The `IntervalJoin` `case None` interval arm requires a JSON payload with
    the field absent (Jackson maps an explicit `None` to `Some(null)`), so
    it is left to integration.
    
    > Note: an earlier revision of this PR also added a
    `CacheSourceOpExecSpec`. `CacheSourceOpExec` turned out to be dead code
    — cache source operators were replaced by input-port materialization
    reader threads in #3425, and its only factory
    (`SpecialPhysicalOpFactory.newSourcePhysicalOp`) has no production
    caller — so that spec was dropped and removal of the operator is tracked
    in #6125.
    
    ### Any related issues, documentation, discussions?
    
    Follow-up to the review feedback on #6043: prioritize tests that fill
    uncovered code paths.
    
    ### How was this PR tested?
    
    - `sbt "WorkflowOperator/testOnly *IntervalOpExecSpec *SleepOpExecSpec"`
    — all green
    - `sbt "WorkflowOperator/Test/scalafmtCheck"` and `sbt
    "WorkflowOperator/scalafixAll --check"` — clean
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (Opus 4.8 [1M context])
---
 .../operator/intervalJoin/IntervalOpExecSpec.scala | 35 +++++++++++++
 .../amber/operator/sleep/SleepOpExecSpec.scala     | 57 ++++++++++++++++++++++
 2 files changed, 92 insertions(+)

diff --git a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/intervalJoin/IntervalOpExecSpec.scala b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/intervalJoin/IntervalOpExecSpec.scala
index 97c6012983..aceab33f9d 100644
--- a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/intervalJoin/IntervalOpExecSpec.scala
+++ b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/intervalJoin/IntervalOpExecSpec.scala
@@ -498,4 +498,39 @@ class IntervalOpExecSpec extends AnyFlatSpec with BeforeAndAfter {
     )
   }
 
+  it should "join timestamps across every interval unit" in {
+    val base = Timestamp.valueOf("2020-01-01 00:00:00")
+    Seq(
+      TimeIntervalType.YEAR,
+      TimeIntervalType.MONTH,
+      TimeIntervalType.HOUR,
+      TimeIntervalType.MINUTE,
+      TimeIntervalType.SECOND
+    ).foreach { unit =>
+      val desc = new IntervalJoinOpDesc("point", "range", 3L, true, true, unit)
+      val exec = new IntervalJoinOpExec(objectMapper.writeValueAsString(desc))
+      exec.open()
+      try {
+        assert(exec.processTuple(timeStampTuple("range", 1, base), right).isEmpty)
+        val out = exec.processTuple(timeStampTuple("point", 1, base), left).toList
+        assert(out.size == 1, s"expected a match for unit $unit")
+        assert(out.head.getFields.length == 4)
+      } finally exec.close()
+    }
+  }
+
+  it should "reject a join key whose type does not support interval comparison" in {
+    val desc = new IntervalJoinOpDesc("point", "range", 3L, true, true, TimeIntervalType.DAY)
+    val exec = new IntervalJoinOpExec(objectMapper.writeValueAsString(desc))
+    exec.open()
+    assert(
+      exec.processTuple(newTuple[String]("range", 1, "a", AttributeType.STRING), right).isEmpty
+    )
+    val ex = intercept[org.apache.texera.amber.core.WorkflowRuntimeException] {
+      exec.processTuple(newTuple[String]("point", 1, "a", AttributeType.STRING), left).toList
+    }
+    assert(ex.getMessage == "The data type can not support comparison: string")
+    exec.close()
+  }
+
 }
diff --git a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/sleep/SleepOpExecSpec.scala b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/sleep/SleepOpExecSpec.scala
new file mode 100644
index 0000000000..2aa143f294
--- /dev/null
+++ b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/sleep/SleepOpExecSpec.scala
@@ -0,0 +1,57 @@
+/*
+ * 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.sleep
+
+import org.apache.texera.amber.core.tuple.{Attribute, AttributeType, Schema, Tuple}
+import org.apache.texera.amber.util.JSONUtils.objectMapper
+import org.scalatest.flatspec.AnyFlatSpec
+
+class SleepOpExecSpec extends AnyFlatSpec {
+
+  private val schema: Schema = Schema().add(new Attribute("v", AttributeType.INTEGER))
+
+  private def tuple(v: Int): Tuple =
+    Tuple.builder(schema).add(new Attribute("v", AttributeType.INTEGER), Integer.valueOf(v)).build()
+
+  // SleepOpDesc is a LogicalOp: serialize a real instance so the polymorphic operatorType
+  // discriminator is present (a hand-written JSON string would fail to deserialize).
+  private def descString(sleepTime: Int): String = {
+    val desc = new SleepOpDesc()
+    desc.sleepTime = sleepTime
+    objectMapper.writeValueAsString(desc)
+  }
+
+  "SleepOpExec" should "construct from a serialized SleepOpDesc" in {
+    val exec = new SleepOpExec(descString(0))
+    assert(exec != null)
+  }
+
+  "SleepOpExec.processTuple" should "return the input tuple unchanged" in {
+    // sleepTime = 0 -> Thread.sleep(0), instant
+    val exec = new SleepOpExec(descString(0))
+    assert(exec.processTuple(tuple(7), 0).toList == List(tuple(7)))
+  }
+
+  it should "emit exactly one tuple per input" in {
+    val exec = new SleepOpExec(descString(0))
+    val emitted = (0 until 5).flatMap(i => exec.processTuple(tuple(i), 0).toList)
+    assert(emitted == (0 until 5).map(tuple).toList)
+  }
+}