This is an automated email from the ASF dual-hosted git repository.
voonhous pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hudi.git
The following commit(s) were added to refs/heads/master by this push:
new 7b377a51ae1b test(spark): cover low-coverage small classes in spark
datasource (#19164)
7b377a51ae1b is described below
commit 7b377a51ae1b3727795b73fafae76d7d67d32d90
Author: Y Ethan Guo <[email protected]>
AuthorDate: Fri Aug 28 02:45:33 2026 -0700
test(spark): cover low-coverage small classes in spark datasource (#19164)
One suite per class, each in the package of the class it covers:
- TestCachingIterator (hudi-spark-common, org.apache.hudi.util): full
iteration and hasNext idempotency against a counting source iterator.
The trait has had no in-repo user since #17457 moved the datasource
read paths onto the file-group reader; removing it is left to a
separate cleanup.
- TestHoodieCatalog: HoodieCatalog.convertTransforms for identity
columns, a single bucket transform, a sorted bucket transform keeping
its sort columns, and the two rejections (multiple bucket transforms,
unsupported transform). HoodieSparkCatalogUtils.MatchBucketTransform
is covered through it, including the sorted_bucket arm.
- TestBasicStagedTable (added by #19251): commit leaves the catalog
alone, abort drops the staged table through it.
- TestHoodieSparkValidateDuplicateKeyRecordMerger: the strategy id
pinned as the literal, since a table that loads the merger through
hoodie.write.record.merge.custom.implementation.classes selects it by
that id and persists it, plus the pre-combining fallback to
DefaultSparkRecordMerger. The merger was never wired in-repo: from
#7345 it appeared in production sources only as a TODO in
ProvidesHoodieConfig, beside the ValidateDuplicateKeyPayload
selection that #12588 deleted; the class doc now says so instead of
pointing at that deleted payload.
- TestProcedureParameterImpl (added by #19161): the factories'
required/default semantics, toString field presence, a hashCode
inequality case, and an equals case that isolates required from
default.
- TestCliSchemaProvider: the CLI bootstrap SchemaProvider in
org.apache.hudi.cli (distinct from the Streamer one; named to avoid
the TestSchemaProvider stubs in hudi-utilities and
hudi-kafka-connect), shaped like flink's
TestSchemaProviderCompatibility: legacy provider,
modern provider whose target falls back to the overridden source
HoodieSchema, and null handling. The getTargetHoodieSchema catch
comment now names the two provider shapes that reach it.
Production changes are limited to those two comments; no behavior
change.
---------
Co-authored-by: voon <[email protected]>
---
...odieSparkValidateDuplicateKeyRecordMerger.scala | 11 ++-
.../org/apache/hudi/util/TestCachingIterator.scala | 84 ++++++++++++++++++++
.../sql/hudi/catalog/TestBasicStagedTable.scala | 14 +++-
.../spark/sql/hudi/catalog/TestHoodieCatalog.scala | 74 ++++++++++++++++++
...odieSparkValidateDuplicateKeyRecordMerger.scala | 37 +++++++++
.../java/org/apache/hudi/cli/SchemaProvider.java | 10 ++-
.../org/apache/hudi/cli/TestCliSchemaProvider.java | 90 ++++++++++++++++++++++
.../procedure/TestProcedureParameterImpl.scala | 41 ++++++++--
8 files changed, 346 insertions(+), 15 deletions(-)
diff --git
a/hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/spark/sql/hudi/command/HoodieSparkValidateDuplicateKeyRecordMerger.scala
b/hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/spark/sql/hudi/command/HoodieSparkValidateDuplicateKeyRecordMerger.scala
index ec99711704b9..a01010d2cdf6 100644
---
a/hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/spark/sql/hudi/command/HoodieSparkValidateDuplicateKeyRecordMerger.scala
+++
b/hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/spark/sql/hudi/command/HoodieSparkValidateDuplicateKeyRecordMerger.scala
@@ -28,9 +28,14 @@ import org.apache.hudi.exception.HoodieDuplicateKeyException
import
org.apache.spark.sql.hudi.command.HoodieSparkValidateDuplicateKeyRecordMerger.STRATEGY_ID
/**
- * Validate the duplicate key for insert statement without enable the
INSERT_DROP_DUPS_OPT
- * config.
- * @see org.apache.spark.sql.hudi.command.ValidateDuplicateKeyPayload
+ * Merger that rejects every merge with a [[HoodieDuplicateKeyException]], for
INSERT statements that must fail on a
+ * duplicate key rather than drop or update it.
+ *
+ * Never wired in-repo: from its introduction in #7345 it appeared in
production sources only as a TODO in
+ * `ProvidesHoodieConfig`, beside the `ValidateDuplicateKeyPayload` selection
that #12588 deleted (the strict-insert
+ * duplicate check lives in `DataSourceUtils.shouldIncludeRecord`). It stays
loadable through
+ * `HoodieWriteConfig.RECORD_MERGE_IMPL_CLASSES`, where it is selected by
[[STRATEGY_ID]], which such tables then
+ * persist as their merge strategy id.
*/
class HoodieSparkValidateDuplicateKeyRecordMerger extends
HoodieSparkRecordMerger with OperationModeAwareness {
diff --git
a/hudi-spark-datasource/hudi-spark-common/src/test/scala/org/apache/hudi/util/TestCachingIterator.scala
b/hudi-spark-datasource/hudi-spark-common/src/test/scala/org/apache/hudi/util/TestCachingIterator.scala
new file mode 100644
index 000000000000..cd9519784549
--- /dev/null
+++
b/hudi-spark-datasource/hudi-spark-common/src/test/scala/org/apache/hudi/util/TestCachingIterator.scala
@@ -0,0 +1,84 @@
+/*
+ * 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.hudi.util
+
+import org.junit.jupiter.api.Assertions.{assertEquals, assertFalse, assertTrue}
+import org.junit.jupiter.api.Test
+
+import scala.collection.mutable.ArrayBuffer
+
+/**
+ * Tests the [[CachingIterator]] contract: repeated
[[CachingIterator.hasNext]] calls step the underlying
+ * iterator at most once until [[CachingIterator.next]] consumes the cached
record.
+ */
+class TestCachingIterator {
+
+ @Test
+ def testYieldsAllElements(): Unit = {
+ val it = new SeqCachingIterator(new CountingIterator(Seq("a", "b", "c")))
+ val collected = ArrayBuffer[String]()
+ while (it.hasNext) {
+ collected += it.next
+ }
+ assertEquals(Seq("a", "b", "c"), collected.toSeq)
+ assertFalse(it.hasNext)
+ }
+
+ @Test
+ def testHasNextIsIdempotent(): Unit = {
+ val source = new CountingIterator(Seq("x", "y"))
+ val it = new SeqCachingIterator(source)
+ // Repeated hasNext without next must not advance the underlying iterator.
+ assertTrue(it.hasNext)
+ assertTrue(it.hasNext)
+ assertTrue(it.hasNext)
+ assertEquals(1, source.advances)
+ assertEquals("x", it.next)
+ assertEquals(1, source.advances)
+ assertTrue(it.hasNext)
+ assertEquals(2, source.advances)
+ assertEquals("y", it.next)
+ assertFalse(it.hasNext)
+ assertEquals(2, source.advances)
+ }
+
+ /** Concrete [[CachingIterator]] backed by a plain iterator, used to
exercise the trait. */
+ private class SeqCachingIterator(source: Iterator[String]) extends
CachingIterator[String] {
+ override protected def doHasNext: Boolean = {
+ if (source.hasNext) {
+ nextRecord = source.next()
+ true
+ } else {
+ false
+ }
+ }
+ }
+
+ /** Iterator that counts how many times it has been advanced. */
+ private class CountingIterator(elems: Seq[String]) extends Iterator[String] {
+ private val underlying = elems.iterator
+ var advances: Int = 0
+
+ override def hasNext: Boolean = underlying.hasNext
+
+ override def next(): String = {
+ advances += 1
+ underlying.next()
+ }
+ }
+}
diff --git
a/hudi-spark-datasource/hudi-spark-common/src/test/scala/org/apache/spark/sql/hudi/catalog/TestBasicStagedTable.scala
b/hudi-spark-datasource/hudi-spark-common/src/test/scala/org/apache/spark/sql/hudi/catalog/TestBasicStagedTable.scala
index 7efc70a82abf..27545fab1eca 100644
---
a/hudi-spark-datasource/hudi-spark-common/src/test/scala/org/apache/spark/sql/hudi/catalog/TestBasicStagedTable.scala
+++
b/hudi-spark-datasource/hudi-spark-common/src/test/scala/org/apache/spark/sql/hudi/catalog/TestBasicStagedTable.scala
@@ -23,12 +23,24 @@ import org.apache.spark.sql.connector.catalog.{Identifier,
SupportsWrite, Table,
import org.apache.spark.sql.connector.write.{LogicalWriteInfo, WriteBuilder}
import org.junit.jupiter.api.Assertions.{assertSame, assertThrows, assertTrue}
import org.junit.jupiter.api.Test
-import org.mockito.Mockito.{mock, when}
+import org.mockito.Mockito.{mock, verify, verifyNoInteractions, when}
class TestBasicStagedTable {
private val ident = Identifier.of(Array("db"), "tbl")
+ @Test
+ def testCommitLeavesCatalogAloneAndAbortDropsTable(): Unit = {
+ val catalog = mock(classOf[TableCatalog])
+ val staged = BasicStagedTable(ident, mock(classOf[Table]), catalog)
+
+ staged.commitStagedChanges()
+ verifyNoInteractions(catalog)
+
+ staged.abortStagedChanges()
+ verify(catalog).dropTable(ident)
+ }
+
@Test
def testNewWriteBuilderDelegatesToWritableTable(): Unit = {
val table = mock(classOf[SupportsWrite])
diff --git
a/hudi-spark-datasource/hudi-spark-common/src/test/scala/org/apache/spark/sql/hudi/catalog/TestHoodieCatalog.scala
b/hudi-spark-datasource/hudi-spark-common/src/test/scala/org/apache/spark/sql/hudi/catalog/TestHoodieCatalog.scala
new file mode 100644
index 000000000000..845ee6f7d040
--- /dev/null
+++
b/hudi-spark-datasource/hudi-spark-common/src/test/scala/org/apache/spark/sql/hudi/catalog/TestHoodieCatalog.scala
@@ -0,0 +1,74 @@
+/*
+ * 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.spark.sql.hudi.catalog
+
+import org.apache.hudi.exception.HoodieException
+
+import org.apache.spark.sql.catalyst.catalog.BucketSpec
+import org.apache.spark.sql.connector.expressions.{Expressions,
LogicalExpressions, Transform}
+import org.junit.jupiter.api.Assertions.{assertEquals, assertThrows,
assertTrue}
+import org.junit.jupiter.api.Test
+
+/**
+ * Tests [[HoodieCatalog.convertTransforms]], which maps the V2 partition
transforms of a CREATE TABLE into
+ * identity partition columns plus an optional bucket spec.
+ * [[org.apache.spark.sql.HoodieSparkCatalogUtils.MatchBucketTransform]] is
exercised through it, including
+ * the sorted-bucket arm.
+ */
+class TestHoodieCatalog {
+
+ @Test
+ def testIdentityTransformsBecomePartitionColumns(): Unit = {
+ val (partitionCols, bucketSpec) = HoodieCatalog.convertTransforms(
+ Seq(Expressions.identity("dt"), Expressions.identity("region")))
+ assertEquals(Seq("dt", "region"), partitionCols)
+ assertTrue(bucketSpec.isEmpty)
+ }
+
+ @Test
+ def testBucketTransformBecomesBucketSpec(): Unit = {
+ val (partitionCols, bucketSpec) = HoodieCatalog.convertTransforms(
+ Seq(Expressions.identity("dt"), Expressions.bucket(8, "id")))
+ assertEquals(Seq("dt"), partitionCols)
+ assertEquals(Some(BucketSpec(8, Seq("id"), Nil)), bucketSpec)
+ }
+
+ @Test
+ def testSortedBucketTransformKeepsSortColumns(): Unit = {
+ val sortedBucket: Transform = LogicalExpressions.bucket(
+ 4, Array(Expressions.column("id")), Array(Expressions.column("ts")))
+ val (partitionCols, bucketSpec) =
HoodieCatalog.convertTransforms(Seq(sortedBucket))
+ assertTrue(partitionCols.isEmpty)
+ assertEquals(Some(BucketSpec(4, Seq("id"), Seq("ts"))), bucketSpec)
+ }
+
+ @Test
+ def testMultipleBucketTransformsAreRejected(): Unit = {
+ val ex = assertThrows(classOf[HoodieException], () =>
HoodieCatalog.convertTransforms(
+ Seq(Expressions.bucket(8, "id"), Expressions.bucket(4, "name"))))
+ assertTrue(ex.getMessage.contains("Multiple bucket transformations are not
supported"))
+ }
+
+ @Test
+ def testUnsupportedTransformIsRejected(): Unit = {
+ val ex = assertThrows(classOf[HoodieException], () =>
HoodieCatalog.convertTransforms(
+ Seq(Expressions.years("ts"))))
+ assertTrue(ex.getMessage.startsWith("Partitioning by transformation"),
ex.getMessage)
+ assertTrue(ex.getMessage.contains("years"), ex.getMessage)
+ }
+}
diff --git
a/hudi-spark-datasource/hudi-spark-common/src/test/scala/org/apache/spark/sql/hudi/command/TestHoodieSparkValidateDuplicateKeyRecordMerger.scala
b/hudi-spark-datasource/hudi-spark-common/src/test/scala/org/apache/spark/sql/hudi/command/TestHoodieSparkValidateDuplicateKeyRecordMerger.scala
new file mode 100644
index 000000000000..ff17f45dda27
--- /dev/null
+++
b/hudi-spark-datasource/hudi-spark-common/src/test/scala/org/apache/spark/sql/hudi/command/TestHoodieSparkValidateDuplicateKeyRecordMerger.scala
@@ -0,0 +1,37 @@
+/*
+ * 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.spark.sql.hudi.command
+
+import org.apache.hudi.DefaultSparkRecordMerger
+
+import org.junit.jupiter.api.Assertions.{assertEquals, assertTrue}
+import org.junit.jupiter.api.Test
+
+class TestHoodieSparkValidateDuplicateKeyRecordMerger {
+
+ @Test
+ def testMergingStrategyAndPreCombiningMode(): Unit = {
+ val merger = new HoodieSparkValidateDuplicateKeyRecordMerger
+ // No in-repo code path has ever instantiated this merger (see the class
doc), but a table may load it through
+ // HoodieWriteConfig.RECORD_MERGE_IMPL_CLASSES; the id then selects the
merger and is persisted as the table's
+ // merge strategy id, so pin the literal: a change to the constant would
break those tables.
+ assertEquals("fb092649-0fdc-4c14-9113-acde3034a6c4",
merger.getMergingStrategy)
+ // Pre-combining falls back to the default Spark record merger.
+
assertTrue(merger.asPreCombiningMode().isInstanceOf[DefaultSparkRecordMerger])
+ }
+}
diff --git
a/hudi-spark-datasource/hudi-spark/src/main/java/org/apache/hudi/cli/SchemaProvider.java
b/hudi-spark-datasource/hudi-spark/src/main/java/org/apache/hudi/cli/SchemaProvider.java
index dd81229aeba6..335480fb04d3 100644
---
a/hudi-spark-datasource/hudi-spark/src/main/java/org/apache/hudi/cli/SchemaProvider.java
+++
b/hudi-spark-datasource/hudi-spark/src/main/java/org/apache/hudi/cli/SchemaProvider.java
@@ -30,8 +30,9 @@ import org.apache.spark.api.java.JavaSparkContext;
import java.io.Serializable;
/**
- * Class to provide schema for reading data and also writing into a Hoodie
table,
- * used by Hudi Streamer (runs over Spark).
+ * Class to provide the schema for the CLI bootstrap path ({@link
BootstrapExecutorUtils}, which loads the
+ * configured implementation by class name); distinct from the Hudi Streamer
provider in
+ * {@code org.apache.hudi.utilities.schema}.
*/
@PublicAPIClass(maturity = ApiMaturityLevel.STABLE)
public abstract class SchemaProvider implements Serializable {
@@ -73,8 +74,9 @@ public abstract class SchemaProvider implements Serializable {
Schema schema = getTargetSchema();
return schema == null ? null : HoodieSchema.fromAvroSchema(schema);
} catch (UnsupportedOperationException e) {
- // If the legacy getTargetSchema() calls getSourceSchema() which is not
implemented,
- // fall back to using getSourceHoodieSchema as target schema
+ // Reached by a provider that overrides getSourceHoodieSchema() and
leaves the deprecated
+ // getSourceSchema() (hence the default getTargetSchema()) throwing, or
that overrides
+ // getTargetSchema() to throw; either way the source HoodieSchema is the
target.
return getSourceHoodieSchema();
}
}
diff --git
a/hudi-spark-datasource/hudi-spark/src/test/java/org/apache/hudi/cli/TestCliSchemaProvider.java
b/hudi-spark-datasource/hudi-spark/src/test/java/org/apache/hudi/cli/TestCliSchemaProvider.java
new file mode 100644
index 000000000000..a9bfc3a2e879
--- /dev/null
+++
b/hudi-spark-datasource/hudi-spark/src/test/java/org/apache/hudi/cli/TestCliSchemaProvider.java
@@ -0,0 +1,90 @@
+/*
+ * 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.hudi.cli;
+
+import org.apache.hudi.common.config.TypedProperties;
+import org.apache.hudi.common.schema.HoodieSchema;
+
+import org.apache.avro.Schema;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+/**
+ * Tests the CLI bootstrap {@link SchemaProvider} ({@code
org.apache.hudi.cli}), which is distinct from the Hudi
+ * Streamer one ({@code org.apache.hudi.utilities.schema.SchemaProvider}). Its
only in-repo consumer is
+ * {@link BootstrapExecutorUtils}, which calls the legacy {@link
SchemaProvider#getTargetSchema()}; the
+ * {@link HoodieSchema} accessors and the target fallback are public API for
out-of-tree providers and are pinned
+ * here directly.
+ */
+public class TestCliSchemaProvider {
+
+ private static final Schema AVRO_SCHEMA = new Schema.Parser().parse(
+
"{\"type\":\"record\",\"name\":\"r\",\"fields\":[{\"name\":\"id\",\"type\":\"int\"}]}");
+
+ @Test
+ @SuppressWarnings("deprecation")
+ void testLegacyProviderConvertsSourceAndTargetSchemas() {
+ SchemaProvider provider = new SchemaProvider(new TypedProperties()) {
+ @Override
+ public Schema getSourceSchema() {
+ return AVRO_SCHEMA;
+ }
+ };
+
+ // The target schema defaults to the source schema, and both are wrapped
into HoodieSchema.
+ assertSame(AVRO_SCHEMA, provider.getTargetSchema());
+ assertEquals(HoodieSchema.fromAvroSchema(AVRO_SCHEMA),
provider.getSourceHoodieSchema());
+ assertEquals(HoodieSchema.fromAvroSchema(AVRO_SCHEMA),
provider.getTargetHoodieSchema());
+ }
+
+ @Test
+ void testModernProviderFallsBackToSourceHoodieSchemaForTarget() {
+ HoodieSchema schema = HoodieSchema.fromAvroSchema(AVRO_SCHEMA);
+ SchemaProvider provider = new SchemaProvider(new TypedProperties()) {
+ @Override
+ public Schema getSourceSchema() {
+ throw new UnsupportedOperationException("legacy accessor not
implemented");
+ }
+
+ @Override
+ public HoodieSchema getSourceHoodieSchema() {
+ return schema;
+ }
+ };
+
+ // The default getTargetSchema() delegates to the throwing
getSourceSchema(), so the target falls back to
+ // the overridden source HoodieSchema.
+ assertSame(schema, provider.getTargetHoodieSchema());
+ }
+
+ @Test
+ void testNullSourceSchemaYieldsNullHoodieSchemas() {
+ SchemaProvider provider = new SchemaProvider(new TypedProperties()) {
+ @Override
+ public Schema getSourceSchema() {
+ return null;
+ }
+ };
+
+ assertNull(provider.getSourceHoodieSchema());
+ assertNull(provider.getTargetHoodieSchema());
+ }
+}
diff --git
a/hudi-spark-datasource/hudi-spark/src/test/scala/org/apache/spark/sql/hudi/procedure/TestProcedureParameterImpl.scala
b/hudi-spark-datasource/hudi-spark/src/test/scala/org/apache/spark/sql/hudi/procedure/TestProcedureParameterImpl.scala
index fac0db989444..79004efd392d 100644
---
a/hudi-spark-datasource/hudi-spark/src/test/scala/org/apache/spark/sql/hudi/procedure/TestProcedureParameterImpl.scala
+++
b/hudi-spark-datasource/hudi-spark/src/test/scala/org/apache/spark/sql/hudi/procedure/TestProcedureParameterImpl.scala
@@ -17,19 +17,33 @@
package org.apache.spark.sql.hudi.procedure
-import org.apache.spark.sql.hudi.command.procedures.ProcedureParameter
+import org.apache.spark.sql.hudi.command.procedures.{ProcedureParameter,
ProcedureParameterImpl}
import org.apache.spark.sql.types.DataTypes
-import org.junit.jupiter.api.Assertions.{assertEquals, assertFalse,
assertNotEquals, assertTrue}
+import org.junit.jupiter.api.Assertions.{assertEquals, assertFalse,
assertNotEquals, assertNull, assertTrue}
import org.scalatest.funsuite.AnyFunSuite
/**
- * Unit tests for ProcedureParameterImpl.equals. The implementation used to
call this == other for
- * the identity check, which dispatches straight back into equals and recurses
until it overflows
- * the stack, and it cast the argument before the null/type guard, so a
foreign argument threw
- * ClassCastException. Both are self-contained checks, so no Spark session is
needed.
+ * Unit tests for ProcedureParameterImpl: the ProcedureParameter factories,
equals, hashCode and
+ * toString. equals used to call this == other for the identity check, which
dispatches straight
+ * back into equals and recurses until it overflows the stack, and it cast the
argument before the
+ * null/type guard, so a foreign argument threw ClassCastException. All checks
are self-contained,
+ * so no Spark session is needed.
*/
class TestProcedureParameterImpl extends AnyFunSuite {
+ test("Test ProcedureParameter factories set required and default") {
+ val required = ProcedureParameter.required(0, "path", DataTypes.StringType)
+ assertTrue(required.required)
+ assertNull(required.default)
+
+ val optional = ProcedureParameter.optional(2, "limit",
DataTypes.IntegerType, 10)
+ assertFalse(optional.required)
+ assertEquals(10, optional.default)
+
+ // The default value itself defaults to null.
+ assertNull(ProcedureParameter.optional(3, "filter",
DataTypes.StringType).default)
+ }
+
test("Test ProcedureParameterImpl equals identity, foreign types and null") {
val param = ProcedureParameter.optional(0, "table", DataTypes.StringType,
"default")
@@ -58,6 +72,19 @@ class TestProcedureParameterImpl extends AnyFunSuite {
assertNotEquals(optional, ProcedureParameter.optional(1, "backup",
DataTypes.BooleanType, true))
assertNotEquals(optional, ProcedureParameter.optional(1, "dry_run",
DataTypes.StringType, true))
assertNotEquals(optional, ProcedureParameter.optional(1, "dry_run",
DataTypes.BooleanType, false))
- assertNotEquals(optional, ProcedureParameter.required(1, "dry_run",
DataTypes.BooleanType))
+ // Built directly so that only `required` differs; the required(...)
factory would also null the default.
+ assertNotEquals(optional, ProcedureParameterImpl(1, "dry_run",
DataTypes.BooleanType, true, required = true))
+
+ // hashCode is derived from the same fields, so a differing index also
changes the hash;
+ // without this a constant hashCode would satisfy the equal-hash
assertions above.
+ assertNotEquals(optional.hashCode(),
+ ProcedureParameter.optional(2, "dry_run", DataTypes.BooleanType,
true).hashCode())
+ }
+
+ test("Test ProcedureParameterImpl toString includes every field") {
+ val rendered = ProcedureParameter.optional(1, "col", DataTypes.StringType,
"def").toString
+ Seq("index='1'", "name='col'", "type=StringType", "required=false",
"default=def").foreach { field =>
+ assertTrue(rendered.contains(field), s"missing $field in $rendered")
+ }
}
}