This is an automated email from the ASF dual-hosted git repository.

danny0405 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 65cf7e8ede7d test: improve meta sync coverage (#19539)
65cf7e8ede7d is described below

commit 65cf7e8ede7d1a08f3b3f13b4a45c6229043cc3e
Author: Danny Chan <[email protected]>
AuthorDate: Fri Aug 7 17:16:05 2026 +0800

    test: improve meta sync coverage (#19539)
    
    * test: improve meta sync coverage
---
 .../hive/TestHoodieHiveSyncClientOperations.java   | 226 +++++++++++++++++++++
 .../hive/ddl/TestHiveQueryDDLExecutorFailures.java |  85 ++++++++
 .../hive/ddl/TestJDBCBasedMetadataOperator.java    |  24 +++
 .../TestHiveSyncGlobalCommitToolUnit.java          |  90 ++++++++
 ...iveMetastoreBasedLockProviderConfiguration.java |  68 +++++++
 .../sync/common/TestHoodieMetaSyncOperations.java  |  72 +++++++
 6 files changed, 565 insertions(+)

diff --git 
a/hudi-sync/hudi-hive-sync/src/test/java/org/apache/hudi/hive/TestHoodieHiveSyncClientOperations.java
 
b/hudi-sync/hudi-hive-sync/src/test/java/org/apache/hudi/hive/TestHoodieHiveSyncClientOperations.java
new file mode 100644
index 000000000000..c380cbb535e7
--- /dev/null
+++ 
b/hudi-sync/hudi-hive-sync/src/test/java/org/apache/hudi/hive/TestHoodieHiveSyncClientOperations.java
@@ -0,0 +1,226 @@
+/*
+ * 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.hive;
+
+import org.apache.hudi.common.util.Option;
+import org.apache.hudi.hive.ddl.DDLExecutor;
+import org.apache.hudi.hive.ddl.JDBCBasedMetadataOperator;
+import org.apache.hudi.sync.common.model.FieldSchema;
+import org.apache.hudi.sync.common.model.Partition;
+
+import org.apache.hadoop.hive.metastore.IMetaStoreClient;
+import org.apache.hadoop.hive.metastore.api.NoSuchObjectException;
+import org.apache.hadoop.hive.metastore.api.SerDeInfo;
+import org.apache.hadoop.hive.metastore.api.StorageDescriptor;
+import org.apache.hadoop.hive.metastore.api.Table;
+import org.apache.thrift.TApplicationException;
+import org.apache.thrift.TException;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+
+import java.lang.reflect.Field;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
+import static 
org.apache.hudi.hadoop.utils.HoodieHiveUtils.GLOBALLY_CONSISTENT_READ_TIMESTAMP;
+import static 
org.apache.hudi.sync.common.HoodieMetaSyncOperations.HOODIE_LAST_COMMIT_COMPLETION_TIME_SYNC;
+import static 
org.apache.hudi.sync.common.HoodieMetaSyncOperations.HOODIE_LAST_COMMIT_TIME_SYNC;
+import static 
org.apache.hudi.sync.common.HoodieSyncConfig.META_SYNC_BASE_FILE_FORMAT;
+import static org.apache.hudi.sync.common.HoodieSyncConfig.META_SYNC_BASE_PATH;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.CALLS_REAL_METHODS;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class TestHoodieHiveSyncClientOperations {
+
+  @Test
+  void thriftMismatchActivatesJdbcMetadataFallback() throws Exception {
+    TestFixture fixture = newFixture();
+    TApplicationException incompatible = new TApplicationException("unknown 
get_table method");
+    when(fixture.metaStoreClient.tableExists("test_db", "table"))
+        .thenThrow(new TException(incompatible));
+    when(fixture.jdbcMetadataOperator.tableExists("table")).thenReturn(true);
+    
when(fixture.jdbcMetadataOperator.databaseExists("test_db")).thenReturn(true);
+    when(fixture.jdbcMetadataOperator.getTableProperty("table", 
HOODIE_LAST_COMMIT_TIME_SYNC))
+        .thenReturn(Option.of("100"));
+    when(fixture.jdbcMetadataOperator.getTableProperty("table", 
HOODIE_LAST_COMMIT_COMPLETION_TIME_SYNC))
+        .thenReturn(Option.of("101"));
+    when(fixture.jdbcMetadataOperator.getTableProperty("table", 
GLOBALLY_CONSISTENT_READ_TIMESTAMP))
+        .thenReturn(Option.of("099"));
+    
when(fixture.jdbcMetadataOperator.getTableLocation("table")).thenReturn("/warehouse/table");
+    List<FieldSchema> fields = Collections.singletonList(new FieldSchema("id", 
"string", Option.empty()));
+    
when(fixture.jdbcMetadataOperator.getFieldSchemas("table")).thenReturn(fields);
+    List<Partition> partitions = Collections.singletonList(
+        new Partition(Collections.singletonList("2026-08-06"), 
"/base/datestr=2026-08-06"));
+    when(fixture.jdbcMetadataOperator.getAllPartitions("table", 
"/base")).thenReturn(partitions);
+
+    assertTrue(fixture.syncClient.tableExists("table"));
+    assertTrue(fixture.syncClient.updateTableProperties("table", 
Collections.singletonMap("owner", "hudi")));
+    assertTrue(fixture.syncClient.updateSerdeProperties("table",
+        new HashMap<>(Collections.singletonMap("compression", "none")), 
false));
+    assertEquals(partitions, fixture.syncClient.getAllPartitions("table"));
+    assertEquals(partitions,
+        fixture.syncClient.getPartitionsFromList("table", 
Collections.singletonList("datestr=2026-08-06")));
+    assertTrue(fixture.syncClient.databaseExists("test_db"));
+    assertEquals("100", 
fixture.syncClient.getLastCommitTimeSynced("table").get());
+    assertEquals("101", 
fixture.syncClient.getLastCommitCompletionTimeSynced("table").get());
+    assertEquals("099", 
fixture.syncClient.getLastReplicatedTime("table").get());
+    assertEquals(fields, fixture.syncClient.getMetastoreFieldSchemas("table"));
+    assertEquals("/warehouse/table", 
fixture.syncClient.getTableLocation("table"));
+
+    fixture.syncClient.deleteLastReplicatedTimeStamp("table");
+    fixture.syncClient.createOrReplaceTable("table", null, "input", "output", 
"serde",
+        Collections.emptyMap(), Collections.emptyMap());
+
+    ArgumentCaptor<String> temporaryTableNameCaptor = 
ArgumentCaptor.forClass(String.class);
+    InOrder replacementOrder = inOrder(fixture.ddlExecutor, 
fixture.jdbcMetadataOperator);
+    
replacementOrder.verify(fixture.ddlExecutor).createTable(temporaryTableNameCaptor.capture(),
+        isNull(), eq("input"), eq("output"), eq("serde"), 
eq(Collections.emptyMap()), eq(Collections.emptyMap()));
+    String temporaryTableName = temporaryTableNameCaptor.getValue();
+    replacementOrder.verify(fixture.jdbcMetadataOperator).dropTable("table");
+    
replacementOrder.verify(fixture.jdbcMetadataOperator).renameTable(temporaryTableName,
 "table");
+
+    verify(fixture.jdbcMetadataOperator).unsetTableProperty("table", 
GLOBALLY_CONSISTENT_READ_TIMESTAMP);
+    verify(fixture.jdbcMetadataOperator).setStorageFormat(eq("table"), 
anyString(), anyString(), anyString(), any());
+  }
+
+  @Test
+  void metastoreFailuresAreWrappedWhenNoFallbackIsConfigured() throws 
Exception {
+    TestFixture fixture = newFixtureWithoutJdbc();
+
+    assertFalse(fixture.syncClient.updateTableProperties("table", 
Collections.emptyMap()));
+    assertFalse(fixture.syncClient.updateSerdeProperties("table", 
Collections.emptyMap(), false));
+
+    when(fixture.metaStoreClient.tableExists("test_db", 
"missing")).thenReturn(false);
+    assertThrows(IllegalArgumentException.class, () -> 
fixture.syncClient.getMetastoreSchema("missing"));
+
+    when(fixture.metaStoreClient.tableExists("test_db", 
"table")).thenThrow(new TException("unavailable"));
+    assertThrows(HoodieHiveSyncException.class, () -> 
fixture.syncClient.tableExists("table"));
+
+    when(fixture.metaStoreClient.getDatabase("test_db")).thenThrow(new 
TException("unavailable"));
+    assertThrows(HoodieHiveSyncException.class, () -> 
fixture.syncClient.databaseExists("test_db"));
+
+    when(fixture.metaStoreClient.getTable("test_db", "absent"))
+        .thenThrow(new NoSuchObjectException("absent"));
+    
assertFalse(fixture.syncClient.getLastReplicatedTime("absent").isPresent());
+    fixture.syncClient.deleteLastReplicatedTimeStamp("absent");
+
+    when(fixture.metaStoreClient.getTable("test_db", "table")).thenThrow(new 
TException("unavailable"));
+    assertThrows(HoodieHiveSyncException.class, () -> 
fixture.syncClient.getLastCommitTimeSynced("table"));
+    assertThrows(HoodieHiveSyncException.class,
+        () -> fixture.syncClient.getLastCommitCompletionTimeSynced("table"));
+    assertThrows(HoodieHiveSyncException.class, () -> 
fixture.syncClient.getLastReplicatedTime("table"));
+    assertThrows(HoodieHiveSyncException.class, () -> 
fixture.syncClient.deleteLastReplicatedTimeStamp("table"));
+    assertThrows(HoodieHiveSyncException.class,
+        () -> fixture.syncClient.updateTableProperties("table", 
Collections.singletonMap("key", "value")));
+    assertThrows(HoodieHiveSyncException.class,
+        () -> fixture.syncClient.updateSerdeProperties("table", new 
HashMap<>(Collections.singletonMap("key", "value")), false));
+    assertThrows(HoodieHiveSyncException.class, () -> 
fixture.syncClient.getTableLocation("table"));
+    assertThrows(HoodieHiveSyncException.class, () -> 
fixture.syncClient.updateHoodieWriterVersion("table"));
+
+    when(fixture.metaStoreClient.getSchema("test_db", "table")).thenThrow(new 
TException("unavailable"));
+    assertThrows(HoodieHiveSyncException.class, () -> 
fixture.syncClient.getMetastoreFieldSchemas("table"));
+    org.mockito.Mockito.doThrow(new TException("unavailable"))
+        .when(fixture.metaStoreClient).dropTable("test_db", "table");
+    assertThrows(HoodieHiveSyncException.class, () -> 
fixture.syncClient.dropTable("table"));
+  }
+
+  @Test
+  void metastoreCreateReplaceAndSerdeAlterPathsUpdateMetadata() throws 
Exception {
+    TestFixture fixture = newFixtureWithoutJdbc();
+    when(fixture.metaStoreClient.tableExists("test_db", 
"table")).thenReturn(true);
+    Table temporaryTable = new Table();
+    when(fixture.metaStoreClient.getTable(eq("test_db"), 
anyString())).thenReturn(temporaryTable);
+
+    fixture.syncClient.createOrReplaceTable("table", null, "input", "output", 
"serde",
+        Collections.emptyMap(), Collections.emptyMap());
+
+    ArgumentCaptor<String> temporaryTableNameCaptor = 
ArgumentCaptor.forClass(String.class);
+    InOrder replacementOrder = inOrder(fixture.ddlExecutor, 
fixture.metaStoreClient);
+    
replacementOrder.verify(fixture.ddlExecutor).createTable(temporaryTableNameCaptor.capture(),
+        isNull(), eq("input"), eq("output"), eq("serde"), 
eq(Collections.emptyMap()), eq(Collections.emptyMap()));
+    String temporaryTableName = temporaryTableNameCaptor.getValue();
+    replacementOrder.verify(fixture.metaStoreClient).dropTable("test_db", 
"table");
+    replacementOrder.verify(fixture.metaStoreClient).alter_table("test_db", 
temporaryTableName, temporaryTable);
+
+    Table serdeTable = new Table();
+    StorageDescriptor storageDescriptor = new StorageDescriptor();
+    storageDescriptor.setInputFormat("old-input");
+    storageDescriptor.setOutputFormat("old-output");
+    storageDescriptor.setSerdeInfo((SerDeInfo) null);
+    serdeTable.setSd(storageDescriptor);
+    when(fixture.metaStoreClient.getTable("test_db", 
"serde_table")).thenReturn(serdeTable);
+
+    assertTrue(fixture.syncClient.updateSerdeProperties("serde_table",
+        new HashMap<>(Collections.singletonMap("compression", "none")), 
false));
+    
assertTrue(storageDescriptor.getSerdeInfo().getParameters().containsKey("serialization.format"));
+    assertFalse(fixture.syncClient.updateTableComments("table", 
Collections.emptyList(), Collections.emptyList()));
+  }
+
+  private static TestFixture newFixture() throws Exception {
+    TestFixture fixture = newFixtureWithoutJdbc();
+    fixture.jdbcMetadataOperator = mock(JDBCBasedMetadataOperator.class);
+    setField(fixture.syncClient, "jdbcMetadataOperator", 
fixture.jdbcMetadataOperator);
+    return fixture;
+  }
+
+  private static TestFixture newFixtureWithoutJdbc() throws Exception {
+    TestFixture fixture = new TestFixture();
+    fixture.syncClient = mock(HoodieHiveSyncClient.class, CALLS_REAL_METHODS);
+    fixture.config = mock(HiveSyncConfig.class);
+    fixture.metaStoreClient = mock(IMetaStoreClient.class);
+    fixture.ddlExecutor = mock(DDLExecutor.class);
+    when(fixture.config.getString(META_SYNC_BASE_PATH)).thenReturn("/base");
+    
when(fixture.config.getStringOrDefault(META_SYNC_BASE_FILE_FORMAT)).thenReturn("PARQUET");
+    
when(fixture.ddlExecutor.supportsUpdatingPartitionColumnComments()).thenReturn(true);
+    setField(fixture.syncClient, "config", fixture.config);
+    setField(fixture.syncClient, "databaseName", "test_db");
+    setField(fixture.syncClient, "client", fixture.metaStoreClient);
+    setField(fixture.syncClient, "ddlExecutor", fixture.ddlExecutor);
+    setField(fixture.syncClient, "initialTableByName", new HashMap<String, 
Table>());
+    return fixture;
+  }
+
+  private static void setField(Object target, String name, Object value) 
throws Exception {
+    Field field = HoodieHiveSyncClient.class.getDeclaredField(name);
+    field.setAccessible(true);
+    field.set(target, value);
+  }
+
+  private static class TestFixture {
+    private HoodieHiveSyncClient syncClient;
+    private HiveSyncConfig config;
+    private IMetaStoreClient metaStoreClient;
+    private DDLExecutor ddlExecutor;
+    private JDBCBasedMetadataOperator jdbcMetadataOperator;
+  }
+}
diff --git 
a/hudi-sync/hudi-hive-sync/src/test/java/org/apache/hudi/hive/ddl/TestHiveQueryDDLExecutorFailures.java
 
b/hudi-sync/hudi-hive-sync/src/test/java/org/apache/hudi/hive/ddl/TestHiveQueryDDLExecutorFailures.java
new file mode 100644
index 000000000000..c9d353ac7718
--- /dev/null
+++ 
b/hudi-sync/hudi-hive-sync/src/test/java/org/apache/hudi/hive/ddl/TestHiveQueryDDLExecutorFailures.java
@@ -0,0 +1,85 @@
+/*
+ * 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.hive.ddl;
+
+import org.apache.hudi.common.util.Option;
+import org.apache.hudi.hive.HiveSyncConfig;
+import org.apache.hudi.hive.HoodieHiveSyncException;
+import org.apache.hudi.sync.common.model.PartitionValueExtractor;
+
+import org.apache.hadoop.hive.metastore.IMetaStoreClient;
+import org.apache.thrift.TException;
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Field;
+import java.util.Collections;
+
+import static 
org.apache.hudi.sync.common.HoodieSyncConfig.META_SYNC_DATABASE_NAME;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.CALLS_REAL_METHODS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class TestHiveQueryDDLExecutorFailures {
+
+  @Test
+  void emptyAndMetastoreFailurePathsAreHandled() throws Exception {
+    HiveQueryDDLExecutor executor = mock(HiveQueryDDLExecutor.class, 
CALLS_REAL_METHODS);
+    IMetaStoreClient metaStoreClient = mock(IMetaStoreClient.class);
+    HiveSyncConfig config = mock(HiveSyncConfig.class);
+    PartitionValueExtractor partitionValueExtractor = 
mock(PartitionValueExtractor.class);
+    setField(executor, "driverPool", Option.empty());
+    setField(executor, "metaStoreClient", metaStoreClient);
+    setField(executor, "databaseName", "test_db");
+    setField(executor, "config", config);
+    setField(executor, "partitionValueExtractor", partitionValueExtractor);
+
+    assertDoesNotThrow(() -> executor.runSQLs(Collections.emptyList()));
+    assertDoesNotThrow(() -> executor.dropPartitionsToTable("table", 
Collections.emptyList()));
+
+    when(metaStoreClient.getTable(anyString(), anyString())).thenThrow(new 
TException("unavailable"));
+    
when(config.getStringOrDefault(META_SYNC_DATABASE_NAME)).thenReturn("test_db");
+    when(partitionValueExtractor.extractPartitionValuesInPath(anyString()))
+        .thenReturn(Collections.singletonList("2026-08-06"));
+    when(metaStoreClient.getPartition(anyString(), anyString(), anyList()))
+        .thenThrow(new TException("unavailable"));
+    assertThrows(HoodieHiveSyncException.class, () -> 
executor.getTableSchema("table"));
+    assertThrows(HoodieHiveSyncException.class,
+        () -> executor.dropPartitionsToTable("table", 
Collections.singletonList("datestr=2026-08-06")));
+  }
+
+  private static void setField(Object target, String name, Object value) 
throws Exception {
+    Class<?> type = target.getClass();
+    while (type != null) {
+      try {
+        Field field = type.getDeclaredField(name);
+        field.setAccessible(true);
+        field.set(target, value);
+        return;
+      } catch (NoSuchFieldException e) {
+        type = type.getSuperclass();
+      }
+    }
+    throw new NoSuchFieldException(name);
+  }
+}
diff --git 
a/hudi-sync/hudi-hive-sync/src/test/java/org/apache/hudi/hive/ddl/TestJDBCBasedMetadataOperator.java
 
b/hudi-sync/hudi-hive-sync/src/test/java/org/apache/hudi/hive/ddl/TestJDBCBasedMetadataOperator.java
index 65ca35a8610d..0c1e0620be1d 100644
--- 
a/hudi-sync/hudi-hive-sync/src/test/java/org/apache/hudi/hive/ddl/TestJDBCBasedMetadataOperator.java
+++ 
b/hudi-sync/hudi-hive-sync/src/test/java/org/apache/hudi/hive/ddl/TestJDBCBasedMetadataOperator.java
@@ -19,6 +19,7 @@
 package org.apache.hudi.hive.ddl;
 
 import org.apache.hudi.common.util.Option;
+import org.apache.hudi.hive.HoodieHiveSyncException;
 import org.apache.hudi.sync.common.model.FieldSchema;
 import org.apache.hudi.sync.common.model.Partition;
 
@@ -34,6 +35,7 @@ import java.util.Map;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.mock;
@@ -101,6 +103,19 @@ class TestJDBCBasedMetadataOperator {
     assertEquals("name", fields.get(1).getName());
   }
 
+  @Test
+  void testGetFieldSchemasHandlesMissingType() throws Exception {
+    when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet);
+    when(mockResultSet.next()).thenReturn(true, false);
+    when(mockResultSet.getString(1)).thenReturn("mystery");
+    when(mockResultSet.getString(2)).thenReturn(null);
+
+    List<FieldSchema> fields = operator.getFieldSchemas("my_table");
+
+    assertEquals(1, fields.size());
+    assertEquals("", fields.get(0).getType());
+  }
+
   @Test
   void testGetTableProperty() throws Exception {
     ResultSetMetaData metaData = mock(ResultSetMetaData.class);
@@ -165,6 +180,15 @@ class TestJDBCBasedMetadataOperator {
     assertEquals("s3a://bucket/warehouse/table", location);
   }
 
+  @Test
+  void testGetTableLocationFailsWhenDescribeHasNoLocation() throws Exception {
+    when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet);
+    when(mockResultSet.next()).thenReturn(true, false);
+    when(mockResultSet.getString(1)).thenReturn("# Detailed Table 
Information");
+
+    assertThrows(HoodieHiveSyncException.class, () -> 
operator.getTableLocation("my_table"));
+  }
+
   @Test
   void testDropTable() throws Exception {
     when(mockStatement.execute(anyString())).thenReturn(true);
diff --git 
a/hudi-sync/hudi-hive-sync/src/test/java/org/apache/hudi/hive/replication/TestHiveSyncGlobalCommitToolUnit.java
 
b/hudi-sync/hudi-hive-sync/src/test/java/org/apache/hudi/hive/replication/TestHiveSyncGlobalCommitToolUnit.java
new file mode 100644
index 000000000000..48ce525f1a3f
--- /dev/null
+++ 
b/hudi-sync/hudi-hive-sync/src/test/java/org/apache/hudi/hive/replication/TestHiveSyncGlobalCommitToolUnit.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.hive.replication;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.io.OutputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Properties;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.mockito.Mockito.CALLS_REAL_METHODS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class TestHiveSyncGlobalCommitToolUnit {
+
+  @TempDir
+  Path tempDir;
+
+  @Test
+  void commitDetectsDivergentReplicationStateAndClosesBothSides() throws 
Exception {
+    ReplicationStateSync local = mock(ReplicationStateSync.class);
+    ReplicationStateSync remote = mock(ReplicationStateSync.class);
+    when(local.getClusterId()).thenReturn("LOCALSYNC");
+    when(remote.getClusterId()).thenReturn("REMOTESYNC");
+    when(local.replicationStateIsInSync(remote)).thenReturn(false);
+
+    HiveSyncGlobalCommitTool tool = mock(HiveSyncGlobalCommitTool.class, 
CALLS_REAL_METHODS);
+    setField(tool, "replicationStateSyncList", Arrays.asList(local, remote));
+
+    assertFalse(tool.commit());
+    tool.close();
+
+    verify(local).sync();
+    verify(remote).sync();
+    verify(local).close();
+    verify(remote).close();
+  }
+
+  @Test
+  void loadParamsReadsXmlConfiguration() throws Exception {
+    Path configFile = tempDir.resolve("global-sync.xml");
+    Properties properties = new Properties();
+    properties.setProperty("marker", "loaded");
+    try (OutputStream output = Files.newOutputStream(configFile)) {
+      properties.storeToXML(output, "test");
+    }
+
+    Method loadParams = 
HiveSyncGlobalCommitTool.class.getDeclaredMethod("loadParams", String[].class);
+    loadParams.setAccessible(true);
+    HiveSyncGlobalCommitParams params = (HiveSyncGlobalCommitParams) 
loadParams.invoke(null, (Object) new String[] {
+        "--config-xml-file", configFile.toString(), "--replicated-timestamp", 
"100",
+        "--base-path", tempDir.resolve("table").toString()
+    });
+
+    assertEquals("loaded", params.loadedProps.getProperty("marker"));
+    assertEquals("100", 
params.globalHiveSyncConfigParams.globallyReplicatedTimeStamp);
+  }
+
+  private static void setField(Object target, String name, Object value) 
throws Exception {
+    Field field = HiveSyncGlobalCommitTool.class.getDeclaredField(name);
+    field.setAccessible(true);
+    field.set(target, value);
+  }
+}
diff --git 
a/hudi-sync/hudi-hive-sync/src/test/java/org/apache/hudi/hive/transaction/lock/TestHiveMetastoreBasedLockProviderConfiguration.java
 
b/hudi-sync/hudi-hive-sync/src/test/java/org/apache/hudi/hive/transaction/lock/TestHiveMetastoreBasedLockProviderConfiguration.java
new file mode 100644
index 000000000000..5abf1acf81fb
--- /dev/null
+++ 
b/hudi-sync/hudi-hive-sync/src/test/java/org/apache/hudi/hive/transaction/lock/TestHiveMetastoreBasedLockProviderConfiguration.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.hive.transaction.lock;
+
+import org.apache.hadoop.hive.conf.HiveConf;
+import org.apache.hadoop.hive.metastore.IMetaStoreClient;
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Method;
+
+import static 
org.apache.hudi.common.config.LockConfiguration.HIVE_METASTORE_URI_PROP_KEY;
+import static 
org.apache.hudi.common.config.LockConfiguration.LOCK_ACQUIRE_NUM_RETRIES_PROP_KEY;
+import static 
org.apache.hudi.common.config.LockConfiguration.LOCK_ACQUIRE_RETRY_WAIT_TIME_IN_MILLIS_PROP_KEY;
+import static 
org.apache.hudi.common.config.LockConfiguration.ZK_CONNECT_URL_PROP_KEY;
+import static org.apache.hudi.common.config.LockConfiguration.ZK_PORT_PROP_KEY;
+import static 
org.apache.hudi.common.config.LockConfiguration.ZK_SESSION_TIMEOUT_MS_PROP_KEY;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+
+class TestHiveMetastoreBasedLockProviderConfiguration extends 
HiveMetastoreBasedLockProviderTestBase {
+
+  @Test
+  void appliesOptionalMetastoreAndZooKeeperSettings() throws Exception {
+    lockConfiguration.getConfig().setProperty(HIVE_METASTORE_URI_PROP_KEY, 
"thrift://localhost:9083");
+    
lockConfiguration.getConfig().setProperty(LOCK_ACQUIRE_NUM_RETRIES_PROP_KEY, 
"4");
+    
lockConfiguration.getConfig().setProperty(LOCK_ACQUIRE_RETRY_WAIT_TIME_IN_MILLIS_PROP_KEY,
 "25");
+    lockConfiguration.getConfig().setProperty(ZK_CONNECT_URL_PROP_KEY, 
"zk-a,zk-b");
+    lockConfiguration.getConfig().setProperty(ZK_PORT_PROP_KEY, "2182");
+    lockConfiguration.getConfig().setProperty(ZK_SESSION_TIMEOUT_MS_PROP_KEY, 
"45000");
+
+    HiveMetastoreBasedLockProvider provider = new 
HiveMetastoreBasedLockProvider(
+        lockConfiguration, mock(IMetaStoreClient.class));
+    try {
+      HiveConf hiveConf = new HiveConf();
+      Method method = 
HiveMetastoreBasedLockProvider.class.getDeclaredMethod("setHiveLockConfs", 
HiveConf.class);
+      method.setAccessible(true);
+      method.invoke(provider, hiveConf);
+
+      assertEquals("thrift://localhost:9083", 
hiveConf.getVar(HiveConf.ConfVars.METASTOREURIS));
+      assertEquals("zk-a,zk-b", hiveConf.get("hive.zookeeper.quorum"));
+      assertEquals("2182", hiveConf.get("hive.zookeeper.client.port"));
+      assertEquals("45000", hiveConf.get("hive.zookeeper.session.timeout"));
+      assertEquals("4", hiveConf.get("hive.lock.numretries"));
+      assertEquals("25", hiveConf.get("hive.lock.sleep.between.retries"));
+      assertTrue(hiveConf.getBoolean("hive.support.concurrency", false));
+    } finally {
+      provider.close();
+    }
+  }
+}
diff --git 
a/hudi-sync/hudi-sync-common/src/test/java/org/apache/hudi/sync/common/TestHoodieMetaSyncOperations.java
 
b/hudi-sync/hudi-sync-common/src/test/java/org/apache/hudi/sync/common/TestHoodieMetaSyncOperations.java
new file mode 100644
index 000000000000..2d5bb10e17df
--- /dev/null
+++ 
b/hudi-sync/hudi-sync-common/src/test/java/org/apache/hudi/sync/common/TestHoodieMetaSyncOperations.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.sync.common;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class TestHoodieMetaSyncOperations {
+
+  private final HoodieMetaSyncOperations operations = new 
HoodieMetaSyncOperations() { };
+
+  @Test
+  void defaultOperationsRemainSafeForMinimalImplementations() {
+    assertDoesNotThrow(() -> operations.createTable("table", null, null, null, 
null,
+        Collections.emptyMap(), Collections.emptyMap()));
+    assertDoesNotThrow(() -> operations.createOrReplaceTable("table", null, 
null, null, null,
+        Collections.emptyMap(), Collections.emptyMap()));
+    assertFalse(operations.tableExists("table"));
+    assertDoesNotThrow(() -> operations.dropTable("table"));
+    assertDoesNotThrow(() -> operations.addPartitionsToTable("table", 
Collections.emptyList()));
+    assertDoesNotThrow(() -> operations.updatePartitionsToTable("table", 
Collections.emptyList()));
+    assertDoesNotThrow(() -> operations.touchPartitionsToTable("table", 
Collections.emptyList()));
+    assertDoesNotThrow(() -> operations.dropPartitions("table", 
Collections.emptyList()));
+    assertTrue(operations.getAllPartitions("table").isEmpty());
+    assertTrue(operations.getPartitionsFromList("table", 
Collections.emptyList()).isEmpty());
+    assertFalse(operations.databaseExists("database"));
+    assertDoesNotThrow(() -> operations.createDatabase("database"));
+    assertTrue(operations.getMetastoreSchema("table").isEmpty());
+    assertNull(operations.getStorageSchema());
+    assertNull(operations.getStorageSchema(true));
+    assertDoesNotThrow(() -> operations.updateTableSchema("table", null, 
null));
+    assertTrue(operations.getMetastoreFieldSchemas("table").isEmpty());
+    assertTrue(operations.getStorageFieldSchemas().isEmpty());
+    assertTrue(operations.getTableLocation("table").isEmpty());
+    assertFalse(operations.updateTableComments("table", 
Collections.emptyList(), Collections.emptyList()));
+    assertFalse(operations.getLastCommitTimeSynced("table").isPresent());
+    
assertFalse(operations.getLastCommitCompletionTimeSynced("table").isPresent());
+    assertDoesNotThrow(() -> operations.updateLastCommitTimeSynced("table"));
+    assertFalse(operations.updateTableProperties("table", 
Collections.emptyMap()));
+    assertFalse(operations.updateSerdeProperties("table", 
Collections.emptyMap(), false));
+    assertFalse(operations.getLastReplicatedTime("table").isPresent());
+    assertDoesNotThrow(() -> operations.updateLastReplicatedTimeStamp("table", 
"001"));
+    assertDoesNotThrow(() -> 
operations.deleteLastReplicatedTimeStamp("table"));
+    assertThrows(UnsupportedOperationException.class,
+        () -> operations.generatePushDownFilter(Collections.emptyList(), 
Collections.emptyList()));
+    assertThrows(UnsupportedOperationException.class, () -> 
operations.updateHoodieWriterVersion("table"));
+  }
+}

Reply via email to