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 07:24:42 UTC
(texera) branch main updated: test(config): add unit test coverage for ApplicationConfig, GuiConfig, and EnvironmentalVariable (#6083)
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
The following commit(s) were added to refs/heads/main by this push:
new 605a192f8a test(config): add unit test coverage for ApplicationConfig, GuiConfig, and EnvironmentalVariable (#6083)
605a192f8a is described below
commit 605a192f8ae196df804d90960984faba519abcd0
Author: Xinyuan Lin <xi...@uci.edu>
AuthorDate: Sat Jul 4 00:14:13 2026 -0700
test(config): add unit test coverage for ApplicationConfig, GuiConfig, and EnvironmentalVariable (#6083)
### What changes were proposed in this PR?
Add unit test coverage for three `common/config` objects, selected from
the Codecov report (all 0%). No production-code changes.
| File | Codecov before | What the tests pin |
| --- | --- | --- |
| `EnvironmentalVariable.scala` | 0% | every env-var-name constant
resolves, is non-empty, and is unique; `get` returns `None`/`Some` for
unset/set vars |
| `ApplicationConfig.scala` | 0% | every value resolves from
application.conf with its default (constants, flow-control,
network-buffering, reconfiguration, fault-tolerance, scheduling,
cleanup, web-server, ai-assistant) |
| `GuiConfig.scala` | 0% | every gui.conf boolean/string/int setting
resolves to its default |
Reading each value forces resolution from the backing `.conf`, so a
renamed or mistyped key surfaces here as a `ConfigException` instead of
at service start-up. Every key carries a `${?ENV}` override, so
exact-value assertions are guarded on the env var being unset —
mirroring the existing `StorageConfigSpec`.
### 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 "Config/testOnly *EnvironmentalVariableSpec
*ApplicationConfigSpec *GuiConfigSpec"` — 16 tests, all green
- `sbt "Config/Test/scalafmtCheck"` and `sbt "Config/scalafixAll
--check"` — clean
### Was this PR authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Opus 4.8 [1M context])
---
.../common/config/ApplicationConfigSpec.scala | 144 +++++++++++++++++++++
.../common/config/EnvironmentalVariableSpec.scala | 80 ++++++++++++
.../texera/common/config/GuiConfigSpec.scala | 107 +++++++++++++++
3 files changed, 331 insertions(+)
diff --git a/common/config/src/test/scala/org/apache/texera/common/config/ApplicationConfigSpec.scala b/common/config/src/test/scala/org/apache/texera/common/config/ApplicationConfigSpec.scala
new file mode 100644
index 0000000000..8a16a8d48e
--- /dev/null
+++ b/common/config/src/test/scala/org/apache/texera/common/config/ApplicationConfigSpec.scala
@@ -0,0 +1,144 @@
+/*
+ * 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.common.config
+
+import org.scalatest.flatspec.AnyFlatSpec
+import org.scalatest.matchers.should.Matchers
+
+/**
+ * Spec for [[ApplicationConfig]]. Reading each value forces resolution from application.conf, so
+ * a renamed or mistyped key surfaces here as a ConfigException instead of at service start-up.
+ * Every key carries a `${?ENV_VAR}` override, so exact-value assertions that could be overridden
+ * are guarded on the env var being unset (mirroring StorageConfigSpec).
+ */
+class ApplicationConfigSpec extends AnyFlatSpec with Matchers {
+
+ // Each key carries a `${?ENV}` override, and application.conf falls back to
+ // ConfigFactory.load() (which also layers JVM system properties). Guard every
+ // exact-value assertion on the override being absent from both sources.
+ private def ifUnset(name: String)(assertion: => Any): Unit =
+ if (!sys.env.contains(name) && !sys.props.contains(name)) assertion
+
+ "ApplicationConfig constants" should "load the default constant values" in {
+ ifUnset("CONSTANTS_LOGGING_QUEUE_SIZE_INTERVAL")(
+ ApplicationConfig.loggingQueueSizeInterval shouldBe 30000
+ )
+ ifUnset("CONSTANTS_MAX_RESOLUTION_ROWS")(ApplicationConfig.MAX_RESOLUTION_ROWS shouldBe 2000)
+ ifUnset("CONSTANTS_MAX_RESOLUTION_COLUMNS")(
+ ApplicationConfig.MAX_RESOLUTION_COLUMNS shouldBe 2000
+ )
+ ifUnset("CONSTANTS_NUM_WORKER_PER_OPERATOR")(
+ ApplicationConfig.numWorkerPerOperatorByDefault shouldBe 2
+ )
+ ifUnset("CONSTANTS_STATUS_UPDATE_INTERVAL")(
+ ApplicationConfig.getStatusUpdateIntervalInMs shouldBe 500L
+ )
+ ifUnset("CONSTANTS_RUNTIME_STATISTICS_PERSISTENCE_INTERVAL")(
+ ApplicationConfig.getRuntimeStatisticsPersistenceIntervalInMs shouldBe 2000L
+ )
+ }
+
+ "ApplicationConfig flow control" should "load credit and polling defaults" in {
+ ifUnset("FLOW_CONTROL_MAX_CREDIT_ALLOWED_IN_BYTES_PER_CHANNEL")(
+ ApplicationConfig.maxCreditAllowedInBytesPerChannel shouldBe 1600000000L
+ )
+ ifUnset("FLOW_CONTROL_CREDIT_POLL_INTERVAL_IN_MS")(
+ ApplicationConfig.creditPollingIntervalInMs shouldBe 200
+ )
+ }
+
+ "ApplicationConfig network buffering" should "load batch size and adaptive buffering defaults" in {
+ ifUnset("NETWORK_BUFFERING_DEFAULT_DATA_TRANSFER_BATCH_SIZE")(
+ ApplicationConfig.defaultDataTransferBatchSize shouldBe 400
+ )
+ ifUnset("NETWORK_BUFFERING_ENABLE_ADAPTIVE_BUFFERING")(
+ ApplicationConfig.enableAdaptiveNetworkBuffering shouldBe true
+ )
+ ifUnset("NETWORK_BUFFERING_ADAPTIVE_BUFFERING_TIMEOUT_MS")(
+ ApplicationConfig.adaptiveBufferingTimeoutMs shouldBe 500
+ )
+ }
+
+ "ApplicationConfig reconfiguration" should "default transactional reconfiguration to false" in {
+ ifUnset("RECONFIGURATION_ENABLE_TRANSACTIONAL_RECONFIGURATION")(
+ ApplicationConfig.enableTransactionalReconfiguration shouldBe false
+ )
+ }
+
+ "ApplicationConfig fault tolerance" should "disable logging with an empty log-storage-uri" in {
+ ifUnset("FAULT_TOLERANCE_LOG_FLUSH_INTERVAL_MS")(
+ ApplicationConfig.faultToleranceLogFlushIntervalInMs shouldBe 0L
+ )
+ ifUnset("FAULT_TOLERANCE_LOG_STORAGE_URI") {
+ ApplicationConfig.faultToleranceLogRootFolder shouldBe None
+ ApplicationConfig.isFaultToleranceEnabled shouldBe false
+ }
+ }
+
+ "ApplicationConfig scheduling" should "load schedule-generator defaults" in {
+ ifUnset("SCHEDULE_GENERATOR_MAX_CONCURRENT_REGIONS")(
+ ApplicationConfig.maxConcurrentRegions shouldBe 1
+ )
+ ifUnset("SCHEDULE_GENERATOR_USE_GLOBAL_SEARCH")(
+ ApplicationConfig.useGlobalSearch shouldBe false
+ )
+ ifUnset("SCHEDULE_GENERATOR_USE_TOP_DOWN_SEARCH")(
+ ApplicationConfig.useTopDownSearch shouldBe false
+ )
+ ifUnset("SCHEDULE_GENERATOR_SEARCH_TIMEOUT_MILLISECONDS")(
+ ApplicationConfig.searchTimeoutMilliseconds shouldBe 1000
+ )
+ }
+
+ "ApplicationConfig storage cleanup" should "load result-cleanup TTL and interval defaults" in {
+ ifUnset("RESULT_CLEANUP_TTL_IN_SECONDS")(ApplicationConfig.sinkStorageTTLInSecs shouldBe 86400)
+ ifUnset("RESULT_CLEANUP_COLLECTION_CHECK_INTERVAL_IN_SECONDS")(
+ ApplicationConfig.sinkStorageCleanUpCheckIntervalInSecs shouldBe 86400
+ )
+ }
+
+ "ApplicationConfig web server" should "load web-server defaults" in {
+ ifUnset("WEB_SERVER_PYTHON_CONSOLE_BUFFER_SIZE")(
+ ApplicationConfig.operatorConsoleBufferSize shouldBe 100
+ )
+ ifUnset("WEB_SERVER_CONSOLE_MESSAGE_MAX_DISPLAY_LENGTH")(
+ ApplicationConfig.consoleMessageDisplayLength shouldBe 100
+ )
+ ifUnset("WEB_SERVER_WORKFLOW_RESULT_PULLING_IN_SECONDS")(
+ ApplicationConfig.executionResultPollingInSecs shouldBe 3
+ )
+ ifUnset("WEB_SERVER_WORKFLOW_STATE_CLEANUP_IN_SECONDS")(
+ ApplicationConfig.executionStateCleanUpInSecs shouldBe 30
+ )
+ ifUnset("WEB_SERVER_CLEAN_ALL_EXECUTION_RESULTS_ON_SERVER_START")(
+ ApplicationConfig.cleanupAllExecutionResults shouldBe false
+ )
+ ifUnset("MAX_WORKFLOW_WEBSOCKET_REQUEST_PAYLOAD_SIZE_KB")(
+ ApplicationConfig.maxWorkflowWebsocketRequestPayloadSizeKb shouldBe 64
+ )
+ }
+
+ "ApplicationConfig AI assistant" should "expose the ai-assistant-server config block" in {
+ ApplicationConfig.aiAssistantConfig should not be empty
+ ifUnset("AI_ASSISTANT_SERVER_ASSISTANT")(
+ ApplicationConfig.aiAssistantConfig.get.getString("assistant") shouldBe "none"
+ )
+ }
+}
diff --git a/common/config/src/test/scala/org/apache/texera/common/config/EnvironmentalVariableSpec.scala b/common/config/src/test/scala/org/apache/texera/common/config/EnvironmentalVariableSpec.scala
new file mode 100644
index 0000000000..90c18f4b0a
--- /dev/null
+++ b/common/config/src/test/scala/org/apache/texera/common/config/EnvironmentalVariableSpec.scala
@@ -0,0 +1,80 @@
+/*
+ * 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.common.config
+
+import org.scalatest.flatspec.AnyFlatSpec
+import org.scalatest.matchers.should.Matchers
+
+/**
+ * Spec for [[EnvironmentalVariable]], the registry of environment-variable *names* used across
+ * the *.conf files. The constants are enumerated reflectively (every no-arg String accessor on the
+ * object) so a newly added `ENV_*` constant is automatically covered by the non-empty and
+ * uniqueness checks — the list never drifts out of sync with the object.
+ */
+class EnvironmentalVariableSpec extends AnyFlatSpec with Matchers {
+
+ // Each `val ENV_* : String` compiles to a public no-arg getter named `ENV_*` returning String.
+ // Enumerating those getters reflects the object's constants without a hand-maintained list; the
+ // `ENV_` name prefix also excludes inherited members such as `toString`.
+ private val envVarNames: Seq[String] =
+ EnvironmentalVariable.getClass.getMethods.toSeq
+ .filter(m =>
+ m.getName.startsWith("ENV_") &&
+ m.getParameterCount == 0 &&
+ m.getReturnType == classOf[String]
+ )
+ .map(_.invoke(EnvironmentalVariable).asInstanceOf[String])
+
+ // A well-formed env-var name: an uppercase letter followed by uppercase letters, digits, or
+ // underscores. Rejects empty, all-whitespace, and padded names (e.g. "STORAGE_JDBC_URL ") that
+ // would silently miss env lookups — a stricter check than `trim.nonEmpty`.
+ private val nameShape = "[A-Z][A-Z0-9_]*".r
+
+ "EnvironmentalVariable" should "expose a non-trivial set of env-var name constants" in {
+ envVarNames.size should be > 20
+ }
+
+ it should "expose well-formed, unique env-var names" in {
+ envVarNames.foreach(name =>
+ withClue(s"malformed env-var name [$name]: ")(name should fullyMatch regex nameShape)
+ )
+ envVarNames.distinct.size shouldBe envVarNames.size
+ }
+
+ it should "use the documented names for a representative sample" in {
+ EnvironmentalVariable.ENV_JAVA_OPTS shouldBe "JAVA_OPTS"
+ EnvironmentalVariable.ENV_AUTH_JWT_SECRET shouldBe "AUTH_JWT_SECRET"
+ EnvironmentalVariable.ENV_JDBC_URL shouldBe "STORAGE_JDBC_URL"
+ EnvironmentalVariable.ENV_S3_ENDPOINT shouldBe "STORAGE_S3_ENDPOINT"
+ EnvironmentalVariable.ENV_CACHE_ENABLED shouldBe "CACHE_ENABLED"
+ }
+
+ "EnvironmentalVariable.get" should "return None for an unset variable" in {
+ val absent = "TEXERA_DEFINITELY_UNSET_" + System.nanoTime().toString
+ EnvironmentalVariable.get(absent) shouldBe None
+ }
+
+ it should "return Some(value) for a variable that is set in the JVM environment" in {
+ val env = System.getenv()
+ if (env.isEmpty) cancel("no environment variables available to exercise the Some(value) case")
+ val entry = env.entrySet().iterator().next()
+ EnvironmentalVariable.get(entry.getKey) shouldBe Some(entry.getValue)
+ }
+}
diff --git a/common/config/src/test/scala/org/apache/texera/common/config/GuiConfigSpec.scala b/common/config/src/test/scala/org/apache/texera/common/config/GuiConfigSpec.scala
new file mode 100644
index 0000000000..fbee0c9804
--- /dev/null
+++ b/common/config/src/test/scala/org/apache/texera/common/config/GuiConfigSpec.scala
@@ -0,0 +1,107 @@
+/*
+ * 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.common.config
+
+import org.scalatest.flatspec.AnyFlatSpec
+import org.scalatest.matchers.should.Matchers
+
+/**
+ * Spec for [[GuiConfig]]. Reading each value forces resolution from gui.conf, so a renamed or
+ * mistyped key surfaces here as a ConfigException. Every key carries a `${?GUI_...}` override, so
+ * exact-value assertions are guarded on the env var being unset (mirroring StorageConfigSpec).
+ */
+class GuiConfigSpec extends AnyFlatSpec with Matchers {
+
+ private def ifUnset(env: String)(assertion: => Any): Unit =
+ if (sys.env.get(env).isEmpty) assertion
+
+ "GuiConfig boolean flags" should "resolve to their gui.conf defaults when env overrides are unset" in {
+ ifUnset("GUI_LOGIN_LOCAL_LOGIN")(GuiConfig.guiLoginLocalLogin shouldBe true)
+ ifUnset("GUI_LOGIN_GOOGLE_LOGIN")(GuiConfig.guiLoginGoogleLogin shouldBe true)
+ ifUnset("GUI_WORKFLOW_WORKSPACE_USER_PRESET_ENABLED")(
+ GuiConfig.guiWorkflowWorkspaceUserPresetEnabled shouldBe false
+ )
+ ifUnset("GUI_WORKFLOW_WORKSPACE_EXPORT_EXECUTION_RESULT_ENABLED")(
+ GuiConfig.guiWorkflowWorkspaceExportExecutionResultEnabled shouldBe false
+ )
+ ifUnset("GUI_WORKFLOW_WORKSPACE_AUTO_ATTRIBUTE_CORRECTION_ENABLED")(
+ GuiConfig.guiWorkflowWorkspaceAutoAttributeCorrectionEnabled shouldBe true
+ )
+ ifUnset("GUI_WORKFLOW_WORKSPACE_SELECTING_FILES_FROM_DATASETS_ENABLED")(
+ GuiConfig.guiWorkflowWorkspaceSelectingFilesFromDatasetsEnabled shouldBe true
+ )
+ ifUnset("GUI_WORKFLOW_WORKSPACE_WORKFLOW_EXECUTIONS_TRACKING_ENABLED")(
+ GuiConfig.guiWorkflowWorkspaceWorkflowExecutionsTrackingEnabled shouldBe false
+ )
+ ifUnset("GUI_WORKFLOW_WORKSPACE_LINK_BREAKPOINT_ENABLED")(
+ GuiConfig.guiWorkflowWorkspaceLinkBreakpointEnabled shouldBe true
+ )
+ ifUnset("GUI_WORKFLOW_WORKSPACE_ASYNC_RENDERING_ENABLED")(
+ GuiConfig.guiWorkflowWorkspaceAsyncRenderingEnabled shouldBe false
+ )
+ ifUnset("GUI_WORKFLOW_WORKSPACE_TIMETRAVEL_ENABLED")(
+ GuiConfig.guiWorkflowWorkspaceTimetravelEnabled shouldBe false
+ )
+ ifUnset("GUI_WORKFLOW_WORKSPACE_PRODUCTION_SHARED_EDITING_SERVER")(
+ GuiConfig.guiWorkflowWorkspaceProductionSharedEditingServer shouldBe false
+ )
+ ifUnset("GUI_WORKFLOW_WORKSPACE_WORKFLOW_EMAIL_NOTIFICATION_ENABLED")(
+ GuiConfig.guiWorkflowWorkspaceWorkflowEmailNotificationEnabled shouldBe false
+ )
+ ifUnset("GUI_WORKFLOW_WORKSPACE_COPILOT_ENABLED")(
+ GuiConfig.guiWorkflowWorkspaceCopilotEnabled shouldBe false
+ )
+ ifUnset("GUI_ATTRIBUTION_ENABLED")(GuiConfig.guiAttributionEnabled shouldBe false)
+ ifUnset("GUI_DEPLOYMENT_VERSION_CHECK_ENABLED")(
+ GuiConfig.guiDeploymentVersionCheckEnabled shouldBe false
+ )
+ ifUnset("GUI_WORKFLOW_WORKSPACE_PYTHON_NOTEBOOK_MIGRATION_ENABLED")(
+ GuiConfig.guiWorkflowWorkspacePythonNotebookMigrationEnabled shouldBe false
+ )
+ }
+
+ "GuiConfig string settings" should "resolve to their gui.conf defaults when env overrides are unset" in {
+ ifUnset("GUI_WORKFLOW_WORKSPACE_DEFAULT_EXECUTION_MODE")(
+ GuiConfig.guiWorkflowWorkspaceDefaultExecutionMode shouldBe "PIPELINED"
+ )
+ // stored as a String even though the conf value is numeric
+ ifUnset("GUI_WORKFLOW_WORKSPACE_PYTHON_LANGUAGE_SERVER_PORT")(
+ GuiConfig.guiWorkflowWorkspacePythonLanguageServerPort shouldBe "3000"
+ )
+ ifUnset("GUI_LOGIN_DEFAULT_LOCAL_USER_USERNAME")(
+ GuiConfig.guiLoginDefaultLocalUserUsername shouldBe ""
+ )
+ ifUnset("GUI_LOGIN_DEFAULT_LOCAL_USER_PASSWORD")(
+ GuiConfig.guiLoginDefaultLocalUserPassword shouldBe ""
+ )
+ }
+
+ "GuiConfig integer settings" should "resolve to their gui.conf defaults when env overrides are unset" in {
+ ifUnset("GUI_WORKFLOW_WORKSPACE_OPERATOR_CONSOLE_MESSAGE_BUFFER_SIZE")(
+ GuiConfig.guiWorkflowWorkspaceOperatorConsoleMessageBufferSize shouldBe 100
+ )
+ ifUnset("GUI_WORKFLOW_WORKSPACE_ACTIVE_TIME_IN_MINUTES")(
+ GuiConfig.guiWorkflowWorkspaceActiveTimeInMinutes shouldBe 15
+ )
+ ifUnset("GUI_WORKFLOW_WORKSPACE_LIMIT_COLUMNS")(
+ GuiConfig.guiWorkflowWorkspaceLimitColumns shouldBe 15
+ )
+ }
+}