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