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 21a931572dfd test(client): cover low-coverage small classes across
hudi-client (#19224)
21a931572dfd is described below
commit 21a931572dfd07d8ba18c4cd64cd721aaf5e3df1
Author: Y Ethan Guo <[email protected]>
AuthorDate: Wed Jul 8 05:30:15 2026 -0700
test(client): cover low-coverage small classes across hudi-client (#19224)
* test(client): cover low-coverage small classes across hudi-client
* test(client): address review nits on tail-sweep coverage tests
- Assert content equality instead of reference identity for the
candidate-key
and data-write-stat accessors, so the tests no longer pin the
no-defensive-copy
implementation detail.
- Document that isKeyInRange fails fast with an NPE when bounds are unset.
- Use assertNotEquals for the BloomIndexFileInfo inequality check, matching
the
sibling equality tests.
---------
Co-authored-by: voon <[email protected]>
---
.../apache/hudi/client/TestClientStatsPojos.java | 94 ++++++++++++++++++++++
.../TestBootstrapPartitionPathTranslators.java | 46 +++++++++++
.../transaction/lock/TestLockResultEnums.java | 62 ++++++++++++++
.../lock/audit/TestAuditOperationState.java | 54 +++++++++++++
.../hudi/exception/TestClientExceptions.java | 71 ++++++++++++++++
.../bulkinsert/TestBulkInsertSortMode.java | 52 ++++++++++++
.../hudi/index/bloom/TestBloomIndexFileInfo.java | 79 ++++++++++++++++++
.../table/action/commit/TestBucketTypeAndInfo.java | 72 +++++++++++++++++
.../apache/hudi/util/TestOperationConverter.java | 46 +++++++++++
...stJavaBulkInsertInternalPartitionerFactory.java | 53 ++++++++++++
.../bloom/TestHoodieBloomFilterProbingResult.java | 64 +++++++++++++++
.../action/commit/TestSparkBucketInfoGetter.java | 68 ++++++++++++++++
12 files changed, 761 insertions(+)
diff --git
a/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/client/TestClientStatsPojos.java
b/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/client/TestClientStatsPojos.java
new file mode 100644
index 000000000000..f971c0369568
--- /dev/null
+++
b/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/client/TestClientStatsPojos.java
@@ -0,0 +1,94 @@
+/*
+ * 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.client;
+
+import org.apache.hudi.common.model.HoodieWriteStat;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests the plain client-side stat holders {@link SecondaryIndexStats} and
{@link TableWriteStats}.
+ */
+public class TestClientStatsPojos {
+
+ @Test
+ void secondaryIndexStatsExposesConstructorValues() {
+ SecondaryIndexStats deleted = new SecondaryIndexStats("rk1", "sk1", true);
+ assertEquals("rk1", deleted.getRecordKey());
+ assertEquals("sk1", deleted.getSecondaryKeyValue());
+ assertTrue(deleted.isDeleted());
+
+ SecondaryIndexStats live = new SecondaryIndexStats("rk2", "sk2", false);
+ assertEquals("rk2", live.getRecordKey());
+ assertEquals("sk2", live.getSecondaryKeyValue());
+ assertFalse(live.isDeleted());
+ }
+
+ @Test
+ void secondaryIndexStatsSetterMutatesRecordKey() {
+ SecondaryIndexStats stats = new SecondaryIndexStats("rk1", "sk1", false);
+ stats.setRecordKey("rk2");
+ stats.setSecondaryKeyValue("sk2");
+ assertEquals("rk2", stats.getRecordKey());
+ assertEquals("sk2", stats.getSecondaryKeyValue());
+ }
+
+ @Test
+ void secondaryIndexStatsEqualityUsesAllFields() {
+ SecondaryIndexStats a = new SecondaryIndexStats("rk", "sk", false);
+ SecondaryIndexStats same = new SecondaryIndexStats("rk", "sk", false);
+ SecondaryIndexStats deleteDiffers = new SecondaryIndexStats("rk", "sk",
true);
+ assertEquals(a, same);
+ assertEquals(a.hashCode(), same.hashCode());
+ assertNotEquals(a, deleteDiffers);
+ }
+
+ @Test
+ void tableWriteStatsSingleArgDefaultsMetadataToEmpty() {
+ List<HoodieWriteStat> dataStats = Collections.singletonList(new
HoodieWriteStat());
+ TableWriteStats stats = new TableWriteStats(dataStats);
+ assertEquals(dataStats, stats.getDataTableWriteStats());
+ assertTrue(stats.getMetadataTableWriteStats().isEmpty());
+ assertFalse(stats.isEmptyDataTableWriteStats());
+ }
+
+ @Test
+ void tableWriteStatsReportsEmptyDataStats() {
+ TableWriteStats empty = new TableWriteStats(Collections.emptyList());
+ assertTrue(empty.isEmptyDataTableWriteStats());
+ }
+
+ @Test
+ void tableWriteStatsRetainsBothLists() {
+ List<HoodieWriteStat> dataStats = Arrays.asList(new HoodieWriteStat(), new
HoodieWriteStat());
+ List<HoodieWriteStat> metadataStats = Collections.singletonList(new
HoodieWriteStat());
+ TableWriteStats stats = new TableWriteStats(dataStats, metadataStats);
+ assertEquals(2, stats.getDataTableWriteStats().size());
+ assertEquals(1, stats.getMetadataTableWriteStats().size());
+ }
+}
diff --git
a/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/client/bootstrap/translator/TestBootstrapPartitionPathTranslators.java
b/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/client/bootstrap/translator/TestBootstrapPartitionPathTranslators.java
new file mode 100644
index 000000000000..9598b581aa68
--- /dev/null
+++
b/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/client/bootstrap/translator/TestBootstrapPartitionPathTranslators.java
@@ -0,0 +1,46 @@
+/*
+ * 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.client.bootstrap.translator;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Tests the two built-in bootstrap partition-path translators.
+ */
+public class TestBootstrapPartitionPathTranslators {
+
+ @Test
+ void identityTranslatorReturnsInputUnchanged() {
+ IdentityBootstrapPartitionPathTranslator translator = new
IdentityBootstrapPartitionPathTranslator();
+ assertEquals("2024/01/01",
translator.getBootstrapTranslatedPath("2024/01/01"));
+ // Even already-encoded input is passed through verbatim.
+ assertEquals("2024%2F01",
translator.getBootstrapTranslatedPath("2024%2F01"));
+ }
+
+ @Test
+ void decodedTranslatorUriDecodesEscapedPath() {
+ DecodedBootstrapPartitionPathTranslator translator = new
DecodedBootstrapPartitionPathTranslator();
+ // %2F decodes to a slash.
+ assertEquals("2024/01",
translator.getBootstrapTranslatedPath("2024%2F01"));
+ // Paths without escape sequences are unaffected.
+ assertEquals("region=us",
translator.getBootstrapTranslatedPath("region=us"));
+ }
+}
diff --git
a/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/client/transaction/lock/TestLockResultEnums.java
b/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/client/transaction/lock/TestLockResultEnums.java
new file mode 100644
index 000000000000..51e944c214a9
--- /dev/null
+++
b/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/client/transaction/lock/TestLockResultEnums.java
@@ -0,0 +1,62 @@
+/*
+ * 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.client.transaction.lock;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+/**
+ * Tests the small result enums used by the storage-based lock provider.
+ */
+public class TestLockResultEnums {
+
+ @Test
+ void lockGetResultCodesAreStable() {
+ assertEquals(0, LockGetResult.NOT_EXISTS.getCode());
+ assertEquals(1, LockGetResult.SUCCESS.getCode());
+ assertEquals(2, LockGetResult.UNKNOWN_ERROR.getCode());
+ // Codes must be unique so callers can map them one-to-one.
+ assertEquals(3, LockGetResult.values().length);
+ }
+
+ @Test
+ void lockGetResultValueOfRoundTrips() {
+ for (LockGetResult result : LockGetResult.values()) {
+ assertSame(result, LockGetResult.valueOf(result.name()));
+ }
+ }
+
+ @Test
+ void lockUpsertResultCodesAreStable() {
+ assertEquals(0, LockUpsertResult.SUCCESS.getCode());
+ assertEquals(1, LockUpsertResult.ACQUIRED_BY_OTHERS.getCode());
+ assertEquals(2, LockUpsertResult.UNKNOWN_ERROR.getCode());
+ assertEquals(3, LockUpsertResult.THROTTLED.getCode());
+ assertEquals(4, LockUpsertResult.values().length);
+ }
+
+ @Test
+ void lockUpsertResultValueOfRoundTrips() {
+ for (LockUpsertResult result : LockUpsertResult.values()) {
+ assertSame(result, LockUpsertResult.valueOf(result.name()));
+ }
+ }
+}
diff --git
a/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/client/transaction/lock/audit/TestAuditOperationState.java
b/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/client/transaction/lock/audit/TestAuditOperationState.java
new file mode 100644
index 000000000000..fd9cdf7f44e1
--- /dev/null
+++
b/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/client/transaction/lock/audit/TestAuditOperationState.java
@@ -0,0 +1,54 @@
+/*
+ * 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.client.transaction.lock.audit;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Tests the {@link AuditOperationState} lifecycle enum.
+ */
+public class TestAuditOperationState {
+
+ @Test
+ void declaresExpectedStatesInOrder() {
+ assertArrayEquals(
+ new AuditOperationState[] {
+ AuditOperationState.START,
+ AuditOperationState.RENEW,
+ AuditOperationState.END
+ },
+ AuditOperationState.values());
+ }
+
+ @Test
+ void valueOfResolvesEachState() {
+ for (AuditOperationState state : AuditOperationState.values()) {
+ assertSame(state, AuditOperationState.valueOf(state.name()));
+ }
+ }
+
+ @Test
+ void valueOfRejectsUnknownState() {
+ assertThrows(IllegalArgumentException.class, () ->
AuditOperationState.valueOf("PAUSE"));
+ }
+}
diff --git
a/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/exception/TestClientExceptions.java
b/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/exception/TestClientExceptions.java
new file mode 100644
index 000000000000..8501aa3774b6
--- /dev/null
+++
b/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/exception/TestClientExceptions.java
@@ -0,0 +1,71 @@
+/*
+ * 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.exception;
+
+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;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests the thin client-side exception types that wrap {@link
HoodieException}.
+ */
+public class TestClientExceptions {
+
+ @Test
+ void keyGeneratorExceptionPreservesMessageAndCause() {
+ Throwable cause = new IllegalStateException("boom");
+ HoodieKeyGeneratorException withCause = new
HoodieKeyGeneratorException("bad key", cause);
+ assertEquals("bad key", withCause.getMessage());
+ assertSame(cause, withCause.getCause());
+ assertTrue(withCause instanceof HoodieException);
+
+ HoodieKeyGeneratorException messageOnly = new
HoodieKeyGeneratorException("no cause");
+ assertEquals("no cause", messageOnly.getMessage());
+ assertNull(messageOnly.getCause());
+ }
+
+ @Test
+ void compactionExceptionPreservesMessageAndCause() {
+ Throwable cause = new RuntimeException("io");
+ HoodieCompactionException withCause = new
HoodieCompactionException("compaction failed", cause);
+ assertEquals("compaction failed", withCause.getMessage());
+ assertSame(cause, withCause.getCause());
+ assertTrue(withCause instanceof HoodieException);
+
+ HoodieCompactionException messageOnly = new
HoodieCompactionException("compaction failed");
+ assertEquals("compaction failed", messageOnly.getMessage());
+ assertNull(messageOnly.getCause());
+ }
+
+ @Test
+ void rollbackExceptionPreservesMessageAndCause() {
+ Throwable cause = new RuntimeException("io");
+ HoodieRollbackException withCause = new HoodieRollbackException("rollback
failed", cause);
+ assertEquals("rollback failed", withCause.getMessage());
+ assertSame(cause, withCause.getCause());
+ assertTrue(withCause instanceof HoodieException);
+
+ HoodieRollbackException messageOnly = new
HoodieRollbackException("rollback failed");
+ assertEquals("rollback failed", messageOnly.getMessage());
+ assertNull(messageOnly.getCause());
+ }
+}
diff --git
a/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/execution/bulkinsert/TestBulkInsertSortMode.java
b/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/execution/bulkinsert/TestBulkInsertSortMode.java
new file mode 100644
index 000000000000..8ff7ef0679d5
--- /dev/null
+++
b/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/execution/bulkinsert/TestBulkInsertSortMode.java
@@ -0,0 +1,52 @@
+/*
+ * 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.execution.bulkinsert;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Tests {@link BulkInsertSortMode}. The constant names double as config
values, so the
+ * full set is pinned here to catch accidental renames or removals.
+ */
+public class TestBulkInsertSortMode {
+
+ @Test
+ void exposesExpectedModes() {
+ assertEquals(
+
"NONE,GLOBAL_SORT,PARTITION_SORT,PARTITION_PATH_REPARTITION,PARTITION_PATH_REPARTITION_AND_SORT",
+ Arrays.stream(BulkInsertSortMode.values())
+ .map(Enum::name)
+ .collect(Collectors.joining(",")));
+ }
+
+ @Test
+ void valueOfResolvesEachModeCaseSensitively() {
+ for (BulkInsertSortMode mode : BulkInsertSortMode.values()) {
+ assertSame(mode, BulkInsertSortMode.valueOf(mode.name()));
+ }
+ assertThrows(IllegalArgumentException.class, () ->
BulkInsertSortMode.valueOf("global_sort"));
+ }
+}
diff --git
a/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/index/bloom/TestBloomIndexFileInfo.java
b/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/index/bloom/TestBloomIndexFileInfo.java
new file mode 100644
index 000000000000..9f235d1d2337
--- /dev/null
+++
b/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/index/bloom/TestBloomIndexFileInfo.java
@@ -0,0 +1,79 @@
+/*
+ * 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.index.bloom;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests {@link BloomIndexFileInfo} accessors and key-range checks.
+ */
+public class TestBloomIndexFileInfo {
+
+ @Test
+ void fileIdOnlyConstructorLeavesRangeUnset() {
+ BloomIndexFileInfo info = new BloomIndexFileInfo("f1");
+ assertEquals("f1", info.getFileId());
+ assertNull(info.getMinRecordKey());
+ assertNull(info.getMaxRecordKey());
+ assertFalse(info.hasKeyRanges());
+ }
+
+ @Test
+ void fullConstructorPopulatesRange() {
+ BloomIndexFileInfo info = new BloomIndexFileInfo("f1", "key05", "key20");
+ assertEquals("key05", info.getMinRecordKey());
+ assertEquals("key20", info.getMaxRecordKey());
+ assertTrue(info.hasKeyRanges());
+ }
+
+ @Test
+ void isKeyInRangeIsInclusiveOfBounds() {
+ BloomIndexFileInfo info = new BloomIndexFileInfo("f1", "key05", "key20");
+ assertTrue(info.isKeyInRange("key05"));
+ assertTrue(info.isKeyInRange("key10"));
+ assertTrue(info.isKeyInRange("key20"));
+ assertFalse(info.isKeyInRange("key04"));
+ assertFalse(info.isKeyInRange("key21"));
+ }
+
+ @Test
+ void isKeyInRangeRequiresBounds() {
+ BloomIndexFileInfo noRange = new BloomIndexFileInfo("f1");
+ // isKeyInRange does not guard on hasKeyRanges(); with no bounds set it
currently
+ // fails fast with an NPE from Objects.requireNonNull rather than
returning false.
+ assertThrows(NullPointerException.class, () ->
noRange.isKeyInRange("key10"));
+ }
+
+ @Test
+ void valueSemanticsForEqualsAndHashCode() {
+ BloomIndexFileInfo a = new BloomIndexFileInfo("f1", "min", "max");
+ BloomIndexFileInfo same = new BloomIndexFileInfo("f1", "min", "max");
+ BloomIndexFileInfo different = new BloomIndexFileInfo("f2", "min", "max");
+ assertEquals(a, same);
+ assertEquals(a.hashCode(), same.hashCode());
+ assertNotEquals(a, different);
+ }
+}
diff --git
a/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/table/action/commit/TestBucketTypeAndInfo.java
b/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/table/action/commit/TestBucketTypeAndInfo.java
new file mode 100644
index 000000000000..537250c00e61
--- /dev/null
+++
b/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/table/action/commit/TestBucketTypeAndInfo.java
@@ -0,0 +1,72 @@
+/*
+ * 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.table.action.commit;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests {@link BucketType} and the {@link BucketInfo} value holder that
carries it.
+ */
+public class TestBucketTypeAndInfo {
+
+ @Test
+ void bucketTypeHasUpdateAndInsert() {
+ assertEquals(2, BucketType.values().length);
+ assertSame(BucketType.UPDATE, BucketType.valueOf("UPDATE"));
+ assertSame(BucketType.INSERT, BucketType.valueOf("INSERT"));
+ }
+
+ @Test
+ void bucketInfoGettersReturnConstructorArgs() {
+ BucketInfo info = new BucketInfo(BucketType.INSERT, "fileId-1",
"2024/01/01");
+ assertSame(BucketType.INSERT, info.getBucketType());
+ assertEquals("fileId-1", info.getFileIdPrefix());
+ assertEquals("2024/01/01", info.getPartitionPath());
+ }
+
+ @Test
+ void bucketInfoEqualsAndHashCodeUseAllFields() {
+ BucketInfo a = new BucketInfo(BucketType.UPDATE, "f1", "p1");
+ BucketInfo same = new BucketInfo(BucketType.UPDATE, "f1", "p1");
+ BucketInfo differentType = new BucketInfo(BucketType.INSERT, "f1", "p1");
+ BucketInfo differentFile = new BucketInfo(BucketType.UPDATE, "f2", "p1");
+ BucketInfo differentPartition = new BucketInfo(BucketType.UPDATE, "f1",
"p2");
+
+ assertEquals(a, same);
+ assertEquals(a.hashCode(), same.hashCode());
+ assertNotEquals(a, differentType);
+ assertNotEquals(a, differentFile);
+ assertNotEquals(a, differentPartition);
+ assertNotEquals(a, null);
+ assertNotEquals(a, "not a bucket info");
+ }
+
+ @Test
+ void bucketInfoToStringContainsFields() {
+ String rendered = new BucketInfo(BucketType.INSERT, "fileId-9",
"part-9").toString();
+ assertTrue(rendered.contains("INSERT"));
+ assertTrue(rendered.contains("fileId-9"));
+ assertTrue(rendered.contains("part-9"));
+ }
+}
diff --git
a/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/util/TestOperationConverter.java
b/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/util/TestOperationConverter.java
new file mode 100644
index 000000000000..e8cee32e7f4d
--- /dev/null
+++
b/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/util/TestOperationConverter.java
@@ -0,0 +1,46 @@
+/*
+ * 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.apache.hudi.common.model.WriteOperationType;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Tests {@link OperationConverter}, the jcommander string-to-enum converter.
+ */
+public class TestOperationConverter {
+
+ private final OperationConverter converter = new OperationConverter();
+
+ @Test
+ void convertsEnumConstantNames() {
+ assertSame(WriteOperationType.INSERT, converter.convert("INSERT"));
+ assertSame(WriteOperationType.UPSERT, converter.convert("UPSERT"));
+ assertSame(WriteOperationType.BULK_INSERT,
converter.convert("BULK_INSERT"));
+ }
+
+ @Test
+ void rejectsUnknownOperation() {
+ assertThrows(IllegalArgumentException.class, () ->
converter.convert("not_an_operation"));
+ }
+}
diff --git
a/hudi-client/hudi-java-client/src/test/java/org/apache/hudi/execution/bulkinsert/TestJavaBulkInsertInternalPartitionerFactory.java
b/hudi-client/hudi-java-client/src/test/java/org/apache/hudi/execution/bulkinsert/TestJavaBulkInsertInternalPartitionerFactory.java
new file mode 100644
index 000000000000..708e25d29cba
--- /dev/null
+++
b/hudi-client/hudi-java-client/src/test/java/org/apache/hudi/execution/bulkinsert/TestJavaBulkInsertInternalPartitionerFactory.java
@@ -0,0 +1,53 @@
+/*
+ * 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.execution.bulkinsert;
+
+import org.apache.hudi.exception.HoodieException;
+import org.apache.hudi.table.BulkInsertPartitioner;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Tests the sort-mode based selection in {@link
JavaBulkInsertInternalPartitionerFactory}.
+ */
+public class TestJavaBulkInsertInternalPartitionerFactory {
+
+ @Test
+ void noneModeReturnsNonSortPartitioner() {
+ BulkInsertPartitioner partitioner =
JavaBulkInsertInternalPartitionerFactory.get(BulkInsertSortMode.NONE);
+ assertInstanceOf(JavaNonSortPartitioner.class, partitioner);
+ }
+
+ @Test
+ void globalSortModeReturnsGlobalSortPartitioner() {
+ BulkInsertPartitioner partitioner =
JavaBulkInsertInternalPartitionerFactory.get(BulkInsertSortMode.GLOBAL_SORT);
+ assertInstanceOf(JavaGlobalSortPartitioner.class, partitioner);
+ }
+
+ @Test
+ void unsupportedModeThrows() {
+ assertThrows(HoodieException.class,
+ () ->
JavaBulkInsertInternalPartitionerFactory.get(BulkInsertSortMode.PARTITION_SORT));
+ assertThrows(HoodieException.class,
+ () ->
JavaBulkInsertInternalPartitionerFactory.get(BulkInsertSortMode.PARTITION_PATH_REPARTITION));
+ }
+}
diff --git
a/hudi-client/hudi-spark-client/src/test/java/org/apache/hudi/index/bloom/TestHoodieBloomFilterProbingResult.java
b/hudi-client/hudi-spark-client/src/test/java/org/apache/hudi/index/bloom/TestHoodieBloomFilterProbingResult.java
new file mode 100644
index 000000000000..ea4f663d2d49
--- /dev/null
+++
b/hudi-client/hudi-spark-client/src/test/java/org/apache/hudi/index/bloom/TestHoodieBloomFilterProbingResult.java
@@ -0,0 +1,64 @@
+/*
+ * 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.index.bloom;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests the {@link HoodieBloomFilterProbingResult} value holder.
+ */
+public class TestHoodieBloomFilterProbingResult {
+
+ @Test
+ void exposesCandidateKeys() {
+ Set<String> keys = new HashSet<>();
+ keys.add("k1");
+ keys.add("k2");
+ HoodieBloomFilterProbingResult result = new
HoodieBloomFilterProbingResult(keys);
+ assertEquals(keys, result.getCandidateKeys());
+ }
+
+ @Test
+ void supportsEmptyCandidateSet() {
+ HoodieBloomFilterProbingResult result =
+ new HoodieBloomFilterProbingResult(Collections.emptySet());
+ assertTrue(result.getCandidateKeys().isEmpty());
+ }
+
+ @Test
+ void valueSemanticsForEqualsAndHashCode() {
+ HoodieBloomFilterProbingResult a =
+ new HoodieBloomFilterProbingResult(new
HashSet<>(Collections.singletonList("k")));
+ HoodieBloomFilterProbingResult same =
+ new HoodieBloomFilterProbingResult(new
HashSet<>(Collections.singletonList("k")));
+ HoodieBloomFilterProbingResult different =
+ new HoodieBloomFilterProbingResult(new
HashSet<>(Collections.singletonList("other")));
+ assertEquals(a, same);
+ assertEquals(a.hashCode(), same.hashCode());
+ assertNotEquals(a, different);
+ }
+}
diff --git
a/hudi-client/hudi-spark-client/src/test/java/org/apache/hudi/table/action/commit/TestSparkBucketInfoGetter.java
b/hudi-client/hudi-spark-client/src/test/java/org/apache/hudi/table/action/commit/TestSparkBucketInfoGetter.java
new file mode 100644
index 000000000000..670db2c77c65
--- /dev/null
+++
b/hudi-client/hudi-spark-client/src/test/java/org/apache/hudi/table/action/commit/TestSparkBucketInfoGetter.java
@@ -0,0 +1,68 @@
+/*
+ * 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.table.action.commit;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Tests the list- and map-backed {@link SparkBucketInfoGetter}
implementations.
+ */
+public class TestSparkBucketInfoGetter {
+
+ private static BucketInfo bucket(String fileIdPrefix) {
+ return new BucketInfo(BucketType.INSERT, fileIdPrefix, "partition");
+ }
+
+ @Test
+ void listBasedGetterIndexesByPosition() {
+ BucketInfo first = bucket("f0");
+ BucketInfo second = bucket("f1");
+ List<BucketInfo> bucketInfoList = Arrays.asList(first, second);
+ ListBasedSparkBucketInfoGetter getter = new
ListBasedSparkBucketInfoGetter(bucketInfoList);
+ assertSame(first, getter.getBucketInfo(0));
+ assertSame(second, getter.getBucketInfo(1));
+ }
+
+ @Test
+ void listBasedGetterRejectsOutOfRangeIndex() {
+ ListBasedSparkBucketInfoGetter getter =
+ new ListBasedSparkBucketInfoGetter(Arrays.asList(bucket("f0")));
+ assertThrows(IndexOutOfBoundsException.class, () ->
getter.getBucketInfo(5));
+ }
+
+ @Test
+ void mapBasedGetterLooksUpByBucketNumber() {
+ Map<Integer, BucketInfo> bucketInfoMap = new HashMap<>();
+ BucketInfo target = bucket("f7");
+ bucketInfoMap.put(7, target);
+ MapBasedSparkBucketInfoGetter getter = new
MapBasedSparkBucketInfoGetter(bucketInfoMap);
+ assertSame(target, getter.getBucketInfo(7));
+ // Missing keys resolve to null rather than throwing.
+ assertNull(getter.getBucketInfo(0));
+ }
+}