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