[improvement](regression-test) Support suite plugin to add third-part… (#9294)
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
This commit is contained in:
@ -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及其日志。
|
||||
|
||||
@ -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 <group>/<suite>.groovy
|
||||
// empty group will test all group
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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<ScriptSource> findScriptSources(String root, Predicate<String> 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.
|
||||
| ____ _ ____ ____ _____ ____
|
||||
|
||||
34
regression-test/plugins/plugin_example.groovy
Normal file
34
regression-test/plugins/plugin_example.groovy
Normal file
@ -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")
|
||||
22
regression-test/suites/demo/test_plugin.groovy
Normal file
22
regression-test/suites/demo/test_plugin.groovy
Normal file
@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user