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/04 08:08:49 UTC
(texera) 01/01: test(workflow-core): add unit test coverage for storage-model classes (#6095)
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-6095-605a192f8ae196df804d90960984faba519abcd0
in repository https://gitbox.apache.org/repos/asf/texera.git
commit bc5accff3797d538d165b7c0acd7f5112eabf031
Author: Xinyuan Lin <xi...@uci.edu>
AuthorDate: Sat Jul 4 01:08:30 2026 -0700
test(workflow-core): add unit test coverage for storage-model classes (#6095)
### What changes were proposed in this PR?
Add unit test coverage for three `common/workflow-core` storage-model
classes, selected from the Codecov report (all 0%). No production-code
changes.
| File | Codecov before | What the tests pin |
| --- | --- | --- |
| `ResultSchema.scala` | 0% | the runtime-statistics schema's 11 columns
(names + types, in order) and the single-column console-messages schema
|
| `ReadonlyLocalFileDocument.scala` | 0% | URI/file accessors and
input-stream read against a local temp file, plus each unsupported
collection accessor throwing its exact message |
| `VirtualDocument.scala` | 6.2% | every default method
(read/write/file/stat) throws `NotImplementedError` with its exact
message, via a minimal concrete subclass |
### 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 "WorkflowCore/testOnly *ResultSchemaSpec
*ReadonlyLocalFileDocumentSpec *VirtualDocumentSpec"` — 20 tests, all
green
- `sbt "WorkflowCore/Test/scalafmtCheck"` and `sbt
"WorkflowCore/scalafixAll --check"` — clean
### Was this PR authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Opus 4.8 [1M context])
---
.../model/ReadonlyLocalFileDocumentSpec.scala | 73 +++++++++++++++++++
.../core/storage/model/VirtualDocumentSpec.scala | 85 ++++++++++++++++++++++
.../core/storage/result/ResultSchemaSpec.scala | 63 ++++++++++++++++
3 files changed, 221 insertions(+)
diff --git a/common/workflow-core/src/test/scala/org/apache/texera/amber/core/storage/model/ReadonlyLocalFileDocumentSpec.scala b/common/workflow-core/src/test/scala/org/apache/texera/amber/core/storage/model/ReadonlyLocalFileDocumentSpec.scala
new file mode 100644
index 0000000000..a9b9ac96d3
--- /dev/null
+++ b/common/workflow-core/src/test/scala/org/apache/texera/amber/core/storage/model/ReadonlyLocalFileDocumentSpec.scala
@@ -0,0 +1,73 @@
+/*
+ * 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.core.storage.model
+
+import org.scalatest.flatspec.AnyFlatSpec
+import org.scalatest.matchers.should.Matchers
+
+import java.io.File
+import java.nio.charset.StandardCharsets
+import java.nio.file.Files
+
+class ReadonlyLocalFileDocumentSpec extends AnyFlatSpec with Matchers {
+
+ private def withTempDoc(
+ content: String
+ )(body: (ReadonlyLocalFileDocument, File) => Unit): Unit = {
+ val tmp = File.createTempFile("readonly-local-", ".txt")
+ try {
+ Files.write(tmp.toPath, content.getBytes(StandardCharsets.UTF_8))
+ body(new ReadonlyLocalFileDocument(tmp.toURI), tmp)
+ } finally {
+ tmp.delete()
+ }
+ }
+
+ "ReadonlyLocalFileDocument" should "expose the backing URI and file" in {
+ withTempDoc("hello") { (doc, tmp) =>
+ doc.getURI shouldBe tmp.toURI
+ doc.asFile() shouldBe new File(tmp.toURI)
+ doc.asFile().getCanonicalFile shouldBe tmp.getCanonicalFile
+ }
+ }
+
+ it should "read the file contents through an input stream" in {
+ withTempDoc("hello world") { (doc, _) =>
+ val in = doc.asInputStream()
+ try new String(in.readAllBytes(), "UTF-8") shouldBe "hello world"
+ finally in.close()
+ }
+ }
+
+ it should "not support the collection accessors" in {
+ withTempDoc("x") { (doc, _) =>
+ intercept[NotImplementedError](doc.getItem(0)).getMessage shouldBe
+ "getItem is not supported for ReadonlyLocalFileDocument"
+ intercept[NotImplementedError](doc.get()).getMessage shouldBe
+ "get is not supported for ReadonlyLocalFileDocument"
+ intercept[NotImplementedError](doc.getRange(0, 1)).getMessage shouldBe
+ "getRange is not supported for ReadonlyLocalFileDocument"
+ intercept[NotImplementedError](doc.getAfter(0)).getMessage shouldBe
+ "getAfter is not supported for ReadonlyLocalFileDocument"
+ intercept[NotImplementedError](doc.getCount).getMessage shouldBe
+ "getCount is not supported for ReadonlyLocalFileDocument"
+ }
+ }
+}
diff --git a/common/workflow-core/src/test/scala/org/apache/texera/amber/core/storage/model/VirtualDocumentSpec.scala b/common/workflow-core/src/test/scala/org/apache/texera/amber/core/storage/model/VirtualDocumentSpec.scala
new file mode 100644
index 0000000000..af23da865f
--- /dev/null
+++ b/common/workflow-core/src/test/scala/org/apache/texera/amber/core/storage/model/VirtualDocumentSpec.scala
@@ -0,0 +1,85 @@
+/*
+ * 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.core.storage.model
+
+import org.scalatest.flatspec.AnyFlatSpec
+import org.scalatest.matchers.should.Matchers
+
+import java.io.ByteArrayInputStream
+import java.net.URI
+
+class VirtualDocumentSpec extends AnyFlatSpec with Matchers {
+
+ // A minimal concrete document overriding only the two abstract members
+ // (getURI, clear); every other method keeps its default throwing body.
+ private class MinimalDoc extends VirtualDocument[Int] {
+ override def getURI: URI = new URI("file:///stub/doc")
+ override def clear(): Unit = ()
+ }
+
+ // one shared instance: every default method throws statelessly, so all assertions can run
+ // against the same document (in particular the URI and clear checks below)
+ private val doc: VirtualDocument[Int] = new MinimalDoc
+
+ "VirtualDocument" should "return the implemented URI and support clear" in {
+ doc.getURI shouldBe new URI("file:///stub/doc")
+ noException should be thrownBy doc.clear()
+ }
+
+ it should "throw NotImplementedError for every unimplemented read accessor" in {
+ intercept[NotImplementedError](doc.getItem(0)).getMessage shouldBe
+ "getItem method is not implemented"
+ intercept[NotImplementedError](doc.get()).getMessage shouldBe "get method is not implemented"
+ intercept[NotImplementedError](doc.getRange(0, 1)).getMessage shouldBe
+ "getRange method is not implemented"
+ intercept[NotImplementedError](doc.getRange(0, 1, columns = Some(Seq("c")))).getMessage shouldBe
+ "getRange method is not implemented"
+ intercept[NotImplementedError](doc.getAfter(0)).getMessage shouldBe
+ "getAfter method is not implemented"
+ intercept[NotImplementedError](doc.getCount).getMessage shouldBe
+ "getCount method is not implemented"
+ }
+
+ it should "throw NotImplementedError for every unimplemented write accessor" in {
+ intercept[NotImplementedError](doc.setItem(0, 5)).getMessage shouldBe
+ "setItem method is not implemented"
+ // note: the writer default reports the message "write method is not implemented"
+ intercept[NotImplementedError](doc.writer("w")).getMessage shouldBe
+ "write method is not implemented"
+ intercept[NotImplementedError](doc.append(5)).getMessage shouldBe
+ "append method is not implemented"
+ intercept[NotImplementedError](doc.append(Iterator(1, 2))).getMessage shouldBe
+ "append method is not implemented"
+ intercept[NotImplementedError](
+ doc.appendStream(new ByteArrayInputStream(Array[Byte](1)))
+ ).getMessage shouldBe "appendStream method is not implemented"
+ }
+
+ it should "throw NotImplementedError for the unimplemented file/stat accessors" in {
+ intercept[NotImplementedError](doc.asInputStream()).getMessage shouldBe
+ "asInputStream method is not implemented"
+ intercept[NotImplementedError](doc.asFile()).getMessage shouldBe
+ "asFile method is not implemented"
+ intercept[NotImplementedError](doc.getTableStatistics).getMessage shouldBe
+ "getTableStatistics method is not implemented"
+ intercept[NotImplementedError](doc.getTotalFileSize).getMessage shouldBe
+ "getTotalFileSize method is not implemented"
+ }
+}
diff --git a/common/workflow-core/src/test/scala/org/apache/texera/amber/core/storage/result/ResultSchemaSpec.scala b/common/workflow-core/src/test/scala/org/apache/texera/amber/core/storage/result/ResultSchemaSpec.scala
new file mode 100644
index 0000000000..20c897c042
--- /dev/null
+++ b/common/workflow-core/src/test/scala/org/apache/texera/amber/core/storage/result/ResultSchemaSpec.scala
@@ -0,0 +1,63 @@
+/*
+ * 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.core.storage.result
+
+import org.apache.texera.amber.core.tuple.AttributeType
+import org.scalatest.flatspec.AnyFlatSpec
+import org.scalatest.matchers.should.Matchers
+
+class ResultSchemaSpec extends AnyFlatSpec with Matchers {
+
+ "ResultSchema.runtimeStatisticsSchema" should "declare the runtime-statistics columns in order" in {
+ val schema = ResultSchema.runtimeStatisticsSchema
+ schema.getAttributeNames shouldBe List(
+ "operatorId",
+ "time",
+ "inputTupleCnt",
+ "inputTupleSize",
+ "outputTupleCnt",
+ "outputTupleSize",
+ "dataProcessingTime",
+ "controlProcessingTime",
+ "idleTime",
+ "numWorkers",
+ "status"
+ )
+ schema.getAttribute("operatorId").getType shouldBe AttributeType.STRING
+ schema.getAttribute("time").getType shouldBe AttributeType.TIMESTAMP
+ List(
+ "inputTupleCnt",
+ "inputTupleSize",
+ "outputTupleCnt",
+ "outputTupleSize",
+ "dataProcessingTime",
+ "controlProcessingTime",
+ "idleTime"
+ ).foreach(name => schema.getAttribute(name).getType shouldBe AttributeType.LONG)
+ schema.getAttribute("numWorkers").getType shouldBe AttributeType.INTEGER
+ schema.getAttribute("status").getType shouldBe AttributeType.INTEGER
+ }
+
+ "ResultSchema.consoleMessagesSchema" should "declare a single string message column" in {
+ val schema = ResultSchema.consoleMessagesSchema
+ schema.getAttributeNames shouldBe List("message")
+ schema.getAttribute("message").getType shouldBe AttributeType.STRING
+ }
+}