Skip to content

Commit 4c9d30a

Browse files
authored
test(config): add unit test coverage for the remaining config objects (apache#6094)
### What changes were proposed in this PR? Add unit test coverage for the remaining `common/config` objects, selected from the Codecov report (all 0%). No production-code changes. | File | Codecov before | What the tests pin | | --- | --- | --- | | `UdfConfig.scala` | 0% | python/R path + log handler defaults from udf.conf | | `DefaultsConfig.scala` | 0% | the `reinit` flag and the flattened `allDefaults` short-key/value map from default.conf | | `ComputingUnitConfig.scala` | 0% | local/sharing enabled flags | | `LLMConfig.scala` | 0% | LiteLLM base URL + master key | | `PekkoConfig.scala` | 0% | actor/serialization, remote/artery, and cluster/failure-detector settings from cluster.conf (no ActorSystem started) | | `PythonUtils.scala` | 0% | `getPythonExecutable` blank→`python3` fallback and trimmed-path branch | Reading each value forces resolution from the backing `.conf`; env/system-property-overridable values are guarded on the override being unset (mirroring `StorageConfigSpec`). ### Any related issues, documentation, discussions? Follow-up to the review feedback on apache#6043: prioritize tests that fill uncovered code paths. ### How was this PR tested? - `sbt "Config/testOnly *UdfConfigSpec *DefaultsConfigSpec *ComputingUnitConfigSpec *LLMConfigSpec *PekkoConfigSpec *PythonUtilsSpec"` — 13 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])
1 parent 5a74b0f commit 4c9d30a

6 files changed

Lines changed: 318 additions & 0 deletions

File tree

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.texera.common.config
21+
22+
import org.scalatest.flatspec.AnyFlatSpec
23+
import org.scalatest.matchers.should.Matchers
24+
25+
/**
26+
* Spec for [[ComputingUnitConfig]]. Reading each value forces resolution from computing-unit.conf,
27+
* so a renamed or mistyped key surfaces here as a ConfigException. Both flags carry a `${?ENV}`
28+
* override, so exact-value assertions are guarded.
29+
*/
30+
class ComputingUnitConfigSpec extends AnyFlatSpec with Matchers {
31+
32+
private def ifUnset(name: String)(assertion: => Any): Unit =
33+
if (!sys.env.contains(name) && !sys.props.contains(name)) assertion
34+
35+
"ComputingUnitConfig" should "resolve the local/sharing flags from computing-unit.conf" in {
36+
ifUnset("COMPUTING_UNIT_LOCAL_ENABLED")(
37+
ComputingUnitConfig.localComputingUnitEnabled shouldBe true
38+
)
39+
ifUnset("COMPUTING_UNIT_SHARING_ENABLED")(
40+
ComputingUnitConfig.sharingComputingUnitEnabled shouldBe false
41+
)
42+
}
43+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.texera.common.config
21+
22+
import org.scalatest.flatspec.AnyFlatSpec
23+
import org.scalatest.matchers.should.Matchers
24+
25+
/**
26+
* Spec for [[DefaultsConfig]]. Reading each value forces resolution from default.conf, so a
27+
* renamed or mistyped key surfaces here as a ConfigException. The `reinit` flag carries a
28+
* `${?ENV}` override, so its exact-value assertion is guarded.
29+
*/
30+
class DefaultsConfigSpec extends AnyFlatSpec with Matchers {
31+
32+
private def ifUnset(name: String)(assertion: => Any): Unit =
33+
if (!sys.env.contains(name) && !sys.props.contains(name)) assertion
34+
35+
"DefaultsConfig.reinit" should "default the reset-to-defaults flag to false" in {
36+
ifUnset("CONFIG_SERVICE_ALWAYS_RESET_CONFIGURATIONS_TO_DEFAULT_VALUES")(
37+
DefaultsConfig.reinit shouldBe false
38+
)
39+
}
40+
41+
"DefaultsConfig.allDefaults" should "flatten default.conf into short-key/value entries" in {
42+
val defaults = DefaultsConfig.allDefaults
43+
defaults should not be empty
44+
// scalar leaves are flattened to their last path segment
45+
ifUnset("DATASET_SINGLE_FILE_UPLOAD_MAX_SIZE_MIB")(
46+
defaults.get("single_file_upload_max_size_mib") shouldBe Some("20")
47+
)
48+
ifUnset("GUI_TABS_HUB_ENABLED")(defaults.get("hub_enabled") shouldBe Some("true"))
49+
// every value is rendered as a String
50+
defaults.values.foreach(_ shouldBe a[String])
51+
}
52+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.texera.common.config
21+
22+
import org.scalatest.flatspec.AnyFlatSpec
23+
import org.scalatest.matchers.should.Matchers
24+
25+
/**
26+
* Spec for [[LLMConfig]]. Reading each value forces resolution from llm.conf, so a renamed or
27+
* mistyped key surfaces here as a ConfigException. Both values carry a `${?ENV}` override, so
28+
* exact-value assertions are guarded.
29+
*/
30+
class LLMConfigSpec extends AnyFlatSpec with Matchers {
31+
32+
private def ifUnset(name: String)(assertion: => Any): Unit =
33+
if (!sys.env.contains(name) && !sys.props.contains(name)) assertion
34+
35+
"LLMConfig" should "resolve the LiteLLM base URL and master key from llm.conf" in {
36+
ifUnset("LITELLM_BASE_URL")(LLMConfig.baseUrl shouldBe "http://0.0.0.0:4000")
37+
ifUnset("LITELLM_MASTER_KEY")(LLMConfig.masterKey shouldBe "")
38+
}
39+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.texera.common.config
21+
22+
import org.scalatest.flatspec.AnyFlatSpec
23+
import org.scalatest.matchers.should.Matchers
24+
25+
/**
26+
* Spec for [[PekkoConfig]]. Reading keys off the returned config forces resolution of cluster.conf
27+
* (merged with the Typesafe default application config), so a renamed or mistyped key surfaces here
28+
* as a ConfigException. The log level carries a `${?ENV}` override, so its assertion is guarded.
29+
*/
30+
class PekkoConfigSpec extends AnyFlatSpec with Matchers {
31+
32+
"PekkoConfig.pekkoConfig" should "load the actor/serialization settings from cluster.conf" in {
33+
val config = PekkoConfig.pekkoConfig
34+
config.getString("pekko.actor.provider") shouldBe "cluster"
35+
config.getBoolean("pekko.actor.allow-java-serialization") shouldBe false
36+
config.getBoolean("pekko.actor.enable-additional-serialization-bindings") shouldBe true
37+
config.getString(
38+
"pekko.actor.serializers.kryo"
39+
) shouldBe "io.altoo.serialization.kryo.pekko.PekkoKryoSerializer"
40+
config.getStringList("pekko.loggers").get(0) shouldBe "org.apache.pekko.event.slf4j.Slf4jLogger"
41+
config.getString(
42+
"pekko.logging-filter"
43+
) shouldBe "org.apache.pekko.event.slf4j.Slf4jLoggingFilter"
44+
}
45+
46+
it should "expose the remote/artery transport settings" in {
47+
val config = PekkoConfig.pekkoConfig
48+
config.getString("pekko.remote.artery.transport") shouldBe "tcp"
49+
config.getString("pekko.remote.artery.canonical.hostname") shouldBe "0.0.0.0"
50+
config.getInt("pekko.remote.artery.canonical.port") shouldBe 0
51+
config.getBytes("pekko.remote.artery.advanced.maximum-frame-size") shouldBe 31457280L
52+
config.getBytes("pekko.remote.artery.advanced.maximum-large-frame-size") shouldBe 125829120L
53+
}
54+
55+
it should "expose the cluster and failure-detector settings" in {
56+
val config = PekkoConfig.pekkoConfig
57+
config.getStringList("pekko.cluster.seed-nodes").size shouldBe 0
58+
config.getString(
59+
"pekko.cluster.downing-provider-class"
60+
) shouldBe "org.apache.pekko.cluster.sbr.SplitBrainResolverProvider"
61+
config.getString("pekko.cluster.gossip-interval") shouldBe "10s"
62+
config.getString("pekko.cluster.failure-detector.acceptable-heartbeat-pause") shouldBe "50s"
63+
config.getString(
64+
"pekko-kryo-serialization.kryo-initializer"
65+
) shouldBe "org.apache.texera.amber.engine.common.AmberKryoInitializer"
66+
}
67+
68+
it should "resolve the log levels" in {
69+
val config = PekkoConfig.pekkoConfig
70+
// ${?TEXERA_SERVICE_LOG_LEVEL} can be satisfied by an OS env var or a JVM system property
71+
if (
72+
!sys.env
73+
.contains("TEXERA_SERVICE_LOG_LEVEL") && !sys.props.contains("TEXERA_SERVICE_LOG_LEVEL")
74+
) {
75+
config.getString("pekko.loglevel") shouldBe "INFO"
76+
}
77+
config.getString("pekko.stdout-loglevel") shouldBe "INFO"
78+
}
79+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.texera.common.config
21+
22+
import org.scalatest.flatspec.AnyFlatSpec
23+
import org.scalatest.matchers.should.Matchers
24+
25+
/**
26+
* Spec for [[PythonUtils]]. `getPythonExecutable` falls back to "python3" when the configured
27+
* python path is blank, else returns the trimmed path. The blank-path default depends on
28+
* UDF_PYTHON_PATH, so the exact-value assertion is guarded on that override being unset.
29+
*/
30+
class PythonUtilsSpec extends AnyFlatSpec with Matchers {
31+
32+
"PythonUtils.getPythonExecutable" should "fall back to python3 when no python path is configured" in {
33+
// ${?UDF_PYTHON_PATH} can be satisfied by an OS env var or a JVM system property
34+
val overrideValue =
35+
sys.env.get("UDF_PYTHON_PATH").orElse(sys.props.get("UDF_PYTHON_PATH"))
36+
if (overrideValue.forall(_.trim.isEmpty)) {
37+
PythonUtils.getPythonExecutable shouldBe "python3"
38+
}
39+
}
40+
41+
it should "never return a blank or untrimmed executable" in {
42+
val executable = PythonUtils.getPythonExecutable
43+
executable should not be empty
44+
executable shouldBe executable.trim
45+
}
46+
47+
it should "match its own fallback logic against the backing UdfConfig value" in {
48+
val expected =
49+
if (UdfConfig.pythonPath.trim.isEmpty) "python3" else UdfConfig.pythonPath.trim
50+
PythonUtils.getPythonExecutable shouldBe expected
51+
}
52+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.texera.common.config
21+
22+
import org.scalatest.flatspec.AnyFlatSpec
23+
import org.scalatest.matchers.should.Matchers
24+
25+
/**
26+
* Spec for [[UdfConfig]]. Reading each value forces resolution from udf.conf, so a renamed or
27+
* mistyped key surfaces here as a ConfigException. Every value carries a `${?ENV}` override, so
28+
* exact-value assertions are guarded on the override being absent from env and system properties.
29+
*/
30+
class UdfConfigSpec extends AnyFlatSpec with Matchers {
31+
32+
private def ifUnset(name: String)(assertion: => Any): Unit =
33+
if (!sys.env.contains(name) && !sys.props.contains(name)) assertion
34+
35+
"UdfConfig" should "resolve the python and R defaults from udf.conf" in {
36+
ifUnset("UDF_PYTHON_PATH")(UdfConfig.pythonPath shouldBe "")
37+
ifUnset("UDF_R_PATH")(UdfConfig.rPath shouldBe "")
38+
ifUnset("UDF_PYTHON_LOG_STREAMHANDLER_LEVEL")(
39+
UdfConfig.pythonLogStreamHandlerLevel shouldBe "INFO"
40+
)
41+
ifUnset("UDF_PYTHON_LOG_FILEHANDLER_DIR")(UdfConfig.pythonLogFileHandlerDir shouldBe "/tmp/")
42+
ifUnset("UDF_PYTHON_LOG_FILEHANDLER_LEVEL")(UdfConfig.pythonLogFileHandlerLevel shouldBe "INFO")
43+
}
44+
45+
it should "resolve the non-empty log format strings" in {
46+
ifUnset("UDF_PYTHON_LOG_STREAMHANDLER_FORMAT")(
47+
UdfConfig.pythonLogStreamHandlerFormat should not be empty
48+
)
49+
ifUnset("UDF_PYTHON_LOG_FILEHANDLER_FORMAT")(
50+
UdfConfig.pythonLogFileHandlerFormat should not be empty
51+
)
52+
}
53+
}

0 commit comments

Comments
 (0)