From e6c3557b1b075fb18f452eb2e35e40b2ce746320 Mon Sep 17 00:00:00 2001 From: 924060929 <924060929@qq.com> Date: Thu, 5 May 2022 20:45:45 +0800 Subject: [PATCH] =?UTF-8?q?[improvement](regression-test)=20Support=20suit?= =?UTF-8?q?e=20plugin=20to=20add=20third-part=E2=80=A6=20(#9294)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Support register suite plugin to add third-party function. See 1. register in: ${DORIS_HOME}/regression-test/plugins/plugin_example.groovy 2. usage: ${DORIS_HOME}/regression-test/suites/demo/test_plugin.groovy 3. doc: ${DORIS_HOME}/docs/zh-CN/developer-guide/regression-testing.md --- .../developer-guide/regression-testing.md | 34 +++++++++++++++++++ regression-test/conf/regression-conf.groovy | 1 + .../org/apache/doris/regression/Config.groovy | 16 +++++++-- .../doris/regression/ConfigOptions.groovy | 10 ++++++ .../doris/regression/RegressionTest.groovy | 29 ++++++++++++++++ regression-test/plugins/plugin_example.groovy | 34 +++++++++++++++++++ .../suites/demo/test_plugin.groovy | 22 ++++++++++++ 7 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 regression-test/plugins/plugin_example.groovy create mode 100644 regression-test/suites/demo/test_plugin.groovy diff --git a/docs/zh-CN/developer-guide/regression-testing.md b/docs/zh-CN/developer-guide/regression-testing.md index 7d2db8d59c..c994d5eee1 100644 --- a/docs/zh-CN/developer-guide/regression-testing.md +++ b/docs/zh-CN/developer-guide/regression-testing.md @@ -50,6 +50,7 @@ under the License. ./${DORIS_HOME} |-- **run-regression-test.sh** 回归测试启动脚本 |-- regression-test + | |-- plugins 插件目录 | |-- conf | | |-- logback.xml 日志配置文件 | | |-- **regression-conf.groovy** 默认配置文件 @@ -100,6 +101,8 @@ feHttpPassword = "" suitePath = "${DORIS_HOME}/regression-test/suites" // 设置输入输出数据的目录 dataPath = "${DORIS_HOME}/regression-test/data" +// 设置插件的目录 +pluginPath = "${DORIS_HOME}/regression-test/plugins" // 默认会读所有的组,读多个组可以用半角逗号隔开,如: "demo,performance" // 一般不需要在配置文件中修改,而是通过run-regression-test.sh --run -g来动态指定和覆盖 @@ -549,6 +552,37 @@ thread, lazyCheck, events, connect, selectUnionAll ./run-regression-test.sh --run sql_action -forceGenOut ``` +## Suite插件 +有的时候我们需要拓展Suite类,但不便于修改Suite类的源码,则可以通过插件来进行拓展。默认插件目录为`${DORIS_HOME}/regression-test/plugins`,在其中可以通过groovy脚本定义拓展方法,以`plugin_example.groovy`为例,为Suite类增加了testPlugin函数用于打印日志: +```groovy +import org.apache.doris.regression.suite.Suite + +// register `testPlugin` function to Suite, +// and invoke in ${DORIS_HOME}/regression-test/suites/demo/test_plugin.groovy +Suite.metaClass.testPlugin = { String info /* param */ -> + + // which suite invoke current function? + Suite suite = delegate as Suite + + // function body + suite.getLogger().info("Test plugin: suiteName: ${suite.name}, info: ${info}".toString()) + + // optional return value + return "OK" +} + +logger.info("Added 'testPlugin' function to Suite") +``` + +增加了testPlugin函数后,则可以在普通用例中使用它,以`${DORIS_HOME}/regression-test/suites/demo/test_plugin.groovy`为例: +```groovy +suite("test_plugin", "demo") { + // register testPlugin function in ${DORIS_HOME}/regression-test/plugins/plugin_example.groovy + def result = testPlugin("message from suite") + assertEquals("OK", result) +} +``` + ## CI/CD的支持 ### TeamCity TeamCity可以通过stdout识别Service Message。当使用`--teamcity`参数启动回归测试框架时,回归测试框架就会在stdout打印TeamCity Service Message,TeamCity将会自动读取stdout中的事件日志,并在当前流水线中展示`Tests`,其中会展示测试的test及其日志。 diff --git a/regression-test/conf/regression-conf.groovy b/regression-test/conf/regression-conf.groovy index ec5b8a21bf..934aab251a 100644 --- a/regression-test/conf/regression-conf.groovy +++ b/regression-test/conf/regression-conf.groovy @@ -32,6 +32,7 @@ feHttpPassword = "" // e.g. java -DDORIS_HOME=./ suitePath = "${DORIS_HOME}/regression-test/suites" dataPath = "${DORIS_HOME}/regression-test/data" +pluginPath = "${DORIS_HOME}/regression-test/plugins" // will test /.groovy // empty group will test all group 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 cf2bc0a0b8..675ca676fb 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 @@ -45,6 +45,7 @@ class Config { public String suitePath public String dataPath + public String pluginPath public String testGroups public String excludeGroups @@ -78,7 +79,8 @@ class Config { Config(String defaultDb, String jdbcUrl, String jdbcUser, String jdbcPassword, String feHttpAddress, String feHttpUser, String feHttpPassword, String suitePath, String dataPath, String testGroups, String excludeGroups, - String testSuites, String excludeSuites, String testDirectories, String excludeDirectories) { + String testSuites, String excludeSuites, String testDirectories, String excludeDirectories, + String pluginPath) { this.defaultDb = defaultDb this.jdbcUrl = jdbcUrl this.jdbcUser = jdbcUser @@ -94,6 +96,7 @@ class Config { this.excludeSuites = excludeSuites this.testDirectories = testDirectories this.excludeDirectories = excludeDirectories + this.pluginPath = pluginPath } static Config fromCommandLine(CommandLine cmd) { @@ -113,6 +116,7 @@ class Config { config.suitePath = FileUtils.getCanonicalPath(cmd.getOptionValue(pathOpt, config.suitePath)) config.dataPath = FileUtils.getCanonicalPath(cmd.getOptionValue(dataOpt, config.dataPath)) + config.pluginPath = FileUtils.getCanonicalPath(cmd.getOptionValue(pluginOpt, config.pluginPath)) config.suiteWildcard = cmd.getOptionValue(suiteOpt, config.testSuites) .split(",") .collect({s -> s.trim()}) @@ -193,7 +197,8 @@ class Config { configToString(obj.testSuites), configToString(obj.excludeSuites), configToString(obj.testDirectories), - configToString(obj.excludeDirectories) + configToString(obj.excludeDirectories), + configToString(obj.pluginPath) ) def declareFileNames = config.getClass() @@ -255,6 +260,11 @@ class Config { log.info("Set dataPath to '${config.dataPath}' because not specify.".toString()) } + if (config.pluginPath == null) { + config.pluginPath = "regression-test/plugins" + log.info("Set dataPath to '${config.pluginPath}' because not specify.".toString()) + } + if (config.testGroups == null) { config.testGroups = "default" log.info("Set testGroups to '${config.testGroups}' because not specify.".toString()) @@ -379,4 +389,4 @@ class Config { // check connection with default db getConnection().close() } -} \ No newline at end of file +} 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 e860f3abe7..3a25cfafa9 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 @@ -37,6 +37,7 @@ class ConfigOptions { static Option feHttpPasswordOpt static Option pathOpt static Option dataOpt + static Option pluginOpt static Option suiteOpt static Option excludeSuiteOpt static Option groupsOpt @@ -117,6 +118,14 @@ class ConfigOptions { .longOpt("dataPath") .desc("the data path") .build() + pluginOpt = Option.builder("plugin") + .argName("pluginPath") + .required(false) + .hasArg(true) + .type(String.class) + .longOpt("plugin") + .desc("the plugin path") + .build() suiteOpt = Option.builder("s") .argName("suiteName") .required(false) @@ -267,6 +276,7 @@ class ConfigOptions { .addOption(passwordOpt) .addOption(pathOpt) .addOption(dataOpt) + .addOption(pluginOpt) .addOption(confOpt) .addOption(suiteOpt) .addOption(excludeSuiteOpt) diff --git a/regression-test/framework/src/main/groovy/org/apache/doris/regression/RegressionTest.groovy b/regression-test/framework/src/main/groovy/org/apache/doris/regression/RegressionTest.groovy index 2456cdb3c0..a367ffb4e9 100644 --- a/regression-test/framework/src/main/groovy/org/apache/doris/regression/RegressionTest.groovy +++ b/regression-test/framework/src/main/groovy/org/apache/doris/regression/RegressionTest.groovy @@ -85,6 +85,8 @@ class RegressionTest { scriptExecutors = Executors.newFixedThreadPool(config.parallel) suiteExecutors = Executors.newFixedThreadPool(config.suiteParallel) actionExecutors = Executors.newFixedThreadPool(config.actionParallel) + + loadPlugins(config) } static List findScriptSources(String root, Predicate directoryFilter, @@ -272,6 +274,33 @@ class RegressionTest { } } + static void loadPlugins(Config config) { + if (config.pluginPath.is(null) || config.pluginPath.isEmpty()) { + return + } + def pluginPath = new File(config.pluginPath) + if (!pluginPath.exists() || !pluginPath.isDirectory()) { + return + } + pluginPath.eachFileRecurse { it -> + if (it.name.endsWith(".groovy")) { + ScriptContext context = new ScriptContext(it, suiteExecutors, actionExecutors, + config, [], { name -> true }) + File pluginFile = it + context.start { + try { + SuiteScript pluginScript = new GroovyFileSource(pluginFile).toScript(context, shell) + log.info("Begin to load plugin: ${pluginFile.getCanonicalPath()}") + pluginScript.run() + log.info("Loaded plugin: ${pluginFile.getCanonicalPath()}") + } catch (Throwable t) { + log.error("Load plugin failed: ${pluginFile.getCanonicalPath()}", t) + } + } + } + } + } + static void printPassed() { log.info('''All suites success. | ____ _ ____ ____ _____ ____ diff --git a/regression-test/plugins/plugin_example.groovy b/regression-test/plugins/plugin_example.groovy new file mode 100644 index 0000000000..4c59e4b7cb --- /dev/null +++ b/regression-test/plugins/plugin_example.groovy @@ -0,0 +1,34 @@ +// 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 + +// register `testPlugin` function to Suite, +// and invoke in ${DORIS_HOME}/regression-test/suites/demo/test_plugin.groovy +Suite.metaClass.testPlugin = { String info /* param */ -> + + // which suite invoke current function? + Suite suite = delegate as Suite + + // function body + suite.getLogger().info("Test plugin: suiteName: ${suite.name}, info: ${info}".toString()) + + // optional return value + return "OK" +} + +logger.info("Added 'testPlugin' function to Suite") diff --git a/regression-test/suites/demo/test_plugin.groovy b/regression-test/suites/demo/test_plugin.groovy new file mode 100644 index 0000000000..f6e461e8b8 --- /dev/null +++ b/regression-test/suites/demo/test_plugin.groovy @@ -0,0 +1,22 @@ +// 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. + +suite("test_plugin", "demo") { + // register testPlugin function in ${DORIS_HOME}/regression-test/plugins/plugin_example.groovy + def result = testPlugin("message from suite") + assertEquals("OK", result) +}