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

gavinchou pushed a commit to branch branch-3.0
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-3.0 by this push:
     new 414f8a94581 [regression-test](framework) add multi cluster result 
compare on cloud mode. (#49735)
414f8a94581 is described below

commit 414f8a9458186cfdc81126fe892e1891111a5362
Author: shuke <sh...@selectdb.com>
AuthorDate: Wed Apr 9 16:14:30 2025 +0800

    [regression-test](framework) add multi cluster result compare on cloud 
mode. (#49735)
---
 .../org/apache/doris/regression/Config.groovy      |  10 ++
 .../apache/doris/regression/ConfigOptions.groovy   |  10 ++
 .../plugins/plugin_multi_cluster.groovy            | 148 +++++++++++++++++++++
 .../check_before_quit/check_before_quit.groovy     |   3 +
 4 files changed, 171 insertions(+)

diff --git 
a/regression-test/framework/src/main/groovy/org/apache/doris/regression/Config.groovy
 
b/regression-test/framework/src/main/groovy/org/apache/doris/regression/Config.groovy
index f51e372a20d..b57976c0578 100644
--- 
a/regression-test/framework/src/main/groovy/org/apache/doris/regression/Config.groovy
+++ 
b/regression-test/framework/src/main/groovy/org/apache/doris/regression/Config.groovy
@@ -93,6 +93,7 @@ class Config {
     public String excludeSuites
     public String testDirectories
     public String excludeDirectories
+    public String clustersToCompareResults
     public boolean generateOutputFile
     public boolean forceGenerateOutputFile
     public boolean randomOrder
@@ -182,6 +183,7 @@ class Config {
             String excludeGroups,
             String testSuites, 
             String excludeSuites,
+            String clustersToCompareResults,
             String testDirectories,
             String excludeDirectories, 
             String pluginPath,
@@ -235,6 +237,7 @@ class Config {
         this.excludeGroups = excludeGroups
         this.testSuites = testSuites
         this.excludeSuites = excludeSuites
+        this.clustersToCompareResults = clustersToCompareResults
         this.testDirectories = testDirectories
         this.excludeDirectories = excludeDirectories
         this.pluginPath = pluginPath
@@ -347,6 +350,7 @@ class Config {
                 .collect({d -> d.trim()})
                 .findAll({d -> d != null && d.length() > 0})
                 .toSet()
+        config.clustersToCompareResults = 
cmd.getOptionValue(clustersToCompareResultsOpt, config.clustersToCompareResults)
 
         if (!config.suiteWildcard && !config.groups && !config.directories && 
!config.excludeSuiteWildcard
             && !config.excludeGroupSet && !config.excludeDirectorySet) {
@@ -560,6 +564,7 @@ class Config {
             configToString(obj.excludeGroups),
             configToString(obj.testSuites),
             configToString(obj.excludeSuites),
+            configToString(obj.clustersToCompareResults),
             configToString(obj.testDirectories),
             configToString(obj.excludeDirectories),
             configToString(obj.pluginPath),
@@ -905,6 +910,11 @@ class Config {
             log.info("Set excludeSuites to empty because not 
specify.".toString())
         }
 
+        if (config.clustersToCompareResults == null) {
+            config.clustersToCompareResults = ""
+            log.info("Not compare multi-cluster results after regression 
test".toString())
+        }
+
         if (config.parallel == null) {
             config.parallel = 1
             log.info("Set parallel to 1 because not specify.".toString())
diff --git 
a/regression-test/framework/src/main/groovy/org/apache/doris/regression/ConfigOptions.groovy
 
b/regression-test/framework/src/main/groovy/org/apache/doris/regression/ConfigOptions.groovy
index a648eb40a3e..823b38daeac 100644
--- 
a/regression-test/framework/src/main/groovy/org/apache/doris/regression/ConfigOptions.groovy
+++ 
b/regression-test/framework/src/main/groovy/org/apache/doris/regression/ConfigOptions.groovy
@@ -59,6 +59,7 @@ class ConfigOptions {
     static Option runModeOpt
     static Option suiteOpt
     static Option excludeSuiteOpt
+    static Option clustersToCompareResultsOpt
     static Option groupsOpt
     static Option excludeGroupsOpt
     static Option directoriesOpt
@@ -245,6 +246,15 @@ class ConfigOptions {
                 .longOpt("excludeSuite")
                 .desc("the suite name wildcard will not be tested")
                 .build()
+        clustersToCompareResultsOpt = 
Option.builder("clustersToCompareResults")
+                .argName("clustersToCompareResults")
+                .required(false)
+                .hasArg(true)
+                .optionalArg(true)
+                .type(String.class)
+                .longOpt("clustersToCompareResults")
+                .desc("compare cluster data after regression")
+                .build()
         groupsOpt = Option.builder("g")
                 .argName("groups")
                 .required(false)
diff --git a/regression-test/plugins/plugin_multi_cluster.groovy 
b/regression-test/plugins/plugin_multi_cluster.groovy
new file mode 100644
index 00000000000..be6ac7d29e4
--- /dev/null
+++ b/regression-test/plugins/plugin_multi_cluster.groovy
@@ -0,0 +1,148 @@
+// 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.
+
+import org.apache.doris.regression.suite.Suite
+
+/*
+ * After running all regression-tests, check all db and tables
+ * have same data in multi cluster deploy on cloud.
+ */
+Suite.metaClass.check_multi_cluster_result = { clusterNames ->
+    def ignoreDBs = ["mysql", "__internal_schema", "information_schema"]
+    // TODO: add skip reason.
+    def specialTables = [
+        "regression_test_function_p0" : ["mock_view": CompareSQLPattern.Skip,],
+        "regression_test_nereids_syntax_p0" : ["test_show_keys_v" : 
CompareSQLPattern.Skip,],
+        "regression_test_datatype_p0_nested_types_base_cases" : 
["test_map_one_level": CompareSQLPattern.Skip,],
+        "regression_test_inverted_index_p0_array_contains": 
["httplogs_dup_arr": CompareSQLPattern.OrderByGroovy,],
+        "nested_array_test_2_vectorized": ["nested_array_test_2_vectorized": 
CompareSQLPattern.OrderByGroovy],
+        
"regression_test_nereids_p0_sql_functions_bitmap_functions":["v1":CompareSQLPattern.OrderByGroovy],
+        "regression_test_partition_p0_auto_partition": ["table_date_list" : 
CompareSQLPattern.OrderByGroovy],
+        "regression_test_query_p0_aggregate": ["test_array_agg_complex": 
CompareSQLPattern.Skip],
+        "regression_test_query_p0_set_operations": 
["nested_array_test_2_vectorized" : CompareSQLPattern.Skip],
+        "regression_test_variant_p0": ["multi_variants" : 
CompareSQLPattern.Skip],
+        "regression_test_variant_p0_rqg": 
["table_100_undef_partitions2_keys3_properties4_distributed_by53", 
CompareSQLPattern.Skip],
+        "regression_test_variant_p0_with_index": 
["test_variant_index_parser_empty" : CompareSQLPattern.Skip],
+    ]
+    enum CompareSQLPattern { Skip, OrderByGroovy, OrderBySQL }
+    
+    /*
+     * return true if column type is a complex type.
+     * type maybe like Map<int, String>, etc.
+     */
+    def isComplexColumn = {String type ->
+        type = type.toLowerCase()
+        def ComplexColumns = ["map", "struct", "hll", "json", "array", 
"bitmap", "variant", "agg_state", "quantile_state"]
+        for (int i = 0; i < noOrderByColumns.size(); i++) {
+            if (type.contains(noOrderByColumns[i])) {
+                return false
+            }
+        }
+        return true
+    }
+    
+    /*
+     * There are 2 compare methods:
+     *  1. OrderByGroovy: retrieve all result from table and sort in groovy. 
like: sql 'select * from table', true
+     *  2. OrderBySQL: retrieve sorted limited result from db, like: sql 
'select * from table order by 1,2,3,4,5 limit 100'
+     */
+    def getOrderByType = { db, table ->
+        def p = specialTables[db]?[t]
+        if (p == null) {
+            return CompareSQLPattern.OrderBySQL
+        } else {
+            return p
+        }
+    }
+
+    def getSQL = { db, tableName ->
+        def sqlStmt = "select * from ${tableName} order by "
+        def columnTypes = sql_return_maparray """ show columns from 
${tableName} """
+        def firstOrderby = true
+        for (int i = 0; i < columnTypes.size(); i++) {
+            def name = columnTypes[i]["Field"]
+            def type = columnTypes[i]["Type"]
+            if (isComplexColumn(type)) {
+                continue // can not add order by.
+            } else {
+                if (firstOrderby) {
+                    firstOrderby = false
+                    sqlStmt += "${i+1}"
+                } else {
+                    sqlStmt += ",${i+1}"
+                }
+            }
+        }
+
+        if (firstOrderby) {
+            throw new IllegalArgumentException("""db: ${db}, table: 
${tableName} is not suitable for orderBySQL """)
+        }
+        return sqlStmt + " limit 100"
+    }
+
+    def assertSameResult = { sqlStmt, order ->
+        def result = []
+
+        clusters.forEach { cluster ->
+            sql """ use @${cluster} """
+            result.add(sql """ ${q} """, order )
+        }
+
+        if (result.toSet().size() != 1) {
+            throw new IllegalArgumentException(""" different result, db: 
${db}, table: ${t}. \n${result}  """)
+        }
+    }
+
+    // main logic
+    def clusters = clusterNames.tokenize(",")
+    if (clusters.size() <= 1) {
+        return
+    }
+
+    int nErrors = 0
+    def dbs = sql """ show databases """
+    dbs.forEach { db ->
+        db = db[0]
+        if (ignoreDBs.contains(db)) {
+            return
+        }
+        sql """ use ${db} """
+        def tables = sql """ show tables """
+        tables.forEach { t -> 
+            try {
+                t = t[0]
+                logger.info("process db: ${db}, table: ${t}")
+                
+                def p = getOrderByType(db, t)
+                if (p == CompareSQLPattern.OrderBySQL) {
+                    assertSameResult(getSQL(db, t), false)
+                } else if (p == CompareSQLPattern.Skip) {
+                    return
+                } else if (p == CompareSQLPattern.OrderByGroovy) {
+                    assertSameResult("select * from ${t}", true)
+                }
+            } catch (Exception e) {
+                nErrors++
+                logger.info(""" asssertSameResult failed: """, e)
+            }
+        }
+    }
+
+    if (nErrors > 0) {
+        throw new Exception(""" There are ${nErrors} met in assertSameResult 
""")
+    }
+}
diff --git a/regression-test/suites/check_before_quit/check_before_quit.groovy 
b/regression-test/suites/check_before_quit/check_before_quit.groovy
index d1675497e4b..f067c22467a 100644
--- a/regression-test/suites/check_before_quit/check_before_quit.groovy
+++ b/regression-test/suites/check_before_quit/check_before_quit.groovy
@@ -349,4 +349,7 @@ suite("check_before_quit", "nonConcurrent,p0") {
     }
 
     assertTrue(clear)
+
+    // check multi-cluster have same data.
+    check_multi_cluster_result(context.config.clustersToCompareResults)
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org
For additional commands, e-mail: commits-h...@doris.apache.org

Reply via email to