You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@dubbo.apache.org by li...@apache.org on 2020/12/25 14:33:22 UTC

[dubbo-samples] branch master updated: Add test framework (#187)

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

liujun pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/dubbo-samples.git


The following commit(s) were added to refs/heads/master by this push:
     new 63ee96e  Add test framework (#187)
63ee96e is described below

commit 63ee96ee0f84eb7f1f4b324c736e40f5034b2af7
Author: gongdewei <ky...@qq.com>
AuthorDate: Fri Dec 25 22:33:12 2020 +0800

    Add test framework (#187)
---
 .github/workflows/golang-ci.yml                    |  26 -
 .github/workflows/java-ci.yml                      | 238 ++++++++-
 .gitignore                                         |   3 +
 dubbo-samples-annotation/case-configuration.yml    |  24 +
 dubbo-samples-api/case-configuration.yml           |  23 +
 .../src/main/resources/log4j.properties            |  25 +
 .../case-configuration.yml                         |  24 +
 .../samples/async/impl/GreetingsServiceImpl.java   |   2 +-
 .../case-configuration.yml                         |  24 +
 .../dubbo-samples-async-onerror/pom.xml            |  13 +
 .../src/main/resources/spring/async-consumer.xml   |   2 +-
 .../dubbo/samples/governance/AsyncServiceIT.java   |  33 ++
 .../case-configuration.yml                         |  24 +
 .../case-configuration.yml                         |  24 +
 .../case-configuration.yml                         |  24 +
 dubbo-samples-attachment/case-configuration.yml    |  24 +
 dubbo-samples-basic/case-configuration.yml         |  24 +
 dubbo-samples-cache/case-configuration.yml         |  24 +
 dubbo-samples-callback/case-configuration.yml      |  24 +
 dubbo-samples-chain/case-configuration.yml         |  58 +++
 dubbo-samples-compatible/case-configuration.yml    |  24 +
 .../case-configuration.yml                         |  24 +
 .../case-configuration.yml                         |  85 ++++
 .../resources/spring/configcenter-consumer.xml     |   2 +
 .../resources/spring/configcenter-provider.xml     |   2 +
 .../case-configuration.yml                         |  24 +
 .../case-configuration.yml                         |  24 +
 .../resources/spring/configcenter-consumer.xml     |   2 +
 .../case-configuration.yml                         |  24 +
 .../resources/spring/configcenter-consumer.xml     |   2 +
 dubbo-samples-echo/case-configuration.yml          |  24 +
 .../case-configuration.yml                         |  24 +
 .../dubbo/samples/generic/call/HelloServiceIT.java |  12 +-
 .../case-configuration.yml                         |  24 +
 dubbo-samples-group/case-configuration.yml         |  24 +
 .../case-configuration.yml                         |  24 +
 .../case-configuration.yml                         |  24 +
 .../case-configuration.yml                         |  24 +
 .../pom.xml                                        |   3 +-
 .../main/resources/spring/metadata-consumer.xml    |   2 +
 .../main/resources/spring/metadata-provider.xml    |   2 +
 .../case-configuration.yml                         |  24 +
 dubbo-samples-metrics/case-configuration.yml       |  24 +
 dubbo-samples-notify/case-configuration.yml        |  24 +
 .../case-configuration.yml                         |  24 +
 .../case-configuration.yml                         |  24 +
 .../case-configuration.yml                         |  24 +
 .../case-configuration.yml                         |  24 +
 .../pom.xml                                        |   3 +-
 .../case-configuration.yml                         |  24 +
 .../case-configuration.yml                         |  24 +
 .../main/resources/spring/dubbo-demo-provider.xml  |   2 +-
 .../case-configuration.yml                         |  24 +
 dubbo-samples-stub/case-configuration.yml          |  24 +
 .../case-configuration.yml                         |  24 +
 test/README.md                                     | 278 +++++++++++
 test/build-test-image.sh                           |   8 +
 test/clean-damaged-image.sh                        |  15 +
 test/dubbo-scenario-builder/pom.xml                |  97 ++++
 .../scenario/builder/AbstractRunningGenerator.java |  68 +++
 .../dubbo/scenario/builder/ConfigurationImpl.java  | 547 +++++++++++++++++++++
 .../builder/DockerComposeRunningGenerator.java     |  75 +++
 .../dubbo/scenario/builder/IConfiguration.java     |  51 ++
 .../scenario/builder/ScenarioBuilderMain.java      |  28 ++
 .../builder/ScenarioRunningScriptGenerator.java    |  25 +
 .../exception/ConfigureFileNotFoundException.java  |  23 +
 .../builder/exception/GenerateFailedException.java |  21 +
 .../scenario/builder/vo/CaseConfiguration.java     |  70 +++
 .../dubbo/scenario/builder/vo/DockerService.java   | 131 +++++
 .../dubbo/scenario/builder/vo/JavaDebugOption.java |  40 ++
 .../scenario/builder/vo/ServiceComponent.java      | 242 +++++++++
 .../main/resources/compose-start-script.template   |  18 +
 .../resources/configs/app-builtin-zookeeper.yml    |  57 +++
 .../resources/configs/app-external-zookeeper.yml   |  64 +++
 .../src/main/resources/docker-compose.template     |  88 ++++
 .../src/main/resources/scenario.sh                 | 140 ++++++
 test/dubbo-test-runner/build.sh                    |  19 +
 test/dubbo-test-runner/pom.xml                     |  85 ++++
 test/dubbo-test-runner/src/docker/Dockerfile       |  61 +++
 test/dubbo-test-runner/src/docker/run-dubbo-app.sh |  48 ++
 .../dubbo-test-runner/src/docker/run-dubbo-test.sh |  36 ++
 test/dubbo-test-runner/src/docker/run.sh           |  45 ++
 test/dubbo-test-runner/src/docker/utils.sh         | 108 ++++
 .../apache/dubbo/test/runner/TestRunnerMain.java   | 245 +++++++++
 test/kill-tests.sh                                 |  19 +
 test/pom.xml                                       |  37 ++
 test/prepare-test.sh                               |  28 ++
 test/quick-start_cn.md                             | 102 ++++
 test/run-tests.sh                                  | 290 +++++++++++
 89 files changed, 4525 insertions(+), 40 deletions(-)

diff --git a/.github/workflows/golang-ci.yml b/.github/workflows/golang-ci.yml
deleted file mode 100644
index 2276c0e..0000000
--- a/.github/workflows/golang-ci.yml
+++ /dev/null
@@ -1,26 +0,0 @@
-name: Golang CI
-
-on:
-  pull_request:
-    paths:
-      - "golang/**"
-  push:
-    paths:
-      - 'golang/**'
-
-jobs:
-  build:
-    runs-on: ubuntu-latest
-    steps:
-      - name: Set up Go 1.13
-        uses: actions/setup-go@v1
-        with:
-          go-version: 1.13
-
-      - name: Check out source code
-        uses: actions/checkout@v1
-
-      - name: Build
-        run: |
-          cd golang
-          ./ci.sh
\ No newline at end of file
diff --git a/.github/workflows/java-ci.yml b/.github/workflows/java-ci.yml
index 0ed342c..47394b6 100644
--- a/.github/workflows/java-ci.yml
+++ b/.github/workflows/java-ci.yml
@@ -3,22 +3,252 @@ name: Java CI
 on:
   pull_request:
     paths:
-      - "java/**"
+      - "**"
   push:
     paths:
-      - 'java/**'
+      - '**'
+
+env:
+  FORK_COUNT: 2
+  FAIL_FAST: 0
+  SHOW_ERROR_DETAIL: 1
 
 jobs:
   build:
     runs-on: ubuntu-latest
-
     steps:
       - uses: actions/checkout@v1
+      - name: Cache local Maven repository
+        uses: actions/cache@v2
+        with:
+          path: ~/.m2/repository
+          key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+          restore-keys: |
+            ${{ runner.os }}-maven-
       - name: Set up JDK 1.8
         uses: actions/setup-java@v1
         with:
           java-version: 1.8
       - name: Build with Maven
         run: |
-          cd java
           ./mvnw  --settings .mvn/settings.xml clean package
+
+  prepare_test:
+    runs-on: ubuntu-latest
+    env:
+      JOB_COUNT: 6
+    steps:
+      - uses: actions/checkout@v1
+      - name: Prepare test list
+        run: |
+          cd test
+          bash ./prepare-test.sh
+      - name: Upload test list result
+        uses: actions/upload-artifact@v2
+        with:
+          name: test-list-result
+          path: test/jobs
+
+  test_job_1:
+    needs: prepare_test
+    runs-on: ubuntu-latest
+    env:
+      TEST_CASE_FILE: jobs/testcases-1.txt
+    steps:
+      - uses: actions/checkout@v1
+      - name: Cache local Maven repository
+        uses: actions/cache@v2
+        with:
+          path: ~/.m2/repository
+          key: ${{ runner.os }}-maven-${{ hashFiles('**/test/pom.xml') }}
+          restore-keys: |
+            ${{ runner.os }}-maven-
+      - name: Set up JDK 1.8
+        uses: actions/setup-java@v1
+        with:
+          java-version: 1.8
+      - name: Download test list result
+        uses: actions/download-artifact@v2
+        with:
+          name: test-list-result
+          path: test/jobs/
+      - uses: satackey/action-docker-layer-caching@v0.0.11
+        with:
+          key: ${{ runner.os }}-docker-layer-${{ hashFiles('**/test/build-test-image.sh') }}
+          restore-keys: ${{ runner.os }}-docker-layer-${{ hashFiles('**/test/build-test-image.sh') }}
+      - name: Build test image
+        run: cd test && bash ./build-test-image.sh
+      - name: Run tests
+        run: cd test && bash ./run-tests.sh
+      - name: Clean images
+        run: cd test && bash ./clean-damaged-image.sh
+
+  test_job_2:
+    needs: prepare_test
+    runs-on: ubuntu-latest
+    env:
+      TEST_CASE_FILE: jobs/testcases-2.txt
+    steps:
+      - uses: actions/checkout@v1
+      - name: Cache local Maven repository
+        uses: actions/cache@v2
+        with:
+          path: ~/.m2/repository
+          key: ${{ runner.os }}-maven-${{ hashFiles('**/test/build-test-image.sh') }}
+          restore-keys: |
+            ${{ runner.os }}-maven-
+      - name: Set up JDK 1.8
+        uses: actions/setup-java@v1
+        with:
+          java-version: 1.8
+      - name: Download test list result
+        uses: actions/download-artifact@v2
+        with:
+          name: test-list-result
+          path: test/jobs/
+      - uses: satackey/action-docker-layer-caching@v0.0.11
+        with:
+          key: ${{ runner.os }}-docker-layer-${{ hashFiles('**/test/build-test-image.sh') }}
+          restore-keys: ${{ runner.os }}-docker-layer-${{ hashFiles('**/test/build-test-image.sh') }}
+      - name: Build test image
+        run: cd test && bash ./build-test-image.sh
+      - name: Run tests
+        run: cd test && bash ./run-tests.sh
+      - name: Clean images
+        run: cd test && bash ./clean-damaged-image.sh
+
+  test_job_3:
+    needs: prepare_test
+    runs-on: ubuntu-latest
+    env:
+      TEST_CASE_FILE: jobs/testcases-3.txt
+    steps:
+      - uses: actions/checkout@v1
+      - name: Cache local Maven repository
+        uses: actions/cache@v2
+        with:
+          path: ~/.m2/repository
+          key: ${{ runner.os }}-maven-${{ hashFiles('**/test/pom.xml') }}
+          restore-keys: |
+            ${{ runner.os }}-maven-
+      - name: Set up JDK 1.8
+        uses: actions/setup-java@v1
+        with:
+          java-version: 1.8
+      - name: Download test list result
+        uses: actions/download-artifact@v2
+        with:
+          name: test-list-result
+          path: test/jobs/
+      - uses: satackey/action-docker-layer-caching@v0.0.11
+        with:
+          key: ${{ runner.os }}-docker-layer-${{ hashFiles('**/test/build-test-image.sh') }}
+          restore-keys: ${{ runner.os }}-docker-layer-${{ hashFiles('**/test/build-test-image.sh') }}
+      - name: Build test image
+        run: cd test && bash ./build-test-image.sh
+      - name: Run tests
+        run: cd test && bash ./run-tests.sh
+      - name: Clean images
+        run: cd test && bash ./clean-damaged-image.sh
+
+  test_job_4:
+    needs: prepare_test
+    runs-on: ubuntu-latest
+    env:
+      TEST_CASE_FILE: jobs/testcases-4.txt
+    steps:
+      - uses: actions/checkout@v1
+      - name: Cache local Maven repository
+        uses: actions/cache@v2
+        with:
+          path: ~/.m2/repository
+          key: ${{ runner.os }}-maven-${{ hashFiles('**/test/build-test-image.sh') }}
+          restore-keys: |
+            ${{ runner.os }}-maven-
+      - name: Set up JDK 1.8
+        uses: actions/setup-java@v1
+        with:
+          java-version: 1.8
+      - name: Download test list result
+        uses: actions/download-artifact@v2
+        with:
+          name: test-list-result
+          path: test/jobs/
+      - uses: satackey/action-docker-layer-caching@v0.0.11
+        with:
+          key: ${{ runner.os }}-docker-layer-${{ hashFiles('**/test/build-test-image.sh') }}
+          restore-keys: ${{ runner.os }}-docker-layer-${{ hashFiles('**/test/build-test-image.sh') }}
+      - name: Build test image
+        run: cd test && bash ./build-test-image.sh
+      - name: Run tests
+        run: cd test && bash ./run-tests.sh
+      - name: Clean images
+        run: cd test && bash ./clean-damaged-image.sh
+
+  test_job_5:
+    needs: prepare_test
+    runs-on: ubuntu-latest
+    env:
+      TEST_CASE_FILE: jobs/testcases-5.txt
+    steps:
+      - uses: actions/checkout@v1
+      - name: Cache local Maven repository
+        uses: actions/cache@v2
+        with:
+          path: ~/.m2/repository
+          key: ${{ runner.os }}-maven-${{ hashFiles('**/test/build-test-image.sh') }}
+          restore-keys: |
+            ${{ runner.os }}-maven-
+      - name: Set up JDK 1.8
+        uses: actions/setup-java@v1
+        with:
+          java-version: 1.8
+      - name: Download test list result
+        uses: actions/download-artifact@v2
+        with:
+          name: test-list-result
+          path: test/jobs/
+      - uses: satackey/action-docker-layer-caching@v0.0.11
+        with:
+          key: ${{ runner.os }}-docker-layer-${{ hashFiles('**/test/build-test-image.sh') }}
+          restore-keys: ${{ runner.os }}-docker-layer-${{ hashFiles('**/test/build-test-image.sh') }}
+      - name: Build test image
+        run: cd test && bash ./build-test-image.sh
+      - name: Run tests
+        run: cd test && bash ./run-tests.sh
+      - name: Clean images
+        run: cd test && bash ./clean-damaged-image.sh
+
+  test_job_6:
+    needs: prepare_test
+    runs-on: ubuntu-latest
+    env:
+      TEST_CASE_FILE: jobs/testcases-6.txt
+    steps:
+      - uses: actions/checkout@v1
+      - name: Cache local Maven repository
+        uses: actions/cache@v2
+        with:
+          path: ~/.m2/repository
+          key: ${{ runner.os }}-maven-${{ hashFiles('**/test/build-test-image.sh') }}
+          restore-keys: |
+            ${{ runner.os }}-maven-
+      - name: Set up JDK 1.8
+        uses: actions/setup-java@v1
+        with:
+          java-version: 1.8
+      - name: Download test list result
+        uses: actions/download-artifact@v2
+        with:
+          name: test-list-result
+          path: test/jobs/
+      - uses: satackey/action-docker-layer-caching@v0.0.11
+        with:
+          key: ${{ runner.os }}-docker-layer-${{ hashFiles('**/test/build-test-image.sh') }}
+          restore-keys: ${{ runner.os }}-docker-layer-${{ hashFiles('**/test/build-test-image.sh') }}
+      - name: Build test image
+        run: cd test && bash ./build-test-image.sh
+      - name: Run tests
+        run: cd test && bash ./run-tests.sh
+      - name: Clean damged images
+        run: cd test && bash ./clean-damaged-image.sh
diff --git a/.gitignore b/.gitignore
index 7905ed6..293c737 100644
--- a/.gitignore
+++ b/.gitignore
@@ -53,3 +53,6 @@ vendor/
 logs/
 .vscode/
 coverage.txt
+
+test/jobs
+testcases*.txt
\ No newline at end of file
diff --git a/dubbo-samples-annotation/case-configuration.yml b/dubbo-samples-annotation/case-configuration.yml
new file mode 100644
index 0000000..4b5f7d6
--- /dev/null
+++ b/dubbo-samples-annotation/case-configuration.yml
@@ -0,0 +1,24 @@
+# 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.
+
+from: app-builtin-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-annotation
+  main_class: org.apache.dubbo.samples.annotation.AnnotationProviderBootstrap
+  zookeeper_port: 2181
+  dubbo_port: 20880
+
diff --git a/dubbo-samples-api/case-configuration.yml b/dubbo-samples-api/case-configuration.yml
new file mode 100644
index 0000000..bbf9251
--- /dev/null
+++ b/dubbo-samples-api/case-configuration.yml
@@ -0,0 +1,23 @@
+# 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.
+
+from: app-external-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-api
+  main_class: org.apache.dubbo.samples.provider.Application
+  dubbo_port: 20880
+
diff --git a/dubbo-samples-api/src/main/resources/log4j.properties b/dubbo-samples-api/src/main/resources/log4j.properties
new file mode 100644
index 0000000..e976f5c
--- /dev/null
+++ b/dubbo-samples-api/src/main/resources/log4j.properties
@@ -0,0 +1,25 @@
+#
+#
+#   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.
+#  
+#
+###set log levels###
+log4j.rootLogger=info, stdout
+###output to the console###
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.Target=System.out
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=[%d{dd/MM/yy hh:mm:ss:sss z}] %t %5p %c{2}: %m%n
\ No newline at end of file
diff --git a/dubbo-samples-async/dubbo-samples-async-generated-future/case-configuration.yml b/dubbo-samples-async/dubbo-samples-async-generated-future/case-configuration.yml
new file mode 100644
index 0000000..d9df910
--- /dev/null
+++ b/dubbo-samples-async/dubbo-samples-async-generated-future/case-configuration.yml
@@ -0,0 +1,24 @@
+# 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.
+
+from: app-builtin-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-async-generated-future
+  main_class: org.apache.dubbo.samples.async.AsyncProvider
+  zookeeper_port: 2181
+  dubbo_port: 20880
+
diff --git a/dubbo-samples-async/dubbo-samples-async-generated-future/src/main/java/org/apache/dubbo/samples/async/impl/GreetingsServiceImpl.java b/dubbo-samples-async/dubbo-samples-async-generated-future/src/main/java/org/apache/dubbo/samples/async/impl/GreetingsServiceImpl.java
index 0d414c8..fe0340e 100644
--- a/dubbo-samples-async/dubbo-samples-async-generated-future/src/main/java/org/apache/dubbo/samples/async/impl/GreetingsServiceImpl.java
+++ b/dubbo-samples-async/dubbo-samples-async-generated-future/src/main/java/org/apache/dubbo/samples/async/impl/GreetingsServiceImpl.java
@@ -28,7 +28,7 @@ public class GreetingsServiceImpl implements GreetingService {
     public String greeting(String name) {
         System.out.println("provider received: " + name);
         try {
-            Thread.sleep(500);
+            Thread.sleep(50);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
diff --git a/dubbo-samples-async/dubbo-samples-async-onerror/case-configuration.yml b/dubbo-samples-async/dubbo-samples-async-onerror/case-configuration.yml
new file mode 100644
index 0000000..5244d7d
--- /dev/null
+++ b/dubbo-samples-async/dubbo-samples-async-onerror/case-configuration.yml
@@ -0,0 +1,24 @@
+# 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.
+
+from: app-builtin-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-async-onerror
+  main_class: org.apache.dubbo.samples.governance.AsyncProvider
+  zookeeper_port: 2181
+  dubbo_port: 20880
+
diff --git a/dubbo-samples-async/dubbo-samples-async-onerror/pom.xml b/dubbo-samples-async/dubbo-samples-async-onerror/pom.xml
index 16ffe54..bfb331b 100644
--- a/dubbo-samples-async/dubbo-samples-async-onerror/pom.xml
+++ b/dubbo-samples-async/dubbo-samples-async-onerror/pom.xml
@@ -209,6 +209,19 @@
             <groupId>org.springframework</groupId>
             <artifactId>spring-context</artifactId>
         </dependency>
+
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
     </dependencies>
 
 
diff --git a/dubbo-samples-async/dubbo-samples-async-onerror/src/main/resources/spring/async-consumer.xml b/dubbo-samples-async/dubbo-samples-async-onerror/src/main/resources/spring/async-consumer.xml
index b6dcca0..d584894 100644
--- a/dubbo-samples-async/dubbo-samples-async-onerror/src/main/resources/spring/async-consumer.xml
+++ b/dubbo-samples-async/dubbo-samples-async-onerror/src/main/resources/spring/async-consumer.xml
@@ -26,7 +26,7 @@
 
     <dubbo:application name="async-consumer"/>
 
-    <dubbo:registry address="zookeeper://127.0.0.1:2181"/>
+    <dubbo:registry address="zookeeper://${zookeeper.address:127.0.0.1}:2181"/>
 
     <dubbo:reference cluster="failfast" async="true" id="asyncService" interface="org.apache.dubbo.samples.governance.api.AsyncService"/>
 
diff --git a/dubbo-samples-async/dubbo-samples-async-onerror/src/test/java/org/apache/dubbo/samples/governance/AsyncServiceIT.java b/dubbo-samples-async/dubbo-samples-async-onerror/src/test/java/org/apache/dubbo/samples/governance/AsyncServiceIT.java
new file mode 100644
index 0000000..548bb99
--- /dev/null
+++ b/dubbo-samples-async/dubbo-samples-async-onerror/src/test/java/org/apache/dubbo/samples/governance/AsyncServiceIT.java
@@ -0,0 +1,33 @@
+package org.apache.dubbo.samples.governance;
+
+import org.apache.dubbo.rpc.RpcContext;
+import org.apache.dubbo.samples.governance.api.AsyncService;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(locations = {"classpath:/spring/async-consumer.xml"})
+public class AsyncServiceIT {
+
+    @Autowired
+    private AsyncService asyncService;
+
+    @Test(expected = org.apache.dubbo.remoting.TimeoutException.class)
+    public void testSayHelloTimeout() throws Throwable {
+        try {
+            asyncService.sayHelloTimeout("timeout world");
+            CompletableFuture<String> helloFuture = RpcContext.getContext().getCompletableFuture();
+            String result = helloFuture.get();
+            System.out.println("result: "+result);
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw e.getCause();
+        }
+    }
+}
diff --git a/dubbo-samples-async/dubbo-samples-async-original-future/case-configuration.yml b/dubbo-samples-async/dubbo-samples-async-original-future/case-configuration.yml
new file mode 100644
index 0000000..4fbaa35
--- /dev/null
+++ b/dubbo-samples-async/dubbo-samples-async-original-future/case-configuration.yml
@@ -0,0 +1,24 @@
+# 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.
+
+from: app-builtin-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-async-original-future
+  main_class: org.apache.dubbo.samples.async.AsyncProvider
+  zookeeper_port: 2181
+  dubbo_port: 20880
+
diff --git a/dubbo-samples-async/dubbo-samples-async-provider/case-configuration.yml b/dubbo-samples-async/dubbo-samples-async-provider/case-configuration.yml
new file mode 100644
index 0000000..df96773
--- /dev/null
+++ b/dubbo-samples-async/dubbo-samples-async-provider/case-configuration.yml
@@ -0,0 +1,24 @@
+# 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.
+
+from: app-builtin-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-async-provider
+  main_class: org.apache.dubbo.samples.async.AsyncProvider
+  zookeeper_port: 2181
+  dubbo_port: 20880
+
diff --git a/dubbo-samples-async/dubbo-samples-async-simple/case-configuration.yml b/dubbo-samples-async/dubbo-samples-async-simple/case-configuration.yml
new file mode 100644
index 0000000..14b9858
--- /dev/null
+++ b/dubbo-samples-async/dubbo-samples-async-simple/case-configuration.yml
@@ -0,0 +1,24 @@
+# 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.
+
+from: app-builtin-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-async-simple
+  main_class: org.apache.dubbo.samples.async.AsyncProvider
+  zookeeper_port: 2181
+  dubbo_port: 20880
+
diff --git a/dubbo-samples-attachment/case-configuration.yml b/dubbo-samples-attachment/case-configuration.yml
new file mode 100644
index 0000000..a87cbb0
--- /dev/null
+++ b/dubbo-samples-attachment/case-configuration.yml
@@ -0,0 +1,24 @@
+# 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.
+
+from: app-builtin-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-attachment
+  main_class: org.apache.dubbo.samples.attachment.AttachmentProvider
+  zookeeper_port: 2181
+  dubbo_port: 20880
+
diff --git a/dubbo-samples-basic/case-configuration.yml b/dubbo-samples-basic/case-configuration.yml
new file mode 100644
index 0000000..b88c91d
--- /dev/null
+++ b/dubbo-samples-basic/case-configuration.yml
@@ -0,0 +1,24 @@
+# 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.
+
+from: app-builtin-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-basic
+  main_class: org.apache.dubbo.samples.basic.BasicProvider
+  zookeeper_port: 2181
+  dubbo_port: 20880
+
diff --git a/dubbo-samples-cache/case-configuration.yml b/dubbo-samples-cache/case-configuration.yml
new file mode 100644
index 0000000..a0795d4
--- /dev/null
+++ b/dubbo-samples-cache/case-configuration.yml
@@ -0,0 +1,24 @@
+# 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.
+
+from: app-builtin-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-cache
+  main_class: org.apache.dubbo.samples.cache.CacheProvider
+  zookeeper_port: 2181
+  dubbo_port: 20880
+
diff --git a/dubbo-samples-callback/case-configuration.yml b/dubbo-samples-callback/case-configuration.yml
new file mode 100644
index 0000000..073f207
--- /dev/null
+++ b/dubbo-samples-callback/case-configuration.yml
@@ -0,0 +1,24 @@
+# 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.
+
+from: app-builtin-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-callback
+  main_class: org.apache.dubbo.samples.callback.CallbackProvider
+  zookeeper_port: 2181
+  dubbo_port: 20880
+
diff --git a/dubbo-samples-chain/case-configuration.yml b/dubbo-samples-chain/case-configuration.yml
new file mode 100644
index 0000000..fd33829
--- /dev/null
+++ b/dubbo-samples-chain/case-configuration.yml
@@ -0,0 +1,58 @@
+# 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.
+
+
+services:
+  zookeeper:
+    image: zookeeper:latest
+
+  dubbo-samples-chain-backend:
+    type: app
+    basedir: dubbo-samples-chain-backend
+    mainClass: org.apache.dubbo.samples.chain.BackendProvider
+    systemProps:
+      - zookeeper.address=zookeeper
+    waitPortsBeforeRun:
+      - zookeeper:2181
+    depends_on:
+      - zookeeper
+
+  dubbo-samples-chain-middle:
+    type: app
+    basedir: dubbo-samples-chain-middle
+    mainClass: org.apache.dubbo.samples.chain.MiddleEndProvider
+    systemProps:
+      - zookeeper.address=zookeeper
+    waitPortsBeforeRun:
+      - zookeeper:2181
+    depends_on:
+      - zookeeper
+
+  dubbo-samples-chain-front:
+    type: test
+    basedir: dubbo-samples-chain-front
+    tests:
+      - "**/*IT.class"
+    systemProps:
+      - zookeeper.address=zookeeper
+    waitPortsBeforeRun:
+      - zookeeper:2181
+      - dubbo-samples-chain-backend:20880
+      - dubbo-samples-chain-middle:20881
+    depends_on:
+      - zookeeper
+      - dubbo-samples-chain-backend
+      - dubbo-samples-chain-middle
diff --git a/dubbo-samples-compatible/case-configuration.yml b/dubbo-samples-compatible/case-configuration.yml
new file mode 100644
index 0000000..03331ea
--- /dev/null
+++ b/dubbo-samples-compatible/case-configuration.yml
@@ -0,0 +1,24 @@
+# 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.
+
+from: app-builtin-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-compatible
+  main_class: org.apache.dubbo.samples.compat.Provider
+  zookeeper_port: 2181
+  dubbo_port: 20890
+
diff --git a/dubbo-samples-configcenter/dubbo-samples-configcenter-annotation/case-configuration.yml b/dubbo-samples-configcenter/dubbo-samples-configcenter-annotation/case-configuration.yml
new file mode 100644
index 0000000..da2a1ac
--- /dev/null
+++ b/dubbo-samples-configcenter/dubbo-samples-configcenter-annotation/case-configuration.yml
@@ -0,0 +1,24 @@
+# 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.
+
+from: app-builtin-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-configcenter-annotation
+  main_class: org.apache.dubbo.samples.configcenter.annotation.AnnotationProvider
+  zookeeper_port: 2181
+  dubbo_port: 20990
+
diff --git a/dubbo-samples-configcenter/dubbo-samples-configcenter-apollo/case-configuration.yml b/dubbo-samples-configcenter/dubbo-samples-configcenter-apollo/case-configuration.yml
new file mode 100644
index 0000000..d715f8e
--- /dev/null
+++ b/dubbo-samples-configcenter/dubbo-samples-configcenter-apollo/case-configuration.yml
@@ -0,0 +1,85 @@
+# 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.
+
+from: app-builtin-zookeeper.yml
+timeout: 120
+
+props:
+  project_name: dubbo-samples-configcenter-apollo
+  main_class: org.apache.dubbo.samples.configcenter.ApolloProvider
+  zookeeper_port: 2181
+  dubbo_port: 20880
+  apollo_address: apollo-quick-start
+  apollo_port: 8080
+
+services:
+  ${project_name}:
+    type: app
+    basedir: .
+    mainClass: ${main_class}
+    systemProps:
+      - apollo.address=${apollo_address}
+      - apollo.port=${apollo_port}
+    waitPortsBeforeRun:
+      - ${apollo_address}:${apollo_port}
+    checkTimeout: 100
+
+  ${project_name}-test:
+    type: test
+    basedir: .
+    tests:
+      - "**/*IT.class"
+    systemProps:
+      - zookeeper.address=${project_name}
+      - zookeeper.port=${zookeeper_port}
+      - dubbo.address=${project_name}
+      - dubbo.port=${dubbo_port}
+    waitPortsBeforeRun:
+      - ${project_name}:${zookeeper_port}
+      - ${project_name}:${dubbo_port}
+    checkTimeout: 100
+    depends_on:
+      - ${project_name}
+  
+  apollo-quick-start:
+    image: nobodyiam/apollo-quick-start
+    depends_on:
+      - apollo-db
+#    ports:
+#      - "8080:8080"
+#      - "8070:8070"
+    links:
+      - apollo-db
+
+  apollo-db:
+    image: mysql:5.7
+    environment:
+      - TZ=Asia/Shanghai
+      - MYSQL_ALLOW_EMPTY_PASSWORD=yes
+#    ports:
+#      - "13306:3306"
+    volumes:
+      - ./classes/docker/sql:/docker-entrypoint-initdb.d
+#      - ./sql_data:/var/lib/mysql
+#    volumes_from:
+#      - apollo-dbdata
+#    depends_on:
+#      - apollo-dbdata
+#
+#  apollo-dbdata:
+#    image: alpine:latest
+#    volumes:
+#      - /var/lib/mysql
diff --git a/dubbo-samples-configcenter/dubbo-samples-configcenter-apollo/src/main/resources/spring/configcenter-consumer.xml b/dubbo-samples-configcenter/dubbo-samples-configcenter-apollo/src/main/resources/spring/configcenter-consumer.xml
index 92965eb..4b25a6f 100644
--- a/dubbo-samples-configcenter/dubbo-samples-configcenter-apollo/src/main/resources/spring/configcenter-consumer.xml
+++ b/dubbo-samples-configcenter/dubbo-samples-configcenter-apollo/src/main/resources/spring/configcenter-consumer.xml
@@ -27,6 +27,8 @@
 
     <dubbo:application name="dubbo-configcenter-apollo-consumer"/>
 
+    <dubbo:registry address="zookeeper://${zookeeper.address:127.0.0.1}:2181"/>
+
     <dubbo:config-center highest-priority="false" protocol="apollo" address="${apollo.address:localhost}:8080"/>
 
     <dubbo:reference id="demoService" interface="org.apache.dubbo.samples.configcenter.api.DemoService"/>
diff --git a/dubbo-samples-configcenter/dubbo-samples-configcenter-apollo/src/main/resources/spring/configcenter-provider.xml b/dubbo-samples-configcenter/dubbo-samples-configcenter-apollo/src/main/resources/spring/configcenter-provider.xml
index de2b0ea..6f0bfbb 100644
--- a/dubbo-samples-configcenter/dubbo-samples-configcenter-apollo/src/main/resources/spring/configcenter-provider.xml
+++ b/dubbo-samples-configcenter/dubbo-samples-configcenter-apollo/src/main/resources/spring/configcenter-provider.xml
@@ -27,6 +27,8 @@
 
     <dubbo:application name="dubbo-configcenter-apollo-provider"/>
 
+    <dubbo:registry address="zookeeper://${zookeeper.address:127.0.0.1}:2181"/>
+
     <dubbo:config-center address="apollo://${apollo.address:localhost}:8080"/>
 
     <bean id="demoService" class="org.apache.dubbo.samples.configcenter.impl.DemoServiceImpl"/>
diff --git a/dubbo-samples-configcenter/dubbo-samples-configcenter-externalconfiguration/case-configuration.yml b/dubbo-samples-configcenter/dubbo-samples-configcenter-externalconfiguration/case-configuration.yml
new file mode 100644
index 0000000..275dfa6
--- /dev/null
+++ b/dubbo-samples-configcenter/dubbo-samples-configcenter-externalconfiguration/case-configuration.yml
@@ -0,0 +1,24 @@
+# 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.
+
+from: app-builtin-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-configcenter-externalconfiguration
+  main_class: org.apache.dubbo.samples.externalconfiguration.provider.AnnotationProvider
+  zookeeper_port: 2181
+  dubbo_port: 20880
+
diff --git a/dubbo-samples-configcenter/dubbo-samples-configcenter-multiprotocol/case-configuration.yml b/dubbo-samples-configcenter/dubbo-samples-configcenter-multiprotocol/case-configuration.yml
new file mode 100644
index 0000000..b312f37
--- /dev/null
+++ b/dubbo-samples-configcenter/dubbo-samples-configcenter-multiprotocol/case-configuration.yml
@@ -0,0 +1,24 @@
+# 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.
+
+from: app-builtin-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-configcenter-multiprotocol
+  main_class: org.apache.dubbo.samples.configcenter.BasicProvider
+  zookeeper_port: 2181
+  dubbo_port: 20991
+
diff --git a/dubbo-samples-configcenter/dubbo-samples-configcenter-multiprotocol/src/main/resources/spring/configcenter-consumer.xml b/dubbo-samples-configcenter/dubbo-samples-configcenter-multiprotocol/src/main/resources/spring/configcenter-consumer.xml
index c6170b7..f3fdd7c 100644
--- a/dubbo-samples-configcenter/dubbo-samples-configcenter-multiprotocol/src/main/resources/spring/configcenter-consumer.xml
+++ b/dubbo-samples-configcenter/dubbo-samples-configcenter-multiprotocol/src/main/resources/spring/configcenter-consumer.xml
@@ -27,6 +27,8 @@
 
     <dubbo:config-center address="zookeeper://${zookeeper.address:127.0.0.1}:2181"/>
 
+    <dubbo:registry address="zookeeper://${zookeeper.address:127.0.0.1}:2181"/>
+
     <dubbo:reference id="demoService" protocol="dubbo"
                      interface="org.apache.dubbo.samples.configcenter.api.DemoService"/>
 
diff --git a/dubbo-samples-configcenter/dubbo-samples-configcenter-xml/case-configuration.yml b/dubbo-samples-configcenter/dubbo-samples-configcenter-xml/case-configuration.yml
new file mode 100644
index 0000000..2a946e8
--- /dev/null
+++ b/dubbo-samples-configcenter/dubbo-samples-configcenter-xml/case-configuration.yml
@@ -0,0 +1,24 @@
+# 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.
+
+from: app-builtin-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-configcenter-xml
+  main_class: org.apache.dubbo.samples.configcenter.BasicProvider
+  zookeeper_port: 2181
+  dubbo_port: 20880
+
diff --git a/dubbo-samples-configcenter/dubbo-samples-configcenter-xml/src/main/resources/spring/configcenter-consumer.xml b/dubbo-samples-configcenter/dubbo-samples-configcenter-xml/src/main/resources/spring/configcenter-consumer.xml
index 2c5dc3e..3d57adb 100644
--- a/dubbo-samples-configcenter/dubbo-samples-configcenter-xml/src/main/resources/spring/configcenter-consumer.xml
+++ b/dubbo-samples-configcenter/dubbo-samples-configcenter-xml/src/main/resources/spring/configcenter-consumer.xml
@@ -30,6 +30,8 @@
 
     <dubbo:config-center highest-priority="false" address="zookeeper://${zookeeper.address:127.0.0.1}:2181"/>
 
+    <dubbo:registry address="zookeeper://${zookeeper.address:127.0.0.1}:2181"/>
+
     <dubbo:reference group="*" id="demoService" interface="org.apache.dubbo.samples.configcenter.api.DemoService"/>
 
 </beans>
diff --git a/dubbo-samples-echo/case-configuration.yml b/dubbo-samples-echo/case-configuration.yml
new file mode 100644
index 0000000..1fd027a
--- /dev/null
+++ b/dubbo-samples-echo/case-configuration.yml
@@ -0,0 +1,24 @@
+# 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.
+
+from: app-builtin-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-echo
+  main_class: org.apache.dubbo.samples.echo.EchoProvider
+  zookeeper_port: 2181
+  dubbo_port: 20880
+
diff --git a/dubbo-samples-generic/dubbo-samples-generic-impl/dubbo-samples-generic-impl-provider/case-configuration.yml b/dubbo-samples-generic/dubbo-samples-generic-impl/dubbo-samples-generic-impl-provider/case-configuration.yml
new file mode 100644
index 0000000..17fbefd
--- /dev/null
+++ b/dubbo-samples-generic/dubbo-samples-generic-impl/dubbo-samples-generic-impl-provider/case-configuration.yml
@@ -0,0 +1,24 @@
+# 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.
+
+from: app-builtin-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-generic-impl-provider
+  main_class: org.apache.dubbo.samples.generic.call.GenericImplProvider
+  zookeeper_port: 2181
+  dubbo_port: 20880
+
diff --git a/dubbo-samples-generic/dubbo-samples-generic-impl/dubbo-samples-generic-impl-provider/src/test/java/org/apache/dubbo/samples/generic/call/HelloServiceIT.java b/dubbo-samples-generic/dubbo-samples-generic-impl/dubbo-samples-generic-impl-provider/src/test/java/org/apache/dubbo/samples/generic/call/HelloServiceIT.java
index 7e1d4b0..78575c1 100644
--- a/dubbo-samples-generic/dubbo-samples-generic-impl/dubbo-samples-generic-impl-provider/src/test/java/org/apache/dubbo/samples/generic/call/HelloServiceIT.java
+++ b/dubbo-samples-generic/dubbo-samples-generic-impl/dubbo-samples-generic-impl-provider/src/test/java/org/apache/dubbo/samples/generic/call/HelloServiceIT.java
@@ -17,6 +17,7 @@
 
 package org.apache.dubbo.samples.generic.call;
 
+import org.apache.dubbo.rpc.service.GenericException;
 import org.apache.dubbo.samples.generic.call.api.HelloService;
 
 import org.junit.Assert;
@@ -42,8 +43,13 @@ public class HelloServiceIT {
         Assert.assertEquals("sayHelloAsync: hello world", helloService.sayHelloAsync("world").get());
     }
 
-    @Test(expected = UnsupportedOperationException.class)
-    public void testNotImplementedHello() throws Exception {
-        helloService.notImplementedHello("dubbo");
+    @Test
+    public void testNotImplementedHello() throws Throwable {
+        try {
+            helloService.notImplementedHello("dubbo");
+            Assert.fail("expect java.lang.UnsupportedOperationException");
+        } catch (GenericException e) {
+            Assert.assertEquals("java.lang.UnsupportedOperationException", e.getExceptionClass());
+        }
     }
 }
diff --git a/dubbo-samples-generic/dubbo-samples-generic-type/case-configuration.yml b/dubbo-samples-generic/dubbo-samples-generic-type/case-configuration.yml
new file mode 100644
index 0000000..f4a0366
--- /dev/null
+++ b/dubbo-samples-generic/dubbo-samples-generic-type/case-configuration.yml
@@ -0,0 +1,24 @@
+# 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.
+
+from: app-builtin-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-generic-type
+  main_class: org.apache.dubbo.samples.generic.GenericProvider
+  zookeeper_port: 2181
+  dubbo_port: 20880
+
diff --git a/dubbo-samples-group/case-configuration.yml b/dubbo-samples-group/case-configuration.yml
new file mode 100644
index 0000000..493a29d
--- /dev/null
+++ b/dubbo-samples-group/case-configuration.yml
@@ -0,0 +1,24 @@
+# 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.
+
+from: app-builtin-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-group
+  main_class: org.apache.dubbo.samples.group.GroupProvider
+  zookeeper_port: 2181
+  dubbo_port: 20880
+
diff --git a/dubbo-samples-metadata-report/dubbo-samples-metadata-report-configcenter/case-configuration.yml b/dubbo-samples-metadata-report/dubbo-samples-metadata-report-configcenter/case-configuration.yml
new file mode 100644
index 0000000..3f48ed7
--- /dev/null
+++ b/dubbo-samples-metadata-report/dubbo-samples-metadata-report-configcenter/case-configuration.yml
@@ -0,0 +1,24 @@
+# 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.
+
+from: app-builtin-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-metadata-report-configcenter
+  main_class: org.apache.dubbo.samples.metadatareport.configcenter.MetadataConfigcenterProvider
+  zookeeper_port: 2181
+  dubbo_port: 20831
+
diff --git a/dubbo-samples-metadata-report/dubbo-samples-metadata-report-local-annotation/case-configuration.yml b/dubbo-samples-metadata-report/dubbo-samples-metadata-report-local-annotation/case-configuration.yml
new file mode 100644
index 0000000..32c8b26
--- /dev/null
+++ b/dubbo-samples-metadata-report/dubbo-samples-metadata-report-local-annotation/case-configuration.yml
@@ -0,0 +1,24 @@
+# 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.
+
+from: app-builtin-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-metadata-report-local-annotation
+  main_class: org.apache.dubbo.samples.metadatareport.local.annotation.MetadataLocalAnnotationProvider
+  zookeeper_port: 2181
+  dubbo_port: 20880
+
diff --git a/dubbo-samples-metadata-report/dubbo-samples-metadata-report-local-properties/case-configuration.yml b/dubbo-samples-metadata-report/dubbo-samples-metadata-report-local-properties/case-configuration.yml
new file mode 100644
index 0000000..a097dc9
--- /dev/null
+++ b/dubbo-samples-metadata-report/dubbo-samples-metadata-report-local-properties/case-configuration.yml
@@ -0,0 +1,24 @@
+# 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.
+
+from: app-builtin-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-metadata-report-local-properties
+  main_class: org.apache.dubbo.samples.metadatareport.local.properties.MetadataLocalPropertiesProvider
+  zookeeper_port: 2181
+  dubbo_port: 20880
+
diff --git a/dubbo-samples-metadata-report/dubbo-samples-metadata-report-local-properties/pom.xml b/dubbo-samples-metadata-report/dubbo-samples-metadata-report-local-properties/pom.xml
index e696083..9c87778 100644
--- a/dubbo-samples-metadata-report/dubbo-samples-metadata-report-local-properties/pom.xml
+++ b/dubbo-samples-metadata-report/dubbo-samples-metadata-report-local-properties/pom.xml
@@ -41,8 +41,7 @@
         <java-image.name>openjdk:8</java-image.name>
         <dubbo.port>20880</dubbo.port>
         <zookeeper.port>2181</zookeeper.port>
-        <main-class>org.apache.dubbo.samples.metadatareport.local.properties.MetadataLocalPropertiesProvider
-        </main-class>
+        <main-class>org.apache.dubbo.samples.metadatareport.local.properties.MetadataLocalPropertiesProvider</main-class>
     </properties>
 
     <dependencies>
diff --git a/dubbo-samples-metadata-report/dubbo-samples-metadata-report-local-properties/src/main/resources/spring/metadata-consumer.xml b/dubbo-samples-metadata-report/dubbo-samples-metadata-report-local-properties/src/main/resources/spring/metadata-consumer.xml
index a5cd6e6..43373eb 100644
--- a/dubbo-samples-metadata-report/dubbo-samples-metadata-report-local-properties/src/main/resources/spring/metadata-consumer.xml
+++ b/dubbo-samples-metadata-report/dubbo-samples-metadata-report-local-properties/src/main/resources/spring/metadata-consumer.xml
@@ -27,6 +27,8 @@
 
     <dubbo:registry address="zookeeper://${zookeeper.address:127.0.0.1}:2181"/>
 
+    <dubbo:metadata-report address="zookeeper://${zookeeper.address:127.0.0.1}:2181"/>
+
     <dubbo:reference id="demoService"
                      interface="org.apache.dubbo.samples.metadatareport.local.properties.api.DemoService"/>
 </beans>
diff --git a/dubbo-samples-metadata-report/dubbo-samples-metadata-report-local-properties/src/main/resources/spring/metadata-provider.xml b/dubbo-samples-metadata-report/dubbo-samples-metadata-report-local-properties/src/main/resources/spring/metadata-provider.xml
index de5fa2c..198d43a 100644
--- a/dubbo-samples-metadata-report/dubbo-samples-metadata-report-local-properties/src/main/resources/spring/metadata-provider.xml
+++ b/dubbo-samples-metadata-report/dubbo-samples-metadata-report-local-properties/src/main/resources/spring/metadata-provider.xml
@@ -27,6 +27,8 @@
 
     <dubbo:registry address="zookeeper://${zookeeper.address:127.0.0.1}:2181"/>
 
+    <dubbo:metadata-report address="zookeeper://${zookeeper.address:127.0.0.1}:2181"/>
+
     <bean id="demoService" class="org.apache.dubbo.samples.metadatareport.local.properties.impl.DemoServiceImpl"/>
     <dubbo:service interface="org.apache.dubbo.samples.metadatareport.local.properties.api.DemoService"
                    ref="demoService"/>
diff --git a/dubbo-samples-metadata-report/dubbo-samples-metadata-report-local-xml/case-configuration.yml b/dubbo-samples-metadata-report/dubbo-samples-metadata-report-local-xml/case-configuration.yml
new file mode 100644
index 0000000..569853e
--- /dev/null
+++ b/dubbo-samples-metadata-report/dubbo-samples-metadata-report-local-xml/case-configuration.yml
@@ -0,0 +1,24 @@
+# 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.
+
+from: app-builtin-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-metadata-report-local-xml
+  main_class: org.apache.dubbo.samples.metadatareport.local.xml.MetadataLocalXmlProvider
+  zookeeper_port: 2181
+  dubbo_port: 20880
+
diff --git a/dubbo-samples-metrics/case-configuration.yml b/dubbo-samples-metrics/case-configuration.yml
new file mode 100644
index 0000000..bd9c7f4
--- /dev/null
+++ b/dubbo-samples-metrics/case-configuration.yml
@@ -0,0 +1,24 @@
+# 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.
+
+from: app-builtin-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-metrics
+  main_class: org.apache.dubbo.samples.metrics.MetricsProvider
+  zookeeper_port: 2181
+  dubbo_port: 20880
+
diff --git a/dubbo-samples-notify/case-configuration.yml b/dubbo-samples-notify/case-configuration.yml
new file mode 100644
index 0000000..5e426db
--- /dev/null
+++ b/dubbo-samples-notify/case-configuration.yml
@@ -0,0 +1,24 @@
+# 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.
+
+from: app-builtin-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-notify
+  main_class: org.apache.dubbo.samples.notify.NotifyProvider
+  zookeeper_port: 2181
+  dubbo_port: 20880
+
diff --git a/dubbo-samples-serialization/dubbo-samples-serialization-java/case-configuration.yml b/dubbo-samples-serialization/dubbo-samples-serialization-java/case-configuration.yml
new file mode 100644
index 0000000..04fd717
--- /dev/null
+++ b/dubbo-samples-serialization/dubbo-samples-serialization-java/case-configuration.yml
@@ -0,0 +1,24 @@
+# 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.
+
+from: app-builtin-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-serialization-java
+  main_class: org.apache.dubbo.samples.serialization.DubboProvider
+  zookeeper_port: 2181
+  dubbo_port: 20880
+
diff --git a/dubbo-samples-simplified-registry/dubbo-samples-simplified-registry-annotation/case-configuration.yml b/dubbo-samples-simplified-registry/dubbo-samples-simplified-registry-annotation/case-configuration.yml
new file mode 100644
index 0000000..6391ff1
--- /dev/null
+++ b/dubbo-samples-simplified-registry/dubbo-samples-simplified-registry-annotation/case-configuration.yml
@@ -0,0 +1,24 @@
+# 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.
+
+from: app-builtin-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-simplified-registry-annotation
+  main_class: org.apache.dubbo.samples.simplified.annotation.SimpleRegistryAnnotationProvider
+  zookeeper_port: 2181
+  dubbo_port: 20880
+
diff --git a/dubbo-samples-simplified-registry/dubbo-samples-simplified-registry-nosimple/case-configuration.yml b/dubbo-samples-simplified-registry/dubbo-samples-simplified-registry-nosimple/case-configuration.yml
new file mode 100644
index 0000000..17b3a6e
--- /dev/null
+++ b/dubbo-samples-simplified-registry/dubbo-samples-simplified-registry-nosimple/case-configuration.yml
@@ -0,0 +1,24 @@
+# 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.
+
+from: app-builtin-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-simplified-registry-nosimple
+  main_class: org.apache.dubbo.samples.simplified.registry.nosimple.NoSimpleRegistryProvider
+  zookeeper_port: 2181
+  dubbo_port: 20880
+
diff --git a/dubbo-samples-simplified-registry/dubbo-samples-simplified-registry-properties/case-configuration.yml b/dubbo-samples-simplified-registry/dubbo-samples-simplified-registry-properties/case-configuration.yml
new file mode 100644
index 0000000..1686b0c
--- /dev/null
+++ b/dubbo-samples-simplified-registry/dubbo-samples-simplified-registry-properties/case-configuration.yml
@@ -0,0 +1,24 @@
+# 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.
+
+from: app-builtin-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-simplified-registry-properties
+  main_class: org.apache.dubbo.samples.simplified.registry.properties.SimpleRegistryPropertiesProvider
+  zookeeper_port: 2181
+  dubbo_port: 20880
+
diff --git a/dubbo-samples-simplified-registry/dubbo-samples-simplified-registry-properties/pom.xml b/dubbo-samples-simplified-registry/dubbo-samples-simplified-registry-properties/pom.xml
index d094a8a..7887573 100644
--- a/dubbo-samples-simplified-registry/dubbo-samples-simplified-registry-properties/pom.xml
+++ b/dubbo-samples-simplified-registry/dubbo-samples-simplified-registry-properties/pom.xml
@@ -43,8 +43,7 @@
         <java-image.name>openjdk:8</java-image.name>
         <dubbo.port>20880</dubbo.port>
         <zookeeper.port>2181</zookeeper.port>
-        <main-class>org.apache.dubbo.samples.simplified.registry.properties.SimpleRegistryPropertiesProvider
-        </main-class>
+        <main-class>org.apache.dubbo.samples.simplified.registry.properties.SimpleRegistryPropertiesProvider</main-class>
     </properties>
 
     <dependencies>
diff --git a/dubbo-samples-simplified-registry/dubbo-samples-simplified-registry-xml/case-configuration.yml b/dubbo-samples-simplified-registry/dubbo-samples-simplified-registry-xml/case-configuration.yml
new file mode 100644
index 0000000..8a54af1
--- /dev/null
+++ b/dubbo-samples-simplified-registry/dubbo-samples-simplified-registry-xml/case-configuration.yml
@@ -0,0 +1,24 @@
+# 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.
+
+from: app-builtin-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-simplified-registry-xml
+  main_class: org.apache.dubbo.samples.simplified.registry.xml.SimpleRegistryXmlProvider
+  zookeeper_port: 2181
+  dubbo_port: 20880
+
diff --git a/dubbo-samples-spi-compatible/case-configuration.yml b/dubbo-samples-spi-compatible/case-configuration.yml
new file mode 100644
index 0000000..11b34bd
--- /dev/null
+++ b/dubbo-samples-spi-compatible/case-configuration.yml
@@ -0,0 +1,24 @@
+# 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.
+
+from: app-builtin-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-spi-compatible
+  main_class: org.apache.dubbo.samples.basic.SpiCompatibleProvider
+  zookeeper_port: 2181
+  dubbo_port: 20880
+
diff --git a/dubbo-samples-spi-compatible/src/main/resources/spring/dubbo-demo-provider.xml b/dubbo-samples-spi-compatible/src/main/resources/spring/dubbo-demo-provider.xml
index 5aa7c46..5718f81 100644
--- a/dubbo-samples-spi-compatible/src/main/resources/spring/dubbo-demo-provider.xml
+++ b/dubbo-samples-spi-compatible/src/main/resources/spring/dubbo-demo-provider.xml
@@ -25,7 +25,7 @@
 
     <dubbo:application name="compatibility-provider"/>
 
-    <dubbo:registry protocol="compatible" address="127.0.0.1:2181"/>
+    <dubbo:registry protocol="compatible" address="${zookeeper.address:127.0.0.1}:2181"/>
 
     <dubbo:protocol name="compatible" port="20880"/>
 
diff --git a/dubbo-samples-spring-boot-hystrix/case-configuration.yml b/dubbo-samples-spring-boot-hystrix/case-configuration.yml
new file mode 100644
index 0000000..8a1f01e
--- /dev/null
+++ b/dubbo-samples-spring-boot-hystrix/case-configuration.yml
@@ -0,0 +1,24 @@
+# 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.
+
+from: app-builtin-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-spring-boot-hystrix
+  main_class: org.apache.dubbo.spring.boot.provider.ProviderApplication
+  zookeeper_port: 2181
+  dubbo_port: 20880
+
diff --git a/dubbo-samples-stub/case-configuration.yml b/dubbo-samples-stub/case-configuration.yml
new file mode 100644
index 0000000..4beb313
--- /dev/null
+++ b/dubbo-samples-stub/case-configuration.yml
@@ -0,0 +1,24 @@
+# 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.
+
+from: app-builtin-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-stub
+  main_class: org.apache.dubbo.samples.stub.StubProvider
+  zookeeper_port: 2181
+  dubbo_port: 20880
+
diff --git a/dubbo-samples-switch-serialization-thread/case-configuration.yml b/dubbo-samples-switch-serialization-thread/case-configuration.yml
new file mode 100644
index 0000000..f798350
--- /dev/null
+++ b/dubbo-samples-switch-serialization-thread/case-configuration.yml
@@ -0,0 +1,24 @@
+# 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.
+
+from: app-builtin-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-switch-serialization-thread
+  main_class: org.apache.dubbo.samples.serialization.change.thread.SerializationSwitchThreadProvider
+  zookeeper_port: 2181
+  dubbo_port: 20880
+
diff --git a/test/README.md b/test/README.md
new file mode 100644
index 0000000..a37cd98
--- /dev/null
+++ b/test/README.md
@@ -0,0 +1,278 @@
+## Dubbo Integration Test
+
+### Test steps
+
+Follow the 3 steps below:
+
+#### Step 1 - Build test image
+
+Please install `docker` and `docker-compose` first, then build the test image.
+
+```
+cd dubbo-samples/test
+./build-test-image.sh
+```
+Rebuild the image after modify any file of the `dubbo-test-runner` project.
+
+#### Step 2 - Add case configuration
+Add a `case-configuration.yml` to the tested project, for examples:
+
+```
+from: app-builtin-zookeeper.yml
+props:
+  project_name: dubbo-samples-annotation
+  main_class: org.apache.dubbo.samples.annotation.AnnotationProviderBootstrap
+  zookeeper_port: 2181
+  dubbo_port: 20880
+
+```
+
+Some example projects: 
+
+ * [dubbo-samples-annotation](../dubbo-samples-annotation/case-configuration.yml) : A simple provider service with builtin zookeeper.
+ * [dubbo-samples-chain](../dubbo-samples-chain/case-configuration.yml) : A multiple services with external zookeeper.
+
+#### Step 3 - Generate and test scenario
+
+```
+cd dubbo-samples/test
+./run-tests.sh
+```
+
+### Builtin parent configuration
+
+Use `from` directive can import parent configuration, merge into current configuration.
+
+Builtin parent configurations is in directory: `test/dubbo-scenario-builder/src/main/resources/configs`. 
+
+* `app-builtin-zookeeper.yml` 
+  
+  Applicable scenario: single dubbo provider application with builtin zookeeper
+and test case.
+
+* `app-external-zookeeper.yml`
+  Applicable scenario: single dubbo provider application, external zookeeper
+and test case.
+
+
+**Usages:** 
+
+* `dubbo-samples-annotation` configuration:
+
+```
+from: app-builtin-zookeeper.yml
+props:
+  project_name: dubbo-samples-annotation
+  main_class: org.apache.dubbo.samples.annotation.AnnotationProviderBootstrap
+  zookeeper_port: 2181
+  dubbo_port: 20880
+```
+
+`project_name` : project name of dubbo sample
+
+`main_class` : main class of dubbo provider application
+
+`dubbo_port` : dubbo provider service port
+
+`zookeeper_port` : builtin zookeeper port
+
+
+* `dubbo-samples-api` configuration:
+
+```
+from: app-external-zookeeper.yml
+
+props:
+  project_name: dubbo-samples-api
+  main_class: org.apache.dubbo.samples.provider.Application
+  dubbo_port: 20880
+  zookeeper_version: latest
+```
+
+`zookeeper_version` : external zookeeper version
+
+External zookeeper service is a fixed port 2181, cause cannot change port unless expose it.
+
+
+### Case configuration details
+
+Top level directives:
+
+| Name | Description |
+| ---- | ----------- |
+| `from` |  load parent case configuration, cover its `props` with current configration |
+| `props` | Internal properties, automatically replace the current configuration or inherited configuration variables when parsing |
+| `services` | A set of app/test services or external services |
+
+
+Directives for dubbo service:
+
+| Name | Description | Defaults |
+| ---- | ----------- | -------- |
+| `type` | service type: `app` - dubbo provider application; `test`- dubbo testcase |  |
+| `basedir` | project basedir of app/test service | `.` (current dir) |
+| `mainClass` | Main class of provider service, only for app service. |  |
+| `systemProps` | set Java app system properties, automatically converted to jvm flags: `-Dname=value` |  |
+| `jvmFlags` | multiple jvm flags, automatically join as one string  | |
+| `waitPortsBeforeRun` | Wait ports before run app/test |  |
+| `tests` | Matching test patterns, only for test service. |  |
+| | | |
+
+
+Service directives compatible with `docker-compose`:
+
+| Name | Description |  Defaults |
+| ---- | ----------- | -------- | 
+| `image` | docker image name of container | app/test service is implicitly set to `dubbo/sample-test`. |
+| `environment` |  |
+| `depends_on` | | 
+| `hostname` | container hostname | app/test service is implicitly set to service id |
+| `volumes` | container mount points | app/test service automatically mounts directory: `$basedir/target:/usr/local/dubbo/app`  |
+| `links` | |
+| `expose` | expose ports between containers |
+| `ports` | mapping ports to host os |
+| `entrypoint` | |
+| `healthcheck` | |
+
+
+#### Configuration example
+
+`app-external-zookeeper.yml` includes :
+
+* External zookeeper 
+* Dubbo provider application
+* Dubbo testcase
+
+**Note:**  
+
+* app/test service connect to zookeeper by system property `zookeeper.address` and `zookeeper.port`
+
+* wait zookeeper port before run dubbo provider application
+
+* wait zookeeper port and dubbo provider service port before run dubbo test
+
+```
+props:
+#  project_name: dubbo-samples-xxx
+#  main_class: org.apache.dubbo.samples.xxx.XxxProviderBootstrap
+  dubbo_port: 20880
+  zookeeper_version: latest
+
+services:
+  zookeeper:
+    image: zookeeper:${zookeeper_version}
+
+  ${project_name}:
+    type: app
+    basedir: .
+    mainClass: ${main_class}
+    systemProps:
+      - zookeeper.address=zookeeper
+      - zookeeper.port=2181
+    waitPortsBeforeRun:
+      - zookeeper:2181
+
+  ${project_name}-test:
+    type: test
+    basedir: .
+    tests:
+      - "**/*IT.class"
+    systemProps:
+      - zookeeper.address=zookeeper
+      - zookeeper.port=2181
+    waitPortsBeforeRun:
+      - zookeeper:2181
+      - ${project_name}:${dubbo_port}
+    depends_on:
+      - ${project_name}
+
+```
+
+
+### Some tricks
+
+#### Scenario
+
+A scenario is a complete test environment, including docker-compose.yml, scenario.sh, app classes, test classes and dependency jars. You can test the scenario separately, just run scenario.sh.
+
+* Scenario home
+
+ `${scenario_home}` default location is: `${project.basedir/target}`.
+ 
+ App / test service automatically mounts directory: `${scenario_home}:/usr/local/dubbo/app`
+
+
+* Scenario running timeout
+ 
+ Default running timeout is 90s. Some test cases require more time, you can modify it in the following way. 
+ 
+ (1) Change timeout in `case-configuration.yml`:
+ 
+   ```
+   timeout: 120
+   ```
+   
+ (2) Change timeout in command line
+ 
+ ```
+  timeout=120 bash ${scenario_home}/scenario.sh 
+ ```
+
+#### Logs
+
+* Container log
+  Container log location is: `${scenario_home}/logs/${serviceName}.log`, include of dubbo app/test 
+  service and external service. 
+
+* Scenario log  
+  Script `scenario.sh` log location: `${scenario_home}/logs/scenario.log`.
+  
+* Scenario builder log
+  Scenario builder log location: `$scenario_home/logs/scenario-builder.log`
+
+#### Test reports
+
+The test reports is in directory: `${scenario_home}/test-reports`
+
+#### Fork run
+
+The fork count is 2 by default, you can modify it by setting env `FORK_COUNT=n`. 
+Increasing the fork count may cause the container to run very slowly, 
+please set it according to the CPU/IO performance of the operating system.
+
+```
+FORK_COUNT=2 bash run-tests.sh
+```
+
+#### Fail-fast
+
+Run tests in fail-fast mode, abort testing when any case is failed. 
+It's useful when running tests in CI server, such as: Jenkins, Github actions.
+Default value: `FAIL_FAST=0`.
+
+```
+FAIL_FAST=1 bash run-tests.sh
+```
+
+#### Show error detail
+
+Show log detail of failed testcase, including app log and test container log. 
+It's useful when running tests in CI server, such as: Jenkins, Github actions.
+Default value: `SHOW_ERROR_DETAIL=0`.
+
+```
+SHOW_ERROR_DETAIL=1 bash run-tests.sh
+```
+
+### Develop
+
+#### dubbo-scenario-builder
+Build dubbo test scenario, generating docker/docker-compose script files and 
+start script files. 
+
+
+#### dubbo-test-runner
+A Junit test runner, execute a set of testcases, replacement for maven-safefail-plugin. 
+Also include files for `dubbo-test-image`.
+
+
diff --git a/test/build-test-image.sh b/test/build-test-image.sh
new file mode 100755
index 0000000..2020d7d
--- /dev/null
+++ b/test/build-test-image.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+# Note: modify follow comment when need refresh github actions docker layer caching
+# dubbo/sample-test updated: 2020.12.25
+
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+
+$DIR/dubbo-test-runner/build.sh
\ No newline at end of file
diff --git a/test/clean-damaged-image.sh b/test/clean-damaged-image.sh
new file mode 100755
index 0000000..8903c90
--- /dev/null
+++ b/test/clean-damaged-image.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+
+echo "Killing dubbo containers .."
+docker ps -a | grep dubbo | awk '{ print $1}' | xargs -I {} docker kill {}
+
+echo "Removing dubbo containers .."
+docker ps -a | grep dubbo | awk '{ print $1}' | xargs -I {} docker rm {}
+
+#echo "Removing dubbo images .."
+#docker images | grep dubbo | awk '{ print $3}' | xargs -I {} docker rmi {}
+
+echo "Removing damaged images .."
+docker image prune -f
diff --git a/test/dubbo-scenario-builder/pom.xml b/test/dubbo-scenario-builder/pom.xml
new file mode 100644
index 0000000..eaf7c87
--- /dev/null
+++ b/test/dubbo-scenario-builder/pom.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>dubbo-test</artifactId>
+        <groupId>org.apache.dubbo</groupId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>dubbo-scenario-builder</artifactId>
+
+    <properties>
+        <snakeyaml.version>1.24</snakeyaml.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.12</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <version>3.0.0</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.freemarker</groupId>
+            <artifactId>freemarker</artifactId>
+            <version>2.3.28</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.yaml</groupId>
+            <artifactId>snakeyaml</artifactId>
+            <version>${snakeyaml.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.11</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <version>1.7.25</version>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>log4j-over-slf4j</artifactId>
+            <version>1.7.25</version>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+            <version>1.2.3</version>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <version>3.3.0</version>
+                <configuration>
+                    <descriptorRefs>
+                        <descriptorRef>jar-with-dependencies</descriptorRef>
+                    </descriptorRefs>
+                    <archive>
+                        <manifest>
+                            <mainClass>org.apache.dubbo.scenario.builder.ScenarioBuilderMain</mainClass>
+                        </manifest>
+                    </archive>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>make-assembly</id> <!-- this is used for inheritance merges -->
+                        <phase>package</phase> <!-- bind to the packaging phase -->
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
\ No newline at end of file
diff --git a/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/AbstractRunningGenerator.java b/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/AbstractRunningGenerator.java
new file mode 100644
index 0000000..4ed7101
--- /dev/null
+++ b/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/AbstractRunningGenerator.java
@@ -0,0 +1,68 @@
+/*
+ * 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.dubbo.scenario.builder;
+
+import freemarker.template.Configuration;
+import freemarker.template.TemplateExceptionHandler;
+import org.apache.dubbo.scenario.builder.exception.GenerateFailedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.lang.invoke.MethodHandles;
+import java.util.Map;
+
+public abstract class AbstractRunningGenerator implements ScenarioRunningScriptGenerator {
+    private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+    protected final Configuration cfg;
+
+    protected AbstractRunningGenerator() {
+        cfg = new Configuration(Configuration.VERSION_2_3_28);
+        try {
+            cfg.setClassLoaderForTemplateLoading(this.getClass().getClassLoader(), "/");
+            cfg.setDefaultEncoding("UTF-8");
+            cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
+            cfg.setLogTemplateExceptions(false);
+            cfg.setWrapUncheckedExceptions(true);
+            cfg.setNumberFormat("computer");
+        } catch (Exception e) {
+            // never to do this
+        }
+    }
+
+    @Override
+    public final void generate(IConfiguration configuration) throws GenerateFailedException {
+        generateAdditionFiles(configuration);
+
+        final Map<String, Object> root = configuration.toMap();
+        root.put("running_script", runningScript(configuration));
+
+        String scriptPath = configuration.outputDir() + File.separator + "scenario.sh";
+        try (FileWriter writer = new FileWriter(new File(scriptPath))) {
+            cfg.getTemplate("scenario.sh")
+                    .process(root, writer);
+        } catch (Exception e) {
+            LOGGER.error("Failed to write scenario.sh", e);
+        }
+    }
+
+    public abstract void generateAdditionFiles(IConfiguration configuration) throws GenerateFailedException;
+
+    public abstract String runningScript(IConfiguration configuration);
+}
diff --git a/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/ConfigurationImpl.java b/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/ConfigurationImpl.java
new file mode 100644
index 0000000..c0a2e6e
--- /dev/null
+++ b/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/ConfigurationImpl.java
@@ -0,0 +1,547 @@
+/*
+ * 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.dubbo.scenario.builder;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.dubbo.scenario.builder.exception.ConfigureFileNotFoundException;
+import org.apache.dubbo.scenario.builder.vo.CaseConfiguration;
+import org.apache.dubbo.scenario.builder.vo.DockerService;
+import org.apache.dubbo.scenario.builder.vo.ServiceComponent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.yaml.snakeyaml.Yaml;
+
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class ConfigurationImpl implements IConfiguration {
+    public static final String SAMPLE_TEST_IMAGE = "dubbo/sample-test";
+    public static final String DUBBO_APP_DIR = "/usr/local/dubbo/app";
+    public static final String DUBBO_LOG_DIR = "/usr/local/dubbo/logs";
+    public static final String ENV_SERVICE_NAME = "SERVICE_NAME";
+    public static final String ENV_SERVICE_TYPE = "SERVICE_TYPE";
+    public static final String ENV_APP_MAIN_CLASS = "APP_MAIN_CLASS";
+    public static final String ENV_WAIT_PORTS_BEFORE_RUN = "WAIT_PORTS_BEFORE_RUN";
+    public static final String ENV_CHECK_PORTS_AFTER_RUN = "CHECK_PORTS_AFTER_RUN";
+    public static final String ENV_CHECK_LOG = "CHECK_LOG";
+    public static final String ENV_CHECK_TIMEOUT = "CHECK_TIMEOUT";
+    public static final String ENV_TEST_PATTERNS = "TEST_PATTERNS";
+    public static final String ENV_JAVA_OPTS = "JAVA_OPTS";
+    public static final String ENV_SCENARIO_HOME = "SCENARIO_HOME";
+
+    private static final Logger logger = LoggerFactory.getLogger(ConfigurationImpl.class);
+    private final CaseConfiguration configuration;
+    private String scenarioHome;
+    private String configBasedir;
+    private String scenarioName;
+    private final String scenarioLogDir;
+    private int scenarioTimeout = 90;
+    private int javaDebugPort=20660;
+    private int debugTimeout=36000;
+    private String debugSuspend="y";
+
+    public ConfigurationImpl() throws IOException, ConfigureFileNotFoundException {
+        String configureFile = System.getProperty("configure.file");
+        if (StringUtils.isBlank(configureFile)) {
+            throw new ConfigureFileNotFoundException();
+        }
+        this.configBasedir = new File(configureFile).getParentFile().getCanonicalPath();
+
+        //set default scenarioHome dir to ${configBasedir}/target
+        this.scenarioHome = System.getProperty("scenario.home");
+        if (StringUtils.isBlank(scenarioHome)) {
+            scenarioHome = configBasedir + "/target";
+        }
+        scenarioLogDir = new File(scenarioHome, "logs").getCanonicalPath();
+
+        //set default scenarioName
+        scenarioName = System.getProperty("scenario.name");
+        if (StringUtils.isBlank(scenarioName)) {
+            scenarioName = new File(configBasedir).getName();
+        }
+
+        this.configuration = loadCaseConfiguration(configureFile);
+        if (this.configuration.getTimeout() > 0) {
+            scenarioTimeout = this.configuration.getTimeout();
+        }
+        String timeout = System.getProperty("timeout");
+        if (StringUtils.isNotBlank(timeout)) {
+            scenarioTimeout = Integer.parseInt(timeout);
+        }
+        if (isDebug()) {
+            scenarioTimeout=debugTimeout;
+            debugSuspend=System.getProperty("debug.suspend", debugSuspend);
+        }
+    }
+
+    private CaseConfiguration loadCaseConfiguration(String configureFile) throws IOException {
+        // read 'props'
+        String configYaml = readFully(configureFile);
+        CaseConfiguration tmpConfiguration = parseConfiguration(configYaml);
+        Map<String, String> props = tmpConfiguration.getProps();
+
+        // process 'from', load parent config
+        CaseConfiguration parentConfiguration = null;
+        if (StringUtils.isNotBlank(tmpConfiguration.getFrom())) {
+            String parentConfigYaml = loadParentConfigYaml(tmpConfiguration);
+            CaseConfiguration tmpParentConfiguration = parseConfiguration(parentConfigYaml);
+
+            //merge props, overwrite parent props
+            Map<String, String> newProps = new HashMap<>(tmpParentConfiguration.getProps());
+            newProps.putAll(props);
+            props = newProps;
+
+            // replace variables '${...}'
+            String newParentConfigYaml = replaceHolders(parentConfigYaml, props);
+            parentConfiguration = parseConfiguration(newParentConfigYaml);
+        }
+
+        // replace variables '${...}'
+        String newConfigYaml = replaceHolders(configYaml, props);
+        CaseConfiguration caseConfiguration = parseConfiguration(newConfigYaml);
+
+        //merge globalSystemProps, overwrite parent
+        if (parentConfiguration != null) {
+            List<String> systemProps = mergeSystemProps(parentConfiguration.getSystemProps(), caseConfiguration.getSystemProps());
+            caseConfiguration.setSystemProps(systemProps);
+        }
+
+        //merge services
+        if (parentConfiguration != null && parentConfiguration.getServices() != null) {
+            Map<String, ServiceComponent> newServices = new LinkedHashMap<>(parentConfiguration.getServices());
+            if (caseConfiguration.getServices() != null) {
+                newServices.putAll(caseConfiguration.getServices());
+            }
+            caseConfiguration.setServices(newServices);
+        }
+
+        fillupServices(caseConfiguration);
+        return caseConfiguration;
+    }
+
+    private List<String> mergeSystemProps(List<String> parentSystemProps, List<String> childSystemProps) {
+        List<String> newSystemProps = new ArrayList<>(parentSystemProps != null ? parentSystemProps : Collections.emptyList());
+        if (childSystemProps != null) {
+            childSystemProps.forEach(entry -> {
+                String[] strs = entry.split("=");
+                addOrReplaceKVEntry(newSystemProps, strs[0].trim(), strs.length > 1 ? strs[1].trim() : "");
+            });
+        }
+        return newSystemProps;
+    }
+
+    private String loadParentConfigYaml(CaseConfiguration caseConfiguration) throws IOException {
+        try {
+            String file = "configs/" + caseConfiguration.getFrom();
+            InputStream inputStream = CaseConfiguration.class.getClassLoader().getResourceAsStream(file);
+            return readFully(inputStream);
+        } catch (Exception e) {
+            logger.error("load parent config failed: " + caseConfiguration.getFrom(), e);
+            throw new IOException("load parent config failed: " + caseConfiguration.getFrom(), e);
+        }
+    }
+
+    private CaseConfiguration parseConfiguration(String configYaml) {
+        return new Yaml().loadAs(configYaml, CaseConfiguration.class);
+    }
+
+    private void fillupServices(CaseConfiguration caseConfiguration) throws IOException {
+        List<String> caseSystemProps = caseConfiguration.getSystemProps();
+        for (Map.Entry<String, ServiceComponent> entry : caseConfiguration.getServices().entrySet()) {
+            String serviceName = entry.getKey();
+            ServiceComponent service = entry.getValue();
+            String type = service.getType();
+            if (isAppService(type)) {
+                service.setImage(SAMPLE_TEST_IMAGE);
+                service.setBasedir(toAbsolutePath(service.getBasedir()));
+                if (service.getVolumes() == null) {
+                    service.setVolumes(new ArrayList<>());
+                }
+                //mount ${project.basedir}/target : DUBBO_APP_DIR
+                String targetPath = new File(service.getBasedir(), "target").getCanonicalPath();
+                service.getVolumes().add(targetPath + ":" + DUBBO_APP_DIR);
+
+                //mount ${scenario_home}/logs : DUBBO_LOG_DIR
+                service.getVolumes().add(scenarioLogDir + ":" + DUBBO_LOG_DIR);
+
+                if (service.getEnvironment() == null) {
+                    service.setEnvironment(new ArrayList<>());
+                }
+                // set service name
+                setEnv(service, ENV_SERVICE_NAME, serviceName);
+
+                //set wait ports
+                if (isNotEmpty(service.getWaitPortsBeforeRun())) {
+                    String str = convertAddrPortsToString(service.getWaitPortsBeforeRun());
+                    setEnv(service, ENV_WAIT_PORTS_BEFORE_RUN, str);
+                }
+
+                //set check timeout
+                if (isDebug()) {
+                    service.setCheckTimeout(debugTimeout);
+
+                    //set java remote debug opts
+                    //-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
+                    int debugPort = nextDebugPort();
+                    String debugOpts=String.format("-agentlib:jdwp=transport=dt_socket,server=y,suspend=%s,address=%s", debugSuspend, debugPort);
+                    appendEnv(service, ENV_JAVA_OPTS, debugOpts);
+
+                    //mapping debug port
+                    if (service.getPorts() == null) {
+                        service.setPorts(new ArrayList<>());
+                    }
+                    service.getPorts().add(debugPort + ":" + debugPort);
+                }
+                if (service.getCheckTimeout() > 0) {
+                    setEnv(service, ENV_CHECK_TIMEOUT, service.getCheckTimeout()+"");
+                }
+
+                if ("app".equals(type)) {
+                    String mainClass = service.getMainClass();
+                    if (StringUtils.isBlank(mainClass)) {
+                        throw new RuntimeException("Missing 'mainClass' for app service [" + serviceName + "]");
+                    }
+                    //set SERVICE_TYPE
+                    setEnv(service, ENV_SERVICE_TYPE, type);
+                    //set mainClass env
+                    setEnv(service, ENV_APP_MAIN_CLASS, mainClass);
+                    //set SCENARIO_HOME
+                    setEnv(service, ENV_SCENARIO_HOME, scenarioHome);
+
+                    //set check_log env
+                    if (StringUtils.isNotBlank(service.getCheckLog())) {
+                        setEnv(service, ENV_CHECK_LOG, service.getCheckLog());
+                    }
+
+                    //set check ports
+                    if (isNotEmpty(service.getCheckPortsAfterRun())) {
+                        String str = convertAddrPortsToString(service.getCheckPortsAfterRun());
+                        setEnv(service, ENV_CHECK_PORTS_AFTER_RUN, str);
+                    }
+                } else if ("test".equals(type)) {
+                    String mainClass = service.getMainClass();
+                    if (StringUtils.isNotBlank(mainClass)) {
+                        throw new RuntimeException("Illegal attribute 'mainClass' for test service [" + serviceName + "]");
+                    }
+                    //set SERVICE_TYPE
+                    setEnv(service, ENV_SERVICE_TYPE, type);
+
+                    //set TEST_PATTERNS
+                    if (isNotEmpty(service.getTests())) {
+                        String str = StringUtils.join(service.getTests(), ';');
+                        setEnv(service, ENV_TEST_PATTERNS, str);
+                    }
+                } else {
+                    throw new RuntimeException("Illegal service type: " + type);
+                }
+            }
+
+            // set hostname to serviceId if absent
+            if (StringUtils.isBlank(service.getHostname())) {
+                service.setHostname(serviceName);
+            }
+
+            //set jvmFlags
+            if (isNotEmpty(service.getJvmFlags())) {
+                String str = StringUtils.join(service.getJvmFlags(), ' ');
+                appendEnv(service, ENV_JAVA_OPTS, str);
+            }
+
+            //set systemProps
+            List<String> systemProps = mergeSystemProps(caseSystemProps, service.getSystemProps());
+            if (isNotEmpty(systemProps)) {
+                String str = convertSystemPropsToJvmFlags(systemProps);
+                appendEnv(service, ENV_JAVA_OPTS, str);
+            }
+
+        }
+    }
+
+    private void appendEnv(ServiceComponent service, String name, String value) {
+        String prefix = name + "=";
+        List<String> environments = service.getEnvironment();
+        if (environments == null) {
+            environments = new ArrayList<>();
+            service.setEnvironment(environments);
+        }
+        for (int i = 0; i < environments.size(); i++) {
+            String env = environments.get(i);
+            if (env.startsWith(prefix)) {
+                // append to exist env
+                env += " " + value;
+                environments.set(i, env);
+                return;
+            }
+        }
+        environments.add(name + "=" + value);
+    }
+
+    private void setEnv(ServiceComponent service, String name, String value) {
+        List<String> environments = service.getEnvironment();
+        addOrReplaceKVEntry(environments, name, value);
+    }
+
+    private void addOrReplaceKVEntry(List<String> map, String name, String value) {
+        String prefix = name + "=";
+        for (int i = 0; i < map.size(); i++) {
+            String env = map.get(i);
+            if (env.startsWith(prefix)) {
+                //replace old env
+                env = name + "=" + value;
+                map.set(i, env);
+                return;
+            }
+        }
+        map.add(name + "=" + value);
+    }
+
+    //convert systemProp key=value to -Dkey=value
+    private String convertSystemPropsToJvmFlags(List<String> systemProps) {
+        StringBuilder sb = new StringBuilder();
+        for (String propkv : systemProps) {
+            sb.append("-D").append(propkv).append(' ');
+        }
+        return sb.toString();
+    }
+
+    private boolean isNotEmpty(List<String> list) {
+        return list != null && list.size() > 0;
+    }
+
+    private String convertAddrPortsToString(List<String> addrPorts) {
+        StringBuilder sb = new StringBuilder();
+        for (String addrPort : addrPorts) {
+            if (!addrPort.contains(":")) {
+                addrPort = "127.0.0.1" + ":" + addrPort;
+            }
+            sb.append(addrPort).append(";");
+        }
+        return sb.toString();
+    }
+
+    private String toAbsolutePath(String path) throws IOException {
+        File file = new File(path);
+        if (file.isAbsolute()) {
+            return file.getCanonicalPath();
+        }
+        //relative path to basedir of configuration file
+        return new File(configBasedir, path).getCanonicalPath();
+    }
+
+    private boolean isAppService(String type) {
+        if (type == null) {
+            return false;
+        }
+        switch (type) {
+            case "app":
+            case "test":
+                return true;
+        }
+        throw new RuntimeException("Illegal service type: " + type);
+    }
+
+    private String readFully(String file) throws IOException {
+        try (FileInputStream fis = new FileInputStream(file)) {
+            return readFully(fis);
+        }
+    }
+
+    private String readFully(InputStream input) throws IOException {
+        DataInputStream dis = new DataInputStream(input);
+        byte[] bytes = new byte[input.available()];
+        dis.readFully(bytes);
+        return new String(bytes, StandardCharsets.UTF_8);
+    }
+
+    private Pattern pattern = Pattern.compile("\\$\\{(.+?)}");
+
+    private String replaceHolders(String str, Map<String, String> props) {
+        StringBuffer buf = new StringBuffer(str.length());
+        Matcher matcher = pattern.matcher(str);
+        while (matcher.find()) {
+            String var = matcher.group(1);
+            String value = props.get(var);
+            matcher.appendReplacement(buf, value != null ? value : matcher.group());
+        }
+        matcher.appendTail(buf);
+        return buf.toString();
+    }
+
+    @Override
+    public ScenarioRunningScriptGenerator scenarioGenerator() {
+        return new DockerComposeRunningGenerator();
+    }
+
+    @Override
+    public CaseConfiguration caseConfiguration() {
+        return this.configuration;
+    }
+
+    @Override
+    public String scenarioName() {
+        return scenarioName;
+    }
+
+    @Override
+    public String scenarioVersion() {
+        return System.getProperty("scenario.version");
+    }
+
+    @Override
+    public String dockerImageVersion() {
+        return System.getProperty("docker.image.version", "latest");
+    }
+
+    @Override
+    public String dockerNetworkName() {
+        return (scenarioName() + "-" + dockerImageVersion()).toLowerCase();
+    }
+
+    @Override
+    public String dockerContainerName() {
+        return (scenarioName() + "-" + scenarioVersion() + "-" + dockerImageVersion()).toLowerCase();
+    }
+
+    @Override
+    public String scenarioHome() {
+        return this.scenarioHome;
+    }
+
+    @Override
+    public String outputDir() {
+        return scenarioHome;
+    }
+
+    @Override
+    public String jacocoHome() {
+        return System.getProperty("jacoco.home");
+    }
+
+    @Override
+    public String debugMode() {
+        return System.getProperty("debug.mode", "0");
+    }
+
+    private boolean isDebug() {
+        return "1".equals(debugMode());
+    }
+
+    private int nextDebugPort() {
+        return javaDebugPort++;
+    }
+
+    @Override
+    public Map<String, Object> toMap() {
+        CaseConfiguration caseConfiguration = caseConfiguration();
+        final Map<String, Object> root = new HashMap<>();
+
+        root.put("scenario_home", scenarioHome());
+        root.put("scenario_name", scenarioName());
+        root.put("scenario_version", scenarioVersion());
+        root.put("docker_container_name", dockerContainerName());
+        root.put("jacoco_home", jacocoHome());
+        root.put("debug_mode", debugMode());
+        root.put("docker_compose_file", outputDir() + File.separator + "docker-compose.yml");
+        root.put("network_name", dockerNetworkName());
+        root.put("timeout", scenarioTimeout);
+
+        final StringBuilder removeImagesScript = new StringBuilder();
+        List<String> links = new ArrayList<>();
+        if (caseConfiguration.getServices() != null) {
+            caseConfiguration.getServices().forEach((name, service) -> {
+                links.add(service.getHostname());
+                if (service.isRemoveOnExit()) {
+                    removeImagesScript.append("docker rmi ")
+                            .append(service.getImage())
+                            .append(System.lineSeparator());
+                }
+            });
+        }
+        root.put("removeImagesScript", removeImagesScript.toString());
+
+//        add links to test service
+//        caseConfiguration.getServices().forEach((name, service) -> {
+//            if ("test".equals(service.getType())) {
+//                if (service.getLinks() == null) {
+//                    service.setLinks(new ArrayList<>());
+//                }
+//                for (String link : links) {
+//                    if (!StringUtils.equals(link, service.getHostname())) {
+//                        service.getLinks().add(link);
+//                    }
+//                }
+//            }
+//        });
+
+        root.put("services", convertDockerServices(scenarioVersion(), caseConfiguration.getServices()));
+        List<String> testServiceNames = findTestServiceNames(caseConfiguration);
+        root.put("test_service_name", testServiceNames.size() > 0 ? testServiceNames.get(0) : "");
+
+        return root;
+    }
+
+    private List<String> findTestServiceNames(CaseConfiguration caseConfiguration) {
+        List<String> serviceNames = new ArrayList<>();
+        for (Map.Entry<String, ServiceComponent> entry : caseConfiguration.getServices().entrySet()) {
+            ServiceComponent service = entry.getValue();
+            if ("test".equals(service.getType())) {
+                serviceNames.add(entry.getKey());
+            }
+        }
+        return serviceNames;
+    }
+
+    protected List<DockerService> convertDockerServices(final String version,
+                                                        Map<String, ServiceComponent> componentMap) {
+        final ArrayList<DockerService> services = new ArrayList<>();
+        if (componentMap == null) {
+            return services;
+        }
+        componentMap.forEach((name, dependency) -> {
+            DockerService service = new DockerService();
+
+            String imageName = dependency.getImage();
+            service.setName(name);
+            service.setImageName(imageName);
+            service.setHostname(dependency.getHostname());
+            service.setExpose(dependency.getExpose());
+            service.setPorts(dependency.getPorts());
+            service.setDepends_on(dependency.getDepends_on());
+            service.setLinks(dependency.getDepends_on());
+            service.setEntrypoint(dependency.getEntrypoint());
+            service.setHealthcheck(dependency.getHealthcheck());
+            service.setEnvironment(dependency.getEnvironment());
+            service.setVolumes(dependency.getVolumes());
+            service.setRemoveOnExit(dependency.isRemoveOnExit());
+            services.add(service);
+        });
+        return services;
+    }
+}
diff --git a/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/DockerComposeRunningGenerator.java b/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/DockerComposeRunningGenerator.java
new file mode 100644
index 0000000..b7e17a5
--- /dev/null
+++ b/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/DockerComposeRunningGenerator.java
@@ -0,0 +1,75 @@
+/*
+ * 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.dubbo.scenario.builder;
+
+import freemarker.template.Configuration;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateExceptionHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.lang.invoke.MethodHandles;
+import java.util.Map;
+
+public class DockerComposeRunningGenerator extends AbstractRunningGenerator {
+    private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+    protected DockerComposeRunningGenerator() {
+    }
+
+    @Override
+    public void generateAdditionFiles(IConfiguration configuration) {
+        final Map<String, Object> root = configuration.toMap();
+
+        Configuration cfg = new Configuration(Configuration.VERSION_2_3_28);
+        try {
+            cfg.setClassLoaderForTemplateLoading(this.getClass().getClassLoader(), "/");
+            cfg.setDefaultEncoding("UTF-8");
+            cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
+            cfg.setLogTemplateExceptions(false);
+            cfg.setWrapUncheckedExceptions(true);
+            cfg.setNumberFormat("computer");
+        } catch (Exception e) {
+            // never to do this
+        }
+        try {
+            cfg.getTemplate("docker-compose.template")
+                    .process(root, new FileWriter(new File(configuration.outputDir(), "docker-compose.yml")));
+        } catch (TemplateException | IOException e) {
+            LOGGER.error("", e);
+        }
+    }
+
+    @Override
+    public String runningScript(IConfiguration configuration) {
+        final Map<String, Object> root = configuration.toMap();
+
+        StringWriter out = new StringWriter();
+
+        try {
+            cfg.getTemplate("compose-start-script.template").process(root, out);
+        } catch (Exception e) {
+            LOGGER.error("Failed to generate running script.", e);
+        }
+        return out.toString();
+    }
+}
diff --git a/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/IConfiguration.java b/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/IConfiguration.java
new file mode 100644
index 0000000..6b19381
--- /dev/null
+++ b/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/IConfiguration.java
@@ -0,0 +1,51 @@
+/*
+ * 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.dubbo.scenario.builder;
+
+
+import org.apache.dubbo.scenario.builder.vo.CaseConfiguration;
+
+import java.util.Map;
+
+public interface IConfiguration {
+
+    ScenarioRunningScriptGenerator scenarioGenerator();
+
+    CaseConfiguration caseConfiguration();
+
+    String scenarioName();
+
+    String scenarioVersion();
+
+    String dockerContainerName();
+
+    String dockerNetworkName();
+
+    String dockerImageVersion();
+
+    String scenarioHome();
+
+    String outputDir();
+
+    String jacocoHome();
+
+    String debugMode();
+
+    Map<String, Object> toMap();
+
+}
diff --git a/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/ScenarioBuilderMain.java b/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/ScenarioBuilderMain.java
new file mode 100644
index 0000000..62be59b
--- /dev/null
+++ b/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/ScenarioBuilderMain.java
@@ -0,0 +1,28 @@
+/*
+ * 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.dubbo.scenario.builder;
+
+public class ScenarioBuilderMain {
+
+    public static void main(String[] args) throws Exception {
+        IConfiguration configuration = new ConfigurationImpl();
+        configuration.scenarioGenerator().generate(configuration);
+        System.out.println("outputDir: " + configuration.outputDir());
+        System.exit(0);
+    }
+}
diff --git a/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/ScenarioRunningScriptGenerator.java b/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/ScenarioRunningScriptGenerator.java
new file mode 100644
index 0000000..d824e32
--- /dev/null
+++ b/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/ScenarioRunningScriptGenerator.java
@@ -0,0 +1,25 @@
+/*
+ * 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.dubbo.scenario.builder;
+
+
+import org.apache.dubbo.scenario.builder.exception.GenerateFailedException;
+
+public interface ScenarioRunningScriptGenerator {
+    void generate(IConfiguration configuration) throws GenerateFailedException;
+}
diff --git a/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/exception/ConfigureFileNotFoundException.java b/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/exception/ConfigureFileNotFoundException.java
new file mode 100644
index 0000000..156bded
--- /dev/null
+++ b/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/exception/ConfigureFileNotFoundException.java
@@ -0,0 +1,23 @@
+/*
+ * 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.dubbo.scenario.builder.exception;
+
+public class ConfigureFileNotFoundException extends Exception {
+    public ConfigureFileNotFoundException() {
+    }
+}
diff --git a/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/exception/GenerateFailedException.java b/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/exception/GenerateFailedException.java
new file mode 100644
index 0000000..4c141c2
--- /dev/null
+++ b/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/exception/GenerateFailedException.java
@@ -0,0 +1,21 @@
+/*
+ * 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.dubbo.scenario.builder.exception;
+
+public class GenerateFailedException extends Exception {
+}
diff --git a/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/vo/CaseConfiguration.java b/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/vo/CaseConfiguration.java
new file mode 100644
index 0000000..6401dec
--- /dev/null
+++ b/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/vo/CaseConfiguration.java
@@ -0,0 +1,70 @@
+/*
+ * 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.dubbo.scenario.builder.vo;
+
+
+import java.util.List;
+import java.util.Map;
+
+public class CaseConfiguration {
+    private String from;
+    private Map<String, String> props;
+    private List<String> systemProps;
+    private Map<String, ServiceComponent> services;
+    private int timeout;
+
+    public String getFrom() {
+        return from;
+    }
+
+    public void setFrom(String from) {
+        this.from = from;
+    }
+
+    public Map<String, ServiceComponent> getServices() {
+        return services;
+    }
+
+    public void setServices(Map<String, ServiceComponent> services) {
+        this.services = services;
+    }
+
+    public Map<String, String> getProps() {
+        return props;
+    }
+
+    public void setProps(Map<String, String> props) {
+        this.props = props;
+    }
+
+    public List<String> getSystemProps() {
+        return systemProps;
+    }
+
+    public void setSystemProps(List<String> systemProps) {
+        this.systemProps = systemProps;
+    }
+
+    public int getTimeout() {
+        return timeout;
+    }
+
+    public void setTimeout(int timeout) {
+        this.timeout = timeout;
+    }
+}
diff --git a/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/vo/DockerService.java b/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/vo/DockerService.java
new file mode 100644
index 0000000..4737d19
--- /dev/null
+++ b/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/vo/DockerService.java
@@ -0,0 +1,131 @@
+/*
+ * 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.dubbo.scenario.builder.vo;
+
+import java.util.List;
+
+public class DockerService {
+    private String name;
+    private String imageName;
+    private String hostname;
+    private boolean removeOnExit;
+    private List<String> links;
+    private List<String> expose;
+    private List<String> ports;
+    private List<String> entrypoint;
+    private List<String> healthcheck;
+    private List<String> depends_on;
+    private List<String> environment;
+    private List<String> volumes;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getImageName() {
+        return imageName;
+    }
+
+    public void setImageName(String imageName) {
+        this.imageName = imageName;
+    }
+
+    public String getHostname() {
+        return hostname;
+    }
+
+    public void setHostname(String hostname) {
+        this.hostname = hostname;
+    }
+
+    public boolean isRemoveOnExit() {
+        return removeOnExit;
+    }
+
+    public void setRemoveOnExit(boolean removeOnExit) {
+        this.removeOnExit = removeOnExit;
+    }
+
+    public List<String> getLinks() {
+        return links;
+    }
+
+    public void setLinks(List<String> links) {
+        this.links = links;
+    }
+
+    public List<String> getExpose() {
+        return expose;
+    }
+
+    public void setExpose(List<String> expose) {
+        this.expose = expose;
+    }
+
+    public List<String> getEntrypoint() {
+        return entrypoint;
+    }
+
+    public void setEntrypoint(List<String> entrypoint) {
+        this.entrypoint = entrypoint;
+    }
+
+    public List<String> getHealthcheck() {
+        return healthcheck;
+    }
+
+    public void setHealthcheck(List<String> healthcheck) {
+        this.healthcheck = healthcheck;
+    }
+
+    public List<String> getDepends_on() {
+        return depends_on;
+    }
+
+    public void setDepends_on(List<String> depends_on) {
+        this.depends_on = depends_on;
+    }
+
+    public List<String> getEnvironment() {
+        return environment;
+    }
+
+    public void setEnvironment(List<String> environment) {
+        this.environment = environment;
+    }
+
+    public List<String> getVolumes() {
+        return volumes;
+    }
+
+    public void setVolumes(List<String> volumes) {
+        this.volumes = volumes;
+    }
+
+    public List<String> getPorts() {
+        return ports;
+    }
+
+    public void setPorts(List<String> ports) {
+        this.ports = ports;
+    }
+}
diff --git a/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/vo/JavaDebugOption.java b/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/vo/JavaDebugOption.java
new file mode 100644
index 0000000..107aa92
--- /dev/null
+++ b/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/vo/JavaDebugOption.java
@@ -0,0 +1,40 @@
+/*
+ * 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.dubbo.scenario.builder.vo;
+
+public class JavaDebugOption {
+
+    private int port;
+    private String suspend;
+
+    public int getPort() {
+        return port;
+    }
+
+    public void setPort(int port) {
+        this.port = port;
+    }
+
+    public String getSuspend() {
+        return suspend;
+    }
+
+    public void setSuspend(String suspend) {
+        this.suspend = suspend;
+    }
+}
diff --git a/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/vo/ServiceComponent.java b/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/vo/ServiceComponent.java
new file mode 100644
index 0000000..c7fd2d6
--- /dev/null
+++ b/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/vo/ServiceComponent.java
@@ -0,0 +1,242 @@
+/*
+ * 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.dubbo.scenario.builder.vo;
+
+
+import java.util.List;
+
+public class ServiceComponent {
+    private String image;
+    private String hostname;
+    private String version;
+    private boolean removeOnExit = false;
+    private List<String> links;
+    private List<String> expose;
+    private List<String> ports;
+    private List<String> entrypoint;
+    private List<String> environment;
+    private List<String> volumes;
+    private List<String> volumes_from;
+    private List<String> depends_on;
+    private List<String> healthcheck;
+
+    // app attrs
+    private String type;
+    private String basedir;
+    private String mainClass;
+    private List<String> waitPortsBeforeRun;
+    private List<String> checkPortsAfterRun;
+    private String checkLog;
+    private int checkTimeout;
+    private List<String> tests;
+    private List<String> systemProps;
+    private List<String> jvmFlags;
+    private JavaDebugOption javaDebug;
+
+    public String getImage() {
+        return image;
+    }
+
+    public void setImage(String image) {
+        this.image = image;
+    }
+
+    public String getHostname() {
+        return hostname;
+    }
+
+    public void setHostname(String hostname) {
+        this.hostname = hostname;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    public boolean isRemoveOnExit() {
+        return removeOnExit;
+    }
+
+    public void setRemoveOnExit(boolean removeOnExit) {
+        this.removeOnExit = removeOnExit;
+    }
+
+    public void setVersion(String version) {
+        this.version = version;
+    }
+
+    public List<String> getLinks() {
+        return links;
+    }
+
+    public void setLinks(List<String> links) {
+        this.links = links;
+    }
+
+    public List<String> getExpose() {
+        return expose;
+    }
+
+    public void setExpose(List<String> expose) {
+        this.expose = expose;
+    }
+
+    public List<String> getEntrypoint() {
+        return entrypoint;
+    }
+
+    public void setEntrypoint(List<String> entrypoint) {
+        this.entrypoint = entrypoint;
+    }
+
+    public List<String> getEnvironment() {
+        return environment;
+    }
+
+    public void setEnvironment(List<String> environment) {
+        this.environment = environment;
+    }
+
+    public List<String> getDepends_on() {
+        return depends_on;
+    }
+
+    public void setDepends_on(List<String> depends_on) {
+        this.depends_on = depends_on;
+    }
+
+    public List<String> getHealthcheck() {
+        return healthcheck;
+    }
+
+    public void setHealthcheck(List<String> healthcheck) {
+        this.healthcheck = healthcheck;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getBasedir() {
+        return basedir;
+    }
+
+    public void setBasedir(String basedir) {
+        this.basedir = basedir;
+    }
+
+    public String getMainClass() {
+        return mainClass;
+    }
+
+    public void setMainClass(String mainClass) {
+        this.mainClass = mainClass;
+    }
+
+    public List<String> getCheckPortsAfterRun() {
+        return checkPortsAfterRun;
+    }
+
+    public void setCheckPortsAfterRun(List<String> checkPortsAfterRun) {
+        this.checkPortsAfterRun = checkPortsAfterRun;
+    }
+
+    public List<String> getWaitPortsBeforeRun() {
+        return waitPortsBeforeRun;
+    }
+
+    public void setWaitPortsBeforeRun(List<String> waitPortsBeforeRun) {
+        this.waitPortsBeforeRun = waitPortsBeforeRun;
+    }
+
+    public String getCheckLog() {
+        return checkLog;
+    }
+
+    public void setCheckLog(String checkLog) {
+        this.checkLog = checkLog;
+    }
+
+    public int getCheckTimeout() {
+        return checkTimeout;
+    }
+
+    public void setCheckTimeout(int checkTimeout) {
+        this.checkTimeout = checkTimeout;
+    }
+
+    public List<String> getTests() {
+        return tests;
+    }
+
+    public void setTests(List<String> tests) {
+        this.tests = tests;
+    }
+
+    public List<String> getSystemProps() {
+        return systemProps;
+    }
+
+    public void setSystemProps(List<String> systemProps) {
+        this.systemProps = systemProps;
+    }
+
+    public List<String> getJvmFlags() {
+        return jvmFlags;
+    }
+
+    public void setJvmFlags(List<String> jvmFlags) {
+        this.jvmFlags = jvmFlags;
+    }
+
+    public List<String> getVolumes() {
+        return volumes;
+    }
+
+    public void setVolumes(List<String> volumes) {
+        this.volumes = volumes;
+    }
+
+    public List<String> getVolumes_from() {
+        return volumes_from;
+    }
+
+    public void setVolumes_from(List<String> volumes_from) {
+        this.volumes_from = volumes_from;
+    }
+
+    public JavaDebugOption getJavaDebug() {
+        return javaDebug;
+    }
+
+    public void setJavaDebug(JavaDebugOption javaDebug) {
+        this.javaDebug = javaDebug;
+    }
+
+    public List<String> getPorts() {
+        return ports;
+    }
+
+    public void setPorts(List<String> ports) {
+        this.ports = ports;
+    }
+}
diff --git a/test/dubbo-scenario-builder/src/main/resources/compose-start-script.template b/test/dubbo-scenario-builder/src/main/resources/compose-start-script.template
new file mode 100644
index 0000000..6c438b5
--- /dev/null
+++ b/test/dubbo-scenario-builder/src/main/resources/compose-start-script.template
@@ -0,0 +1,18 @@
+<#--
+  ~ 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.
+-->
+
+
diff --git a/test/dubbo-scenario-builder/src/main/resources/configs/app-builtin-zookeeper.yml b/test/dubbo-scenario-builder/src/main/resources/configs/app-builtin-zookeeper.yml
new file mode 100644
index 0000000..436df2f
--- /dev/null
+++ b/test/dubbo-scenario-builder/src/main/resources/configs/app-builtin-zookeeper.yml
@@ -0,0 +1,57 @@
+# 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.
+
+# example:
+
+#from: app-builtin-zookeeper.yml
+#props:
+#  project_name: dubbo-samples-annotation
+#  main_class: org.apache.dubbo.samples.annotation.AnnotationProviderBootstrap
+#  zookeeper_port: 2181
+#  dubbo_port: 20880
+
+
+props:
+#  project_name: dubbo-samples-xxx
+#  main_class: org.apache.dubbo.samples.xxx.XxxProviderBootstrap
+  zookeeper_port: 2181
+  dubbo_port: 20880
+
+services:
+  ${project_name}:
+    type: app
+    basedir: .
+    mainClass: ${main_class}
+#    checkPortsAfterRun:
+#      - ${zookeeper_port}
+#      - ${dubbo_port}
+#    checkLog: "dubbo service started"
+
+  ${project_name}-test:
+    type: test
+    basedir: .
+    tests:
+      - "**/*IT.class"
+    systemProps:
+      - zookeeper.address=${project_name}
+      - zookeeper.port=${zookeeper_port}
+      - dubbo.address=${project_name}
+      - dubbo.port=${dubbo_port}
+    waitPortsBeforeRun:
+      - ${project_name}:${zookeeper_port}
+      - ${project_name}:${dubbo_port}
+    depends_on:
+      - ${project_name}
diff --git a/test/dubbo-scenario-builder/src/main/resources/configs/app-external-zookeeper.yml b/test/dubbo-scenario-builder/src/main/resources/configs/app-external-zookeeper.yml
new file mode 100644
index 0000000..6031fe2
--- /dev/null
+++ b/test/dubbo-scenario-builder/src/main/resources/configs/app-external-zookeeper.yml
@@ -0,0 +1,64 @@
+# 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.
+
+# example:
+
+#from: app-external-zookeeper.yml
+#props:
+#  project_name: dubbo-samples-annotation
+#  main_class: org.apache.dubbo.samples.annotation.AnnotationProviderBootstrap
+#  dubbo_port: 20880
+#  zookeeper_version: latest
+
+
+props:
+#  project_name: dubbo-samples-xxx
+#  main_class: org.apache.dubbo.samples.xxx.XxxProviderBootstrap
+  dubbo_port: 20880
+  zookeeper_version: latest
+
+services:
+  zookeeper:
+    image: zookeeper:${zookeeper_version}
+
+  ${project_name}:
+    type: app
+    basedir: .
+    mainClass: ${main_class}
+    systemProps:
+      - zookeeper.address=zookeeper
+      - zookeeper.port=2181
+    waitPortsBeforeRun:
+      - zookeeper:2181
+#    checkPortsAfterRun:
+#      - ${dubbo_port}
+#    checkLog: "dubbo service started"
+
+  ${project_name}-test:
+    type: test
+    basedir: .
+    tests:
+      - "**/*IT.class"
+    systemProps:
+      - zookeeper.address=zookeeper
+      - zookeeper.port=2181
+      - dubbo.address=${project_name}
+      - dubbo.port=${dubbo_port}
+    waitPortsBeforeRun:
+      - zookeeper:2181
+      - ${project_name}:${dubbo_port}
+    depends_on:
+      - ${project_name}
diff --git a/test/dubbo-scenario-builder/src/main/resources/docker-compose.template b/test/dubbo-scenario-builder/src/main/resources/docker-compose.template
new file mode 100644
index 0000000..1211966
--- /dev/null
+++ b/test/dubbo-scenario-builder/src/main/resources/docker-compose.template
@@ -0,0 +1,88 @@
+<#--
+  ~ 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.
+-->
+version: '2.1'
+
+networks:
+   default:
+     name: ${network_name}
+
+services:
+<#list services as service>
+    ${service.name}:
+        image: ${service.imageName}
+        hostname: ${service.hostname}
+        <#if service.volumes??>
+        volumes:
+        <#list service.volumes as volume>
+            - ${volume}
+        </#list>
+        </#if>
+        <#if service.volumes_from??>
+        volumes_from:
+        <#list service.volumes_from as volume>
+            - ${volume}
+        </#list>
+        </#if>
+        <#if service.environment??>
+        environment:
+        <#list service.environment as environment>
+            - ${environment}
+        </#list>
+        </#if>
+        <#if service.expose??>
+        expose:
+        <#list service.expose as expose>
+            - ${expose}
+        </#list>
+        </#if>
+        <#if service.ports??>
+        ports:
+        <#list service.ports as port>
+            - ${port}
+        </#list>
+        </#if>
+        <#if service.startScript??>
+        command:
+        <#list service.startScript as startScript>
+            - ${startScript}
+        </#list>
+        </#if>
+        <#if service.depends_on??>
+        depends_on:
+        <#list service.depends_on as item>
+            - ${item}
+        </#list>
+        </#if>
+        <#if service.entrypoint??>
+        entrypoint:
+        <#list service.entrypoint as item>
+            - ${item}
+        </#list>
+        </#if>
+        <#if service.healthcheck??>
+        healthcheck:
+        <#list service.healthcheck as item>
+            ${item}
+        </#list>
+        </#if>
+        <#if service.links??>
+        links:
+        <#list service.links as item>
+            - ${item}
+        </#list>
+        </#if>
+</#list>
diff --git a/test/dubbo-scenario-builder/src/main/resources/scenario.sh b/test/dubbo-scenario-builder/src/main/resources/scenario.sh
new file mode 100644
index 0000000..29ae046
--- /dev/null
+++ b/test/dubbo-scenario-builder/src/main/resources/scenario.sh
@@ -0,0 +1,140 @@
+#!/usr/bin/env bash
+
+# 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.
+
+PRG="$0"
+PRGDIR=`dirname "$PRG"`
+[ -z "$SCENARIO_HOME" ] && SCENARIO_HOME=`cd "$PRGDIR" >/dev/null; pwd`
+cd $SCENARIO_HOME
+
+# set vars from freemarker
+testcase_name=${scenario_name}-${scenario_version}
+config_debug_mode=${debug_mode}
+config_timeout=${timeout}
+scenario_name=${scenario_name}
+compose_file="${docker_compose_file}"
+project_name=$(echo "${scenario_name}_${scenario_version}" |sed -e "s/\.//g" |awk '{print tolower($0)}')
+test_service_name="${test_service_name}_1"
+network_name="${network_name}"
+
+<#noparse>
+status=1
+start=$SECONDS
+
+mkdir -p ${SCENARIO_HOME}/logs
+scenario_log=${SCENARIO_HOME}/logs/scenario.log
+rm -f $scenario_log
+
+# overrite configs
+debug_mode=${debug_mode:-$config_debug_mode}
+timeout=${timeout:-$config_timeout}
+echo "[$scenario_name] debug_mode: $debug_mode" >> $scenario_log
+echo "[$scenario_name] timeout: $timeout" >> $scenario_log
+
+function wait_container_exit() {
+  container_name=$1
+  start=$2
+  timeout=$3
+
+  # check and get exit code
+  while [ 1 = 1 ];
+  do
+    status=`docker inspect $container_name --format='{{.State.Status}}'`
+    result=$?
+    if [ $result -ne 0 ];then
+      echo "check container status failure: $result"
+      return 1
+    fi
+    if [ "$status" == "exited" ];then
+        return 0
+    fi
+
+    duration=$(( SECONDS - start ))
+    if [ $duration -gt $timeout ];then
+      echo "wait for container is timeout: $duration s"
+      return 1
+    fi
+    sleep 2
+  done
+}
+
+#Starting test containers
+container_name="${project_name}_${test_service_name}"
+
+#kill and clean first
+echo "[$scenario_name] Killing test containers .." | tee -a $scenario_log
+docker-compose -p ${project_name} -f ${compose_file} kill 2>&1 | tee -a $scenario_log > /dev/null
+
+echo "[$scenario_name] Removing test containers .." | tee -a $scenario_log
+docker-compose -p ${project_name} -f ${compose_file} rm -f 2>&1 | tee -a $scenario_log > /dev/null
+
+# complete pull fail interactive by <<< "NN"
+echo "[$scenario_name] Starting test containers .." | tee -a $scenario_log
+docker-compose -p ${project_name} -f ${compose_file} up -d --no-build 2>&1 <<< "NN" | tee -a $scenario_log > /dev/null
+
+container_id=`docker ps -qf "name=${container_name}"`
+if [[ -z "${container_id}" ]]; then
+    echo "[$scenario_name] docker startup failure!" | tee -a $scenario_log
+    status=1
+else
+    # check and get exit code
+    wait_container_exit ${container_name} $start $timeout
+    result=$?
+    if [ $result -eq 0 ]; then
+        result=`docker inspect ${container_name} --format='{{.State.ExitCode}}'`
+        if [ $result -eq 0 ]; then
+            status=0
+            echo "[$scenario_name] Run tests successfully" | tee -a $scenario_log
+        else
+            status=$result
+            echo "[$scenario_name] Run tests failed" | tee -a $scenario_log
+        fi
+    else
+        status=1
+        echo "[$scenario_name] Run tests timeout" | tee -a $scenario_log
+    fi
+
+    echo "[$scenario_name] Stopping test containers .." | tee -a $scenario_log
+    docker-compose -p ${project_name} -f ${compose_file} kill 2>&1 | tee -a $scenario_log > /dev/null
+
+fi
+
+</#noparse>
+
+#copy logs
+echo "Copying container logs .." >> $scenario_log
+
+<#list services as service>
+service_name="${service.name}"
+<#noparse>
+docker logs ${project_name}_${service_name}_1 &> $SCENARIO_HOME/logs/${service_name}.log
+</#noparse>
+
+</#list>
+
+<#noparse>
+if [[ "$debug_mode" != "1" && $status == 0 ]];then
+    docker-compose -p $project_name -f $compose_file rm -f 2>&1 | tee -a $scenario_log > /dev/null
+    ${removeImagesScript}
+fi
+
+# clear network
+docker network rm $network_name 2>&1 | tee -a $scenario_log > /dev/null
+
+exit $status
+
+</#noparse>
\ No newline at end of file
diff --git a/test/dubbo-test-runner/build.sh b/test/dubbo-test-runner/build.sh
new file mode 100755
index 0000000..d34a42d
--- /dev/null
+++ b/test/dubbo-test-runner/build.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+
+DOCKER_DIR=$DIR/target/docker
+
+mvn clean package
+result=$?
+if [ $result -ne 0 ]; then
+  echo "Build dubbo-test-runner failure"
+  exit $result
+fi
+
+mkdir -p $DOCKER_DIR
+cp -r $DIR/src/docker/* $DOCKER_DIR/
+cp $DIR/target/dubbo-test-runner-*-jar-with-dependencies.jar $DOCKER_DIR/
+
+cd $DOCKER_DIR
+docker build -t dubbo/sample-test .
diff --git a/test/dubbo-test-runner/pom.xml b/test/dubbo-test-runner/pom.xml
new file mode 100644
index 0000000..b91e715
--- /dev/null
+++ b/test/dubbo-test-runner/pom.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>dubbo-test</artifactId>
+        <groupId>org.apache.dubbo</groupId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>dubbo-test-runner</artifactId>
+
+
+    <dependencies>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.12</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.maven.surefire</groupId>
+            <artifactId>surefire-junit4</artifactId>
+            <version>2.22.2</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.maven.surefire</groupId>
+            <artifactId>maven-surefire-common</artifactId>
+            <version>2.22.2</version>
+            <exclusions>
+                <exclusion>
+                    <artifactId>maven-core</artifactId>
+                    <groupId>org.apache.maven</groupId>
+                </exclusion>
+                <exclusion>
+                    <artifactId>maven-plugin-annotations</artifactId>
+                    <groupId>org.apache.maven.plugin-tools</groupId>
+                </exclusion>
+                <exclusion>
+                    <artifactId>maven-plugin-descriptor</artifactId>
+                    <groupId>org.apache.maven</groupId>
+                </exclusion>
+                <exclusion>
+                    <artifactId>maven-project</artifactId>
+                    <groupId>org.apache.maven</groupId>
+                </exclusion>
+                <exclusion>
+                    <artifactId>asm</artifactId>
+                    <groupId>org.ow2.asm</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <version>3.3.0</version>
+                <configuration>
+                    <descriptorRefs>
+                        <descriptorRef>jar-with-dependencies</descriptorRef>
+                    </descriptorRefs>
+                    <archive>
+                        <manifest>
+                            <mainClass>org.apache.dubbo.test.runner.TestRunnerMain</mainClass>
+                        </manifest>
+                    </archive>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>make-assembly</id> <!-- this is used for inheritance merges -->
+                        <phase>package</phase> <!-- bind to the packaging phase -->
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
\ No newline at end of file
diff --git a/test/dubbo-test-runner/src/docker/Dockerfile b/test/dubbo-test-runner/src/docker/Dockerfile
new file mode 100644
index 0000000..ad7ef51
--- /dev/null
+++ b/test/dubbo-test-runner/src/docker/Dockerfile
@@ -0,0 +1,61 @@
+#   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.
+
+FROM openjdk:8
+
+RUN apt-get update && \
+    apt-get upgrade -y && \
+    apt-get install -y telnet && \
+    apt-get clean
+
+VOLUME /usr/local/dubbo/app
+VOLUME /usr/local/dubbo/logs
+
+# service name
+ENV SERVICE_NAME=""
+
+# classpath
+# assert mounting $project_dir/target:/usr/local/dubbo/app/
+ENV TEST_CLASSES_DIR="/usr/local/dubbo/app/test-classes"
+ENV APP_CLASSES_DIR="/usr/local/dubbo/app/classes"
+ENV APP_DEPENDENCY_DIR="/usr/local/dubbo/app/dependency"
+
+# check ports/log timeout
+ENV CHECK_TIMEOUT=60
+
+# service type: app, test
+ENV SERVICE_TYPE="app"
+ENV WAIT_PORTS_BEFORE_RUN=""
+
+# dubbo provider bootstrap class
+ENV APP_MAIN_CLASS=""
+ENV CHECK_PORTS_AFTER_RUN=""
+ENV CHECK_LOG=""
+
+# test envs
+ENV TEST_PATTERNS="**/*IT.class"
+
+# Jvm flags
+ENV JAVA_OPTS=""
+
+
+ADD *.sh /usr/local/dubbo/
+ADD dubbo-test-runner-*-jar-with-dependencies.jar /usr/local/dubbo/dubbo-test-runner.jar
+
+WORKDIR /usr/local/dubbo/
+#ENTRYPOINT exec java $JAVA_OPTS -jar dubbo-test-runner.jar $TEST_CLASSES_DIR $APP_CLASSES_DIR $APP_DEPENDENCY_DIR
+ENTRYPOINT ["bash", "-x", "./run.sh"]
+
+
diff --git a/test/dubbo-test-runner/src/docker/run-dubbo-app.sh b/test/dubbo-test-runner/src/docker/run-dubbo-app.sh
new file mode 100755
index 0000000..6fe3d2c
--- /dev/null
+++ b/test/dubbo-test-runner/src/docker/run-dubbo-app.sh
@@ -0,0 +1,48 @@
+#!/bin/bash
+
+DIR=/usr/local/dubbo/
+cd $DIR
+
+source $DIR/utils.sh
+
+if [ "$APP_MAIN_CLASS" == "" ]; then
+  echo "Missing env 'APP_MAIN_CLASS' for app service"
+  return 1
+fi
+
+# wait ports before run app: WAIT_PORTS_BEFORE_RUN=host:port;host:port
+if [ "$WAIT_PORTS_BEFORE_RUN" != "" ]; then
+  echo "Waiting ports before run app .."
+  # check ports
+  split_and_check_tcp_ports "$WAIT_PORTS_BEFORE_RUN" $SECONDS $CHECK_TIMEOUT
+  result=$?
+  if [ $result -ne 0 ]; then
+    echo "Wait ports before run app failure"
+    exit $result
+  fi
+fi
+
+echo "Running app : [$APP_MAIN_CLASS] ..."
+start=$SECONDS
+java $JAVA_OPTS -cp "$APP_CLASSES_DIR:$APP_DEPENDENCY_DIR/*" $APP_MAIN_CLASS 2>&1 &
+pid=$!
+
+sleep 20
+
+# check ports after run
+if [ "$CHECK_PORTS_AFTER_RUN" != "" ]; then
+  split_and_check_tcp_ports "$CHECK_PORTS_AFTER_RUN" $start $CHECK_TIMEOUT
+  result=$?
+  if [ $result -ne 0 ]; then
+    echo "Wait ports after run app failure"
+    exit $result
+  fi
+fi
+
+echo "Wait for process to exit: $pid .."
+wait $pid
+result=$?
+if [ $result -ne 0 ]; then
+  exit $result
+fi
+
diff --git a/test/dubbo-test-runner/src/docker/run-dubbo-test.sh b/test/dubbo-test-runner/src/docker/run-dubbo-test.sh
new file mode 100755
index 0000000..d950974
--- /dev/null
+++ b/test/dubbo-test-runner/src/docker/run-dubbo-test.sh
@@ -0,0 +1,36 @@
+#!/bin/bash
+
+DIR=/usr/local/dubbo/
+
+source $DIR/utils.sh
+
+cd $DIR
+
+if [ "$TEST_CLASSES_DIR" == "" ]; then
+  echo "Missing env 'TEST_CLASSES_DIR' for test service"
+  return 1
+fi
+
+# wait ports before run app: WAIT_PORTS_BEFORE_RUN=host:port;host:port
+if [ "$WAIT_PORTS_BEFORE_RUN" != "" ]; then
+  echo "Waiting ports before run test .."
+  # check ports
+  split_and_check_tcp_ports "$WAIT_PORTS_BEFORE_RUN" $SECONDS $CHECK_TIMEOUT
+  result=$?
+  if [ $result -ne 0 ]; then
+    echo "Wait ports before run test failure"
+    exit $result
+  fi
+fi
+
+# run testcase
+echo "Running tests ..."
+report_dir=$DIR/app/test-reports
+java $JAVA_OPTS -jar dubbo-test-runner.jar "$TEST_CLASSES_DIR" "$APP_CLASSES_DIR" "$APP_DEPENDENCY_DIR" "$report_dir" "$TEST_PATTERNS" 2>&1
+result=$?
+if [ $result -ne 0 ]; then
+  echo "Run tests failure"
+  exit $result
+else
+  echo "Run tests successfully"
+fi
diff --git a/test/dubbo-test-runner/src/docker/run.sh b/test/dubbo-test-runner/src/docker/run.sh
new file mode 100755
index 0000000..e52479e
--- /dev/null
+++ b/test/dubbo-test-runner/src/docker/run.sh
@@ -0,0 +1,45 @@
+#!/bin/bash
+
+DIR=/usr/local/dubbo/
+cd $DIR
+
+LOG_DIR=/usr/local/dubbo/logs
+if [ ! -d $LOG_DIR ];then
+  mkdir -p $LOG_DIR
+fi
+LOG_FILE=$LOG_DIR/$SERVICE_NAME.log
+rm -f $LOG_FILE
+
+source $DIR/utils.sh
+
+function print_log() {
+    msg=$1
+    echo $msg | tee -a $LOG_FILE
+}
+
+if [ "$SERVICE_NAME" == "" ]; then
+  print_log "Missing env 'SERVICE_NAME'"
+  return 1
+fi
+
+if [ "$APP_CLASSES_DIR" == "" ]; then
+  print_log "Missing env 'APP_CLASSES_DIR'"
+  return 1
+fi
+
+if [ "$APP_DEPENDENCY_DIR" == "" ]; then
+  print_log "Missing env 'APP_DEPENDENCY_DIR'"
+  return 1
+fi
+
+
+if [ "$SERVICE_TYPE" == "app"  ]; then
+  script_file=$DIR/run-dubbo-app.sh
+elif [ "$SERVICE_TYPE" == "test"  ]; then
+  script_file=$DIR/run-dubbo-test.sh
+fi
+
+/bin/bash -x $script_file 2>&1 | tee -a $LOG_FILE
+# get proc exitcode before tee, use $PIPESTATUS variable instead of $? (https://stackoverflow.com/a/6871917)
+result=${PIPESTATUS[0]}
+exit $result
\ No newline at end of file
diff --git a/test/dubbo-test-runner/src/docker/utils.sh b/test/dubbo-test-runner/src/docker/utils.sh
new file mode 100644
index 0000000..76e0650
--- /dev/null
+++ b/test/dubbo-test-runner/src/docker/utils.sh
@@ -0,0 +1,108 @@
+#!/bin/bash
+
+function check_tcp_port() {
+    host=$1
+    port=$2
+    start=$3
+    timeout=$4
+
+    start=${start:-$SECONDS}
+    timeout=${timeout:-60}
+
+    if [[ "$host" == "" || "$port" == "" ]]; then
+      echo "invalid args, usage: check_tcp_port <host> <port> [start_at_seconds] [timeout_in_seconds]"
+      return 1
+    fi
+
+    echo "checking tcp port [$host:$port] ..."
+    for ((i=1; i<=${timeout}; i++));
+    do
+      # sometimes nc/ping is too slowly, why?
+      #nc -zv -w2 $host $port
+
+      # connect to remote host:port by telnet, and auto close it
+      echo -e '\x1dclose\x0d' | telnet $host $port 2>&1
+      result=$?
+      if [ $result -eq 0 ]; then
+        echo "check tcp port [$host:$port] success."
+        return 0
+      fi
+
+      duration=$(( SECONDS - start ))
+      if [ $duration -gt $timeout ];then
+        echo "check tcp port [$host:$port] is timeout: $duration s"
+        return 1
+      fi
+      sleep 1
+    done
+
+    duration=$(( SECONDS - start ))
+    echo "check tcp port [$host:$port] is timeout: $duration s"
+    return 1
+}
+
+function split_and_check_tcp_ports() {
+  addrs_str=$1
+  start=$2
+  timeout=$3
+
+  start=${start:-$SECONDS}
+  timeout=${timeout:-60}
+  echo "checking tcp ports: $addrs_str, start at: $start, timeout: $timeout"
+
+  addr_ports=$(echo $addrs_str | tr ";" "\n")
+  for addr_port in $addr_ports
+  do
+    # process
+    host=${addr_port%:*}
+    port=${addr_port#*:}
+
+    check_tcp_port $host $port $start $timeout
+    result=$?
+    if [ $result -ne 0 ]; then
+      return $result
+    fi
+  done
+}
+
+# examples
+#sleep 3
+#split_and_check_tcp_ports "$@" $SECONDS  10
+
+
+# check and wait for log message
+function check_log() {
+  file="$1"
+  match_str="$2"
+  start=$3
+  timeout=$4
+
+  start=${start:-$SECONDS}
+  timeout=${timeout:-60}
+
+  echo "checking log [$file], matching str: $match_str .."
+  for ((i=1; i<=${timeout}; i++));
+  do
+    grep "$match_str" "$file"
+    result=$?
+    if [ $result -eq 0 ];then
+      return 0
+    fi
+
+    duration=$(( SECONDS - start ))
+    if [ $duration -gt $timeout ];then
+      echo "check log [$file] for matching string [$match_str] is timeout: $duration s"
+      return 1
+    fi
+
+    sleep 2
+  done
+
+  duration=$(( SECONDS - start ))
+  echo "check log [$file] for matching string [$match_str] is timeout: $duration s"
+  return 1
+}
+
+# examples
+#sleep 3
+#check_log "$@" $SECONDS  10
diff --git a/test/dubbo-test-runner/src/main/java/org/apache/dubbo/test/runner/TestRunnerMain.java b/test/dubbo-test-runner/src/main/java/org/apache/dubbo/test/runner/TestRunnerMain.java
new file mode 100644
index 0000000..fd0ba07
--- /dev/null
+++ b/test/dubbo-test-runner/src/main/java/org/apache/dubbo/test/runner/TestRunnerMain.java
@@ -0,0 +1,245 @@
+/*
+ * 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.dubbo.test.runner;
+
+
+import org.apache.maven.plugin.surefire.InPluginVMSurefireStarter;
+import org.apache.maven.plugin.surefire.StartupReportConfiguration;
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.plugin.surefire.log.api.PrintStreamLogger;
+import org.apache.maven.plugin.surefire.report.ConsoleReporter;
+import org.apache.maven.plugin.surefire.util.DirectoryScanner;
+import org.apache.maven.surefire.booter.AbstractPathConfiguration;
+import org.apache.maven.surefire.booter.ClassLoaderConfiguration;
+import org.apache.maven.surefire.booter.Classpath;
+import org.apache.maven.surefire.booter.ClasspathConfiguration;
+import org.apache.maven.surefire.booter.ProviderConfiguration;
+import org.apache.maven.surefire.booter.Shutdown;
+import org.apache.maven.surefire.booter.StartupConfiguration;
+import org.apache.maven.surefire.cli.CommandLineOption;
+import org.apache.maven.surefire.junit4.JUnit4Provider;
+import org.apache.maven.surefire.report.ReporterConfiguration;
+import org.apache.maven.surefire.suite.RunResult;
+import org.apache.maven.surefire.testset.DirectoryScannerParameters;
+import org.apache.maven.surefire.testset.RunOrderParameters;
+import org.apache.maven.surefire.testset.TestListResolver;
+import org.apache.maven.surefire.testset.TestRequest;
+import org.apache.maven.surefire.util.DefaultScanResult;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
+import static java.util.Collections.EMPTY_LIST;
+import static java.util.Collections.emptyList;
+
+/**
+ * gongdewei 2020/12/7
+ */
+public class TestRunnerMain {
+
+    public static void main(String[] args) throws Exception {
+
+        if (args.length < 4) {
+            throw new IllegalArgumentException("Invalid arguments, usage: TestRunnerMain <testClassesDir> <targetClassesDir> <dependencyJarsDir> <reportDir> [test-pattern1;test-pattern2]");
+        }
+
+        File testClassesDir = new File(args[0]);
+        if (!testClassesDir.exists() || !testClassesDir.isDirectory()) {
+            throw new IllegalArgumentException("testClassesDir is not exists or is not a directory: " + testClassesDir.getAbsolutePath());
+        }
+        File targetClassesDir = new File(args[1]);
+        if (!targetClassesDir.exists() || !targetClassesDir.isDirectory()) {
+            throw new IllegalArgumentException("targetClassesDir is not exists or is not a directory: " + targetClassesDir.getAbsolutePath());
+        }
+        File dependencyJarsDir = new File(args[2]);
+        if (!dependencyJarsDir.exists() || !dependencyJarsDir.isDirectory()) {
+            throw new IllegalArgumentException("dependencyJarsDir is not exists or is not a directory: " + dependencyJarsDir.getAbsolutePath());
+        }
+
+        File reportsDirectory = new File(args[3]);
+        reportsDirectory.mkdirs();
+        if (!reportsDirectory.exists() || !reportsDirectory.isDirectory()) {
+            throw new IllegalArgumentException("reportDir is not exists or is not a directory: " + reportsDirectory.getAbsolutePath());
+        }
+
+        // tests pattern:  **/*IT.class;**/*Test.class
+        List<String> tests = new ArrayList<>();
+        if (args.length > 4) {
+            String[] testPatterns = args[4].split(";");
+            for (String pattern : testPatterns) {
+                String s = pattern.trim();
+                if (s.endsWith(".java")) {
+                    s = s.replace(".java", ".class");
+                }
+                tests.add(s);
+            }
+        }
+
+        System.out.println("testClassesDir: " + testClassesDir.getAbsolutePath());
+        System.out.println("targetClassesDir: " + targetClassesDir.getAbsolutePath());
+        System.out.println("dependencyJarsDir: " + dependencyJarsDir.getAbsolutePath());
+        System.out.println("reportsDirectory: " + reportsDirectory.getAbsolutePath());
+        System.out.println("test patterns: " + tests);
+
+
+        File statisticsFile = new File(reportsDirectory, "test-statistics.txt");
+        StartupReportConfiguration startupReportConfiguration = new StartupReportConfiguration(true,
+                true,
+                ConsoleReporter.PLAIN,
+                false,
+                true,
+                reportsDirectory,
+                false,
+                "report",
+                statisticsFile,
+                true,
+                10,
+                null,
+                "UTF-8",
+                false
+        );
+
+        DirectoryScannerParameters directoryScannerParameters = new DirectoryScannerParameters(testClassesDir,
+                emptyList(), emptyList(), emptyList(), false, "");
+
+        File runStatisticsFile = new File(reportsDirectory, "run-statistics.txt");
+        RunOrderParameters runOrderParameters = new RunOrderParameters((String) null, runStatisticsFile);
+
+        TestListResolver requestTests = new TestListResolver(Collections.emptyList());
+        TestRequest testRequest = new TestRequest(Collections.emptyList(), testClassesDir, requestTests);
+
+        HashMap<String, String> providerProperties = new HashMap<>();
+
+        ReporterConfiguration reporterConfiguration = new ReporterConfiguration(reportsDirectory, false);
+
+        List<CommandLineOption> cliOptions = new ArrayList<>();
+        cliOptions.add(CommandLineOption.LOGGING_LEVEL_ERROR);
+        cliOptions.add(CommandLineOption.LOGGING_LEVEL_WARN);
+        cliOptions.add(CommandLineOption.LOGGING_LEVEL_INFO);
+        cliOptions.add(CommandLineOption.REACTOR_FAIL_FAST);
+
+        ProviderConfiguration providerConfiguration = new ProviderConfiguration(
+                directoryScannerParameters,
+                runOrderParameters,
+                false,
+                reporterConfiguration,
+                null,
+                testRequest,
+                providerProperties,
+                null,
+                false,
+                cliOptions,
+                0,
+                Shutdown.DEFAULT,
+                30
+        );
+
+        String providerClassName = JUnit4Provider.class.getName();//"org.apache.maven.surefire.junit4.JUnit4Provider";
+        Classpath testClasspath = generateTestClasspath(testClassesDir, targetClassesDir, dependencyJarsDir);
+        Classpath inprocClasspath = getInprocClasspath();
+        Classpath surefireClasspath = inprocClasspath;
+        AbstractPathConfiguration classpathConfiguration = new ClasspathConfiguration(
+                testClasspath,
+                surefireClasspath,
+                inprocClasspath,
+                false,
+                false
+        );
+
+        ClassLoaderConfiguration classloaderConfiguration = new ClassLoaderConfiguration(false, false);
+        StartupConfiguration startupConfiguration = new StartupConfiguration(
+                providerClassName,
+                classpathConfiguration,
+                classloaderConfiguration,
+                false,
+                false);
+
+
+        DefaultScanResult scanResult = getScanResult(testClassesDir, tests);
+
+        ConsoleLogger consoleLogger = new PrintStreamLogger(System.out);
+        InPluginVMSurefireStarter testStarter = new InPluginVMSurefireStarter(startupConfiguration, providerConfiguration,
+                startupReportConfiguration, consoleLogger);
+        RunResult runResult = testStarter.runSuitesInProcess(scanResult);
+        boolean runSuccess = runResult.getCompletedCount() > 0 && runResult.isErrorFree() && !runResult.isTimeout();
+
+        String line = "------------------------------------------------------------------------\n";
+        consoleLogger.info(String.format(line + "TEST %s, Total: %d, Failures: %d, Errors: %d, Skipped: %d\n" + line,
+                runSuccess ? "SUCCESS" : "FAILURE", runResult.getCompletedCount(), runResult.getFailures(), runResult.getErrors(),
+                runResult.getSkipped()));
+
+//        File tmpDirectory = new File(reportsDirectory, "tmp");
+//        tmpDirectory.mkdirs();
+//        ForkConfiguration forkConfiguration = new ClasspathForkConfiguration(inprocClasspath, tmpDirectory,  null,
+//                reportsDirectory, new Properties(), "", System.getenv(), false, 1, true,
+//                new Platform(), consoleLogger);
+//        ForkStarter forkStarter = new ForkStarter(providerConfiguration,
+//                startupConfiguration,
+//                forkConfiguration,
+//                30,
+//                startupReportConfiguration,
+//                consoleLogger);
+//        RunResult runResult = forkStarter.run(new SurefireProperties(), scanResult);
+//        System.out.println(String.format("RunResult: %d, Failures: %d, Errors: %d, failure: %s",
+//                runResult.getCompletedCount(), runResult.getFailures(), runResult.getErrors(), runResult.getFailure()));
+
+        if (runSuccess) {
+            System.exit(0);
+        } else {
+            System.exit(1);
+        }
+    }
+
+    public static DefaultScanResult getScanResult(File testClassesDir, Collection<String> tests) {
+        DirectoryScanner directoryScanner = new DirectoryScanner(testClassesDir, new TestListResolver(tests));
+        return directoryScanner.scan();
+    }
+
+    private static Classpath getInprocClasspath() {
+        List<String> classpath = new ArrayList<>();
+
+        ClassLoader cl = ClassLoader.getSystemClassLoader();
+        URL[] urls = ((URLClassLoader) cl).getURLs();
+        for (URL url : urls) {
+            classpath.add(url.getFile());
+        }
+        return new Classpath(classpath);
+    }
+
+    private static Classpath generateTestClasspath(File testClassesDir, File targetClassDir, File dependenciesDir) {
+        List<String> classpath = new ArrayList<>();
+        classpath.add(testClassesDir.getAbsolutePath());
+        classpath.add(targetClassDir.getAbsolutePath());
+
+        File[] jarFiles = dependenciesDir.listFiles((dir, name) -> name.toLowerCase().endsWith(".jar"));
+        if (jarFiles != null) {
+            for (File jarFile : jarFiles) {
+                classpath.add(jarFile.getAbsolutePath());
+            }
+        }
+        return new Classpath(classpath);
+    }
+
+}
\ No newline at end of file
diff --git a/test/kill-tests.sh b/test/kill-tests.sh
new file mode 100755
index 0000000..3736033
--- /dev/null
+++ b/test/kill-tests.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+
+echo "Killing run-tests.sh .."
+ps -ef | grep run-tests.sh | grep -v grep  | awk '{ print $2 }' | xargs -I {} kill {}
+
+echo "Killing scenario.sh .."
+ps -ef | grep scenario.sh | grep -v grep  | awk '{ print $2 }' | xargs -I {} kill {}
+
+echo "Killing docker logs procs .."
+ps -ef | grep "docker logs" | grep -v grep  | awk '{ print $2 }' | xargs -I {} kill {}
+
+echo "Killing dubbo containers .."
+docker ps -a | grep dubbo | awk '{ print $1}' | xargs -I {} docker kill {}
+
+echo "Removing unused networks .."
+docker network prune -f
+
diff --git a/test/pom.xml b/test/pom.xml
new file mode 100644
index 0000000..b69de03
--- /dev/null
+++ b/test/pom.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.apache.dubbo</groupId>
+    <artifactId>dubbo-test</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <modules>
+        <module>dubbo-scenario-builder</module>
+        <module>dubbo-test-runner</module>
+    </modules>
+    <packaging>pom</packaging>
+
+    <properties>
+        <source.level>1.8</source.level>
+        <target.level>1.8</target.level>
+        <spring.version>4.3.16.RELEASE</spring.version>
+        <junit.version>4.12</junit.version>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.7.0</version>
+                <configuration>
+                    <source>${source.level}</source>
+                    <target>${target.level}</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
\ No newline at end of file
diff --git a/test/prepare-test.sh b/test/prepare-test.sh
new file mode 100755
index 0000000..499d6a2
--- /dev/null
+++ b/test/prepare-test.sh
@@ -0,0 +1,28 @@
+#!/bin/bash
+
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+
+JOB_COUNT=${JOB_COUNT:-5}
+echo "JOB_COUNT: $JOB_COUNT"
+
+# find all case-configuration.yml
+CONFIG_FILE="case-configuration.yml"
+test_list_file=$DIR/testcases.txt
+test_base_dir="$( cd $DIR/.. && pwd )"
+echo "Searching all '$CONFIG_FILE' under dir $test_base_dir .."
+find $test_base_dir -name $CONFIG_FILE | grep -v "$DIR" > $test_list_file
+
+# Split test list into JOB_COUNT parts
+jobs_dir=$DIR/jobs
+mkdir -p $jobs_dir
+rm -f $jobs_dir/*
+case_index=0
+while read file
+do
+  job=$((case_index % JOB_COUNT + 1))
+  case_index=$((case_index + 1))
+  echo ${file%/$CONFIG_FILE} >> $jobs_dir/testcases-${job}.txt
+done < $test_list_file
+
+echo "Total $case_index cases split into $JOB_COUNT jobs:"
+grep -r "" -c $jobs_dir
\ No newline at end of file
diff --git a/test/quick-start_cn.md b/test/quick-start_cn.md
new file mode 100644
index 0000000..96fdf9b
--- /dev/null
+++ b/test/quick-start_cn.md
@@ -0,0 +1,102 @@
+
+
+## Dubbo Integration Test
+
+### 测试框架
+
+* 基于docker-compose 以容器方式运行
+
+* dubbo-test-runner 模块
+  
+  构建`dubbo/sample-test` 镜像,在容器中启动Dubbo provider application 和 Dubbo testcase.
+
+* dubbo-scenario-builder 模块
+
+  构建测试场景,包含`docker-compose.yml`及`scenario.sh`脚本等。
+  构建成功后,`scenario.sh`脚本可以单独运行。
+  
+### 编译测试镜像
+
+```
+cd dubbo-samples/test
+./build-test-image.sh
+```
+
+### 运行测试案例
+
+#### 编译方式(BUILD)
+
+* `BUILD=all`  
+  编译整个dubbo-samples
+  
+* `BUILD=case`  
+  编译需要运行的测试工程(注意有部分测试工程因为依赖问题不能独立编译)
+
+* `BUILD=n`  
+  不自动编译测试工程,需要先手工编译成功后,再运行测试。
+  
+  Maven编译参数: mvn clean package dependency:copy-dependencies -DskipTests
+
+#### 测试步骤
+
+* 编译测试工程
+
+  `mvn clean package dependency:copy-dependencies -DskipTests`
+  
+* 生成测试场景
+
+  测试场景`scenario_home`的位置位于`${project.basedir}/target`
+  
+  `$scenario_home/scenario.sh`: 运行测试的脚本
+  
+  `$scenario_home/docker-compose.yml` : 生成的容器配置文件
+  
+  `$scenario_home/logs` : 测试相关日志
+   
+* 运行测试
+
+  `$scenario_home/scenario.sh`
+
+#### 运行方式
+
+* 运行全部测试案例
+
+  (1) 编译整个dubbo-samples : `BUILD=all`   
+  (2) 查找所有`case-configuration.yml`  
+  (3) fork多进程按顺序运行测试  
+ 
+  ```
+   ./run-tests.sh
+  ```
+   等同于
+  ```
+   BUILD=all  ./run-tests.sh
+  ```
+  
+* 运行单个测试案例
+
+  `BUILD=case  ./run-tests.sh <project.basedir>`
+  
+* 调试单个测试案例
+
+  ```
+  BUILD=case DEBUG=1 ./run-tests.sh <project.basedir>
+  ```
+
+  默认`suspend=y`,可以用`DEBUG_SUSPEND=n`修改为不等待连接调试端口:
+  
+  ```
+  BUILD=case DEBUG=1 DEBUG_SUSPEND=n ./run-tests.sh <project.basedir>
+  ```
+
+* 运行指定的测试案例列表
+
+  ```
+  BUILD=case TEST_CASE_FILE=testcases1.txt ./run-tests.sh
+  ```
+    
+### 定义测试用例
+
+ 测试用例配置文件为:`case-configuration.yml`,放在每个需要测试的工程basedir下。 
+ 
+ 
diff --git a/test/run-tests.sh b/test/run-tests.sh
new file mode 100755
index 0000000..065f02f
--- /dev/null
+++ b/test/run-tests.sh
@@ -0,0 +1,290 @@
+#!/bin/bash
+
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+
+FAIL_FAST=${FAIL_FAST:-0}
+echo "FAIL_FAST: $FAIL_FAST"
+
+SHOW_ERROR_DETAIL=${SHOW_ERROR_DETAIL:-0}
+export SHOW_ERROR_DETAIL=$SHOW_ERROR_DETAIL
+echo "SHOW_ERROR_DETAIL: $SHOW_ERROR_DETAIL"
+
+maxForks=${FORK_COUNT:-2}
+echo "FORK_COUNT: $maxForks"
+
+#Build mode: all, case, no
+BUILD=${BUILD:-all}
+export BUILD=$BUILD
+echo "BUILD: $BUILD"
+
+#DUBBO_VERSION=
+echo "DUBBO_VERSION: $DUBBO_VERSION"
+
+BUILD_OPTS="clean package dependency:copy-dependencies -DskipTests"
+if [ "$DUBBO_VERSION" != "" ]; then
+  BUILD_OPTS="$BUILD_OPTS -Ddubbo.version=$DUBBO_VERSION"
+fi
+export BUILD_OPTS=$BUILD_OPTS
+echo "BUILD_OPTS: $BUILD_OPTS"
+
+#debug
+DEBUG=${DEBUG:-0}
+DEBUG_SUSPEND=${DEBUG_SUSPEND:-y}
+export DEBUG=$DEBUG
+export DEBUG_SUSPEND=$DEBUG_SUSPEND
+echo "DEBUG=$DEBUG, DEBUG_SUSPEND=$DEBUG_SUSPEND"
+
+#TEST_CASE_FILE
+if [ "$TEST_CASE_FILE" != "" ]; then
+  # convert relative path to absolute path
+  if [[ $TEST_CASE_FILE != /* ]]; then
+    TEST_CASE_FILE=$DIR/$TEST_CASE_FILE
+  fi
+  echo "TEST_CASE_FILE: $TEST_CASE_FILE"
+fi
+
+echo "Test logs dir: \${project.basedir}/target/logs"
+echo "Test reports dir: \${project.basedir}/target/test-reports"
+
+
+#check dubbo/sample-test image and version
+test_image="dubbo/sample-test"
+echo "Checking test image [$test_image] .. "
+docker images --format 'table {{.Repository}}:{{.Tag}}\t{{.ID}}\t{{.CreatedAt}}\t{{.Size}}' | grep $test_image
+result=$?
+if [ $result != 0 ];then
+  echo "Test image not found: $test_image, please run 'bash ./build-test-image.sh' first."
+  exit 1
+fi
+
+# build scenario-builder
+SCENARIO_BUILDER_DIR=$DIR/dubbo-scenario-builder
+echo "Building scenario builder .."
+cd $SCENARIO_BUILDER_DIR
+mvn clean package -DskipTests &> $SCENARIO_BUILDER_DIR/mvn.log
+result=$?
+if [ $result -ne 0 ]; then
+  echo "Build dubbo-scenario-builder failure, please check logs: $SCENARIO_BUILDER_DIR/mvn.log"
+  exit $result
+fi
+
+# find jar
+test_builder_jar=`ls $SCENARIO_BUILDER_DIR/target/dubbo-scenario-builder*-with-dependencies.jar`
+if [ "$test_builder_jar" == "" ]; then
+  echo "dubbo-scenario-builder jar not found"
+  exit 1
+else
+  echo "Found test builder : $test_builder_jar"
+fi
+
+if [ "$BUILD" == "all" ]; then
+  echo "Building dubbo-samples .."
+  cd $DIR/..
+  mvn $BUILD_OPTS
+  result=$?
+  if [ $result -ne 0 ]; then
+    echo "Build dubbo-samples failure, please check logs"
+    exit $result
+  fi
+fi
+
+# prepare testcases
+cd $DIR
+
+CONFIG_FILE="case-configuration.yml"
+
+testListFile=$DIR/testcases.txt
+targetTestcases=$1
+if [ "$targetTestcases" != "" ];then
+  echo "Target testcase: $targetTestcases"
+  echo $targetTestcases > $testListFile
+else
+  # use input testcases file
+  if [ "$TEST_CASE_FILE" != "" ]; then
+    testListFile=$TEST_CASE_FILE
+    if [ ! -f $testListFile ]; then
+      echo "Testcases file not found: $testListFile"
+      exit 1
+    fi
+  else
+    # find all case-configuration.yml
+    test_base_dir="$( cd $DIR/.. && pwd )"
+    rm -f $testListFile
+    echo "Searching all '$CONFIG_FILE' under dir $test_base_dir .."
+    find $test_base_dir -name $CONFIG_FILE | grep -v "$DIR" > $testListFile
+  fi
+fi
+
+caseCount=`grep "" -c $testListFile`
+echo "Total test cases : $caseCount"
+
+#clear test results
+testResultFile=${testListFile%.*}-result.txt
+rm -f $testResultFile
+echo "Test results: $testResultFile"
+
+# constant
+TEST_SUCCESS="TEST SUCCESS"
+TEST_FAILURE="TEST FAILURE"
+
+function print_log_file() {
+  title=$1
+  file=$2
+
+  if [ -f $file ]; then
+    echo ""
+    echo "----------------------------------------------------------"
+    echo " $title"
+    echo "----------------------------------------------------------"
+    cat $file
+    echo ""
+  fi
+}
+
+function process_case() {
+  file=$1
+  case_no=$2
+
+  if [ -d $file ]; then
+    file=$file/$CONFIG_FILE
+  fi
+
+  if [ ! -f $file ]; then
+    echo "$TEST_FAILURE: case config not found: $file" | tee -a $testResultFile
+    return 1
+  fi
+
+  project_home=`dirname $file`
+  scenario_home=$project_home/target
+  scenario_name=`basename $project_home`
+  log_prefix="[${case_no}/${caseCount}] [$scenario_name]"
+  start_time=$SECONDS
+  echo "$log_prefix Processing : $project_home .."
+
+  # mvn build
+  if [ "$BUILD" == "case" ]; then
+    echo "$log_prefix Building project : $scenario_name .."
+    cd $project_home
+    mvn $BUILD_OPTS &> $project_home/mvn.log
+    result=$?
+    if [ $result -ne 0 ]; then
+      echo "$log_prefix $TEST_FAILURE: Build failure, please check log: $project_home/mvn.log" | tee -a $testResultFile
+      return 1
+    fi
+  fi
+
+  #check build
+  echo "$log_prefix Checking project artifacts .."
+  if [ ! -d "$project_home/target" ]; then
+    echo "$log_prefix $TEST_FAILURE: Missing artifacts" | tee -a $testResultFile
+    return 1
+  fi
+
+  # generate case configuration
+  mkdir -p $scenario_home/logs
+  echo "$log_prefix Generating test case configuration .."
+  config_time=$SECONDS
+  mkdir -p $scenario_home
+  java -Dconfigure.file=$file \
+    -Dscenario.home=$scenario_home \
+    -Dscenario.name=$scenario_name \
+    -Dscenario.version=$DUBBO_VERSION \
+    -Ddebug.mode=$DEBUG \
+    -Ddebug.suspend=$DEBUG_SUSPEND \
+    -jar $test_builder_jar  &> $scenario_home/logs/scenario-builder.log
+  result=$?
+  if [ $result -ne 0 ]; then
+    echo "$log_prefix $TEST_FAILURE: Generate case configuration failure: $scenario_home/logs/scenario-builder.log" | tee -a $testResultFile
+    return 1
+  fi
+
+  # run test
+  echo "$log_prefix Running test case .."
+  running_time=$SECONDS
+  bash $scenario_home/scenario.sh
+  result=$?
+  end_time=$SECONDS
+
+  if [ $result == 0 ]; then
+    echo "$log_prefix $TEST_SUCCESS: total cost $((end_time - start_time)) s" | tee -a $testResultFile
+  else
+    echo "$log_prefix $TEST_FAILURE, please check logs: $scenario_home/logs" | tee -a $testResultFile
+
+    # show test log
+    if [ "$SHOW_ERROR_DETAIL" == "1" ]; then
+      for log_file in $scenario_home/logs/*.log; do
+        print_log_file "$scenario_name : `basename $log_file`" $log_file
+      done
+    fi
+    return 1
+  fi
+}
+
+# start run tests
+testStartTime=$SECONDS
+
+#counter
+allTest=0
+finishedTest=0
+
+while read line
+do
+  allTest=$((allTest + 1))
+  # fork process testcase
+  process_case $line $allTest &
+  sleep 1
+
+  #wait for tests finished
+  delta=$maxForks
+  if [ $allTest == $caseCount ];then
+    delta=0
+  fi
+  while [ $finishedTest -lt $caseCount ] && [ $((allTest - finishedTest)) -ge $delta ]
+  do
+    sleep 1
+    if [ -f $testResultFile ]; then
+      finishedTest=`grep "" -c $testResultFile`
+      # check fail fast
+      if [ "$FAIL_FAST" == "1" ]; then
+        failedTest=`grep "$TEST_FAILURE" -c $testResultFile`
+        if [ $failedTest -ne 0 ]; then
+          echo "Aborting, wait for subprocess finished .."
+          wait
+          echo "----------------------------------------------------------"
+          echo "Test is aborted cause some testcase is failed (fail-fast mode). "
+          echo "Fail tests:"
+          grep "$TEST_FAILURE" $testResultFile
+          echo "----------------------------------------------------------"
+          exit 1
+        fi
+      fi
+    fi
+  done
+
+done < $testListFile
+
+successTest=`grep "$TEST_SUCCESS" -c $testResultFile`
+failedTest=`grep "$TEST_FAILURE" -c $testResultFile`
+
+echo "----------------------------------------------------------"
+echo "Test logs dir: \${project.basedir}/target/logs"
+echo "Test reports dir: \${project.basedir}/target/test-reports"
+echo "Test results: $testResultFile"
+echo "Total cost: $((SECONDS - testStartTime)) seconds"
+echo "All tests count: $caseCount"
+echo "Success tests count: $successTest"
+
+if [ $successTest == $caseCount ]
+then
+   echo "All tests pass"
+   echo "----------------------------------------------------------"
+   exit 0
+else
+   echo "Some tests fail: $failedTest"
+   echo "----------------------------------------------------------"
+   echo "Fail tests:"
+   grep "$TEST_FAILURE" $testResultFile
+   echo "----------------------------------------------------------"
+   exit 1
+fi
+


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org