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 8c44ce5a8559 test(spark): add HiveSyncProcedure coverage (#19407)
8c44ce5a8559 is described below

commit 8c44ce5a85597e7137a6c676fe706b9a62a307bb
Author: Y Ethan Guo <[email protected]>
AuthorDate: Fri Jul 31 04:32:47 2026 -0700

    test(spark): add HiveSyncProcedure coverage (#19407)
    
    * test(spark): add HiveSyncProcedure coverage
    
    Add TestHiveSyncProcedure, which invokes the hive_sync stored procedure 
with every optional argument set and an unresolvable partition extractor, 
covering the argument handling, config assembly, and failure wrapping in the 
procedure's call method. It pins that the failure is surfaced as a 
HoodieException carrying the "hive sync failed" message and originates from 
loading the supplied extractor.
    
    * Assert the exception type and written conf in the hive_sync test
    
    Use assume() so the test reports as skipped, not passed, on Java 17. Assert 
a HoodieException appears in the cause chain rather than matching the message 
alone. Assert every session and hadoop conf value the procedure writes before 
the finally clears them, so dropping any setConfString call is caught. Switch 
the manual try/catch to intercept.
---
 .../sql/hudi/procedure/TestHiveSyncProcedure.scala | 113 +++++++++++++++++++++
 1 file changed, 113 insertions(+)

diff --git 
a/hudi-spark-datasource/hudi-spark/src/test/scala/org/apache/spark/sql/hudi/procedure/TestHiveSyncProcedure.scala
 
b/hudi-spark-datasource/hudi-spark/src/test/scala/org/apache/spark/sql/hudi/procedure/TestHiveSyncProcedure.scala
new file mode 100644
index 000000000000..26719e78b141
--- /dev/null
+++ 
b/hudi-spark-datasource/hudi-spark/src/test/scala/org/apache/spark/sql/hudi/procedure/TestHiveSyncProcedure.scala
@@ -0,0 +1,113 @@
+/*
+ * 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.procedure
+
+import org.apache.hudi.common.testutils.HoodieTestUtils
+import org.apache.hudi.exception.HoodieException
+import org.apache.hudi.hive.{HiveSyncConfig, HiveSyncConfigHolder}
+import org.apache.hudi.sync.common.HoodieSyncConfig
+
+import org.apache.hadoop.hive.conf.HiveConf
+import org.junit.jupiter.api.Assertions.{assertEquals, assertTrue}
+
+class TestHiveSyncProcedure extends HoodieSparkProcedureTestBase {
+
+  // Session-conf keys the procedure writes from its optional arguments; reset 
after the call so
+  // they do not leak into sibling tests sharing the session.
+  private val sessionSyncConfKeys = Seq(
+    HiveSyncConfig.HIVE_USER.key, HiveSyncConfig.HIVE_PASS.key, 
HiveSyncConfig.HIVE_USE_JDBC.key,
+    HiveSyncConfigHolder.HIVE_SYNC_MODE.key, 
HoodieSyncConfig.META_SYNC_PARTITION_FIELDS.key,
+    HoodieSyncConfig.META_SYNC_PARTITION_EXTRACTOR_CLASS.key,
+    HiveSyncConfigHolder.HIVE_SYNC_TABLE_STRATEGY.key, 
HoodieSyncConfig.META_SYNC_INCREMENTAL.key)
+
+  private def createHudiTable(tableName: String, basePath: String): Unit = {
+    spark.sql(
+      s"""
+         |create table $tableName (
+         |  id int,
+         |  name string,
+         |  price double,
+         |  ts long
+         |) using hudi
+         | options (
+         |  primaryKey = 'id',
+         |  type = 'cow',
+         |  preCombineField = 'ts'
+         | )
+         | location '$basePath'
+       """.stripMargin)
+    spark.sql(s"insert into $tableName values(1, 'a1', 10.0, 1000)")
+  }
+
+  test("Test call hive_sync procedure surfaces a sync failure as 
HoodieException") {
+    // The procedure constructs a HiveConf; Hive 2.3.7 is compiled with Java 
1.8 and its class loader
+    // throws when the Hive APIs are exercised on Java 17. Cancel (report as 
skipped, not passed) there.
+    assume(HoodieTestUtils.getJavaVersion < 17)
+    withTempDir { tmp =>
+      val tableName = generateTableName
+      val basePath = s"${tmp.getCanonicalPath}/$tableName"
+      createHudiTable(tableName, basePath)
+
+      try {
+        // Every optional argument is supplied so each argument branch of the 
procedure runs. The
+        // unresolvable partition_extractor_class makes the HiveSyncTool 
constructor throw while
+        // loading the extractor (in the HoodieSyncClient constructor, before 
any metastore
+        // connection is attempted).
+        val thrown = intercept[Throwable] {
+          spark.sql(
+            s"call hive_sync(table => '$tableName', metastore_uri => 
'thrift://localhost:9083'," +
+              s" username => 'hive', password => 'hive', use_jdbc => 'false', 
mode => 'hms'," +
+              s" partition_fields => 'name', strategy => 'ALL', 
sync_incremental => 'false'," +
+              s" partition_extractor_class => 
'org.apache.hudi.hive.MissingPartitionExtractor')")
+        }
+
+        // The procedure must wrap the failure as HoodieException("hive sync 
failed"), and the cause
+        // must be the unresolvable extractor supplied as an argument. 
Asserting the exception type,
+        // the wrapping message, and the cause together pins the wrapping 
contract and that the
+        // supplied extractor is applied and fails before the metastore.
+        val causes = Iterator.iterate(thrown)(_.getCause).takeWhile(_ != 
null).toList
+        assertTrue(causes.exists(_.isInstanceOf[HoodieException]),
+          "expected a HoodieException in the cause chain, but got: " +
+            causes.map(_.getClass.getName).mkString(" -> "))
+        val chain = causes.flatMap(t => Option(t.getMessage)).mkString(" | ")
+        assertTrue(chain.contains("hive sync failed"),
+          s"expected the procedure to wrap the failure as HoodieException, but 
got: $chain")
+        
assertTrue(chain.contains("org.apache.hudi.hive.MissingPartitionExtractor"),
+          s"expected the failure to originate from loading the supplied 
extractor, but got: $chain")
+
+        // Each supplied optional argument must be applied to the 
session/hadoop conf before the sync
+        // is attempted; asserting the written values catches dropping any of 
the setConfString calls.
+        val sqlConf = spark.sessionState.conf
+        assertEquals("hive", 
sqlConf.getConfString(HiveSyncConfig.HIVE_USER.key))
+        assertEquals("hive", 
sqlConf.getConfString(HiveSyncConfig.HIVE_PASS.key))
+        assertEquals("false", 
sqlConf.getConfString(HiveSyncConfig.HIVE_USE_JDBC.key))
+        assertEquals("hms", 
sqlConf.getConfString(HiveSyncConfigHolder.HIVE_SYNC_MODE.key))
+        assertEquals("name", 
sqlConf.getConfString(HoodieSyncConfig.META_SYNC_PARTITION_FIELDS.key))
+        assertEquals("org.apache.hudi.hive.MissingPartitionExtractor",
+          
sqlConf.getConfString(HoodieSyncConfig.META_SYNC_PARTITION_EXTRACTOR_CLASS.key))
+        assertEquals("ALL", 
sqlConf.getConfString(HiveSyncConfigHolder.HIVE_SYNC_TABLE_STRATEGY.key))
+        assertEquals("false", 
sqlConf.getConfString(HoodieSyncConfig.META_SYNC_INCREMENTAL.key))
+        assertEquals("thrift://localhost:9083",
+          
spark.sparkContext.hadoopConfiguration.get(HiveConf.ConfVars.METASTOREURIS.varname))
+      } finally {
+        
spark.sparkContext.hadoopConfiguration.unset(HiveConf.ConfVars.METASTOREURIS.varname)
+        sessionSyncConfKeys.foreach(spark.sessionState.conf.unsetConf)
+      }
+    }
+  }
+}

Reply via email to